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

Introduce ExecutionContext instead of depth param #766

Merged
merged 5 commits into from
Mar 29, 2021
Merged
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
10 changes: 7 additions & 3 deletions include/fizzy/fizzy.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,24 @@ typedef struct FizzyExecutionResult
FizzyValue value;
} FizzyExecutionResult;

/// The opaque data type representing an execution context.
typedef struct FizzyExecutionContext FizzyExecutionContext;
chfast marked this conversation as resolved.
Show resolved Hide resolved


/// Pointer to external function.
///
/// @param context Opaque pointer to execution context.
/// @param host_ctx Opaque pointer to host context.
/// @param instance Pointer to module instance.
/// @param args Pointer to the argument array. Can be NULL iff function has no inputs.
/// @param depth Call stack depth.
/// @param ctx Opaque pointer to execution context. If NULL new execution context
/// will be allocated.
/// @return Result of execution.
///
/// @note
/// External functions implemented in C++ must be non-throwing, i.e. the effect of any exception
/// escaping the function is std::terminate being called.
typedef FizzyExecutionResult (*FizzyExternalFn)(
void* context, FizzyInstance* instance, const FizzyValue* args, int depth);
void* host_ctx, FizzyInstance* instance, const FizzyValue* args, FizzyExecutionContext* ctx);

/// Value type.
typedef uint8_t FizzyValueType;
Expand Down
43 changes: 30 additions & 13 deletions lib/fizzy/capi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ inline fizzy::Value* unwrap(FizzyValue* value) noexcept
return reinterpret_cast<fizzy::Value*>(value);
}

inline FizzyExecutionContext* wrap(fizzy::ExecutionContext& ctx) noexcept
{
return reinterpret_cast<FizzyExecutionContext*>(&ctx);
}

inline fizzy::ExecutionContext& unwrap(FizzyExecutionContext* ctx) noexcept
{
return *reinterpret_cast<fizzy::ExecutionContext*>(ctx);
}

