Designed as a target for compiler testing it performs enough validation to pass the WebAssembly Spec Core Tests but not to run untrusted modules. The core interpreter loop is kept as simple as possible by reading the bytecode directly without the need for an intermediate representation. A small amount of auxillary information is created during a partial validation step on a per-function just-in-time basis (similar in nature to A fast in-place interpreter for WebAssembly). Many validation checks are skipped to keep the implementation small and meet the use case of a flexible development target. All non-SIMD test cases pass with the built-in tester.
-
Winter is relatively simple. The interpreter is under 3,500 semicolons. The main interpreter loop is implemented as a giant switch/case statement. As much information is pre-computed to reduce instruction overhead. For instance functions calls that cross module boundaries are detected ahead of time and handled separately from the main call instruction. Given the overhead of decoding each instruction on every execution extra optimizations such as threaded code are unlikely to have a significant impact. The main loop could be swapped out for an assembly version with minimal changes.
-
Winter has a REPL. Using the command line option
--repl
or--repl=<script-path>
allows modules to be instantiated, functions to be invoked and globals introspected. -
Winter can trace execution paths. Using the command line option
--trace
on the debug build causes the auxillary information to be displayed when functions are first invoked. Each instruction executed is then traced for debugging. -
Winter's module reader uses a novel form of the specificaton. The specificaton for the binary representation of modules is given by WebAssembly.json and in human readabale form WebAssembly.txt. A Python tool then generates a C++ reader automagically. This form of the specification was derived from online sources and hand crafted from the offical specificaton which unfortunately does not lend itself to programmatic generation.
-
Winter can be fully tested using command line arguments. The specificaton tests are stateful when they run meaning most implementations cannot be tested without linking directly to the source. Winter uses its REPL to invoke methods, read globals and register and instantiate modules in a single (stateful) session. The implicit
spectest
module is provided externally as a compiled.wat
file. The tester is written in Python and runs externally to the interpreter.
./build.sh
Runs GCC to compile the main source to bin
.
./test.sh --winter=bin/winterd <path-to-webassembly-spec>/test/core/ --exclude-dir=simd
Run the Python tester Twinter
on all .wast
files in the target directory.
Each file is compiled as needed using wat2wasm
and run in a single Winter
session as an external process. If tests fail then the additional command line option --trace
will show the trace of the last test.
./bin/winterd <path-to-webassembly-spec>/test/core/output/names.2.wasm --repl --trace
> ?
...
Ꙗ: () -> (i32)
: () -> (i32)
: () -> (i32)
Ɐ: () -> (i32)
🅐: () -> (i32)
🅰: () -> (i32)
Ⱝ: () -> (i32)
𐐂: () -> (i32)
...
> Ɐ
Entering: __main__
-- Tracing --
eval[ 0 ]: trap guard
-- Building 174 --
function(() -> (i32))[0]
in[ 0 ]: 41 {+1 @1}
in[ 3 ]: end {@1}
function(() -> (i32))[0]
out[ 0 ]: 41 {+1 @1}
out[ 3/ 0]: return {@1} {move 1/0 jmp 3/0}
eval[ 0 ]: i32.const 174
eval[ 3/ 0]: return {move 1/0 jmp 3/0}
eval[ 0 ]: trap exit
174
or
./bin/winterd <path-to-webassembly-spec>/test/core/output/names.2.wasm --trace --invoke="Ɐ"
./bin/winterd --repl --trace
> instantiate <path-to-webassembly-spec>/test/core/output/names.2.wasm
> ""
Entering: __main__
-- Tracing --
eval[ 0 ]: trap guard
-- Building 0 --
function(() -> (i32))[0]
in[ 0 ]: 41 {+1 @1}
in[ 2 ]: end {@1}
function(() -> (i32))[0]
out[ 0 ]: 41 {+1 @1}
out[ 2/ 0]: return {@1} {move 1/0 jmp 2/0}
eval[ 0 ]: i32.const 0
eval[ 2/ 0]: return {move 1/0 jmp 2/0}
eval[ 0 ]: trap exit
0
> register "some long alias"
> "some long alias":""
Entering: __main__
-- Tracing --
eval[ 0 ]: i32.const 0
eval[ 2/ 0]: return {move 1/0 jmp 2/0}
eval[ 0 ]: trap exit
0
>
./bin/winterd <path-to-webassembly-spec>/test/core/output/imports.2.wasm --instantiate=spectest:spec/spectest.wasm --repl --trace
> ?
-- Globals --
spectest:global_i32: 666
spectest:global_i64: 666
spectest:global_f32: 0x1.4dp+9
spectest:global_f64: 0x1.4dp+9
-- Functions --
print_i32: (i32) -> ()
spectest:print: () -> ()
spectest:print_i32: (i32) -> ()
spectest:print_i64: (i64) -> ()
spectest:print_f32: (f32) -> ()
spectest:print_f64: (f64) -> ()
spectest:print_i32_f32: (i32, f32) -> ()
spectest:print_f64_f64: (f64, f64) -> ()
> spectest:print_i32 21
Entering: spectest
-- Tracing --
eval[ 0 ]: trap guard
-- Building 1 --
function((i32) -> ())[0]
in[ 0 ]: end {@0}
function((i32) -> ())[0]
out[ 0/ 0]: return {@0} {move 0/1 jmp 0/0}
eval[ 0/ 0]: return {move 0/1 jmp 0/0}
eval[ 0 ]: trap exit
> get spectest:global_i32
666
>