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)
  )
 )