Skip to content

Commit 09300e4

Browse files
authored
[interpreter] Begin implementation of a new interpreter (#7227)
The current interpreter used in wasm-shell, the fuzzer, and optimizations like precompute works by recursively walking the expression tree and computing expression results as it goes. This kind of recursive interpretation is not going to work for stack switching, since stack switching requires stashing context away and restoring it later. The recursive interpreter stores intermediate values on the native stack and returns early to implement control flow, so there is no way to suspend a computation and resume it later. To support stack switching and support other use future interpreter use cases such as running the full spec test suite and fuzzing multithreaded programs, introduce a new interpreter that is not recursive and does not store intermediate state that needs to persist beyond the execution of a single instruction on the native stack. The new interpreter works by iterating through instructions and visiting them one at a time in a loop. The visitor pushes and pops values from a stack and signals control flow via its return values. Control flow transfers are handled by the main interpreter loop, so expressions are only visited when they are actually executed. This design will not only support stack switching and other features better than the old interpreter, but it will also significantly decrease the amount of code in the interpreter. In addition to the core interpreter loop, also lay out a skeleton of the execution context for the new interpreter, including a call stack and store. The code contains several TODOs describing how these runtime structures will need to be extended to support interpreting the full spec test suite, including the ability to interpret over multiple linked instances at once. Most of the actual interpretation of expressions is left as future work, but the interpretation of `Const` expressions and i32.add is implemented and tested in a new gtest file to demonstrate it working end-to-end. One of the first milestones for the new interpreter will be getting real spec tests running with it, at which point the gtest file can be removed.
1 parent eb1f90a commit 09300e4

File tree

9 files changed

+459
-3
lines changed

9 files changed

+459
-3
lines changed

CMakeLists.txt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ add_subdirectory(src/ir)
408408
add_subdirectory(src/asmjs)
409409
add_subdirectory(src/cfg)
410410
add_subdirectory(src/emscripten-optimizer)
411+
add_subdirectory(src/interpreter)
411412
add_subdirectory(src/passes)
412413
add_subdirectory(src/parser)
413414
add_subdirectory(src/support)
@@ -439,7 +440,8 @@ set(binaryen_objs
439440
$<TARGET_OBJECTS:cfg>
440441
$<TARGET_OBJECTS:support>
441442
$<TARGET_OBJECTS:analysis>
442-
$<TARGET_OBJECTS:parser>)
443+
$<TARGET_OBJECTS:parser>
444+
$<TARGET_OBJECTS:interpreter>)
443445

444446
if(BUILD_LLVM_DWARF)
445447
SET(binaryen_objs ${binaryen_objs} $<TARGET_OBJECTS:llvm_dwarf>)
@@ -481,7 +483,7 @@ if(EMSCRIPTEN)
481483
# binaryen.js WebAssembly variant
482484
add_executable(binaryen_wasm
483485
${binaryen_SOURCES})
484-
target_link_libraries(binaryen_wasm wasm asmjs emscripten-optimizer passes ir cfg support analysis parser wasm)
486+
target_link_libraries(binaryen_wasm wasm asmjs emscripten-optimizer passes ir cfg support analysis parser interpreter wasm)
485487
target_link_libraries(binaryen_wasm "-sFILESYSTEM")
486488
target_link_libraries(binaryen_wasm "-sEXPORT_NAME=Binaryen")
487489
target_link_libraries(binaryen_wasm "-sNODERAWFS=0")
@@ -511,7 +513,7 @@ if(EMSCRIPTEN)
511513
# binaryen.js JavaScript variant
512514
add_executable(binaryen_js
513515
${binaryen_SOURCES})
514-
target_link_libraries(binaryen_js wasm asmjs emscripten-optimizer passes ir cfg support analysis parser wasm)
516+
target_link_libraries(binaryen_js wasm asmjs emscripten-optimizer passes ir cfg support analysis parser interpreter wasm)
515517
target_link_libraries(binaryen_js "-sWASM=0")
516518
target_link_libraries(binaryen_js "-sWASM_ASYNC_COMPILATION=0")
517519
if(${CMAKE_CXX_COMPILER_VERSION} STREQUAL "6.0.1")

