From b77409db60f7eb57445cd29e5b989a4d5996f8f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 10 Sep 2020 17:47:15 +0200 Subject: [PATCH 1/5] Stack external storage --- lib/fizzy/execute.cpp | 5 ++++- lib/fizzy/stack.hpp | 12 +++--------- test/unittests/span_test.cpp | 2 +- test/unittests/stack_test.cpp | 20 +++++++++++--------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index f93de221b..8dcda160d 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -477,7 +477,10 @@ ExecutionResult execute( const auto& code = instance.module.get_code(func_idx); auto* const memory = instance.memory.get(); - OperandStack stack(args, code.local_count, static_cast(code.max_stack_height)); + constexpr auto stack_space_size = 128 / sizeof(Value); + Value stack_space[stack_space_size]; + OperandStack stack(args, code.local_count, static_cast(code.max_stack_height), + stack_space, std::size(stack_space)); const Instr* pc = code.instructions.data(); const uint8_t* immediates = code.immediates.data(); diff --git a/lib/fizzy/stack.hpp b/lib/fizzy/stack.hpp index 2da812c94..41a56eebc 100644 --- a/lib/fizzy/stack.hpp +++ b/lib/fizzy/stack.hpp @@ -59,9 +59,6 @@ class Stack /// from the stack itself. class OperandStack { - /// The size of the pre-allocated internal storage: 128 bytes. - static constexpr auto small_storage_size = 128 / sizeof(Value); - /// The pointer to the top item of the operand stack, /// or below the stack bottom if stack is empty. /// @@ -77,9 +74,6 @@ class OperandStack /// The pointer to the bottom of the operand stack. Value* m_bottom; - /// The pre-allocated internal storage. - Value m_small_storage[small_storage_size]; - /// The unbounded storage for items. std::unique_ptr m_large_storage; @@ -96,14 +90,14 @@ class OperandStack /// after the arguments. /// @param max_stack_height The maximum operand stack height in the function. This excludes /// args and num_local_variables. - OperandStack(span args, size_t num_local_variables, size_t max_stack_height) + OperandStack(span args, size_t num_local_variables, size_t max_stack_height, Value* external_storage, size_t external_storage_size) { const auto num_args = args.size(); const auto storage_size_required = num_args + num_local_variables + max_stack_height; - if (storage_size_required <= small_storage_size) + if (storage_size_required <= external_storage_size) { - m_locals = &m_small_storage[0]; + m_locals = external_storage; } else { diff --git a/test/unittests/span_test.cpp b/test/unittests/span_test.cpp index 512a45e0e..f456dc717 100644 --- a/test/unittests/span_test.cpp +++ b/test/unittests/span_test.cpp @@ -50,7 +50,7 @@ TEST(span, array) TEST(span, stack) { - OperandStack stack({}, 0, 4); + OperandStack stack({}, 0, 4, nullptr, 0); stack.push(10); stack.push(11); stack.push(12); diff --git a/test/unittests/stack_test.cpp b/test/unittests/stack_test.cpp index 853ab9cdb..b9944b8b5 100644 --- a/test/unittests/stack_test.cpp +++ b/test/unittests/stack_test.cpp @@ -123,13 +123,13 @@ TEST(stack, struct_item) TEST(operand_stack, construct) { - OperandStack stack({}, 0, 0); + OperandStack stack({}, 0, 0, nullptr, 0); EXPECT_EQ(stack.size(), 0); } TEST(operand_stack, top) { - OperandStack stack({}, 0, 1); + OperandStack stack({}, 0, 1, nullptr, 0); EXPECT_EQ(stack.size(), 0); stack.push(1); @@ -158,7 +158,8 @@ TEST(operand_stack, top) TEST(operand_stack, small) { - OperandStack stack({}, 0, 3); + fizzy::Value storage[3]; + OperandStack stack({}, 0, 3, storage, std::size(storage)); ASSERT_LT(address_diff(&stack, stack.rbegin()), 100) << "not allocated on the system stack"; EXPECT_EQ(stack.size(), 0); @@ -188,8 +189,9 @@ TEST(operand_stack, small) TEST(operand_stack, small_with_locals) { + fizzy::Value storage[6]; const fizzy::Value args[] = {0xa1, 0xa2}; - OperandStack stack(args, 3, 1); + OperandStack stack(args, 3, 1, storage, std::size(storage)); ASSERT_LT(address_diff(&stack, stack.rbegin()), 100) << "not allocated on the system stack"; EXPECT_EQ(stack.size(), 0); @@ -229,7 +231,7 @@ TEST(operand_stack, small_with_locals) TEST(operand_stack, large) { constexpr auto max_height = 33; - OperandStack stack({}, 0, max_height); + OperandStack stack({}, 0, max_height, nullptr, 0); ASSERT_GT(address_diff(&stack, stack.rbegin()), 100) << "not allocated on the heap"; for (unsigned i = 0; i < max_height; ++i) @@ -247,7 +249,7 @@ TEST(operand_stack, large_with_locals) constexpr auto max_height = 33; constexpr auto num_locals = 5; constexpr auto num_args = std::size(args); - OperandStack stack(args, num_locals, max_height); + OperandStack stack(args, num_locals, max_height, nullptr, 0); ASSERT_GT(address_diff(&stack, stack.rbegin()), 100) << "not allocated on the heap"; for (unsigned i = 0; i < max_height; ++i) @@ -279,7 +281,7 @@ TEST(operand_stack, large_with_locals) TEST(operand_stack, rbegin_rend) { - OperandStack stack({}, 0, 3); + OperandStack stack({}, 0, 3, nullptr, 0); EXPECT_EQ(stack.rbegin(), stack.rend()); stack.push(1); @@ -293,7 +295,7 @@ TEST(operand_stack, rbegin_rend) TEST(operand_stack, rbegin_rend_locals) { const fizzy::Value args[] = {0xa1}; - OperandStack stack(args, 4, 2); + OperandStack stack(args, 4, 2, nullptr, 0); EXPECT_EQ(stack.rbegin(), stack.rend()); stack.push(1); @@ -313,7 +315,7 @@ TEST(operand_stack, rbegin_rend_locals) TEST(operand_stack, to_vector) { - OperandStack stack({}, 0, 3); + OperandStack stack({}, 0, 3, nullptr, 0); EXPECT_THAT(std::vector(stack.rbegin(), stack.rend()), IsEmpty()); stack.push(1); From 030c83697ad65f8538b96a0245500bb7419236f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 10 Sep 2020 09:52:01 +0200 Subject: [PATCH 2/5] Drop default value for depth parameter in execute() --- lib/fizzy/execute.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/fizzy/execute.hpp b/lib/fizzy/execute.hpp index a71e67cab..4327edf46 100644 --- a/lib/fizzy/execute.hpp +++ b/lib/fizzy/execute.hpp @@ -36,9 +36,16 @@ struct ExecutionResult constexpr ExecutionResult Void{true}; constexpr ExecutionResult Trap{false}; -// Execute a function on an instance. +/// Execute a function on an instance. ExecutionResult execute( - Instance& instance, FuncIdx func_idx, span args, int depth = 0) noexcept; + Instance& instance, FuncIdx func_idx, span args, int depth) noexcept; + +/// Execute a function on an instance with implicit depth 0. +inline ExecutionResult execute( + Instance& instance, FuncIdx func_idx, span args) noexcept +{ + return execute(instance, func_idx, args, 0); +} inline ExecutionResult execute( Instance& instance, FuncIdx func_idx, std::initializer_list args) noexcept From 652ad729b494cbd72f511f98169a3fbd25b7d447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 10 Sep 2020 10:31:52 +0200 Subject: [PATCH 3/5] Introduce ThreadContext --- lib/fizzy/execute.cpp | 43 +++++++++++++++++-------- lib/fizzy/execute.hpp | 7 ++-- lib/fizzy/instantiate.cpp | 13 +++++--- lib/fizzy/instantiate.hpp | 14 ++++++-- test/unittests/api_test.cpp | 17 ++++++---- test/unittests/execute_call_test.cpp | 34 +++++++++++-------- test/unittests/execute_control_test.cpp | 4 ++- test/unittests/execute_test.cpp | 18 +++++------ test/unittests/instantiate_test.cpp | 15 +++++---- test/utils/fizzy_engine.cpp | 3 +- tools/wasi/wasi.cpp | 14 ++++---- 11 files changed, 115 insertions(+), 67 deletions(-) diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index 8dcda160d..4eb3c152b 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -54,14 +54,14 @@ void branch(const Code& code, OperandStack& stack, const Instr*& pc, const uint8 template bool invoke_function(const FuncType& func_type, const F& func, Instance& instance, - OperandStack& stack, int depth) noexcept + OperandStack& stack, ThreadContext& thread_context) noexcept { const auto num_args = func_type.inputs.size(); assert(stack.size() >= num_args); span call_args{stack.rend() - num_args, num_args}; stack.drop(num_args); - const auto ret = func(instance, call_args, depth + 1); + const auto ret = func(instance, call_args, thread_context); // Bubble up traps if (ret.trapped) return false; @@ -78,12 +78,13 @@ bool invoke_function(const FuncType& func_type, const F& func, Instance& instanc } inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instance& instance, - OperandStack& stack, int depth) noexcept + OperandStack& stack, ThreadContext& thread_context) noexcept { - const auto func = [func_idx](Instance& _instance, span args, int _depth) noexcept { - return execute(_instance, func_idx, args, _depth); + const auto func = [func_idx](Instance& _instance, span args, + ThreadContext& _thread_context) noexcept { + return execute(_instance, func_idx, args, _thread_context); }; - return invoke_function(func_type, func, instance, stack, depth); + return invoke_function(func_type, func, instance, stack, thread_context); } template @@ -459,20 +460,35 @@ __attribute__((no_sanitize("float-cast-overflow"))) inline constexpr float demot return static_cast(value); } +class ThreadContextGuard +{ + ThreadContext& m_thread_context; + +public: + explicit ThreadContextGuard(ThreadContext& thread_context) noexcept + : m_thread_context{thread_context} + { + m_thread_context.lock(); + } + + ~ThreadContextGuard() noexcept { m_thread_context.unlock(); } +}; + } // namespace -ExecutionResult execute( - Instance& instance, FuncIdx func_idx, span args, int depth) noexcept +ExecutionResult execute(Instance& instance, FuncIdx func_idx, span args, + ThreadContext& thread_context) noexcept { - assert(depth >= 0); - if (depth > CallStackLimit) + if (thread_context.depth > CallStackLimit) return Trap; + ThreadContextGuard guard{thread_context}; + assert(args.size() == instance.module.get_function_type(func_idx).inputs.size()); assert(instance.module.imported_function_types.size() == instance.imported_functions.size()); if (func_idx < instance.imported_functions.size()) - return instance.imported_functions[func_idx].function(instance, args, depth); + return instance.imported_functions[func_idx].function(instance, args, thread_context); const auto& code = instance.module.get_code(func_idx); auto* const memory = instance.memory.get(); @@ -565,7 +581,7 @@ ExecutionResult execute( const auto called_func_idx = read(immediates); const auto& called_func_type = instance.module.get_function_type(called_func_idx); - if (!invoke_function(called_func_type, called_func_idx, instance, stack, depth)) + if (!invoke_function(called_func_type, called_func_idx, instance, stack, thread_context)) goto trap; break; } @@ -590,7 +606,8 @@ ExecutionResult execute( if (expected_type != actual_type) goto trap; - if (!invoke_function(actual_type, called_func->function, instance, stack, depth)) + if (!invoke_function( + actual_type, called_func->function, instance, stack, thread_context)) goto trap; break; } diff --git a/lib/fizzy/execute.hpp b/lib/fizzy/execute.hpp index 4327edf46..5cbbec6c7 100644 --- a/lib/fizzy/execute.hpp +++ b/lib/fizzy/execute.hpp @@ -37,14 +37,15 @@ constexpr ExecutionResult Void{true}; constexpr ExecutionResult Trap{false}; /// Execute a function on an instance. -ExecutionResult execute( - Instance& instance, FuncIdx func_idx, span args, int depth) noexcept; +ExecutionResult execute(Instance& instance, FuncIdx func_idx, span args, + ThreadContext& thread_context) noexcept; /// Execute a function on an instance with implicit depth 0. inline ExecutionResult execute( Instance& instance, FuncIdx func_idx, span args) noexcept { - return execute(instance, func_idx, args, 0); + ThreadContext thread_context; + return execute(instance, func_idx, args, thread_context); } inline ExecutionResult execute( diff --git a/lib/fizzy/instantiate.cpp b/lib/fizzy/instantiate.cpp index 4353803db..b2012c223 100644 --- a/lib/fizzy/instantiate.cpp +++ b/lib/fizzy/instantiate.cpp @@ -328,7 +328,9 @@ std::unique_ptr instantiate(Module module, for (const auto idx : instance->module.elementsec[i].init) { auto func = [idx, &instance_ref = *instance](fizzy::Instance&, span args, - int depth) { return execute(instance_ref, idx, args, depth); }; + ThreadContext& thread_context) { + return execute(instance_ref, idx, args, thread_context); + }; *it_table++ = ExternalFunction{std::move(func), instance->module.get_function_type(idx)}; @@ -361,7 +363,9 @@ std::unique_ptr instantiate(Module module, auto& table_function = (*it_table)->function; table_function = [shared_instance, func = std::move(table_function)]( fizzy::Instance& _instance, span args, - int depth) { return func(_instance, args, depth); }; + ThreadContext& thread_context) { + return func(_instance, args, thread_context); + }; ++it_table; } } @@ -432,8 +436,9 @@ std::optional find_exported_function(Instance& instance, std:: return std::nullopt; const auto idx = *opt_index; - auto func = [idx, &instance](fizzy::Instance&, span args, int depth) { - return execute(instance, idx, args, depth); + auto func = [idx, &instance]( + fizzy::Instance&, span args, ThreadContext& thread_context) { + return execute(instance, idx, args, thread_context); }; return ExternalFunction{std::move(func), instance.module.get_function_type(idx)}; diff --git a/lib/fizzy/instantiate.hpp b/lib/fizzy/instantiate.hpp index 224e5587e..023e8e30f 100644 --- a/lib/fizzy/instantiate.hpp +++ b/lib/fizzy/instantiate.hpp @@ -22,9 +22,19 @@ namespace fizzy struct ExecutionResult; struct Instance; +class ThreadContext +{ +public: + int depth = 0; + + void lock() noexcept { ++depth; } + + void unlock() noexcept { --depth; } +}; + struct ExternalFunction { - std::function, int depth)> function; + std::function, ThreadContext&)> function; FuncType type; }; @@ -102,7 +112,7 @@ struct ImportedFunction std::string name; std::vector inputs; std::optional output; - std::function, int depth)> function; + std::function, ThreadContext&)> function; }; // Create vector of ExternalFunctions ready to be passed to instantiate. diff --git a/test/unittests/api_test.cpp b/test/unittests/api_test.cpp index 5a913ed54..84bddd4cd 100644 --- a/test/unittests/api_test.cpp +++ b/test/unittests/api_test.cpp @@ -16,10 +16,10 @@ namespace { auto function_returning_value(Value value) noexcept { - return [value](Instance&, span, int) { return value; }; + return [value](Instance&, span, ThreadContext&) { return value; }; } -ExecutionResult function_returning_void(Instance&, span, int) noexcept +ExecutionResult function_returning_void(Instance&, span, ThreadContext&) noexcept { return Void; } @@ -243,7 +243,8 @@ TEST(api, find_exported_function) auto opt_function = find_exported_function(*instance, "foo"); ASSERT_TRUE(opt_function); - EXPECT_THAT(opt_function->function(*instance, {}, 0), Result(42)); + ThreadContext thread_context; + EXPECT_THAT(opt_function->function(*instance, {}, thread_context), Result(42)); EXPECT_TRUE(opt_function->type.inputs.empty()); ASSERT_EQ(opt_function->type.outputs.size(), 1); EXPECT_EQ(opt_function->type.outputs[0], ValType::i32); @@ -262,7 +263,7 @@ TEST(api, find_exported_function) "0061736d010000000105016000017f021001087370656374657374036261720000040401700000050401010102" "0606017f0041000b07170403666f6f000001670300037461620100036d656d0200"); - auto bar = [](Instance&, span, int) { return Value{42}; }; + auto bar = [](Instance&, span, ThreadContext&) { return Value{42}; }; const auto bar_type = FuncType{{}, {ValType::i32}}; auto instance_reexported_function = @@ -270,7 +271,7 @@ TEST(api, find_exported_function) opt_function = find_exported_function(*instance_reexported_function, "foo"); ASSERT_TRUE(opt_function); - EXPECT_THAT(opt_function->function(*instance, {}, 0), Result(42)); + EXPECT_THAT(opt_function->function(*instance, {}, thread_context), Result(42)); EXPECT_TRUE(opt_function->type.inputs.empty()); ASSERT_EQ(opt_function->type.outputs.size(), 1); EXPECT_EQ(opt_function->type.outputs[0], ValType::i32); @@ -411,8 +412,10 @@ TEST(api, find_exported_table) ASSERT_TRUE(opt_table); EXPECT_EQ(opt_table->table, instance->table.get()); EXPECT_EQ(opt_table->table->size(), 2); - EXPECT_THAT((*opt_table->table)[0]->function(*instance, {}, 0), Result(2)); - EXPECT_THAT((*opt_table->table)[1]->function(*instance, {}, 0), Result(1)); + + ThreadContext thread_context; + EXPECT_THAT((*opt_table->table)[0]->function(*instance, {}, thread_context), Result(2)); + EXPECT_THAT((*opt_table->table)[1]->function(*instance, {}, thread_context), Result(1)); EXPECT_EQ(opt_table->limits.min, 2); ASSERT_TRUE(opt_table->limits.max.has_value()); EXPECT_EQ(opt_table->limits.max, 20); diff --git a/test/unittests/execute_call_test.cpp b/test/unittests/execute_call_test.cpp index a338459df..dc7b88600 100644 --- a/test/unittests/execute_call_test.cpp +++ b/test/unittests/execute_call_test.cpp @@ -147,11 +147,11 @@ TEST(execute_call, call_indirect_imported_table) const Module module = parse(bin); - auto f1 = [](Instance&, span, int) { return Value{1}; }; - auto f2 = [](Instance&, span, int) { return Value{2}; }; - auto f3 = [](Instance&, span, int) { return Value{3}; }; - auto f4 = [](Instance&, span, int) { return Value{4}; }; - auto f5 = [](Instance&, span, int) { return Trap; }; + auto f1 = [](Instance&, span, ThreadContext&) { return Value{1}; }; + auto f2 = [](Instance&, span, ThreadContext&) { return Value{2}; }; + auto f3 = [](Instance&, span, ThreadContext&) { return Value{3}; }; + auto f4 = [](Instance&, span, ThreadContext&) { return Value{4}; }; + auto f5 = [](Instance&, span, ThreadContext&) { return Trap; }; auto out_i32 = FuncType{{}, {ValType::i32}}; auto out_i64 = FuncType{{}, {ValType::i64}}; @@ -218,7 +218,9 @@ TEST(execute_call, imported_function_call) const auto module = parse(wasm); - constexpr auto host_foo = [](Instance&, span, int) { return Value{42}; }; + constexpr auto host_foo = [](Instance&, span, ThreadContext&) { + return Value{42}; + }; const auto host_foo_type = module.typesec[0]; auto instance = instantiate(module, {{host_foo, host_foo_type}}); @@ -243,7 +245,7 @@ TEST(execute_call, imported_function_call_with_arguments) const auto module = parse(wasm); - auto host_foo = [](Instance&, span args, int) { + auto host_foo = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) * 2}; }; const auto host_foo_type = module.typesec[0]; @@ -287,11 +289,11 @@ TEST(execute_call, imported_functions_call_indirect) ASSERT_EQ(module.importsec.size(), 2); ASSERT_EQ(module.codesec.size(), 2); - constexpr auto sqr = [](Instance&, span args, int) { + constexpr auto sqr = [](Instance&, span args, ThreadContext&) { const auto x = as_uint32(args[0]); return Value{uint64_t{x} * uint64_t{x}}; }; - constexpr auto isqrt = [](Instance&, span args, int) { + constexpr auto isqrt = [](Instance&, span args, ThreadContext&) { const auto x = as_uint32(args[0]); return Value{(11 + uint64_t{x} / 11) / 2}; }; @@ -337,7 +339,8 @@ TEST(execute_call, imported_function_from_another_module) const auto func_idx = fizzy::find_exported_function(module1, "sub"); ASSERT_TRUE(func_idx.has_value()); - auto sub = [&instance1, func_idx](Instance&, span args, int) -> ExecutionResult { + auto sub = [&instance1, func_idx]( + Instance&, span args, ThreadContext&) -> ExecutionResult { return fizzy::execute(*instance1, *func_idx, args); }; @@ -476,8 +479,10 @@ TEST(execute_call, call_max_depth) const auto module = parse(bin); auto instance = instantiate(module); - EXPECT_THAT(execute(*instance, 0, {}, 2048), Result(42)); - EXPECT_THAT(execute(*instance, 1, {}, 2048), Traps()); + ThreadContext thread_context; + thread_context.depth = 2048; + EXPECT_THAT(execute(*instance, 0, {}, thread_context), Result(42)); + EXPECT_THAT(execute(*instance, 1, {}, thread_context), Traps()); } // A regression test for incorrect number of arguments passed to a call. @@ -515,8 +520,9 @@ TEST(execute_call, call_imported_infinite_recursion) "0061736d010000000105016000017f020b01036d6f6403666f6f0000030201000a0601040010000b"); const auto module = parse(wasm); - auto host_foo = [](Instance& instance, span, int depth) -> ExecutionResult { - return execute(instance, 0, {}, depth + 1); + auto host_foo = [](Instance& instance, span, + ThreadContext& thread_context) -> ExecutionResult { + return execute(instance, 0, {}, thread_context); }; const auto host_foo_type = module.typesec[0]; diff --git a/test/unittests/execute_control_test.cpp b/test/unittests/execute_control_test.cpp index bbd7c6330..b13a9d034 100644 --- a/test/unittests/execute_control_test.cpp +++ b/test/unittests/execute_control_test.cpp @@ -665,7 +665,9 @@ TEST(execute_control, br_1_out_of_function_and_imported_function) "0a0d010b00034041010c010b41000b"); constexpr auto fake_imported_function = [](Instance&, span, - int) noexcept -> ExecutionResult { return Void; }; + ThreadContext&) noexcept -> ExecutionResult { + return Void; + }; const auto module = parse(bin); auto instance = instantiate(module, {{fake_imported_function, module.typesec[0]}}); diff --git a/test/unittests/execute_test.cpp b/test/unittests/execute_test.cpp index af6e79713..cd860d50e 100644 --- a/test/unittests/execute_test.cpp +++ b/test/unittests/execute_test.cpp @@ -776,7 +776,7 @@ TEST(execute, imported_function) const auto module = parse(wasm); ASSERT_EQ(module.typesec.size(), 1); - constexpr auto host_foo = [](Instance&, span args, int) { + constexpr auto host_foo = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) + as_uint32(args[1])}; }; @@ -796,10 +796,10 @@ TEST(execute, imported_two_functions) const auto module = parse(wasm); ASSERT_EQ(module.typesec.size(), 1); - constexpr auto host_foo1 = [](Instance&, span args, int) { + constexpr auto host_foo1 = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) + as_uint32(args[1])}; }; - constexpr auto host_foo2 = [](Instance&, span args, int) { + constexpr auto host_foo2 = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) * as_uint32(args[1])}; }; @@ -823,10 +823,10 @@ TEST(execute, imported_functions_and_regular_one) "0061736d0100000001070160027f7f017f021702036d6f6404666f6f310000036d6f6404666f6f320000030201" "000a0901070041aa80a8010b"); - constexpr auto host_foo1 = [](Instance&, span args, int) { + constexpr auto host_foo1 = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) + as_uint32(args[1])}; }; - constexpr auto host_foo2 = [](Instance&, span args, int) { + constexpr auto host_foo2 = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) * as_uint32(args[1])}; }; @@ -851,7 +851,7 @@ TEST(execute, imported_functions_count_args) "0001"); // Check if correct number of arguments is passed to host functions. - constexpr auto count_args = [](Instance&, span args, int) { + constexpr auto count_args = [](Instance&, span args, ThreadContext&) { return Value{args.size()}; }; @@ -878,10 +878,10 @@ TEST(execute, imported_two_functions_different_type) "0061736d01000000010c0260027f7f017f60017e017e021702036d6f6404666f6f310000036d6f6404666f6f32" "0001030201010a0901070042aa80a8010b"); - constexpr auto host_foo1 = [](Instance&, span args, int) { + constexpr auto host_foo1 = [](Instance&, span args, ThreadContext&) { return Value{as_uint32(args[0]) + as_uint32(args[1])}; }; - constexpr auto host_foo2 = [](Instance&, span args, int) { + constexpr auto host_foo2 = [](Instance&, span args, ThreadContext&) { return Value{args[0].i64 * args[0].i64}; }; @@ -902,7 +902,7 @@ TEST(execute, imported_function_traps) */ const auto wasm = from_hex("0061736d0100000001070160027f7f017f020b01036d6f6403666f6f0000"); - constexpr auto host_foo = [](Instance&, span, int) -> ExecutionResult { + constexpr auto host_foo = [](Instance&, span, ThreadContext&) -> ExecutionResult { return Trap; }; diff --git a/test/unittests/instantiate_test.cpp b/test/unittests/instantiate_test.cpp index a31366f07..b7b01e216 100644 --- a/test/unittests/instantiate_test.cpp +++ b/test/unittests/instantiate_test.cpp @@ -17,7 +17,8 @@ namespace uint32_t call_table_func(Instance& instance, size_t idx) { const auto& elem = (*instance.table)[idx]; - const auto res = elem->function(instance, {}, 0); + ThreadContext thread_context; + const auto res = elem->function(instance, {}, thread_context); EXPECT_TRUE(res.has_value); return as_uint32(res.value); } @@ -31,7 +32,7 @@ TEST(instantiate, imported_functions) const auto bin = from_hex("0061736d0100000001060160017f017f020b01036d6f6403666f6f0000"); const auto module = parse(bin); - auto host_foo = [](Instance&, span, int) { return Trap; }; + auto host_foo = [](Instance&, span, ThreadContext&) { return Trap; }; auto instance = instantiate(module, {{host_foo, module.typesec[0]}}); ASSERT_EQ(instance->imported_functions.size(), 1); @@ -52,8 +53,8 @@ TEST(instantiate, imported_functions_multiple) "0061736d0100000001090260017f017f600000021702036d6f6404666f6f310000036d6f6404666f6f320001"); const auto module = parse(bin); - auto host_foo1 = [](Instance&, span, int) { return Trap; }; - auto host_foo2 = [](Instance&, span, int) { return Trap; }; + auto host_foo1 = [](Instance&, span, ThreadContext&) { return Trap; }; + auto host_foo2 = [](Instance&, span, ThreadContext&) { return Trap; }; auto instance = instantiate(module, {{host_foo1, module.typesec[0]}, {host_foo2, module.typesec[1]}}); @@ -88,7 +89,7 @@ TEST(instantiate, imported_function_wrong_type) const auto bin = from_hex("0061736d0100000001060160017f017f020b01036d6f6403666f6f0000"); const auto module = parse(bin); - auto host_foo = [](Instance&, span, int) { return Trap; }; + auto host_foo = [](Instance&, span, ThreadContext&) { return Trap; }; const auto host_foo_type = FuncType{{}, {}}; EXPECT_THROW_MESSAGE(instantiate(module, {{host_foo, host_foo_type}}), instantiate_error, @@ -654,7 +655,7 @@ TEST(instantiate, element_section_fills_imported_table) "0061736d010000000105016000017f020b01016d037461620170000403050400000000090f020041010b020001" "0041020b0202030a1504040041010b040041020b040041030b040041040b"); - auto f0 = [](Instance&, span, int) { return Value{0}; }; + auto f0 = [](Instance&, span, ThreadContext&) { return Value{0}; }; table_elements table(4); table[0] = ExternalFunction{f0, FuncType{{}, {ValType::i32}}}; @@ -682,7 +683,7 @@ TEST(instantiate, element_section_out_of_bounds_doesnt_change_imported_table) "0b0200000a0601040041010b"); Module module = parse(bin); - auto f0 = [](Instance&, span, int) { return Value{0}; }; + auto f0 = [](Instance&, span, ThreadContext&) { return Value{0}; }; table_elements table(3); table[0] = ExternalFunction{f0, FuncType{{}, {ValType::i32}}}; diff --git a/test/utils/fizzy_engine.cpp b/test/utils/fizzy_engine.cpp index 5d3abdcf4..11d93f52a 100644 --- a/test/utils/fizzy_engine.cpp +++ b/test/utils/fizzy_engine.cpp @@ -53,7 +53,8 @@ FuncType translate_signature(std::string_view signature) return func_type; } -fizzy::ExecutionResult env_adler32(fizzy::Instance& instance, fizzy::span args, int) +fizzy::ExecutionResult env_adler32( + fizzy::Instance& instance, fizzy::span args, ThreadContext&) { assert(instance.memory != nullptr); const auto ret = fizzy::test::adler32( diff --git a/tools/wasi/wasi.cpp b/tools/wasi/wasi.cpp index 228c09d91..3d9f31434 100644 --- a/tools/wasi/wasi.cpp +++ b/tools/wasi/wasi.cpp @@ -18,12 +18,14 @@ namespace // and we are a single-run tool. This may change in the future and should reevaluate. uvwasi_t state; -fizzy::ExecutionResult wasi_return_enosys(fizzy::Instance&, fizzy::span, int) +fizzy::ExecutionResult wasi_return_enosys( + fizzy::Instance&, fizzy::span, fizzy::ThreadContext&) { return fizzy::Value{uint32_t{UVWASI_ENOSYS}}; } -fizzy::ExecutionResult wasi_proc_exit(fizzy::Instance&, fizzy::span args, int) +fizzy::ExecutionResult wasi_proc_exit( + fizzy::Instance&, fizzy::span args, fizzy::ThreadContext&) { uvwasi_proc_exit(&state, static_cast(args[0].as())); // Should not reach this. @@ -31,7 +33,7 @@ fizzy::ExecutionResult wasi_proc_exit(fizzy::Instance&, fizzy::span args, int) + fizzy::Instance& instance, fizzy::span args, fizzy::ThreadContext&) { const auto fd = args[0].as(); const auto iov_ptr = args[1].as(); @@ -53,7 +55,7 @@ fizzy::ExecutionResult wasi_fd_write( } fizzy::ExecutionResult wasi_fd_read( - fizzy::Instance& instance, fizzy::span args, int) + fizzy::Instance& instance, fizzy::span args, fizzy::ThreadContext&) { const auto fd = args[0].as(); const auto iov_ptr = args[1].as(); @@ -75,7 +77,7 @@ fizzy::ExecutionResult wasi_fd_read( } fizzy::ExecutionResult wasi_fd_prestat_get( - fizzy::Instance& instance, fizzy::span args, int) + fizzy::Instance& instance, fizzy::span args, fizzy::ThreadContext&) { const auto fd = args[0].as(); const auto prestat_ptr = args[1].as(); @@ -89,7 +91,7 @@ fizzy::ExecutionResult wasi_fd_prestat_get( } fizzy::ExecutionResult wasi_environ_sizes_get( - fizzy::Instance& instance, fizzy::span args, int) + fizzy::Instance& instance, fizzy::span args, fizzy::ThreadContext&) { const auto environc = args[0].as(); const auto environ_buf_size = args[1].as(); From 3dd80fbe534e2f7c917cbbc6c773de003f1ba79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 10 Sep 2020 10:48:13 +0200 Subject: [PATCH 4/5] Add stack space to ThreadContext --- lib/fizzy/instantiate.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/fizzy/instantiate.hpp b/lib/fizzy/instantiate.hpp index 023e8e30f..28ef49ff5 100644 --- a/lib/fizzy/instantiate.hpp +++ b/lib/fizzy/instantiate.hpp @@ -25,6 +25,10 @@ struct Instance; class ThreadContext { public: + Value stack_space[512]; + + size_t space_left = 512; + int depth = 0; void lock() noexcept { ++depth; } From 6e65de2333260e8baac82caeabd3a1facfe35828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 10 Sep 2020 14:16:11 +0200 Subject: [PATCH 5/5] Use thread context space --- lib/fizzy/execute.cpp | 24 +++++++--- lib/fizzy/instantiate.hpp | 11 ++++- lib/fizzy/stack.hpp | 3 ++ test/unittests/execute_call_test.cpp | 71 +++++++++++++++++++++++++--- test/unittests/stack_test.cpp | 6 ++- 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/lib/fizzy/execute.cpp b/lib/fizzy/execute.cpp index 4eb3c152b..bf674ca61 100644 --- a/lib/fizzy/execute.cpp +++ b/lib/fizzy/execute.cpp @@ -463,15 +463,20 @@ __attribute__((no_sanitize("float-cast-overflow"))) inline constexpr float demot class ThreadContextGuard { ThreadContext& m_thread_context; + Value* const m_free_space; public: explicit ThreadContextGuard(ThreadContext& thread_context) noexcept - : m_thread_context{thread_context} + : m_thread_context{thread_context}, m_free_space{m_thread_context.free_space} { m_thread_context.lock(); } - ~ThreadContextGuard() noexcept { m_thread_context.unlock(); } + ~ThreadContextGuard() noexcept + { + m_thread_context.unlock(); + m_thread_context.free_space = m_free_space; + } }; } // namespace @@ -493,10 +498,14 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span const auto& code = instance.module.get_code(func_idx); auto* const memory = instance.memory.get(); - constexpr auto stack_space_size = 128 / sizeof(Value); - Value stack_space[stack_space_size]; + auto* current_thread_context = &thread_context; + OperandStack stack(args, code.local_count, static_cast(code.max_stack_height), - stack_space, std::size(stack_space)); + current_thread_context->free_space, current_thread_context->space_left()); + const auto storage_size_required = + args.size() + code.local_count + static_cast(code.max_stack_height); + if (storage_size_required <= current_thread_context->space_left()) + current_thread_context->free_space += storage_size_required; const Instr* pc = code.instructions.data(); const uint8_t* immediates = code.immediates.data(); @@ -581,7 +590,8 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span const auto called_func_idx = read(immediates); const auto& called_func_type = instance.module.get_function_type(called_func_idx); - if (!invoke_function(called_func_type, called_func_idx, instance, stack, thread_context)) + if (!invoke_function( + called_func_type, called_func_idx, instance, stack, *current_thread_context)) goto trap; break; } @@ -607,7 +617,7 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, span goto trap; if (!invoke_function( - actual_type, called_func->function, instance, stack, thread_context)) + actual_type, called_func->function, instance, stack, *current_thread_context)) goto trap; break; } diff --git a/lib/fizzy/instantiate.hpp b/lib/fizzy/instantiate.hpp index 28ef49ff5..665df13a9 100644 --- a/lib/fizzy/instantiate.hpp +++ b/lib/fizzy/instantiate.hpp @@ -25,9 +25,16 @@ struct Instance; class ThreadContext { public: - Value stack_space[512]; + static constexpr size_t N = 128; - size_t space_left = 512; + Value stack_space[N]; + + Value* free_space = stack_space; + + size_t space_left() const noexcept + { + return static_cast((stack_space + N) - free_space); + } int depth = 0; diff --git a/lib/fizzy/stack.hpp b/lib/fizzy/stack.hpp index 41a56eebc..7e13b4ca8 100644 --- a/lib/fizzy/stack.hpp +++ b/lib/fizzy/stack.hpp @@ -106,7 +106,10 @@ class OperandStack } m_bottom = m_locals + num_args + num_local_variables; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" m_top = m_bottom - 1; +#pragma GCC diagnostic pop std::copy(std::begin(args), std::end(args), m_locals); std::fill_n(m_locals + num_args, num_local_variables, 0); diff --git a/test/unittests/execute_call_test.cpp b/test/unittests/execute_call_test.cpp index dc7b88600..e8b6866ab 100644 --- a/test/unittests/execute_call_test.cpp +++ b/test/unittests/execute_call_test.cpp @@ -58,6 +58,34 @@ TEST(execute_call, call_with_arguments) EXPECT_THAT(execute(parse(wasm), 1, {}), Result(4)); } +TEST(execute_call, call_shared_stack_space) +{ + /* wat2wasm + (module + (func $double (param i32) (result i64) + local.get 0 + i64.extend_i32_u + local.get 0 + i64.extend_i32_u + i64.add + ) + + (func $main (param i32) (param i32) (result i64) + local.get 1 + local.get 0 + i32.add + call $double + ) + ) + */ + const auto wasm = from_hex( + "0061736d01000000010c0260017f017e60027f7f017e03030200010a150209002000ad2000ad7c0b0900200120" + "006a10000b"); + + auto instance = instantiate(parse(wasm)); + EXPECT_THAT(execute(*instance, 1, {2, 3}), Result(10)); // double(2+3) +} + TEST(execute_call, call_indirect) { /* wat2wasm @@ -205,6 +233,36 @@ TEST(execute_call, call_indirect_uninited_table) EXPECT_THAT(execute(module, 3, {4}), Traps()); } +TEST(execute_call, call_indirect_shared_stack_space) +{ + /* wat2wasm + (module + (type $ft (func (param i32) (result i64))) + (func $double (param i32) (result i64) + local.get 0 + i64.extend_i32_u + local.get 0 + i64.extend_i32_u + i64.add + ) + + (func $main (param i32) (param i32) (result i64) + local.get 1 + local.get 0 + call_indirect (type $ft) + ) + + (table anyfunc (elem $double)) + ) + */ + const auto wasm = from_hex( + "0061736d01000000010c0260017f017e60027f7f017e0303020001040501700101010907010041000b01000a15" + "0209002000ad2000ad7c0b0900200120001100000b"); + + auto instance = instantiate(parse(wasm)); + EXPECT_THAT(execute(*instance, 1, {0, 10}), Result(20)); // double(10) +} + TEST(execute_call, imported_function_call) { /* wat2wasm @@ -263,16 +321,16 @@ TEST(execute_call, imported_functions_call_indirect) (func $sqr (import "env" "sqr") (param i32) (result i64)) (func $isqrt (import "env" "isqrt") (param i32) (result i64)) (func $double (param i32) (result i64) - get_local 0 - i64.extend_u/i32 - get_local 0 - i64.extend_u/i32 + local.get 0 + i64.extend_i32_u + local.get 0 + i64.extend_i32_u i64.add ) (func $main (param i32) (param i32) (result i64) - get_local 1 - get_local 0 + local.get 1 + local.get 0 call_indirect (type $ft) ) @@ -529,6 +587,7 @@ TEST(execute_call, call_imported_infinite_recursion) auto instance = instantiate(module, {{host_foo, host_foo_type}}); EXPECT_THAT(execute(*instance, 0, {}), Traps()); + EXPECT_THAT(execute(*instance, 1, {}), Traps()); } TEST(execute_call, call_indirect_imported_table_infinite_recursion) diff --git a/test/unittests/stack_test.cpp b/test/unittests/stack_test.cpp index b9944b8b5..9b97a5e45 100644 --- a/test/unittests/stack_test.cpp +++ b/test/unittests/stack_test.cpp @@ -10,9 +10,11 @@ using namespace testing; namespace { -intptr_t address_diff(const void* a, const void* b) noexcept +/// Computes absolute difference of two addresses. +/*[[clang::no_sanitize("address")]]*/ intptr_t address_diff(const void* a, const void* b) noexcept { - return std::abs(reinterpret_cast(a) - reinterpret_cast(b)); + const auto diff = reinterpret_cast(a) - reinterpret_cast(b); + return std::abs(diff); } } // namespace