diff --git a/include/fizzy/fizzy.h b/include/fizzy/fizzy.h index f6f1e144c..b13392747 100644 --- a/include/fizzy/fizzy.h +++ b/include/fizzy/fizzy.h @@ -130,6 +130,8 @@ typedef struct FizzyExternalFunction { /// Function type. FizzyFunctionType type; + FizzyInstance* instance; + uint32_t function_index; /// Pointer to function. FizzyExternalFn function; /// Opaque pointer to execution context, that will be passed to function. @@ -556,15 +558,6 @@ size_t fizzy_get_instance_memory_size(FizzyInstance* instance) FIZZY_NOEXCEPT; bool fizzy_find_exported_function( FizzyInstance* instance, const char* name, FizzyExternalFunction* out_function) FIZZY_NOEXCEPT; -/// Free resources associated with exported function. -/// -/// @param external_function Pointer to external function struct filled by -/// fizzy_find_exported_function(). Cannot be NULL. -/// -/// @note This function may not be called with external function, which was not returned from -/// fizzy_find_exported_function(). -void fizzy_free_exported_function(FizzyExternalFunction* external_function) FIZZY_NOEXCEPT; - /// Find exported table by name. /// /// @param instance Pointer to instance. Cannot be NULL. diff --git a/lib/fizzy/capi.cpp b/lib/fizzy/capi.cpp index 6a1459845..4d9d5d512 100644 --- a/lib/fizzy/capi.cpp +++ b/lib/fizzy/capi.cpp @@ -133,21 +133,11 @@ 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 FizzyValue* wrap(fizzy::Value* values) noexcept { return reinterpret_cast(values); } -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); @@ -158,16 +148,6 @@ inline fizzy::Value* unwrap(FizzyValue* value) noexcept return reinterpret_cast(value); } -inline FizzyExecutionContext* wrap(fizzy::ExecutionContext& ctx) noexcept -{ - return reinterpret_cast(&ctx); -} - -inline fizzy::ExecutionContext& unwrap(FizzyExecutionContext* ctx) noexcept -{ - return *reinterpret_cast(ctx); -} - inline FizzyInstance* wrap(fizzy::Instance* instance) noexcept { return reinterpret_cast(instance); @@ -183,49 +163,20 @@ inline FizzyExecutionResult wrap(const fizzy::ExecutionResult& result) noexcept return {result.trapped, result.has_value, wrap(result.value)}; } -inline fizzy::ExecutionResult unwrap(const FizzyExecutionResult& result) noexcept -{ - if (result.trapped) - return fizzy::Trap; - else if (!result.has_value) - return fizzy::Void; - else - return unwrap(result.value); -} - -inline fizzy::ExecuteFunction unwrap(FizzyExternalFn c_function, void* c_host_context) -{ - static constexpr fizzy::HostFunctionPtr function = - [](std::any& host_ctx, fizzy::Instance& instance, const fizzy::Value* args, - fizzy::ExecutionContext& ctx) noexcept { - const auto [c_func, c_host_ctx] = - *std::any_cast>(&host_ctx); - return unwrap(c_func(c_host_ctx, wrap(&instance), wrap(args), wrap(ctx))); - }; - - return {function, std::make_any>(c_function, c_host_context)}; -} - inline FizzyExternalFunction wrap(fizzy::ExternalFunction external_func) { - static constexpr FizzyExternalFn c_function = - [](void* host_ctx, FizzyInstance* instance, const FizzyValue* args, - FizzyExecutionContext* c_ctx) noexcept -> FizzyExecutionResult { - // If execution context not provided, allocate new one. - // It must be allocated on heap otherwise the stack will explode in recursive calls. - std::unique_ptr new_ctx; - if (c_ctx == nullptr) - new_ctx = std::make_unique(); - auto& ctx = new_ctx ? *new_ctx : unwrap(c_ctx); - - auto* func = static_cast(host_ctx); - return wrap((func->function)(*unwrap(instance), unwrap(args), ctx)); - }; + const auto c_type = wrap(external_func.input_types, external_func.output_types); - auto host_ctx = std::make_unique(std::move(external_func)); - const auto c_type = wrap(host_ctx->input_types, host_ctx->output_types); - void* c_host_ctx = host_ctx.release(); - return {c_type, c_function, c_host_ctx}; + if (external_func.function.get_c_host_function()) + { + return {c_type, nullptr, 0, external_func.function.get_c_host_function(), + std::any_cast(external_func.function.get_host_context())}; + } + else + { + return {c_type, wrap(external_func.function.get_instance()), + external_func.function.get_function_index(), nullptr, nullptr}; + } } inline fizzy::ExternalFunction unwrap(const FizzyExternalFunction& external_func) @@ -237,8 +188,16 @@ inline fizzy::ExternalFunction unwrap(const FizzyExternalFunction& external_func fizzy::span{} : fizzy::span{unwrap(&external_func.type.output), 1}); - return fizzy::ExternalFunction{ - unwrap(external_func.function, external_func.context), input_types, output_types}; + if (external_func.instance != nullptr) + { + fizzy::ExecuteFunction func(*unwrap(external_func.instance), external_func.function_index); + return {std::move(func), input_types, output_types}; + } + else + { + fizzy::ExecuteFunction func(external_func.function, external_func.context); + return {std::move(func), input_types, output_types}; + } } inline std::vector unwrap( @@ -265,11 +224,20 @@ inline fizzy::ImportedFunction unwrap(const FizzyImportedFunction& c_imported_fu std::nullopt : std::make_optional(unwrap(c_type.output))); - auto function = unwrap( - c_imported_func.external_function.function, c_imported_func.external_function.context); - - return {c_imported_func.module, c_imported_func.name, std::move(inputs), output, - std::move(function)}; + if (c_imported_func.external_function.instance != nullptr) + { + fizzy::ExecuteFunction func(*unwrap(c_imported_func.external_function.instance), + c_imported_func.external_function.function_index); + return {c_imported_func.module, c_imported_func.name, std::move(inputs), output, + std::move(func)}; + } + else + { + fizzy::ExecuteFunction func( + c_imported_func.external_function.function, c_imported_func.external_function.context); + return {c_imported_func.module, c_imported_func.name, std::move(inputs), output, + std::move(func)}; + } } inline std::vector unwrap( @@ -559,6 +527,10 @@ bool fizzy_find_exported_function( if (!optional_func) return false; + // C++ host functions not supported + if (optional_func->function.get_host_function() != nullptr) + return false; + try { *out_function = wrap(std::move(*optional_func)); @@ -570,11 +542,6 @@ bool fizzy_find_exported_function( } } -void fizzy_free_exported_function(FizzyExternalFunction* external_function) noexcept -{ - delete static_cast(external_function->context); -} - bool fizzy_find_exported_table( FizzyInstance* instance, const char* name, FizzyExternalTable* out_table) noexcept { diff --git a/lib/fizzy/instantiate.cpp b/lib/fizzy/instantiate.cpp index 189e71979..dfd2ef0ac 100644 --- a/lib/fizzy/instantiate.cpp +++ b/lib/fizzy/instantiate.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "instantiate.hpp" +#include "cxx20/bit.hpp" #include "execute.hpp" // needed for implementation of ExecuteFunction for Wasm functions #include #include @@ -322,8 +323,21 @@ ExecutionResult ExecuteFunction::operator()( { if (m_instance) return execute(*m_instance, m_func_idx, args, ctx); - else + else if (m_host_function) return m_host_function(m_host_context, instance, args, ctx); + else + { + assert(m_c_host_function != nullptr); + const auto result = m_c_host_function(std::any_cast(m_host_context), + reinterpret_cast(&instance), reinterpret_cast(args), + reinterpret_cast(&ctx)); + if (result.trapped) + return fizzy::Trap; + else if (!result.has_value) + return fizzy::Void; + else + return fizzy::bit_cast(result.value); + } } std::unique_ptr instantiate(std::unique_ptr module, diff --git a/lib/fizzy/instantiate.hpp b/lib/fizzy/instantiate.hpp index 7960531c7..b1d7a6dbe 100644 --- a/lib/fizzy/instantiate.hpp +++ b/lib/fizzy/instantiate.hpp @@ -10,6 +10,7 @@ #include "module.hpp" #include "types.hpp" #include "value.hpp" +#include #include #include #include @@ -42,6 +43,8 @@ class ExecuteFunction /// Equals nullptr in case this ExecuteFunction represents WebAssembly function. HostFunctionPtr m_host_function = nullptr; + FizzyExternalFn m_c_host_function = nullptr; + /// Opaque context of host function execution, which is passed to it as host_context parameter. /// Doesn't have value in case this ExecuteFunction represents WebAssembly function. std::any m_host_context; @@ -67,8 +70,20 @@ class ExecuteFunction ExecutionResult operator()( Instance& instance, const Value* args, ExecutionContext& ctx) noexcept; + ExecuteFunction(FizzyExternalFn f, void* host_context) noexcept + : m_c_host_function{f}, m_host_context{host_context} + {} + + Instance* get_instance() const noexcept { return m_instance; } + /// Function pointer stored inside this object. HostFunctionPtr get_host_function() const noexcept { return m_host_function; } + + FizzyExternalFn get_c_host_function() const noexcept { return m_c_host_function; } + + FuncIdx get_function_index() const noexcept { return m_func_idx; } + + std::any& get_host_context() noexcept { return m_host_context; } }; /// Function with associated input/output types, diff --git a/test/unittests/capi_test.cpp b/test/unittests/capi_test.cpp index cc85d6273..3820d569b 100644 --- a/test/unittests/capi_test.cpp +++ b/test/unittests/capi_test.cpp @@ -479,12 +479,14 @@ TEST(capi, find_exported_function) ASSERT_TRUE(fizzy_find_exported_function(instance, "foo", &function)); EXPECT_EQ(function.type.inputs_size, 0); EXPECT_EQ(function.type.output, FizzyValueTypeI32); - EXPECT_NE(function.context, nullptr); - ASSERT_NE(function.function, nullptr); + EXPECT_EQ(function.context, nullptr); + ASSERT_EQ(function.function, nullptr); + EXPECT_EQ(function.instance, instance); + EXPECT_EQ(function.function_index, 0); - EXPECT_THAT(function.function(function.context, instance, nullptr, nullptr), CResult(42_u32)); - fizzy_free_exported_function(&function); + // EXPECT_THAT(function.function(function.context, instance, nullptr, nullptr), + // CResult(42_u32)); EXPECT_FALSE(fizzy_find_exported_function(instance, "foo2", &function)); EXPECT_FALSE(fizzy_find_exported_function(instance, "g1", &function)); @@ -731,7 +733,8 @@ TEST(capi, instantiate_imported_function) module = fizzy_parse(wasm.data(), wasm.size(), nullptr); ASSERT_NE(module, nullptr); - FizzyExternalFunction host_funcs[] = {{{FizzyValueTypeI32, nullptr, 0}, NullFn, nullptr}}; + FizzyExternalFunction host_funcs[] = { + {{FizzyValueTypeI32, nullptr, 0}, nullptr, 0, NullFn, nullptr}}; auto instance = fizzy_instantiate( module, host_funcs, 1, nullptr, nullptr, nullptr, 0, FizzyMemoryPagesLimitDefault, nullptr); @@ -921,7 +924,7 @@ TEST(capi, resolve_instantiate_no_imports) // providing unnecessary import FizzyImportedFunction host_funcs[] = { - {"mod", "foo", {{FizzyValueTypeVoid, nullptr, 0}, NullFn, nullptr}}}; + {"mod", "foo", {{FizzyValueTypeVoid, nullptr, 0}, nullptr, 0, NullFn, nullptr}}}; instance = fizzy_resolve_instantiate( module, host_funcs, 1, nullptr, nullptr, nullptr, 0, FizzyMemoryPagesLimitDefault, nullptr); @@ -976,15 +979,19 @@ TEST(capi, resolve_instantiate_functions) const FizzyValueType input_type = FizzyValueTypeI32; FizzyValue result_int32{42}; - FizzyExternalFunction mod1foo1 = {{FizzyValueTypeI32, &input_type, 1}, host_fn, &result_int32}; + FizzyExternalFunction mod1foo1 = { + {FizzyValueTypeI32, &input_type, 1}, nullptr, 0, host_fn, &result_int32}; FizzyValue result_int64{43}; - FizzyExternalFunction mod1foo2 = {{FizzyValueTypeI64, &input_type, 1}, host_fn, &result_int64}; + FizzyExternalFunction mod1foo2 = { + {FizzyValueTypeI64, &input_type, 1}, nullptr, 0, host_fn, &result_int64}; FizzyValue result_f32; result_f32.f32 = 44.44f; - FizzyExternalFunction mod2foo1 = {{FizzyValueTypeF32, &input_type, 1}, host_fn, &result_f32}; + FizzyExternalFunction mod2foo1 = { + {FizzyValueTypeF32, &input_type, 1}, nullptr, 0, host_fn, &result_f32}; FizzyValue result_f64; result_f64.f64 = 45.45; - FizzyExternalFunction mod2foo2 = {{FizzyValueTypeF64, &input_type, 1}, host_fn, &result_f64}; + FizzyExternalFunction mod2foo2 = { + {FizzyValueTypeF64, &input_type, 1}, nullptr, 0, host_fn, &result_f64}; FizzyImportedFunction host_funcs[] = {{"mod1", "foo1", mod1foo1}, {"mod1", "foo2", mod1foo2}, {"mod2", "foo1", mod2foo1}, {"mod2", "foo2", mod2foo2}}; @@ -1048,7 +1055,8 @@ TEST(capi, resolve_instantiate_function_duplicate) return FizzyExecutionResult{false, true, FizzyValue{42}}; }; - FizzyExternalFunction mod1foo1 = {{FizzyValueTypeI32, nullptr, 0}, host_fn, nullptr}; + FizzyExternalFunction mod1foo1 = { + {FizzyValueTypeI32, nullptr, 0}, nullptr, 0, host_fn, nullptr}; FizzyImportedFunction host_funcs[] = {{"mod1", "foo1", mod1foo1}}; auto instance = fizzy_resolve_instantiate( @@ -1096,7 +1104,7 @@ TEST(capi, resolve_instantiate_globals) return FizzyExecutionResult{true, false, {0}}; }; FizzyImportedFunction mod1foo1 = { - "mod1", "foo1", {{FizzyValueTypeVoid, nullptr, 0}, host_fn, nullptr}}; + "mod1", "foo1", {{FizzyValueTypeVoid, nullptr, 0}, nullptr, 0, host_fn, nullptr}}; FizzyValue mod1g1value{42}; FizzyExternalGlobal mod1g1 = {&mod1g1value, {FizzyValueTypeI32, false}}; @@ -1428,12 +1436,12 @@ TEST(capi, execute_with_host_function) const FizzyValueType inputs[] = {FizzyValueTypeI32, FizzyValueTypeI32}; FizzyExternalFunction host_funcs[] = { - {{FizzyValueTypeI32, nullptr, 0}, + {{FizzyValueTypeI32, nullptr, 0}, nullptr, 0, [](void*, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) noexcept { return FizzyExecutionResult{false, true, {42}}; }, nullptr}, - {{FizzyValueTypeI32, &inputs[0], 2}, + {{FizzyValueTypeI32, &inputs[0], 2}, nullptr, 0, [](void*, FizzyInstance*, const FizzyValue* args, FizzyExecutionContext*) noexcept { FizzyValue v; v.i32 = args[0].i32 / args[1].i32; @@ -1466,7 +1474,7 @@ TEST(capi, imported_function_traps) auto module = fizzy_parse(wasm.data(), wasm.size(), nullptr); ASSERT_NE(module, nullptr); - FizzyExternalFunction host_funcs[] = {{{FizzyValueTypeI32, nullptr, 0}, + FizzyExternalFunction host_funcs[] = {{{FizzyValueTypeI32, nullptr, 0}, nullptr, 0, [](void*, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) noexcept { return FizzyExecutionResult{true, false, {}}; }, @@ -1495,7 +1503,7 @@ TEST(capi, imported_function_void) ASSERT_NE(module, nullptr); bool called = false; - FizzyExternalFunction host_funcs[] = {{{}, + FizzyExternalFunction host_funcs[] = {{{}, nullptr, 0, [](void* context, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) noexcept { *static_cast(context) = true; return FizzyExecutionResult{false, false, {}}; @@ -1558,7 +1566,6 @@ TEST(capi, imported_function_from_another_module) FizzyValue args[] = {{44}, {2}}; EXPECT_THAT(fizzy_execute(instance2, 1, args), CResult(42_u32)); - fizzy_free_exported_function(&func); fizzy_free_instance(instance2); fizzy_free_instance(instance1); } @@ -1936,7 +1943,8 @@ TEST(capi, import_name_after_instantiate) EXPECT_STREQ(import0.module, "m"); EXPECT_STREQ(import0.name, "f1"); - FizzyExternalFunction host_funcs[] = {{{FizzyValueTypeI32, nullptr, 0}, NullFn, nullptr}}; + FizzyExternalFunction host_funcs[] = { + {{FizzyValueTypeI32, nullptr, 0}, nullptr, 0, NullFn, nullptr}}; auto instance = fizzy_instantiate( module, host_funcs, 1, nullptr, nullptr, nullptr, 0, FizzyMemoryPagesLimitDefault, nullptr); diff --git a/test/utils/fizzy_c_engine.cpp b/test/utils/fizzy_c_engine.cpp index 9d4c731d5..ce51056a9 100644 --- a/test/utils/fizzy_c_engine.cpp +++ b/test/utils/fizzy_c_engine.cpp @@ -71,7 +71,7 @@ bool FizzyCEngine::instantiate(bytes_view wasm_binary) FizzyValueType inputs[] = {FizzyValueTypeI32, FizzyValueTypeI32}; FizzyImportedFunction imports[] = { - {"env", "adler32", {{FizzyValueTypeI32, inputs, 2}, env_adler32, nullptr}}}; + {"env", "adler32", {{FizzyValueTypeI32, inputs, 2}, nullptr, 0, env_adler32, nullptr}}}; m_instance.reset(fizzy_resolve_instantiate( module, imports, 1, nullptr, nullptr, nullptr, 0, FizzyMemoryPagesLimitDefault, nullptr));