v4 / vlib / v / gen / wasm / mem.v
957 lines · 848 sloc · 21.56 KB · f05b0450b1493d16b6518e7cee11b1b09b39beb4
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
6// Memory contract for the native WebAssembly backend
7// ---------------------------------------------------
8// `-b wasm` selects `-gc none` automatically (every non-C backend defaults to
9// no_gc in v.pref.default), and autofree is off by default. Crucially, autofree
10// code generation lives only in the C backend (v/gen/c/autofree.v); this backend
11// never sees frontend-inserted free/drop calls, so the AST it lowers is "natural".
12//
13// Today the only memory mechanism is the shadow stack (__vsp, grows down).
14// `g.func.drop()` calls in this backend are WebAssembly operand-stack drops, not
15// memory frees. There is no malloc/free/GC.
16//
17// This invariant is load-bearing: a future manual allocator (__v_alloc/__v_free)
18// and explicit dispose() will be the sole heap-management entry points, and must
19// not collide with frontend-inserted frees. The no-frontend-free property is
20// regression-guarded by tests_decompile/no_frontend_free_under_wasm.vv.
21import wasm
22import v.ast
23import v.gen.wasm.serialise
24import encoding.binary
25
26pub struct Var {
27 name string
28mut:
29 typ ast.Type
30 idx wasm.LocalIndex
31 is_address bool
32 is_global bool
33 g_idx wasm.GlobalIndex
34 offset int
35}
36
37pub fn (mut g Gen) get_var_from_ident(ident ast.Ident) Var {
38 mut obj := ident.obj
39 if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] {
40 obj = ident.scope.find(ident.name) or { g.w_error('unknown variable ${ident.name}') }
41 }
42
43 match mut obj {
44 ast.Var {
45 if g.local_vars.len == 0 {
46 g.w_error('get_var_from_ident: g.local_vars.len == 0')
47 }
48 mut c := g.local_vars.len
49 for {
50 c--
51 if g.local_vars[c].name == obj.name {
52 return g.local_vars[c]
53 }
54 if c == 0 {
55 break
56 }
57 }
58 g.w_error('get_var_from_ident: unreachable, variable not found')
59 }
60 ast.ConstField {
61 if gbl := g.global_vars[obj.name] {
62 return gbl.v
63 }
64 gbl := g.new_global(obj.name, obj.typ, obj.expr, false)
65 g.global_vars[obj.name] = gbl
66 return gbl.v
67 }
68 ast.GlobalField {
69 if gbl := g.global_vars[obj.name] {
70 return gbl.v
71 }
72 gbl := g.new_global(obj.name, obj.typ, obj.expr, true)
73 g.global_vars[obj.name] = gbl
74 return gbl.v
75 }
76 else {
77 g.w_error('unsupported variable type type:${obj} name:${ident.name}')
78 }
79 }
80}
81
82pub fn (mut g Gen) get_var_from_expr(node ast.Expr) ?Var {
83 match node {
84 ast.Ident {
85 return g.get_var_from_ident(node)
86 }
87 ast.ParExpr {
88 return g.get_var_from_expr(node.expr)
89 }
90 ast.CastExpr {
91 // For cast expressions like &u32(), we need to look through
92 // the cast to get the underlying variable because WASM don't have
93 // really pointers like in C
94 return g.get_var_from_expr(node.expr)
95 }
96 ast.SelectorExpr {
97 mut addr := g.get_var_from_expr(node.expr) or {
98 // if place {
99 // g.field_offset(node.expr_type, node.field_name)
100 // }
101 return none
102 }
103 addr.is_address = true
104
105 offset := g.get_field_offset(node.expr_type, node.field_name)
106 return g.offset(addr, node.typ, offset)
107 }
108 else {
109 // g.w_error('get_var_from_expr: unexpected `${node.type_name()}`')
110 return none
111 }
112 }
113}
114
115// ONLY call this with the LHS of an assign, an lvalue
116// use get_var_from_expr in all other cases
117pub fn (mut g Gen) get_var_or_make_from_expr(node ast.Expr, typ ast.Type) Var {
118 if v := g.get_var_from_expr(node) {
119 return v
120 }
121
122 mut v := g.new_local('__tmp', ast.voidptr_type)
123 g.needs_address = true
124 {
125 g.set_with_expr(node, v)
126 }
127 g.needs_address = false
128
129 v.typ = typ
130 v.is_address = true
131
132 return v
133}
134
135pub fn (mut g Gen) bp() wasm.LocalIndex {
136 if g.bp_idx == -1 {
137 g.bp_idx = g.func.new_local_named(.i32_t, '__vbp')
138 }
139 return g.bp_idx
140}
141
142pub fn (mut g Gen) sp() wasm.GlobalIndex {
143 if sp := g.sp_global {
144 return sp
145 }
146 // (64KiB - 1KiB) of stack space, grows downwards.
147 // Memory addresses from 0 to 1024 are forbidden.
148 g.sp_global = g.mod.new_global('__vsp', false, .i32_t, true, wasm.constexpr_value(0))
149 return g.sp()
150}
151
152pub fn (mut g Gen) hp() wasm.GlobalIndex {
153 if hp := g.heap_base {
154 return hp
155 }
156 hp := g.mod.new_global('__heap_base', false, .i32_t, false, wasm.constexpr_value(0))
157 g.heap_base = hp
158 return hp
159}
160
161pub fn (mut g Gen) new_local(name string, typ_ ast.Type) Var {
162 mut typ := typ_
163 ts := g.table.sym(typ)
164
165 match ts.info {
166 ast.Enum {
167 typ = ts.info.typ
168 }
169 ast.Alias {
170 typ = ts.info.parent_type
171 }
172 else {}
173 }
174
175 is_address := !g.is_pure_type(typ)
176 wtyp := g.get_wasm_type(typ)
177
178 mut v := Var{
179 name: name
180 typ: typ
181 is_address: is_address
182 }
183
184 if !is_address {
185 v.idx = g.func.new_local_named(wtyp, g.dbg_type_name(name, typ_))
186 g.local_vars << v
187 return v
188 }
189 v.idx = g.bp()
190
191 // allocate memory, then assign an offset
192 //
193 match ts.info {
194 ast.Struct, ast.ArrayFixed {
195 size, align := g.pool.type_size(typ)
196 padding := calc_padding(g.stack_frame, align)
197 address := g.stack_frame
198 g.stack_frame += size + padding
199
200 v.offset = address
201 }
202 else {
203 g.w_error('new_local: type `${*ts}` (${ts.info.type_name()}) is not a supported local type')
204 }
205 }
206
207 g.local_vars << v
208 return v
209}
210
211pub fn (mut g Gen) literal_to_constant_expression(typ_ ast.Type, init ast.Expr) ?wasm.ConstExpression {
212 typ := ast.mktyp(typ_)
213 match init {
214 ast.BoolLiteral {
215 return wasm.constexpr_value(int(init.val))
216 }
217 ast.CharLiteral {
218 return wasm.constexpr_value(int(init.val.runes()[0]))
219 }
220 ast.FloatLiteral {
221 if typ == ast.f32_type {
222 return wasm.constexpr_value(init.val.f32())
223 } else if typ == ast.f64_type {
224 return wasm.constexpr_value(init.val.f64())
225 }
226 }
227 ast.IntegerLiteral {
228 if !typ.is_pure_int() {
229 return none
230 }
231 t := g.get_wasm_type(typ)
232 match t {
233 .i32_t { return wasm.constexpr_value(init.val.int()) }
234 .i64_t { return wasm.constexpr_value(init.val.i64()) }
235 else {}
236 }
237 }
238 ast.Ident {
239 mut obj := init.obj
240 if obj !in [ast.ConstField, ast.GlobalField] {
241 obj = init.scope.find(init.name) or { return none }
242 }
243 match mut obj {
244 ast.ConstField {
245 return g.literal_to_constant_expression(typ, obj.expr)
246 }
247 ast.GlobalField {
248 return g.literal_to_constant_expression(typ, obj.expr)
249 }
250 else {
251 return none
252 }
253 }
254 }
255 else {}
256 }
257
258 return none
259}
260
261pub fn (mut g Gen) new_global(name string, typ_ ast.Type, init ast.Expr, is_global_mut bool) Global {
262 mut typ := ast.mktyp(typ_)
263 ts := g.table.sym(typ)
264
265 match ts.info {
266 ast.Enum {
267 typ = ts.info.typ
268 }
269 ast.Alias {
270 typ = ts.info.parent_type
271 }
272 else {}
273 }
274
275 mut is_mut := false
276 is_address := !g.is_pure_type(typ)
277 mut init_expr := ?ast.Expr(none)
278
279 cexpr := if cexpr_v := g.literal_to_constant_expression(unpack_literal_int(typ), init) {
280 is_mut = is_global_mut
281 cexpr_v
282 } else {
283 // Isn't a literal ...
284 if is_address {
285 // ... allocate memory and append
286 pos, is_init := g.pool.append(init, typ)
287 if !is_init {
288 // ... AND wait for init in `_vinit`
289 init_expr = init
290 }
291 wasm.constexpr_value(g.data_base + pos)
292 } else {
293 // ... wait for init in `_vinit`
294 init_expr = init
295 is_mut = true
296
297 t := g.get_wasm_type(typ)
298 wasm.constexpr_value_zero(t)
299 }
300 }
301
302 mut glbl := Global{
303 init: init_expr
304 v: Var{
305 name: name
306 typ: typ
307 is_address: is_address
308 is_global: true
309 g_idx: g.mod.new_global(g.dbg_type_name(name, typ), false,
310 g.get_wasm_type_int_literal(typ), is_mut, cexpr)
311 }
312 }
313
314 return glbl
315}
316
317// is_pure_type(voidptr) == true
318// is_pure_type(&Struct) == false
319pub fn (g &Gen) is_pure_type(typ ast.Type) bool {
320 if typ.is_pure_int() || typ.is_pure_float() || typ == ast.char_type_idx
321 || typ.is_any_kind_of_pointer() || typ.is_bool() {
322 return true
323 }
324 ts := g.table.sym(typ)
325 match ts.info {
326 ast.Alias {
327 return g.is_pure_type(ts.info.parent_type)
328 }
329 ast.Enum {
330 return g.is_pure_type(ts.info.typ)
331 }
332 ast.FnType {
333 // a function value is an i32 index into the indirect function table
334 return true
335 }
336 else {}
337 }
338
339 return false
340}
341
342pub fn log2(size int) int {
343 return match size {
344 1 { 0 }
345 2 { 1 }
346 4 { 2 }
347 8 { 3 }
348 else { panic('unreachable') }
349 }
350}
351
352pub fn (mut g Gen) load(typ ast.Type, offset int) {
353 size, align := g.pool.type_size(typ)
354 wtyp := g.as_numtype(g.get_wasm_type(typ))
355
356 match size {
357 1 { g.func.load8(wtyp, typ.is_signed(), log2(align), offset) }
358 2 { g.func.load16(wtyp, typ.is_signed(), log2(align), offset) }
359 else { g.func.load(wtyp, log2(align), offset) }
360 }
361}
362
363pub fn (mut g Gen) store(typ ast.Type, offset int) {
364 size, align := g.pool.type_size(typ)
365 wtyp := g.as_numtype(g.get_wasm_type(typ))
366
367 match size {
368 1 { g.func.store8(wtyp, log2(align), offset) }
369 2 { g.func.store16(wtyp, log2(align), offset) }
370 else { g.func.store(wtyp, log2(align), offset) }
371 }
372}
373
374pub fn (mut g Gen) get(v Var) {
375 if v.is_global {
376 g.func.global_get(v.g_idx)
377 } else {
378 g.func.local_get(v.idx)
379 }
380
381 if v.is_address && g.is_pure_type(v.typ) {
382 g.load(v.typ, v.offset)
383 } else if v.is_address && v.offset != 0 {
384 g.func.i32_const(i32(v.offset))
385 g.func.add(.i32_t)
386 }
387}
388
389pub fn (mut g Gen) mov(to Var, v Var) {
390 if !v.is_address || g.is_pure_type(v.typ) {
391 g.get(v)
392 g.cast(v.typ, to.typ)
393 g.set(to)
394 return
395 }
396
397 size, _ := g.pool.type_size(v.typ)
398
399 if size > 16 {
400 g.ref(to)
401 g.ref(v)
402 g.func.i32_const(i32(size))
403 g.func.memory_copy()
404 return
405 }
406
407 mut sz := size
408 mut oz := 0
409 for sz > 0 {
410 g.ref_ignore_offset(to)
411 g.ref_ignore_offset(v)
412 if sz - 8 >= 0 {
413 g.load(ast.u64_type_idx, v.offset + oz)
414 g.store(ast.u64_type_idx, to.offset + oz)
415 sz -= 8
416 oz += 8
417 } else if sz - 4 >= 0 {
418 g.load(ast.u32_type_idx, v.offset + oz)
419 g.store(ast.u32_type_idx, to.offset + oz)
420 sz -= 4
421 oz += 4
422 } else if sz - 2 >= 0 {
423 g.load(ast.u16_type_idx, v.offset + oz)
424 g.store(ast.u16_type_idx, to.offset + oz)
425 sz -= 2
426 oz += 2
427 } else if sz - 1 >= 0 {
428 g.load(ast.u8_type_idx, v.offset + oz)
429 g.store(ast.u8_type_idx, to.offset + oz)
430 sz -= 1
431 oz += 1
432 }
433 }
434}
435
436pub fn (mut g Gen) set_prepare(v Var) {
437 if !v.is_address {
438 return
439 }
440
441 if g.is_pure_type(v.typ) {
442 if v.is_global {
443 g.func.global_get(v.g_idx)
444 } else {
445 g.func.local_get(v.idx)
446 }
447 return
448 }
449}
450
451pub fn (mut g Gen) set_set(v Var) {
452 if !v.is_address {
453 if v.is_global {
454 g.func.global_set(v.g_idx)
455 } else {
456 g.func.local_set(v.idx)
457 }
458 return
459 }
460
461 if g.is_pure_type(v.typ) {
462 g.store(v.typ, v.offset)
463 return
464 }
465
466 from := Var{
467 typ: v.typ
468 idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>')
469 is_address: v.is_address
470 }
471
472 g.func.local_set(from.idx)
473 g.mov(v, from)
474}
475
476// set structures with pointer, memcpy.
477// set pointers with value, get local, store value.
478// set value, set local.
479// -- set works with a single value present on the stack beforehand
480// -- not optimal for copying stack memory or shuffling structs
481// -- use mov instead
482pub fn (mut g Gen) set(v Var) {
483 if !v.is_address {
484 if v.is_global {
485 g.func.global_set(v.g_idx)
486 } else {
487 g.func.local_set(v.idx)
488 }
489 return
490 }
491
492 if g.is_pure_type(v.typ) {
493 l := g.new_local('__tmp', v.typ)
494 g.func.local_set(l.idx)
495
496 if v.is_global {
497 g.func.global_get(v.g_idx)
498 } else {
499 g.func.local_get(v.idx)
500 }
501
502 g.func.local_get(l.idx)
503
504 g.store(v.typ, v.offset)
505 return
506 }
507
508 from := Var{
509 typ: v.typ
510 idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>')
511 is_address: v.is_address
512 }
513
514 g.func.local_set(from.idx)
515 g.mov(v, from)
516}
517
518// to satisfy inline assembly needs
519// never used by actual codegen
520pub fn (mut g Gen) tee(v Var) {
521 assert !v.is_global
522
523 if !v.is_address {
524 g.func.local_tee(v.idx)
525 return
526 }
527
528 if g.is_pure_type(v.typ) {
529 l := g.new_local('__tmp', v.typ)
530 g.func.local_tee(l.idx) // tee here, leave on stack
531
532 g.func.local_get(v.idx)
533 g.func.local_get(l.idx)
534 g.store(v.typ, v.offset)
535 return
536 }
537
538 from := Var{
539 typ: v.typ
540 idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>')
541 is_address: v.is_address
542 }
543
544 g.func.local_tee(from.idx) // tee here, leave on stack
545 g.mov(v, from)
546}
547
548pub fn (mut g Gen) ref(v Var) {
549 g.ref_ignore_offset(v)
550
551 if v.offset != 0 {
552 g.func.i32_const(i32(v.offset))
553 g.func.add(.i32_t)
554 }
555}
556
557pub fn (mut g Gen) ref_ignore_offset(v Var) {
558 if !v.is_address {
559 panic('unreachable')
560 }
561
562 if v.is_global {
563 g.func.global_get(v.g_idx)
564 } else {
565 g.func.local_get(v.idx)
566 }
567}
568
569// creates a new pointer variable with the offset `offset` and type `typ`
570pub fn (mut g Gen) offset(v Var, typ ast.Type, offset int) Var {
571 if !v.is_address {
572 panic('unreachable')
573 }
574
575 nv := Var{
576 ...v
577 typ: typ
578 offset: v.offset + offset
579 }
580
581 return nv
582}
583
584pub fn (mut g Gen) zero_fill(v Var, size int) {
585 assert size > 0
586
587 // TODO: support coalescing `zero_fill` calls together.
588 // maybe with some kind of context?
589 //
590 // ```v
591 // struct AA {
592 // a bool
593 // b int = 20
594 // c int
595 // d int
596 // }
597 // ```
598 //
599 // ```wast
600 // (i32.store8
601 // (local.get $0)
602 // (i32.const 0)
603 // )
604 // (i32.store offset=4
605 // (i32.const 20)
606 // (local.get $0)
607 // ) ;; /- join these together.
608 // (i32.store offset=8 ;;-\
609 // (local.get $0) ;; |
610 // (i32.const 0) ;; |
611 // ) ;; |
612 // (i32.store offset=12 ;; |
613 // (local.get $0) ;; |
614 // (i32.const 0) ;; |
615 // ) ;;-/
616 // ```
617
618 if size > 16 {
619 g.ref(v)
620 g.func.i32_const(0)
621 g.func.i32_const(i32(size))
622 g.func.memory_fill()
623 return
624 }
625
626 mut sz := size
627 mut oz := 0
628 for sz > 0 {
629 g.ref_ignore_offset(v)
630 if sz - 8 >= 0 {
631 g.func.i64_const(0)
632 g.store(ast.u64_type_idx, v.offset + oz)
633 sz -= 8
634 oz += 8
635 } else if sz - 4 >= 0 {
636 g.func.i32_const(0)
637 g.store(ast.u32_type_idx, v.offset + oz)
638 sz -= 4
639 oz += 4
640 } else if sz - 2 >= 0 {
641 g.func.i32_const(0)
642 g.store(ast.u16_type_idx, v.offset + oz)
643 sz -= 2
644 oz += 2
645 } else if sz - 1 >= 0 {
646 g.func.i32_const(0)
647 g.store(ast.u8_type_idx, v.offset + oz)
648 sz -= 1
649 oz += 1
650 }
651 }
652}
653
654pub fn (g &Gen) is_param(v Var) bool {
655 if v.is_global {
656 return false
657 }
658
659 return v.idx < g.fn_local_idx_end
660}
661
662pub fn (mut g Gen) set_with_multi_expr(init ast.Expr, expected ast.Type, existing_rvars []Var) {
663 // misleading name: this doesn't really perform similar to `set_with_expr`
664 match init {
665 ast.ConcatExpr {
666 mut r := 0
667 types := g.unpack_type(expected)
668 for idx, expr in init.vals {
669 typ := types[idx]
670 if g.is_param_type(typ) {
671 if rhs := g.get_var_from_expr(expr) {
672 g.mov(existing_rvars[r], rhs)
673 } else {
674 g.set_with_expr(expr, existing_rvars[r])
675 }
676 r++
677 } else {
678 g.expr(expr, typ)
679 }
680 }
681 }
682 ast.IfExpr {
683 g.if_expr(init, expected, existing_rvars)
684 }
685 ast.MatchExpr {
686 g.match_expr(init, expected, existing_rvars)
687 }
688 ast.CallExpr {
689 g.call_expr(init, expected, existing_rvars)
690 }
691 else {
692 if existing_rvars.len > 1 {
693 g.w_error('wasm.set_with_multi_expr(): (existing_rvars.len > 1) node: ' +
694 init.type_name())
695 }
696
697 if existing_rvars.len == 1 && g.is_param_type(expected) {
698 if rhs := g.get_var_from_expr(init) {
699 g.mov(existing_rvars[0], rhs)
700 } else {
701 g.set_with_expr(init, existing_rvars[0])
702 }
703 } else {
704 g.expr(init, expected)
705 }
706 }
707 }
708}
709
710pub fn (mut g Gen) set_with_expr(init ast.Expr, v Var) {
711 match init {
712 ast.StructInit {
713 size, _ := g.pool.type_size(v.typ)
714 ts := g.table.sym(v.typ)
715 ts_info := ts.info as ast.Struct
716 si := g.pool.type_struct_info(v.typ) or { panic('unreachable') }
717
718 if init.init_fields.len == 0 && !(ts_info.fields.any(it.has_default_expr)) {
719 // Struct definition contains no default initialisers
720 // AND struct init contains no set values.
721 g.zero_fill(v, size)
722 return
723 }
724
725 for i, f in ts_info.fields {
726 field_to_be_set := init.init_fields.map(it.name).contains(f.name)
727
728 if !field_to_be_set {
729 offset := si.offsets[i]
730 offset_var := g.offset(v, f.typ, offset)
731
732 fsize, _ := g.pool.type_size(f.typ)
733
734 if f.has_default_expr {
735 g.set_with_expr(f.default_expr, offset_var)
736 } else {
737 g.zero_fill(offset_var, fsize)
738 }
739 }
740 }
741
742 for f in init.init_fields {
743 field := ts.find_field(f.name) or {
744 g.w_error('could not find field `${f.name}` on init')
745 }
746
747 offset := si.offsets[field.i]
748 offset_var := g.offset(v, f.expected_type, offset)
749
750 g.set_with_expr(f.expr, offset_var)
751 }
752 }
753 ast.StringLiteral {
754 val := serialise.eval_escape_codes(init) or { panic('unreachable') }
755 str_pos := g.pool.append_string(val)
756
757 if v.typ != ast.string_type {
758 // c'str'
759 g.set_prepare(v)
760 {
761 g.literalint(g.data_base + str_pos, ast.voidptr_type)
762 }
763 g.set_set(v)
764 return
765 }
766
767 // init struct
768 // fields: str, len
769 g.ref(v)
770 g.literalint(g.data_base + str_pos, ast.voidptr_type)
771 g.store_field(ast.string_type, ast.voidptr_type, 'str')
772 g.ref(v)
773 g.literalint(val.len, ast.int_type)
774 g.store_field(ast.string_type, ast.int_type, 'len')
775 }
776 ast.CallExpr {
777 // `set_with_expr` is never called with a multireturn call expression
778 is_pt := g.is_param_type(v.typ)
779
780 g.call_expr(init, v.typ, if is_pt { [v] } else { []Var{} })
781 if !is_pt {
782 g.set(v)
783 }
784 }
785 ast.ArrayInit {
786 if !init.is_fixed {
787 g.v_error('wasm backend does not support non fixed arrays yet', init.pos)
788 }
789
790 elm_typ := init.elem_type
791 elm_size, _ := g.pool.type_size(elm_typ)
792
793 if !init.has_val {
794 arr_size, _ := g.pool.type_size(v.typ)
795
796 g.zero_fill(v, arr_size)
797 return
798 }
799
800 mut voff := g.offset(v, elm_typ, 0) // index zero
801
802 for e in init.exprs {
803 g.set_with_expr(e, voff)
804 voff = g.offset(voff, elm_typ, elm_size)
805 }
806 }
807 else {
808 // impl of set but taken out
809
810 if !v.is_address {
811 g.expr(init, v.typ)
812 if v.is_global {
813 g.func.global_set(v.g_idx)
814 } else {
815 g.func.local_set(v.idx)
816 }
817 return
818 }
819
820 if g.is_pure_type(v.typ) {
821 if v.is_global {
822 g.func.global_get(v.g_idx)
823 } else {
824 g.func.local_get(v.idx)
825 }
826 g.expr(init, v.typ)
827 g.store(v.typ, v.offset)
828 return
829 }
830
831 if var := g.get_var_from_expr(init) {
832 g.mov(v, var)
833 return
834 }
835
836 from := Var{
837 typ: v.typ
838 idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>')
839 is_address: v.is_address // true
840 }
841
842 g.expr(init, v.typ)
843 g.func.local_set(from.idx)
844 g.mov(v, from)
845 }
846 }
847}
848
849pub fn calc_padding(value int, alignment int) int {
850 if alignment == 0 {
851 return value
852 }
853 return (alignment - value % alignment) % alignment
854}
855
856pub fn calc_align(value int, alignment int) int {
857 if alignment == 0 {
858 return value
859 }
860 return (value + alignment - 1) / alignment * alignment
861}
862
863pub fn (mut g Gen) make_vinit() {
864 g.func = g.mod.new_function('_vinit', [], [])
865 func_start := g.func.patch_pos()
866 {
867 for mod_name in g.table.modules {
868 if mod_name == 'v.reflection' {
869 g.w_error('the wasm backend does not implement `v.reflection` yet')
870 }
871 init_fn_name := if mod_name != 'builtin' { '${mod_name}.init' } else { 'init' }
872 if _ := g.table.find_fn(init_fn_name) {
873 g.func.call(init_fn_name)
874 }
875 cleanup_fn_name := if mod_name != 'builtin' { '${mod_name}.cleanup' } else { 'cleanup' }
876 if _ := g.table.find_fn(cleanup_fn_name) {
877 g.func.call(cleanup_fn_name)
878 }
879 }
880 for _, gv in g.global_vars {
881 if init := gv.init {
882 g.set_with_expr(init, gv.v)
883 }
884 }
885 g.bare_function_frame(func_start)
886 }
887 g.mod.commit(g.func, false)
888 g.bare_function_end()
889}
890
891pub fn (mut g Gen) housekeeping() {
892 g.make_vinit()
893
894 // Compile any pending non-capturing anonymous functions (their bodies may
895 // reference further anon fns, so loop until the queue drains), then declare
896 // and populate the indirect function table used by `call_indirect`.
897 for g.pending_anon_fns.len > 0 {
898 g.fn_decl(g.pending_anon_fns.pop())
899 }
900 // Declare the indirect function table whenever a `call_indirect` was emitted,
901 // even if no function value was registered (e.g. a module that only consumes a
902 // callback), otherwise the emitted `call_indirect` references a table that does
903 // not exist and the module fails to validate.
904 if g.uses_call_indirect || g.fn_value_indices.len > 0 {
905 // names[i] holds the function for table slot `i + 1`; slot 0 is the
906 // reserved null/trap slot (left as `ref.null func`).
907 mut names := []string{len: g.fn_value_indices.len}
908 for name, idx in g.fn_value_indices {
909 names[idx - 1] = name
910 }
911 // +1 for the reserved null slot at index 0
912 t :=
913 g.mod.assign_table('__indirect_function_table', false, .funcref_t, u32(names.len + 1), none)
914 if names.len > 0 {
915 g.mod.new_active_element(t, 1, names)
916 }
917 }
918
919 heap_base := calc_align(g.data_base + g.pool.buf.len, 16) // 16?
920 page_boundary := calc_align(g.data_base + g.pool.buf.len, 64 * 1024)
921 preallocated_pages := page_boundary / (64 * 1024)
922
923 if g.pref.is_verbose {
924 eprintln('housekeeping(): acceptable addresses are > 1024')
925 eprintln('housekeeping(): stack top: ${g.stack_top}, data_base: ${g.data_base} (size: ${g.pool.buf.len}), heap_base: ${heap_base}')
926 eprintln('housekeeping(): preallocated pages: ${preallocated_pages}')
927 }
928
929 if sp := g.sp_global {
930 g.mod.assign_global_init(sp, wasm.constexpr_value(g.stack_top))
931 }
932 if g.sp_global != none || g.pool.buf.len > 0 {
933 g.mod.assign_memory('memory', true, u32(preallocated_pages), none)
934 if g.pool.buf.len > 0 {
935 mut buf := g.pool.buf.clone()
936
937 for reloc in g.pool.relocs {
938 binary.little_endian_put_u32_at(mut buf, u32(g.data_base + reloc.offset), reloc.pos)
939 }
940 g.mod.new_data_segment(none, g.data_base, buf)
941 }
942 }
943 if hp := g.heap_base {
944 g.mod.assign_global_init(hp, wasm.constexpr_value(heap_base))
945 }
946
947 if g.pref.os == .wasi && !g.pref.is_shared {
948 mut fn_start := g.mod.new_function('_start', [], [])
949 {
950 fn_start.call('_vinit')
951 fn_start.call('main.main')
952 }
953 g.mod.commit(fn_start, true)
954 } else {
955 g.mod.assign_start('_vinit')
956 }
957}
958