vxx2 / vlib / wasm / encoding.v
527 lines · 496 sloc · 12.08 KB · 863a2090f8eda7dc75e061b26e0bca252f167e24
Raw
1// Copyright (c) 2023 l-m.dev. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module wasm
5
6import encoding.leb128
7import math.bits
8
9fn (mut mod Module) u32(v u32) {
10 mod.buf << leb128.encode_u32(v)
11}
12
13fn (mut mod Module) patch_start() int {
14 return mod.buf.len
15}
16
17fn (mut mod Module) patch_len(pos int) {
18 len := mod.buf.len - pos
19 data := leb128.encode_u32(u32(len))
20 mod.buf.insert(pos, data)
21}
22
23fn (mut mod Module) patch_u32(pos int, val u32) {
24 data := leb128.encode_u32(val)
25 mod.buf.insert(pos, data)
26}
27
28fn (mut mod Module) result_type(results []ValType) {
29 mod.u32(u32(results.len))
30 for r in results {
31 mod.buf << u8(r)
32 }
33}
34
35fn (mut mod Module) function_type(ft FuncType) {
36 mod.buf << 0x60 // function type indicator
37 mod.result_type(ft.parameters)
38 mod.result_type(ft.results)
39}
40
41fn (mut mod Module) global_type(vt ValType, is_mut bool) {
42 mod.buf << u8(vt)
43 mod.buf << u8(is_mut)
44}
45
46fn push_f32(mut buf []u8, v f32) {
47 rv := bits.f32_bits(v)
48 buf << u8(rv >> u32(0))
49 buf << u8(rv >> u32(8))
50 buf << u8(rv >> u32(16))
51 buf << u8(rv >> u32(24))
52}
53
54fn push_f64(mut buf []u8, v f64) {
55 rv := bits.f64_bits(v)
56 buf << u8(rv >> u32(0))
57 buf << u8(rv >> u32(8))
58 buf << u8(rv >> u32(16))
59 buf << u8(rv >> u32(24))
60 buf << u8(rv >> u32(32))
61 buf << u8(rv >> u32(40))
62 buf << u8(rv >> u32(48))
63 buf << u8(rv >> u32(56))
64}
65
66fn (mod &Module) get_local_func_idx(name string) int {
67 ftt := mod.functions[name] or { panic('function ${name} does not exist') }
68 return ftt.idx + mod.fn_imports.len
69}
70
71fn (mod &Module) get_function_idx(patch CallPatch) int {
72 mut idx := -1
73
74 match patch {
75 FunctionCallPatch {
76 idx = mod.get_local_func_idx(patch.name)
77 }
78 ImportCallPatch {
79 for fnidx, c in mod.fn_imports {
80 if c.mod == patch.mod && c.name == patch.name {
81 idx = fnidx
82 break
83 }
84 }
85 if idx == -1 {
86 panic('called imported function ${patch.mod}.${patch.name} does not exist')
87 }
88 }
89 }
90
91 return idx
92}
93
94fn (mut mod Module) patch(ft Function) {
95 mut ptr := 0
96
97 for patch in ft.patches {
98 mut idx := 0
99 match patch {
100 CallPatch {
101 idx = mod.get_function_idx(patch)
102 }
103 FunctionGlobalPatch {
104 idx = mod.global_imports.len + patch.idx
105 }
106 }
107
108 mod.buf << ft.code[ptr..patch.pos]
109 mod.u32(u32(idx))
110 ptr = patch.pos
111 }
112
113 mod.buf << ft.code[ptr..]
114}
115
116fn (mut mod Module) name(name string) {
117 mod.u32(u32(name.len))
118 mod.buf << name.bytes()
119}
120
121fn (mut mod Module) start_subsection(sec Subsection) int {
122 mod.buf << u8(sec)
123 return mod.patch_start()
124}
125
126fn (mut mod Module) start_section(sec Section) int {
127 mod.buf << u8(sec)
128 return mod.patch_start()
129}
130
131fn (mut mod Module) end_section(tpatch int) {
132 mod.patch_len(tpatch)
133}
134
135// compile serialises the WebAssembly module into a byte array.
136// The returned byte array can be written out into a `.wasm`, or executed in memory.
137pub fn (mut mod Module) compile() []u8 {
138 mod.buf = []u8{cap: 128}
139
140 // WASM_BINARY_MAGIC, WASM_BINARY_VERSION
141 mod.buf << [u8(0x00), 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]
142
143 // https://webassembly.github.io/spec/core/binary/modules.html#type-section
144 //
145 if mod.functypes.len > 0 {
146 tpatch := mod.start_section(.type_section)
147 {
148 mod.u32(u32(mod.functypes.len))
149 for ft in mod.functypes {
150 mod.function_type(ft)
151 }
152 }
153 mod.end_section(tpatch)
154 }
155 // https://webassembly.github.io/spec/core/binary/modules.html#import-section
156 //
157 if mod.fn_imports.len > 0 || mod.global_imports.len > 0 {
158 tpatch := mod.start_section(.import_section)
159 {
160 mod.u32(u32(mod.fn_imports.len + mod.global_imports.len))
161 for ft in mod.fn_imports {
162 mod.name(ft.mod)
163 mod.name(ft.name)
164 mod.buf << 0x00 // function
165 mod.u32(u32(ft.tidx))
166 }
167 for gt in mod.global_imports {
168 mod.name(gt.mod)
169 mod.name(gt.name)
170 mod.buf << 0x03 // global
171 mod.global_type(gt.typ, gt.is_mut)
172 }
173 }
174 mod.end_section(tpatch)
175 }
176 // https://webassembly.github.io/spec/core/binary/modules.html#binary-funcsec
177 //
178 if mod.functions.len > 0 {
179 tpatch := mod.start_section(.function_section)
180 {
181 mod.u32(u32(mod.functions.len))
182 for _, ft in mod.functions {
183 mod.u32(u32(ft.tidx))
184 }
185 }
186 mod.end_section(tpatch)
187 }
188 // https://webassembly.github.io/spec/core/binary/modules.html#table-section
189 //
190 if mod.tables.len > 0 {
191 tpatch := mod.start_section(.table_section)
192 {
193 mod.u32(u32(mod.tables.len))
194 for tbl in mod.tables {
195 mod.buf << u8(tbl.reftype)
196 if max := tbl.max {
197 mod.buf << 0x01 // limit, max present
198 mod.u32(tbl.min)
199 mod.u32(max)
200 } else {
201 mod.buf << 0x00 // limit, max not present
202 mod.u32(tbl.min)
203 }
204 }
205 }
206 mod.end_section(tpatch)
207 }
208 // https://webassembly.github.io/spec/core/binary/modules.html#binary-memsec
209 //
210 if memory := mod.memory {
211 tpatch := mod.start_section(.memory_section)
212 {
213 mod.u32(1)
214 if max := memory.max {
215 mod.buf << 0x01 // limit, max present
216 mod.u32(memory.min)
217 mod.u32(max)
218 } else {
219 mod.buf << 0x00 // limit, max not present
220 mod.u32(memory.min)
221 }
222 }
223 mod.end_section(tpatch)
224 }
225 // https://webassembly.github.io/spec/core/binary/modules.html#global-section
226 //
227 if mod.globals.len > 0 {
228 tpatch := mod.start_section(.global_section)
229 {
230 mod.u32(u32(mod.globals.len))
231 for gt in mod.globals {
232 mod.global_type(gt.typ, gt.is_mut)
233
234 {
235 mut ptr := 0
236 for patch in gt.init.call_patches {
237 idx := mod.get_function_idx(patch)
238
239 mod.buf << gt.init.code[ptr..patch.pos]
240 mod.u32(u32(idx))
241 ptr = patch.pos
242 }
243 mod.buf << gt.init.code[ptr..]
244 }
245 mod.buf << 0x0B // END expression opcode
246 }
247 }
248 mod.end_section(tpatch)
249 }
250 // https://webassembly.github.io/spec/core/binary/modules.html#export-section
251 //
252 {
253 tpatch := mod.start_section(.export_section)
254 {
255 lpatch := mod.patch_start()
256 mut lsz := 0
257 for _, ft in mod.functions {
258 if !ft.export {
259 continue
260 }
261 lsz++
262 mod.name(ft.export_name or { ft.name })
263 mod.buf << 0x00 // function
264 mod.u32(u32(ft.idx + mod.fn_imports.len))
265 }
266 if memory := mod.memory {
267 if memory.export {
268 lsz++
269 mod.name(memory.name)
270 mod.buf << 0x02 // function
271 mod.u32(0)
272 }
273 }
274 for tblidx, tbl in mod.tables {
275 if !tbl.export {
276 continue
277 }
278 lsz++
279 mod.name(tbl.name)
280 mod.buf << 0x01 // table
281 mod.u32(u32(tblidx))
282 }
283 for idx, gbl in mod.globals {
284 if !gbl.export {
285 continue
286 }
287 lsz++
288 mod.name(gbl.name)
289 mod.buf << 0x03 // global
290 mod.u32(u32(idx + mod.global_imports.len))
291 }
292 mod.patch_u32(lpatch, u32(lsz))
293 }
294 mod.end_section(tpatch)
295 }
296 // https://webassembly.github.io/spec/core/binary/modules.html#binary-startsec
297 //
298 if start := mod.start {
299 ftt := mod.functions[start] or { panic('start function ${start} does not exist') }
300 tpatch := mod.start_section(.start_section)
301 {
302 mod.u32(u32(ftt.idx + mod.fn_imports.len))
303 }
304 mod.end_section(tpatch)
305 }
306 // https://webassembly.github.io/spec/core/binary/modules.html#element-section
307 //
308 if mod.elements.len > 0 {
309 tpatch := mod.start_section(.element_section)
310 {
311 mod.u32(u32(mod.elements.len))
312 for el in mod.elements {
313 match el.mode {
314 .active {
315 if el.tableidx == 0 {
316 mod.buf << 0x00 // active, table 0, vec(funcidx)
317 // offset constant expression
318 mod.buf << 0x41 // i32.const
319 mod.buf << leb128.encode_i32(i32(el.offset))
320 mod.buf << 0x0B // END expression opcode
321 } else {
322 mod.buf << 0x02 // active, explicit tableidx + elemkind
323 mod.u32(u32(el.tableidx))
324 mod.buf << 0x41 // i32.const
325 mod.buf << leb128.encode_i32(i32(el.offset))
326 mod.buf << 0x0B // END expression opcode
327 mod.buf << 0x00 // elemkind: funcref
328 }
329 }
330 .declarative {
331 mod.buf << 0x03 // declarative + elemkind
332 mod.buf << 0x00 // elemkind: funcref
333 }
334 .passive {
335 mod.buf << 0x01 // passive + elemkind
336 mod.buf << 0x00 // elemkind: funcref
337 }
338 }
339
340 // vec(funcidx)
341 mod.u32(u32(el.funcs.len))
342 for fnname in el.funcs {
343 mod.u32(u32(mod.get_local_func_idx(fnname)))
344 }
345 }
346 }
347 mod.end_section(tpatch)
348 }
349 // https://webassembly.github.io/spec/core/binary/modules.html#data-count-section
350 //
351 if mod.segments.len > 0 {
352 tpatch := mod.start_section(.data_count_section)
353 {
354 mod.u32(u32(mod.segments.len))
355 }
356 mod.end_section(tpatch)
357 }
358 // https://webassembly.github.io/spec/core/binary/modules.html#binary-codesec
359 //
360 if mod.functions.len > 0 {
361 tpatch := mod.start_section(.code_section)
362 {
363 mod.u32(u32(mod.functions.len))
364 for _, ft in mod.functions {
365 fpatch := mod.patch_start()
366 rloc := ft.locals[mod.functypes[ft.tidx].parameters.len..]
367 {
368 mod.u32(u32(rloc.len))
369 for lt in rloc {
370 mod.u32(1)
371 mod.buf << u8(lt.typ)
372 }
373 mod.patch(ft)
374 mod.buf << 0x0B // END expression opcode
375 }
376 mod.patch_len(fpatch)
377 }
378 }
379 mod.end_section(tpatch)
380 }
381 // https://webassembly.github.io/spec/core/binary/modules.html#data-section
382 //
383 if mod.segments.len > 0 {
384 tpatch := mod.start_section(.data_section)
385 {
386 mod.u32(u32(mod.segments.len))
387 for _, seg in mod.segments {
388 if idx := seg.idx {
389 mod.buf << 0x00 // active
390 // constant expr
391 mod.buf << 0x41 // i32.const
392 mod.buf << leb128.encode_i32(i32(idx))
393 mod.buf << 0x0B // END expression opcode
394 } else {
395 mod.buf << 0x01 // passive
396 }
397 mod.u32(u32(seg.data.len))
398 mod.buf << seg.data
399 }
400 }
401 mod.end_section(tpatch)
402 }
403 // https://webassembly.github.io/spec/core/appendix/custom.html#name-section
404 //
405 if mod.debug {
406 tpatch := mod.start_section(.custom_section)
407 mod.name('name')
408 if mod_name := mod.mod_name {
409 mpatch := mod.start_subsection(.name_module)
410 {
411 mod.name(mod_name)
412 }
413 mod.end_section(mpatch)
414 }
415 {
416 mpatch := mod.start_subsection(.name_function)
417 {
418 mod.u32(u32(mod.functions.len + mod.fn_imports.len))
419 mut idx := 0
420 for f in mod.fn_imports {
421 mod.u32(u32(idx))
422 mod.name('${f.mod}.${f.name}')
423 idx++
424 }
425 for n, _ in mod.functions {
426 mod.u32(u32(idx))
427 mod.name(n)
428 idx++
429 }
430 }
431 mod.end_section(mpatch)
432 }
433 {
434 mpatch := mod.start_subsection(.name_local)
435 {
436 fpatch := mod.patch_start()
437 mut fcount := 0
438 mut idx := mod.fn_imports.len // after imports
439 for _, ft in mod.functions {
440 // only add entry if it contains a local with an assigned name
441 if ft.locals.any(it.name != none) {
442 mod.u32(u32(idx)) // function idx
443
444 mut lcount := 0
445 lcpatch := mod.patch_start()
446 for lidx, loc in ft.locals {
447 if name := loc.name {
448 mod.u32(u32(lidx))
449 mod.name(name)
450 lcount++
451 }
452 }
453 mod.patch_u32(lcpatch, u32(lcount))
454 fcount++
455 }
456 idx++
457 }
458 mod.patch_u32(fpatch, u32(fcount))
459 }
460 mod.end_section(mpatch)
461 }
462 {
463 mpatch := mod.start_subsection(.name_type)
464 {
465 fpatch := mod.patch_start()
466 mut fcount := 0
467 for idx, ft in mod.functypes {
468 if name := ft.name {
469 mod.u32(u32(idx))
470 mod.name(name)
471 fcount++
472 }
473 }
474 mod.patch_u32(fpatch, u32(fcount))
475 }
476 mod.end_section(mpatch)
477 }
478 if memory := mod.memory {
479 mpatch := mod.start_subsection(.name_memory)
480 {
481 mod.u32(u32(1)) // one memory in vec
482 mod.u32(u32(0)) // 0 idx
483 mod.name(memory.name) // memory name
484 }
485 mod.end_section(mpatch)
486 }
487 if mod.globals.len != 0 || mod.global_imports.len != 0 {
488 mpatch := mod.start_subsection(.name_global)
489 {
490 fpatch := mod.patch_start()
491 mut fcount := 0
492 for gbl in mod.global_imports {
493 mod.u32(u32(fcount))
494 mod.name('${gbl.mod}.${gbl.name}')
495 fcount++
496 }
497 for gbl in mod.globals {
498 mod.u32(u32(fcount))
499 mod.name(gbl.name)
500 fcount++
501 }
502 mod.patch_u32(fpatch, u32(fcount))
503 }
504 mod.end_section(mpatch)
505 }
506 if mod.segments.any(it.name != none) {
507 mpatch := mod.start_subsection(.name_data)
508 {
509 fpatch := mod.patch_start()
510 mut fcount := 0
511 for idx, ds in mod.segments {
512 if name := ds.name {
513 mod.u32(u32(idx))
514 mod.name(name)
515 fcount++
516 }
517 }
518 mod.patch_u32(fpatch, u32(fcount))
519 }
520 mod.end_section(mpatch)
521 }
522
523 mod.end_section(tpatch)
524 }
525
526 return mod.buf
527}
528