Skip to content

Staged Miniwasm Interpreter #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 62 commits into from
Jul 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
157aabf
try lms
butterunderflow Apr 22, 2025
a11a7ff
compose all parts
butterunderflow Apr 25, 2025
9408c85
Frame should be opaque
butterunderflow Apr 25, 2025
60c782b
function call
butterunderflow Apr 26, 2025
442d8d1
factor out getFuncType
butterunderflow Apr 26, 2025
ab679f3
fix: use restK when non-tail call
butterunderflow Apr 26, 2025
8ba8657
compile Block-like instructions(if-else, loop, block)
butterunderflow Apr 26, 2025
e2cc801
branching instructions
butterunderflow Apr 26, 2025
fa3d628
local set
butterunderflow Apr 27, 2025
d78a96b
operators
butterunderflow Apr 27, 2025
dac7e1c
global instructions
butterunderflow Apr 27, 2025
cc65f63
placeholder for mem instructions
butterunderflow Apr 27, 2025
cf3063a
scala code generation
butterunderflow Apr 27, 2025
80bfa68
some imported function
butterunderflow Apr 27, 2025
881eda4
polish
butterunderflow Apr 27, 2025
7a2bfd4
ci
butterunderflow Apr 27, 2025
294fcea
tweak
butterunderflow Apr 27, 2025
f450b5c
try some simplification
butterunderflow Apr 27, 2025
336eec5
improve runtime(the prelude)
butterunderflow Apr 27, 2025
6a666f3
some fixes
butterunderflow Apr 28, 2025
9947bec
fix: Frame creation is not optimizable
butterunderflow Apr 28, 2025
e7da823
demo br_table's attempts
butterunderflow Apr 29, 2025
2de28f5
fix: tail call
butterunderflow Apr 29, 2025
b5a69dc
fix global
ahuoguo Apr 29, 2025
de8f18e
fix: code generation for global.set
butterunderflow Apr 29, 2025
3bbd27e
brtable seems to work, but there is code duplication problem
Apr 29, 2025
a83eb06
effectful staged interpreter
butterunderflow May 4, 2025
b8a9aea
remove non-sense tests
butterunderflow May 4, 2025
b7b8786
scratch cpp backend
butterunderflow May 5, 2025
0a8339e
some tweaks
butterunderflow May 6, 2025
b4703c7
fix some of the nothing type
Kraks May 12, 2025
29acef0
manually supply the reflect's type arguments
butterunderflow May 13, 2025
67b077b
lift every function to top level & avoid lms's common subexpr elimina…
butterunderflow May 14, 2025
6e41521
stack pop example
butterunderflow May 14, 2025
9f04722
not inlining + shallow
Kraks May 15, 2025
ed9c8e4
an almost work runtime
butterunderflow May 17, 2025
d5ed20d
emit functions
butterunderflow May 17, 2025
4fb5424
read a dummy node to avoid lambda lifting
butterunderflow May 18, 2025
8e293b8
capture by value is not friendly with recursion
butterunderflow May 18, 2025
51dd632
redirect generated code to a file
butterunderflow May 18, 2025
2c6d5f6
fix printing logic in test
butterunderflow May 19, 2025
5dbc219
extract the dummy writing pattern as a function
butterunderflow May 19, 2025
a0d31e5
don't inline stack-pop to improve readability
butterunderflow May 19, 2025
39baa4a
make topFun work
butterunderflow May 20, 2025
5985803
update runtime
butterunderflow May 20, 2025
d314ebe
add all passed test cases
butterunderflow May 20, 2025
1f902e0
store/load operation
butterunderflow May 20, 2025
6a1db1d
more memory operations
butterunderflow May 20, 2025
a2b63f9
some fixes
butterunderflow May 21, 2025
d1ba899
some little polish
butterunderflow May 21, 2025
89d9a77
shift stack elements when exiting block instructions
butterunderflow May 21, 2025
8b4429f
fix: evalTop should be aware of frame size
butterunderflow May 23, 2025
8f4d6e0
comment IO statements
butterunderflow May 23, 2025
e68218c
benchmark code
butterunderflow May 23, 2025
46b4894
with std c++17
Kraks May 23, 2025
4dd702f
ensure the compiled program is executed correctly
butterunderflow May 23, 2025
f3861b1
utilize type information
butterunderflow May 16, 2025
4e907a3
lifting to the top
butterunderflow Jun 11, 2025
3adc60c
avoid re-registering top function
butterunderflow Jun 11, 2025
86061f1
remove std::vector usages & use O3 in benchmark
butterunderflow Jun 16, 2025
333a8d6
split header from prelude
butterunderflow Jun 20, 2025
251e014
move NewStagedEvalCPS.scala to attic
butterunderflow Jul 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ jobs:
sbt 'testOnly gensym.wasm.TestScriptRun'
sbt 'testOnly gensym.wasm.TestConcolic'
sbt 'testOnly gensym.wasm.TestDriver'
sbt 'testOnly gensym.wasm.TestStagedEval'
19 changes: 19 additions & 0 deletions benchmarks/wasm/global.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(module
(type (;0;) (func (result i32)))
(type (;1;) (func))

(func (;0;) (type 0) (result i32)
i32.const 42
global.set 0
global.get 0
)
(func (;1;) (type 1)
call 0
;; should be 42
;; drop
)
(start 1)
(memory (;0;) 2)
(export "main" (func 1))
(global (;0;) (mut i32) (i32.const 0))
)
70 changes: 70 additions & 0 deletions benchmarks/wasm/performance/ack.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
(module $ack.wat.temp
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (result i32)))
(func $ack (type 0) (param i32 i32) (result i32)
local.get 0
local.set 0
local.get 1
local.set 1
block ;; label = @1
loop ;; label = @2
local.get 1
local.set 1
local.get 0
local.tee 0
i32.eqz
br_if 1 (;@1;)
block ;; label = @3
block ;; label = @4
local.get 1
br_if 0 (;@4;)
i32.const 1
local.set 1
br 1 (;@3;)
end
local.get 0
local.get 1
i32.const -1
i32.add
call $ack
local.set 1
end
local.get 0
i32.const -1
i32.add
local.set 0
local.get 1
local.set 1
br 0 (;@2;)
end
end
local.get 1
i32.const 1
i32.add)
(func $real_main (type 1) (result i32)
(local i32 i32)
i32.const 10000
local.set 0
loop
i32.const 2
i32.const 1
call $ack
local.set 1
local.get 0
i32.const 1
i32.sub
local.tee 0
br_if 0
end
local.get 1)
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global $__stack_pointer (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
(global (;2;) i32 (i32.const 1048576))
(export "memory" (memory 0))
(export "ack" (func 0))
(export "real_main" (func 1))
(start 1)
(export "__data_end" (global 1))
(export "__heap_base" (global 2)))
55 changes: 55 additions & 0 deletions benchmarks/wasm/performance/pow.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
(module $pow.temp
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (result i32)))
(func $power (type 0) (param i32 i32) (result i32)
(local i32)
i32.const 1
local.set 2
local.get 1
local.set 1
block ;; label = @1
loop ;; label = @2
local.get 2
local.set 2
local.get 1
local.tee 1
i32.eqz
br_if 1 (;@1;)
local.get 2
local.get 0
i32.mul
local.set 2
local.get 1
i32.const -1
i32.add
local.set 1
br 0 (;@2;)
end
end
local.get 2)
(func $real_main (type 1) (result i32)
(local i32 i32)
i32.const 10000 ;; loop counter
local.set 0 ;; reuse param 0 as loop counter
loop ;; label = @2
i32.const 2
i32.const 20
call $power
local.set 1
local.get 0
i32.const 1
i32.sub
local.tee 0
br_if 0 ;; continue loop if counter != 0
end
local.get 1)
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global $__stack_pointer (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
(global (;2;) i32 (i32.const 1048576))
(export "memory" (memory 0))
(export "power" (func 0))
(export "real_main" (func 1))
(export "__data_end" (global 1))
(export "__heap_base" (global 2)))
12 changes: 12 additions & 0 deletions benchmarks/wasm/staged/brtable.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(module $push-drop
(global (;0;) (mut i32) (i32.const 1048576))
(func (;0;) (type 1) (result i32)
i32.const 2
(block
(block
i32.const 1
br_table 0 1 0 ;; br_table will consume an element from the stack
)
)
)
(start 0))
8 changes: 8 additions & 0 deletions benchmarks/wasm/staged/pop.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(module $push-drop
(global (;0;) (mut i32) (i32.const 1048576))
(func (;0;) (type 1) (result)
i32.const 2
i32.const 2
i32.add
)
(start 0))
6 changes: 6 additions & 0 deletions headers/wasm.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef WASM_HEADERS
#define WASM_HEADERS