src/interpreter/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FILE(GLOB interpreter_HEADERS *.h)
2+
set(interpreter_SOURCES
3+
expression-iterator.cpp
4+
interpreter.cpp
5+
${interpreter_HEADERS}
6+
)
7+
add_library(interpreter OBJECT ${interpreter_SOURCES})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025 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 <algorithm>
18+
19+
#include "expression-iterator.h"
20+
#include "wasm-traversal.h"
21+
22+
namespace wasm::interpreter {
23+
24+
ExpressionIterator::ExpressionIterator(Expression* root) {
25+
// TODO: Visit loops at their beginnings instead of their ends.
26+
struct Collector
27+
: PostWalker<Collector, UnifiedExpressionVisitor<Collector>> {
28+
std::vector<Expression*>& exprs;
29+
Collector(std::vector<Expression*>& exprs) : exprs(exprs) {}
30+
void visitExpression(Expression* curr) { exprs.push_back(curr); }
31+
} collector(exprs);
32+
collector.walk(root);
33+
34+
// Reverse the expressions so we can pop from the back to advance the
35+
// iterator.
36+
std::reverse(exprs.begin(), exprs.end());
37+
}
38+
39+
} // namespace wasm::interpreter
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2025 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 interpreter_expression_iterator_h
18+
#define interpreter_expression_iterator_h
19+
20+
#include <cassert>
21+
#include <iterator>
22+
#include <vector>
23+
24+
#include "wasm.h"
25+
26+
namespace wasm::interpreter {
27+
28+
// TODO: This is a quick and dirty hack. We should implement a proper iterator
29+
// in ir/iteration.h that keeps only a vector of (Expression*, index) pairs or
30+
// alternatively a stack of ChildIterators to find the current location in the
31+
// expression tree. Better yet, improve ChildIterator and then use it here.
32+
struct ExpressionIterator {
33+
using difference_type = std::ptrdiff_t;
34+
using value_type = Expression*;
35+
using pointer = Expression**;
36+
using reference = Expression*&;
37+
using iterator_category = std::input_iterator_tag;
38+
39+
// The list of remaining instructions in reverse order so we can pop from the
40+
// back to advance the iterator.
41+
std::vector<Expression*> exprs;
42+
43+
ExpressionIterator(Expression* root);
44+
45+
ExpressionIterator() = default;
46+
ExpressionIterator(const ExpressionIterator&) = default;
47+
ExpressionIterator(ExpressionIterator&&) = default;
48+
ExpressionIterator& operator=(const ExpressionIterator&) = default;
49+
ExpressionIterator& operator=(ExpressionIterator&&) = default;
50+
51+
operator bool() { return exprs.size(); }
52+
53+
Expression* operator*() {
54+
assert(exprs.size());
55+
return exprs.back();
56+
}
57+
58+
ExpressionIterator& operator++() {
59+
assert(exprs.size());
60+
exprs.pop_back();
61+
return *this;
62+
}
63+
64+
bool operator==(const ExpressionIterator& other) {
65+
return exprs.size() == other.exprs.size();
66+
}
67+
68+
bool operator!=(const ExpressionIterator& other) { return !(*this == other); }
69+
};
70+
71+
} // namespace wasm::interpreter
72+
73+
#endif // interpreter_expression_iterator_h

