diff --git a/src/ir/import-name.h b/src/ir/import-name.h new file mode 100644 index 00000000000..81fd0a3e768 --- /dev/null +++ b/src/ir/import-name.h @@ -0,0 +1,33 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_ir_import_name_h +#define wasm_ir_import_name_h + +#include + +#include "support/name.h" + +namespace wasm { + +struct ImportNames { + Name module; + Name name; +}; + +} // namespace wasm + +#endif // wasm_ir_import_name_h diff --git a/src/ir/import-utils.h b/src/ir/import-utils.h index d0b5a8042a6..0c75704d609 100644 --- a/src/ir/import-utils.h +++ b/src/ir/import-utils.h @@ -17,6 +17,7 @@ #ifndef wasm_ir_import_h #define wasm_ir_import_h +#include "ir/import-name.h" #include "literal.h" #include "wasm.h" @@ -123,6 +124,37 @@ struct ImportInfo { Index getNumDefinedTags() { return wasm.tags.size() - getNumImportedTags(); } }; +class ImportResolver { +public: + virtual ~ImportResolver() = default; + + // Returns null if the `name` wasn't found. The returned Literals* lives as + // long as the ImportResolver instance. + virtual Literals* getGlobalOrNull(ImportNames name, Type type) const = 0; +}; + +// Looks up imports from the given `linkedInstances`. +template +class LinkedInstancesImportResolver : public ImportResolver { +public: + LinkedInstancesImportResolver( + std::map> linkedInstances) + : linkedInstances(std::move(linkedInstances)) {} + + Literals* getGlobalOrNull(ImportNames name, Type type) const override { + auto it = linkedInstances.find(name.module); + if (it == linkedInstances.end()) { + return nullptr; + } + + ModuleRunnerType* instance = it->second.get(); + return instance->getExportedGlobalOrNull(name.name); + } + +private: + const std::map> linkedInstances; +}; + } // namespace wasm #endif // wasm_ir_import_h diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index d2c76c67208..1cd4734e604 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3944,4 +3944,9 @@ std::ostream& operator<<(std::ostream& o, wasm::ModuleHeapType pair) { return o << "(unnamed)"; } +std::ostream& operator<<(std::ostream& o, + const wasm::ImportNames& importNames) { + return o << importNames.module << "." << importNames.name; +} + } // namespace std diff --git a/src/shell-interface.h b/src/shell-interface.h index fb9b645bb5b..6fa7d8af1a8 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -129,20 +129,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { wasm, [&](Table* table) { tables[table->name].resize(table->initial); }); } - void importGlobals(std::map& globals, Module& wasm) override { - ModuleUtils::iterImportedGlobals(wasm, [&](Global* import) { - auto inst = getImportInstance(import); - auto* exportedGlobal = inst->wasm.getExportOrNull(import->base); - if (!exportedGlobal || exportedGlobal->kind != ExternalKind::Global) { - trap((std::stringstream() - << "importGlobals: unknown import: " << import->module.str << "." - << import->name.str) - .str()); - } - globals[import->name] = inst->globals[*exportedGlobal->getInternalName()]; - }); - } - Literal getImportedFunction(Function* import) override { // TODO: We should perhaps restrict the types with which the well-known // functions can be imported. diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 455c247ccd7..4839577225a 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -67,13 +67,27 @@ bool isNullableAndMutable(Expression* ref, Index fieldIndex) { // the output. #define RECOMMENDATION "\n recommendation: " +class EvallingModuleRunner; + +class EvallingImportResolver : public ImportResolver { +public: + EvallingImportResolver() = default; + + Literals* getGlobalOrNull(ImportNames name, Type type) const override { + throw FailToEvalException("Accessed imported global"); + } +}; + class EvallingModuleRunner : public ModuleRunnerBase { public: EvallingModuleRunner( Module& wasm, ExternalInterface* externalInterface, std::map> linkedInstances_ = {}) - : ModuleRunnerBase(wasm, externalInterface, linkedInstances_) {} + : ModuleRunnerBase(wasm, + externalInterface, + std::make_shared(), + linkedInstances_) {} Flow visitGlobalGet(GlobalGet* curr) { // Error on reads of imported globals. @@ -147,19 +161,6 @@ std::unique_ptr buildEnvModule(Module& wasm) { } }); - 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) { @@ -231,26 +232,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { } } - void importGlobals(GlobalValueSet& globals, Module& wasm_) override { - ModuleUtils::iterImportedGlobals(wasm_, [&](Global* global) { - auto it = linkedInstances.find(global->module); - if (it != linkedInstances.end()) { - auto* inst = it->second.get(); - auto* globalExport = inst->wasm.getExportOrNull(global->base); - if (!globalExport || globalExport->kind != ExternalKind::Global) { - throw FailToEvalException(std::string("importGlobals: ") + - global->module.toString() + "." + - global->base.toString()); - } - globals[global->name] = inst->globals[*globalExport->getInternalName()]; - } else { - throw FailToEvalException(std::string("importGlobals: ") + - global->module.toString() + "." + - global->base.toString()); - } - }); - } - Literal getImportedFunction(Function* import) override { auto f = [import, this](const Literals& arguments) -> Flow { Name WASI("wasi_snapshot_preview1"); @@ -558,8 +539,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { void applyGlobalsToModule() { if (!wasm->features.hasGC()) { // Without GC, we can simply serialize the globals in place as they are. - for (const auto& [name, values] : instance->globals) { - wasm->getGlobal(name)->init = getSerialization(values); + for (const auto& [name, values] : instance->allGlobals) { + wasm->getGlobal(name)->init = getSerialization(*values); } return; } @@ -590,9 +571,9 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // for it. (If there is no value, then this is a new global we've added // during execution, for whom we've already set up a proper serialized // value when we created it.) - auto iter = instance->globals.find(oldGlobal->name); - if (iter != instance->globals.end()) { - oldGlobal->init = getSerialization(iter->second, name); + auto iter = instance->allGlobals.find(oldGlobal->name); + if (iter != instance->allGlobals.end()) { + oldGlobal->init = getSerialization(*iter->second, name); } // Add the global back to the module. diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 103da88368f..046bb33cecd 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -280,7 +280,7 @@ struct Shell { } auto& instance = it->second; try { - return instance->getExportedGlobal(get->name); + return instance->getExportedGlobalOrTrap(get->name); } catch (TrapException&) { return TrapResult{}; } catch (...) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 86895d374d2..89b767af2aa 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -35,6 +35,7 @@ #include #include "fp16.h" +#include "ir/import-utils.h" #include "ir/intrinsics.h" #include "ir/iteration.h" #include "ir/memory-utils.h" @@ -2943,8 +2944,6 @@ class ConstantExpressionRunner : public ExpressionRunner { } }; -using GlobalValueSet = std::map; - // // A runner for a module. Each runner contains the information to execute the // module, such as the state of globals, and so forth, so it basically @@ -2972,7 +2971,6 @@ class ModuleRunnerBase : public ExpressionRunner { std::map> linkedInstances = {}) {} virtual ~ExternalInterface() = default; virtual void init(Module& wasm, SubType& instance) {} - virtual void importGlobals(GlobalValueSet& globals, Module& wasm) = 0; virtual Literal getImportedFunction(Function* import) = 0; virtual bool growMemory(Name name, Address oldSize, Address newSize) = 0; virtual bool growTable(Name name, @@ -3177,18 +3175,23 @@ class ModuleRunnerBase : public ExpressionRunner { // TODO: this duplicates module in ExpressionRunner, and can be removed Module& wasm; - // Values of globals - GlobalValueSet globals; - // Multivalue ABI support (see push/pop). std::vector multiValues; + // Keyed by internal name. All globals in the module, including imports. + // `definedGlobals` contains non-imported globals. Points to `definedGlobals` + // of this instance and other instances. + std::map allGlobals; + ModuleRunnerBase( Module& wasm, ExternalInterface* externalInterface, + std::shared_ptr importResolver, std::map> linkedInstances_ = {}) : ExpressionRunner(&wasm), wasm(wasm), - externalInterface(externalInterface), linkedInstances(linkedInstances_) { + externalInterface(externalInterface), + linkedInstances(std::move(linkedInstances_)), + importResolver(std::move(importResolver)) { // Set up a single shared CurrContinuations for all these linked instances, // reusing one if it exists. std::shared_ptr shared; @@ -3211,16 +3214,11 @@ class ModuleRunnerBase : public ExpressionRunner { // (This is separate from the constructor so that it does not occur // synchronously, which makes some code patterns harder to write.) void instantiate(bool validateImports_ = false) { - // import globals from the outside - externalInterface->importGlobals(globals, wasm); - // generate internal (non-imported) globals - ModuleUtils::iterDefinedGlobals(wasm, [&](Global* global) { - globals[global->name] = self()->visit(global->init).values; - }); - // initialize the rest of the external interface externalInterface->init(wasm, *self()); + initializeGlobals(); + if (validateImports_) { validateImports(); } @@ -3261,20 +3259,30 @@ class ModuleRunnerBase : public ExpressionRunner { func->type); } - // get an exported global - Literals getExportedGlobal(Name name) { + Literals* getExportedGlobalOrNull(Name name) { Export* export_ = wasm.getExportOrNull(name); if (!export_ || export_->kind != ExternalKind::Global) { - externalInterface->trap("getExport external not found"); + return nullptr; } Name internalName = *export_->getInternalName(); - auto iter = globals.find(internalName); - if (iter == globals.end()) { - externalInterface->trap("getExport internal not found"); + auto iter = allGlobals.find(internalName); + if (iter == allGlobals.end()) { + return nullptr; } return iter->second; } + Literals& getExportedGlobalOrTrap(Name name) { + auto* global = getExportedGlobalOrNull(name); + if (!global) { + externalInterface->trap((std::stringstream() + << "getExportedGlobal: export " << name + << " not found.") + .str()); + } + return *global; + } + Tag* getExportedTag(Name name) { Export* export_ = wasm.getExportOrNull(name); if (!export_ || export_->kind != ExternalKind::Tag) { @@ -3297,6 +3305,11 @@ class ModuleRunnerBase : public ExpressionRunner { } private: + // Globals that were defined in this module and not from an import. + // `allGlobals` contains these values + imported globals, keyed by their + // internal name. + std::vector definedGlobals; + // Keep a record of call depth, to guard against excessive recursion. size_t callDepth = 0; @@ -3403,6 +3416,41 @@ class ModuleRunnerBase : public ExpressionRunner { return TableInstanceInfo{self(), name}; } + void initializeGlobals() { + int definedGlobalCount = 0; + ModuleUtils::iterDefinedGlobals( + wasm, [&definedGlobalCount](auto&& _) { ++definedGlobalCount; }); + definedGlobals.reserve(definedGlobalCount); + + for (auto& global : wasm.globals) { + if (global->imported()) { + auto importNames = global->importNames(); + auto importedGlobal = + importResolver->getGlobalOrNull(importNames, global->type); + if (!importedGlobal) { + externalInterface->trap((std::stringstream() + << "Imported global " << importNames + << " not found.") + .str()); + } + auto [_, inserted] = + allGlobals.try_emplace(global->name, importedGlobal); + (void)inserted; // for noassert builds + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated global name"); + } else { + Literals init = self()->visit(global->init).values; + auto& definedGlobal = definedGlobals.emplace_back(std::move(init)); + + auto [_, inserted] = + allGlobals.try_emplace(global->name, &definedGlobal); + (void)inserted; // for noassert builds + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated global name"); + } + } + } + void initializeTableContents() { for (auto& table : wasm.tables) { if (table->type.isNullable()) { @@ -3602,20 +3650,8 @@ class ModuleRunnerBase : public ExpressionRunner { SmallVector, 4> exceptionStack; protected: - // Returns a reference to the current value of a potentially imported global. - Literals& getGlobal(Name name) { - auto* inst = self(); - auto* global = inst->wasm.getGlobal(name); - while (global->imported()) { - inst = inst->linkedInstances.at(global->module).get(); - Export* globalExport = inst->wasm.getExport(global->base); - global = inst->wasm.getGlobal(*globalExport->getInternalName()); - } - - return inst->globals[global->name]; - } - - // As above, but for a function. + // Returns a reference to the current value of a potentially imported + // function. Literal getFunction(Name name) { auto* inst = self(); auto* func = inst->wasm.getFunction(name); @@ -3937,13 +3973,13 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitGlobalGet(GlobalGet* curr) { auto name = curr->name; - return getGlobal(name); + return *allGlobals.at(name); } Flow visitGlobalSet(GlobalSet* curr) { auto name = curr->name; VISIT(flow, curr->value) - getGlobal(name) = flow.values; + *allGlobals.at(name) = flow.values; return Flow(); } @@ -5146,6 +5182,7 @@ class ModuleRunnerBase : public ExpressionRunner { ExternalInterface* externalInterface; std::map> linkedInstances; + std::shared_ptr importResolver; }; class ModuleRunner : public ModuleRunnerBase { @@ -5154,7 +5191,12 @@ class ModuleRunner : public ModuleRunnerBase { Module& wasm, ExternalInterface* externalInterface, std::map> linkedInstances = {}) - : ModuleRunnerBase(wasm, externalInterface, linkedInstances) {} + : ModuleRunnerBase( + wasm, + externalInterface, + std::make_shared>( + linkedInstances), + linkedInstances) {} Literal makeFuncData(Name name, Type type) { // As the super's |makeFuncData|, but here we also provide a way to diff --git a/src/wasm.h b/src/wasm.h index 5c36d5b40b9..57b41a5d1a7 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -33,6 +33,7 @@ #include #include +#include "ir/import-name.h" #include "literal.h" #include "support/index.h" #include "support/mixed_arena.h" @@ -2176,6 +2177,7 @@ struct Importable : Named { Name module, base; bool imported() const { return module.is(); } + ImportNames importNames() const { return ImportNames{module, base}; }; }; class Function; @@ -2669,6 +2671,7 @@ std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression); std::ostream& operator<<(std::ostream& o, wasm::ModuleType pair); std::ostream& operator<<(std::ostream& o, wasm::ModuleHeapType pair); std::ostream& operator<<(std::ostream& os, wasm::MemoryOrder mo); +std::ostream& operator<<(std::ostream& o, const wasm::ImportNames& importNames); } // namespace std