Skip to content
Open
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
15 changes: 5 additions & 10 deletions scripts/test/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,15 +440,14 @@ def get_tests(test_dir, extensions=[], recursive=False):
'func.wast', # Duplicate parameter names not properly rejected
'global.wast', # Fail to parse table
'if.wast', # Requires more precise unreachable validation
'imports.wast', # Missing validation of missing function on instantiation
'imports.wast', # Requires fixing handling of mutation to imported globals
'proposals/threads/imports.wast', # Missing memory type validation on instantiation
'linking.wast', # Missing function type validation on instantiation
'proposals/threads/memory.wast', # Missing memory type validation on instantiation
'memory64-imports.wast', # Missing validation on instantiation
'annotations.wast', # String annotations IDs should be allowed
'id.wast', # Empty IDs should be disallowed
# Requires correct handling of tag imports from different instances of the same module,
# ref.null wast constants, and splitting for module instances
# Requires correct handling of tag imports from different instances of the same module
# and splitting for module instances
'instance.wast',
'table64.wast', # Requires validations for table size
'table_grow.wast', # Incorrect table linking semantics in interpreter
Expand All @@ -473,12 +472,8 @@ def get_tests(test_dir, extensions=[], recursive=False):
'type-rec.wast', # Missing function type validation on instantiation
'type-subtyping.wast', # ShellExternalInterface::callTable does not handle subtyping
'call_indirect.wast', # Bug with 64-bit inline element segment parsing
'memory64.wast', # Requires validations for memory size
'imports0.wast', # Missing memory type validation on instantiation
'imports2.wast', # Missing memory type validation on instantiation
'imports3.wast', # Missing memory type validation on instantiation
'linking0.wast', # Missing memory type validation on instantiation
'linking3.wast', # Fatal error on missing table.
'memory64.wast', # Requires validations on the max memory size
'imports3.wast', # Requires better checking of exports from the special "spectest" module
'i16x8_relaxed_q15mulr_s.wast', # Requires wast `either` support
'i32x4_relaxed_trunc.wast', # Requires wast `either` support
'i8x16_relaxed_swizzle.wast', # Requires wast `either` support
Expand Down
10 changes: 7 additions & 3 deletions src/shell-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,10 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
auto inst = getImportInstance(import);
auto* exportedGlobal = inst->wasm.getExportOrNull(import->base);
if (!exportedGlobal || exportedGlobal->kind != ExternalKind::Global) {
Fatal() << "importGlobals: unknown import: " << import->module.str
<< "." << import->name.str;
trap((std::stringstream()
<< "importGlobals: unknown import: " << import->module.str << "."
<< import->name.str)
.str());
Copy link
Member

Choose a reason for hiding this comment

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

I did not know about this nice pattern for stringstream 😄

}
globals[import->name] = inst->globals[*exportedGlobal->getInternalName()];
});
Expand Down Expand Up @@ -325,7 +327,9 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
return true;
}

void trap(const char* why) override {
void trap(const char* why) override { trap(std::string_view(why)); }

void trap(std::string_view why) override {
std::cout << "[trap " << why << "]\n";
throw TrapException();
}
Expand Down
140 changes: 80 additions & 60 deletions src/tools/wasm-ctor-eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,63 +115,79 @@ class EvallingModuleRunner : public ModuleRunnerBase<EvallingModuleRunner> {
}
};

// Build an artificial `env` module based on a module's imports, so that the
// Build artificial modules based on a module's imports, so that the
// interpreter can use correct object instances. It initializes usable global
// imports, and fills the rest with fake values since those are dangerous to
// use. we will fail if dangerous globals are used.
std::unique_ptr<Module> buildEnvModule(Module& wasm) {
auto env = std::make_unique<Module>();
env->name = "env";

// create empty functions with similar signature
ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) {
if (func->module == env->name) {
Builder builder(*env);
auto* copied = ModuleUtils::copyFunction(func, *env);
copied->module = Name();
copied->base = Name();
copied->body = builder.makeUnreachable();
env->addExport(
builder.makeExport(func->base, copied->name, ExternalKind::Function));
}
});

// create tables with similar initial and max values
ModuleUtils::iterImportedTables(wasm, [&](Table* table) {
if (table->module == env->name) {
auto* copied = ModuleUtils::copyTable(table, *env);
copied->module = Name();
copied->base = Name();
env->addExport(Builder(*env).makeExport(
table->base, copied->name, ExternalKind::Table));
}
});

ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) {
if (global->module == env->name) {
auto* copied = ModuleUtils::copyGlobal(global, *env);
copied->module = Name();
copied->base = Name();

Builder builder(*env);
copied->init = builder.makeConst(Literal::makeZero(global->type));
env->addExport(
builder.makeExport(global->base, copied->name, ExternalKind::Global));
}
});

