Stub out all imports in ctor-eval (#8115)
ctor-eval doesn't have access to imports and previously didn't have any
special handling for this. Globals would fail to
import (https://github.com/WebAssembly/binaryen/blob/main/src/tools/wasm-ctor-eval.cpp#L247),
which would prevent further
evaluation (https://github.com/WebAssembly/binaryen/blob/main/src/tools/wasm-ctor-eval.cpp#L1451).
This is why global-get-init.wast didn't optimize away even though it
does nothing.
We already stubbed out imports to "env". Change the code to create a
stub module for all modules named in imports, so that instantiation is
valid and evaluation can continue. This also unblocks #8086 which checks
imports and requires them to exist during instantiation.
diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp
index b33fb5a..7173ec0 100644
--- a/src/tools/wasm-ctor-eval.cpp
+++ b/src/tools/wasm-ctor-eval.cpp
@@ -115,63 +115,79 @@
}
};
-// 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";
+// 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;
- // 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));
- }
- });
+ ModuleUtils::iterImports(
+ wasm,
+ [&modules](std::variant<Memory*, Table*, Global*, Function*, Tag*> import) {
+ Importable* importable =
+ std::visit([](auto* i) -> Importable* { return i; }, import);
- // 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));
- }
- });
+ 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();
- ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) {
- if (global->module == env->name) {
- auto* copied = ModuleUtils::copyGlobal(global, *env);
- copied->module = Name();
- copied->base = Name();
+ 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(*env);
- copied->init = builder.makeConst(Literal::makeZero(global->type));
- env->addExport(
- builder.makeExport(global->base, copied->name, ExternalKind::Global));
- }
- });
+ 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);
+ });
- // 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));
- }
- });
-
- 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
@@ -1356,12 +1372,16 @@
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 {
diff --git a/test/ctor-eval/global-get-init.wast b/test/ctor-eval/global-get-init.wast
index 125e672..5c085e2 100644
--- a/test/ctor-eval/global-get-init.wast
+++ b/test/ctor-eval/global-get-init.wast
@@ -1,8 +1,9 @@
(module
(import "import" "global" (global $imported i32))
- (func $test1 (export "test1")
- ;; This should be safe to eval in theory, but the imported global stops us,
- ;; so this function will not be optimized out.
- ;; TODO: perhaps if we never use that global that is ok?
+ (func $use-global (export "use-global") (result i32)
+ (global.get $imported)
)
+ ;; The imported global isn't used in the ctor,
+ ;; so we're free to remove it completely.
+ (func $test1 (export "test1"))
)
diff --git a/test/ctor-eval/global-get-init.wast.out b/test/ctor-eval/global-get-init.wast.out
index 519e96d..0e5138f 100644
--- a/test/ctor-eval/global-get-init.wast.out
+++ b/test/ctor-eval/global-get-init.wast.out
@@ -1,7 +1,8 @@
(module
- (type $0 (func))
- (export "test1" (func $test1))
- (func $test1 (type $0)
- (nop)
+ (type $0 (func (result i32)))
+ (import "import" "global" (global $imported i32))
+ (export "use-global" (func $use-global))
+ (func $use-global (type $0) (result i32)
+ (global.get $imported)
)
)