Skip to content

Commit 836d2b5

Browse files
authored
v3: add WebAssembly backend (#27545)
1 parent abb03c7 commit 836d2b5

7 files changed

Lines changed: 4050 additions & 9 deletions

File tree

vlib/v3/README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
Clean rewrite of the V compiler. Reuses v2's scanner, uses a flat AST parser
44
with Pratt parsing, a structured type system with sum-type variants, lexical
55
scoping, a transformer for AST simplification, a shared type-checking phase, a
6-
markused pass for dead-code elimination, recursive import resolution, and two
7-
backends: a direct flat-AST-to-C backend and a native ARM64 backend via SSA IR
8-
with a built-in linker. With `-prod`, the ARM64 backend runs SSA optimization,
9-
MIR lowering, and instruction selection.
6+
markused pass for dead-code elimination, recursive import resolution, and three
7+
backends: a direct flat-AST-to-C backend, a native ARM64 backend via SSA IR with
8+
a built-in linker, and a direct flat-AST-to-WebAssembly backend. With `-prod`,
9+
the ARM64 backend runs SSA optimization, MIR lowering, and instruction
10+
selection.
1011

1112
Imports all `vlib/builtin/` V source files, both pure V (`.v`) and C-interop
1213
(`.c.v`), for struct, enum, type alias, interface, C function declarations, and
@@ -63,9 +64,25 @@ repeats until no new imports are found.
6364
source + vlib/builtin -> scanner -> flat parser -> flat AST -> imports
6465
-> check -> transform -> annotate types -> markused -> gen C -> cc
6566
\-> SSA build -> ARM64 gen -> link
66-
\-> optimize -> MIR -> insel (-prod)
67+
| \-> optimize -> MIR -> insel (-prod)
68+
\-> gen WASM -> .wasm
6769
```
6870

71+
The WebAssembly backend (`-b wasm`) walks the flat AST directly, like the C
72+
backend, since WASM's structured control flow (`block`/`loop`/`if`/`br`) maps
73+
cleanly from the tree and needs no relooping. It emits a self-contained `.wasm`
74+
module via its own minimal binary encoder (LEB128 + section assembly, mirroring
75+
how the ARM64 backend ships its own `asm`/`macho`/`linker`), so v3 stays
76+
self-contained. The current scope is the integer/float core: functions with
77+
numeric/bool params and locals, arithmetic, comparison, logical (short-circuit),
78+
bitwise and shift operators, casts, `if`/`else`/`else if`, all `for` forms with
79+
`break`/`continue`, direct calls, and recursion. `print`/`println` of string
80+
literals, integers, and booleans is provided through WASI `fd_write` with a
81+
built-in `itoa` helper. The module is a WASI command (`_start` calls `main`) and
82+
also exports every compiled function for direct testing. Generics, strings as
83+
values, structs, arrays, and maps are out of scope for now. Output runs under any
84+
WASI runtime (e.g. `node:wasi`).
85+
6986
The parser directly emits a flat AST. There is no recursive AST intermediate and
7087
no flatten step. All nodes live in a single `[]Node` array with children as
7188
indices into a separate `[]NodeId` array. No pointer chasing, no recursive sum

vlib/v3/gen/wasm/code.v

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
module wasm
2+
3+
// code.v emits the WASM instruction stream for a single function body. It is a
4+
// thin layer over a []u8 buffer with one method per opcode used by the backend,
5+
// analogous to the arm64 backend's asm.v instruction encoders.
6+
7+
pub struct Code {
8+
pub mut:
9+
bytes []u8
10+
}
11+
12+
@[inline]
13+
pub fn (mut c Code) raw(b u8) {
14+
c.bytes << b
15+
}
16+
17+
// ---- constants ----
18+
19+
pub fn (mut c Code) i32_const(v i64) {
20+
c.bytes << 0x41
21+
// Wrap to a signed 32-bit value so the LEB128 stays in i32 range; e.g.
22+
// u32(4000000000) must encode as the 32-bit pattern, not a 5-byte i64.
23+
leb_i(mut c.bytes, i64(i32(v)))
24+
}
25+
26+
pub fn (mut c Code) i64_const(v i64) {
27+
c.bytes << 0x42
28+
leb_i(mut c.bytes, v)
29+
}
30+
31+
pub fn (mut c Code) f32_const(v f32) {
32+
c.bytes << 0x43
33+
bits := u32(f32_bits(v))
34+
for i in 0 .. 4 {
35+
c.bytes << u8((bits >> (8 * i)) & 0xff)
36+
}
37+
}
38+
39+
pub fn (mut c Code) f64_const(v f64) {
40+
c.bytes << 0x44
41+
bits := f64_bits(v)
42+
for i in 0 .. 8 {
43+
c.bytes << u8((bits >> (8 * i)) & 0xff)
44+
}
45+
}
46+
47+
fn f32_bits(v f32) u32 {
48+
return unsafe { *(&u32(&v)) }
49+
}
50+
51+
fn f64_bits(v f64) u64 {
52+
return unsafe { *(&u64(&v)) }
53+
}
54+
55+
// ---- locals / globals ----
56+
57+
pub fn (mut c Code) local_get(idx int) {
58+
c.bytes << 0x20
59+
leb_u(mut c.bytes, u64(idx))
60+
}
61+
62+
pub fn (mut c Code) local_set(idx int) {
63+
c.bytes << 0x21
64+
leb_u(mut c.bytes, u64(idx))
65+
}
66+
67+
pub fn (mut c Code) local_tee(idx int) {
68+
c.bytes << 0x22
69+
leb_u(mut c.bytes, u64(idx))
70+
}
71+
72+
pub fn (mut c Code) global_get(idx int) {
73+
c.bytes << 0x23
74+
leb_u(mut c.bytes, u64(idx))
75+
}
76+
77+
pub fn (mut c Code) global_set(idx int) {
78+
c.bytes << 0x24
79+
leb_u(mut c.bytes, u64(idx))
80+
}
81+
82+
// ---- memory ----
83+
84+
pub fn (mut c Code) load(op u8, align int, offset int) {
85+
c.bytes << op
86+
leb_u(mut c.bytes, u64(align))
87+
leb_u(mut c.bytes, u64(offset))
88+
}
89+
90+
pub fn (mut c Code) store(op u8, align int, offset int) {
91+
c.bytes << op
92+
leb_u(mut c.bytes, u64(align))
93+
leb_u(mut c.bytes, u64(offset))
94+
}
95+
96+
// i32_store / i32_load with natural alignment for a full i32.
97+
pub fn (mut c Code) i32_store(offset int) {
98+
c.store(0x36, 2, offset)
99+
}
100+
101+
pub fn (mut c Code) i32_store8(offset int) {
102+
c.store(0x3a, 0, offset)
103+
}
104+
105+
pub fn (mut c Code) i32_load(offset int) {
106+
c.load(0x28, 2, offset)
107+
}
108+
109+
// ---- control flow ----
110+
111+
pub fn (mut c Code) block_void() {
112+
c.bytes << 0x02
113+
c.bytes << 0x40
114+
}
115+
116+
pub fn (mut c Code) loop_void() {
117+
c.bytes << 0x03
118+
c.bytes << 0x40
119+
}
120+
121+
pub fn (mut c Code) if_void() {
122+
c.bytes << 0x04
123+
c.bytes << 0x40
124+
}
125+
126+
pub fn (mut c Code) else_() {
127+
c.bytes << 0x05
128+
}
129+
130+
pub fn (mut c Code) end() {
131+
c.bytes << 0x0b
132+
}
133+
134+
pub fn (mut c Code) br(depth int) {
135+
c.bytes << 0x0c
136+
leb_u(mut c.bytes, u64(depth))
137+
}
138+
139+
pub fn (mut c Code) br_if(depth int) {
140+
c.bytes << 0x0d
141+
leb_u(mut c.bytes, u64(depth))
142+
}
143+
144+
pub fn (mut c Code) ret() {
145+
c.bytes << 0x0f
146+
}
147+
148+
pub fn (mut c Code) call(idx int) {
149+
c.bytes << 0x10
150+
leb_u(mut c.bytes, u64(idx))
151+
}
152+
153+
pub fn (mut c Code) drop() {
154+
c.bytes << 0x1a
155+
}

0 commit comments

Comments
 (0)