AsmGrader 0.0.0
Loading...
Searching...
No Matches
asm_function.hpp
Go to the documentation of this file.
1#pragma once
2
12
13#include <fmt/base.h>
14#include <fmt/format.h>
15#include <gsl/narrow>
16#include <gsl/util>
17#include <libassert/assert.hpp>
18#include <range/v3/action/split.hpp>
19#include <range/v3/view/enumerate.hpp>
20
21#include <array>
22#include <concepts>
23#include <cstdint>
24#include <optional>
25#include <span>
26#include <string>
27#include <string_view>
28#include <tuple>
29#include <type_traits>
30#include <utility>
31
32namespace asmgrader {
33
38template <typename Ret, typename... Args>
39class AsmFunctionResult : public Result<Ret>
40{
41public:
42 // NOLINTNEXTLINE(readability-identifier-naming) - rational: don't shadow member variables
43 constexpr AsmFunctionResult(std::tuple<std::decay_t<Args>...> args_, std::string_view function_name_)
44 : args{std::move(args_)}
45 , function_name{function_name_} {}
46
47 template <typename U>
48 void set_result(U&& val) {
49 Result<Ret>::operator=(std::forward<U>(val));
50 }
51
52 std::tuple<std::decay_t<Args>...> args;
53 std::string_view function_name;
54
55 std::string repr(std::span<const inspection::Token> tokens, std::string_view raw_str) const {
56 // auto split_arg_tokens_fn = [open_groupings = std::stack<char>{}](const inspection::Token& tok) mutable {
57 // using inspection::Token::Kind::Grouping;
58 //
59 // // Opening grouping symbol
60 // if (tok.str == "(" || tok.str == "{" || (tok.kind == Grouping && tok.str == "<")) {
61 // open_groupings.push(tok.str.at(0));
62 // }
63 // // Closing grouping symbol
64 // else if (!open_groupings.empty() &&
65 // (tok.str == ")" || tok.str == "}" || (tok.kind == Grouping && tok.str == ">"))) {
66 // // This *should* always be true, but it's not important enough to crash in a release build
67 // DEBUG_ASSERT(open_groupings.top() ==
68 // (std::map<char, char>{{')', '('}, {'}', '{'}, {'>', '<'}}).at(tok.str.at(0)));
69 // open_groupings.pop();
70 // }
71 //
72 // return open_groupings.empty() && tok.str == ",";
73 // };
74
75 // FIXME: This is very much hacked together and will likely break in a lot of edge cases
76
77 // std::array<std::span<const inspection::Token>, sizeof...(Args)> arg_tokens{};
78 // std::array<std::string_view, sizeof...(Args)> arg_raw_strs{};
79
80 // DEBUG_ASSERT(tokens[0].kind == inspection::Token::Kind::Identifier);
81 // DEBUG_ASSERT(tokens[1] == (inspection::Token{.kind = inspection::Token::Kind::Operator, .str = "("}),
82 // tokens[1].kind, tokens[1].str);
83 // DEBUG_ASSERT(tokens.back() == (inspection::Token{.kind = inspection::Token::Kind::Operator, .str = ")"}),
84 // tokens.back().kind, tokens.back().str);
85 //
86 // tokens = tokens.subspan(2);
87 //
88 // for (std::size_t last_start = 0, len = 0, i = 0; const auto& [ti, tok] : tokens | ranges::views::enumerate) {
89 // if (i >= arg_tokens.size()) {
90 // DEBUG_ASSERT(false, i, arg_tokens, tokens);
91 // LOG_WARN("Parsing error for asm function arguments");
92 // }
93 // if (split_arg_tokens_fn(tok) || ti + 1 == tokens.size()) {
94 // arg_tokens.at(i) = std::span{tokens.begin() + gsl::narrow_cast<std::ptrdiff_t>(last_start), len};
95 // auto str_start = tokens[last_start].str.data() - raw_str.data();
96 // arg_raw_strs.at(i) = raw_str.substr(str_start, tok.str.data() - raw_str.data() - str_start);
97 //
98 // len = 0;
99 // last_start = ti + 1;
100 // ++i;
101 // } else {
102 // len++;
103 // }
104 // }
105
106 // nesting is still an issue with literal blocks, but these are guaranteed to be nested (at least not by means
107 // of this function), so using the original strings is ok here.
108 std::array stringized_args = std::apply(
109 []<typename... Ts>(const Ts&... fn_args) { return std::array{stringize::str(fn_args).original...}; }, args);
110
111 return fmt::format("{}({})", function_name, fmt::join(stringized_args, ", "));
112 }
113
114 // using the default `str` implementation for the base Expected type
115};
116
117template <typename T>
119{
120 static_assert(always_false_v<T>, "AsmFunction is not specialized for non-function types!");
121};
122
123namespace detail {
124
125template <typename T>
126concept IsUnwrappable = requires(const T& val) {
127 { val.has_value() } -> std::same_as<bool>;
128 { val.value() };
129};
130
132static_assert(IsUnwrappable<Expected<int>>);
133static_assert(IsUnwrappable<Expected<>>);
134
136template <typename T>
137 requires IsUnwrappable<T>
138using UnwrapInnerT = std::remove_reference_t<decltype(std::declval<T>().value())>;
139
140static_assert(std::same_as<UnwrapInnerT<std::optional<int>>, int>);
141static_assert(std::same_as<UnwrapInnerT<Expected<int>>, int>);
142static_assert(std::same_as<UnwrapInnerT<Expected<>>, void>);
143
144template <typename T>
146
147template <typename T>
148 requires(!IsUnwrappable<T>)
149struct UnwrapInnerOr<T>
150{
151 using type = T;
152};
153
154template <typename T>
155 requires(IsUnwrappable<T>)
157{
159};
160
161template <typename T>
163
164static_assert(std::same_as<UnwrapInnerOrT<std::optional<int>>, int>);
165static_assert(std::same_as<UnwrapInnerOrT<const std::optional<int>>, const int>);
166static_assert(std::same_as<UnwrapInnerOrT<int>, int>);
167static_assert(std::same_as<UnwrapInnerOrT<int&>, int&>);
168
169} // namespace detail
170
171template <typename Ret, typename... Args>
172class AsmFunction<Ret(Args...)>
173{
174public:
175 AsmFunction(Program& prog, std::string name, std::uintptr_t address);
176 AsmFunction(Program& prog, std::string name, ErrorKind resolution_err);
177
178 template <typename... Ts>
179 requires(sizeof...(Ts) == sizeof...(Args)) &&
180 (true && ... && MemoryIOCompatible<detail::UnwrapInnerOrT<Ts>, Args>)
181 AsmFunctionResult<Ret, Ts...> operator()(const Ts&... args) {
182 static_assert((true && ... && std::copyable<std::decay_t<detail::UnwrapInnerOrT<Ts>>>),
183 "All arguments must be copyable");
184 (check_ptr_arg<Ts>(), ...);
185
186 // making copies of args...
187 AsmFunctionResult<Ret, Ts...> res{{args...}, name_};
188
189 if (resolution_err_.has_value()) {
190 res.set_result(*resolution_err_);
191 return res;
192 }
193
194 // try to unwrap any optional / expected / etc.
195 std::optional unwrapped_args = try_unwrap_args(args...);
196
197 if (!unwrapped_args) {
198 res.set_result(ErrorKind::BadArgument);
199 return res;
200 }
201
202 // this is a little messy
203 // 1. resolve the overloaded fn
204 // 2. bind the Program instance and address to the fn ptr
205 // 3. call fn with unwrapped args tuple
206 using FnType = Result<Ret> (Program::*)(decltype(address_), const detail::UnwrapInnerOrT<Ts>&...);
207 const auto& prog_call_fn =
208 std::bind_front(static_cast<FnType>(&Program::call_function<Ret(Args...)>), prog_, address_);
209 auto call_res = std::apply(prog_call_fn, *unwrapped_args);
210
211 res.set_result(std::move(call_res));
212
213 return res;
214 }
215
216 const std::string& get_name() const { return name_; }
217
218private:
219 template <typename T>
220 void check_ptr_arg() {
221 using NormT = std::decay_t<T>;
222 static_assert(!std::is_pointer_v<NormT> && !std::is_array_v<NormT>,
223 "Passing a raw pointer as argument for an asm function, which is probably not what you meant to "
224 "do. See docs on program memory for more info.");
225 }
226
229 template <typename T>
230 bool check_valid_unwrappable(const T& arg) {
231 if constexpr (detail::IsUnwrappable<T>) {
232 return arg.has_value();
233 }
234
235 return true;
236 }
237
247 template <typename... Ts>
248 auto try_unwrap_args(const Ts&... args) -> std::optional<std::tuple<const detail::UnwrapInnerOrT<Ts>&...>> {
249 if ((false || ... || !check_valid_unwrappable(args))) {
250 return std::nullopt;
251 }
252
253 // unwrap a single argument, if it's unwrappable, or return the argument passed if not
254 // will always return a reference, as lifetimes will be valid for this function's intended use-case.
255 auto maybe_unwrap = []<typename T>(const T& arg) -> decltype(auto) {
256 if constexpr (detail::IsUnwrappable<T>) {
257 return arg.value();
258 } else {
259 return arg;
260 }
261 };
262
263 return std::tuple<const detail::UnwrapInnerOrT<Ts>&...>{maybe_unwrap(args)...};
264 }
265
266 Program* prog_;
267
268 std::uintptr_t address_;
269 std::string name_;
270 std::optional<ErrorKind> resolution_err_;
271};
272
273template <typename Ret, typename... Args>
274AsmFunction<Ret(Args...)>::AsmFunction(Program& prog, std::string name, std::uintptr_t address)
275 : prog_{&prog}
276 , address_{address}
277 , name_{std::move(name)} {}
278
279template <typename Ret, typename... Args>
280AsmFunction<Ret(Args...)>::AsmFunction(Program& prog, std::string name, ErrorKind resolution_err)
281 : prog_{&prog}
282 , address_{0x0}
283 , name_{std::move(name)}
284 , resolution_err_{resolution_err} {}
285
286} // namespace asmgrader
287
288template <typename Ret, typename... Ts>
289struct fmt::formatter<::asmgrader::AsmFunctionResult<Ret, Ts...>> : formatter<::asmgrader::Result<Ret>>
290
291{
292};
Transparent wrapper around Result<Ret>, as far as the user is concerned.
Definition asm_function.hpp:40
std::string repr(std::span< const inspection::Token > tokens, std::string_view raw_str) const
Definition asm_function.hpp:55
constexpr AsmFunctionResult(std::tuple< std::decay_t< Args >... > args_, std::string_view function_name_)
Definition asm_function.hpp:43
std::string_view function_name
Definition asm_function.hpp:53
std::tuple< std::decay_t< Args >... > args
Definition asm_function.hpp:52
void set_result(U &&val)
Definition asm_function.hpp:48
const std::string & get_name() const
Definition asm_function.hpp:216
Definition asm_function.hpp:119
std::variant wrapper for a partial implementation of C++23's expected type
Definition expected.hpp:34
Definition program.hpp:32
Result< typename FunctionTraits< Func >::Ret > call_function(std::string_view name, Args &&... args)
Returns the result of the function call, or nullopt if the symbol name was not found or some other er...
Definition program.hpp:146
A trait for types that are compatible in how they are read and written to memory.
Definition concepts.hpp:80
Definition asm_function.hpp:126
UnwrapInnerOr< T >::type UnwrapInnerOrT
Definition asm_function.hpp:162
std::remove_reference_t< decltype(std::declval< T >().value())> UnwrapInnerT
Obtain the inner type of a T satisfying IsUnwrappable (e.g., std::optional or asmgrader::Expected)
Definition asm_function.hpp:138
constexpr const auto & str
str customization point object The return value of this function is guaranteed to be parsable by high...
Definition stringize.hpp:190
Definition asm_buffer.hpp:20
constexpr bool always_false_v
Definition always_false.hpp:16
Expected< T, ErrorKind > Result
Definition error_types.hpp:25
ErrorKind
Definition error_types.hpp:11
@ BadArgument
Bad argument to an AsmFunction. For an unwrappable type with no inner value.
Definition byte_array.hpp:94
See asmgrader::stringize.
T type
Definition asm_function.hpp:151
Definition asm_function.hpp:145