Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
host_functions_tests.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <vector>
3#include <iterator>
4#include <cstdlib>
5#include <fstream>
6#include <string>
7
8#include <catch2/catch.hpp>
9
10#include <sysio/vm/backend.hpp>
11#include "wasm_config.hpp"
12#include "utils.hpp"
13
14using namespace sysio;
15using namespace sysio::vm;
16
17// host functions that are C-style functions
18// wasm hex
19/* Code used to generate test, compile with sysio-cpp v1.6.2 with minor manual edits to remove unneeded imports
20 * extern "C" {
21 struct state_t { float f; int i; };
22 [[sysio::wasm_import]]
23 void c_style_host_function_0();
24 [[sysio::wasm_import]]
25 void c_style_host_function_1(int);
26 [[sysio::wasm_import]]
27 void c_style_host_function_2(int, int);
28 [[sysio::wasm_import]]
29 void c_style_host_function_3(int, float);
30 [[sysio::wasm_import]]
31 void c_style_host_function_4(const state_t&);
32
33 [[sysio::wasm_entry]]
34 void apply(unsigned long long a, unsigned long long b, unsigned long long c) {
35 if (a == 0)
36 c_style_host_function_0();
37 else if (a == 1)
38 c_style_host_function_1((int)b);
39 else if (a == 2)
40 c_style_host_function_2((int)b, (int)c);
41 else if (a == 3)
42 c_style_host_function_3((int)b, *((int*)&c));
43 else if (a == 4) {
44 state_t s = {*((float*)&c), (int)b};
45 c_style_host_function_4(s);
46 }
47 }
48 } */
49
51// no return value and no input parameters
53struct state_t {
54 float f = 0;
55 int i = 0;
56};
66void c_style_host_function_3(int a, float b) {
68}
72
73// Combinations:
74// Member/Free
75// host object/no host object - done
76// parameters: none/bool/int32_t/uint32_t/int64_t/uint64_t/float/double/cv-pointer/cv-reference/two
77// result: void/bool/int32_t/uint32_t/int64_t/uint64_t/float/double/cv-pointer/cv_reference - done
78// call/call_indirect/direct execution - done
79//
80// Things that a host function might do:
81// - call back into wasm
82// - exit
83// - wasm -> native -> wasm -> native -> exit
84// - throw
85// - calling into a different execution context
86// - wasm1/wasm2 mixed -> native -> exit wasm2
87
88
89namespace {
90
91template<typename Host>
92struct cnv : type_converter<Host> {
94 using type_converter<Host>::from_wasm;
95 using type_converter<Host>::to_wasm;
96 template<typename T>
97 auto from_wasm(void* ptr) const -> std::enable_if_t<std::is_pointer_v<T>, T> {
98 return static_cast<T>(ptr);
99 }
100 template<typename T>
101 auto from_wasm(void* ptr) const -> std::enable_if_t<std::is_lvalue_reference_v<T>, T> {
102 return *static_cast<std::remove_reference_t<T>*>(ptr);
103 }
104 template<typename T>
105 auto to_wasm(T*&& ptr) -> const volatile void* {
106 return ptr;
107 }
108 template<typename T>
109 auto to_wasm(T& ref) -> const volatile void* {
110 return &ref;
111 }
112};
113
114template<typename T>
115struct ref {
116 ref() = default;
117 ref(T& arg) : val(&arg) {}
118 operator T&() { return *val; }
119 T* val;
120};
121
122template<typename T>
123using maybe_ref = std::conditional_t<std::is_reference_v<T>, ref<std::remove_reference_t<T>>, T>;
124
125template<typename T>
126maybe_ref<T> global_test_value;
127
128struct static_host_function {
129 template<typename T>
130 static void put(T t) { global_test_value<T> = t; }
131 template<typename T>
132 static T get() { return global_test_value<T>; }
133};
134
135struct member_host_function {
136 template<typename T>
137 void put(T t) { global_test_value<T> = t; }
138 template<typename T>
139 T get() const { return global_test_value<T>; }
140};
141
142}
143
145
149
150template<class Functions, class Host, class Impl>
155
156 static void init_host_functions() {
157 add<bool>("b");
158 add<int32_t>("i32");
159 add<uint32_t>("ui32");
160 add<int64_t>("i64");
161 add<uint64_t>("ui64");
162 add<float>("f32");
163 add<double>("f64");
164 add<char*>("ptr");
165 add<const char*>("cptr");
166 add<volatile char*>("vptr");
168 add<char&>("ref");
169 add<const char&>("cref");
170 add<volatile char&>("vref");
172 }
173
174 template<typename T>
175 static void add(const std::string& name) {
176 rhf_t::template add<&Functions::template put<T>>("env", "put_" + name);
177 rhf_t::template add<&Functions::template get<T>>("env", "get_" + name);
178 }
179 // forwarding functions
180 template<typename... A>
181 auto call_with_return(A&&... a) {
182 if constexpr (std::is_same_v<Host, std::nullptr_t>) {
183 return bkend.call_with_return(static_cast<A&&>(a)...);
184 } else {
185 return bkend.call_with_return(*_host, static_cast<A&&>(a)...);
186 }
187 }
188 template<typename... A>
189 auto call(A&&... a) {
190 if constexpr (std::is_same_v<Host, std::nullptr_t>) {
191 return bkend.call(static_cast<A&&>(a)...);
192 } else {
193 return bkend.call(*_host, static_cast<A&&>(a)...);
194 }
195 }
196 decltype(auto) get_context() { return bkend.get_context(); }
197
202 Host * _host;
203};
204
205const std::vector<std::string> fun_prefixes = { "", "call.", "call_indirect." };
206
207template<typename T>
208std::vector<T> test_values = { 0, 1, std::numeric_limits<T>::min(), std::numeric_limits<T>::max() };
209
210template<>
211std::vector<bool> test_values<bool> = { true, false };
212
214 static int counter = 4;
215 return counter++;
216}
217
218template<typename T, typename B>
219void check_put(B& bkend, const std::string& name) {
220 for(auto fun : fun_prefixes) {
221 fun += "put_" + name;
222 for(const T value : test_values<T>) {
223 bkend.call("env", fun, value);
224 CHECK(global_test_value<T> == value);
225 }
226 }
227}
228
229template<typename T, typename B>
230void check_put_ptr(B& bkend, const std::string& name) {
231 for(auto fun : fun_prefixes) {
232 fun += "put_" + name;
233 int offset = next_ptr_offset();
234 const T value = const_cast<T>(bkend.get_context().linear_memory() + offset);
235 bkend.call("env", fun, value);
236 CHECK(global_test_value<T> == value);
237 }
238}
239
240template<typename T, typename B>
241void check_put_ref(B& bkend, const std::string& name) {
242 for(auto fun : fun_prefixes) {
243 fun += "put_" + name;
244 int offset = next_ptr_offset();
245 const auto value = &const_cast<T>(*(bkend.get_context().linear_memory() + offset));
246 bkend.call("env", fun, value);
247 CHECK(global_test_value<T>.val == value);
248 }
249}
250
251template<typename T, typename B, typename F>
252void check_get(B& bkend, const std::string& name, F getter) {
253 for(auto fun : fun_prefixes) {
254 fun += "get_" + name;
255 for(const T value : test_values<T>) {
256 global_test_value<T> = value;
257 CHECK(getter(bkend.call_with_return("env", fun)) == value);
258 }
259 }
260}
261
262template<typename T, typename B>
263void check_get_ptr(B& bkend, const std::string& name) {
264 for(auto fun : fun_prefixes) {
265 fun += "get_" + name;
266 int offset = next_ptr_offset();
267 global_test_value<T> = const_cast<T>(bkend.get_context().linear_memory() + offset);
268 CHECK(bkend.call_with_return("env", fun)->to_ui32() == offset);
269 }
270}
271
272template<typename T, typename B>
273void check_get_ref(B& bkend, const std::string& name) {
274 for(auto fun : fun_prefixes) {
275 fun += "get_" + name;
276 int offset = next_ptr_offset();
277 global_test_value<T> = const_cast<T>(*(bkend.get_context().linear_memory() + offset));
278 CHECK(bkend.call_with_return("env", fun)->to_ui32() == offset);
279 }
280}
281
282template<class Backend>
283void test_parameters(Backend&& bkend) {
284 for(auto fun : fun_prefixes) {
285 fun += "put_b";
286 bkend.call("env", fun, true);
287 CHECK(global_test_value<bool> == true);
288 bkend.call("env", fun, false);
289 CHECK(global_test_value<bool> == false);
290 // Extra tests for bool:
291 bkend.call("env", fun, 42);
292 CHECK(global_test_value<bool> == true);
293 bkend.call("env", fun, 0x10000);
294 CHECK(global_test_value<bool> == true);
295 }
296
298 check_put<uint32_t>(bkend, "ui32");
300 check_put<uint64_t>(bkend, "ui64");
301 check_put<float>(bkend, "f32");
302 check_put<double>(bkend, "f64");
311}
312
313BACKEND_TEST_CASE( "Test host function parameters", "[host_functions_parameters]" ) {
315 member_host_function mhf;
317}
318
319template<class Backend>
320void test_results(Backend&& bkend) {
321 for(auto fun : {/*"get_b",*/ "call.get_b", "call_indirect.get_b"}) {
322 global_test_value<bool> = false;
323 CHECK(bkend.call_with_return("env", fun)->to_ui32() == 0u);
324 global_test_value<bool> = true;
325 CHECK(bkend.call_with_return("env", fun)->to_ui32() == 1u);
326 }
327
328 check_get<int32_t>(bkend, "i32", [](auto x){ return x->to_i32(); });
329 check_get<uint32_t>(bkend, "ui32", [](auto x){ return x->to_ui32(); });
330 check_get<int64_t>(bkend, "i64", [](auto x){ return x->to_i64(); });
331 check_get<uint64_t>(bkend, "ui64", [](auto x){ return x->to_ui64(); });
332 check_get<float>(bkend, "f32", [](auto x){ return x->to_f32(); });
333 check_get<double>(bkend, "f64", [](auto x){ return x->to_f64(); });
342}
343
344BACKEND_TEST_CASE( "Test host function results", "[host_functions_results]" ) {
346 member_host_function mhf;
348}
349
350BACKEND_TEST_CASE( "Test C-style host function system", "[C-style_host_functions_tests]") {
354 rhf_t::add<&c_style_host_function_0>("env", "c_style_host_function_0");
355 rhf_t::add<&c_style_host_function_1>("env", "c_style_host_function_1");
356 rhf_t::add<&c_style_host_function_2>("env", "c_style_host_function_2");
357 rhf_t::add<&c_style_host_function_3>("env", "c_style_host_function_3");
358 rhf_t::add<&c_style_host_function_4>("env", "c_style_host_function_4");
359
361
362 bkend.call("env", "apply", (uint64_t)0, (uint64_t)0, (uint64_t)0);
364
365 bkend.call("env", "apply", (uint64_t)1, (uint64_t)2, (uint64_t)0);
367
368 bkend.call("env", "apply", (uint64_t)2, (uint64_t)1, (uint64_t)2);
370
371 float f = 2.4f;
372 bkend.call("env", "apply", (uint64_t)3, (uint64_t)2, (uint64_t)bit_cast<uint32_t>(f));
373 CHECK(c_style_host_function_state == 0x40199980);
374
375 bkend.call("env", "apply", (uint64_t)4, (uint64_t)5, (uint64_t)bit_cast<uint32_t>(f));
377}
378
380 static int test(int value) { return value + 42; }
381 static int test2(int value) { return value * 42; }
382};
383
384extern wasm_allocator wa;
385
386BACKEND_TEST_CASE( "Testing host functions", "[host_functions_test]" ) {
391
393
394 auto code = read_wasm( host_wasm );
395 backend_t bkend( code, &wa );
396
397 CHECK(bkend.call_with_return("env", "test", UINT32_C(5))->to_i32() == 49);
398 CHECK(bkend.call_with_return("env", "test.indirect", UINT32_C(5), UINT32_C(0))->to_i32() == 47);
399 CHECK(bkend.call_with_return("env", "test.indirect", UINT32_C(5), UINT32_C(1))->to_i32() == 210);
400 CHECK(bkend.call_with_return("env", "test.indirect", UINT32_C(5), UINT32_C(2))->to_i32() == 49);
401 CHECK_THROWS_AS(bkend.call("env", "test.indirect", UINT32_C(5), UINT32_C(3)), std::exception);
402 CHECK(bkend.call_with_return("env", "test.local-call", UINT32_C(5))->to_i32() == 147);
403}
404
408
410 operator has_stateful_conversion() const { return { value }; }
412};
413
415 static int test(has_stateful_conversion x) { return x.value + 42; }
416 static int test2(has_stateful_conversion x) { return x.value * 42; }
417};
418
419struct stateful_cnv : type_converter<standalone_function_t> {
420 using type_converter::type_converter;
422 template<typename T>
423 auto from_wasm(uint32_t val) const
424 -> std::enable_if_t<std::is_same_v<T, has_stateful_conversion>,
426 return { val };
427 }
428};
429
430BACKEND_TEST_CASE( "Testing stateful ", "[host_functions_stateful_converter]") {
435
437
438 auto code = read_wasm( host_wasm );
439 backend_t bkend( code, &wa );
440
441 CHECK(bkend.call_with_return("env", "test", UINT32_C(5))->to_i32() == 49);
442 CHECK(bkend.call_with_return("env", "test.indirect", UINT32_C(5), UINT32_C(0))->to_i32() == 47);
443 CHECK(bkend.call_with_return("env", "test.indirect", UINT32_C(5), UINT32_C(1))->to_i32() == 210);
444 CHECK(bkend.call_with_return("env", "test.indirect", UINT32_C(5), UINT32_C(2))->to_i32() == 49);
445 CHECK_THROWS_AS(bkend.call("env", "test.indirect", UINT32_C(5), UINT32_C(3)), std::exception);
446 CHECK(bkend.call_with_return("env", "test.local-call", UINT32_C(5))->to_i32() == 147);
447}
448
449// Test overloaded frow_wasm and order of destruction of converters
450
451
452#warning TODO figure out a way to make this work with the new host function system
453
454#if 0
455
456struct has_multi_converter {
457 uint32_t v1;
458 uint32_t v2;
459};
460static std::vector<int> multi_converter_destructor_order;
461struct multi_converter {
462 uint32_t v1;
463 uint32_t v2;
464 operator has_multi_converter() const { return { v1, v2 }; }
465 ~multi_converter() { multi_converter_destructor_order.push_back(v1); }
466 multi_converter(multi_converter&&) = delete;
467};
468
469struct host_functions_multi_converter {
470 static unsigned test(has_multi_converter x, has_multi_converter y) {
471 return 1*x.v1 + 10*x.v2 + 100*y.v1 + 1000*y.v2;
472 }
473};
474
475struct multi_cnv : type_converter<standalone_function_t> {
476 using type_converter::type_converter;
478 template<typename T>
479 auto from_wasm(uint32_t v0, uint32_t v1) const
480 -> std::enable_if_t<std::is_same_v<T, has_multi_converter>,
481 multi_converter> {
482 return { v0, v1 };
483 }
484};
485
486BACKEND_TEST_CASE( "Testing multi ", "[host_functions_multi_converter]") {
487 host_functions_multi_converter host;
490
492
493 /*
494 (module
495 (func (export "test") (import "host" "test") (param i32 i32 i32 i32) (result i32))
496 )
497 */
498
499 wasm_code code = {
500 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x09, 0x01, 0x60,
501 0x04, 0x7f, 0x7f, 0x7f, 0x7f, 0x01, 0x7f, 0x02, 0x0d, 0x01, 0x04, 0x68,
502 0x6f, 0x73, 0x74, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x07, 0x08,
503 0x01, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00
504 };
505 backend_t bkend( code, &wa );
506
507 multi_converter_destructor_order.clear();
508 CHECK(bkend.call_with_return("env", "test", UINT32_C(1), UINT32_C(2), UINT32_C(3), UINT32_C(4))->to_i32() == 4321);
509 CHECK(multi_converter_destructor_order == std::vector{1, 3});
510}
511
512#endif
513
515
517 static int test(int) { throw test_exception{}; }
518};
519
520BACKEND_TEST_CASE( "Testing throwing host functions", "[host_functions_throw_test]" ) {
525
527
528 auto code = read_wasm( host_wasm );
529 backend_t bkend( code, &wa );;
530
531 CHECK_THROWS_AS(bkend.call("env", "test", UINT32_C(2)), test_exception);
532}
533
534template<typename Impl>
537 int test(int) { context->exit(); return 0; }
538};
539
540BACKEND_TEST_CASE( "Testing exiting host functions", "[host_functions_exit_test]" ) {
542 rhf_t::template add<&host_functions_exit<TestType>::test>("host", "test");
543 rhf_t::template add<&host_functions_exit<TestType>::test>("host", "test2");
544
546
547 auto code = read_wasm( host_wasm );
548 backend_t bkend( code, &wa );
550
551 CHECK(!bkend.call_with_return(host, "env", "test", UINT32_C(2)));
552}
std::string name
bool call(host_t *host, uint32_t func_index, Args... args)
Definition backend.hpp:148
auto & get_context()
Definition backend.hpp:250
auto call_with_return(host_t &host, const std::string_view &mod, const std::string_view &func, Args... args)
Definition backend.hpp:178
module & get_module()
Definition backend.hpp:248
#define CHECK(cond)
Definition util.h:80
void put()
Definition gen_code.cpp:234
backend_t bkend(hello_wasm, ehm, &wa)
void check_put(B &bkend, const std::string &name)
int next_ptr_offset()
void check_get_ptr(B &bkend, const std::string &name)
void check_put_ref(B &bkend, const std::string &name)
wasm_allocator wa
Definition main.cpp:10
void c_style_host_function_0()
const std::vector< std::string > fun_prefixes
void c_style_host_function_4(const state_t &ss)
std::vector< T > test_values
wasm_code host_functions_tests_1_code
void test_parameters(Backend &&bkend)
std::vector< bool > test_values< bool >
void c_style_host_function_3(int a, float b)
void check_get(B &bkend, const std::string &name, F getter)
void test_results(Backend &&bkend)
void check_get_ref(B &bkend, const std::string &name)
void c_style_host_function_1(int s)
int c_style_host_function_state
void c_style_host_function_2(int a, int b)
void check_put_ptr(B &bkend, const std::string &name)
std::vector< uint8_t > host_functions_test_0_wasm
unsigned char host_functions_tests_1_wasm[]
#define CHECK_THROWS_AS(expr, exceptionType)
Definition catch.hpp:203
uint64_t y
Definition sha3.cpp:34
basic_type_converter< sysio::vm::execution_interface > type_converter
Definition common.hpp:116
std::vector< uint8_t > wasm_code
Definition types.hpp:147
std::vector< uint8_t > read_wasm(const std::string &fname)
Definition utils.hpp:30
@ test
Unit testing utility error code.
Definition error.hpp:96
#define value
Definition pkcs11.h:157
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
Definition pointer.h:1181
#define T(meth, val, expected)
schedule config_dir_name data_dir_name p2p_port http_port file_size name host(p2p_endpoint)) FC_REFLECT(tn_node_def
#define UINT32_C(val)
Definition stdint.h:283
unsigned int uint32_t
Definition stdint.h:126
unsigned __int64 uint64_t
Definition stdint.h:136
auto from_wasm(wasm_ptr_t ptr, wasm_size_t len, tag< T >={}) const -> std::enable_if_t< is_span_type_v< T >, T >
Impl::template context< registered_host_functions< host_functions_exit > > * context
static int test(has_stateful_conversion x)
static int test2(has_stateful_conversion x)
decltype(auto) get_context()
static void init_host_functions()
auto call_with_return(A &&... a)
static void add(const std::string &name)
auto call(A &&... a)
static int test2(int value)
static int test(int value)
auto from_wasm(uint32_t val) const -> std::enable_if_t< std::is_same_v< T, has_stateful_conversion >, stateful_conversion >
auto from_wasm(vm::wasm_ptr_t ptr) const -> std::enable_if_t< std::is_pointer_v< T >, vm::argument_proxy< T > >
Definition common.hpp:88
Immutable except for fc::from_variant.
Definition name.hpp:43
static void add(const std::string &mod, const std::string &name)
uint32_t to_wasm(bool &&value)
auto from_wasm(wasm_ptr_t ptr, wasm_size_t len, tag< T >={}) const -> std::enable_if_t< is_span_type_v< T >, T >
#define BACKEND_TEST_CASE(name, tags)
Definition utils.hpp:59
T bit_cast(const U &u)
Definition utils.hpp:35
char * s