From fb8775959126393d5f6d1b09c5dd0a06971a03c4 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Thu, 15 Oct 2020 15:36:07 +0200 Subject: [PATCH] capi: Instantiate with resolving host functions --- include/fizzy/fizzy.h | 27 +++++++++++ lib/fizzy/capi.cpp | 45 +++++++++++++++++ test/unittests/capi_test.cpp | 93 ++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) diff --git a/include/fizzy/fizzy.h b/include/fizzy/fizzy.h index 921486f1c..c2e570244 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 the 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..392bf71a7 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);