#include "wasm/concrete_rt.hpp"

#endif
203 changes: 203 additions & 0 deletions headers/wasm/concrete_rt.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <memory>
#include <ostream>
#include <variant>
#include <vector>

void info() {
#ifdef DEBUG
std::cout << std::endl;
#endif
}

template <typename T, typename... Args>
void info(const T &first, const Args &...args) {
#ifdef DEBUG
std::cout << first << " ";
info(args...);
#endif
}

struct Num {
Num(int64_t value) : value(value) {}
Num() : value(0) {}
int64_t value;
int32_t toInt() { return static_cast<int32_t>(value); }

bool operator==(const Num &other) const { return value == other.value; }
bool operator!=(const Num &other) const { return !(*this == other); }
Num operator+(const Num &other) const { return Num(value + other.value); }
Num operator-(const Num &other) const { return Num(value - other.value); }
Num operator*(const Num &other) const { return Num(value * other.value); }
Num operator/(const Num &other) const {
if (other.value == 0) {
throw std::runtime_error("Division by zero");
}
return Num(value / other.value);
}
Num operator<(const Num &other) const { return Num(value < other.value); }
Num operator<=(const Num &other) const { return Num(value <= other.value); }
Num operator>(const Num &other) const { return Num(value > other.value); }
Num operator>=(const Num &other) const { return Num(value >= other.value); }
Num operator&(const Num &other) const { return Num(value & other.value); }
};

