diff --git a/check.py b/check.py index 31537971b73..fc9634ec811 100755 --- a/check.py +++ b/check.py @@ -198,7 +198,9 @@ def check_expected(actual, expected, stdout=None): shared.fail(actual, expected) -UNSPLITTABLE_TESTS = [Path(x) for x in ["spec/testsuite/instance.wast"]] +UNSPLITTABLE_TESTS = [Path(x) for x in [ + "spec/testsuite/instance.wast", + "spec/instance.wast"]] def is_splittable(wast: Path): diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 7f8e02e9fc9..72a026fb595 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -423,7 +423,7 @@ def get_tests(test_dir, extensions=[], recursive=False): 'proposals/threads/memory.wast', # Missing memory type validation on instantiation 'annotations.wast', # String annotations IDs should be allowed 'id.wast', # Empty IDs should be disallowed - 'instance.wast', # Requires correct handling of tag imports from different instances of the same module + 'instance.wast', # Requires support for table default elements 'table64.wast', # Requires validations for table size 'tag.wast', # Non-empty tag results allowed by stack switching 'local_init.wast', # Requires local validation to respect unnamed blocks diff --git a/src/ir/import-utils.h b/src/ir/import-utils.h index 481d62af7f1..edd422fb44b 100644 --- a/src/ir/import-utils.h +++ b/src/ir/import-utils.h @@ -19,6 +19,7 @@ #include "ir/import-name.h" #include "ir/runtime-table.h" +#include "ir/runtime-tag.h" #include "literal.h" #include "wasm.h" @@ -137,6 +138,9 @@ class ImportResolver { // as long as the ImportResolver instance. virtual RuntimeTable* getTableOrNull(ImportNames name, const Table& type) const = 0; + + virtual RuntimeTag* getTagOrNull(ImportNames name, + const Signature& type) const = 0; }; // Looks up imports from the given `linkedInstances`. @@ -168,6 +172,17 @@ class LinkedInstancesImportResolver : public ImportResolver { return instance->getExportedTableOrNull(name.name); } + RuntimeTag* getTagOrNull(ImportNames name, + const Signature& type) const override { + auto it = linkedInstances.find(name.module); + if (it == linkedInstances.end()) { + return nullptr; + } + + ModuleRunnerType* instance = it->second.get(); + return instance->getExportedTagOrNull(name.name); + } + private: const std::map> linkedInstances; }; diff --git a/src/ir/runtime-tag.h b/src/ir/runtime-tag.h new file mode 100644 index 00000000000..8775a05a121 --- /dev/null +++ b/src/ir/runtime-tag.h @@ -0,0 +1,45 @@ +/* + * 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_runtime_tag_h +#define wasm_ir_runtime_tag_h + +#include "wasm.h" + +namespace wasm { + +// Class representing an instantiation of a tag. +// Note that we compare identity of the `tagDefinition` in the case that +// multiple instances instantiate the same tag definition. +class RuntimeTag { +public: + explicit RuntimeTag(const Tag& tagDefinition) : definition(&tagDefinition) {} + + // Move-only + RuntimeTag(RuntimeTag&& other) : definition(other.definition) {} + + // todo make an accessor for this? + const Tag* const definition; + + bool operator==(const RuntimeTag& other) const { + // pointer comparison + return definition == other.definition; + } +}; + +} // namespace wasm + +#endif // wasm_ir_runtime_tag_h \ No newline at end of file diff --git a/src/shell-interface.h b/src/shell-interface.h index d35bc9bb65e..615000a7fba 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -151,10 +151,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { return Literal::makeFunc(import->name, import->type); } - Tag* getImportedTag(Tag* tag) override { - WASM_UNREACHABLE("missing imported tag"); - } - int8_t load8s(Address addr, Name memoryName) override { auto it = memories.find(memoryName); assert(it != memories.end()); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index bec95777bae..ea3caffa47d 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -18,13 +18,45 @@ // Shared execution result checking code // +#include "ir/runtime-tag.h" #include "shell-interface.h" #include "wasm.h" namespace wasm { +namespace { + using Loggings = std::vector; +RuntimeTag getWasmTag() { + static Tag tag = []() { + Tag tag; + tag.module = "fuzzing-support"; + tag.base = "wasmtag"; + tag.name = "imported-wasm-tag"; + tag.type = Signature(Type::i32, Type::none); + + return tag; + }(); + return RuntimeTag{tag}; +} + +RuntimeTag getJsTag() { + static Tag tag = []() { + Tag tag; + tag.module = "fuzzing-support"; + tag.base = "jstag"; + tag.name = "imported-js-tag"; + tag.type = Signature(Type(HeapType::ext, Nullable), Type::none); + return tag; + }(); + + // todo make this static also? + return RuntimeTag{tag}; +} + +} // namespace + // Logs every relevant import call parameter. struct LoggingExternalInterface : public ShellExternalInterface { private: @@ -43,10 +75,10 @@ struct LoggingExternalInterface : public ShellExternalInterface { Module& wasm; // The imported fuzzing tag for wasm. - Tag wasmTag; + RuntimeTag wasmTag; // The imported tag for js exceptions. - Tag jsTag; + RuntimeTag jsTag; // The ModuleRunner and this ExternalInterface end up needing links both ways, // so we cannot init this in the constructor. @@ -57,34 +89,14 @@ struct LoggingExternalInterface : public ShellExternalInterface { Loggings& loggings, Module& wasm, std::map> linkedInstances_ = {}) - : ShellExternalInterface(linkedInstances_), loggings(loggings), wasm(wasm) { + : ShellExternalInterface(linkedInstances_), loggings(loggings), wasm(wasm), + wasmTag(getWasmTag()), jsTag(getJsTag()) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { exportedTable = *exp->getInternalName(); break; } } - - // Set up tags. (Setting these values is useful for debugging - making the - // Tag objects valid - and also appears in fuzz-exec logging.) - wasmTag.module = "fuzzing-support"; - wasmTag.base = "wasmtag"; - wasmTag.name = "imported-wasm-tag"; - wasmTag.type = Signature(Type::i32, Type::none); - - jsTag.module = "fuzzing-support"; - jsTag.base = "jstag"; - jsTag.name = "imported-js-tag"; - jsTag.type = Signature(Type(HeapType::ext, Nullable), Type::none); - } - - Tag* getImportedTag(Tag* tag) override { - for (auto* imported : {&wasmTag, &jsTag}) { - if (imported->module == tag->module && imported->base == tag->base) { - return imported; - } - } - Fatal() << "missing host tag " << tag->module << '.' << tag->base; } Literal getImportedFunction(Function* import) override { @@ -131,7 +143,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { if (arguments[0].geti32() == 0) { throwJSException(); } else { - auto payload = std::make_shared(&wasmTag, arguments); + auto payload = std::make_shared(wasmTag, arguments); throwException(WasmException{Literal(payload)}); } } else if (import->base == "table-get") { @@ -224,7 +236,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { auto empty = HeapType(Struct{}); auto inner = Literal(std::make_shared(empty, Literals{}), empty); Literals arguments = {inner.externalize()}; - auto payload = std::make_shared(&jsTag, arguments); + auto payload = std::make_shared(jsTag, arguments); throwException(WasmException{Literal(payload)}); } @@ -299,6 +311,28 @@ struct LoggingExternalInterface : public ShellExternalInterface { void setModuleRunner(ModuleRunner* instance_) { instance = instance_; } }; +class FuzzerImportResolver + : public LinkedInstancesImportResolver { + using LinkedInstancesImportResolver::LinkedInstancesImportResolver; + RuntimeTag* getTagOrNull(ImportNames name, + const Signature& type) const override { + if (name.module == "fuzzing-support") { + if (name.name == "wasmtag") { + return &wasmTag; + } + if (name.name == "jstag") { + return &jsTag; + } + } + + return LinkedInstancesImportResolver::getTagOrNull(name, type); + } + +private: + mutable RuntimeTag wasmTag = getWasmTag(); + mutable RuntimeTag jsTag = getJsTag(); +}; + // gets execution results from a wasm module. this is useful for fuzzing // // we can only get results when there are no imports. we then call each method @@ -319,12 +353,19 @@ struct ExecutionResults { try { // Instantiate the first module. LoggingExternalInterface interface(loggings, wasm); - auto instance = std::make_shared(wasm, &interface); + + // `linkedInstances` is empty at this point and the below constructors + // make copies. + std::map> linkedInstances; + auto instance = std::make_shared( + wasm, + &interface, + linkedInstances, + std::make_shared(linkedInstances)); instantiate(*instance, interface); // Instantiate the second, if there is one (we instantiate both before // running anything, so that we match the behavior of fuzz_shell.js). - std::map> linkedInstances; std::unique_ptr secondInterface; std::shared_ptr secondInstance; if (second) { diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index fb57865999d..dbac2b33052 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -84,6 +84,12 @@ class EvallingImportResolver : public ImportResolver { throw FailToEvalException{"Imported table access."}; } + RuntimeTag* getTagOrNull(ImportNames name, + const Signature& signature) const override { + Fatal() << "getTagOrNull not implemented in ctor-eval."; + return nullptr; + } + private: mutable Literals stubLiteral; }; @@ -393,10 +399,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { import->type); } - Tag* getImportedTag(Tag* tag) override { - WASM_UNREACHABLE("missing imported tag"); - } - int8_t load8s(Address addr, Name memoryName) override { return doLoad(addr, memoryName); } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d43f100d5ac..de6038cc732 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -42,6 +42,7 @@ #include "ir/module-utils.h" #include "ir/properties.h" #include "ir/runtime-table.h" +#include "ir/runtime-tag.h" #include "ir/table-utils.h" #include "support/bits.h" #include "support/safe_integer.h" @@ -85,15 +86,17 @@ class Flow { Flow(Name breakTo, Literal value) : values{value}, breakTo(breakTo) {} Flow(Name breakTo, Literals&& values) : values(std::move(values)), breakTo(breakTo) {} - Flow(Name breakTo, Tag* suspendTag, Literals&& values) - : values(std::move(values)), breakTo(breakTo), suspendTag(suspendTag) { + // TODO + Flow(Name breakTo, const RuntimeTag& suspendTag, Literals&& values) + : values(std::move(values)), breakTo(breakTo), suspendTag(&suspendTag) { assert(breakTo == SUSPEND_FLOW); } Literals values; - Name breakTo; // if non-null, a break is going on - Tag* suspendTag = nullptr; // if non-null, breakTo must be SUSPEND_FLOW, and - // this is the tag being suspended + Name breakTo; // if non-null, a break is going on + std::optional suspendTag = + std::nullopt; // if non-null, breakTo must be SUSPEND_FLOW, and + // this is the tag being suspended // A helper function for the common case where there is only one value const Literal& getSingleValue() { @@ -128,7 +131,7 @@ class Flow { o << flow.values[i]; } if (flow.suspendTag) { - o << " [suspend:" << flow.suspendTag->name << ']'; + o << " [suspend:" << (*flow.suspendTag)->definition->name << ']'; } o << "})"; return o; @@ -167,15 +170,11 @@ struct FuncData { // The data of a (ref exn) literal. struct ExnData { - // The tag of this exn data. - // TODO: Add self, like in FuncData, to handle the case of a module that is - // instantiated multiple times. - Tag* tag; - - // The payload of this exn data. + const RuntimeTag& tag; Literals payload; - ExnData(Tag* tag, Literals payload) : tag(tag), payload(payload) {} + ExnData(const RuntimeTag& tag, Literals payload) + : tag(tag), payload(payload) {} }; // Suspend/resume support. @@ -280,7 +279,7 @@ struct ContData { // If set, this is the tag for an exception to be thrown at the resume point // (from resume_throw). - Tag* exceptionTag = nullptr; + RuntimeTag* exceptionTag = nullptr; // If set, this is the exception ref to be thrown at the resume point (from // resume_throw_ref). @@ -380,7 +379,7 @@ class ExpressionRunner : public OverriddenVisitor { } // Same as makeGCData but for ExnData. - Literal makeExnData(Tag* tag, const Literals& payload) { + Literal makeExnData(const RuntimeTag& tag, const Literals& payload) { auto allocation = std::make_shared(tag, payload); #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer) __lsan_ignore_object(allocation.get()); @@ -1955,19 +1954,23 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitTry(Try* curr) { WASM_UNREACHABLE("unimp"); } Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrow(Throw* curr) { - // Single-module implementation. This is used from Precompute, for example. - // It is overriden in ModuleRunner to add logic for finding the proper - // imported tag (which single-module cases don't care about). - Literals arguments; - VISIT_ARGUMENTS(flow, curr->operands, arguments); - auto* tag = self()->getModule()->getTag(curr->tag); - if (tag->imported()) { - // The same tag can be imported twice, so by looking at only the current - // module we can't tell if two tags are the same or not. - return NONCONSTANT_FLOW; - } - throwException(WasmException{self()->makeExnData(tag, arguments)}); - WASM_UNREACHABLE("throw"); + // todo? + WASM_UNREACHABLE("unimp"); + // // Single-module implementation. This is used from Precompute, for + // example. + // // It is overriden in ModuleRunner to add logic for finding the proper + // // imported tag (which single-module cases don't care about). + // Literals arguments; + // VISIT_ARGUMENTS(flow, curr->operands, arguments); + // auto* tag = self()->getModule()->getTag(curr->tag); + // if (tag->imported()) { + // // The same tag can be imported twice, so by looking at only the + // current + // // module we can't tell if two tags are the same or not. + // return NONCONSTANT_FLOW; + // } + // throwException(WasmException{self()->makeExnData(tag, arguments)}); + // WASM_UNREACHABLE("throw"); } Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("unimp"); } Flow visitThrowRef(ThrowRef* curr) { @@ -2977,9 +2980,6 @@ class ModuleRunnerBase : public ExpressionRunner { virtual void trap(std::string_view why) = 0; virtual void hostLimit(std::string_view why) = 0; virtual void throwException(const WasmException& exn) = 0; - // Get the Tag instance for a tag implemented in the host, that is, not - // among the linked ModuleRunner instances, but imported from the host. - virtual Tag* getImportedTag(Tag* tag) = 0; // the default impls for load and store switch on the sizes. you can either // customize load/store, or the sub-functions which they call @@ -3173,6 +3173,9 @@ class ModuleRunnerBase : public ExpressionRunner { // Like `allGlobals`. Keyed by internal name. All tables including imports. std::unordered_map allTables; + // TODO: maybe these should just be values. + std::unordered_map allTags; + using CreateTableFunc = std::unique_ptr(Literal, Table); ModuleRunnerBase( @@ -3223,6 +3226,7 @@ class ModuleRunnerBase : public ExpressionRunner { initializeGlobals(); initializeTables(); + initializeTags(); initializeMemoryContents(); @@ -3296,16 +3300,28 @@ class ModuleRunnerBase : public ExpressionRunner { return *global; } - Tag* getExportedTag(Name name) { + RuntimeTag* getExportedTagOrNull(Name name) { Export* export_ = wasm.getExportOrNull(name); if (!export_ || export_->kind != ExternalKind::Tag) { - externalInterface->trap("exported tag not found"); + return nullptr; } - auto* tag = wasm.getTag(*export_->getInternalName()); - if (tag->imported()) { - tag = externalInterface->getImportedTag(tag); + Name internalName = *export_->getInternalName(); + auto it = allTags.find(internalName); + if (it == allTags.end()) { + return nullptr; } - return tag; + return it->second; + } + + RuntimeTag& getExportedTagOrTrap(Name name) { + auto* tag = getExportedTagOrNull(name); + if (!tag) { + externalInterface->trap((std::stringstream() << "getExportedTag: export " + << name << " not found.") + .str()); + } + + return *tag; } std::string printFunctionStack() { @@ -3323,6 +3339,7 @@ class ModuleRunnerBase : public ExpressionRunner { // internal name. std::vector definedGlobals; std::vector> definedTables; + std::vector definedTags; // Keep a record of call depth, to guard against excessive recursion. size_t callDepth = 0; @@ -3441,24 +3458,54 @@ class ModuleRunnerBase : public ExpressionRunner { << " not found.") .str()); } - auto [_, inserted] = + [[maybe_unused]] 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] = + [[maybe_unused]] 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 initializeTags() { + int definedTagCount = 0; + ModuleUtils::iterDefinedTags( + wasm, [&definedTagCount](auto&& _) { ++definedTagCount; }); + definedTags.reserve(definedTagCount); + + for (auto& tag : wasm.tags) { + if (tag->imported()) { + auto importNames = tag->importNames(); + auto importedTag = + importResolver->getTagOrNull(importNames, tag->type.getSignature()); + if (!importedTag) { + externalInterface->trap((std::stringstream() + << "Imported tag " << importNames + << " not found.") + .str()); + } + [[maybe_unused]] auto [_, inserted] = + allTags.try_emplace(tag->name, importedTag); + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated tag name"); + } else { + auto& definedTag = definedTags.emplace_back(*tag); + + [[maybe_unused]] auto [_, inserted] = + allTags.try_emplace(tag->name, &definedTag); + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated tag name"); + } + } + } + void initializeTables() { int definedTableCount = 0; ModuleUtils::iterDefinedTables( @@ -3476,8 +3523,8 @@ class ModuleRunnerBase : public ExpressionRunner { << " not found.") .str()); } - auto [_, inserted] = allTables.try_emplace(table->name, importedTable); - (void)inserted; // for noassert builds + [[maybe_unused]] auto [_, inserted] = + allTables.try_emplace(table->name, importedTable); // parsing/validation checked this already. assert(inserted && "Unexpected repeated table name"); } else { @@ -3487,9 +3534,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto null = Literal::makeNull(table->type.getHeapType()); auto& runtimeTable = definedTables.emplace_back(createTable(null, *table)); - auto [_, inserted] = + [[maybe_unused]] auto [_, inserted] = allTables.try_emplace(table->name, runtimeTable.get()); - (void)inserted; // for noassert builds assert(inserted && "Unexpected repeated table name"); } } @@ -3702,24 +3748,6 @@ class ModuleRunnerBase : public ExpressionRunner { return inst->getExportedFunction(func->base); } - // Get a tag object while looking through imports, i.e., this uses the name as - // the name of the tag in the current module, and finds the actual canonical - // Tag* object for it: the Tag in this module, if not imported, and if - // imported, the Tag in the originating module. - Tag* getCanonicalTag(Name name) { - auto* inst = self(); - auto* tag = inst->wasm.getTag(name); - if (!tag->imported()) { - return tag; - } - auto iter = inst->linkedInstances.find(tag->module); - if (iter == inst->linkedInstances.end()) { - return externalInterface->getImportedTag(tag); - } - inst = iter->second.get(); - return inst->getExportedTag(tag->base); - } - public: Flow visitCall(Call* curr) { Name target = curr->target; @@ -4612,8 +4640,8 @@ class ModuleRunnerBase : public ExpressionRunner { auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { - auto* tag = self()->getCanonicalTag(curr->catchTags[i]); - if (tag == exnData->tag) { + auto* tag = allTags[curr->catchTags[i]]; + if (*tag == exnData->tag) { multiValues.push_back(exnData->payload); return processCatchBody(curr->catchBodies[i]); } @@ -4635,8 +4663,20 @@ class ModuleRunnerBase : public ExpressionRunner { auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { auto catchTag = curr->catchTags[i]; - if (!catchTag.is() || - self()->getCanonicalTag(catchTag) == exnData->tag) { + + // note: allTags[catchTag] will be null if it's a tag that we don't know + // about, i.e. an unimported tag. + // We do a pointer comparison here (`tag->second == exnData->tag`) + // because a tag just consists of a signature, and two tags may look the + // same but have different identities, e.g. given + // (tag $a (param i32)) + // (tag $b (param i32)) + // a catch clause for $b won't catch $a and vice versa. + // This can also happen when a module is instantiated twice and the same + // tag is imported from each instance. See the instance.wast spec test. + if (auto tag = allTags.find(catchTag); + !catchTag.is() || + ((tag != allTags.end()) && *tag->second == exnData->tag)) { Flow ret; ret.breakTo = curr->catchDests[i]; if (catchTag.is()) { @@ -4657,8 +4697,8 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitThrow(Throw* curr) { Literals arguments; VISIT_ARGUMENTS(flow, curr->operands, arguments); - throwException(WasmException{ - self()->makeExnData(self()->getCanonicalTag(curr->tag), arguments)}); + throwException( + WasmException{self()->makeExnData(*allTags[curr->tag], arguments)}); WASM_UNREACHABLE("throw"); } Flow visitRethrow(Rethrow* curr) { @@ -4714,7 +4754,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (tag) { // resume_throw throwException(WasmException{ - self()->makeExnData(tag, currContinuation->resumeArguments)}); + self()->makeExnData(*tag, currContinuation->resumeArguments)}); } else if (exnref) { // resume_throw_ref throwException(WasmException{currContinuation->exception}); @@ -4753,9 +4793,9 @@ class ModuleRunnerBase : public ExpressionRunner { // old one may exist, in which case we still emit a continuation, but it is // meaningless (it will error when it reaches the host). auto old = self()->getCurrContinuationOrNull(); - auto* tag = self()->getCanonicalTag(curr->tag); + auto* tag = allTags[curr->tag]; if (!old) { - return Flow(SUSPEND_FLOW, tag, std::move(arguments)); + return Flow(SUSPEND_FLOW, *tag, std::move(arguments)); } assert(old->executed); // An old one exists, so we can create a proper new one. It starts out @@ -4774,7 +4814,7 @@ class ModuleRunnerBase : public ExpressionRunner { // We will resume from this precise spot, when the new continuation is // resumed. new_->resumeExpr = curr; - return Flow(SUSPEND_FLOW, tag, std::move(arguments)); + return Flow(SUSPEND_FLOW, *tag, std::move(arguments)); } template Flow doResume(T* curr) { Literals arguments; @@ -4808,8 +4848,7 @@ class ModuleRunnerBase : public ExpressionRunner { if (auto* resumeThrow = curr->template dynCast()) { if (resumeThrow->tag) { // resume_throw - contData->exceptionTag = - self()->getModule()->getTag(resumeThrow->tag); + contData->exceptionTag = allTags[resumeThrow->tag]; } else { // resume_throw_ref contData->exception = arguments[0]; @@ -4839,8 +4878,8 @@ class ModuleRunnerBase : public ExpressionRunner { } else { // We are suspending. See if a suspension arrived that we support. for (size_t i = 0; i < curr->handlerTags.size(); i++) { - auto* handlerTag = self()->getCanonicalTag(curr->handlerTags[i]); - if (handlerTag == ret.suspendTag) { + auto* handlerTag = allTags[curr->handlerTags[i]]; + if (ret.suspendTag && *handlerTag == **ret.suspendTag) { // Switch the flow from suspending to branching. ret.suspendTag = nullptr; ret.breakTo = curr->handlerBlocks[i]; @@ -5213,12 +5252,15 @@ class ModuleRunner : public ModuleRunnerBase { ModuleRunner( Module& wasm, ExternalInterface* externalInterface, - std::map> linkedInstances = {}) + std::map> linkedInstances = {}, + std::shared_ptr importResolver = nullptr) : ModuleRunnerBase( wasm, externalInterface, - std::make_shared>( - linkedInstances), + importResolver + ? importResolver + : std::make_shared>( + linkedInstances), linkedInstances) {} Literal makeFuncData(Name name, Type type) { diff --git a/src/wasm.h b/src/wasm.h index 2904bcefd5f..fe27c191ef9 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2494,8 +2494,8 @@ class Tag : public Importable { public: HeapType type; - Type params() { return type.getSignature().params; } - Type results() { return type.getSignature().results; } + Type params() const { return type.getSignature().params; } + Type results() const { return type.getSignature().results; } }; // "Opaque" data, not part of the core wasm spec, that is held in binaries. diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp index 3af29d2c773..785e029f4fd 100644 --- a/src/wasm/wasm-interpreter.cpp +++ b/src/wasm/wasm-interpreter.cpp @@ -4,7 +4,7 @@ namespace wasm { std::ostream& operator<<(std::ostream& o, const WasmException& exn) { auto exnData = exn.exn.getExnData(); - return o << exnData->tag->name << " " << exnData->payload; + return o << exnData->tag.definition->name << " " << exnData->payload; } } // namespace wasm diff --git a/test/spec/instance.wast b/test/spec/instance.wast new file mode 100644 index 00000000000..87693221687 --- /dev/null +++ b/test/spec/instance.wast @@ -0,0 +1,148 @@ +;; Copy of test/spec/testsuite/instance.wast +;; TODO: Use the upstream test after adding support for default values for tables. + +;; Instantiation is generative + +(module definition $M + (global (export "glob") (mut i32) (i32.const 0)) + (memory (export "mem") 1) + (tag (export "tag")) +) + +(module instance $I1 $M) +(module instance $I2 $M) +(register "I1" $I1) +(register "I2" $I2) + +(module + (import "I1" "glob" (global $glob1 (mut i32))) + (import "I2" "glob" (global $glob2 (mut i32))) + (import "I1" "mem" (memory $mem1 1)) + (import "I2" "mem" (memory $mem2 1)) + (import "I1" "tag" (tag $tag1)) + (import "I2" "tag" (tag $tag2)) + + (func $f) + (elem declare func $f) + + (func (export "glob") (result i32) + (global.set $glob1 (i32.const 1)) + (global.get $glob2) + ) + (func (export "mem") (result i32) + (i32.store $mem1 (i32.const 0) (i32.const 1)) + (i32.load $mem2 (i32.const 0)) + ) + (func (export "tag") (result i32) + (block $on_tag1 + (block $on_other + (try_table (catch $tag1 $on_tag1) (catch_all $on_other) + (throw $tag2) + ) + (unreachable) + ) + (return (i32.const 0)) + ) + (return (i32.const 1)) + ) +) + +(assert_return (invoke "glob") (i32.const 0)) +(assert_return (invoke "mem") (i32.const 0)) +(assert_return (invoke "tag") (i32.const 0)) + + +;; Import is not generative + +(module + (import "I1" "glob" (global $glob1 (mut i32))) + (import "I1" "glob" (global $glob2 (mut i32))) + (import "I1" "mem" (memory $mem1 1)) + (import "I1" "mem" (memory $mem2 1)) + (import "I1" "tag" (tag $tag1)) + (import "I1" "tag" (tag $tag2)) + + (func $f) + (elem declare func $f) + + (func (export "glob") (result i32) + (global.set $glob1 (i32.const 1)) + (global.get $glob2) + ) + (func (export "mem") (result i32) + (i32.store $mem1 (i32.const 0) (i32.const 1)) + (i32.load $mem2 (i32.const 0)) + ) + (func (export "tag") (result i32) + (block $on_tag1 + (block $on_other + (try_table (catch $tag1 $on_tag1) (catch_all $on_other) + (throw $tag2) + ) + (unreachable) + ) + (return (i32.const 0)) + ) + (return (i32.const 1)) + ) +) + +(assert_return (invoke "glob") (i32.const 1)) +(assert_return (invoke "mem") (i32.const 1)) +(assert_return (invoke "tag") (i32.const 1)) + + +;; Export is not generative + +(module definition $N + (global $glob (mut i32) (i32.const 0)) + (memory $mem 1) + (tag $tag) + + (export "glob1" (global $glob)) + (export "glob2" (global $glob)) + (export "mem1" (memory $mem)) + (export "mem2" (memory $mem)) + (export "tag1" (tag $tag)) + (export "tag2" (tag $tag)) +) + +(module instance $I $N) +(register "I" $I) + +(module + (import "I" "glob1" (global $glob1 (mut i32))) + (import "I" "glob2" (global $glob2 (mut i32))) + (import "I" "mem1" (memory $mem1 1)) + (import "I" "mem2" (memory $mem2 1)) + (import "I" "tag1" (tag $tag1)) + (import "I" "tag2" (tag $tag2)) + + (func $f) + (elem declare func $f) + + (func (export "glob") (result i32) + (global.set $glob1 (i32.const 1)) + (global.get $glob2) + ) + (func (export "mem") (result i32) + (i32.store $mem1 (i32.const 0) (i32.const 1)) + (i32.load $mem2 (i32.const 0)) + ) + (func (export "tag") (result i32) + (block $on_tag1 + (block $on_other + (try_table (catch $tag1 $on_tag1) (catch_all $on_other) + (throw $tag2) + ) + (unreachable) + ) + (return (i32.const 0)) + ) + (return (i32.const 1)) + ) +) + +(assert_return (invoke "glob") (i32.const 1)) +(assert_return (invoke "mem") (i32.const 1)) +(assert_return (invoke "tag") (i32.const 1))