src/interpreter/interpreter.cpp

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright 2025 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 "interpreter/interpreter.h"
18+
#include "interpreter/expression-iterator.h"
19+
#include "interpreter/store.h"
20+
#include "wasm-traversal.h"
21+
22+
namespace wasm {
23+
24+
using namespace interpreter;
25+
26+
// Provides access to the interpreter's private store.
27+
class InterpreterImpl {
28+
public:
29+
static WasmStore& getStore(Interpreter& interpreter) {
30+
return interpreter.store;
31+
}
32+
};
33+
34+
namespace {
35+
36+
struct Branch {
37+
Name label;
38+
};
39+
40+
// TODO: Handle other forms of control flow.
41+
struct Flow : std::variant<std::monostate, Branch> {
42+
operator bool() { return !std::get_if<std::monostate>(this); }
43+
};
44+
45+
struct ExpressionInterpreter : OverriddenVisitor<ExpressionInterpreter, Flow> {
46+
Interpreter& parent;
47+
ExpressionInterpreter(Interpreter& parent) : parent(parent) {}
48+
49+
WasmStore& store() { return InterpreterImpl::getStore(parent); }
50+
void push(Literal val) { store().push(val); }
51+
Literal pop() { return store().pop(); }
52+
53+
Flow visitNop(Nop* curr) { WASM_UNREACHABLE("TODO"); }
54+
Flow visitBlock(Block* curr) { WASM_UNREACHABLE("TODO"); }
55+
Flow visitIf(If* curr) { WASM_UNREACHABLE("TODO"); }
56+
Flow visitLoop(Loop* curr) { WASM_UNREACHABLE("TODO"); }
57+
Flow visitBreak(Break* curr) { WASM_UNREACHABLE("TODO"); }
58+
Flow visitSwitch(Switch* curr) { WASM_UNREACHABLE("TODO"); }
59+
Flow visitCall(Call* curr) { WASM_UNREACHABLE("TODO"); }
60+
Flow visitCallIndirect(CallIndirect* curr) { WASM_UNREACHABLE("TODO"); }
61+
Flow visitLocalGet(LocalGet* curr) { WASM_UNREACHABLE("TODO"); }
62+
Flow visitLocalSet(LocalSet* curr) { WASM_UNREACHABLE("TODO"); }
63+
Flow visitGlobalGet(GlobalGet* curr) { WASM_UNREACHABLE("TODO"); }
64+
Flow visitGlobalSet(GlobalSet* curr) { WASM_UNREACHABLE("TODO"); }
65+
Flow visitLoad(Load* curr) { WASM_UNREACHABLE("TODO"); }
66+
Flow visitStore(Store* curr) { WASM_UNREACHABLE("TODO"); }
67+
Flow visitAtomicRMW(AtomicRMW* curr) { WASM_UNREACHABLE("TODO"); }
68+
Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { WASM_UNREACHABLE("TODO"); }
69+
Flow visitAtomicWait(AtomicWait* curr) { WASM_UNREACHABLE("TODO"); }
70+
Flow visitAtomicNotify(AtomicNotify* curr) { WASM_UNREACHABLE("TODO"); }
71+
Flow visitAtomicFence(AtomicFence* curr) { WASM_UNREACHABLE("TODO"); }
72+
Flow visitSIMDExtract(SIMDExtract* curr) { WASM_UNREACHABLE("TODO"); }
73+
Flow visitSIMDReplace(SIMDReplace* curr) { WASM_UNREACHABLE("TODO"); }
74+
Flow visitSIMDShuffle(SIMDShuffle* curr) { WASM_UNREACHABLE("TODO"); }
75+
Flow visitSIMDTernary(SIMDTernary* curr) { WASM_UNREACHABLE("TODO"); }
76+
Flow visitSIMDShift(SIMDShift* curr) { WASM_UNREACHABLE("TODO"); }
77+
Flow visitSIMDLoad(SIMDLoad* curr) { WASM_UNREACHABLE("TODO"); }
78+
Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {
79+
WASM_UNREACHABLE("TODO");
80+
}
81+
Flow visitMemoryInit(MemoryInit* curr) { WASM_UNREACHABLE("TODO"); }
82+
Flow visitDataDrop(DataDrop* curr) { WASM_UNREACHABLE("TODO"); }
83+
Flow visitMemoryCopy(MemoryCopy* curr) { WASM_UNREACHABLE("TODO"); }
84+
Flow visitMemoryFill(MemoryFill* curr) { WASM_UNREACHABLE("TODO"); }
85+
Flow visitConst(Const* curr) {
86+
push(curr->value);
87+
return {};
88+
}
89+
Flow visitUnary(Unary* curr) { WASM_UNREACHABLE("TODO"); }
90+
Flow visitBinary(Binary* curr) {
91+
auto rhs = pop();
92+
auto lhs = pop();
93+
// TODO: switch-case over all operations.
94+
if (curr->op == AddInt32) {
95+
push(lhs.add(rhs));
96+
return {};
97+
}
98+
WASM_UNREACHABLE("TODO");
99+
}
100+
Flow visitSelect(Select* curr) { WASM_UNREACHABLE("TODO"); }
101+
Flow visitDrop(Drop* curr) { WASM_UNREACHABLE("TODO"); }
102+
Flow visitReturn(Return* curr) { WASM_UNREACHABLE("TODO"); }
103+
Flow visitMemorySize(MemorySize* curr) { WASM_UNREACHABLE("TODO"); }
104+
Flow visitMemoryGrow(MemoryGrow* curr) { WASM_UNREACHABLE("TODO"); }
105+
Flow visitUnreachable(Unreachable* curr) { WASM_UNREACHABLE("TODO"); }
106+
Flow visitPop(Pop* curr) { WASM_UNREACHABLE("TODO"); }
107+
Flow visitRefNull(RefNull* curr) { WASM_UNREACHABLE("TODO"); }
108+
Flow visitRefIsNull(RefIsNull* curr) { WASM_UNREACHABLE("TODO"); }
109+
Flow visitRefFunc(RefFunc* curr) { WASM_UNREACHABLE("TODO"); }
110+
Flow visitRefEq(RefEq* curr) { WASM_UNREACHABLE("TODO"); }
111+
Flow visitTableGet(TableGet* curr) { WASM_UNREACHABLE("TODO"); }
112+
Flow visitTableSet(TableSet* curr) { WASM_UNREACHABLE("TODO"); }
113+
Flow visitTableSize(TableSize* curr) { WASM_UNREACHABLE("TODO"); }
114+
Flow visitTableGrow(TableGrow* curr) { WASM_UNREACHABLE("TODO"); }
115+
Flow visitTableFill(TableFill* curr) { WASM_UNREACHABLE("TODO"); }
116+
Flow visitTableCopy(TableCopy* curr) { WASM_UNREACHABLE("TODO"); }
117+
Flow visitTableInit(TableInit* curr) { WASM_UNREACHABLE("TODO"); }
118+
Flow visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); }
119+
Flow visitTryTable(TryTable* curr) { WASM_UNREACHABLE("TODO"); }
120+
Flow visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); }
121+
Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("TODO"); }
122+
Flow visitThrowRef(ThrowRef* curr) { WASM_UNREACHABLE("TODO"); }
123+
Flow visitTupleMake(TupleMake* curr) { WASM_UNREACHABLE("TODO"); }
124+
Flow visitTupleExtract(TupleExtract* curr) { WASM_UNREACHABLE("TODO"); }
125+
Flow visitRefI31(RefI31* curr) { WASM_UNREACHABLE("TODO"); }
126+
Flow visitI31Get(I31Get* curr) { WASM_UNREACHABLE("TODO"); }
127+
Flow visitCallRef(CallRef* curr) { WASM_UNREACHABLE("TODO"); }
128+
Flow visitRefTest(RefTest* curr) { WASM_UNREACHABLE("TODO"); }
129+
Flow visitRefCast(RefCast* curr) { WASM_UNREACHABLE("TODO"); }
130+
Flow visitBrOn(BrOn* curr) { WASM_UNREACHABLE("TODO"); }
131+
Flow visitStructNew(StructNew* curr) { WASM_UNREACHABLE("TODO"); }
132+
Flow visitStructGet(StructGet* curr) { WASM_UNREACHABLE("TODO"); }
133+
Flow visitStructSet(StructSet* curr) { WASM_UNREACHABLE("TODO"); }
134+
Flow visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); }
135+
Flow visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); }
136+
Flow visitArrayNew(ArrayNew* curr) { WASM_UNREACHABLE("TODO"); }
137+
Flow visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("TODO"); }
138+
Flow visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("TODO"); }
139+
Flow visitArrayNewFixed(ArrayNewFixed* curr) { WASM_UNREACHABLE("TODO"); }
140+
Flow visitArrayGet(ArrayGet* curr) { WASM_UNREACHABLE("TODO"); }
141+
Flow visitArraySet(ArraySet* curr) { WASM_UNREACHABLE("TODO"); }
142+
Flow visitArrayLen(ArrayLen* curr) { WASM_UNREACHABLE("TODO"); }
143+
Flow visitArrayCopy(ArrayCopy* curr) { WASM_UNREACHABLE("TODO"); }
144+
Flow visitArrayFill(ArrayFill* curr) { WASM_UNREACHABLE("TODO"); }
145+
Flow visitArrayInitData(ArrayInitData* curr) { WASM_UNREACHABLE("TODO"); }
146+
Flow visitArrayInitElem(ArrayInitElem* curr) { WASM_UNREACHABLE("TODO"); }
147+
Flow visitRefAs(RefAs* curr) { WASM_UNREACHABLE("TODO"); }
148+
Flow visitStringNew(StringNew* curr) { WASM_UNREACHABLE("TODO"); }
149+
Flow visitStringConst(StringConst* curr) { WASM_UNREACHABLE("TODO"); }
150+
Flow visitStringMeasure(StringMeasure* curr) { WASM_UNREACHABLE("TODO"); }
151+
Flow visitStringEncode(StringEncode* curr) { WASM_UNREACHABLE("TODO"); }
152+
Flow visitStringConcat(StringConcat* curr) { WASM_UNREACHABLE("TODO"); }
153+
Flow visitStringEq(StringEq* curr) { WASM_UNREACHABLE("TODO"); }
154+
Flow visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); }
155+
Flow visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); }
156+
Flow visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); }
157+
Flow visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); }
158+
Flow visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); }
159+
Flow visitSuspend(Suspend* curr) { WASM_UNREACHABLE("TODO"); }
160+
};
161+
162+
} // anonymous namespace
163+
164+
std::vector<Literal> Interpreter::run(Expression* root) {
165+
// Create a fresh store and execution frame, then run the expression to
166+
// completion.
167+
store = WasmStore();
168+
store.callStack.emplace_back();
169+
store.callStack.back().exprs = ExpressionIterator(root);
170+
171+
ExpressionInterpreter interpreter(*this);
172+
while (auto& it = store.callStack.back().exprs) {
173+
if (auto flow = interpreter.visit(*it)) {
174+
// TODO: Handle control flow transfers.
175+
} else {
176+
++it;
177+
}
178+
}
179+
180+
return store.callStack.back().valueStack;
181+
}
182+
183+
} // namespace wasm

src/interpreter/interpreter.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2025 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 interpreter_interpreter_h
18+
#define interpreter_interpreter_h
19+
20+
#include "store.h"
21+
22+
namespace wasm {
23+
24+
class Interpreter {
25+
public:
26+
// TODO: Methods to instantiate modules.
27+
// TODO: Methods to run exported functions.
28+
std::vector<Literal> run(Expression* root);
29+
30+
private:
31+
interpreter::WasmStore store;
32+
friend class InterpreterImpl;
33+
};
34+
35+
} // namespace wasm
36+
37+
#endif // interpreter_interpreter_h

0 commit comments

Comments
 (0)