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/support/nullability.h b/src/support/nullability.h new file mode 100644 index 00000000000..ed31481ab5c --- /dev/null +++ b/src/support/nullability.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +// Trivial wrappers to annotate the nullability of pointers. + +#ifndef _wasm_support_nullability +#define _wasm_support_nullability + +#include +#include + +namespace wasm::nullability { + +template struct is_pointer : std::false_type {}; + +template +struct is_pointer::element_type>> + : std::true_type {}; + +template using Nullable = std::enable_if_t::value, T>; + +template using NonNull = std::enable_if_t::value, T>; + +} // namespace wasm::nullability + +#endif // _wasm_support_nullability diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 455c247ccd7..02c64806dfa 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -36,6 +36,7 @@ #include "support/colors.h" #include "support/file.h" #include "support/insert_ordered.h" +#include "support/nullability.h" #include "support/small_set.h" #include "support/string.h" #include "support/topological_sort.h" @@ -67,13 +68,37 @@ bool isNullableAndMutable(Expression* ref, Index fieldIndex) { // the output. #define RECOMMENDATION "\n recommendation: " +class EvallingModuleRunner; + +class EvallingImportResolver : public ImportResolver { +public: + EvallingImportResolver( + std::map> linkedInstances, + ModuleRunnerBase::ExternalInterface* + externalInterface) + : externalInterface(externalInterface) {} + + nullability::Nullable getGlobal(QualifiedName name, + Type type) const override { + externalInterface->trap("Accessed imported global"); + return nullptr; + } + +private: + ModuleRunnerBase::ExternalInterface* externalInterface; +}; + class EvallingModuleRunner : public ModuleRunnerBase { public: EvallingModuleRunner( Module& wasm, ExternalInterface* externalInterface, std::map> linkedInstances_ = {}) - : ModuleRunnerBase(wasm, externalInterface, linkedInstances_) {} + : ModuleRunnerBase(wasm, + externalInterface, + std::make_shared( + linkedInstances_, externalInterface), + linkedInstances_) {} Flow visitGlobalGet(GlobalGet* curr) { // Error on reads of imported globals. @@ -147,19 +172,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 +243,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 +550,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 +582,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 ce9e03a022f..cf28fb55b47 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -49,6 +49,7 @@ #include "wasm-limits.h" #include "wasm-traversal.h" #include "wasm.h" +#include "wasm/import-resolver.h" #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer) #include @@ -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,22 @@ 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. + 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 +3213,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 +3258,30 @@ class ModuleRunnerBase : public ExpressionRunner { func->type); } - // get an exported global - Literals getExportedGlobal(Name name) { + nullability::Nullable getExportedGlobal(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 = getExportedGlobal(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 +3304,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 +3415,39 @@ 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()) { + QualifiedName name{global->module, global->base}; + auto importedGlobal = importResolver->getGlobal(name, global->type); + if (!importedGlobal) { + externalInterface->trap( + (std::stringstream() << "Imported global " << name << " not found.") + .str()); + } + auto [_, inserted] = + allGlobals.try_emplace(global->name, importedGlobal); + (void)_; + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated global name"); + } else { + Literals init = self()->visit(global->init).values; + auto& definedGlobal = definedGlobals.emplace_back(init); + + auto [_, inserted] = + allGlobals.try_emplace(global->name, &definedGlobal); + (void)_; + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated global name"); + } + } + } + void initializeTableContents() { for (auto& table : wasm.tables) { if (table->type.isNullable()) { @@ -3602,20 +3647,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 +3970,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 +5179,7 @@ class ModuleRunnerBase : public ExpressionRunner { ExternalInterface* externalInterface; std::map> linkedInstances; + std::shared_ptr importResolver; }; class ModuleRunner : public ModuleRunnerBase { @@ -5154,7 +5188,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/import-resolver.h b/src/wasm/import-resolver.h new file mode 100644 index 00000000000..ca10f858a43 --- /dev/null +++ b/src/wasm/import-resolver.h @@ -0,0 +1,67 @@ +/* + * 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_import_resolver_h +#define wasm_import_resolver_h + +#include +#include +#include +#include + +#include "support/name.h" +#include "support/nullability.h" +#include "wasm.h" +#include "wasm/qualified-name.h" + +namespace wasm { + +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 nullability::Nullable getGlobal(QualifiedName 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)) {} + + nullability::Nullable getGlobal(QualifiedName name, + Type type) const override { + auto it = linkedInstances.find(name.module); + if (it == linkedInstances.end()) { + return nullptr; + } + + ModuleRunnerType* instance = it->second.get(); + return instance->getExportedGlobal(name.name); + } + +private: + const std::map> linkedInstances; +}; + +} // namespace wasm + +#endif // wasm_import_resolver_h diff --git a/src/wasm/qualified-name.h b/src/wasm/qualified-name.h new file mode 100644 index 00000000000..87b72be84ab --- /dev/null +++ b/src/wasm/qualified-name.h @@ -0,0 +1,36 @@ +/* + * 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_qualified_name_h +#define wasm_qualified_name_h + +#include "support/name.h" +#include + +namespace wasm { + +struct QualifiedName { + Name module; + Name name; + + friend std::ostream& operator<<(std::ostream& o, const QualifiedName& qname) { + return o << qname.module << "." << qname.name; + } +}; + +} // namespace wasm + +#endif // wasm_qualified_name_h