From 34c8e9548b1a3f473bb219fc9684496ff95de108 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 18 Sep 2020 12:38:35 +0200 Subject: [PATCH 1/3] ci: add capi_test.cpp to wat2wasm4cpp check --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index 510bf0ef5..4aa9e9b5d 100644 --- a/circle.yml +++ b/circle.yml @@ -265,6 +265,7 @@ jobs: command: | export PATH=$PATH:~/bin ./wat2wasm4cpp.py test/unittests/api_test.cpp + ./wat2wasm4cpp.py test/unittests/capi_test.cpp ./wat2wasm4cpp.py test/unittests/end_to_end_test.cpp ./wat2wasm4cpp.py test/unittests/execute_call_test.cpp ./wat2wasm4cpp.py test/unittests/execute_control_test.cpp From 06e0f20164687c8274e2f1f914caa602fe4b8d9d Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 5 Oct 2020 12:47:08 +0200 Subject: [PATCH 2/3] capi: Add functions for parse, instantiate, execute --- include/fizzy/fizzy.h | 78 ++++++++++++ lib/fizzy/capi.cpp | 129 ++++++++++++++++++++ test/unittests/capi_test.cpp | 225 +++++++++++++++++++++++++++++++++++ 3 files changed, 432 insertions(+) diff --git a/include/fizzy/fizzy.h b/include/fizzy/fizzy.h index d1dc720bd..285e46b75 100644 --- a/include/fizzy/fizzy.h +++ b/include/fizzy/fizzy.h @@ -11,8 +11,86 @@ extern "C" { #endif +/// The opaque data type representing a module. +struct FizzyModule; + +/// The opaque data type representing an instance (instantiated module). +struct FizzyInstance; + +/// The data type representing numeric values. +/// +/// i64 member is used to represent values of both i32 and i64 type. +union FizzyValue +{ + uint64_t i64; + float f32; + double f64; +}; + +/// Result of execution of a function. +typedef struct FizzyExecutionResult +{ + /// Whether execution ended with a trap. + bool trapped; + /// Whether function returned a value. Valid only if trapped == false + bool has_value; + /// Value returned from a function. Valid only if has_value == true + union FizzyValue value; +} FizzyExecutionResult; + + +/// Pointer to external function. +/// +/// @param context Opaque pointer to execution context. +/// @param instance Pointer to module instance. +/// @param args Pointer to the argument array. +/// @param args_size Size of the argument array. +/// @param depth Call stack depth. +typedef struct FizzyExecutionResult (*FizzyExternalFn)(void* context, + struct FizzyInstance* instance, const union FizzyValue* args, size_t args_size, int depth); + +/// External function. +typedef struct FizzyExternalFunction +{ + // TODO function type + + /// Pointer to function. + FizzyExternalFn function; + /// Opaque pointer to execution context, that will be passed to function. + void* context; +} FizzyExternalFunction; + +/// Validate binary module. bool fizzy_validate(const uint8_t* wasm_binary, size_t wasm_binary_size); +/// Parse binary module. +struct FizzyModule* fizzy_parse(const uint8_t* wasm_binary, size_t wasm_binary_size); + +/// Free resources associated with the module. +/// +/// Should be called unless @p module was passed to fizzy_instantiate. +void fizzy_free_module(struct FizzyModule* module); + +/// Instantiate a module. +/// Takes ownership of module, i.e. @p module is invalidated after this call. +/// +/// @param module Pointer to module. +/// @param imported_functions Pointer to the imported function array. +/// @param imported_functions_size Size of the imported function array. +struct FizzyInstance* fizzy_instantiate(struct FizzyModule* module, + const struct FizzyExternalFunction* imported_functions, size_t imported_functions_size); + +/// Free resources associated with the instance. +void fizzy_free_instance(struct FizzyInstance* instance); + +/// Execute module function. +/// +/// @param instance Pointer to module instance. +/// @param args Pointer to the argument array. +/// @param depth Call stack depth. +FizzyExecutionResult fizzy_execute( + struct FizzyInstance* instance, uint32_t func_idx, const union FizzyValue* args, int depth); + #ifdef __cplusplus } #endif diff --git a/lib/fizzy/capi.cpp b/lib/fizzy/capi.cpp index 8f6bb719e..6f322ecfc 100644 --- a/lib/fizzy/capi.cpp +++ b/lib/fizzy/capi.cpp @@ -2,10 +2,76 @@ // Copyright 2020 The Fizzy Authors. // SPDX-License-Identifier: Apache-2.0 +#include "cxx20/bit.hpp" +#include "execute.hpp" +#include "instantiate.hpp" #include "parser.hpp" #include +#include + +namespace +{ +inline FizzyValue wrap(fizzy::Value value) noexcept +{ + return fizzy::bit_cast(value); +} + +inline fizzy::Value unwrap(FizzyValue value) noexcept +{ + return fizzy::bit_cast(value); +} + +inline const FizzyValue* wrap(const fizzy::Value* values) noexcept +{ + return reinterpret_cast(values); +} + +inline const fizzy::Value* unwrap(const FizzyValue* values) noexcept +{ + return reinterpret_cast(values); +} + +inline FizzyInstance* wrap(fizzy::Instance* instance) noexcept +{ + return reinterpret_cast(instance); +} + +inline fizzy::Instance* unwrap(FizzyInstance* instance) noexcept +{ + return reinterpret_cast(instance); +} + +inline FizzyExecutionResult wrap(const fizzy::ExecutionResult& result) noexcept +{ + return {result.trapped, result.has_value, wrap(result.value)}; +} + +inline fizzy::ExecutionResult unwrap(FizzyExecutionResult result) noexcept +{ + if (result.trapped) + return fizzy::Trap; + else if (!result.has_value) + return fizzy::Void; + else + return unwrap(result.value); +} + +inline auto unwrap(FizzyExternalFn func, void* context) noexcept +{ + return [func, context](fizzy::Instance& instance, fizzy::span args, + int depth) noexcept -> fizzy::ExecutionResult { + const auto result = func(context, wrap(&instance), wrap(args.data()), args.size(), depth); + return unwrap(result); + }; +} +} // namespace extern "C" { +struct FizzyModule +{ + fizzy::Module module; +}; + bool fizzy_validate(const uint8_t* wasm_binary, size_t wasm_binary_size) { try @@ -18,4 +84,67 @@ bool fizzy_validate(const uint8_t* wasm_binary, size_t wasm_binary_size) return false; } } + +FizzyModule* fizzy_parse(const uint8_t* wasm_binary, size_t wasm_binary_size) +{ + try + { + auto cmodule = std::make_unique(); + cmodule->module = fizzy::parse({wasm_binary, wasm_binary_size}); + return cmodule.release(); + } + catch (...) + { + return nullptr; + } +} + +void fizzy_free_module(FizzyModule* module) +{ + delete module; +} + +FizzyInstance* fizzy_instantiate(FizzyModule* module, + const FizzyExternalFunction* imported_functions, size_t imported_functions_size) +{ + try + { + // Convert fizzy_external_function to fizzy::ExternalFunction + std::vector functions(imported_functions_size); + for (size_t imported_func_idx = 0; imported_func_idx < imported_functions_size; + ++imported_func_idx) + { + const auto& cfunc = imported_functions[imported_func_idx]; + + auto func = unwrap(cfunc.function, cfunc.context); + // TODO get type from input array + auto func_type = module->module.imported_function_types[imported_func_idx]; + + functions[imported_func_idx] = + fizzy::ExternalFunction{std::move(func), std::move(func_type)}; + } + + auto instance = fizzy::instantiate(std::move(module->module), std::move(functions)); + + fizzy_free_module(module); + return wrap(instance.release()); + } + catch (...) + { + fizzy_free_module(module); + return nullptr; + } +} + +void fizzy_free_instance(FizzyInstance* instance) +{ + delete unwrap(instance); +} + +FizzyExecutionResult fizzy_execute( + FizzyInstance* instance, uint32_t func_idx, const FizzyValue* args, int depth) +{ + const auto result = fizzy::execute(*unwrap(instance), func_idx, unwrap(args), depth); + return wrap(result); +} } diff --git a/test/unittests/capi_test.cpp b/test/unittests/capi_test.cpp index b5cc77a42..8360961ab 100644 --- a/test/unittests/capi_test.cpp +++ b/test/unittests/capi_test.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +using namespace fizzy::test; TEST(capi, validate) { @@ -12,3 +16,224 @@ TEST(capi, validate) wasm_prefix[7] = 1; EXPECT_FALSE(fizzy_validate(wasm_prefix, sizeof(wasm_prefix))); } + +TEST(capi, parse) +{ + uint8_t wasm_prefix[]{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}; + auto module = fizzy_parse(wasm_prefix, sizeof(wasm_prefix)); + EXPECT_NE(module, nullptr); + fizzy_free_module(module); + wasm_prefix[7] = 1; + EXPECT_EQ(fizzy_parse(wasm_prefix, sizeof(wasm_prefix)), nullptr); +} + +TEST(capi, instantiate) +{ + uint8_t wasm_prefix[]{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}; + auto module = fizzy_parse(wasm_prefix, sizeof(wasm_prefix)); + EXPECT_NE(module, nullptr); + + auto instance = fizzy_instantiate(module, nullptr, 0); + EXPECT_NE(instance, nullptr); + + fizzy_free_instance(instance); +} + +TEST(capi, instantiate_imported_function) +{ + /* wat2wasm + (func (import "mod1" "foo1") (result i32)) + */ + const auto wasm = from_hex("0061736d010000000105016000017f020d01046d6f643104666f6f310000"); + auto module = fizzy_parse(wasm.data(), wasm.size()); + EXPECT_NE(module, nullptr); + + EXPECT_EQ(fizzy_instantiate(module, nullptr, 0), nullptr); + + module = fizzy_parse(wasm.data(), wasm.size()); + EXPECT_NE(module, nullptr); + + FizzyExternalFunction host_funcs[] = { + {[](void*, FizzyInstance*, const FizzyValue*, size_t, int) { + return FizzyExecutionResult{false, true, {42}}; + }, + nullptr}}; + + auto instance = fizzy_instantiate(module, host_funcs, 1); + EXPECT_NE(instance, nullptr); + + fizzy_free_instance(instance); +} + +TEST(capi, execute) +{ + /* wat2wasm + (func) + (func (result i32) i32.const 42) + (func (param i32 i32) (result i32) + (i32.div_u (local.get 0) (local.get 1)) + ) + (func unreachable) + */ + const auto wasm = from_hex( + "0061736d01000000010e036000006000017f60027f7f017f030504000102000a150402000b0400412a0b070020" + "0020016e0b0300000b"); + + auto module = fizzy_parse(wasm.data(), wasm.size()); + EXPECT_NE(module, nullptr); + + auto instance = fizzy_instantiate(module, nullptr, 0); + EXPECT_NE(instance, nullptr); + + EXPECT_THAT(fizzy_execute(instance, 0, nullptr, 0), Result()); + EXPECT_THAT(fizzy_execute(instance, 1, nullptr, 0), Result(42)); + FizzyValue args[] = {{42}, {2}}; + EXPECT_THAT(fizzy_execute(instance, 2, args, 0), Result(21)); + EXPECT_THAT(fizzy_execute(instance, 3, nullptr, 0), Traps()); + + fizzy_free_instance(instance); +} + +TEST(capi, execute_with_host_function) +{ + /* wat2wasm + (func (import "mod1" "foo1") (result i32)) + (func (import "mod1" "foo2") (param i32 i32) (result i32)) + */ + const auto wasm = from_hex( + "0061736d01000000010b026000017f60027f7f017f021902046d6f643104666f6f310000046d6f643104666f6f" + "320001"); + auto module = fizzy_parse(wasm.data(), wasm.size()); + EXPECT_NE(module, nullptr); + + FizzyExternalFunction host_funcs[] = { + {[](void*, FizzyInstance*, const FizzyValue*, size_t, int) { + return FizzyExecutionResult{false, true, {42}}; + }, + nullptr}, + {[](void*, FizzyInstance*, const FizzyValue* args, size_t, int) { + return FizzyExecutionResult{false, true, {args[0].i64 / args[1].i64}}; + }, + nullptr}}; + + auto instance = fizzy_instantiate(module, host_funcs, 2); + EXPECT_NE(instance, nullptr); + + EXPECT_THAT(fizzy_execute(instance, 0, nullptr, 0), Result(42)); + + FizzyValue args[] = {{42}, {2}}; + EXPECT_THAT(fizzy_execute(instance, 1, args, 0), Result(21)); + + fizzy_free_instance(instance); +} + +TEST(capi, imported_function_traps) +{ + /* wat2wasm + (func (import "m" "foo") (result i32)) + (func (result i32) + call 0 + ) + */ + const auto wasm = + from_hex("0061736d010000000105016000017f020901016d03666f6f0000030201000a0601040010000b"); + auto module = fizzy_parse(wasm.data(), wasm.size()); + EXPECT_NE(module, nullptr); + + FizzyExternalFunction host_funcs[] = { + {[](void*, FizzyInstance*, const FizzyValue*, size_t, int) { + return FizzyExecutionResult{true, false, {}}; + }, + nullptr}}; + + auto instance = fizzy_instantiate(module, host_funcs, 1); + EXPECT_NE(instance, nullptr); + + EXPECT_THAT(fizzy_execute(instance, 1, nullptr, 0), Traps()); + + fizzy_free_instance(instance); +} + +TEST(capi, imported_function_void) +{ + /* wat2wasm + (func (import "m" "foo")) + (func + call 0 + ) + */ + const auto wasm = + from_hex("0061736d01000000010401600000020901016d03666f6f0000030201000a0601040010000b"); + auto module = fizzy_parse(wasm.data(), wasm.size()); + EXPECT_NE(module, nullptr); + + bool called = false; + FizzyExternalFunction host_funcs[] = { + {[](void* context, FizzyInstance*, const FizzyValue*, size_t, int) { + *static_cast(context) = true; + return FizzyExecutionResult{false, false, {}}; + }, + &called}}; + + auto instance = fizzy_instantiate(module, host_funcs, 1); + EXPECT_NE(instance, nullptr); + + EXPECT_THAT(fizzy_execute(instance, 1, nullptr, 0), Result()); + EXPECT_TRUE(called); + + fizzy_free_instance(instance); +} + +TEST(capi, imported_function_from_another_module) +{ + /* wat2wasm + (module + (func $sub (param $lhs i32) (param $rhs i32) (result i32) + get_local $lhs + get_local $rhs + i32.sub) + (export "sub" (func $sub)) + ) + */ + const auto bin1 = from_hex( + "0061736d0100000001070160027f7f017f030201000707010373756200000a09010700200020016b0b"); + auto module1 = fizzy_parse(bin1.data(), bin1.size()); + EXPECT_NE(module1, nullptr); + auto instance1 = fizzy_instantiate(module1, nullptr, 0); + EXPECT_NE(instance1, nullptr); + + /* wat2wasm + (module + (func $sub (import "m1" "sub") (param $lhs i32) (param $rhs i32) (result i32)) + + (func $main (param i32) (param i32) (result i32) + get_local 0 + get_local 1 + call $sub + ) + ) + */ + const auto bin2 = from_hex( + "0061736d0100000001070160027f7f017f020a01026d31037375620000030201000a0a0108002000200110000" + "b"); + auto module2 = fizzy_parse(bin2.data(), bin2.size()); + EXPECT_NE(module2, nullptr); + + // TODO fizzy_find_exported_function + + auto sub = [](void* context, FizzyInstance*, const FizzyValue* args, size_t, + int depth) -> FizzyExecutionResult { + auto* called_instance = static_cast(context); + return fizzy_execute(called_instance, 0, args, depth + 1); + }; + FizzyExternalFunction host_funcs[] = {{sub, instance1}}; + + auto instance2 = fizzy_instantiate(module2, host_funcs, 1); + EXPECT_NE(instance2, nullptr); + + FizzyValue args[] = {{44}, {2}}; + EXPECT_THAT(fizzy_execute(instance2, 1, args, 0), Result(42)); + + fizzy_free_instance(instance2); + fizzy_free_instance(instance1); +} From 2a3d5c7a47420438450f4f12a908d0429f726e00 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Mon, 5 Oct 2020 13:17:00 +0200 Subject: [PATCH 3/3] test: compilation of fizzy_parse, fizzy_instantiate, fizzy_execute --- test/compilation/compilation_test.c | 83 +++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/test/compilation/compilation_test.c b/test/compilation/compilation_test.c index 7e5a84601..7f1fa1329 100644 --- a/test/compilation/compilation_test.c +++ b/test/compilation/compilation_test.c @@ -4,9 +4,86 @@ #include -bool dummy(void); +bool validate(const uint8_t* binary, size_t size); +bool parse(const uint8_t* binary, size_t size); +bool instantiate(const uint8_t* binary, size_t size); +struct FizzyExecutionResult dummy_host_func(void* context, struct FizzyInstance* instance, + const union FizzyValue* args, size_t args_size, int depth); +bool instantiate_with_host_func(const uint8_t* binary, size_t size); +bool execute(const uint8_t* binary, size_t size); -bool dummy() +bool validate(const uint8_t* binary, size_t size) { - return fizzy_validate(NULL, 0); + return fizzy_validate(binary, size); +} + +bool parse(const uint8_t* binary, size_t size) +{ + struct FizzyModule* module = fizzy_parse(binary, size); + if (!module) + return false; + + fizzy_free_module(module); + return true; +} + +bool instantiate(const uint8_t* binary, size_t size) +{ + struct FizzyModule* module = fizzy_parse(binary, size); + if (!module) + return false; + + struct FizzyInstance* instance = fizzy_instantiate(module, NULL, 0); + if (!instance) + return false; + + fizzy_free_instance(instance); + return true; +} + +struct FizzyExecutionResult dummy_host_func(void* context, struct FizzyInstance* instance, + const union FizzyValue* args, size_t args_size, int depth) +{ + (void)context; + (void)instance; + (void)args; + (void)args_size; + (void)depth; + struct FizzyExecutionResult res = {true, false, {0}}; + return res; +} + +bool instantiate_with_host_func(const uint8_t* binary, size_t size) +{ + struct FizzyModule* module = fizzy_parse(binary, size); + if (!module) + return false; + + FizzyExternalFunction host_funcs[] = {{dummy_host_func, NULL}}; + + struct FizzyInstance* instance = fizzy_instantiate(module, host_funcs, 1); + if (!instance) + return false; + + fizzy_free_instance(instance); + return true; +} + +bool execute(const uint8_t* binary, size_t size) +{ + struct FizzyModule* module = fizzy_parse(binary, size); + if (!module) + return false; + + struct FizzyInstance* instance = fizzy_instantiate(module, NULL, 0); + if (!instance) + return false; + + fizzy_execute(instance, 0, NULL, 0); + + union FizzyValue args[] = {{1}, {2}}; + fizzy_execute(instance, 1, args, 0); + + fizzy_free_instance(instance); + return true; }