vq / vlib / v3 / gen / wasm / encode.v
310 lines · 275 sloc · 6.71 KB · 7e64778441b0a076e75a61df7eb5cfcef6c75f39
Raw
1module wasm
2
3// encode.v is the self-contained WebAssembly binary encoder used by the v3
4// wasm backend. It mirrors the role of the arm64 backend's asm.v + macho.v:
5// gen.v walks the flat AST and drives this module, which knows nothing about V
6// and only emits raw WASM bytecode (LEB128 + sections). No external module is
7// imported, so v3 stays self-contained and self-hostable.
8
9// WASM value types.
10pub const valtype_i32 = u8(0x7f)
11pub const valtype_i64 = u8(0x7e)
12pub const valtype_f32 = u8(0x7d)
13pub const valtype_f64 = u8(0x7c)
14
15// Export kinds.
16pub const export_func = u8(0x00)
17pub const export_mem = u8(0x02)
18
19// FuncType is a single `(params) -> (results)` signature.
20struct FuncType {
21 params []u8
22 results []u8
23}
24
25// ImportFunc is an imported function (only the WASI import is used for now).
26struct ImportFunc {
27 module string
28 name string
29 type_idx int
30}
31
32// Func is a locally defined function. `code` already ends with the `end` (0x0b)
33// opcode; `locals` lists the value type of each local declared beyond params.
34struct Func {
35 type_idx int
36 locals []u8
37 code []u8
38}
39
40struct Export {
41 name string
42 kind u8
43 index int
44}
45
46// DataSeg is an active data segment placed at a fixed linear-memory offset.
47struct DataSeg {
48 offset int
49 bytes []u8
50}
51
52// Global is a module-level mutable global variable.
53struct Global {
54 valtype u8
55 init []u8 // constant init expression, ending with `end` (0x0b)
56}
57
58@[heap]
59pub struct Module {
60mut:
61 types []FuncType
62 imports []ImportFunc
63 funcs []Func
64 exports []Export
65 datas []DataSeg
66 globals []Global
67 mem_min int = 2
68 n_import int
69}
70
71pub fn Module.new() &Module {
72 return &Module{}
73}
74
75// add_type registers a signature, deduplicating identical ones.
76pub fn (mut m Module) add_type(params []u8, results []u8) int {
77 for i, t in m.types {
78 if t.params == params && t.results == results {
79 return i
80 }
81 }
82 m.types << FuncType{
83 params: params.clone()
84 results: results.clone()
85 }
86 return m.types.len - 1
87}
88
89// add_import_func registers an imported function and returns its function index.
90// Imports must be added before any defined function so indices stay stable.
91pub fn (mut m Module) add_import_func(module_ string, name string, type_idx int) int {
92 m.imports << ImportFunc{
93 module: module_
94 name: name
95 type_idx: type_idx
96 }
97 m.n_import = m.imports.len
98 return m.imports.len - 1
99}
100
101// add_global appends a mutable global and returns its index.
102pub fn (mut m Module) add_global(valtype u8, init []u8) int {
103 m.globals << Global{
104 valtype: valtype
105 init: init.clone()
106 }
107 return m.globals.len - 1
108}
109
110// reserve_func_index returns the function index a later add_func will occupy.
111pub fn (m &Module) reserve_func_index(defined_so_far int) int {
112 return m.n_import + defined_so_far
113}
114
115// add_func appends a defined function and returns its global function index.
116pub fn (mut m Module) add_func(type_idx int, locals []u8, code []u8) int {
117 m.funcs << Func{
118 type_idx: type_idx
119 locals: locals.clone()
120 code: code.clone()
121 }
122 return m.n_import + m.funcs.len - 1
123}
124
125pub fn (mut m Module) add_export(name string, kind u8, index int) {
126 m.exports << Export{
127 name: name
128 kind: kind
129 index: index
130 }
131}
132
133pub fn (mut m Module) add_data(offset int, bytes []u8) {
134 m.datas << DataSeg{
135 offset: offset
136 bytes: bytes.clone()
137 }
138}
139
140pub fn (mut m Module) set_mem_min(pages int) {
141 m.mem_min = pages
142}
143
144// ---- LEB128 ----
145
146fn leb_u(mut out []u8, val_ u64) {
147 mut val := val_
148 for {
149 mut b := u8(val & 0x7f)
150 val >>= 7
151 if val != 0 {
152 b |= 0x80
153 }
154 out << b
155 if val == 0 {
156 break
157 }
158 }
159}
160
161fn leb_i(mut out []u8, val_ i64) {
162 mut val := val_
163 for {
164 mut b := u8(val & 0x7f)
165 val >>= 7
166 done := (val == 0 && (b & 0x40) == 0) || (val == -1 && (b & 0x40) != 0)
167 if !done {
168 b |= 0x80
169 }
170 out << b
171 if done {
172 break
173 }
174 }
175}
176
177fn write_name(mut out []u8, name string) {
178 leb_u(mut out, u64(name.len))
179 for c in name {
180 out << c
181 }
182}
183
184// section wraps a section body with its id byte and LEB-encoded length.
185fn section(mut out []u8, id u8, body []u8) {
186 if body.len == 0 {
187 return
188 }
189 out << id
190 leb_u(mut out, u64(body.len))
191 out << body
192}
193
194// compress_locals run-length-encodes the per-local value types into the wasm
195// locals vector: count-of-groups, then (count, valtype) groups.
196fn compress_locals(locals []u8) []u8 {
197 mut groups := [][]int{}
198 for vt in locals {
199 if groups.len > 0 && groups[groups.len - 1][1] == int(vt) {
200 groups[groups.len - 1][0]++
201 } else {
202 groups << [1, int(vt)]
203 }
204 }
205 mut out := []u8{}
206 leb_u(mut out, u64(groups.len))
207 for g in groups {
208 leb_u(mut out, u64(g[0]))
209 out << u8(g[1])
210 }
211 return out
212}
213
214// compile serializes the whole module into a `.wasm` byte buffer.
215pub fn (m &Module) compile() []u8 {
216 mut out := []u8{}
217 // magic + version
218 out << [u8(0x00), 0x61, 0x73, 0x6d]
219 out << [u8(0x01), 0x00, 0x00, 0x00]
220
221 // type section (1)
222 mut tsec := []u8{}
223 leb_u(mut tsec, u64(m.types.len))
224 for t in m.types {
225 tsec << 0x60
226 leb_u(mut tsec, u64(t.params.len))
227 tsec << t.params
228 leb_u(mut tsec, u64(t.results.len))
229 tsec << t.results
230 }
231 section(mut out, 0x01, tsec)
232
233 // import section (2)
234 if m.imports.len > 0 {
235 mut isec := []u8{}
236 leb_u(mut isec, u64(m.imports.len))
237 for imp in m.imports {
238 write_name(mut isec, imp.module)
239 write_name(mut isec, imp.name)
240 isec << 0x00 // import kind: func
241 leb_u(mut isec, u64(imp.type_idx))
242 }
243 section(mut out, 0x02, isec)
244 }
245
246 // function section (3)
247 mut fsec := []u8{}
248 leb_u(mut fsec, u64(m.funcs.len))
249 for f in m.funcs {
250 leb_u(mut fsec, u64(f.type_idx))
251 }
252 section(mut out, 0x03, fsec)
253
254 // memory section (5)
255 mut msec := []u8{}
256 leb_u(mut msec, 1) // one memory
257 msec << 0x00 // limits: min only
258 leb_u(mut msec, u64(m.mem_min))
259 section(mut out, 0x05, msec)
260
261 // global section (6)
262 if m.globals.len > 0 {
263 mut gsec := []u8{}
264 leb_u(mut gsec, u64(m.globals.len))
265 for gl in m.globals {
266 gsec << gl.valtype
267 gsec << 0x01 // mutable
268 gsec << gl.init
269 }
270 section(mut out, 0x06, gsec)
271 }
272
273 // export section (7)
274 mut esec := []u8{}
275 leb_u(mut esec, u64(m.exports.len))
276 for e in m.exports {
277 write_name(mut esec, e.name)
278 esec << e.kind
279 leb_u(mut esec, u64(e.index))
280 }
281 section(mut out, 0x07, esec)
282
283 // code section (10)
284 mut csec := []u8{}
285 leb_u(mut csec, u64(m.funcs.len))
286 for f in m.funcs {
287 mut body := compress_locals(f.locals)
288 body << f.code
289 leb_u(mut csec, u64(body.len))
290 csec << body
291 }
292 section(mut out, 0x0a, csec)
293
294 // data section (11)
295 if m.datas.len > 0 {
296 mut dsec := []u8{}
297 leb_u(mut dsec, u64(m.datas.len))
298 for d in m.datas {
299 dsec << 0x00 // active segment, memory 0
300 dsec << 0x41 // i32.const offset
301 leb_i(mut dsec, i64(d.offset))
302 dsec << 0x0b // end of offset expr
303 leb_u(mut dsec, u64(d.bytes.len))
304 dsec << d.bytes
305 }
306 section(mut out, 0x0b, dsec)
307 }
308
309 return out
310}
311