Skip to content

Commit 83f2835

Browse files
butterunderflowahuoguoKraks
authored
Staged Miniwasm Interpreter (#88)
* try lms * compose all parts * Frame should be opaque * function call * factor out getFuncType * fix: use restK when non-tail call * compile Block-like instructions(if-else, loop, block) * branching instructions * local set * operators * global instructions * placeholder for mem instructions * scala code generation * some imported function * polish * ci * tweak * try some simplification * improve runtime(the prelude) * some fixes * fix: Frame creation is not optimizable * demo br_table's attempts * fix: tail call * fix global * fix: code generation for global.set * brtable seems to work, but there is code duplication problem * effectful staged interpreter * remove non-sense tests * scratch cpp backend * some tweaks * fix some of the nothing type * manually supply the reflect's type arguments * lift every function to top level & avoid lms's common subexpr elimination * stack pop example * not inlining + shallow * an almost work runtime * emit functions * read a dummy node to avoid lambda lifting it seems that the lambda lifting is unsound * capture by value is not friendly with recursion * redirect generated code to a file * fix printing logic in test * extract the dummy writing pattern as a function * don't inline stack-pop to improve readability * make topFun work * update runtime * add all passed test cases * store/load operation * more memory operations * some fixes * some little polish * shift stack elements when exiting block instructions * fix: evalTop should be aware of frame size * comment IO statements * benchmark code * with std c++17 * ensure the compiled program is executed correctly * utilize type information * lifting to the top * avoid re-registering top function * remove std::vector usages & use O3 in benchmark * split header from prelude * move NewStagedEvalCPS.scala to attic --------- Co-authored-by: ahuoguo <ahuoguo@gmail.com> Co-authored-by: Guannan Wei <guannan.wei@tufts.edu>
1 parent 96561f3 commit 83f2835

File tree

17 files changed

+1721
-37
lines changed

17 files changed

+1721
-37
lines changed

.github/workflows/scala.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,4 @@ jobs:
7878
sbt 'testOnly gensym.wasm.TestScriptRun'
7979
sbt 'testOnly gensym.wasm.TestConcolic'
8080
sbt 'testOnly gensym.wasm.TestDriver'
81+
sbt 'testOnly gensym.wasm.TestStagedEval'

benchmarks/wasm/global.wat

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(module
2+
(type (;0;) (func (result i32)))
3+
(type (;1;) (func))
4+
5+
(func (;0;) (type 0) (result i32)
6+
i32.const 42
7+
global.set 0
8+
global.get 0
9+
)
10+
(func (;1;) (type 1)
11+
call 0
12+
;; should be 42
13+
;; drop
14+
)
15+
(start 1)
16+
(memory (;0;) 2)
17+
(export "main" (func 1))
18+
(global (;0;) (mut i32) (i32.const 0))
19+
)

benchmarks/wasm/performance/ack.wat

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
(module $ack.wat.temp
2+
(type (;0;) (func (param i32 i32) (result i32)))
3+
(type (;1;) (func (result i32)))
4+
(func $ack (type 0) (param i32 i32) (result i32)
5+
local.get 0
6+
local.set 0
7+
local.get 1
8+
local.set 1
9+
block ;; label = @1
10+
loop ;; label = @2
11+
local.get 1
12+
local.set 1
13+
local.get 0
14+
local.tee 0
15+
i32.eqz
16+
br_if 1 (;@1;)
17+
block ;; label = @3
18+
block ;; label = @4
19+
local.get 1
20+
br_if 0 (;@4;)
21+
i32.const 1
22+
local.set 1
23+
br 1 (;@3;)
24+
end
25+
local.get 0
26+
local.get 1
27+
i32.const -1
28+
i32.add
29+
call $ack
30+
local.set 1
31+
end
32+
local.get 0
33+
i32.const -1
34+
i32.add
35+
local.set 0
36+
local.get 1
37+
local.set 1
38+
br 0 (;@2;)
39+
end
40+
end
41+
local.get 1
42+
i32.const 1
43+
i32.add)
44+
(func $real_main (type 1) (result i32)
45+
(local i32 i32)
46+
i32.const 10000
47+
local.set 0
48+
loop
49+
i32.const 2
50+
i32.const 1
51+
call $ack
52+
local.set 1
53+
local.get 0
54+
i32.const 1
55+
i32.sub
56+
local.tee 0
57+
br_if 0
58+
end
59+
local.get 1)
60+
(table (;0;) 1 1 funcref)
61+
(memory (;0;) 16)
62+
(global $__stack_pointer (mut i32) (i32.const 1048576))
63+
(global (;1;) i32 (i32.const 1048576))
64+
(global (;2;) i32 (i32.const 1048576))
65+
(export "memory" (memory 0))
66+
(export "ack" (func 0))
67+
(export "real_main" (func 1))
68+
(start 1)
69+
(export "__data_end" (global 1))
70+
(export "__heap_base" (global 2)))

benchmarks/wasm/performance/pow.wat

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
(module $pow.temp
2+
(type (;0;) (func (param i32 i32) (result i32)))
3+
(type (;1;) (func (result i32)))
4+
(func $power (type 0) (param i32 i32) (result i32)
5+
(local i32)
6+
i32.const 1
7+
local.set 2
8+
local.get 1
9+
local.set 1
10+
block ;; label = @1
11+
loop ;; label = @2
12+
local.get 2
13+
local.set 2
14+
local.get 1
15+
local.tee 1
16+
i32.eqz
17+
br_if 1 (;@1;)
18+
local.get 2
19+
local.get 0
20+
i32.mul
21+
local.set 2
22+
local.get 1
23+
i32.const -1
24+
i32.add
25+
local.set 1
26+
br 0 (;@2;)
27+
end
28+
end
29+
local.get 2)
30+
(func $real_main (type 1) (result i32)
31+
(local i32 i32)
32+
i32.const 10000 ;; loop counter
33+
local.set 0 ;; reuse param 0 as loop counter
34+
loop ;; label = @2
35+
i32.const 2
36+
i32.const 20
37+
call $power
38+
local.set 1
39+
local.get 0
40+
i32.const 1
41+
i32.sub
42+
local.tee 0
43+
br_if 0 ;; continue loop if counter != 0
44+
end
45+
local.get 1)
46+
(table (;0;) 1 1 funcref)
47+
(memory (;0;) 16)
48+
(global $__stack_pointer (mut i32) (i32.const 1048576))
49+
(global (;1;) i32 (i32.const 1048576))
50+
(global (;2;) i32 (i32.const 1048576))
51+
(export "memory" (memory 0))
52+
(export "power" (func 0))
53+
(export "real_main" (func 1))
54+
(export "__data_end" (global 1))
55+
(export "__heap_base" (global 2)))

benchmarks/wasm/staged/brtable.wat

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(module $push-drop
2+
(global (;0;) (mut i32) (i32.const 1048576))
3+
(func (;0;) (type 1) (result i32)
4+
i32.const 2
5+
(block
6+
(block
7+
i32.const 1
8+
br_table 0 1 0 ;; br_table will consume an element from the stack
9+
)
10+
)
11+
)
12+
(start 0))

benchmarks/wasm/staged/pop.wat

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(module $push-drop
2+
(global (;0;) (mut i32) (i32.const 1048576))
3+
(func (;0;) (type 1) (result)
4+
i32.const 2
5+
i32.const 2
6+
i32.add
7+
)
8+
(start 0))

headers/wasm.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef WASM_HEADERS
2+
#define WASM_HEADERS
3+
4+
#include "wasm/concrete_rt.hpp"
5+
6+
#endif

headers/wasm/concrete_rt.hpp

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#include <cassert>
2+
#include <cstdint>
3+
#include <cstdio>
4+
#include <iostream>
5+
#include <memory>
6+
#include <ostream>
7+
#include <variant>
8+
#include <vector>
9+
10+
void info() {
11+
#ifdef DEBUG
12+
std::cout << std::endl;
13+
#endif
14+
}
15+
16+
template <typename T, typename... Args>
17+
void info(const T &first, const Args &...args) {
18+
#ifdef DEBUG
19+
std::cout << first << " ";
20+
info(args...);
21+
#endif
22+
}
23+
24+
struct Num {
25+
Num(int64_t value) : value(value) {}
26+
Num() : value(0) {}
27+
int64_t value;
28+
int32_t toInt() { return static_cast<int32_t>(value); }
29+
30+
bool operator==(const Num &other) const { return value == other.value; }
31+
bool operator!=(const Num &other) const { return !(*this == other); }
32+
Num operator+(const Num &other) const { return Num(value + other.value); }
33+
Num operator-(const Num &other) const { return Num(value - other.value); }
34+
Num operator*(const Num &other) const { return Num(value * other.value); }
35+
Num operator/(const Num &other) const {
36+
if (other.value == 0) {
37+
throw std::runtime_error("Division by zero");
38+
}
39+
return Num(value / other.value);
40+
}
41+
Num operator<(const Num &other) const { return Num(value < other.value); }
42+
Num operator<=(const Num &other) const { return Num(value <= other.value); }
43+
Num operator>(const Num &other) const { return Num(value > other.value); }
44+
Num operator>=(const Num &other) const { return Num(value >= other.value); }
45+
Num operator&(const Num &other) const { return Num(value & other.value); }
46+
};
47+
48+
static Num I32V(int v) { return v; }
49+
50+
static Num I64V(int64_t v) { return v; }
51+
52+
using Slice = std::vector<Num>;
53+
54+
const int STACK_SIZE = 1024 * 64;
55+
56+
class Stack_t {
57+
public:
58+
Stack_t() : count(0), stack_ptr(new Num[STACK_SIZE]) {}
59+
60+
std::monostate push(Num &&num) {
61+
stack_ptr[count] = num;
62+
count++;
63+
return std::monostate{};
64+
}
65+
66+
std::monostate push(Num &num) {
67+
stack_ptr[count] = num;
68+
count++;
69+
return std::monostate{};
70+
}
71+
72+
Num pop() {
73+
#ifdef DEBUG
74+
if (count == 0) {
75+
throw std::runtime_error("Stack underflow");
76+
}
77+
#endif
78+
Num num = stack_ptr[count - 1];
79+
count--;
80+
return num;
81+
}
82+
83+
Num peek() {
84+
#ifdef DEBUG
85+
if (count == 0) {
86+
throw std::runtime_error("Stack underflow");
87+
}
88+
#endif
89+
return stack_ptr[count - 1];
90+
}
91+
92+
int32_t size() { return count; }
93+
94+
void shift(int32_t offset, int32_t size) {
95+
#ifdef DEBUG
96+
if (offset < 0) {
97+
throw std::out_of_range("Invalid offset: " + std::to_string(offset));
98+
}
99+
if (size < 0) {
100+
throw std::out_of_range("Invalid size: " + std::to_string(size));
101+
}
102+
#endif
103+
// shift last `size` of numbers forward of `offset`
104+
for (int32_t i = count - size; i < count; ++i) {
105+
stack_ptr[i - offset] = stack_ptr[i];
106+
}
107+
count -= offset;
108+
}
109+
110+
void print() {
111+
std::cout << "Stack contents: " << std::endl;
112+
for (int32_t i = 0; i < count; ++i) {
113+
std::cout << stack_ptr[count - i - 1].value << std::endl;
114+
}
115+
}
116+
117+
void initialize() {
118+
// do nothing for now
119+
}
120+
121+
private:
122+
int32_t count;
123+
Num *stack_ptr;
124+
};
125+
static Stack_t Stack;
126+
127+
const int FRAME_SIZE = 1024;
128+
129+
class Frames_t {
130+
public:
131+
Frames_t() : count(0), stack_ptr(new Num[FRAME_SIZE]) {}
132+
133+
std::monostate popFrame(std::int32_t size) {
134+
assert(size >= 0);
135+
count -= size;
136+
return std::monostate{};
137+
}
138+
139+
Num get(std::int32_t index) {
140+
auto ret = stack_ptr[count - 1 - index];
141+
return ret;
142+
}
143+
144+
void set(std::int32_t index, Num num) { stack_ptr[count - 1 - index] = num; }
145+
146+
void pushFrame(std::int32_t size) {
147+
assert(size >= 0);
148+
count += size;
149+
}
150+
151+
private:
152+
int32_t count;
153+
Num *stack_ptr;
154+
};
155+
156+
static Frames_t Frames;
157+
158+
static void initRand() {
159+
// for now, just do nothing
160+
}
161+
162+
static std::monostate unreachable() {
163+
std::cout << "Unreachable code reached!" << std::endl;
164+
throw std::runtime_error("Unreachable code reached");
165+
}
166+
167+
static int32_t pagesize = 65536;
168+
static int32_t page_count = 0;
169+
170+
struct Memory_t {
171+
std::vector<uint8_t> memory;
172+
Memory_t(int32_t init_page_count) : memory(init_page_count * pagesize) {}
173+
174+
int32_t loadInt(int32_t base, int32_t offset) {
175+
return *reinterpret_cast<int32_t *>(static_cast<uint8_t *>(memory.data()) +
176+
base + offset);
177+
}
178+
179+
std::monostate storeInt(int32_t base, int32_t offset, int32_t value) {
180+
*reinterpret_cast<int32_t *>(static_cast<uint8_t *>(memory.data()) + base +
181+
offset) = value;
182+
return std::monostate{};
183+
}
184+
185+
// grow memory by delta bytes when bytes > 0. return -1 if failed, return old
186+
// size when success
187+
int32_t grow(int32_t delta) {
188+
if (delta <= 0) {
189+
return memory.size();
190+
}
191+
192+
try {
193+
memory.resize(memory.size() + delta * pagesize);
194+
auto old_page_count = page_count;
195+
page_count += delta;
196+
return memory.size();
197+
} catch (const std::bad_alloc &e) {
198+
return -1;
199+
}
200+
}
201+
};
202+
203+
static Memory_t Memory(1); // 1 page memory

0 commit comments

Comments
 (0)