// create an exported memory with the same initial and max size
ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) {
if (memory->module == env->name) {
auto* copied = ModuleUtils::copyMemory(memory, *env);
copied->module = Name();
copied->base = Name();
env->addExport(Builder(*env).makeExport(
memory->base, copied->name, ExternalKind::Memory));
}
});
// use. Imported globals can't be read anyway; see
// `EvallingModuleRunner::visitGlobalGet`.
// Note: wasi_ modules have stubs generated but won't be called due to the
// special handling in `CtorEvalExternalInterface::getImportedFunction`. We
// still generate the stubs to ensure the link-time validation passes.
std::vector<std::unique_ptr<Module>> buildStubModules(Module& wasm) {
std::map<Name, std::unique_ptr<Module>> modules;

ModuleUtils::iterImports(
wasm,
[&modules](std::variant<Memory*, Table*, Global*, Function*, Tag*> import) {
Importable* importable =
std::visit([](auto* i) -> Importable* { return i; }, import);

auto [it, inserted] = modules.try_emplace(importable->module, nullptr);
if (inserted) {
it->second = std::make_unique<Module>();
it->second->name = importable->module;
}
Module* module = it->second.get();

struct Visitor {
Module* module;
void operator()(Memory* memory) {
auto* copied = ModuleUtils::copyMemory(memory, *module);
copied->module = Name();
copied->base = Name();
module->addExport(Builder(*module).makeExport(
memory->base, copied->name, ExternalKind::Memory));
}
void operator()(Table* table) {
// create tables with similar initial and max values
auto* copied = ModuleUtils::copyTable(table, *module);
copied->module = Name();
copied->base = Name();
module->addExport(Builder(*module).makeExport(
table->base, copied->name, ExternalKind::Table));
}
void operator()(Global* global) {
auto* copied = ModuleUtils::copyGlobal(global, *module);
copied->module = Name();
copied->base = Name();

Builder builder(*module);
copied->init = builder.makeConst(Literal::makeZero(global->type));
module->addExport(builder.makeExport(
global->base, copied->name, ExternalKind::Global));
}
void operator()(Function* func) {
Builder builder(*module);
auto* copied = ModuleUtils::copyFunction(func, *module);
copied->module = Name();
copied->base = Name();
copied->body = builder.makeUnreachable();
module->addExport(builder.makeExport(
func->base, copied->name, ExternalKind::Function));
}
void operator()(Tag* tag) {
// no-op
}
};
std::visit(Visitor{module}, import);
});

return env;
std::vector<std::unique_ptr<Module>> modulesVector;
modulesVector.reserve(modules.size());
for (auto& [_, ptr] : modules) {
modulesVector.push_back(std::move(ptr));
}
return modulesVector;
}

// Whether to ignore external input to the program as it runs. If set, we will
Expand Down Expand Up @@ -1356,12 +1372,16 @@ void evalCtors(Module& wasm,

std::map<Name, std::shared_ptr<EvallingModuleRunner>> linkedInstances;

// build and link the env module
auto envModule = buildEnvModule(wasm);
CtorEvalExternalInterface envInterface;
auto envInstance =
std::make_shared<EvallingModuleRunner>(*envModule, &envInterface);
linkedInstances[envModule->name] = envInstance;
// stubModules and interfaces must be kept alive since they are referenced in
// linkedInstances.
std::vector<std::unique_ptr<Module>> stubModules = buildStubModules(wasm);
std::vector<std::unique_ptr<CtorEvalExternalInterface>> interfaces;

for (auto& module : stubModules) {
interfaces.push_back(std::make_unique<CtorEvalExternalInterface>());
linkedInstances[module->name] =
std::make_shared<EvallingModuleRunner>(*module, interfaces.back().get());
}

CtorEvalExternalInterface interface(linkedInstances);
try {
Expand Down
2 changes: 2 additions & 0 deletions src/tools/wasm-shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ struct Shell {
// SIMD instructions.
instance->setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute);
instance->instantiate();
} catch (const std::exception& e) {
return Err{std::string("failed to instantiate module: ") + e.what()};
} catch (...) {
return Err{"failed to instantiate module"};
}
Expand Down
Loading
Loading