Skip to content

Commit 8b778c5

Browse files
authored
[NFC-ish] Refactor away callTable (#7776)
Historically, wasm only had call_indirect, and we put callTable as a method on the interpreter's ExternalInterface. Later, wasm added table.get etc., and we added tableLoad to that interface. Given that, we don't really need callTable: It is simpler for e.g. visitCallIndirect to use tableLoad + do a call of the loaded reference. This removes a bunch of duplicate code around the codebase (implementers of callTable), and makes working on visitCallIndirect simpler (which I will need for stack switching). Some minor test changes happen here, mostly that the trap message is slightly different in some cases. The one interesting change, which makes this not NFC, is that a TODO is now complete in test/lit/ctor-eval/return_call.wast
1 parent 3ab6e80 commit 8b778c5

File tree

6 files changed

+47
-128
lines changed

6 files changed

+47
-128
lines changed

src/shell-interface.h

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -152,53 +152,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {
152152
<< import->name.str;
153153
}
154154

155-
Literals callTable(Name tableName,
156-
Address index,
157-
HeapType sig,
158-
Literals& arguments,
159-
Type results,
160-
ModuleRunner& instance) override {
161-
162-
auto it = tables.find(tableName);
163-
if (it == tables.end()) {
164-
trap("callTable on non-existing table");
165-
}
166-
167-
auto& table = it->second;
168-
if (index >= table.size()) {
169-
trap("callTable overflow");
170-
}
171-
Function* func = nullptr;
172-
if (table[index].isFunction() && !table[index].isNull()) {
173-
func = instance.wasm.getFunctionOrNull(table[index].getFunc());
174-
}
175-
if (!func) {
176-
trap("uninitialized table element");
177-
}
178-
if (sig != func->type) {
179-
trap("callIndirect: function types don't match");
180-
}
181-
if (func->getParams().size() != arguments.size()) {
182-
trap("callIndirect: bad # of arguments");
183-
}
184-
size_t i = 0;
185-
for (const auto& param : func->getParams()) {
186-
if (!Type::isSubType(arguments[i++].type, param)) {
187-
trap("callIndirect: bad argument type");
188-
}
189-
}
190-
if (func->getResults() != results) {
191-
trap("callIndirect: bad result type");
192-
}
193-
if (func->imported()) {
194-
return callImport(func, arguments);
195-
} else {
196-
auto flow = instance.callFunction(func->name, arguments);
197-
assert(!flow.suspendTag); // TODO: support stack switching on calls
198-
return flow.values;
199-
}
200-
}
201-
202155
int8_t load8s(Address addr, Name memoryName) override {
203156
auto it = memories.find(memoryName);
204157
assert(it != memories.end());

src/tools/wasm-ctor-eval.cpp

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ class EvallingModuleRunner : public ModuleRunnerBase<EvallingModuleRunner> {
8686

8787
return ModuleRunnerBase<EvallingModuleRunner>::visitGlobalGet(curr);
8888
}
89+
90+
Flow visitTableGet(TableGet* curr) {
91+
// We support tableLoad, below, so that call_indirect works (it calls it
92+
// internally), but we want to disable table.get for now.
93+
throw FailToEvalException("TODO: table.get");
94+
}
8995
};
9096

9197
// Build an artificial `env` module based on a module's imports, so that the
@@ -289,23 +295,15 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
289295
}
290296

