diff --git a/src/interpreter/exception.h b/src/interpreter/exception.h new file mode 100644 index 00000000000..5535d70aa92 --- /dev/null +++ b/src/interpreter/exception.h @@ -0,0 +1,35 @@ +/* + * 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_interpreter_exception_h +#define wasm_interpreter_exception_h + +namespace wasm { + +// An exception emitted when exit() is called. +struct ExitException {}; + +// An exception emitted when a wasm trap occurs. +struct TrapException {}; + +// An exception emitted when a host limitation is hit. (These are not wasm traps +// as they are not in the spec; for example, the spec has no limit on how much +// GC memory may be allocated, but hosts have limits.) +struct HostLimitException {}; + +} // namespace wasm + +#endif // wasm_interpreter_exception_h diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 2dce7c22509..a67ee5a91d3 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -20,6 +20,7 @@ set(ir_SOURCES public-type-validator.cpp ReFinalize.cpp return-utils.cpp + runtime-table.cpp stack-utils.cpp table-utils.cpp type-updating.cpp diff --git a/src/ir/import-utils.h b/src/ir/import-utils.h index 0c75704d609..481d62af7f1 100644 --- a/src/ir/import-utils.h +++ b/src/ir/import-utils.h @@ -18,6 +18,7 @@ #define wasm_ir_import_h #include "ir/import-name.h" +#include "ir/runtime-table.h" #include "literal.h" #include "wasm.h" @@ -131,6 +132,11 @@ class ImportResolver { // 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; + + // Returns null if the `name` wasn't found. The returned RuntimeTable* lives + // as long as the ImportResolver instance. + virtual RuntimeTable* getTableOrNull(ImportNames name, + const Table& type) const = 0; }; // Looks up imports from the given `linkedInstances`. @@ -151,6 +157,17 @@ class LinkedInstancesImportResolver : public ImportResolver { return instance->getExportedGlobalOrNull(name.name); } + RuntimeTable* getTableOrNull(ImportNames name, + const Table& type) const override { + auto it = linkedInstances.find(name.module); + if (it == linkedInstances.end()) { + return nullptr; + } + + ModuleRunnerType* instance = it->second.get(); + return instance->getExportedTableOrNull(name.name); + } + private: const std::map> linkedInstances; }; diff --git a/src/ir/runtime-table.cpp b/src/ir/runtime-table.cpp new file mode 100644 index 00000000000..92a6578f5d7 --- /dev/null +++ b/src/ir/runtime-table.cpp @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#include "ir/runtime-table.h" +#include "interpreter/exception.h" +#include "support/stdckdint.h" +#include "wasm-limits.h" + +namespace wasm { + +namespace { + +[[noreturn]] void trap(std::string_view reason) { + // Print so lit tests can check this. + std::cout << "[trap " << reason << "]\n"; + throw TrapException{}; +} + +} // namespace + +void RealRuntimeTable::set(std::size_t i, Literal l) { + if (i >= table.size()) { + trap("RuntimeTable::set out of bounds"); + WASM_UNREACHABLE("trapped"); + } + + table[i] = std::move(l); +} + +Literal RealRuntimeTable::get(std::size_t i) const { + if (i >= table.size()) { + trap("out of bounds table access"); + WASM_UNREACHABLE("trapped"); + } + + return table[i]; +} + +std::optional RealRuntimeTable::grow(std::size_t delta, + Literal fill) { + std::size_t newSize; + if (std::ckd_add(&newSize, table.size(), delta)) { + return std::nullopt; + } + + if (newSize > WebLimitations::MaxTableSize || newSize > tableMeta_.max) { + return std::nullopt; + } + + std::size_t oldSize = table.size(); + table.resize(newSize, fill); + return oldSize; +} + +} // namespace wasm diff --git a/src/ir/runtime-table.h b/src/ir/runtime-table.h new file mode 100644 index 00000000000..54bf23f2c30 --- /dev/null +++ b/src/ir/runtime-table.h @@ -0,0 +1,73 @@ +/* + * 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_table_h +#define wasm_ir_runtime_table_h + +#include +#include + +#include "literal.h" +#include "wasm.h" + +namespace wasm { + +// Traps on out of bounds access +class RuntimeTable { +public: + RuntimeTable(Table table) : tableMeta_(table) {} + virtual ~RuntimeTable() = default; + + virtual void set(std::size_t i, Literal l) = 0; + + virtual Literal get(std::size_t i) const = 0; + + // Returns nullopt if the table grew beyond the max possible size. + [[nodiscard]] virtual std::optional grow(std::size_t delta, + Literal fill) = 0; + + virtual std::size_t size() const = 0; + + virtual const Table* tableMeta() const { return &tableMeta_; } + +protected: + const Table tableMeta_; +}; + +class RealRuntimeTable : public RuntimeTable { +public: + RealRuntimeTable(Literal initial, Table table_) : RuntimeTable(table_) { + table.resize(tableMeta_.initial, initial); + } + + RealRuntimeTable(const RealRuntimeTable&) = delete; + RealRuntimeTable& operator=(const RealRuntimeTable&) = delete; + + void set(std::size_t i, Literal l) override; + + Literal get(std::size_t i) const override; + + std::optional grow(std::size_t delta, Literal fill) override; + + std::size_t size() const override { return table.size(); } + +private: + std::vector table; +}; + +} // namespace wasm + +#endif // wasm_ir_runtime_table_h diff --git a/src/shell-interface.h b/src/shell-interface.h index 6fa7d8af1a8..d35bc9bb65e 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -22,6 +22,7 @@ #define wasm_shell_interface_h #include "asmjs/shared-constants.h" +#include "interpreter/exception.h" #include "ir/module-utils.h" #include "shared-constants.h" #include "support/name.h" @@ -31,17 +32,6 @@ namespace wasm { -// An exception emitted when exit() is called. -struct ExitException {}; - -// An exception emitted when a wasm trap occurs. -struct TrapException {}; - -// An exception emitted when a host limitation is hit. (These are not wasm traps -// as they are not in the spec; for example, the spec has no limit on how much -// GC memory may be allocated, but hosts have limits.) -struct HostLimitException {}; - struct ShellExternalInterface : ModuleRunner::ExternalInterface { // The underlying memory can be accessed through unaligned pointers which // isn't well-behaved in C++. WebAssembly nonetheless expects it to behave @@ -93,7 +83,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { }; std::map memories; - std::unordered_map> tables; std::map> linkedInstances; ShellExternalInterface( @@ -125,8 +114,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { shellMemory.resize(memory->initial * wasm::Memory::kPageSize); memories[memory->name] = shellMemory; }); - ModuleUtils::iterDefinedTables( - wasm, [&](Table* table) { tables[table->name].resize(table->initial); }); } Literal getImportedFunction(Function* import) override { @@ -255,35 +242,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { auto& memory = it->second; memory.set>(addr, value); } - - Index tableSize(Name tableName) override { - return (Index)tables[tableName].size(); - } - - void - tableStore(Name tableName, Address index, const Literal& entry) override { - auto& table = tables[tableName]; - if (index >= table.size()) { - trap("out of bounds table access"); - } else { - table[index] = entry; - } - } - - Literal tableLoad(Name tableName, Address index) override { - auto it = tables.find(tableName); - if (it == tables.end()) { - trap("tableGet on non-existing table"); - } - - auto& table = it->second; - if (index >= table.size()) { - trap("out of bounds table access"); - } - - return table[index]; - } - bool growMemory(Name memoryName, Address /*oldSize*/, Address newSize) override { // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the @@ -299,20 +257,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { memory.resize(newSize); return true; } - - bool growTable(Name name, - const Literal& value, - Index /*oldSize*/, - Index newSize) override { - // Apply a reasonable limit on table size, 1GB, to avoid DOS on the - // interpreter. - if (newSize > 1024 * 1024 * 1024) { - return false; - } - tables[name].resize(newSize, value); - return true; - } - void trap(std::string_view why) override { std::cout << "[trap " << why << "]\n"; throw TrapException(); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 83f861ab7d4..bec95777bae 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -142,19 +142,21 @@ struct LoggingExternalInterface : public ShellExternalInterface { throwJSException(); } auto index = arguments[0].getUnsigned(); - if (index >= tables[exportedTable].size()) { + auto* table = instance->allTables[exportedTable]; + if (index >= table->size()) { throwJSException(); } - return {tableLoad(exportedTable, index)}; + return table->get(index); } else if (import->base == "table-set") { if (!exportedTable) { throwJSException(); } auto index = arguments[0].getUnsigned(); - if (index >= tables[exportedTable].size()) { + auto* table = instance->allTables[exportedTable]; + if (index >= table->size()) { throwJSException(); } - tableStore(exportedTable, index, arguments[1]); + table->set(index, arguments[1]); return {}; } else if (import->base == "call-export") { callExportAsJS(arguments[0].geti32()); diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 1407682faf9..256c2309730 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -33,6 +33,7 @@ #include "ir/memory-utils.h" #include "ir/names.h" #include "pass.h" +#include "shell-interface.h" #include "support/colors.h" #include "support/file.h" #include "support/insert_ordered.h" @@ -67,8 +68,6 @@ bool isNullableAndMutable(Expression* ref, Index fieldIndex) { // the output. #define RECOMMENDATION "\n recommendation: " -class EvallingModuleRunner; - class EvallingImportResolver : public ImportResolver { public: EvallingImportResolver() : stubLiteral({Literal(0)}){}; @@ -80,20 +79,114 @@ class EvallingImportResolver : public ImportResolver { return &stubLiteral; } + RuntimeTable* getTableOrNull(ImportNames name, + const Table& type) const override { + throw FailToEvalException{"Imported table access."}; + } + private: mutable Literals stubLiteral; }; +class EvallingRuntimeTable : public RuntimeTable { +public: + // TODO: putting EvallingModuleRunner into its own header would allow us to + // take an EvallingModuleRunner as input here instead of passing functions. + EvallingRuntimeTable(Table table, + const bool& instanceInitialized, + const Module& wasm, + std::function makeFuncData) + : RuntimeTable(table), instanceInitialized(instanceInitialized), wasm(wasm), + makeFuncData(std::move(makeFuncData)) {} + + void set(std::size_t i, Literal l) override { + if (instanceInitialized) { + throw FailToEvalException("tableStore after init: TODO"); + } + } + + Literal get(std::size_t index) const override { + // Look through the segments and find the value. Segments can overlap, + // so we want the last one. + Expression* value = nullptr; + for (auto& segment : wasm.elementSegments) { + if (segment->table != tableMeta_.name) { + continue; + } + + Index start; + // look for the index in this segment. if it has a constant offset, we + // look in the proper range. if it instead gets a global, we rely on the + // fact that when not dynamically linking then the table is loaded at + // offset 0. + // TODO: This is an Emscripten-specific assumption. We can add an + // Emscripten-only mode and only make the assumption in that case. + if (auto* c = segment->offset->dynCast()) { + start = c->value.getInteger(); + } else if (segment->offset->is()) { + start = 0; + } else { + // TODO: Handle extended consts. + // wasm spec only allows const and global.get there + WASM_UNREACHABLE("invalid expr type"); + } + auto end = start + segment->data.size(); + if (start <= index && index < end) { + value = segment->data[index - start]; + } + } + + if (!value) { + // No segment had a value for this. + // TODO: Handle non-function tables. + return Literal::makeNull(HeapTypes::func); + } + if (!Properties::isConstantExpression(value)) { + throw FailToEvalException("tableLoad of non-literal"); + } + if (auto* r = value->dynCast()) { + return makeFuncData(r->func, r->type); + } + return Properties::getLiteral(value); + } + + [[nodiscard]] virtual std::optional grow(std::size_t delta, + Literal fill) override { + throw FailToEvalException("grow table"); + } + + std::size_t size() const override { + // See set() above, we assume the table is not modified FIXME + return tableMeta_.initial; + } + +private: + const bool& instanceInitialized; + const Module& wasm; + const std::function makeFuncData; +}; + class EvallingModuleRunner : public ModuleRunnerBase { public: EvallingModuleRunner( Module& wasm, ExternalInterface* externalInterface, + const bool& instanceInitialized, std::map> linkedInstances_ = {}) - : ModuleRunnerBase(wasm, - externalInterface, - std::make_shared(), - linkedInstances_) {} + : ModuleRunnerBase( + wasm, + externalInterface, + std::make_shared(), + linkedInstances_, + // TODO: Only use EvallingRuntimeTable for table imports. We can use + // RealRuntimeTable for non-imported tables. + [this, &instanceInitialized](Literal initial, Table table) { + return std::make_unique( + table, + instanceInitialized, + this->wasm, + [this](Name name, Type type) { return makeFuncData(name, type); }); + }) {} Flow visitGlobalGet(GlobalGet* curr) { // Error on reads of imported globals. @@ -156,17 +249,6 @@ std::unique_ptr buildEnvModule(Module& wasm) { } }); - // 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)); - } - }); - // create an exported memory with the same initial and max size ModuleUtils::iterImportedMemories(wasm, [&](Memory* memory) { if (memory->module == env->name) { @@ -315,70 +397,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { WASM_UNREACHABLE("missing imported tag"); } - // We assume the table is not modified FIXME - Literal tableLoad(Name tableName, Address index) override { - auto* table = wasm->getTableOrNull(tableName); - if (!table) { - throw FailToEvalException("tableLoad on non-existing table"); - } - - // Look through the segments and find the value. Segments can overlap, - // so we want the last one. - Expression* value = nullptr; - for (auto& segment : wasm->elementSegments) { - if (segment->table != tableName) { - continue; - } - - Index start; - // look for the index in this segment. if it has a constant offset, we - // look in the proper range. if it instead gets a global, we rely on the - // fact that when not dynamically linking then the table is loaded at - // offset 0. - if (auto* c = segment->offset->dynCast()) { - start = c->value.getInteger(); - } else if (segment->offset->is()) { - start = 0; - } else { - // wasm spec only allows const and global.get there - WASM_UNREACHABLE("invalid expr type"); - } - auto end = start + segment->data.size(); - if (start <= index && index < end) { - value = segment->data[index - start]; - } - } - - if (!value) { - // No segment had a value for this. - return Literal::makeNull(HeapTypes::func); - } - if (!Properties::isConstantExpression(value)) { - throw FailToEvalException("tableLoad of non-literal"); - } - if (auto* r = value->dynCast()) { - return instance->makeFuncData(r->func, r->type); - } - return Properties::getLiteral(value); - } - - Index tableSize(Name tableName) override { - // See tableLoad above, we assume the table is not modified FIXME - return wasm->getTableOrNull(tableName)->initial; - } - - // called during initialization - void - tableStore(Name tableName, Address index, const Literal& value) override { - // We allow stores to the table during initialization, but not after, as we - // assume the table does not change at runtime. - // TODO: Allow table changes by updating the table later like we do with the - // memory, by tracking and serializing them. - if (instanceInitialized) { - throw FailToEvalException("tableStore after init: TODO"); - } - } - int8_t load8s(Address addr, Name memoryName) override { return doLoad(addr, memoryName); } @@ -431,13 +449,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { throw FailToEvalException("grow memory"); } - bool growTable(Name /*name*/, - const Literal& /*value*/, - Index /*oldSize*/, - Index /*newSize*/) override { - throw FailToEvalException("grow table"); - } - void trap(std::string_view why) override { throw FailToEvalException(std::string("trap: ") + std::string(why)); } @@ -1355,14 +1366,15 @@ void evalCtors(Module& wasm, // build and link the env module auto envModule = buildEnvModule(wasm); CtorEvalExternalInterface envInterface; - auto envInstance = - std::make_shared(*envModule, &envInterface); + auto envInstance = std::make_shared( + *envModule, &envInterface, envInterface.instanceInitialized); linkedInstances[envModule->name] = envInstance; CtorEvalExternalInterface interface(linkedInstances); try { // create an instance for evalling - EvallingModuleRunner instance(wasm, &interface, linkedInstances); + EvallingModuleRunner instance( + wasm, &interface, interface.instanceInitialized, linkedInstances); instance.instantiate(); interface.instanceInitialized = true; // go one by one, in order, until we fail diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 89b767af2aa..6563b2709cc 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -41,6 +41,7 @@ #include "ir/memory-utils.h" #include "ir/module-utils.h" #include "ir/properties.h" +#include "ir/runtime-table.h" #include "ir/table-utils.h" #include "support/bits.h" #include "support/safe_integer.h" @@ -2973,10 +2974,6 @@ class ModuleRunnerBase : public ExpressionRunner { virtual void init(Module& wasm, SubType& instance) {} virtual Literal getImportedFunction(Function* import) = 0; virtual bool growMemory(Name name, Address oldSize, Address newSize) = 0; - virtual bool growTable(Name name, - const Literal& value, - Index oldSize, - Index newSize) = 0; virtual void trap(std::string_view why) = 0; virtual void hostLimit(std::string_view why) = 0; virtual void throwException(const WasmException& exn) = 0; @@ -3158,16 +3155,6 @@ class ModuleRunnerBase : public ExpressionRunner { store128(Address addr, const std::array&, Name memoryName) { WASM_UNREACHABLE("unimp"); } - - virtual Index tableSize(Name tableName) = 0; - - virtual void - tableStore(Name tableName, Address index, const Literal& entry) { - WASM_UNREACHABLE("unimp"); - } - virtual Literal tableLoad(Name tableName, Address index) { - WASM_UNREACHABLE("unimp"); - } }; SubType* self() { return static_cast(this); } @@ -3181,17 +3168,30 @@ class ModuleRunnerBase : public ExpressionRunner { // 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; + std::unordered_map allGlobals; + + // Like `allGlobals`. Keyed by internal name. All tables including imports. + std::unordered_map allTables; + + using CreateTableFunc = std::unique_ptr(Literal, Table); ModuleRunnerBase( Module& wasm, ExternalInterface* externalInterface, std::shared_ptr importResolver, - std::map> linkedInstances_ = {}) + std::map> linkedInstances_ = {}, + std::function createTable = {}) : ExpressionRunner(&wasm), wasm(wasm), externalInterface(externalInterface), linkedInstances(std::move(linkedInstances_)), - importResolver(std::move(importResolver)) { + importResolver(std::move(importResolver)), + createTable( + createTable != nullptr + ? std::move(createTable) + : static_cast>( + [](Literal initial, Table t) -> std::unique_ptr { + return std::make_unique(initial, t); + })) { // Set up a single shared CurrContinuations for all these linked instances, // reusing one if it exists. std::shared_ptr shared; @@ -3217,13 +3217,13 @@ class ModuleRunnerBase : public ExpressionRunner { // initialize the rest of the external interface externalInterface->init(wasm, *self()); - initializeGlobals(); - if (validateImports_) { validateImports(); } - initializeTableContents(); + initializeGlobals(); + initializeTables(); + initializeMemoryContents(); // run start, if present @@ -3272,6 +3272,19 @@ class ModuleRunnerBase : public ExpressionRunner { return iter->second; } + RuntimeTable* getExportedTableOrNull(Name name) { + Export* export_ = wasm.getExportOrNull(name); + if (!export_ || export_->kind != ExternalKind::Table) { + return nullptr; + } + Name internalName = *export_->getInternalName(); + auto iter = allTables.find(internalName); + if (iter == allTables.end()) { + return nullptr; + } + return iter->second; + } + Literals& getExportedGlobalOrTrap(Name name) { auto* global = getExportedGlobalOrNull(name); if (!global) { @@ -3309,6 +3322,7 @@ class ModuleRunnerBase : public ExpressionRunner { // `allGlobals` contains these values + imported globals, keyed by their // internal name. std::vector definedGlobals; + std::vector> definedTables; // Keep a record of call depth, to guard against excessive recursion. size_t callDepth = 0; @@ -3320,15 +3334,6 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_set droppedDataSegments; std::unordered_set droppedElementSegments; - struct TableInstanceInfo { - // The ModuleRunner instance in which the memory is defined. - SubType* instance; - // The external interface in which the table is defined - ExternalInterface* interface() { return instance->externalInterface; } - // The name the table has in that interface. - Name name; - }; - // Validates that the export that provides `importable` exists and has the // same kind that the import expects (`kind`). void validateImportKindMatches(ExternalKind kind, @@ -3394,6 +3399,7 @@ class ModuleRunnerBase : public ExpressionRunner { } } + // TODO: Use the table's runtime information when checking this. if (auto** table = std::get_if(&import)) { Table* exportedTable = importedInstance->wasm.getTable(*export_->getInternalName()); @@ -3404,18 +3410,6 @@ class ModuleRunnerBase : public ExpressionRunner { }); } - TableInstanceInfo getTableInstanceInfo(Name name) { - auto* table = wasm.getTable(name); - if (table->imported()) { - auto& importedInstance = linkedInstances.at(table->module); - auto* tableExport = importedInstance->wasm.getExport(table->base); - return importedInstance->getTableInstanceInfo( - *tableExport->getInternalName()); - } - - return TableInstanceInfo{self(), name}; - } - void initializeGlobals() { int definedGlobalCount = 0; ModuleUtils::iterDefinedGlobals( @@ -3451,15 +3445,38 @@ class ModuleRunnerBase : public ExpressionRunner { } } - void initializeTableContents() { + void initializeTables() { + int definedTableCount = 0; + ModuleUtils::iterDefinedTables( + wasm, [&definedTableCount](auto&& _) { ++definedTableCount; }); + definedTables.reserve(definedTableCount); + for (auto& table : wasm.tables) { - if (table->type.isNullable()) { - // Initial with nulls in a nullable table. - auto info = getTableInstanceInfo(table->name); - auto null = Literal::makeNull(table->type.getHeapType()); - for (Address i = 0; i < table->initial; i++) { - info.interface()->tableStore(info.name, i, null); + if (table->imported()) { + auto importNames = table->importNames(); + auto* importedTable = + importResolver->getTableOrNull(importNames, *table); + if (!importedTable) { + externalInterface->trap((std::stringstream() + << "Imported table " << importNames + << " not found.") + .str()); } + auto [_, inserted] = allTables.try_emplace(table->name, importedTable); + (void)inserted; // for noassert builds + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated table name"); + } else { + assert(table->type.isNullable() && + "We only support nullable tables today"); + + auto null = Literal::makeNull(table->type.getHeapType()); + auto& runtimeTable = + definedTables.emplace_back(createTable(null, *table)); + auto [_, inserted] = + allTables.try_emplace(table->name, runtimeTable.get()); + (void)inserted; // for noassert builds + assert(inserted && "Unexpected repeated table name"); } } @@ -3728,11 +3745,10 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(target, curr->target) auto index = target.getSingleValue().getUnsigned(); - auto info = getTableInstanceInfo(curr->table); Literal funcref; if (!self()->isResuming()) { // Normal execution: Load from the table. - funcref = info.interface()->tableLoad(info.name, index); + funcref = allTables[curr->table]->get(index); } else { // Use the stashed funcref (see below). auto entry = self()->popResumeEntry("call_indirect"); @@ -3812,70 +3828,54 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitTableGet(TableGet* curr) { VISIT(index, curr->index) - auto info = getTableInstanceInfo(curr->table); auto address = index.getSingleValue().getUnsigned(); - return info.interface()->tableLoad(info.name, address); + return allTables[curr->table]->get(address); } Flow visitTableSet(TableSet* curr) { VISIT(index, curr->index) VISIT(value, curr->value) - auto info = getTableInstanceInfo(curr->table); auto address = index.getSingleValue().getUnsigned(); - info.interface()->tableStore(info.name, address, value.getSingleValue()); + + allTables[curr->table]->set(address, value.getSingleValue()); + return Flow(); } Flow visitTableSize(TableSize* curr) { - auto info = getTableInstanceInfo(curr->table); - auto* table = info.instance->wasm.getTable(info.name); - Index tableSize = info.interface()->tableSize(curr->table); - return Literal::makeFromInt64(tableSize, table->addressType); + auto* table = allTables[curr->table]; + return Literal::makeFromInt64(static_cast(table->size()), + table->tableMeta()->addressType); } Flow visitTableGrow(TableGrow* curr) { VISIT(valueFlow, curr->value) VISIT(deltaFlow, curr->delta) - auto info = getTableInstanceInfo(curr->table); - uint64_t tableSize = info.interface()->tableSize(info.name); - auto* table = info.instance->wasm.getTable(info.name); - Flow ret = Literal::makeFromInt64(tableSize, table->addressType); - Flow fail = Literal::makeFromInt64(-1, table->addressType); - uint64_t delta = deltaFlow.getSingleValue().getUnsigned(); - - uint64_t newSize; - if (std::ckd_add(&newSize, tableSize, delta)) { - return fail; - } - if (newSize > table->max || newSize > WebLimitations::MaxTableSize) { - return fail; - } - if (!info.interface()->growTable( - info.name, valueFlow.getSingleValue(), tableSize, newSize)) { - // We failed to grow the table in practice, even though it was valid - // to try to do so. - return fail; + auto* table = allTables[curr->table]; + if (auto newSize = table->grow(deltaFlow.getSingleValue().getUnsigned(), + valueFlow.getSingleValue())) { + return Literal::makeFromInt64(*newSize, table->tableMeta()->addressType); } - return ret; + + return Literal::makeFromInt64(-1, table->tableMeta()->addressType); } Flow visitTableFill(TableFill* curr) { VISIT(destFlow, curr->dest) VISIT(valueFlow, curr->value) VISIT(sizeFlow, curr->size) - auto info = getTableInstanceInfo(curr->table); auto dest = destFlow.getSingleValue().getUnsigned(); Literal value = valueFlow.getSingleValue(); auto size = sizeFlow.getSingleValue().getUnsigned(); - auto tableSize = info.interface()->tableSize(info.name); - if (dest + size > tableSize) { + auto* table = allTables[curr->table]; + if (dest + size > table->size()) { trap("out of bounds table access"); } for (uint64_t i = 0; i < size; i++) { - info.interface()->tableStore(info.name, dest + i, value); + table->set(dest + i, value); } return Flow(); } @@ -3888,12 +3888,10 @@ class ModuleRunnerBase : public ExpressionRunner { Address sourceVal(source.getSingleValue().getUnsigned()); Address sizeVal(size.getSingleValue().getUnsigned()); - auto destInfo = getTableInstanceInfo(curr->destTable); - auto sourceInfo = getTableInstanceInfo(curr->sourceTable); - auto destTableSize = destInfo.interface()->tableSize(destInfo.name); - auto sourceTableSize = sourceInfo.interface()->tableSize(sourceInfo.name); - if (sourceVal + sizeVal > sourceTableSize || - destVal + sizeVal > destTableSize || + auto* destTable = allTables[curr->destTable]; + auto* sourceTable = allTables[curr->sourceTable]; + if (sourceVal + sizeVal > sourceTable->size() || + destVal + sizeVal > destTable->size() || // FIXME: better/cheaper way to detect wrapping? sourceVal + sizeVal < sourceVal || sourceVal + sizeVal < sizeVal || destVal + sizeVal < destVal || destVal + sizeVal < sizeVal) { @@ -3910,10 +3908,7 @@ class ModuleRunnerBase : public ExpressionRunner { step = -1; } for (int64_t i = start; i != end; i += step) { - destInfo.interface()->tableStore( - destInfo.name, - destVal + i, - sourceInfo.interface()->tableLoad(sourceInfo.name, sourceVal + i)); + destTable->set(destVal + i, sourceTable->get(sourceVal + i)); } return {}; } @@ -3936,9 +3931,9 @@ class ModuleRunnerBase : public ExpressionRunner { if (offsetVal + sizeVal > segment->data.size()) { trap("out of bounds segment access in table.init"); } - auto info = getTableInstanceInfo(curr->table); - auto tableSize = info.interface()->tableSize(info.name); - if (destVal + sizeVal > tableSize) { + + auto* table = allTables[curr->table]; + if (destVal + sizeVal > table->size()) { trap("out of bounds table access in table.init"); } for (size_t i = 0; i < sizeVal; ++i) { @@ -3947,8 +3942,9 @@ class ModuleRunnerBase : public ExpressionRunner { // and then read here as needed. For example, if we had a // struct.new here then we should not allocate a new struct each // time we table.init that data. - auto value = self()->visit(segment->data[offsetVal + i]).getSingleValue(); - info.interface()->tableStore(info.name, destVal + i, value); + Literal value = + self()->visit(segment->data[offsetVal + i]).getSingleValue(); + table->set(destVal + i, value); } return {}; } @@ -5183,6 +5179,7 @@ class ModuleRunnerBase : public ExpressionRunner { ExternalInterface* externalInterface; std::map> linkedInstances; std::shared_ptr importResolver; + std::function createTable; }; class ModuleRunner : public ModuleRunnerBase {