Skip to content

Commit bc7632d

Browse files
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.
1 parent 21239c1 commit bc7632d

File tree

3 files changed

+90
-68
lines changed

3 files changed

+90
-68
lines changed

src/tools/wasm-ctor-eval.cpp

Lines changed: 80 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -115,63 +115,79 @@ class EvallingModuleRunner : public ModuleRunnerBase<EvallingModuleRunner> {
115115
}
116116
};
117117

118-
// Build an artificial `env` module based on a module's imports, so that the
118+
// Build artificial modules based on a module's imports, so that the
119119
// interpreter can use correct object instances. It initializes usable global
120120
// imports, and fills the rest with fake values since those are dangerous to
121-
// use. we will fail if dangerous globals are used.
122-
std::unique_ptr<Module> buildEnvModule(Module& wasm) {
123-
auto env = std::make_unique<Module>();
124-
env->name = "env";
125-
126-
// create empty functions with similar signature
127-
ModuleUtils::iterImportedFunctions(wasm, [&](Function* func) {
128-
if (func->module == env->name) {
129-
Builder builder(*env);
130-
auto* copied = ModuleUtils::copyFunction(func, *env);
131-
copied->module = Name();
132-
copied->base = Name();
133-
copied->body = builder.makeUnreachable();
134-
env->addExport(
135-
builder.makeExport(func->base, copied->name, ExternalKind::Function));
136-
}
137-
});
138-
139-
// create tables with similar initial and max values
140-
ModuleUtils::iterImportedTables(wasm, [&](Table* table) {
141-
if (table->module == env->name) {
142-
auto* copied = ModuleUtils::copyTable(table, *env);
143-
copied->module = Name();
144-
copied->base = Name();
145-
env->addExport(Builder(*env).makeExport(
146-
table->base, copied->name, ExternalKind::Table));
147-
}
148-
});
149-
150-
ModuleUtils::iterImportedGlobals(wasm, [&](Global* global) {
151-
if (global->module == env->name) {
152-
auto* copied = ModuleUtils::copyGlobal(global, *env);
153-
copied->module = Name();
154-
copied->base = Name();
155-
156-
Builder builder(*env);
157-
copied->init = builder.makeConst(Literal::makeZero(global->type));
158-
env->addExport(
159-
builder.makeExport(global->base, copied->name, ExternalKind::Global));
160-
}
161-
});
162-
163-
// create an exported memory with the same initial and max size
164-
ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) {
165-
if (memory->module == env->name) {
166-
auto* copied = ModuleUtils::copyMemory(memory, *env);
167-
copied->module = Name();
168-
copied->base = Name();
169-
env->addExport(Builder(*env).makeExport(
170-
memory->base, copied->name, ExternalKind::Memory));
171-
}
172-
});
121+
// use. Imported globals can't be read anyway; see
122+
// `EvallingModuleRunner::visitGlobalGet`.
123+
// Note: wasi_ modules have stubs generated but won't be called due to the
124+
// special handling in `CtorEvalExternalInterface::getImportedFunction`. We
125+
// still generate the stubs to ensure the link-time validation passes.
126+
std::vector<std::unique_ptr<Module>> buildStubModules(Module& wasm) {
127+
std::map<Name, std::unique_ptr<Module>> modules;
128+
129+
ModuleUtils::iterImports(
130+
wasm,
131+
[&modules](std::variant<Memory*, Table*, Global*, Function*, Tag*> import) {
132+
Importable* importable =
133+
std::visit([](auto* i) -> Importable* { return i; }, import);
134+
135+
auto [it, inserted] = modules.try_emplace(importable->module, nullptr);
136+
if (inserted) {
137+
it->second = std::make_unique<Module>();
138+
it->second->name = importable->module;
139+
}
140+
Module* module = it->second.get();
141+
142+
struct Visitor {
143+
Module* module;
144+
void operator()(Memory* memory) {
145+
auto* copied = ModuleUtils::copyMemory(memory, *module);
146+
copied->module = Name();
147+
copied->base = Name();
148+
module->addExport(Builder(*module).makeExport(
149+
memory->base, copied->name, ExternalKind::Memory));
150+
}
151+
void operator()(Table* table) {
152+
// create tables with similar initial and max values
153+
auto* copied = ModuleUtils::copyTable(table, *module);
154+
copied->module = Name();
155+
copied->base = Name();
156+
module->addExport(Builder(*module).makeExport(
157+
table->base, copied->name, ExternalKind::Table));
158+
}
159+
void operator()(Global* global) {
160+
auto* copied = ModuleUtils::copyGlobal(global, *module);
161+
copied->module = Name();
162+
copied->base = Name();
163+
164+
Builder builder(*module);
165+
copied->init = builder.makeConst(Literal::makeZero(global->type));
166+
module->addExport(builder.makeExport(
167+
global->base, copied->name, ExternalKind::Global));
168+
}
169+
void operator()(Function* func) {
170+
Builder builder(*module);
171+
auto* copied = ModuleUtils::copyFunction(func, *module);
172+
copied->module = Name();
173+
copied->base = Name();
174+
copied->body = builder.makeUnreachable();
175+
module->addExport(builder.makeExport(
176+
func->base, copied->name, ExternalKind::Function));
177+
}
178+
void operator()(Tag* tag) {
179+
// no-op
180+
}
181+
};
182+
std::visit(Visitor{module}, import);
183+
});
173184

174-
return env;
185+
std::vector<std::unique_ptr<Module>> modulesVector;
186+
modulesVector.reserve(modules.size());
187+
for (auto& [_, ptr] : modules) {
188+
modulesVector.push_back(std::move(ptr));
189+
}
190+
return modulesVector;
175191
}
176192

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

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

1359-
// build and link the env module
1360-
auto envModule = buildEnvModule(wasm);
1361-
CtorEvalExternalInterface envInterface;
1362-
auto envInstance =
1363-
std::make_shared<EvallingModuleRunner>(*envModule, &envInterface);
1364-
linkedInstances[envModule->name] = envInstance;
1375+
// stubModules and interfaces must be kept alive since they are referenced in
1376+
// linkedInstances.
1377+
std::vector<std::unique_ptr<Module>> stubModules = buildStubModules(wasm);
1378+
std::vector<std::unique_ptr<CtorEvalExternalInterface>> interfaces;
1379+
1380+
for (auto& module : stubModules) {
1381+
interfaces.push_back(std::make_unique<CtorEvalExternalInterface>());
1382+
linkedInstances[module->name] =
1383+
std::make_shared<EvallingModuleRunner>(*module, interfaces.back().get());
1384+
}
13651385

13661386
CtorEvalExternalInterface interface(linkedInstances);
13671387
try {
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
(module
22
(import "import" "global" (global $imported i32))
3-
(func $test1 (export "test1")
4-
;; This should be safe to eval in theory, but the imported global stops us,
5-
;; so this function will not be optimized out.
6-
;; TODO: perhaps if we never use that global that is ok?
3+
(func $use-global (export "use-global") (result i32)
4+
(global.get $imported)
75
)
6+
;; The imported global isn't used in the ctor,
7+
;; so we're free to remove it completely.
8+
(func $test1 (export "test1"))
89
)
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
(module
2-
(type $0 (func))
3-
(export "test1" (func $test1))
4-
(func $test1 (type $0)
5-
(nop)
2+
(type $0 (func (result i32)))
3+
(import "import" "global" (global $imported i32))
4+
(export "use-global" (func $use-global))
5+
(func $use-global (type $0) (result i32)
6+
(global.get $imported)
67
)
78
)

0 commit comments

Comments
 (0)