From 34bcd69ff07063b2fbcfefb8fd6b862cb9a8fba0 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 7 Oct 2020 14:58:04 +0200 Subject: [PATCH] capi: Instantiate with resolving host functions --- include/fizzy/fizzy.h | 27 +++++++++ lib/fizzy/capi.cpp | 45 +++++++++++++++ test/unittests/capi_test.cpp | 105 +++++++++++++++++++++++++++++++++-- 3 files changed, 173 insertions(+), 4 deletions(-) diff --git a/include/fizzy/fizzy.h b/include/fizzy/fizzy.h index 921486f1c..5fa162775 100644 --- a/include/fizzy/fizzy.h +++ b/include/fizzy/fizzy.h @@ -80,6 +80,17 @@ typedef struct FizzyExternalFunction void* context; } FizzyExternalFunction; +/// Imported function. +typedef struct FizzyImportedFunction +{ + /// Module name. NULL-terminated string. Cannot be NULL. + const char* module; + /// Function name. NULL-terminated string. Cannot be NULL. + const char* name; + /// External function, defining its type, pointer to function and context for calling it. + FizzyExternalFunction external_function; +} FizzyImportedFunction; + /// Validate binary module. bool fizzy_validate(const uint8_t* wasm_binary, size_t wasm_binary_size); @@ -130,6 +141,22 @@ bool fizzy_find_exported_function( FizzyInstance* fizzy_instantiate(const FizzyModule* module, const FizzyExternalFunction* imported_functions, size_t imported_functions_size); +/// Instantiate a module resolving imported functions. +/// 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. Can be NULL iff +/// imported_functions_size equals 0. +/// @param imported_functions_size Size of the imported function array. Can be zero. +/// @returns non-NULL pointer to instance in case of success, NULL otherwise. +/// +/// @note +/// Functions in @a imported_functions are allowed to be in any order and allowed to include some +/// functions not required by instantiated module. +/// Functions are matched to module's imports based on their module and name strings. +FizzyInstance* fizzy_resolve_instantiate(const FizzyModule* module, + const FizzyImportedFunction* imported_functions, size_t imported_functions_size); + /// Free resources associated with the instance. /// If passed pointer is NULL, has no effect. void fizzy_free_instance(FizzyInstance* instance); diff --git a/lib/fizzy/capi.cpp b/lib/fizzy/capi.cpp index 87432ca62..1f7af9086 100644 --- a/lib/fizzy/capi.cpp +++ b/lib/fizzy/capi.cpp @@ -115,6 +115,28 @@ inline fizzy::ExternalFunction unwrap(const FizzyExternalFunction& external_func return fizzy::ExternalFunction{ unwrap(external_func.function, external_func.context), unwrap(external_func.type)}; } + +inline fizzy::ImportedFunction unwrap(const FizzyImportedFunction& c_imported_func) +{ + fizzy::ImportedFunction imported_func; + imported_func.module = + c_imported_func.module ? std::string{c_imported_func.module} : std::string{}; + imported_func.name = c_imported_func.name ? std::string{c_imported_func.name} : std::string{}; + + const auto& c_type = c_imported_func.external_function.type; + fizzy::ValType (*unwrap_valtype_fn)(FizzyValueType value) = &unwrap; + std::transform(c_type.inputs, c_type.inputs + c_type.inputs_size, imported_func.inputs.begin(), + unwrap_valtype_fn); + imported_func.output = c_type.output == FizzyValueTypeVoid ? + std::nullopt : + std::make_optional(unwrap(c_type.output)); + + imported_func.function = unwrap( + c_imported_func.external_function.function, c_imported_func.external_function.context); + + return imported_func; +} + } // namespace extern "C" { @@ -186,6 +208,29 @@ FizzyInstance* fizzy_instantiate(const FizzyModule* module, } } +FizzyInstance* fizzy_resolve_instantiate(const FizzyModule* c_module, + const FizzyImportedFunction* c_imported_functions, size_t imported_functions_size) +{ + try + { + std::vector imported_functions(imported_functions_size); + fizzy::ImportedFunction (*unwrap_imported_func_fn)(const FizzyImportedFunction&) = &unwrap; + std::transform(c_imported_functions, c_imported_functions + imported_functions_size, + imported_functions.begin(), unwrap_imported_func_fn); + + std::unique_ptr module{unwrap(c_module)}; + auto resolved_imports = fizzy::resolve_imported_functions(*module, imported_functions); + + auto instance = fizzy::instantiate(std::move(module), std::move(resolved_imports)); + + return wrap(instance.release()); + } + catch (...) + { + return nullptr; + } +} + void fizzy_free_instance(FizzyInstance* instance) { delete unwrap(instance); diff --git a/test/unittests/capi_test.cpp b/test/unittests/capi_test.cpp index 579c6634b..61a32fa6d 100644 --- a/test/unittests/capi_test.cpp +++ b/test/unittests/capi_test.cpp @@ -136,6 +136,99 @@ TEST(capi, instantiate_imported_function) fizzy_free_instance(instance); } +TEST(capi, resolve_instantiate_no_imports) +{ + /* wat2wasm + (module) + */ + const auto wasm = from_hex("0061736d01000000"); + auto module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + + auto instance = fizzy_resolve_instantiate(module, nullptr, 0); + EXPECT_NE(instance, nullptr); + + fizzy_free_instance(instance); + + module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + + FizzyImportedFunction host_funcs[] = {{"mod", "foo", + {{FizzyValueTypeVoid, nullptr, 0}, + [](void*, FizzyInstance*, const FizzyValue*, int) { return FizzyExecutionResult{}; }, + nullptr}}}; + + instance = fizzy_resolve_instantiate(module, host_funcs, 1); + EXPECT_NE(instance, nullptr); + + fizzy_free_instance(instance); +} + +TEST(capi, resolve_instantiate) +{ + /* wat2wasm + (func (import "mod1" "foo1") (result i32)) + (func (import "mod1" "foo2") (result i64)) + (func (import "mod2" "foo1") (result f32)) + (func (import "mod2" "foo2") (result f64)) + */ + const auto wasm = from_hex( + "0061736d010000000111046000017f6000017e6000017d6000017c023104046d6f643104666f6f310000046d6f" + "643104666f6f320001046d6f643204666f6f310002046d6f643204666f6f320003"); + auto module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + + EXPECT_EQ(fizzy_instantiate(module, nullptr, 0), nullptr); + + module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + + FizzyExternalFn host_fn = [](void* context, FizzyInstance*, const FizzyValue*, int) { + return FizzyExecutionResult{true, false, *static_cast(context)}; + }; + + FizzyValue result_int{42}; + FizzyExternalFunction mod1foo1 = {{FizzyValueTypeI32, nullptr, 0}, host_fn, &result_int}; + FizzyExternalFunction mod1foo2 = {{FizzyValueTypeI64, nullptr, 0}, host_fn, &result_int}; + FizzyValue result_f32; + result_f32.f32 = 42; + FizzyExternalFunction mod2foo1 = {{FizzyValueTypeF32, nullptr, 0}, host_fn, &result_f32}; + FizzyValue result_f64; + result_f64.f64 = 42; + FizzyExternalFunction mod2foo2 = {{FizzyValueTypeF64, nullptr, 0}, host_fn, &result_f64}; + + FizzyImportedFunction host_funcs[] = {{"mod1", "foo1", mod1foo1}, {"mod1", "foo2", mod1foo2}, + {"mod2", "foo1", mod2foo1}, {"mod2", "foo2", mod2foo2}}; + + auto instance = fizzy_resolve_instantiate(module, host_funcs, 4); + EXPECT_NE(instance, nullptr); + fizzy_free_instance(instance); + + // reordered functions + module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + FizzyImportedFunction host_funcs_reordered[] = {{"mod1", "foo2", mod1foo2}, + {"mod2", "foo1", mod2foo1}, {"mod2", "foo2", mod2foo2}, {"mod1", "foo1", mod1foo1}}; + instance = fizzy_resolve_instantiate(module, host_funcs_reordered, 4); + EXPECT_NE(instance, nullptr); + fizzy_free_instance(instance); + + // extra functions + module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + FizzyImportedFunction host_funcs_extra[] = {{"mod1", "foo1", mod1foo1}, + {"mod1", "foo2", mod1foo2}, {"mod2", "foo1", mod2foo1}, {"mod2", "foo2", mod2foo2}, + {"mod3", "foo1", mod1foo1}}; + instance = fizzy_resolve_instantiate(module, host_funcs_extra, 4); + EXPECT_NE(instance, nullptr); + fizzy_free_instance(instance); + + // not enough functions + module = fizzy_parse(wasm.data(), wasm.size()); + ASSERT_NE(module, nullptr); + EXPECT_EQ(fizzy_resolve_instantiate(module, host_funcs, 3), nullptr); +} + TEST(capi, free_instance_null) { fizzy_free_instance(nullptr); @@ -190,7 +283,8 @@ TEST(capi, memory_access) ) */ const auto wasm = from_hex( - "0061736d010000000105016000017f0302010005030100010a0901070041002802000b0b08010041010b02112" + "0061736d010000000105016000017f0302010005030100010a0901070041002802000b0b08010041010b02" + "112" "2"); auto module = fizzy_parse(wasm.data(), wasm.size()); ASSERT_NE(module, nullptr); @@ -237,7 +331,8 @@ TEST(capi, execute) (func unreachable) */ const auto wasm = from_hex( - "0061736d01000000010e036000006000017f60027f7f017f030504000102000a150402000b0400412a0b070020" + "0061736d01000000010e036000006000017f60027f7f017f030504000102000a150402000b0400412a0b07" + "0020" "0020016e0b0300000b"); auto module = fizzy_parse(wasm.data(), wasm.size()); @@ -262,7 +357,8 @@ TEST(capi, execute_with_host_function) (func (import "mod1" "foo2") (param i32 i32) (result i32)) */ const auto wasm = from_hex( - "0061736d01000000010b026000017f60027f7f017f021902046d6f643104666f6f310000046d6f643104666f6f" + "0061736d01000000010b026000017f60027f7f017f021902046d6f643104666f6f310000046d6f64310466" + "6f6f" "320001"); auto module = fizzy_parse(wasm.data(), wasm.size()); ASSERT_NE(module, nullptr); @@ -378,7 +474,8 @@ TEST(capi, imported_function_from_another_module) ) */ const auto bin2 = from_hex( - "0061736d0100000001070160027f7f017f020a01026d31037375620000030201000a0a0108002000200110000" + "0061736d0100000001070160027f7f017f020a01026d31037375620000030201000a0a0108002000200110" + "000" "b"); auto module2 = fizzy_parse(bin2.data(), bin2.size()); ASSERT_NE(module2, nullptr);