Skip to content

Commit

Permalink
api: Add function for resolving imported globals
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 committed Nov 18, 2020
1 parent 0ae57d0 commit 2bf2e87
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 59 deletions.
105 changes: 74 additions & 31 deletions lib/fizzy/instantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,62 @@ Value eval_constant_expression(ConstantExpression expr,
return globals[global_idx - imported_globals.size()];
}

ExternalFunction find_imported_function(const std::string& module, const std::string& name,
FuncType module_func_type, const std::vector<ImportedFunction>& imported_functions)
{
const auto it = std::find_if(imported_functions.begin(), imported_functions.end(),
[module, name](const auto& func) { return module == func.module && name == func.name; });

if (it == imported_functions.end())
{
throw instantiate_error{"imported function " + module + "." + name + " is required"};
}

if (module_func_type.inputs != it->inputs)
{
throw instantiate_error{"function " + module + "." + name +
" input types don't match imported function in module"};
}
if (module_func_type.outputs.empty() && it->output.has_value())
{
throw instantiate_error{
"function " + module + "." + name + " has output but is defined void in module"};
}
if (!module_func_type.outputs.empty() &&
(!it->output.has_value() || module_func_type.outputs[0] != *it->output))
{
throw instantiate_error{"function " + module + "." + name +
" output type doesn't match imported function in module"};
}

return {it->function, module_func_type};
}

ExternalGlobal find_imported_global(const std::string& module, const std::string& name,
GlobalType module_global_type, const std::vector<ImportedGlobal>& imported_globals)
{
const auto it = std::find_if(imported_globals.begin(), imported_globals.end(),
[module, name](const auto& func) { return module == func.module && name == func.name; });

if (it == imported_globals.end())
{
throw instantiate_error{"imported global " + module + "." + name + " is required"};
}

if (module_global_type.value_type != it->type)
{
throw instantiate_error{"global " + module + "." + name +
" value type doesn't match imported global in module"};
}
if (module_global_type.is_mutable != it->is_mutable)
{
throw instantiate_error{"global " + module + "." + name +
" mutability doesn't match imported global in module"};
}

return {it->value, module_global_type};
}

std::optional<uint32_t> find_export(const Module& module, ExternalKind kind, std::string_view name)
{
const auto it = std::find_if(module.exportsec.begin(), module.exportsec.end(),
Expand Down Expand Up @@ -369,51 +425,38 @@ std::unique_ptr<Instance> instantiate(std::unique_ptr<const Module> module,
}

std::vector<ExternalFunction> resolve_imported_functions(
const Module& module, std::vector<ImportedFunction> imported_functions)
const Module& module, const std::vector<ImportedFunction>& imported_functions)
{
std::vector<ExternalFunction> external_functions;
for (const auto& import : module.importsec)
{
if (import.kind != ExternalKind::Function)
continue;

const auto it = std::find_if(
imported_functions.begin(), imported_functions.end(), [&import](const auto& func) {
return import.module == func.module && import.name == func.name;
});

if (it == imported_functions.end())
{
throw instantiate_error{
"imported function " + import.module + "." + import.name + " is required"};
}

assert(import.desc.function_type_index < module.typesec.size());
const auto& module_func_type = module.typesec[import.desc.function_type_index];

if (module_func_type.inputs != it->inputs)
{
throw instantiate_error{"function " + import.module + "." + import.name +
" input types don't match imported function in module"};
}
if (module_func_type.outputs.empty() && it->output.has_value())
{
throw instantiate_error{"function " + import.module + "." + import.name +
" has output but is defined void in module"};
}
if (!module_func_type.outputs.empty() &&
(!it->output.has_value() || module_func_type.outputs[0] != *it->output))
{
throw instantiate_error{"function " + import.module + "." + import.name +
" output type doesn't match imported function in module"};
}

external_functions.emplace_back(ExternalFunction{it->function, module_func_type});
external_functions.emplace_back(find_imported_function(
import.module, import.name, module_func_type, imported_functions));
}

return external_functions;
}

std::vector<ExternalGlobal> resolve_imported_globals(
const Module& module, const std::vector<ImportedGlobal>& imported_globals)
{
std::vector<ExternalGlobal> external_globals;
for (const auto& import : module.importsec)
{
if (import.kind != ExternalKind::Global)
continue;

external_globals.emplace_back(
find_imported_global(import.module, import.name, import.desc.global, imported_globals));
}
return external_globals;
}

std::optional<FuncIdx> find_exported_function(const Module& module, std::string_view name)
{
return find_export(module, ExternalKind::Function, name);
Expand Down
24 changes: 20 additions & 4 deletions lib/fizzy/instantiate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ std::unique_ptr<Instance> instantiate(std::unique_ptr<const Module> module,
std::vector<ExternalGlobal> imported_globals = {},
uint32_t memory_pages_limit = DefaultMemoryPagesLimit);

// Function that should be used by instantiate as imports, identified by module and function name.
// Function that should be used by instantiate as import, identified by module and function name.
struct ImportedFunction
{
std::string module;
Expand All @@ -118,10 +118,26 @@ struct ImportedFunction
};

// Create vector of ExternalFunctions ready to be passed to instantiate.
// imported_functions may be in any order,
// but must contain functions for all of the imported function names defined in the module.
// imported_functions may be in any order, but must contain functions for all of the imported
// function names defined in the module.
std::vector<ExternalFunction> resolve_imported_functions(
const Module& module, std::vector<ImportedFunction> imported_functions);
const Module& module, const std::vector<ImportedFunction>& imported_functions);

// Global that should be used by instantiate as import, identified by module and global name.
struct ImportedGlobal
{
std::string module;
std::string name;
Value* value = nullptr;
ValType type = ValType::i32;
bool is_mutable = false;
};

// Create vector of ExternalGlobals ready to be passed to instantiate.
// imported_globals may be in any order, but must contain globals for all of the imported global
// names defined in the module.
std::vector<ExternalGlobal> resolve_imported_globals(
const Module& module, const std::vector<ImportedGlobal>& imported_globals);

// Find exported function index by name.
std::optional<FuncIdx> find_exported_function(const Module& module, std::string_view name);
Expand Down
42 changes: 18 additions & 24 deletions test/unittests/api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,14 @@ TEST(api, resolve_imported_functions)
"04666f6f320001046d6f643204666f6f310001046d6f643204666f6f320002046d6f64310167037f00");
const auto module = parse(wasm);