inline FizzyInstance* wrap(fizzy::Instance* instance) noexcept
{
return reinterpret_cast<FizzyInstance*>(instance);
Expand Down Expand Up @@ -113,29 +123,36 @@ inline fizzy::ExecutionResult unwrap(const FizzyExecutionResult& result) noexcep
inline fizzy::ExecuteFunction unwrap(FizzyExternalFn c_function, void* c_host_context)
{
static constexpr fizzy::HostFunctionPtr function =
[](std::any& host_context, fizzy::Instance& instance, const fizzy::Value* args,
int depth) noexcept {
[](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<std::pair<FizzyExternalFn, void*>>(&host_context);
return unwrap(c_func(c_host_ctx, wrap(&instance), wrap(args), depth));
*std::any_cast<std::pair<FizzyExternalFn, void*>>(&host_ctx);
return unwrap(c_func(c_host_ctx, wrap(&instance), wrap(args), wrap(ctx)));
};

return {function, std::make_any<std::pair<FizzyExternalFn, void*>>(c_function, c_host_context)};
}

inline FizzyExternalFunction wrap(fizzy::ExternalFunction external_func)
{
static constexpr FizzyExternalFn c_function = [](void* context, FizzyInstance* instance,
const FizzyValue* args,
int depth) -> FizzyExecutionResult {
auto* func = static_cast<fizzy::ExternalFunction*>(context);
return wrap((func->function)(*unwrap(instance), unwrap(args), depth));
static constexpr FizzyExternalFn c_function =
[](void* host_ctx, FizzyInstance* instance, const FizzyValue* args,
FizzyExecutionContext* c_ctx) -> FizzyExecutionResult {
// If execution context not provided, allocate new one.
Copy link
Member

@axic axic Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is not provided`

or could just say "Allocate new execution context if none provided."

// It must be allocated on heap otherwise the stack will explode in recursive calls.
std::unique_ptr<fizzy::ExecutionContext> new_ctx;
if (c_ctx == nullptr)
new_ctx = std::make_unique<fizzy::ExecutionContext>();
auto& ctx = new_ctx ? *new_ctx : unwrap(c_ctx);

auto* func = static_cast<fizzy::ExternalFunction*>(host_ctx);
return wrap((func->function)(*unwrap(instance), unwrap(args), ctx));
};

auto context = std::make_unique<fizzy::ExternalFunction>(std::move(external_func));
const auto c_type = wrap(context->input_types, context->output_types);
void* c_context = context.release();
return {c_type, c_function, c_context};
auto host_ctx = std::make_unique<fizzy::ExternalFunction>(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};
}

inline fizzy::ExternalFunction unwrap(const FizzyExternalFunction& external_func)
Expand Down
18 changes: 10 additions & 8 deletions lib/fizzy/execute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,13 +531,14 @@ void branch(const Code& code, OperandStack& stack, const uint8_t*& pc, uint32_t
}

inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instance& instance,
OperandStack& stack, int depth) noexcept
OperandStack& stack, ExecutionContext& ctx) noexcept
{
const auto num_args = func_type.inputs.size();
assert(stack.size() >= num_args);
const auto call_args = stack.rend() - num_args;

const auto ret = execute(instance, func_idx, call_args, depth + 1);
const auto ctx_guard = ctx.increment_call_depth();
const auto ret = execute(instance, func_idx, call_args, ctx);
// Bubble up traps
if (ret.trapped)
return false;
Expand All @@ -557,17 +558,18 @@ inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instan

} // namespace

ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args, int depth) noexcept
ExecutionResult execute(
Instance& instance, FuncIdx func_idx, const Value* args, ExecutionContext& ctx) noexcept
{
assert(depth >= 0);
if (depth >= CallStackLimit)
assert(ctx.depth >= 0);
if (ctx.depth >= CallStackLimit)
return Trap;

const auto& func_type = instance.module->get_function_type(func_idx);

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, ctx);

const auto& code = instance.module->get_code(func_idx);
auto* const memory = instance.memory.get();
Expand Down Expand Up @@ -650,7 +652,7 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args,
const auto called_func_idx = read<uint32_t>(pc);
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, ctx))
goto trap;
break;
}
Expand All @@ -677,7 +679,7 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args,
goto trap;

if (!invoke_function(
actual_type, called_func.func_idx, *called_func.instance, stack, depth))
actual_type, called_func.func_idx, *called_func.instance, stack, ctx))
goto trap;
break;
}
Expand Down
38 changes: 33 additions & 5 deletions lib/fizzy/execute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ constexpr ExecutionResult Void{true};
/// Shortcut for execution that resulted in a trap.
constexpr ExecutionResult Trap{false};

/// The storage for information shared by calls in the same execution "thread".
/// Users may decide how to allocate the execution context, but some good defaults are available.
class ExecutionContext
{
/// Call depth increment guard.
/// It will automatically decrement the call depth to the original value
/// when going out of scope.
class [[nodiscard]] Guard
{
ExecutionContext& m_execution_context; ///< Reference to the guarded execution context.

public:
explicit Guard(ExecutionContext& ctx) noexcept : m_execution_context{ctx} {}
~Guard() noexcept { --m_execution_context.depth; }
};

public:
int depth = 0; ///< Current call depth.

/// Increments the call depth and returns the guard object which decrements
/// the call depth back to the original value when going out of scope.
Guard increment_call_depth() noexcept
{
++depth;
return Guard{*this};
}
};

/// Execute a function from an instance.
///
/// @param instance The instance.
Expand All @@ -49,16 +77,16 @@ constexpr ExecutionResult Trap{false};
/// @param args The pointer to the arguments. The number of items and their types must match
/// the expected number of input parameters of the function, otherwise undefined
/// behaviour (including crash) happens.
/// @param depth The call depth (indexing starts at 0).
/// @param ctx Execution context.
/// @return The result of the execution.
ExecutionResult execute(
Instance& instance, FuncIdx func_idx, const Value* args, int depth) noexcept;
Instance& instance, FuncIdx func_idx, const Value* args, ExecutionContext& ctx) noexcept;

/// Execute a function from an instance starting at default depth of 0.
/// Execute a function from an instance with execution context starting with default depth of 0.
/// Arguments and behavior is the same as in the other execute().
inline ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args) noexcept
{
constexpr int depth = 0;
return execute(instance, func_idx, args, depth);
ExecutionContext ctx;
return execute(instance, func_idx, args, ctx);
}
} // namespace fizzy
6 changes: 3 additions & 3 deletions lib/fizzy/instantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,12 @@ std::optional<uint32_t> find_export(const Module& module, ExternalKind kind, std
} // namespace

ExecutionResult ExecuteFunction::operator()(
Instance& instance, const Value* args, int depth) noexcept
Instance& instance, const Value* args, ExecutionContext& ctx) noexcept
{
if (m_instance)
return execute(*m_instance, m_func_idx, args, depth);
return execute(*m_instance, m_func_idx, args, ctx);
else
return m_host_function(m_host_context, instance, args, depth);
return m_host_function(m_host_context, instance, args, ctx);
}

std::unique_ptr<Instance> instantiate(std::unique_ptr<const Module> module,
Expand Down
6 changes: 4 additions & 2 deletions lib/fizzy/instantiate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
namespace fizzy
{
struct ExecutionResult;
class ExecutionContext;
chfast marked this conversation as resolved.
Show resolved Hide resolved
struct Instance;

/// Function pointer to the execution function.
using HostFunctionPtr = ExecutionResult (*)(
std::any& host_context, Instance&, const Value* args, int depth) noexcept;
std::any& host_context, Instance&, const Value* args, ExecutionContext& ctx) noexcept;

/// Function representing either WebAssembly or host function execution.
class ExecuteFunction
Expand Down Expand Up @@ -63,7 +64,8 @@ class ExecuteFunction
{}

/// Function call operator.
ExecutionResult operator()(Instance& instance, const Value* args, int depth) noexcept;
ExecutionResult operator()(
Instance& instance, const Value* args, ExecutionContext& ctx) noexcept;

/// Function pointer stored inside this object.
HostFunctionPtr get_host_function() const noexcept { return m_host_function; }
Expand Down
18 changes: 10 additions & 8 deletions test/unittests/api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ namespace
ExecuteFunction function_returning_value(Value value)
{
static constexpr HostFunctionPtr func = [](std::any& host_context, Instance&, const Value*,
int) noexcept -> ExecutionResult {
ExecutionContext&) noexcept -> ExecutionResult {
return *std::any_cast<Value>(&host_context);
};

return {func, std::make_any<Value>(value)};
}

ExecutionResult function_returning_void(std::any&, Instance&, const Value*, int) noexcept
ExecutionResult function_returning_void(
std::any&, Instance&, const Value*, ExecutionContext&) noexcept
{
return Void;
}
Expand Down Expand Up @@ -227,7 +228,8 @@ TEST(api, resolve_imported_function_duplicate_with_context)
"0061736d01000000010401600000021902046d6f643104666f6f310000046d6f643104666f6f310000");
const auto module = parse(wasm);

constexpr auto host_func = [](std::any& context, Instance&, const Value*, int) noexcept {
constexpr auto host_func = [](std::any& context, Instance&, const Value*,
ExecutionContext&) noexcept {
auto* counter = *std::any_cast<int*>(&context);
++(*counter);
return Void;
Expand Down Expand Up @@ -446,7 +448,8 @@ TEST(api, find_exported_function)

auto opt_function = find_exported_function(*instance, "foo");
ASSERT_TRUE(opt_function);
EXPECT_THAT(TypedExecutionResult(opt_function->function(*instance, {}, 0), ValType::i32),
ExecutionContext ctx;
EXPECT_THAT(TypedExecutionResult(opt_function->function(*instance, {}, ctx), ValType::i32),
Result(42_u32));
EXPECT_TRUE(opt_function->input_types.empty());
ASSERT_EQ(opt_function->output_types.size(), 1);
Expand All @@ -466,9 +469,8 @@ TEST(api, find_exported_function)
"0061736d010000000105016000017f021001087370656374657374036261720000040401700000050401010102"
"0606017f0041000b07170403666f6f000001670300037461620100036d656d0200");

constexpr auto bar = [](std::any&, Instance&, const Value*, int) noexcept -> ExecutionResult {
return Value{42};
};
constexpr auto bar = [](std::any&, Instance&, const Value*,
ExecutionContext&) noexcept -> ExecutionResult { return Value{42}; };
const auto bar_type = FuncType{{}, {ValType::i32}};

auto instance_reexported_function =
Expand All @@ -477,7 +479,7 @@ TEST(api, find_exported_function)
auto opt_reexported_function = find_exported_function(*instance_reexported_function, "foo");
ASSERT_TRUE(opt_reexported_function);
EXPECT_THAT(
TypedExecutionResult(opt_reexported_function->function(*instance, {}, 0), ValType::i32),
TypedExecutionResult(opt_reexported_function->function(*instance, {}, ctx), ValType::i32),
Result(42_u32));
EXPECT_TRUE(opt_reexported_function->input_types.empty());
ASSERT_EQ(opt_reexported_function->output_types.size(), 1);
Expand Down
27 changes: 15 additions & 12 deletions test/unittests/capi_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ TEST(capi, find_exported_function)
EXPECT_EQ(function.type.output, FizzyValueTypeI32);
EXPECT_NE(function.context, nullptr);
ASSERT_NE(function.function, nullptr);
EXPECT_THAT(function.function(function.context, instance, nullptr, 0), CResult(42_u32));

EXPECT_THAT(function.function(function.context, instance, nullptr, nullptr), CResult(42_u32));

fizzy_free_exported_function(&function);

Expand Down Expand Up @@ -696,7 +697,8 @@ TEST(capi, resolve_instantiate_functions)
module = fizzy_parse(wasm.data(), wasm.size());
ASSERT_NE(module, nullptr);

FizzyExternalFn host_fn = [](void* context, FizzyInstance*, const FizzyValue*, int) {
FizzyExternalFn host_fn = [](void* context, FizzyInstance*, const FizzyValue*,
FizzyExecutionContext*) {
return FizzyExecutionResult{false, true, *static_cast<FizzyValue*>(context)};
};

Expand Down Expand Up @@ -764,7 +766,7 @@ TEST(capi, resolve_instantiate_function_duplicate)
auto module = fizzy_parse(wasm.data(), wasm.size());
ASSERT_NE(module, nullptr);

FizzyExternalFn host_fn = [](void*, FizzyInstance*, const FizzyValue*, int) {
FizzyExternalFn host_fn = [](void*, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) {
return FizzyExecutionResult{false, true, FizzyValue{42}};
};

Expand Down Expand Up @@ -805,7 +807,7 @@ TEST(capi, resolve_instantiate_globals)
module = fizzy_parse(wasm.data(), wasm.size());
ASSERT_NE(module, nullptr);

FizzyExternalFn host_fn = [](void*, FizzyInstance*, const FizzyValue*, int) {
FizzyExternalFn host_fn = [](void*, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) {
return FizzyExecutionResult{true, false, {0}};
};
FizzyImportedFunction mod1foo1 = {
Expand Down Expand Up @@ -1096,13 +1098,14 @@ TEST(capi, execute_with_host_function)

const FizzyValueType inputs[] = {FizzyValueTypeI32, FizzyValueTypeI32};

FizzyExternalFunction host_funcs[] = {{{FizzyValueTypeI32, nullptr, 0},
[](void*, FizzyInstance*, const FizzyValue*, int) {
return FizzyExecutionResult{false, true, {42}};
},
nullptr},
FizzyExternalFunction host_funcs[] = {
{{FizzyValueTypeI32, nullptr, 0},
[](void*, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) {
return FizzyExecutionResult{false, true, {42}};
},
nullptr},
{{FizzyValueTypeI32, &inputs[0], 2},
[](void*, FizzyInstance*, const FizzyValue* args, int) {
[](void*, FizzyInstance*, const FizzyValue* args, FizzyExecutionContext*) {
FizzyValue v;
v.i32 = args[0].i32 / args[1].i32;
return FizzyExecutionResult{false, true, {v}};
Expand Down Expand Up @@ -1134,7 +1137,7 @@ TEST(capi, imported_function_traps)
ASSERT_NE(module, nullptr);

FizzyExternalFunction host_funcs[] = {{{FizzyValueTypeI32, nullptr, 0},
[](void*, FizzyInstance*, const FizzyValue*, int) {
[](void*, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) {
return FizzyExecutionResult{true, false, {}};
},
nullptr}};
Expand Down Expand Up @@ -1162,7 +1165,7 @@ TEST(capi, imported_function_void)

bool called = false;
FizzyExternalFunction host_funcs[] = {{{},
[](void* context, FizzyInstance*, const FizzyValue*, int) {
[](void* context, FizzyInstance*, const FizzyValue*, FizzyExecutionContext*) {
*static_cast<bool*>(context) = true;
return FizzyExecutionResult{false, false, {}};
},
Expand Down
Loading