v4 / vlib / v / gen / wasm / gen.v
2121 lines · 1907 sloc · 54.17 KB · 5bc4b1a14c0acac3567faeb1317e4840ea179c36
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 v.ast
7import v.pref
8import v.util
9import v.token
10import v.errors
11import v.gen.wasm.serialise
12import wasm
13import os
14
15@[heap; minify]
16pub struct Gen {
17 out_name string
18 pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct
19 files []&ast.File
20mut:
21 file_path string // current ast.File path
22 warnings []errors.Warning
23 errors []errors.Error
24 table &ast.Table = unsafe { nil }
25 enum_vals map[string]Enum
26
27 mod wasm.Module
28 pool serialise.Pool
29 func wasm.Function
30 local_vars []Var
31 global_vars map[string]Global
32 ret_rvars []Var
33 ret ast.Type
34 ret_types []ast.Type
35 ret_br wasm.LabelIndex
36 bp_idx wasm.LocalIndex = -1 // Base pointer temporary's index for function, if needed (-1 for none)
37 sp_global ?wasm.GlobalIndex
38 heap_base ?wasm.GlobalIndex
39 fn_local_idx_end int
40 fn_name string
41 stack_frame int // Size of the current stack frame, if needed
42 is_leaf_function bool = true
43 loop_breakpoint_stack []LoopBreakpoint
44 stack_top int // position in linear memory
45 data_base int // position in linear memory
46 needs_address bool
47 defer_vars []Var
48 is_direct_array_access bool // inside a `[direct_array_access]` function
49 // function values: fn name -> slot in the `__indirect_function_table`.
50 // Single owner of the indirect-call table population (reused by later phases).
51 fn_value_indices map[string]int
52 pending_anon_fns []ast.FnDecl // non-capturing anon fns to compile after toplevel stmts
53 compiled_anon_fns map[string]bool // dedup for pending_anon_fns
54 uses_call_indirect bool // a `call_indirect` was emitted, so the table must exist
55}
56
57// fn_table_index returns the slot of `name` in the indirect function table,
58// registering it (and growing the table) on first use.
59pub fn (mut g Gen) fn_table_index(name string) int {
60 if idx := g.fn_value_indices[name] {
61 return idx
62 }
63 // Slot 0 is reserved as the null/trap slot, so a real function value never
64 // collides with `unsafe { nil }` (which also lowers to `i32.const 0`). Real
65 // functions therefore start at index 1.
66 idx := g.fn_value_indices.len + 1
67 g.fn_value_indices[name] = idx
68 return idx
69}
70
71struct Global {
72mut:
73 init ?ast.Expr
74 v Var
75}
76
77pub struct LoopBreakpoint {
78 c_continue wasm.LabelIndex
79 c_break wasm.LabelIndex
80 name string
81}
82
83@[noreturn]
84pub fn (mut g Gen) v_error(s string, pos token.Pos) {
85 util.show_compiler_message('error:', pos: pos, file_path: g.file_path, message: s)
86 exit(1)
87 /*
88 if g.pref.output_mode == .stdout {
89 util.show_compiler_message('error:', pos: pos, file_path: g.file_path, message: s)
90 exit(1)
91 } else {
92 g.errors << errors.Error{
93 file_path: g.file_path
94 pos: pos
95 reporter: .gen
96 message: s
97 }
98 }
99 */
100}
101
102pub fn (mut g Gen) warning(s string, pos token.Pos) {
103 if g.pref.output_mode == .stdout {
104 util.show_compiler_message('warning:', pos: pos, file_path: g.file_path, message: s)
105 } else {
106 g.warnings << errors.Warning{
107 file_path: g.file_path
108 pos: pos
109 reporter: .gen
110 message: s
111 }
112 }
113}
114
115@[noreturn]
116pub fn (mut g Gen) w_error(s string) {
117 if g.pref.is_verbose {
118 print_backtrace()
119 }
120 util.verror('wasm error', s)
121}
122
123pub fn (g &Gen) unpack_type(typ ast.Type) []ast.Type {
124 ts := g.table.sym(typ)
125 return match ts.info {
126 ast.MultiReturn {
127 ts.info.types
128 }
129 else {
130 [typ]
131 }
132 }
133}
134
135pub fn (g &Gen) is_param_type(typ ast.Type) bool {
136 return !typ.is_ptr() && !g.is_pure_type(typ)
137}
138
139pub fn (mut g Gen) dbg_type_name(name string, typ ast.Type) string {
140 return '${name}<${`&`.repeat(typ.nr_muls())}${*g.table.sym(typ)}>'
141}
142
143pub fn unpack_literal_int(typ ast.Type) ast.Type {
144 return if typ == ast.int_literal_type { ast.i64_type } else { typ }
145}
146
147pub fn (g &Gen) get_ns_plus_name(default_name string, attrs []ast.Attr) (string, string) {
148 mut name := default_name
149 mut namespace := 'env'
150
151 if cattr := attrs.find_first('wasm_import_namespace') {
152 namespace = cattr.arg
153 }
154 if cattr := attrs.find_first('wasm_import_name') {
155 name = cattr.arg
156 }
157
158 return namespace, name
159}
160
161pub fn (mut g Gen) fn_external_import(node ast.FnDecl) {
162 if !node.no_body || node.is_method {
163 g.v_error('interop functions cannot have bodies', node.body_pos)
164 }
165 if node.language == .js && g.pref.os == .wasi {
166 g.v_error('javascript interop functions are not allowed in a `wasi` build', node.pos)
167 }
168 if node.return_type.has_option_or_result() {
169 g.v_error('interop functions must not return option or result', node.pos)
170 }
171
172 mut paraml := []wasm.ValType{cap: node.params.len}
173 mut retl := []wasm.ValType{cap: 1}
174 for arg in node.params {
175 if !g.is_pure_type(arg.typ) {
176 g.v_error('interop functions do not support complex arguments', arg.type_pos)
177 }
178 paraml << g.get_wasm_type(arg.typ)
179 }
180
181 is_ret := node.return_type != ast.void_type
182
183 if is_ret && !g.is_pure_type(node.return_type) {
184 g.v_error('interop functions do not support complex returns', node.return_type_pos)
185 }
186 if is_ret {
187 retl << g.get_wasm_type(node.return_type)
188 }
189
190 namespace, name := g.get_ns_plus_name(node.short_name, node.attrs)
191 g.mod.new_function_import(namespace, name, paraml, retl)
192}
193
194pub fn (mut g Gen) fn_decl(node ast.FnDecl) {
195 if node.language in [.js, .wasm] {
196 g.fn_external_import(node)
197 return
198 }
199
200 if node.attrs.contains('flag_enum_fn') {
201 // TODO: remove, when support for fn results is done
202 return
203 }
204
205 name := if node.is_method {
206 '${g.table.get_type_name(node.receiver.typ)}.${node.name}'
207 } else {
208 node.name
209 }
210
211 util.timing_start('${@METHOD}: ${name}')
212 defer {
213 util.timing_measure('${@METHOD}: ${name}')
214 }
215
216 if node.no_body {
217 return
218 }
219 if g.pref.is_verbose {
220 // println(term.green('\n${name}:'))
221 }
222 if node.is_deprecated {
223 g.warning('fn_decl: ${name} is deprecated', node.pos)
224 }
225
226 mut paramdbg := []?string{cap: node.params.len}
227 mut paraml := []wasm.ValType{cap: node.params.len}
228 mut retl := []wasm.ValType{cap: 1}
229
230 // fn ()! | fn () &IError
231 // fn () ?(...) | fn () (..., bool)
232 // fn () !(...) | fn () (..., &IError)
233 //
234 // fn (...) struct | fn (_ &struct, ...)
235 // fn (...) !struct | fn (_ &struct, ...) &IError
236 // fn (...) (...struct) | fn (...&struct, ...)
237
238 g.ret_rvars = []Var{}
239 rt := node.return_type
240 rts := g.table.sym(rt)
241 g.ret = rt
242 match rts.info {
243 ast.MultiReturn {
244 for t in rts.info.types {
245 wtyp := g.get_wasm_type(t)
246 if g.is_param_type(t) {
247 paramdbg << g.dbg_type_name('__rval(${g.ret_rvars.len})', t)
248 paraml << wtyp
249 g.ret_rvars << Var{
250 typ: t
251 idx: g.ret_rvars.len
252 is_address: true
253 }
254 } else {
255 retl << wtyp
256 }
257 g.ret_types << t
258 }
259 if rt.has_flag(.option) {
260 g.v_error('option types are not implemented', node.return_type_pos)
261 retl << .i32_t // bool
262 }
263 }
264 else {
265 if rt.idx() != ast.void_type_idx {
266 // A scalar `?T`/`!T` return reaches here (it is not a MultiReturn),
267 // so the option/result guards below — which only run inside the
268 // MultiReturn arm or after the match — are dead for it. Guard before
269 // `get_wasm_type`, which would otherwise abort with an internal
270 // "unreachable type" ICE on the option/result-wrapped type.
271 if rt.has_flag(.option) {
272 g.v_error('option types are not implemented', node.return_type_pos)
273 }
274 if rt.has_flag(.result) {
275 g.v_error('result types are not implemented', node.return_type_pos)
276 }
277 wtyp := g.get_wasm_type(rt)
278 if g.is_param_type(rt) {
279 paramdbg << g.dbg_type_name('__rval(0)', rt)
280 paraml << wtyp
281 g.ret_rvars << Var{
282 typ: rt
283 is_address: true
284 }
285 } else {
286 retl << wtyp
287 }
288 g.ret_types << rt
289 } else if rt.has_flag(.option) {
290 g.v_error('returning a void option is forbidden', node.return_type_pos)
291 }
292 }
293 }
294
295 if rt.has_flag(.result) {
296 g.v_error('result types are not implemented', node.return_type_pos)
297 retl << .i32_t // &IError
298 }
299
300 for p in node.params {
301 typ := g.get_wasm_type_int_literal(p.typ)
302 ntyp := unpack_literal_int(p.typ)
303 g.local_vars << Var{
304 name: p.name
305 typ: ntyp
306 idx: g.local_vars.len + g.ret_rvars.len
307 is_address: !g.is_pure_type(p.typ)
308 }
309 paramdbg << g.dbg_type_name(p.name, p.typ)
310 paraml << typ
311 }
312
313 // bottom scope
314
315 g.is_direct_array_access = node.is_direct_arr || g.pref.no_bounds_checking
316 g.fn_local_idx_end = (g.local_vars.len + g.ret_rvars.len)
317 g.fn_name = name
318
319 mut should_export := g.pref.os in [.browser, .wasi] && node.is_pub && node.mod == 'main'
320
321 g.func = g.mod.new_debug_function(name, wasm.FuncType{paraml, retl, none}, paramdbg)
322 func_start := g.func.patch_pos()
323 if node.stmts.len > 0 {
324 g.ret_br = g.func.c_block([], retl)
325 {
326 g.expr_stmts(node.stmts, ast.void_type)
327 }
328 {
329 for idx, defer_stmt in node.defer_stmts {
330 g.get(g.defer_vars[idx])
331 lbl := g.func.c_if([], [])
332 {
333 g.expr_stmts(defer_stmt.stmts, ast.void_type)
334 }
335 g.func.c_end(lbl)
336 }
337 }
338 g.func.c_end(g.ret_br)
339 g.bare_function_frame(func_start)
340 }
341 if cattr := node.attrs.find_first('export') {
342 g.func.export_name(cattr.arg)
343 should_export = true
344 }
345 g.mod.commit(g.func, should_export)
346 g.bare_function_end()
347
348 // printfn is not implemented!
349}
350
351pub fn (mut g Gen) bare_function_frame(func_start wasm.PatchPos) {
352 // Setup stack frame.
353 // If the function does not call other functions,
354 // a leaf function, the omission of setting the
355 // stack pointer is perfectly acceptable.
356 //
357 if g.stack_frame != 0 {
358 prologue := g.func.patch_pos()
359 {
360 g.func.global_get(g.sp())
361 g.func.i32_const(i32(g.stack_frame))
362 g.func.sub(.i32_t)
363 if !g.is_leaf_function {
364 g.func.local_tee(g.bp())
365 g.func.global_set(g.sp())
366 } else {
367 g.func.local_set(g.bp())
368 }
369 }
370 g.func.patch(func_start, prologue)
371 if !g.is_leaf_function {
372 g.func.global_get(g.sp())
373 g.func.i32_const(i32(g.stack_frame))
374 g.func.add(.i32_t)
375 g.func.global_set(g.sp())
376 }
377 }
378}
379
380pub fn (mut g Gen) bare_function_end() {
381 g.local_vars.clear()
382 g.ret_rvars.clear()
383 g.ret_types.clear()
384 g.defer_vars.clear()
385 g.bp_idx = -1
386 g.stack_frame = 0
387 g.is_leaf_function = true
388 g.is_direct_array_access = false
389 assert g.loop_breakpoint_stack.len == 0
390}
391
392pub fn (mut g Gen) literalint(val i64, expected ast.Type) {
393 match g.get_wasm_type(expected) {
394 .i32_t { g.func.i32_const(i32(val)) }
395 .i64_t { g.func.i64_const(val) }
396 .f32_t { g.func.f32_const(f32(val)) }
397 .f64_t { g.func.f64_const(f64(val)) }
398 else { g.w_error('literalint: bad type `${expected}`') }
399 }
400}
401
402pub fn (mut g Gen) literal(val string, expected ast.Type) {
403 match g.get_wasm_type(expected) {
404 .i32_t { g.func.i32_const(i32(val.int())) }
405 .i64_t { g.func.i64_const(val.i64()) }
406 .f32_t { g.func.f32_const(val.f32()) }
407 .f64_t { g.func.f64_const(val.f64()) }
408 else { g.w_error('literal: bad type `${expected}`') }
409 }
410}
411
412pub fn (mut g Gen) cast(typ ast.Type, expected_type ast.Type) {
413 wtyp := g.as_numtype(g.get_wasm_type_int_literal(typ))
414 expected_wtype := g.as_numtype(g.get_wasm_type_int_literal(expected_type))
415
416 g.func.cast(wtyp, typ.is_signed(), expected_wtype)
417}
418
419pub fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_type ast.Type) {
420 if expr is ast.IntegerLiteral {
421 g.literal(expr.val, expected_type)
422 return
423 } else if expr is ast.FloatLiteral {
424 g.literal(expr.val, expected_type)
425 return
426 }
427
428 got_type := ast.mktyp(got_type_raw)
429 got_wtype := g.as_numtype(g.get_wasm_type(got_type))
430 expected_wtype := g.as_numtype(g.get_wasm_type(expected_type))
431
432 g.expr(expr, got_type)
433 g.func.cast(got_wtype, got_type.is_signed(), expected_wtype)
434}
435
436pub fn (mut g Gen) handle_ptr_arithmetic(typ ast.Type) {
437 if typ.is_ptr() {
438 size, _ := g.pool.type_size(typ)
439 g.func.i32_const(i32(size))
440 g.func.mul(.i32_t)
441 }
442}
443
444fn (mut g Gen) handle_string_operation(op token.Kind) {
445 left_tmp := g.func.new_local_named(.i32_t, '__tmp<string>.left')
446 right_tmp := g.func.new_local_named(.i32_t, '__tmp<string>.right')
447 g.func.local_set(right_tmp)
448 g.func.local_set(left_tmp)
449
450 match op {
451 .plus {
452 ret_var := g.new_local('', ast.string_type)
453 g.ref(ret_var)
454 g.func.local_get(left_tmp)
455 g.func.local_get(right_tmp)
456 g.func.call('string.+')
457 g.get(ret_var)
458 }
459 .eq {
460 g.func.local_get(left_tmp)
461 g.func.local_get(right_tmp)
462 g.func.call('string.==')
463 }
464 .ne {
465 g.func.local_get(left_tmp)
466 g.func.local_get(right_tmp)
467 g.func.call('string.==')
468 g.func.eqz(.i32_t)
469 }
470 .lt {
471 g.func.local_get(left_tmp)
472 g.func.local_get(right_tmp)
473 g.func.call('string.<')
474 }
475 .gt {
476 g.func.local_get(right_tmp)
477 g.func.local_get(left_tmp)
478 g.func.call('string.<')
479 }
480 .le {
481 g.func.local_get(right_tmp)
482 g.func.local_get(left_tmp)
483 g.func.call('string.<')
484 g.func.eqz(.i32_t)
485 }
486 .ge {
487 g.func.local_get(left_tmp)
488 g.func.local_get(right_tmp)
489 g.func.call('string.<')
490 g.func.eqz(.i32_t)
491 }
492 else {
493 g.w_error('unsupported string operation: `${op}`')
494 }
495 }
496}
497
498pub fn (mut g Gen) string_inter_literal_expr(node ast.StringInterLiteral, expected ast.Type) {
499 if node.exprs.len == 0 {
500 g.expr(ast.StringLiteral{ val: node.vals[0], pos: node.pos }, expected)
501 return
502 }
503
504 result_var := g.new_local('__str_inter', ast.string_type)
505
506 g.set_with_expr(ast.StringLiteral{ val: node.vals[0], pos: node.pos }, result_var)
507
508 for i, expr in node.exprs {
509 mut expr_to_concat := expr
510 typ := node.expr_types[i]
511
512 if typ != ast.string_type {
513 has_str, _, _ := g.table.sym(typ).str_method_info()
514 if !has_str {
515 g.v_error('cannot interpolate type without .str() method', node.fmt_poss[i])
516 }
517
518 expr_to_concat = ast.CallExpr{
519 name: 'str'
520 left: expr
521 left_type: typ
522 receiver_type: typ
523 return_type: ast.string_type
524 is_method: true
525 is_return_used: true
526 }
527 }
528
529 // result = result + expr_as_string
530 {
531 g.get(result_var)
532 g.expr(expr_to_concat, ast.string_type)
533 g.handle_string_operation(.plus)
534 g.set(result_var)
535 }
536
537 // Concat the next string segment (if not empty)
538 if i + 1 < node.vals.len && node.vals[i + 1].len > 0 {
539 g.get(result_var)
540 g.expr(ast.StringLiteral{ val: node.vals[i + 1], pos: node.pos }, ast.string_type)
541 g.handle_string_operation(.plus)
542 g.set(result_var)
543 }
544 }
545
546 g.get(result_var)
547}
548
549pub fn (mut g Gen) infix_expr(node ast.InfixExpr, expected ast.Type) {
550 if node.op in [.logical_or, .and] {
551 temp := g.func.new_local_named(.i32_t, '__tmp<bool>')
552 {
553 g.expr(node.left, ast.bool_type)
554 g.func.local_set(temp)
555 }
556 g.func.local_get(temp)
557 if node.op == .logical_or {
558 g.func.eqz(.i32_t)
559 }
560
561 blk := g.func.c_if([], [.i32_t])
562 {
563 g.expr(node.right, ast.bool_type)
564 }
565 g.func.c_else(blk)
566 {
567 g.func.local_get(temp)
568 }
569 g.func.c_end(blk)
570 return
571 }
572
573 {
574 g.expr(node.left, node.left_type)
575 }
576 {
577 g.expr_with_cast(node.right, node.right_type, node.left_type)
578 if node.op in [.plus, .minus] && node.left_type.is_ptr() {
579 g.handle_ptr_arithmetic(node.left_type.deref())
580 }
581 }
582 g.infix_from_typ(node.left_type, node.op)
583
584 res_typ := if node.op in [.eq, .ne, .gt, .lt, .ge, .le] { ast.bool_type } else { node.left_type }
585 g.func.cast(g.as_numtype(g.get_wasm_type(res_typ)), res_typ.is_signed(),
586 g.as_numtype(g.get_wasm_type(expected)))
587}
588
589pub fn (mut g Gen) prefix_expr(node ast.PrefixExpr, expected ast.Type) {
590 match node.op {
591 .minus {
592 if node.right_type.is_pure_float() {
593 g.expr(node.right, node.right_type)
594 if node.right_type == ast.f32_type_idx {
595 g.func.neg(.f32_t)
596 } else {
597 g.func.neg(.f64_t)
598 }
599 } else {
600 // -val == 0 - val
601
602 vt := g.get_wasm_type(node.right_type)
603
604 g.literalint(0, node.right_type)
605 g.expr(node.right, node.right_type)
606 g.func.sub(g.as_numtype(vt))
607 }
608 }
609 .not {
610 g.expr(node.right, node.right_type)
611 g.func.eqz(.i32_t) // !expr
612 }
613 .bit_not {
614 // ~val == val ^ -1
615
616 vt := g.get_wasm_type(node.right_type)
617
618 g.expr(node.right, node.right_type)
619 g.literalint(-1, node.right_type)
620 g.func.b_xor(g.as_numtype(vt))
621 }
622 .amp {
623 if v := g.get_var_from_expr(node.right) {
624 if !v.is_address {
625 g.v_error("cannot take the address of a value that doesn't live on the stack",
626 node.pos)
627 }
628 g.ref(v)
629 } else {
630 g.needs_address = true
631 {
632 g.expr(node.right, node.right_type)
633 }
634 g.needs_address = false
635 }
636 }
637 .mul {
638 g.expr(node.right, node.right_type)
639 if g.is_pure_type(expected) && !g.needs_address {
640 // in a RHS context, not lvalue
641 g.load(expected, 0)
642 }
643 }
644 else {
645 // impl deref (.mul), and impl address of (.amp)
646 g.w_error('`${node.op}val` prefix expression not implemented')
647 }
648 }
649}
650
651pub fn (mut g Gen) if_branch(ifexpr ast.IfExpr, expected ast.Type, unpacked_params []wasm.ValType, idx int,
652 existing_rvars []Var) {
653 curr := ifexpr.branches[idx]
654
655 g.expr(curr.cond, ast.bool_type)
656 blk := g.func.c_if([], unpacked_params)
657 {
658 g.rvar_expr_stmts(curr.stmts, expected, existing_rvars)
659 }
660 {
661 if ifexpr.has_else && idx + 2 >= ifexpr.branches.len {
662 g.func.c_else(blk)
663 g.rvar_expr_stmts(ifexpr.branches[idx + 1].stmts, expected, existing_rvars)
664 } else if !(idx + 1 >= ifexpr.branches.len) {
665 g.func.c_else(blk)
666 g.if_branch(ifexpr, expected, unpacked_params, idx + 1, existing_rvars)
667 }
668 }
669 g.func.c_end(blk)
670}
671
672pub fn (mut g Gen) if_expr(ifexpr ast.IfExpr, expected ast.Type, existing_rvars []Var) {
673 if ifexpr.is_comptime {
674 g.comptime_if_expr(ifexpr, expected, existing_rvars)
675 return
676 }
677
678 params := if expected == ast.void_type {
679 []wasm.ValType{}
680 } else if existing_rvars.len == 0 {
681 g.unpack_type(expected).map(g.get_wasm_type(it))
682 } else {
683 g.unpack_type(expected).filter(!g.is_param_type(it)).map(g.get_wasm_type(it))
684 }
685 g.if_branch(ifexpr, expected, params, 0, existing_rvars)
686}
687
688pub fn (mut g Gen) match_expr(node ast.MatchExpr, expected ast.Type, existing_rvars []Var) {
689 results := if expected == ast.void_type {
690 []wasm.ValType{}
691 } else if existing_rvars.len == 0 {
692 g.unpack_type(expected).map(g.get_wasm_type(it))
693 } else {
694 g.unpack_type(expected).filter(!g.is_param_type(it)).map(g.get_wasm_type(it))
695 }
696 g.match_branch(node, expected, results, 0, existing_rvars)
697}
698
699fn (mut g Gen) match_branch(node ast.MatchExpr, expected ast.Type, unpacked_params []wasm.ValType, branch_idx int, existing_rvars []Var) {
700 if branch_idx >= node.branches.len {
701 return
702 }
703
704 branch := node.branches[branch_idx]
705 mut is_last_branch := branch_idx + 1 >= node.branches.len
706 mut has_else := branch.is_else
707
708 if has_else {
709 if branch.stmts.len > 0 {
710 g.rvar_expr_stmts(branch.stmts, expected, existing_rvars)
711 }
712 return
713 }
714
715 if branch.exprs.len > 0 {
716 g.match_branch_exprs(node, expected, unpacked_params, branch_idx, 0, existing_rvars, branch)
717 } else {
718 if branch.stmts.len > 0 {
719 g.rvar_expr_stmts(branch.stmts, expected, existing_rvars)
720 }
721 if !is_last_branch {
722 g.match_branch(node, expected, unpacked_params, branch_idx + 1, existing_rvars)
723 }
724 }
725}
726
727fn (mut g Gen) match_branch_exprs(node ast.MatchExpr, expected ast.Type, unpacked_params []wasm.ValType, branch_idx int, expr_idx int, existing_rvars []Var, branch ast.MatchBranch) {
728 if expr_idx >= branch.exprs.len {
729 return
730 }
731
732 mut is_last_branch := branch_idx + 1 >= node.branches.len
733 mut is_last_expr := expr_idx + 1 >= branch.exprs.len
734
735 expr := branch.exprs[expr_idx]
736
737 if expr is ast.RangeExpr {
738 wasm_type := g.as_numtype(g.get_wasm_type(node.cond_type))
739 is_signed := node.cond_type.is_signed()
740
741 g.expr(node.cond, node.cond_type)
742 g.expr(expr.high, node.cond_type)
743 g.func.le(wasm_type, is_signed)
744 } else {
745 if g.is_param_type(node.cond_type) {
746 // Param types -> strings etc
747 g.expr(node.cond, node.cond_type)
748 g.expr(expr, node.cond_type)
749 g.infix_from_typ(node.cond_type, .eq)
750 } else {
751 // Numeric types -> direct comparison
752 wasm_type := g.as_numtype(g.get_wasm_type(node.cond_type))
753 g.expr(node.cond, node.cond_type)
754 g.expr(expr, node.cond_type)
755 g.func.eq(wasm_type)
756 }
757 }
758
759 blk := g.func.c_if([], unpacked_params)
760 {
761 if branch.stmts.len > 0 {
762 g.rvar_expr_stmts(branch.stmts, expected, existing_rvars)
763 }
764 }
765 {
766 g.func.c_else(blk)
767 if is_last_expr {
768 if !is_last_branch {
769 g.match_branch(node, expected, unpacked_params, branch_idx + 1, existing_rvars)
770 }
771 } else {
772 g.match_branch_exprs(node, expected, unpacked_params, branch_idx, expr_idx + 1,
773 existing_rvars, branch)
774 }
775 }
776 g.func.c_end(blk)
777}
778
779// fn_value_functype lowers a V function type into the wasm function type used
780// by `call_indirect`. It mirrors the parameter/return lowering done in `fn_decl`
781// (a non-pure return becomes a leading rvar pointer parameter), so the computed
782// type index matches the type the target function was committed with.
783fn (mut g Gen) fn_value_functype(fn_typ ast.Type) wasm.FuncType {
784 // final_sym unwraps any alias layer (e.g. `type Callback = fn (int) int`)
785 ts := g.table.final_sym(fn_typ)
786 if ts.info !is ast.FnType {
787 g.w_error('fn_value_functype: `${ts.name}` is not a function type')
788 }
789 func := (ts.info as ast.FnType).func
790
791 mut paraml := []wasm.ValType{}
792 mut retl := []wasm.ValType{}
793
794 rt := func.return_type
795 if g.table.sym(rt).info is ast.MultiReturn {
796 g.w_error('wasm backend: calling a function value with multiple return values is not yet supported')
797 }
798 if rt.has_flag(.option) || rt.has_flag(.result) {
799 g.w_error('wasm backend: option/result function values are not yet supported')
800 }
801 if rt.idx() != ast.void_type_idx {
802 wtyp := g.get_wasm_type(rt)
803 if g.is_param_type(rt) {
804 paraml << wtyp // non-pure return -> leading rvar pointer (i32)
805 } else {
806 retl << wtyp
807 }
808 }
809 for p in func.params {
810 paraml << g.get_wasm_type_int_literal(p.typ)
811 }
812
813 return wasm.FuncType{paraml, retl, none}
814}
815
816// push_fn_value evaluates the callee of a function-value call and leaves its
817// i32 index into the indirect function table on the stack.
818fn (mut g Gen) push_fn_value(node ast.CallExpr, fn_typ ast.Type) {
819 if node.is_method {
820 // a fn-typed struct field, e.g. `b.op` in `b.op(x)`: load the field
821 g.expr(ast.SelectorExpr{
822 expr: node.left
823 field_name: node.name
824 expr_type: node.left_type
825 typ: fn_typ
826 }, fn_typ)
827 } else if node.name == '' {
828 // the callee is the expression itself, e.g. `(expr)(x)` or `make_cb()(x)`
829 g.expr(node.left, fn_typ)
830 } else if node.is_fn_a_const {
831 // a const of function type. The const is registered in the global scope
832 // under its fully-qualified name (`node.const_name`, e.g. `main.cb`), not
833 // the lexical call name, so a plain `scope.find(node.name)` misses it.
834 obj := g.table.global_scope.find(node.const_name) or {
835 g.w_error('wasm: cannot resolve function const `${node.const_name}`')
836 }
837 v := g.get_var_from_ident(ast.Ident{
838 name: node.const_name
839 scope: node.scope
840 obj: obj
841 })
842 g.get(v)
843 } else {
844 // a named local/param variable of function type
845 obj := node.scope.find(node.name) or {
846 g.w_error('wasm: cannot resolve function value `${node.name}`')
847 }
848 v := g.get_var_from_ident(ast.Ident{
849 name: node.name
850 scope: node.scope
851 obj: obj
852 })
853 g.get(v)
854 }
855}
856
857pub fn (mut g Gen) call_expr(node ast.CallExpr, expected ast.Type, existing_rvars []Var) {
858 mut wasm_ns := ?string(none)
859 mut name := node.name
860
861 // Detect a call to a function value: either a fn-typed local/param/const
862 // (is_fn_var/is_fn_a_const), a fn-typed struct field, which the checker
863 // represents as a method call (`b.op()`), or an expression callee that
864 // evaluates to a function (`make_cb()(x)`, `(expr)(x)`), which the checker
865 // leaves with an empty name and a fn-typed `left_type`.
866 mut is_fn_value := false
867 mut fn_value_typ := ast.void_type
868 if node.is_fn_var || node.is_fn_a_const {
869 is_fn_value = true
870 fn_value_typ = node.fn_var_type
871 } else if node.is_method && node.left_type != 0 {
872 if field := g.table.find_field(g.table.sym(node.left_type), node.name) {
873 if g.table.final_sym(field.typ).info is ast.FnType {
874 is_fn_value = true
875 fn_value_typ = field.typ
876 }
877 }
878 } else if node.name == '' && node.left_type != 0 {
879 if g.table.final_sym(node.left_type).info is ast.FnType {
880 is_fn_value = true
881 fn_value_typ = node.left_type
882 }
883 }
884
885 is_print := name in ['panic', 'println', 'print', 'eprintln', 'eprint']
886
887 if node.is_method {
888 name = '${g.table.get_type_name(node.receiver_type)}.${node.name}'
889 }
890
891 if node.language in [.js, .wasm] {
892 cfn_attrs := unsafe { g.table.fns[node.name].attrs }
893
894 short_name := if node.language == .js {
895 node.name.all_after_last('JS.')
896 } else {
897 node.name.all_after_last('WASM.')
898 }
899
900 // setting a `?string` in a multireturn causes UNDEFINED BEHAVIOR AND STACK CORRUPTION
901 // best to use a workaround till that is fixed
902
903 mut wasm_ns_storage := ''
904 wasm_ns_storage, name = g.get_ns_plus_name(short_name, cfn_attrs)
905 wasm_ns = wasm_ns_storage
906 }
907
908 // callconv: {return structs} {method self} {arguments}
909
910 // {return structs}
911 //
912 mut rvars := existing_rvars.clone()
913 rts := g.unpack_type(node.return_type)
914 if rvars.len == 0 && node.return_type != ast.void_type {
915 for rt in rts {
916 if g.is_param_type(rt) {
917 v := g.new_local('', rt)
918 rvars << v
919 }
920 }
921 }
922 for v in rvars {
923 g.ref(v)
924 }
925
926 // {method self}
927 //
928 if node.is_method && !is_fn_value {
929 expr := if !node.left_type.is_ptr() && node.receiver_type.is_ptr() { ast.Expr(ast.PrefixExpr{
930 op: .amp
931 right: node.left
932 }) } else { node.left }
933 // hack alert!
934 if node.receiver_type == ast.int_literal_type && expr is ast.IntegerLiteral {
935 g.literal(expr.val, ast.i64_type)
936 } else {
937 g.expr(expr, node.receiver_type)
938 }
939 }
940
941 // {callee}
942 //
943 // Evaluate the callee of a function-value call *before* its arguments, so a
944 // callee with side effects (e.g. `make_box().op(next())`) keeps V's
945 // left-to-right evaluation order. `call_indirect` wants the table index on
946 // top of the stack (after the args), so stash it in a temp and reload it
947 // once the arguments are in place.
948 mut fn_value_idx := Var{}
949 if is_fn_value {
950 g.is_leaf_function = false
951 g.uses_call_indirect = true
952 fn_value_idx = g.new_local('', fn_value_typ)
953 g.push_fn_value(node, fn_value_typ)
954 g.set(fn_value_idx)
955 }
956
957 // {arguments}
958 //
959 for idx, arg in node.args {
960 mut expr := arg.expr
961
962 mut typ := arg.typ
963 if is_print && typ != ast.string_type {
964 has_str, _, _ := g.table.sym(typ).str_method_info()
965 if typ != ast.string_type && !has_str {
966 g.v_error('cannot implicitly convert as argument does not have a .str() function',
967 arg.pos)
968 }
969
970 expr = ast.CallExpr{
971 name: 'str'
972 left: expr
973 left_type: typ
974 receiver_type: typ
975 return_type: ast.string_type
976 is_method: true
977 is_return_used: true
978 }
979 }
980
981 // another hack alert!
982 if node.expected_arg_types[idx] == ast.int_literal_type && mut expr is ast.IntegerLiteral {
983 g.literal(expr.val, ast.i64_type)
984 } else {
985 g.expr(expr, node.expected_arg_types[idx])
986 }
987 }
988
989 if is_fn_value {
990 // args are already on the stack; reload the callee's table index (computed
991 // before the args, above) on top, then dispatch through the indirect table
992 typeidx := g.mod.new_functype(g.fn_value_functype(fn_value_typ))
993 g.get(fn_value_idx)
994 g.func.call_indirect(typeidx, 0)
995 } else if namespace := wasm_ns {
996 // import calls won't touch `__vsp` !
997
998 g.func.call_import(namespace, name)
999 } else {
1000 // other calls may...
1001 g.is_leaf_function = false
1002
1003 g.func.call(name)
1004 }
1005
1006 if expected == ast.void_type && node.return_type != ast.void_type {
1007 for rt in rts { // order doesn't matter
1008 if !g.is_param_type(rt) {
1009 g.func.drop()
1010 }
1011 }
1012 } else if rvars.len > 0 && existing_rvars.len == 0 {
1013 mut rr_vars := []Var{cap: rts.len}
1014 mut r := rvars.len
1015
1016 for rt in rts.reverse() {
1017 if !g.is_param_type(rt) {
1018 v := g.new_local('', rt)
1019 rr_vars << v
1020 g.set(v)
1021 } else {
1022 r--
1023 rr_vars << rvars[r]
1024 }
1025 }
1026
1027 for v in rr_vars.reverse() {
1028 g.get(v)
1029 }
1030 }
1031 if node.is_noreturn {
1032 g.func.unreachable()
1033 }
1034}
1035
1036pub fn (mut g Gen) get_field_offset(typ ast.Type, name string) int {
1037 ts := g.table.sym(typ)
1038 field := ts.find_field(name) or { g.w_error('could not find field `${name}` on init') }
1039 si := g.pool.type_struct_info(typ) or { panic('unreachable') }
1040 return si.offsets[field.i]
1041}
1042
1043pub fn (mut g Gen) field_offset(typ ast.Type, name string) {
1044 offset := g.get_field_offset(typ, name)
1045 if offset != 0 {
1046 g.func.i32_const(i32(offset))
1047 g.func.add(.i32_t)
1048 }
1049}
1050
1051pub fn (mut g Gen) load_field(typ ast.Type, ftyp ast.Type, name string) {
1052 offset := g.get_field_offset(typ, name)
1053 g.load(ftyp, offset)
1054}
1055
1056pub fn (mut g Gen) store_field(typ ast.Type, ftyp ast.Type, name string) {
1057 offset := g.get_field_offset(typ, name)
1058 g.store(ftyp, offset)
1059}
1060
1061pub fn (mut g Gen) expr(node ast.Expr, expected ast.Type) {
1062 match node {
1063 ast.ParExpr, ast.UnsafeExpr {
1064 g.expr(node.expr, expected)
1065 }
1066 ast.ArrayInit {
1067 v := g.new_local('', node.typ)
1068 g.set_with_expr(node, v)
1069 g.get(v)
1070 }
1071 ast.GoExpr {
1072 g.w_error('wasm backend does not support threads')
1073 }
1074 ast.IndexExpr {
1075 mut direct_array_access := g.is_direct_array_access || node.is_direct
1076 mut tmp_voidptr_var := wasm.LocalIndex(-1)
1077
1078 // ptr + index * size
1079 mut typ := node.left_type
1080 ts := g.table.sym(typ)
1081
1082 g.expr(node.left, node.left_type)
1083 if node.left_type == ast.string_type {
1084 if !direct_array_access {
1085 tmp_voidptr_var = g.func.new_local_named(.i32_t, '__tmp<voidptr>')
1086 g.func.local_tee(tmp_voidptr_var)
1087 }
1088
1089 // be pedantic...
1090 g.load_field(ast.string_type, ast.voidptr_type, 'str')
1091 typ = ast.u8_type
1092 } else if typ.is_ptr() {
1093 typ = typ.deref()
1094 direct_array_access = true
1095 } else {
1096 match ts.info {
1097 ast.Array {
1098 g.w_error('wasm backend does not support dynamic arrays')
1099 }
1100 ast.ArrayFixed {
1101 typ = ts.info.elem_type
1102 if node.index.is_pure_literal() {
1103 // checker would have gotten this by now
1104 direct_array_access = true
1105 }
1106 }
1107 else {
1108 g.w_error('ast.IndexExpr: unreachable')
1109 }
1110 }
1111 }
1112
1113 size, _ := g.pool.type_size(typ)
1114
1115 old_needs_address := g.needs_address
1116 g.needs_address = false
1117 g.expr(node.index, ast.int_type)
1118 g.needs_address = old_needs_address
1119
1120 if !direct_array_access {
1121 g.is_leaf_function = false // calls panic()
1122
1123 idx_temp := g.func.new_local_named(.i32_t, '__tmp<int>')
1124 g.func.local_tee(idx_temp)
1125
1126 // .len
1127 if node.left_type == ast.string_type {
1128 g.func.local_get(tmp_voidptr_var)
1129 g.load_field(ast.string_type, ast.int_type, 'len')
1130 } else if ts.info is ast.ArrayFixed {
1131 g.func.i32_const(i32(ts.info.size))
1132 } else {
1133 panic('unreachable')
1134 }
1135
1136 g.func.ge(.i32_t, false)
1137 // is_signed: false, negative numbers will be reinterpreted as > 2^31 and will also trigger false
1138 blk := g.func.c_if([], [])
1139 {
1140 g.expr(ast.StringLiteral{ val: '${g.file_pos(node.pos)}: ${ast.Expr(node)}' },
1141 ast.string_type)
1142 g.func.call('eprintln')
1143 g.expr(ast.StringLiteral{ val: 'index out of range' }, ast.string_type)
1144 g.func.call('panic')
1145 }
1146 g.func.c_end(blk)
1147
1148 g.func.local_get(idx_temp)
1149 }
1150
1151 if size > 1 {
1152 g.literalint(size, ast.int_type)
1153 g.func.mul(.i32_t)
1154 }
1155
1156 g.func.add(.i32_t)
1157
1158 if !g.is_pure_type(typ) {
1159 return
1160 }
1161
1162 if !g.needs_address {
1163 // ptr
1164 g.load(typ, 0)
1165 }
1166 g.cast(typ, expected)
1167 }
1168 ast.StructInit {
1169 v := g.new_local('', node.typ)
1170 g.set_with_expr(node, v)
1171 g.get(v)
1172 }
1173 ast.SelectorExpr {
1174 final := g.table.final_sym(node.expr_type)
1175 if node.field_name == 'len' && final.info is ast.ArrayFixed {
1176 // a fixed array's `.len` is a compile-time constant; the checker
1177 // types it as `int` but emits no field, so resolve it here rather
1178 // than aborting in get_field_offset ("could not find field len").
1179 g.func.i32_const(i32(final.info.size))
1180 } else if v := g.get_var_from_expr(node) {
1181 if g.needs_address {
1182 if !v.is_address {
1183 g.v_error("cannot take the address of a value that doesn't live on the stack. this is a current limitation.",
1184 node.pos)
1185 }
1186 g.ref(v)
1187 } else {
1188 g.get(v)
1189 }
1190 } else {
1191 g.needs_address = true
1192 {
1193 g.expr(node.expr, node.typ)
1194 }
1195 g.needs_address = false
1196 g.field_offset(node.expr_type, node.field_name)
1197 if g.is_pure_type(node.typ) && !g.needs_address {
1198 // expected to be a pointer
1199 g.load(node.typ, 0)
1200 }
1201 }
1202 g.cast(node.typ, expected)
1203 }
1204 ast.MatchExpr {
1205 g.match_expr(node, expected, [])
1206 }
1207 ast.EnumVal {
1208 type_name := g.table.get_type_name(node.typ)
1209 ts_type := (g.table.sym(node.typ).info as ast.Enum).typ
1210 g.literalint(g.enum_vals[type_name].fields[node.val], ts_type)
1211 }
1212 ast.OffsetOf {
1213 sym := g.table.sym(node.struct_type)
1214 if sym.kind != .struct {
1215 g.v_error('__offsetof expects a struct Type as first argument', node.pos)
1216 }
1217 off := g.get_field_offset(node.struct_type, node.field)
1218 g.literalint(off, ast.u32_type)
1219 }
1220 ast.SizeOf {
1221 if !g.table.known_type_idx(node.typ) {
1222 g.v_error('unknown type `${*g.table.sym(node.typ)}`', node.pos)
1223 }
1224 size, _ := g.pool.type_size(node.typ)
1225 g.literalint(size, ast.u32_type)
1226 }
1227 ast.BoolLiteral {
1228 g.func.i32_const(i32(node.val))
1229 }
1230 ast.StringLiteral {
1231 if expected != ast.string_type {
1232 val := serialise.eval_escape_codes(node) or { panic('unreachable') }
1233 str_pos := g.pool.append_string(val)
1234
1235 // c'str'
1236 g.literalint(g.data_base + str_pos, ast.voidptr_type)
1237 return
1238 }
1239
1240 v := g.new_local('', ast.string_type)
1241 g.set_with_expr(node, v)
1242 g.get(v)
1243 }
1244 ast.StringInterLiteral {
1245 g.string_inter_literal_expr(node, expected)
1246 }
1247 ast.InfixExpr {
1248 g.infix_expr(node, expected)
1249 }
1250 ast.PrefixExpr {
1251 g.prefix_expr(node, expected)
1252 }
1253 ast.PostfixExpr {
1254 kind := if node.op == .inc { token.Kind.plus } else { token.Kind.minus }
1255 v := g.get_var_or_make_from_expr(node.expr, node.typ)
1256
1257 g.set_prepare(v)
1258 {
1259 g.get(v)
1260 g.literalint(1, node.typ)
1261 g.handle_ptr_arithmetic(node.typ)
1262 g.infix_from_typ(node.typ, kind)
1263 }
1264 g.set_set(v)
1265 }
1266 ast.CharLiteral {
1267 rns := serialise.eval_escape_codes_raw(node.val) or { panic('unreachable') }.runes()[0]
1268 g.func.i32_const(i32(rns))
1269 }
1270 ast.Ident {
1271 if node.kind == .function {
1272 // a function used as a value: push its index into the
1273 // indirect function table (an i32)
1274 g.func.i32_const(i32(g.fn_table_index(node.name)))
1275 } else {
1276 v := g.get_var_from_ident(node)
1277 g.get(v)
1278 g.cast(v.typ, expected)
1279 }
1280 }
1281 ast.IntegerLiteral, ast.FloatLiteral {
1282 g.literal(node.val, expected)
1283 }
1284 ast.Nil {
1285 g.func.i32_const(0)
1286 }
1287 ast.EmptyExpr {}
1288 ast.IfExpr {
1289 g.if_expr(node, expected, [])
1290 }
1291 ast.CastExpr {
1292 // don't want to handle ast.int_literal_type
1293 if node.expr is ast.IntegerLiteral || node.expr is ast.FloatLiteral {
1294 g.expr(node.expr, node.typ)
1295 return
1296 }
1297
1298 g.expr(node.expr, node.expr_type)
1299
1300 // A cast involving a function value (e.g. `type Alias = fn (...)`) is an
1301 // i32 table index on both sides, so the value passes through unchanged.
1302 // Return before the numeric-cast path, which would also mis-handle a
1303 // function callee (`Alias(some_fn)`) as a variable in get_var_from_ident.
1304 if g.table.final_sym(node.typ).info is ast.FnType
1305 || g.table.final_sym(node.expr_type).info is ast.FnType {
1306 return
1307 }
1308
1309 // TODO: unbelievable colossal hack
1310 mut typ := node.expr_type
1311 if node.expr is ast.Ident {
1312 v := g.get_var_from_ident(node.expr)
1313 if g.is_param(v) && node.expr_type == ast.int_literal_type {
1314 typ = ast.i64_type
1315 }
1316 }
1317
1318 g.func.cast(g.as_numtype(g.get_wasm_type(typ)), typ.is_signed(),
1319 g.as_numtype(g.get_wasm_type(node.typ)))
1320 }
1321 ast.CallExpr {
1322 g.call_expr(node, expected, [])
1323 }
1324 ast.AnonFn {
1325 if node.inherited_vars.len > 0 {
1326 // closures lower to runtime-generated executable memory, which
1327 // WebAssembly does not support efficiently yet; reject for now.
1328 g.v_error('closures (capturing anonymous functions) are not yet supported on the `wasm` backend',
1329 node.decl.pos)
1330 }
1331 // compile the anon fn after the current toplevel stmts (when the
1332 // per-function state is clean again), then push its table index
1333 idx := g.fn_table_index(node.decl.name)
1334 if node.decl.name !in g.compiled_anon_fns {
1335 g.compiled_anon_fns[node.decl.name] = true
1336 g.pending_anon_fns << node.decl
1337 }
1338 g.func.i32_const(i32(idx))
1339 }
1340 else {
1341 g.w_error('wasm.expr(): unhandled node: ' + node.type_name())
1342 }
1343 }
1344}
1345
1346pub fn (mut g Gen) for_in_stmt(node ast.ForInStmt) {
1347 if node.is_range {
1348 g.for_in_range(node)
1349 return
1350 }
1351
1352 cond_sym := g.table.sym(node.cond_type)
1353
1354 match cond_sym.kind {
1355 .array_fixed {
1356 g.for_in_array_fixed(node, cond_sym)
1357 }
1358 .string {
1359 g.for_in_string(node)
1360 }
1361 else {
1362 g.w_error('unsupported iter type: ${cond_sym.kind}')
1363 }
1364 }
1365}
1366
1367fn (mut g Gen) for_in_range(node ast.ForInStmt) {
1368 loop_var_type := unpack_literal_int(node.val_type)
1369 block := g.func.c_block([], [])
1370 {
1371 mut loop_var := Var{}
1372 loop_var = g.new_local(node.val_var, loop_var_type)
1373
1374 g.expr(node.cond, loop_var_type)
1375 g.set(loop_var)
1376
1377 loop := g.func.c_loop([], [])
1378 {
1379 g.loop_breakpoint_stack << LoopBreakpoint{
1380 c_continue: loop
1381 c_break: block
1382 name: node.label
1383 }
1384
1385 g.get(loop_var)
1386 g.expr(node.high, loop_var_type)
1387 wtyp := g.as_numtype(g.get_wasm_type(loop_var_type))
1388 g.func.lt(wtyp, loop_var_type.is_signed())
1389 g.func.eqz(.i32_t)
1390 g.func.c_br_if(block)
1391
1392 g.expr_stmts(node.stmts, ast.void_type)
1393
1394 g.set_prepare(loop_var)
1395 {
1396 g.get(loop_var)
1397 g.literalint(1, loop_var_type)
1398 g.func.add(wtyp)
1399 }
1400 g.set(loop_var)
1401
1402 g.func.c_br(loop)
1403 g.loop_breakpoint_stack.pop()
1404 }
1405 g.func.c_end(loop)
1406 }
1407 g.func.c_end(block)
1408}
1409
1410fn (mut g Gen) for_in_array_fixed(node ast.ForInStmt, cond_sym &ast.TypeSymbol) {
1411 info := cond_sym.info as ast.ArrayFixed
1412 array_size := info.size
1413
1414 block := g.func.c_block([], [])
1415 {
1416 idx_var := g.new_local('__idx', ast.int_type)
1417 g.literalint(0, ast.int_type)
1418 g.set(idx_var)
1419
1420 array_base := g.new_local('__array_base', node.cond_type)
1421 g.expr(node.cond, node.cond_type)
1422 g.set(array_base)
1423
1424 loop := g.func.c_loop([], [])
1425 {
1426 g.loop_breakpoint_stack << LoopBreakpoint{
1427 c_continue: loop
1428 c_break: block
1429 name: node.label
1430 }
1431
1432 // if index >= array_size
1433 g.get(idx_var)
1434 g.literalint(array_size, ast.int_type)
1435 g.func.ge(.i32_t, false)
1436 g.func.c_br_if(block)
1437
1438 // _ -> No variable in the loop
1439 if node.val_var != '_' {
1440 element_var := g.new_local(node.val_var, node.val_type)
1441
1442 // array_base + idx * element_size
1443 g.get(array_base)
1444 g.get(idx_var)
1445
1446 elem_size, _ := g.pool.type_size(node.val_type)
1447 if elem_size > 1 {
1448 g.literalint(elem_size, ast.int_type)
1449 g.func.mul(.i32_t)
1450 }
1451 g.func.add(.i32_t)
1452
1453 if g.is_pure_type(node.val_type) {
1454 g.load(node.val_type, 0)
1455 }
1456
1457 g.set(element_var)
1458 }
1459
1460 // Inside loop
1461 g.expr_stmts(node.stmts, ast.void_type)
1462
1463 // idx++
1464 g.set_prepare(idx_var)
1465 {
1466 g.get(idx_var)
1467 g.literalint(1, ast.int_type)
1468 g.func.add(.i32_t)
1469 }
1470 g.set(idx_var)
1471
1472 g.func.c_br(loop)
1473 g.loop_breakpoint_stack.pop()
1474 }
1475 g.func.c_end(loop)
1476 }
1477 g.func.c_end(block)
1478}
1479
1480fn (mut g Gen) for_in_string(node ast.ForInStmt) {
1481 block := g.func.c_block([], [])
1482 {
1483 idx_var := g.new_local('__idx', ast.int_type)
1484 g.literalint(0, ast.int_type)
1485 g.set(idx_var)
1486
1487 // String ptr
1488 string_var := g.new_local('__string', ast.string_type)
1489 g.expr(node.cond, ast.string_type)
1490 g.set(string_var)
1491
1492 len_var := g.new_local('__len', ast.int_type)
1493 g.get(string_var)
1494 g.load_field(ast.string_type, ast.int_type, 'len')
1495 g.set(len_var)
1496
1497 loop := g.func.c_loop([], [])
1498 {
1499 g.loop_breakpoint_stack << LoopBreakpoint{
1500 c_continue: loop
1501 c_break: block
1502 name: node.label
1503 }
1504
1505 // if index >= length
1506 g.get(idx_var)
1507 g.get(len_var)
1508 g.func.ge(.i32_t, false)
1509 g.func.c_br_if(block)
1510
1511 // _ -> No variable in the loop
1512 if node.val_var != '_' {
1513 char_var := g.new_local(node.val_var, node.val_type)
1514
1515 // Use string.at(idx) method to get the byte, don't reinvent the wheel
1516 g.get(string_var)
1517 g.get(idx_var)
1518 g.func.call('string.at')
1519
1520 g.set(char_var)
1521 }
1522
1523 // Inside loop
1524 g.expr_stmts(node.stmts, ast.void_type)
1525
1526 // idx++
1527 g.set_prepare(idx_var)
1528 {
1529 g.get(idx_var)
1530 g.literalint(1, ast.int_type)
1531 g.func.add(.i32_t)
1532 }
1533 g.set(idx_var)
1534
1535 g.func.c_br(loop)
1536 g.loop_breakpoint_stack.pop()
1537 }
1538 g.func.c_end(loop)
1539 }
1540 g.func.c_end(block)
1541}
1542
1543pub fn (g &Gen) file_pos(pos token.Pos) string {
1544 return '${os.to_slash(g.file_path)}:${pos.line_nr + 1}:${pos.col + 1}'
1545}
1546
1547pub fn (mut g Gen) expr_stmt(node ast.Stmt, expected ast.Type) {
1548 match node {
1549 ast.Block {
1550 g.expr_stmts(node.stmts, expected)
1551 }
1552 ast.Return {
1553 if node.exprs.len > 1 {
1554 g.set_with_multi_expr(ast.ConcatExpr{ vals: node.exprs }, g.ret, g.ret_rvars)
1555 } else if node.exprs.len == 1 {
1556 g.set_with_multi_expr(node.exprs[0], g.ret, g.ret_rvars)
1557 }
1558 g.func.c_br(g.ret_br)
1559 }
1560 ast.ExprStmt {
1561 g.expr(node.expr, expected)
1562 }
1563 ast.ForStmt {
1564 block := g.func.c_block([], [])
1565 {
1566 loop := g.func.c_loop([], [])
1567 {
1568 g.loop_breakpoint_stack << LoopBreakpoint{
1569 c_continue: loop
1570 c_break: block
1571 name: node.label
1572 }
1573
1574 if !node.is_inf {
1575 g.expr(node.cond, ast.bool_type)
1576 g.func.eqz(.i32_t)
1577 g.func.c_br_if(block) // !cond, goto end
1578 }
1579 g.expr_stmts(node.stmts, ast.void_type)
1580 g.func.c_br(loop) // goto loop
1581
1582 g.loop_breakpoint_stack.pop()
1583 }
1584 g.func.c_end(loop)
1585 }
1586 g.func.c_end(block)
1587 }
1588 ast.ForCStmt {
1589 block := g.func.c_block([], [])
1590 {
1591 if node.has_init {
1592 g.expr_stmt(node.init, ast.void_type)
1593 }
1594
1595 loop := g.func.c_loop([], [])
1596 {
1597 continue_block := g.func.c_block([], [])
1598 {
1599 g.loop_breakpoint_stack << LoopBreakpoint{
1600 c_continue: continue_block
1601 c_break: block
1602 name: node.label
1603 }
1604
1605 if node.has_cond {
1606 g.expr(node.cond, ast.bool_type)
1607 g.func.eqz(.i32_t)
1608 g.func.c_br_if(block) // !cond, goto end
1609 }
1610
1611 g.expr_stmts(node.stmts, ast.void_type)
1612
1613 g.loop_breakpoint_stack.pop()
1614 }
1615 g.func.c_end(continue_block)
1616
1617 if node.has_inc {
1618 g.expr_stmt(node.inc, ast.void_type)
1619 }
1620
1621 g.func.c_br(loop)
1622 }
1623 g.func.c_end(loop)
1624 }
1625 g.func.c_end(block)
1626 }
1627 ast.ForInStmt {
1628 g.for_in_stmt(node)
1629 }
1630 ast.BranchStmt {
1631 mut bp := g.loop_breakpoint_stack.last()
1632 if node.label != '' {
1633 for i := g.loop_breakpoint_stack.len; i > 0; {
1634 i--
1635 if g.loop_breakpoint_stack[i].name == node.label {
1636 bp = g.loop_breakpoint_stack[i]
1637 }
1638 }
1639 }
1640
1641 if node.kind == .key_break {
1642 g.func.c_br(bp.c_break)
1643 } else {
1644 g.func.c_br(bp.c_continue)
1645 }
1646 }
1647 ast.DeferStmt {
1648 v := g.new_local('__defer(${node.idx_in_fn})', ast.bool_type)
1649 g.func.i32_const(1)
1650 g.set(v)
1651 g.defer_vars << v
1652 }
1653 ast.AssertStmt {
1654 if !node.is_used {
1655 return
1656 }
1657
1658 // calls builtin functions, don't want to corrupt stack frame!
1659 g.is_leaf_function = false
1660
1661 g.expr(node.expr, ast.bool_type)
1662 g.func.eqz(.i32_t) // !expr
1663 lbl := g.func.c_if([], [])
1664 {
1665 // main.main: ${msg}
1666 // V panic: Assertion failed...
1667
1668 mut msg := '${g.file_pos(node.pos)}: fn ${g.fn_name}: ${ast.Stmt(node)}'
1669 if node.extra is ast.StringLiteral {
1670 msg += ", '${node.extra.val}'"
1671 }
1672
1673 g.expr(ast.StringLiteral{ val: msg }, ast.string_type)
1674 g.func.call('eprintln')
1675 g.expr(ast.StringLiteral{ val: 'Assertion failed...' }, ast.string_type)
1676 g.func.call('panic')
1677 }
1678 g.func.c_end(lbl)
1679 }
1680 ast.AssignStmt {
1681 if node.has_cross_var {
1682 g.w_error('complex assign statements are not implemented')
1683 // `a, b = b, a`
1684 } else {
1685 // `a := 1` | `a, b := 1, 2`
1686 // `a, b := foo()`
1687 // `a, b := if cond { 1, 2 } else { 3, 4 }`
1688
1689 is_expr_assign := node.op !in [.decl_assign, .assign]
1690
1691 // similar code from `call_expr()`
1692 // create variables or obtain them for use as rvals
1693 mut rvars := []Var{cap: node.left_types.len}
1694 for idx, rt in node.left_types {
1695 left := node.left[idx]
1696
1697 mut var := Var{}
1698 mut passed := false
1699
1700 if left is ast.Ident {
1701 if left.kind == .blank_ident {
1702 var = g.new_local('_', rt)
1703 passed = true
1704 } else if node.op == .decl_assign {
1705 var = g.new_local(left.name, rt)
1706 passed = true
1707 }
1708 }
1709
1710 if !passed {
1711 if node.op == .assign {
1712 if v := g.get_var_from_expr(left) {
1713 var = v
1714 }
1715 } else if node.op == .plus_assign && g.is_param_type(rt) {
1716 var = g.new_local('', rt)
1717 }
1718 }
1719
1720 if g.is_param_type(rt) {
1721 rvars << var
1722 }
1723 }
1724
1725 mut set := false
1726 if node.right.len == 1 {
1727 right := node.right[0]
1728 match right {
1729 ast.IfExpr {
1730 params :=
1731 node.left_types.filter(!g.is_param_type(it)).map(g.get_wasm_type(it))
1732 g.if_branch(right, right.typ, params, 0, rvars)
1733 set = true
1734 }
1735 ast.CallExpr {
1736 g.call_expr(right, 0, rvars)
1737 set = true
1738 }
1739 else {
1740 // : set = false
1741 // execute below instead
1742 }
1743 }
1744 }
1745
1746 // will never be a multi expr
1747 // assume len == 1 for left and right
1748 if is_expr_assign {
1749 left, right, typ := node.left[0], node.right[0], node.left_types[0]
1750
1751 rop := token.assign_op_to_infix_op(node.op)
1752 lhs := g.get_var_or_make_from_expr(left, typ)
1753
1754 if !g.is_pure_type(lhs.typ) {
1755 // main.struct.+
1756 name := '${g.table.get_type_name(lhs.typ)}.${rop}'
1757 g.ref(lhs)
1758 g.ref(lhs)
1759 g.expr(right, lhs.typ)
1760 g.func.call(name)
1761 } else {
1762 g.set_prepare(lhs)
1763 {
1764 g.get(lhs)
1765 g.expr(right, lhs.typ)
1766 g.infix_from_typ(lhs.typ, rop)
1767 }
1768 g.set_set(lhs)
1769 }
1770
1771 return
1772 }
1773
1774 // prepare variables using expr()
1775 // if is an rvar, set it and ignore following
1776 if !set {
1777 assert node.left.len == node.right.len
1778 mut ridx := 0
1779 for idx, right in node.right {
1780 typ := node.left_types[idx]
1781 if g.is_param_type(typ) {
1782 g.set_with_expr(right, rvars[ridx])
1783 ridx++
1784 } else {
1785 g.expr(right, typ)
1786 }
1787 }
1788 }
1789
1790 for i := node.left.len; i > 0; {
1791 i--
1792 left := node.left[i]
1793 typ := node.left_types[i]
1794
1795 if g.is_param_type(typ) {
1796 // is already set
1797 continue
1798 }
1799
1800 if left is ast.Ident {
1801 // `_ = expr`
1802 if left.kind == .blank_ident {
1803 // expression still may have side effect
1804 g.func.drop()
1805 continue
1806 }
1807 }
1808
1809 v := g.get_var_or_make_from_expr(left, typ)
1810 g.set(v)
1811 }
1812 }
1813 }
1814 ast.AsmStmt {
1815 // assumed expected == void
1816 g.asm_stmt(node)
1817 }
1818 ast.EmptyStmt {
1819 // EmptyStmt nodes are emitted by earlier compiler passes for eliminated statements.
1820 }
1821 else {
1822 g.w_error('wasm.expr_stmt(): unhandled node: ' + node.type_name())
1823 }
1824 }
1825}
1826
1827pub fn (mut g Gen) expr_stmts(stmts []ast.Stmt, expected ast.Type) {
1828 g.rvar_expr_stmts(stmts, expected, [])
1829}
1830
1831pub fn (mut g Gen) rvar_expr_stmts(stmts []ast.Stmt, expected ast.Type, existing_rvars []Var) {
1832 for idx, stmt in stmts {
1833 if idx + 1 >= stmts.len {
1834 if stmt is ast.ExprStmt {
1835 g.set_with_multi_expr(stmt.expr, expected, existing_rvars)
1836 } else {
1837 g.expr_stmt(stmt, expected)
1838 }
1839 } else {
1840 g.expr_stmt(stmt, ast.void_type)
1841 }
1842 }
1843}
1844
1845pub fn (mut g Gen) toplevel_stmt(node ast.Stmt) {
1846 match node {
1847 ast.FnDecl {
1848 g.fn_decl(node)
1849 }
1850 ast.Module {}
1851 ast.GlobalDecl {}
1852 ast.ConstDecl {}
1853 ast.Import {}
1854 ast.StructDecl {}
1855 ast.EnumDecl {}
1856 ast.TypeDecl {}
1857 else {
1858 g.w_error('wasm.toplevel_stmt(): unhandled node: ' + node.type_name())
1859 }
1860 }
1861}
1862
1863pub fn (mut g Gen) toplevel_stmts(stmts []ast.Stmt) {
1864 for stmt in stmts {
1865 g.toplevel_stmt(stmt)
1866 }
1867}
1868
1869struct Enum {
1870mut:
1871 fields map[string]i64
1872}
1873
1874fn (mut g Gen) eval_enum_field_expr(expr ast.Expr) ?i64 {
1875 match expr {
1876 ast.IntegerLiteral {
1877 return expr.val.i64()
1878 }
1879 ast.CharLiteral {
1880 runes := expr.val.runes()
1881 if runes.len == 0 {
1882 return none
1883 }
1884 return i64(runes[0])
1885 }
1886 ast.BoolLiteral {
1887 return if expr.val { i64(1) } else { i64(0) }
1888 }
1889 ast.ParExpr {
1890 return g.eval_enum_field_expr(expr.expr)
1891 }
1892 ast.CastExpr {
1893 return g.eval_enum_field_expr(expr.expr)
1894 }
1895 ast.PrefixExpr {
1896 right := g.eval_enum_field_expr(expr.right)?
1897 match expr.op {
1898 .plus {
1899 return right
1900 }
1901 .minus {
1902 return -right
1903 }
1904 .bit_not {
1905 return ~right
1906 }
1907 else {
1908 return none
1909 }
1910 }
1911 }
1912 ast.InfixExpr {
1913 left := g.eval_enum_field_expr(expr.left)?
1914 right := g.eval_enum_field_expr(expr.right)?
1915 match expr.op {
1916 .plus {
1917 return left + right
1918 }
1919 .minus {
1920 return left - right
1921 }
1922 .mul {
1923 return left * right
1924 }
1925 .div {
1926 if right == 0 {
1927 return none
1928 }
1929 return left / right
1930 }
1931 .mod {
1932 if right == 0 {
1933 return none
1934 }
1935 return left % right
1936 }
1937 .left_shift {
1938 return i64(u64(left) << int(right))
1939 }
1940 .right_shift {
1941 return left >> int(right)
1942 }
1943 .unsigned_right_shift {
1944 return i64(u64(left) >> int(right))
1945 }
1946 .amp {
1947 return left & right
1948 }
1949 .pipe {
1950 return left | right
1951 }
1952 .xor {
1953 return left ^ right
1954 }
1955 else {
1956 return none
1957 }
1958 }
1959 }
1960 ast.Ident {
1961 mut obj := expr.obj
1962 if obj !in [ast.ConstField, ast.GlobalField] {
1963 obj = expr.scope.find(expr.name) or { return none }
1964 }
1965 match mut obj {
1966 ast.ConstField {
1967 return g.eval_enum_field_expr(obj.expr)
1968 }
1969 ast.GlobalField {
1970 return g.eval_enum_field_expr(obj.expr)
1971 }
1972 else {
1973 return none
1974 }
1975 }
1976 }
1977 else {
1978 return none
1979 }
1980 }
1981}
1982
1983pub fn (mut g Gen) calculate_enum_fields() {
1984 // `enum Enum as u64` is supported
1985 for name, decl in g.table.enum_decls {
1986 mut enum_vals := Enum{}
1987 mut value := if decl.is_flag { i64(1) } else { 0 }
1988 for field in decl.fields {
1989 if field.has_expr {
1990 value = g.eval_enum_field_expr(field.expr) or {
1991 g.w_error('wasm: unsupported enum expression for `${name}.${field.name}`')
1992 }
1993 }
1994 enum_vals.fields[field.name] = value
1995 if decl.is_flag {
1996 value <<= 1
1997 } else {
1998 value++
1999 }
2000 }
2001 g.enum_vals[name] = enum_vals
2002 }
2003}
2004
2005pub fn gen(files []&ast.File, mut table ast.Table, out_name string, w_pref &pref.Preferences) {
2006 stack_top := w_pref.wasm_stack_top
2007 mut g := &Gen{
2008 table: table
2009 pref: w_pref
2010 files: files
2011 pool: serialise.new_pool(table, store_relocs: true, null_terminated: false)
2012 stack_top: stack_top
2013 data_base: calc_align(stack_top + 1, 16)
2014 }
2015 g.mod.assign_memory('memory', true, 1, none)
2016
2017 if g.pref.is_debug {
2018 g.mod.enable_debug(none)
2019 }
2020
2021 g.calculate_enum_fields()
2022 for file in g.files {
2023 g.file_path = file.path
2024 if file.errors.len > 0 {
2025 util.verror('wasm error', file.errors[0].str())
2026 }
2027 g.toplevel_stmts(file.stmts)
2028 }
2029 g.housekeeping()
2030
2031 mod := g.mod.compile()
2032
2033 if out_name == '-' {
2034 if os.is_atty(1) == 1 {
2035 eprintln('pretty printing to stdout is not implemented for the time being')
2036 eprintln('raw bytes can mess up your terminal, are you piping into a file?')
2037 exit(1)
2038 } else {
2039 print(mod.bytestr())
2040 }
2041 }
2042
2043 if out_name != '-' {
2044 os.write_file_array(out_name, mod) or { panic(err) }
2045 // The enabled feature set (Safari-15 floor plus any `-d` opt-in) drives
2046 // both validation and optimisation, so compute it once and share it.
2047 feats := g.enabled_wasm_features()
2048 if g.pref.wasm_validate {
2049 g.validate_wasm(out_name, feats)
2050 }
2051 if g.pref.is_prod {
2052 exe := $if windows { 'wasm-opt.exe' } $else { 'wasm-opt' }
2053 if rt := os.find_abs_path_of_executable(exe) {
2054 g.check_wasm_opt_version(rt)
2055 // -lmu: low memory unused, very important optimisation.
2056 // Feature flags are an explicit floor-safe allowlist (never `-all`),
2057 // so wasm-opt cannot silently introduce opcodes past the baseline.
2058 flags := binaryen_feature_flags(feats)
2059 res :=
2060 os.execute('${os.quoted_path(rt)} ${flags} -lmu -c -O4 ${os.quoted_path(out_name)} -o ${os.quoted_path(out_name)}')
2061 if res.exit_code != 0 {
2062 eprintln(res.output)
2063 g.w_error('${rt} failed, this should not happen. Report an issue with the above messages, the webassembly generated, and appropriate code.')
2064 }
2065 // Re-validate AFTER optimisation: a passing pre-opt validation is
2066 // not sufficient, wasm-opt must not have introduced any opcode past
2067 // the enabled feature floor.
2068 if g.pref.wasm_validate {
2069 g.validate_wasm(out_name, feats)
2070 }
2071 } else {
2072 g.w_error('${exe} not found! Try installing Binaryen.
2073 | Run `./cmd/tools/install_binaryen.vsh`, to download a prebuilt executable for your platform.
2074 | After that, either copy or symlink thirdparty/binaryen/bin/wasm-opt to a folder on your PATH,
2075 | or add thirdparty/binaryen/bin to your PATH.
2076 | Use `wasm-opt --version` to verify that it can be found.
2077 '.strip_margin())
2078 }
2079 }
2080 } else if g.pref.wasm_validate || g.pref.is_prod {
2081 eprintln('stdout output, cannot validate or optimise wasm')
2082 }
2083}
2084
2085// validate_wasm runs `wasm-validate` over the emitted module at `out_name`,
2086// constraining it to the enabled feature set so that any opcode past the floor
2087// fails validation instead of passing under WABT's permissive defaults.
2088fn (mut g Gen) validate_wasm(out_name string, feats []WasmFeature) {
2089 exe := $if windows { 'wasm-validate.exe' } $else { 'wasm-validate' }
2090 if rt := os.find_abs_path_of_executable(exe) {
2091 mut p := os.new_process(rt)
2092 mut vargs := wabt_validate_args(feats)
2093 vargs << out_name
2094 p.set_args(vargs)
2095 p.set_redirect_stdio()
2096 p.run()
2097 err := p.stderr_slurp()
2098 p.wait()
2099 if p.code != 0 {
2100 eprintln(err)
2101 g.w_error('validation failed, this should not happen. report an issue with the above messages, the webassembly generated, and appropriate code.')
2102 }
2103 } else {
2104 g.w_error('${exe} not found! Try installing WABT (WebAssembly Binary Toolkit). Run `./cmd/tools/install_wabt.vsh`, to download a prebuilt executable for your platform.')
2105 }
2106}
2107
2108// check_wasm_opt_version emits a soft warning if the resolved wasm-opt is older
2109// than the version the toolchain pins. It never fails the build (a hard pin with
2110// checksums belongs to the build/release pipeline, not the backend).
2111fn (mut g Gen) check_wasm_opt_version(exe string) {
2112 res := os.execute('${os.quoted_path(exe)} --version')
2113 if res.exit_code != 0 {
2114 return
2115 }
2116 // `wasm-opt --version` prints e.g. "wasm-opt version 108"
2117 ver := res.output.all_after_last(' ').trim_space().int()
2118 if ver != 0 && ver < wasm_opt_min_version {
2119 eprintln('warning: wasm-opt version ${ver} is older than the pinned minimum (${wasm_opt_min_version}); `-prod` output may differ. Run `./cmd/tools/install_binaryen.vsh` to update.')
2120 }
2121}
2122