Skip to content

Commit a77c558

Browse files
Use ImportResolver for table imports (#8194)
Part of #8180. We currently replicate the existing behavior. In the future, we can also change ctor-eval to really simulate tables, and only use the EvallingRuntimeTable in the case of table imports. Removes existing pointer chasing logic that we repeat for each table operation (e.g. tableLoad, tableStore). Allows us to fix spec tests where a table import becomes valid by resizing it in #8222.
1 parent 181b356 commit a77c558

File tree

9 files changed

+398
-249
lines changed

9 files changed

+398
-249
lines changed

src/interpreter/exception.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_interpreter_exception_h
18+
#define wasm_interpreter_exception_h
19+
20+
namespace wasm {
21+
22+
// An exception emitted when exit() is called.
23+
struct ExitException {};
24+
25+
// An exception emitted when a wasm trap occurs.
26+
struct TrapException {};
27+
28+
// An exception emitted when a host limitation is hit. (These are not wasm traps
29+
// as they are not in the spec; for example, the spec has no limit on how much
30+
// GC memory may be allocated, but hosts have limits.)
31+
struct HostLimitException {};
32+
33+
} // namespace wasm
34+
35+
#endif // wasm_interpreter_exception_h

src/ir/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(ir_SOURCES
2020
public-type-validator.cpp
2121
ReFinalize.cpp
2222
return-utils.cpp
23+
runtime-table.cpp
2324
stack-utils.cpp
2425
table-utils.cpp
2526
type-updating.cpp

src/ir/import-utils.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define wasm_ir_import_h
1919

2020
#include "ir/import-name.h"
21+
#include "ir/runtime-table.h"
2122
#include "literal.h"
2223
#include "wasm.h"
2324

@@ -131,6 +132,11 @@ class ImportResolver {
131132
// Returns null if the `name` wasn't found. The returned Literals* lives as
132133
// long as the ImportResolver instance.
133134
virtual Literals* getGlobalOrNull(ImportNames name, Type type) const = 0;
135+
136+
// Returns null if the `name` wasn't found. The returned RuntimeTable* lives
137+
// as long as the ImportResolver instance.
138+
virtual RuntimeTable* getTableOrNull(ImportNames name,
139+
const Table& type) const = 0;
134140
};
135141

136142
// Looks up imports from the given `linkedInstances`.
@@ -151,6 +157,17 @@ class LinkedInstancesImportResolver : public ImportResolver {
151157
return instance->getExportedGlobalOrNull(name.name);
152158
}
153159

160+
RuntimeTable* getTableOrNull(ImportNames name,
161+
const Table& type) const override {
162+
auto it = linkedInstances.find(name.module);
163+
if (it == linkedInstances.end()) {
164+
return nullptr;
165+
}
166+
167+
ModuleRunnerType* instance = it->second.get();
168+
return instance->getExportedTableOrNull(name.name);
169+
}
170+
154171
private:
155172
const std::map<Name, std::shared_ptr<ModuleRunnerType>> linkedInstances;
156173
};

src/ir/runtime-table.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "ir/runtime-table.h"
18+
#include "interpreter/exception.h"
19+
#include "support/stdckdint.h"
20+
#include "wasm-limits.h"
21+
22+
namespace wasm {
23+
24+
namespace {
25+
26+
[[noreturn]] void trap(std::string_view reason) {
27+
// Print so lit tests can check this.
28+
std::cout << "[trap " << reason << "]\n";
29+
throw TrapException{};
30+
}
31+
32+
} // namespace
33+
34+
void RealRuntimeTable::set(std::size_t i, Literal l) {
35+
if (i >= table.size()) {
36+
trap("RuntimeTable::set out of bounds");
37+
WASM_UNREACHABLE("trapped");
38+
}
39+
40+
table[i] = std::move(l);
41+
}
42+
43+
Literal RealRuntimeTable::get(std::size_t i) const {
44+
if (i >= table.size()) {
45+
trap("out of bounds table access");
46+
WASM_UNREACHABLE("trapped");
47+
}
48+
49+
return table[i];
50+
}
51+
52+
std::optional<std::size_t> RealRuntimeTable::grow(std::size_t delta,
53+
Literal fill) {
54+
std::size_t newSize;
55+
if (std::ckd_add(&newSize, table.size(), delta)) {
56+
return std::nullopt;
57+
}
58+
59+
if (newSize > WebLimitations::MaxTableSize || newSize > tableMeta_.max) {
60+
return std::nullopt;
61+
}
62+
63+
std::size_t oldSize = table.size();
64+
table.resize(newSize, fill);
65+
return oldSize;
66+
}
67+
68+
} // namespace wasm

src/ir/runtime-table.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ir_runtime_table_h
18+
#define wasm_ir_runtime_table_h
19+
20+
#include <stddef.h>
21+
#include <vector>
22+
23+
#include "literal.h"
24+
#include "wasm.h"
25+
26+
namespace wasm {
27+
28+
// Traps on out of bounds access
29+
class RuntimeTable {
30+
public:
31+
RuntimeTable(Table table) : tableMeta_(table) {}
32+
virtual ~RuntimeTable() = default;
33+
34+
virtual void set(std::size_t i, Literal l) = 0;
35+
36+
virtual Literal get(std::size_t i) const = 0;
37+
38+
// Returns nullopt if the table grew beyond the max possible size.
39+
[[nodiscard]] virtual std::optional<std::size_t> grow(std::size_t delta,
40+
Literal fill) = 0;
41+
42+
virtual std::size_t size() const = 0;
43+
44+
virtual const Table* tableMeta() const { return &tableMeta_; }
45+
46+
protected:
47+
const Table tableMeta_;
48+
};
49+
50+
class RealRuntimeTable : public RuntimeTable {
51+
public:
52+
RealRuntimeTable(Literal initial, Table table_) : RuntimeTable(table_) {
53+
table.resize(tableMeta_.initial, initial);
54+
}
55+
56+
RealRuntimeTable(const RealRuntimeTable&) = delete;
57+
RealRuntimeTable& operator=(const RealRuntimeTable&) = delete;
58+
59+
void set(std::size_t i, Literal l) override;
60+
61+
Literal get(std::size_t i) const override;
62+
63+
std::optional<std::size_t> grow(std::size_t delta, Literal fill) override;
64+
65+
std::size_t size() const override { return table.size(); }
66+
67+
private:
68+
std::vector<Literal> table;
69+
};
70+
71+
} // namespace wasm
72+
73+
#endif // wasm_ir_runtime_table_h

src/shell-interface.h

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#define wasm_shell_interface_h
2323

2424
#include "asmjs/shared-constants.h"
25+
#include "interpreter/exception.h"
2526
#include "ir/module-utils.h"
2627
#include "shared-constants.h"
2728
#include "support/name.h"
@@ -31,17 +32,6 @@
3132

3233
namespace wasm {
3334

34-
// An exception emitted when exit() is called.
35-
struct ExitException {};
36-
37-
// An exception emitted when a wasm trap occurs.
38-
struct TrapException {};
39-
40-
// An exception emitted when a host limitation is hit. (These are not wasm traps
41-
// as they are not in the spec; for example, the spec has no limit on how much
42-
// GC memory may be allocated, but hosts have limits.)
43-
struct HostLimitException {};
44-
4535
struct ShellExternalInterface : ModuleRunner::ExternalInterface {
4636
// The underlying memory can be accessed through unaligned pointers which
4737
// isn't well-behaved in C++. WebAssembly nonetheless expects it to behave
@@ -93,7 +83,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
9383
};
9484

9585
std::map<Name, Memory> memories;
96-
std::unordered_map<Name, std::vector<Literal>> tables;
9786
std::map<Name, std::shared_ptr<ModuleRunner>> linkedInstances;
9887

9988
ShellExternalInterface(
@@ -125,8 +114,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
125114
shellMemory.resize(memory->initial * wasm::Memory::kPageSize);
126115
memories[memory->name] = shellMemory;
127116
});
128-
ModuleUtils::iterDefinedTables(
129-
wasm, [&](Table* table) { tables[table->name].resize(table->initial); });
130117
}
131118