static Num I32V(int v) { return v; }

static Num I64V(int64_t v) { return v; }

using Slice = std::vector<Num>;

const int STACK_SIZE = 1024 * 64;

class Stack_t {
public:
Stack_t() : count(0), stack_ptr(new Num[STACK_SIZE]) {}

std::monostate push(Num &&num) {
stack_ptr[count] = num;
count++;
return std::monostate{};
}

std::monostate push(Num &num) {
stack_ptr[count] = num;
count++;
return std::monostate{};
}

Num pop() {
#ifdef DEBUG
if (count == 0) {
throw std::runtime_error("Stack underflow");
}
#endif
Num num = stack_ptr[count - 1];
count--;
return num;
}

Num peek() {
#ifdef DEBUG
if (count == 0) {
throw std::runtime_error("Stack underflow");
}
#endif
return stack_ptr[count - 1];
}

int32_t size() { return count; }

void shift(int32_t offset, int32_t size) {
#ifdef DEBUG
if (offset < 0) {
throw std::out_of_range("Invalid offset: " + std::to_string(offset));
}
if (size < 0) {
throw std::out_of_range("Invalid size: " + std::to_string(size));
}
#endif
// shift last `size` of numbers forward of `offset`
for (int32_t i = count - size; i < count; ++i) {
stack_ptr[i - offset] = stack_ptr[i];
}
count -= offset;
}

void print() {
std::cout << "Stack contents: " << std::endl;
for (int32_t i = 0; i < count; ++i) {
std::cout << stack_ptr[count - i - 1].value << std::endl;
}
}

void initialize() {
// do nothing for now
}

private:
int32_t count;
Num *stack_ptr;
};
static Stack_t Stack;

const int FRAME_SIZE = 1024;

class Frames_t {
public:
Frames_t() : count(0), stack_ptr(new Num[FRAME_SIZE]) {}

std::monostate popFrame(std::int32_t size) {
assert(size >= 0);
count -= size;
return std::monostate{};
}

Num get(std::int32_t index) {
auto ret = stack_ptr[count - 1 - index];
return ret;
}

void set(std::int32_t index, Num num) { stack_ptr[count - 1 - index] = num; }

void pushFrame(std::int32_t size) {
assert(size >= 0);
count += size;
}

private:
int32_t count;
Num *stack_ptr;
};

static Frames_t Frames;

static void initRand() {
// for now, just do nothing
}

static std::monostate unreachable() {
std::cout << "Unreachable code reached!" << std::endl;
throw std::runtime_error("Unreachable code reached");
}

static int32_t pagesize = 65536;
static int32_t page_count = 0;

struct Memory_t {
std::vector<uint8_t> memory;
Memory_t(int32_t init_page_count) : memory(init_page_count * pagesize) {}

int32_t loadInt(int32_t base, int32_t offset) {
return *reinterpret_cast<int32_t *>(static_cast<uint8_t *>(memory.data()) +
base + offset);
}

std::monostate storeInt(int32_t base, int32_t offset, int32_t value) {
*reinterpret_cast<int32_t *>(static_cast<uint8_t *>(memory.data()) + base +
offset) = value;
return std::monostate{};
}

// grow memory by delta bytes when bytes > 0. return -1 if failed, return old
// size when success
int32_t grow(int32_t delta) {
if (delta <= 0) {
return memory.size();
}

try {
memory.resize(memory.size() + delta * pagesize);
auto old_page_count = page_count;
page_count += delta;
return memory.size();
} catch (const std::bad_alloc &e) {
return -1;
}
}
};

static Memory_t Memory(1); // 1 page memory
Loading