std::vector<ImportedFunction> imported_functions = {
const std::vector<ImportedFunction> imported_functions = {
{"mod1", "foo1", {}, ValType::i32, function_returning_value(0)},
{"mod1", "foo2", {ValType::i32}, ValType::i32, function_returning_value(1)},
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
{"mod2", "foo2", {ValType::i64, ValType::i32}, std::nullopt, function_returning_void},
};

const auto external_functions =
resolve_imported_functions(*module, std::move(imported_functions));
const auto external_functions = resolve_imported_functions(*module, imported_functions);

EXPECT_EQ(external_functions.size(), 4);

Expand All @@ -105,27 +104,27 @@ TEST(api, resolve_imported_functions)
EXPECT_THAT(execute(*instance, 3, {0, 0}), Result());


std::vector<ImportedFunction> imported_functions_reordered = {
const std::vector<ImportedFunction> imported_functions_reordered = {
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
{"mod1", "foo2", {ValType::i32}, ValType::i32, function_returning_value(1)},
{"mod1", "foo1", {}, ValType::i32, function_returning_value(0)},
{"mod2", "foo2", {ValType::i64, ValType::i32}, std::nullopt, function_returning_void},
};

const auto external_functions_reordered =
resolve_imported_functions(*module, std::move(imported_functions_reordered));
resolve_imported_functions(*module, imported_functions_reordered);
EXPECT_EQ(external_functions_reordered.size(), 4);

auto instance_reordered = instantiate(*module, external_functions_reordered, {}, {},
std::vector<ExternalGlobal>(external_globals));
auto instance_reordered =
instantiate(*module, external_functions_reordered, {}, {}, external_globals);

EXPECT_THAT(execute(*instance_reordered, 0, {}), Result(0));
EXPECT_THAT(execute(*instance_reordered, 1, {Value{0}}), Result(1));
EXPECT_THAT(execute(*instance_reordered, 2, {Value{0}}), Result(2));
EXPECT_THAT(execute(*instance_reordered, 3, {0, 0}), Result());


std::vector<ImportedFunction> imported_functions_extra = {
const std::vector<ImportedFunction> imported_functions_extra = {
{"mod1", "foo1", {}, ValType::i32, function_returning_value(0)},
{"mod1", "foo2", {ValType::i32}, ValType::i32, function_returning_value(1)},
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
Expand All @@ -135,60 +134,56 @@ TEST(api, resolve_imported_functions)
};

const auto external_functions_extra =
resolve_imported_functions(*module, std::move(imported_functions_extra));
resolve_imported_functions(*module, imported_functions_extra);
EXPECT_EQ(external_functions_extra.size(), 4);

auto instance_extra = instantiate(
*module, external_functions_extra, {}, {}, std::vector<ExternalGlobal>(external_globals));
auto instance_extra = instantiate(*module, external_functions_extra, {}, {}, external_globals);

EXPECT_THAT(execute(*instance_extra, 0, {}), Result(0));
EXPECT_THAT(execute(*instance_extra, 1, {Value{0}}), Result(1));
EXPECT_THAT(execute(*instance_extra, 2, {Value{0}}), Result(2));
EXPECT_THAT(execute(*instance_extra, 3, {0, 0}), Result());


std::vector<ImportedFunction> imported_functions_missing = {
const std::vector<ImportedFunction> imported_functions_missing = {
{"mod1", "foo1", {}, ValType::i32, function_returning_value(0)},
{"mod1", "foo2", {ValType::i32}, ValType::i32, function_returning_value(1)},
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
};

EXPECT_THROW_MESSAGE(resolve_imported_functions(*module, std::move(imported_functions_missing)),
EXPECT_THROW_MESSAGE(resolve_imported_functions(*module, imported_functions_missing),
instantiate_error, "imported function mod2.foo2 is required");


std::vector<ImportedFunction> imported_functions_invalid_type1 = {
const std::vector<ImportedFunction> imported_functions_invalid_type1 = {
{"mod1", "foo1", {ValType::i32}, ValType::i32, function_returning_value(0)},
{"mod1", "foo2", {ValType::i32}, ValType::i32, function_returning_value(1)},
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
{"mod2", "foo2", {ValType::i64, ValType::i32}, std::nullopt, function_returning_void},
};

EXPECT_THROW_MESSAGE(
resolve_imported_functions(*module, std::move(imported_functions_invalid_type1)),
EXPECT_THROW_MESSAGE(resolve_imported_functions(*module, imported_functions_invalid_type1),
instantiate_error,
"function mod1.foo1 input types don't match imported function in module");

std::vector<ImportedFunction> imported_functions_invalid_type2 = {
const std::vector<ImportedFunction> imported_functions_invalid_type2 = {
{"mod1", "foo1", {}, ValType::i32, function_returning_value(0)},
{"mod1", "foo2", {ValType::i32}, ValType::i32, function_returning_value(1)},
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
{"mod2", "foo2", {ValType::i64, ValType::i32}, ValType::i64, function_returning_value(3)},
};

EXPECT_THROW_MESSAGE(
resolve_imported_functions(*module, std::move(imported_functions_invalid_type2)),
EXPECT_THROW_MESSAGE(resolve_imported_functions(*module, imported_functions_invalid_type2),
instantiate_error, "function mod2.foo2 has output but is defined void in module");

std::vector<ImportedFunction> imported_functions_invalid_type3 = {
const std::vector<ImportedFunction> imported_functions_invalid_type3 = {
{"mod1", "foo1", {}, ValType::i32, function_returning_value(0)},
{"mod1", "foo2", {ValType::i32}, ValType::i64, function_returning_value(1)},
{"mod2", "foo1", {ValType::i32}, ValType::i32, function_returning_value(2)},
{"mod2", "foo2", {ValType::i64, ValType::i32}, std::nullopt, function_returning_void},
};

EXPECT_THROW_MESSAGE(
resolve_imported_functions(*module, std::move(imported_functions_invalid_type3)),
EXPECT_THROW_MESSAGE(resolve_imported_functions(*module, imported_functions_invalid_type3),
instantiate_error,
"function mod1.foo2 output type doesn't match imported function in module");
}
Expand All @@ -207,8 +202,7 @@ TEST(api, resolve_imported_function_duplicate)
{"mod1", "foo1", {ValType::i32}, ValType::i32, function_returning_value(42)},
};

const auto external_functions =
resolve_imported_functions(*module, std::move(imported_functions));
const auto external_functions = resolve_imported_functions(*module, imported_functions);

EXPECT_EQ(external_functions.size(), 2);

Expand Down

0 comments on commit 2bf2e87

Please sign in to comment.