132119
Literal getImportedFunction(Function* import) override {
@@ -255,35 +242,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
255242
auto& memory = it->second;
256243
memory.set<std::array<uint8_t, 16>>(addr, value);
257244
}
258-
259-
Index tableSize(Name tableName) override {
260-
return (Index)tables[tableName].size();
261-
}
262-
263-
void
264-
tableStore(Name tableName, Address index, const Literal& entry) override {
265-
auto& table = tables[tableName];
266-
if (index >= table.size()) {
267-
trap("out of bounds table access");
268-
} else {
269-
table[index] = entry;
270-
}
271-
}
272-
273-
Literal tableLoad(Name tableName, Address index) override {
274-
auto it = tables.find(tableName);
275-
if (it == tables.end()) {
276-
trap("tableGet on non-existing table");
277-
}
278-
279-
auto& table = it->second;
280-
if (index >= table.size()) {
281-
trap("out of bounds table access");
282-
}
283-
284-
return table[index];
285-
}
286-
287245
bool
288246
growMemory(Name memoryName, Address /*oldSize*/, Address newSize) override {
289247
// Apply a reasonable limit on memory size, 1GB, to avoid DOS on the
@@ -299,20 +257,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
299257
memory.resize(newSize);
300258
return true;
301259
}
302-
303-
bool growTable(Name name,
304-
const Literal& value,
305-
Index /*oldSize*/,
306-
Index newSize) override {
307-
// Apply a reasonable limit on table size, 1GB, to avoid DOS on the
308-
// interpreter.
309-
if (newSize > 1024 * 1024 * 1024) {
310-
return false;
311-
}
312-
tables[name].resize(newSize, value);
313-
return true;
314-
}
315-
316260
void trap(std::string_view why) override {
317261
std::cout << "[trap " << why << "]\n";
318262
throw TrapException();

src/tools/execution-results.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,19 +142,21 @@ struct LoggingExternalInterface : public ShellExternalInterface {
142142
throwJSException();
143143
}
144144
auto index = arguments[0].getUnsigned();
145-
if (index >= tables[exportedTable].size()) {
145+
auto* table = instance->allTables[exportedTable];
146+
if (index >= table->size()) {
146147
throwJSException();
147148
}
148-
return {tableLoad(exportedTable, index)};
149+
return table->get(index);
149150
} else if (import->base == "table-set") {
150151
if (!exportedTable) {
151152
throwJSException();
152153
}
153154
auto index = arguments[0].getUnsigned();
154-
if (index >= tables[exportedTable].size()) {
155+
auto* table = instance->allTables[exportedTable];
156+
if (index >= table->size()) {
155157
throwJSException();
156158
}
157-
tableStore(exportedTable, index, arguments[1]);
159+
table->set(index, arguments[1]);
158160
return {};
159161
} else if (import->base == "call-export") {
160162
callExportAsJS(arguments[0].geti32());

0 commit comments

Comments
 (0)