291297
// We assume the table is not modified FIXME
292-
Literals callTable(Name tableName,
293-
Address index,
294-
HeapType sig,
295-
Literals& arguments,
296-
Type result,
297-
EvallingModuleRunner& instance) override {
298-
299-
std::unordered_map<wasm::Name, std::vector<wasm::Name>>::iterator it;
300-
298+
Literal tableLoad(Name tableName, Address index) override {
301299
auto* table = wasm->getTableOrNull(tableName);
302300
if (!table) {
303-
throw FailToEvalException("callTable on non-existing table");
301+
throw FailToEvalException("tableLoad on non-existing table");
304302
}
305303

306-
// Look through the segments and find the function. Segments can overlap,
304+
// Look through the segments and find the value. Segments can overlap,
307305
// so we want the last one.
308-
Name targetFunc;
306+
Expression* value = nullptr;
309307
for (auto& segment : wasm->elementSegments) {
310308
if (segment->table != tableName) {
311309
continue;
@@ -326,51 +324,25 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface {
326324
}
327325
auto end = start + segment->data.size();
328326
if (start <= index && index < end) {
329-
auto entry = segment->data[index - start];
330-
if (auto* get = entry->dynCast<RefFunc>()) {
331-
targetFunc = get->func;
332-
} else {
333-
throw FailToEvalException(
334-
std::string("callTable on uninitialized entry"));
335-
}
327+
value = segment->data[index - start];
336328
}
337329
}
338330

339-
if (!targetFunc.is()) {
340-
throw FailToEvalException(
341-
std::string("callTable on index not found in static segments: ") +
342-
std::to_string(index));
331+
if (!value) {
332+
// No segment had a value for this.
333+
return Literal::makeNull(HeapTypes::func);
343334
}
344-
345-
// If this is one of our functions, we can call it; if it was
346-
// imported, fail.
347-
auto* func = wasm->getFunction(targetFunc);
348-
if (func->type != sig) {
349-
throw FailToEvalException(std::string("callTable signature mismatch: ") +
350-
targetFunc.toString());
351-
}
352-
if (!func->imported()) {
353-
auto flow = instance.callFunction(targetFunc, arguments);
354-
if (flow.suspendTag) {
355-
throw FailToEvalException("unhandled suspend");
356-
}
357-
return flow.values;
358-
} else {
359-
throw FailToEvalException(
360-
std::string("callTable on imported function: ") +
361-
targetFunc.toString());
335+
if (!Properties::isConstantExpression(value)) {
336+
throw FailToEvalException("tableLoad of non-literal");
362337
}
338+
return Properties::getLiteral(value);
363339
}
364340

365341
Index tableSize(Name tableName) override {
366-
// See callTable above, we assume the table is not modified FIXME
342+
// See tableLoad above, we assume the table is not modified FIXME
367343
return wasm->getTableOrNull(tableName)->initial;
368344
}
369345

370-
Literal tableLoad(Name tableName, Address index) override {
371-
throw FailToEvalException("table.get: TODO");
372-
}
373-
374346
// called during initialization
375347
void
376348
tableStore(Name tableName, Address index, const Literal& value) override {

src/wasm-interpreter.h

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3006,12 +3006,6 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
30063006
virtual void importGlobals(GlobalValueSet& globals, Module& wasm) = 0;
30073007
virtual Literals callImport(Function* import,
30083008
const Literals& arguments) = 0;
3009-
virtual Literals callTable(Name tableName,
3010-
Address index,
3011-
HeapType sig,
3012-
Literals& arguments,
3013-
Type result,
3014-
SubType& instance) = 0;
30153009
virtual bool growMemory(Name name, Address oldSize, Address newSize) = 0;
30163010
virtual bool growTable(Name name,
30173011
const Literal& value,
@@ -3560,23 +3554,34 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
35603554

35613555
auto index = target.getSingleValue().getUnsigned();
35623556
auto info = getTableInstanceInfo(curr->table);
3557+
auto funcref = info.interface()->tableLoad(info.name, index);
35633558

35643559
if (curr->isReturn) {
35653560
// Return calls are represented by their arguments followed by a reference
35663561
// to the function to be called.
3567-
auto funcref = info.interface()->tableLoad(info.name, index);
35683562
if (!Type::isSubType(funcref.type, Type(curr->heapType, NonNullable))) {
35693563
trap("cast failure in call_indirect");
35703564
}
35713565
arguments.push_back(funcref);
35723566
return Flow(RETURN_CALL_FLOW, std::move(arguments));
35733567
}
35743568

3569+
if (funcref.isNull()) {
3570+
trap("null target in call_indirect");
3571+
}
3572+
if (!funcref.isFunction()) {
3573+
trap("non-function target in call_indirect");
3574+
}
3575+
3576+
auto* func = self()->getModule()->getFunction(funcref.getFunc());
3577+
if (!HeapType::isSubType(func->type, curr->heapType)) {
3578+
trap("callIndirect: non-subtype");
3579+
}
3580+
35753581
#if WASM_INTERPRETER_DEBUG
35763582
std::cout << self()->indent() << "(calling table)\n";
35773583
#endif
3578-
Flow ret = info.interface()->callTable(
3579-
info.name, index, curr->heapType, arguments, curr->type, *self());
3584+
Flow ret = callFunction(funcref.getFunc(), arguments);
35803585
#if WASM_INTERPRETER_DEBUG
35813586
std::cout << self()->indent() << "(returned to " << scope->function->name
35823587
<< ")\n";

test/lit/ctor-eval/return_call.wast

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
;; CHECK-NEXT: )
6161
(module
6262
;; Basic return call indirect
63-
;; TODO: Implement `tableLoad` to make this test work.
6463

6564
(import "env" "import" (func $import))
6665

@@ -69,10 +68,9 @@
6968
;; CHECK: (global $g1 (mut i32) (i32.const 1))
7069
(global $g1 (export "g1") (mut i32) (i32.const 0))
7170

72-
;; CHECK: (global $g2 (mut i32) (i32.const 0))
71+
;; CHECK: (global $g2 (mut i32) (i32.const 2))
7372
(global $g2 (export "g2") (mut i32) (i32.const 0))
7473

75-
;; CHECK: (table $t 1 1 funcref)
7674
(table $t funcref (elem $test2))
7775

7876
(func $test (export "test")
@@ -86,30 +84,21 @@
8684
(call $import)
8785
)
8886

89-
;; CHECK: (elem $implicit-elem (i32.const 0) $test2)
90-
91-
;; CHECK: (export "g1" (global $g1))
92-
93-
;; CHECK: (export "g2" (global $g2))
94-
95-
;; CHECK: (export "test" (func $test_3))
96-
97-
;; CHECK: (func $test2 (type $0)
98-
;; CHECK-NEXT: (global.set $g2
99-
;; CHECK-NEXT: (i32.const 2)
100-
;; CHECK-NEXT: )
101-
;; CHECK-NEXT: )
10287
(func $test2
10388
(global.set $g2
10489
(i32.const 2)
10590
)
10691
)
10792
)
10893

94+
;; CHECK: (export "g1" (global $g1))
95+
96+
;; CHECK: (export "g2" (global $g2))
97+
98+
;; CHECK: (export "test" (func $test_3))
99+
109100
;; CHECK: (func $test_3 (type $0)
110-
;; CHECK-NEXT: (return_call_indirect $t (type $0)
111-
;; CHECK-NEXT: (i32.const 0)
112-
;; CHECK-NEXT: )
101+
;; CHECK-NEXT: (nop)
113102
;; CHECK-NEXT: )
114103
(module
115104
;; Basic return call ref

test/lit/exec/table.fill.wast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
)
2424
;; CHECK: [fuzz-exec] calling call
25-
;; CHECK-NEXT: [trap uninitialized table element]
25+
;; CHECK-NEXT: [trap null target in call_indirect]
2626
(func $call (export "call") (result i32)
2727
;; Nothing was written, so this traps.
2828
(call_indirect $table (type $i32)

test/lit/exec/table64.wast

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
)
3636

3737
;; CHECK: [fuzz-exec] calling oob
38-
;; CHECK-NEXT: [trap callTable overflow]
38+
;; CHECK-NEXT: [trap out of bounds table access]
3939
(func $oob (export "oob") (result i32)
4040
;; This call traps on oob.
4141
(call_indirect (type $i32)
@@ -44,7 +44,7 @@
4444
)
4545

4646
;; CHECK: [fuzz-exec] calling oob-huge
47-
;; CHECK-NEXT: [trap callTable overflow]
47+
;; CHECK-NEXT: [trap out of bounds table access]
4848
(func $oob-huge (export "oob-huge") (result i32)
4949
;; This call traps on oob with a value over 32 bits, 2**32 + 1, which if we
5050
;; truncated to 32 bits, would seem in bounds, and end up calling a valid
@@ -58,7 +58,7 @@
5858
)
5959

6060
;; CHECK: [fuzz-exec] calling null
61-
;; CHECK-NEXT: [trap uninitialized table element]
61+
;; CHECK-NEXT: [trap null target in call_indirect]
6262
(func $null (export "null") (result i32)
6363
;; This call traps on null
6464
(call_indirect (type $i32)
@@ -74,13 +74,13 @@
7474
;; CHECK-NEXT: [fuzz-exec] note result: call-b => 1337
7575

7676
;; CHECK: [fuzz-exec] calling oob
77-
;; CHECK-NEXT: [trap callTable overflow]
77+
;; CHECK-NEXT: [trap out of bounds table access]
7878

7979
;; CHECK: [fuzz-exec] calling oob-huge
80-
;; CHECK-NEXT: [trap callTable overflow]
80+
;; CHECK-NEXT: [trap out of bounds table access]
8181

8282
;; CHECK: [fuzz-exec] calling null
83-
;; CHECK-NEXT: [trap uninitialized table element]
83+
;; CHECK-NEXT: [trap null target in call_indirect]
8484
;; CHECK-NEXT: [fuzz-exec] comparing call-a
8585
;; CHECK-NEXT: [fuzz-exec] comparing call-b
8686
;; CHECK-NEXT: [fuzz-exec] comparing null

0 commit comments

Comments
 (0)