Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread stack space #529

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 40 additions & 23 deletions lib/fizzy/execute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ void branch(const Code& code, OperandStack& stack, const Instr*& pc, const uint8

template <class F>
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<const Value> 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;
Expand All @@ -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<const Value> args, int _depth) noexcept {
return execute(_instance, func_idx, args, _depth);
const auto func = [func_idx](Instance& _instance, span<const Value> 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 <typename T>
Expand Down Expand Up @@ -459,28 +460,46 @@ __attribute__((no_sanitize("float-cast-overflow"))) inline constexpr float demot
return static_cast<float>(value);
}

class ThreadContextGuard
{
ThreadContext& m_thread_context;
Value* const m_free_space;

public:
explicit ThreadContextGuard(ThreadContext& thread_context) noexcept
: m_thread_context{thread_context}, m_free_space{m_thread_context.free_space}
{
m_thread_context.lock();
}

~ThreadContextGuard() noexcept
{
m_thread_context.unlock();
m_thread_context.free_space = m_free_space;
}
};

} // namespace

ExecutionResult execute(
Instance& instance, FuncIdx func_idx, span<const Value> args, int depth) noexcept
ExecutionResult execute(Instance& instance, FuncIdx func_idx, span<const Value> args,
ThreadContext& thread_context) noexcept
{
assert(depth >= 0);
if (depth > CallStackLimit)
if (thread_context.depth > CallStackLimit)
return Trap;

ThreadContextGuard guard{thread_context};

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_idx = func_idx - instance.imported_functions.size();
assert(code_idx < instance.module.codesec.size());

const auto& code = instance.module.codesec[code_idx];
auto* const memory = instance.memory.get();

std::vector<Value> locals(args.size() + code.local_count);
std::copy_n(args.begin(), args.size(), locals.begin());

OperandStack stack(static_cast<size_t>(code.max_stack_height));
OperandStack stack(args, code.local_count, static_cast<size_t>(code.max_stack_height),
thread_context.free_space, thread_context.space_left());

const Instr* pc = code.instructions.data();
const uint8_t* immediates = code.immediates.data();
Expand Down Expand Up @@ -565,7 +584,7 @@ ExecutionResult execute(
const auto called_func_idx = read<uint32_t>(immediates);
const auto& func_type = instance.module.get_function_type(called_func_idx);

if (!invoke_function(func_type, called_func_idx, instance, stack, depth))
if (!invoke_function(func_type, called_func_idx, instance, stack, thread_context))
goto trap;
break;
}
Expand All @@ -590,7 +609,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;
}
Expand All @@ -614,22 +634,19 @@ ExecutionResult execute(
case Instr::local_get:
{
const auto idx = read<uint32_t>(immediates);
assert(idx <= locals.size());
stack.push(locals[idx]);
stack.push(stack.local(idx));
break;
}
case Instr::local_set:
{
const auto idx = read<uint32_t>(immediates);
assert(idx <= locals.size());
locals[idx] = stack.pop();
stack.local(idx) = stack.pop();
break;
}
case Instr::local_tee:
{
const auto idx = read<uint32_t>(immediates);
assert(idx <= locals.size());
locals[idx] = stack.top();
stack.local(idx) = stack.top();
break;
}
case Instr::global_get:
Expand Down
14 changes: 11 additions & 3 deletions lib/fizzy/execute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,17 @@ struct ExecutionResult
constexpr ExecutionResult Void{true};
constexpr ExecutionResult Trap{false};

// Execute a function on an instance.
ExecutionResult execute(
Instance& instance, FuncIdx func_idx, span<const Value> args, int depth = 0) noexcept;
/// Execute a function on an instance.
ExecutionResult execute(Instance& instance, FuncIdx func_idx, span<const Value> args,
ThreadContext& thread_context) noexcept;

/// Execute a function on an instance with implicit depth 0.
inline ExecutionResult execute(
Instance& instance, FuncIdx func_idx, span<const Value> args) noexcept
{
ThreadContext thread_context;
return execute(instance, func_idx, args, thread_context);
}

inline ExecutionResult execute(
Instance& instance, FuncIdx func_idx, std::initializer_list<Value> args) noexcept
Expand Down
13 changes: 9 additions & 4 deletions lib/fizzy/instantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ std::unique_ptr<Instance> instantiate(Module module,
for (const auto idx : instance->module.elementsec[i].init)
{
auto func = [idx, &instance_ref = *instance](fizzy::Instance&, span<const Value> 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)};
Expand Down Expand Up @@ -361,7 +363,9 @@ std::unique_ptr<Instance> instantiate(Module module,
auto& table_function = (*it_table)->function;
table_function = [shared_instance, func = std::move(table_function)](
fizzy::Instance& _instance, span<const Value> args,
int depth) { return func(_instance, args, depth); };
ThreadContext& thread_context) {
return func(_instance, args, thread_context);
};
++it_table;
}
}
Expand Down Expand Up @@ -432,8 +436,9 @@ std::optional<ExternalFunction> find_exported_function(Instance& instance, std::
return std::nullopt;

const auto idx = *opt_index;
auto func = [idx, &instance](fizzy::Instance&, span<const Value> args, int depth) {
return execute(instance, idx, args, depth);
auto func = [idx, &instance](
fizzy::Instance&, span<const Value> args, ThreadContext& thread_context) {
return execute(instance, idx, args, thread_context);
};

return ExternalFunction{std::move(func), instance.module.get_function_type(idx)};
Expand Down
25 changes: 23 additions & 2 deletions lib/fizzy/instantiate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,30 @@ namespace fizzy
struct ExecutionResult;
struct Instance;

class ThreadContext
{
public:
static constexpr size_t N = 512;

Value stack_space[N];

Value* free_space = stack_space;

size_t space_left() const noexcept
{
return static_cast<size_t>((stack_space + N) - free_space);
}

int depth = 0;

void lock() noexcept { ++depth; }

void unlock() noexcept { --depth; }
};

struct ExternalFunction
{
std::function<ExecutionResult(Instance&, span<const Value>, int depth)> function;
std::function<ExecutionResult(Instance&, span<const Value>, ThreadContext&)> function;
FuncType type;
};

Expand Down Expand Up @@ -102,7 +123,7 @@ struct ImportedFunction
std::string name;
std::vector<ValType> inputs;
std::optional<ValType> output;
std::function<ExecutionResult(Instance&, span<const Value>, int depth)> function;
std::function<ExecutionResult(Instance&, span<const Value>, ThreadContext&)> function;
};

// Create vector of ExternalFunctions ready to be passed to instantiate.
Expand Down
50 changes: 31 additions & 19 deletions lib/fizzy/stack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include "span.hpp"
#include "value.hpp"
#include <cassert>
#include <cstdint>
Expand Down Expand Up @@ -51,47 +52,58 @@ class Stack

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, or below the stack bottom if stack is empty.
///
/// This pointer always alias m_storage, but it is kept as the first field
/// because it is accessed the most. Therefore, it must be initialized
/// in the constructor after the m_storage.
Value* m_top;

/// The pre-allocated internal storage.
Value m_small_storage[small_storage_size];
Value* m_bottom;

Value* m_locals;

/// The unbounded storage for items.
std::unique_ptr<Value[]> m_large_storage;

Value* bottom() noexcept { return m_large_storage ? m_large_storage.get() : m_small_storage; }

const Value* bottom() const noexcept
{
return m_large_storage ? m_large_storage.get() : m_small_storage;
}

public:
/// Default constructor.
///
/// Based on @p max_stack_height decides if to use small pre-allocated storage or allocate
/// large storage.
/// Sets the top item pointer to below the stack bottom.
explicit OperandStack(size_t max_stack_height)
OperandStack(span<const Value> args, size_t num_locals, size_t max_stack_height, Value*& external_storage, size_t external_storage_size)
{
if (max_stack_height > small_storage_size)
m_large_storage = std::make_unique<Value[]>(max_stack_height);
m_top = bottom() - 1;
const auto num_args = args.size();
const auto storage_size_required = num_args + num_locals + max_stack_height;

Value* storage;
if (storage_size_required <= external_storage_size)
{
storage = external_storage;
external_storage += storage_size_required;
}
else
{
m_large_storage = std::make_unique<Value[]>(storage_size_required);
storage = &m_large_storage[0];
}

m_locals = storage;
m_bottom = m_locals + num_args + num_locals;
m_top = m_bottom - 1;

std::copy(std::begin(args), std::end(args), m_locals);
std::fill_n(m_locals + num_args, num_locals, 0);
}

OperandStack(const OperandStack&) = delete;
OperandStack& operator=(const OperandStack&) = delete;

Value& local(size_t index) noexcept { return m_locals[index]; }

/// The current number of items on the stack (aka stack height).
size_t size() const noexcept { return static_cast<size_t>(m_top + 1 - bottom()); }
size_t size() const noexcept { return static_cast<size_t>(m_top + 1 - m_bottom); }

/// Returns the reference to the top item.
/// Requires non-empty stack.
Expand Down Expand Up @@ -135,11 +147,11 @@ class OperandStack
{
assert(new_size <= size());
// For new_size == 0, the m_top will point below the storage.
m_top = bottom() + new_size - 1;
m_top = m_bottom + new_size - 1;
}

/// Returns iterator to the bottom of the stack.
const Value* rbegin() const noexcept { return bottom(); }
const Value* rbegin() const noexcept { return m_bottom; }

/// Returns end iterator counting from the bottom of the stack.
const Value* rend() const noexcept { return m_top + 1; }
Expand Down
17 changes: 10 additions & 7 deletions test/unittests/api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ namespace
{
auto function_returning_value(Value value) noexcept
{
return [value](Instance&, span<const Value>, int) { return value; };
return [value](Instance&, span<const Value>, ThreadContext&) { return value; };
}

ExecutionResult function_returning_void(Instance&, span<const Value>, int) noexcept
ExecutionResult function_returning_void(Instance&, span<const Value>, ThreadContext&) noexcept
{
return Void;
}
Expand Down Expand Up @@ -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);
Expand All @@ -262,15 +263,15 @@ TEST(api, find_exported_function)
"0061736d010000000105016000017f021001087370656374657374036261720000040401700000050401010102"
"0606017f0041000b07170403666f6f000001670300037461620100036d656d0200");

auto bar = [](Instance&, span<const Value>, int) { return Value{42}; };
auto bar = [](Instance&, span<const Value>, ThreadContext&) { return Value{42}; };
const auto bar_type = FuncType{{}, {ValType::i32}};

auto instance_reexported_function =
instantiate(parse(wasm_reexported_function), {{bar, bar_type}});

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);
Expand Down Expand Up @@ -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);
Expand Down
Loading