From ea2f4351d06814aaf5976c650ae98da6e97c6696 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 10 Dec 2025 22:16:31 +0000 Subject: [PATCH 1/6] Stub out all imports in ctor-eval --- src/tools/wasm-ctor-eval.cpp | 139 ++++++++++++++---------- test/ctor-eval/global-get-init.wast | 6 +- test/ctor-eval/global-get-init.wast.out | 5 - 3 files changed, 80 insertions(+), 70 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index b33fb5aa999..2f4aab6d476 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -115,63 +115,78 @@ class EvallingModuleRunner : public ModuleRunnerBase { } }; -// 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 buildEnvModule(Module& wasm) { - auto env = std::make_unique(); - 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. We will fail if dangerous globals are used. +std::vector> buildStubModules(Module& wasm) { + std::map> modules; + + ModuleUtils::iterImports( + wasm, + [&modules](std::variant import) { + Importable* importable = + std::visit([](auto* i) -> Importable* { return i; }, import); + + Module* module = nullptr; + if (auto it = modules.find(importable->module); it != modules.end()) { + module = it->second.get(); + } else { + auto [inserted, _] = + modules.emplace(importable->module, std::make_unique()); + module = inserted->second.get(); + module->name = importable->module; + } - return env; + 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); + }); + + std::vector> 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 +1371,16 @@ void evalCtors(Module& wasm, std::map> linkedInstances; - // build and link the env module - auto envModule = buildEnvModule(wasm); - CtorEvalExternalInterface envInterface; - auto envInstance = - std::make_shared(*envModule, &envInterface); - linkedInstances[envModule->name] = envInstance; + // stubModules and interfaces must be kept alive since they are referenced in + // linkedInstances. + std::vector> stubModules = buildStubModules(wasm); + std::vector> interfaces; + + for (auto& module : stubModules) { + interfaces.push_back(std::make_unique()); + linkedInstances[module->name] = + std::make_shared(*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 125e672d6c5..95fcadaff59 100644 --- a/test/ctor-eval/global-get-init.wast +++ b/test/ctor-eval/global-get-init.wast @@ -1,8 +1,4 @@ (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 $test1 (export "test1")) ) diff --git a/test/ctor-eval/global-get-init.wast.out b/test/ctor-eval/global-get-init.wast.out index 519e96dbabd..4427f36e041 100644 --- a/test/ctor-eval/global-get-init.wast.out +++ b/test/ctor-eval/global-get-init.wast.out @@ -1,7 +1,2 @@ (module - (type $0 (func)) - (export "test1" (func $test1)) - (func $test1 (type $0) - (nop) - ) ) From e1cef5b4575247c254c2dd4a076f154ea7c6c0eb Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 12 Dec 2025 00:40:08 +0000 Subject: [PATCH 2/6] Update comment to make it clear how stubbed imported globals work --- src/tools/wasm-ctor-eval.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 2f4aab6d476..c3d6baa3b4d 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -118,7 +118,8 @@ class EvallingModuleRunner : public ModuleRunnerBase { // 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. +// use. Imported globals can't be read anyway; see +// `EvallingModuleRunner::visitGlobalGet`. std::vector> buildStubModules(Module& wasm) { std::map> modules; From 8c8b209c114e6d745b4e09a7b23f93f34d2c56d8 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 12 Dec 2025 01:03:23 +0000 Subject: [PATCH 3/6] Remove an extra map lookup --- src/tools/wasm-ctor-eval.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index c3d6baa3b4d..6661704fd9b 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -129,15 +129,12 @@ std::vector> buildStubModules(Module& wasm) { Importable* importable = std::visit([](auto* i) -> Importable* { return i; }, import); - Module* module = nullptr; - if (auto it = modules.find(importable->module); it != modules.end()) { - module = it->second.get(); - } else { - auto [inserted, _] = - modules.emplace(importable->module, std::make_unique()); - module = inserted->second.get(); - module->name = importable->module; + auto [it, inserted] = modules.try_emplace(importable->module, nullptr); + if (inserted) { + it->second = std::make_unique(); + it->second->name = importable->module; } + Module* module = it->second.get(); struct Visitor { Module* module; From b81af3538cab5a9d5806fe82d7b9fe1f3888372a Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 12 Dec 2025 01:10:55 +0000 Subject: [PATCH 4/6] Don't generate stubs for WASI functions --- src/tools/wasm-ctor-eval.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 6661704fd9b..0c14bbe0b61 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -129,6 +129,13 @@ std::vector> buildStubModules(Module& wasm) { Importable* importable = std::visit([](auto* i) -> Importable* { return i; }, import); + // Ignore WASI functions. They are handled separately in + // `getImportedFunction`. + if (std::holds_alternative(import) && + importable->module.startsWith("wasi_")) { + return; + } + auto [it, inserted] = modules.try_emplace(importable->module, nullptr); if (inserted) { it->second = std::make_unique(); From 759b45ca9aa90772a9ef7e68842a2512fbb5e263 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 12 Dec 2025 21:18:30 +0000 Subject: [PATCH 5/6] Generate stubs for WASI functions. They are still needed --- src/tools/wasm-ctor-eval.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 0c14bbe0b61..7173ec0fb64 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -120,6 +120,9 @@ class EvallingModuleRunner : public ModuleRunnerBase { // imports, and fills the rest with fake values since those are dangerous to // 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> buildStubModules(Module& wasm) { std::map> modules; @@ -129,13 +132,6 @@ std::vector> buildStubModules(Module& wasm) { Importable* importable = std::visit([](auto* i) -> Importable* { return i; }, import); - // Ignore WASI functions. They are handled separately in - // `getImportedFunction`. - if (std::holds_alternative(import) && - importable->module.startsWith("wasi_")) { - return; - } - auto [it, inserted] = modules.try_emplace(importable->module, nullptr); if (inserted) { it->second = std::make_unique(); From 899fb042052fa92a838b36af7832ee0e0795b72f Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Sun, 14 Dec 2025 02:15:14 +0000 Subject: [PATCH 6/6] Make global test reference an unneeded global --- test/ctor-eval/global-get-init.wast | 5 +++++ test/ctor-eval/global-get-init.wast.out | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/test/ctor-eval/global-get-init.wast b/test/ctor-eval/global-get-init.wast index 95fcadaff59..5c085e245cb 100644 --- a/test/ctor-eval/global-get-init.wast +++ b/test/ctor-eval/global-get-init.wast @@ -1,4 +1,9 @@ (module (import "import" "global" (global $imported i32)) + (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 4427f36e041..0e5138fb621 100644 --- a/test/ctor-eval/global-get-init.wast.out +++ b/test/ctor-eval/global-get-init.wast.out @@ -1,2 +1,8 @@ (module + (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) + ) )