vxx2 / vlib / v3 / gen / wasm / gen.v
2897 lines · 2764 sloc · 72.53 KB · 1f8a06056a77c1daa7ffb9e0a39d1da81d08183e
Raw
1module wasm
2
3import os
4import v3.flat
5import v3.types
6
7// gen.v is the v3 WebAssembly backend. Like the C backend's FlatGen it walks
8// the flat AST directly (WASM has structured control flow, so no SSA/relooping
9// is needed), and like the arm64 backend it emits a binary module. Generics,
10// strings-as-values, structs, arrays and maps are out of scope for now; the
11// backend targets the integer/float core plus WASI `print`/`println`.
12
13// Fixed low-memory scratch layout (bytes):
14// [0..3] nwritten result for fd_write
15// [4..11] iovec (ptr @4, len @8)
16// [12] newline byte '\n'
17// [16..47] itoa scratch buffer (BUF_END = 48, filled back-to-front)
18// [64..] interned string-literal data
19const nwritten_ptr = 0
20const iov_ptr = 4
21const nl_ptr = 12
22const buf_end = 48
23const data_base = 64
24
25pub enum WType {
26 void
27 i32
28 i64
29 f32
30 f64
31}
32
33struct FnInfo {
34 name string
35 node_id flat.NodeId
36 module string
37 file string
38 params []WType
39 ret WType
40 index int
41}
42
43struct Frame {
44 tag FrameTag
45 label string // loop label for `break label` / `continue label`, else ''
46}
47
48// VarScope snapshots the local-name bindings so an inner block or for-loop can
49// shadow an outer local and have the outer binding restored on scope exit.
50struct VarScope {
51 index map[string]int
52 wtype map[string]WType
53 unsigned map[string]bool
54 widths map[string]int
55}
56
57enum FrameTag {
58 plain
59 brk
60 cont
61}
62
63@[heap]
64pub struct Gen {
65mut:
66 a &flat.FlatAst = unsafe { nil }
67 tc &types.TypeChecker = unsafe { nil }
68 used_fns map[string]bool
69 mod &Module = unsafe { nil }
70 // per-function state
71 cur Code
72 local_types []u8
73 nparams int
74 var_index map[string]int
75 var_wtype map[string]WType
76 var_unsigned map[string]bool
77 var_widths map[string]int // sub-32-bit int locals: 8 or 16; else 32
78 frames []Frame
79 pending_label string // label of a preceding `label:` marker, for the next loop
80 cur_ret WType
81 cur_fn_module string
82 cur_fn_file string
83 // module-wide; keyed by qualified function name (see qualified_fn_key)
84 fn_index map[string]int
85 fn_ret map[string]WType
86 fn_params map[string][]WType
87 file_aliases map[string]map[string]string // file -> (import alias -> full import path)
88 import_paths []string // every `import a.b.c` path (full)
89 module_imports map[string][]string // module path -> imported module paths ('' = main)
90 global_index map[string]int // __global name -> wasm global index
91 global_wtype map[string]WType
92 global_unsigned map[string]bool
93 global_widths map[string]int
94 data_pool []u8
95 data_off map[string]int
96 uses_print bool
97 write_idx int
98 print_int_idx int
99 has_main bool
100 init_fns []int // function indices of init() entry points, in call order
101 warnings []string
102}
103
104pub fn Gen.new(a &flat.FlatAst, tc &types.TypeChecker, used_fns map[string]bool) &Gen {
105 return &Gen{
106 a: a
107 tc: tc
108 used_fns: used_fns
109 mod: Module.new()
110 }
111}
112
113// gen builds the whole module from the flat AST.
114pub fn (mut g Gen) gen() {
115 g.collect_imports()
116 g.collect_globals()
117 user_fns := g.collect_user_fns()
118 g.uses_print = g.detect_print(user_fns)
119
120 // 1. function-index assignment (imports first, then helpers, then user fns).
121 mut fd_write_idx := -1
122 if g.uses_print {
123 ft := g.mod.add_type([valtype_i32, valtype_i32, valtype_i32, valtype_i32], [
124 valtype_i32,
125 ])
126 fd_write_idx = g.mod.add_import_func('wasi_snapshot_preview1', 'fd_write', ft)
127 }
128 mut defined := 0
129 if g.uses_print {
130 g.write_idx = g.mod.reserve_func_index(defined)
131 defined++
132 g.print_int_idx = g.mod.reserve_func_index(defined)
133 defined++
134 }
135 mut module_init := map[string]string{} // module path -> its init fn key
136 for i, f in user_fns {
137 idx := g.mod.reserve_func_index(defined + i)
138 key := qualified_fn_key(f.module, f.name)
139 g.fn_index[key] = idx
140 g.fn_ret[key] = f.ret
141 g.fn_params[key] = f.params
142 if f.name == 'main' && (f.module == '' || f.module == 'main') {
143 g.has_main = true
144 } else if f.name == 'init' && f.params.len == 0 {
145 module_init[f.module] = key
146 }
147 }
148 // Order init calls by the import graph (dependencies first), so a module's
149 // init observes its imports' startup; main's init runs last.
150 mut visited := map[string]bool{}
151 g.order_module_inits('', module_init, mut visited)
152
153 // 2. helper bodies (must be added in the same order as reserved above).
154 if g.uses_print {
155 g.emit_write_helper(fd_write_idx)
156 g.emit_print_int_helper()
157 }
158
159 // 3. user function bodies.
160 for f in user_fns {
161 g.emit_user_fn(f)
162 }
163
164 // 4. WASI `_start` entry that calls main.
165 mut start_idx := -1
166 if g.has_main {
167 start_idx = g.emit_start()
168 }
169
170 // 5. exports + memory sizing + data. WASM export names must be unique, so
171 // reserve the runtime names and skip any user function that would clash.
172 g.mod.add_export('memory', export_mem, 0)
173 mut used_exports := map[string]bool{}
174 used_exports['memory'] = true
175 used_exports['_start'] = true
176 for f in user_fns {
177 ename := export_fn_name(f.module, f.name)
178 if ename in used_exports {
179 g.warn('not exporting ${f.name}: export name `${ename}` is already in use')
180 continue
181 }
182 used_exports[ename] = true
183 g.mod.add_export(ename, export_func, g.fn_index[qualified_fn_key(f.module, f.name)])
184 }
185 if start_idx >= 0 {
186 g.mod.add_export('_start', export_func, start_idx)
187 }
188 if g.data_pool.len > 0 {
189 g.mod.add_data(data_base, g.data_pool)
190 }
191 if g.uses_print {
192 g.mod.add_data(nl_ptr, [u8(0x0a)])
193 }
194 total := data_base + g.data_pool.len
195 pages := if total <= 0x10000 { 2 } else { (total + 0xffff) / 0x10000 + 1 }
196 g.mod.set_mem_min(pages)
197}
198
199pub fn (g &Gen) warnings_list() []string {
200 return g.warnings
201}
202
203// write serializes the module to `path`.
204pub fn (mut g Gen) write(path string) ! {
205 bytes := g.mod.compile()
206 os.write_file_array(path, bytes)!
207}
208
209// ---- function collection ----
210
211fn (mut g Gen) collect_user_fns() []FnInfo {
212 // 1. Gather every candidate (primitive-signature, non-generic) function in
213 // user code, keyed by qualified name, preserving source order.
214 mut candidates := map[string]FnInfo{}
215 mut order := []string{}
216 mut cur_module := ''
217 mut cur_file := ''
218 mut cur_module_path := ''
219 for i in 0 .. g.a.nodes.len {
220 node := g.a.nodes[i]
221 match node.kind {
222 .file {
223 cur_module = ''
224 cur_file = node.value
225 cur_module_path = ''
226 }
227 .module_decl {
228 cur_module = node.value
229 // Only files that declare a non-main module are imported modules;
230 // resolve their import path. Main files (no decl or `module main`)
231 // must never be classified by a directory-name coincidence.
232 cur_module_path = if cur_module != '' && cur_module != 'main' {
233 g.module_path_for_file(cur_file)
234 } else {
235 ''
236 }
237 }
238 .fn_decl {
239 if i < g.a.user_code_start {
240 continue
241 }
242 if node.generic_params.len > 0 || node.value == '' {
243 continue
244 }
245 // Prefer the import path (distinguishes same-leaf modules); fall
246 // back to the module declaration name if no import matched.
247 mod_key := if cur_module_path != '' {
248 cur_module_path
249 } else if cur_module != '' && cur_module != 'main' {
250 cur_module
251 } else {
252 ''
253 }
254 if fi := g.fn_info(node, flat.NodeId(i), mod_key, cur_file) {
255 key := qualified_fn_key(mod_key, node.value)
256 if key !in candidates {
257 candidates[key] = fi
258 order << key
259 }
260 }
261 }
262 else {}
263 }
264 }
265
266 // 2. Seed with every main-module candidate (all of them are compiled and
267 // exported), then BFS-follow direct calls into imported-module
268 // candidates. This deliberately ignores markused reachability: builtins
269 // such as strconv__format_int are intercepted by the print path and
270 // never emitted as real calls, so their numeric helpers must not be
271 // pulled in.
272 mut reached := map[string]bool{}
273 mut work := []string{}
274 for key in order {
275 f := candidates[key]
276 if f.module == '' || f.module == 'main' {
277 reached[key] = true
278 work << key
279 }
280 }
281 // `init` functions are entry points (run before main), like the C path's
282 // _vinit. Every imported module runs its init regardless of whether any of
283 // its other functions are called, so seed the init of main and of every
284 // imported module, then follow the calls those inits make.
285 mut active := map[string]bool{}
286 active[''] = true
287 for p in g.import_paths {
288 active[p] = true
289 }
290 for key in order {
291 f := candidates[key]
292 if key !in reached && f.name == 'init' && f.params.len == 0 && f.module in active {
293 reached[key] = true
294 work << key
295 }
296 }
297 for work.len > 0 {
298 f := candidates[work.pop()]
299 for callee_key in g.called_candidate_keys(f, candidates) {
300 if callee_key !in reached {
301 reached[callee_key] = true
302 work << callee_key
303 }
304 }
305 }
306
307 // 3. Emit reached functions in source order for deterministic indices.
308 mut out := []FnInfo{}
309 for key in order {
310 if reached[key] {
311 out << candidates[key]
312 }
313 }
314 return out
315}
316
317// called_candidate_keys returns the qualified keys of the candidate functions
318// directly called inside f's body (calls to non-candidates such as intercepted
319// builtins are ignored).
320fn (g &Gen) called_candidate_keys(f FnInfo, candidates map[string]FnInfo) []string {
321 mut keys := []string{}
322 g.collect_call_keys(g.a.nodes[int(f.node_id)], f.module, f.file, candidates, mut keys)
323 return keys
324}
325
326fn (g &Gen) collect_call_keys(node flat.Node, cur_mod string, cur_file string, candidates map[string]FnInfo, mut keys []string) {
327 if node.kind == .call && node.children_count > 0 {
328 for key in g.resolve_call_keys(g.a.child_node(&node, 0), cur_mod, cur_file) {
329 if key in candidates {
330 keys << key
331 break
332 }
333 }
334 }
335 for i in 0 .. node.children_count {
336 cid := g.a.child(&node, i)
337 if int(cid) >= 0 {
338 g.collect_call_keys(g.a.nodes[int(cid)], cur_mod, cur_file, candidates, mut keys)
339 }
340 }
341}
342
343// fn_info extracts a compilable signature, or none if the function uses types
344// the backend does not handle yet (strings, structs, arrays, pointers, ...).
345fn (mut g Gen) fn_info(node flat.Node, id flat.NodeId, module_ string, file string) ?FnInfo {
346 mut params := []WType{}
347 for i in 0 .. node.children_count {
348 p := g.a.child_node(&node, i)
349 if p.kind != .param || p.value.len == 0 {
350 continue
351 }
352 pt := g.tc.parse_type(p.typ)
353 w := prim_wtype(pt) or { return none }
354 params << w
355 }
356 mut ret := WType.void
357 if node.typ.len > 0 && node.typ != 'void' {
358 rt := g.tc.parse_type(node.typ)
359 ret = prim_wtype(rt) or { return none }
360 }
361 return FnInfo{
362 name: node.value
363 node_id: id
364 module: module_
365 file: file
366 params: params
367 ret: ret
368 }
369}
370
371fn (mut g Gen) detect_print(fns []FnInfo) bool {
372 for f in fns {
373 node := g.a.nodes[int(f.node_id)]
374 if g.subtree_has_print(node) {
375 return true
376 }
377 }
378 return false
379}
380
381fn (g &Gen) subtree_has_print(node flat.Node) bool {
382 if node.kind == .call {
383 callee := g.a.child_node(&node, 0)
384 if callee.kind == .ident && callee.value in ['println', 'print', 'eprintln', 'eprint'] {
385 return true
386 }
387 }
388 for i in 0 .. node.children_count {
389 child_id := g.a.child(&node, i)
390 if int(child_id) < 0 {
391 continue
392 }
393 if g.subtree_has_print(g.a.nodes[int(child_id)]) {
394 return true
395 }
396 }
397 return false
398}
399
400// ---- user function emission ----
401
402fn (mut g Gen) emit_user_fn(f FnInfo) {
403 node := g.a.nodes[int(f.node_id)]
404 g.cur = Code{}
405 g.local_types = []u8{}
406 g.var_index = map[string]int{}
407 g.var_wtype = map[string]WType{}
408 g.var_unsigned = map[string]bool{}
409 g.var_widths = map[string]int{}
410 g.frames = []Frame{}
411 g.pending_label = ''
412 g.cur_ret = f.ret
413 g.cur_fn_module = f.module
414 g.cur_fn_file = f.file
415
416 // register params as locals 0..n-1
417 mut pi := 0
418 mut wparams := []u8{}
419 for i in 0 .. node.children_count {
420 p := g.a.child_node(&node, i)
421 if p.kind != .param || p.value.len == 0 {
422 continue
423 }
424 w := f.params[pi]
425 pt := g.tc.parse_type(p.typ)
426 g.var_index[p.value] = pi
427 g.var_wtype[p.value] = w
428 g.var_unsigned[p.value] = type_is_unsigned(pt)
429 g.var_widths[p.value] = narrow_width(pt)
430 wparams << wt_valtype(w)
431 pi++
432 }
433 g.nparams = pi
434
435 // body: non-param children
436 for i in 0 .. node.children_count {
437 child := g.a.child_node(&node, i)
438 if child.kind == .param {
439 continue
440 }
441 g.gen_stmt(g.a.child(&node, i))
442 }
443 // A non-void function returns via explicit `return`s; if control still
444 // reaches the end (e.g. the body is `if c { return ... } else { return
445 // ... }`), the fall-through is unreachable. `unreachable` keeps the stack
446 // type-valid without inventing a bogus result value.
447 if f.ret != .void {
448 g.cur.raw(0x00) // unreachable
449 }
450 g.cur.end()
451
452 mut results := []u8{}
453 if f.ret != .void {
454 results << wt_valtype(f.ret)
455 }
456 type_idx := g.mod.add_type(wparams, results)
457 g.mod.add_func(type_idx, g.local_types, g.cur.bytes)
458}
459
460fn (mut g Gen) emit_start() int {
461 mut c := Code{}
462 // Run init() entry points before main, matching the C path's _vinit.
463 for init_idx in g.init_fns {
464 c.call(init_idx)
465 }
466 if main_idx := g.fn_index['main'] {
467 c.call(main_idx)
468 }
469 c.end()
470 type_idx := g.mod.add_type([], [])
471 return g.mod.add_func(type_idx, [], c.bytes)
472}
473
474// new_local allocates a fresh local beyond the params and records its type.
475fn (mut g Gen) new_local(name string, w WType, unsigned bool, width int) int {
476 idx := g.nparams + g.local_types.len
477 g.local_types << wt_valtype(w)
478 g.var_index[name] = idx
479 g.var_wtype[name] = w
480 g.var_unsigned[name] = unsigned
481 g.var_widths[name] = width
482 return idx
483}
484
485// alloc_temp allocates an anonymous function-level local (e.g. for the shift
486// over-width guard) and returns its index.
487fn (mut g Gen) alloc_temp(w WType) int {
488 idx := g.nparams + g.local_types.len
489 g.local_types << wt_valtype(w)
490 return idx
491}
492
493// snapshot_scope / restore_scope save and restore local-name bindings so inner
494// declarations that shadow an outer local do not leak past their scope.
495fn (g &Gen) snapshot_scope() VarScope {
496 return VarScope{
497 index: g.var_index.clone()
498 wtype: g.var_wtype.clone()
499 unsigned: g.var_unsigned.clone()
500 widths: g.var_widths.clone()
501 }
502}
503
504fn (mut g Gen) restore_scope(s VarScope) {
505 g.var_index = s.index.clone()
506 g.var_wtype = s.wtype.clone()
507 g.var_unsigned = s.unsigned.clone()
508 g.var_widths = s.widths.clone()
509}
510
511// ---- statements ----
512
513fn (mut g Gen) gen_stmt(id flat.NodeId) {
514 if int(id) < 0 {
515 return
516 }
517 node := g.a.nodes[int(id)]
518 if node.kind == .label_stmt {
519 // `outer:` marker; attach the label to the next loop. Goto-only markers
520 // (outer_break/outer_continue) aren't consumed by a loop and are dropped.
521 g.pending_label = node.value
522 return
523 }
524 loop_label := g.pending_label
525 g.pending_label = ''
526 match node.kind {
527 .fn_decl, .c_fn_decl {}
528 .block {
529 saved := g.snapshot_scope()
530 for i in 0 .. node.children_count {
531 g.gen_stmt(g.a.child(&node, i))
532 }
533 g.restore_scope(saved)
534 }
535 .expr_stmt {
536 child_id := g.a.child(&node, 0)
537 w := g.gen_expr(child_id)
538 if w != .void {
539 g.cur.drop()
540 }
541 }
542 .decl_assign {
543 g.gen_decl_assign(node)
544 }
545 .assign, .selector_assign {
546 g.gen_assign(node)
547 }
548 .return_stmt {
549 if node.children_count > 0 {
550 g.gen_expr_as(g.a.child(&node, 0), g.cur_ret)
551 }
552 g.cur.ret()
553 }
554 .if_expr {
555 g.gen_if(node)
556 }
557 .for_stmt {
558 g.gen_for(node, loop_label)
559 }
560 .break_stmt {
561 g.gen_branch(node, .brk)
562 }
563 .continue_stmt {
564 g.gen_branch(node, .cont)
565 }
566 .assert_stmt {
567 g.gen_assert(node)
568 }
569 else {
570 g.warn('unsupported statement: ${node.kind}')
571 }
572 }
573}
574
575fn (mut g Gen) gen_decl_assign(node flat.Node) {
576 // Multi-declaration (`x, y := a, b`): evaluate every RHS against the outer
577 // scope before binding any new name, so a shadowing `x, y := x+1, x+2`
578 // reads the outer x for both initializers.
579 if node.children_count > 2 {
580 g.gen_multi_decl(node)
581 return
582 }
583 lhs := g.a.child_node(&node, 0)
584 rhs_id := g.a.child(&node, 1)
585 w := g.expr_wtype(rhs_id, node.typ)
586 uns := g.decl_is_unsigned(rhs_id, node.typ)
587 width := g.decl_width(rhs_id, node.typ)
588 if lhs.kind == .ident {
589 // Emit the initializer before binding the new name, so a shadowing
590 // `x := x + 1` reads the outer x rather than the fresh zero local.
591 g.gen_expr_as(rhs_id, w)
592 idx := g.new_local(lhs.value, w, uns, width)
593 g.narrow_for_local(lhs.value)
594 g.cur.local_set(idx)
595 }
596}
597
598fn (mut g Gen) gen_multi_decl(node flat.Node) {
599 mut temps := []int{}
600 mut names := []string{}
601 mut ws := []WType{}
602 mut unss := []bool{}
603 mut widths := []int{}
604 // Phase 1: buffer every RHS into a temporary (outer bindings still in scope).
605 mut i := 0
606 for i + 1 < node.children_count {
607 lhs := g.a.child_node(&node, i)
608 rhs_id := g.a.child(&node, i + 1)
609 if lhs.kind == .ident {
610 w := g.expr_wtype(rhs_id, node.typ)
611 g.gen_expr_as(rhs_id, w)
612 t := g.alloc_temp(w)
613 g.cur.local_set(t)
614 temps << t
615 names << lhs.value
616 ws << w
617 unss << g.decl_is_unsigned(rhs_id, node.typ)
618 widths << g.decl_width(rhs_id, node.typ)
619 }
620 i += 2
621 }
622 // Phase 2: bind each new local and store its buffered value.
623 for k, name in names {
624 idx := g.new_local(name, ws[k], unss[k], widths[k])
625 g.cur.local_get(temps[k])
626 g.narrow_for_local(name)
627 g.cur.local_set(idx)
628 }
629}
630
631fn (mut g Gen) gen_assign(node flat.Node) {
632 // Multi-target assignment (`a, b = b, a`): V evaluates every RHS before any
633 // store, so buffer them in temporaries first. The transformer normally
634 // pre-decomposes these into temp-backed single assigns, so this path is a
635 // safeguard rather than the common case.
636 if node.op == .assign && node.children_count > 2 {
637 g.gen_multi_assign(node)
638 return
639 }
640 mut i := 0
641 for i + 1 < node.children_count {
642 lhs := g.a.child_node(&node, i)
643 rhs_id := g.a.child(&node, i + 1)
644 if lhs.kind == .ident && lhs.value == '_' {
645 // Blank assignment `_ = expr`: evaluate for side effects, discard.
646 w := g.gen_expr(rhs_id)
647 if w != .void {
648 g.cur.drop()
649 }
650 } else if lhs.kind == .ident && lhs.value in g.var_index {
651 idx := g.var_index[lhs.value]
652 w := g.var_wtype[lhs.value]
653 if node.op == .assign {
654 g.gen_expr_as(rhs_id, w)
655 } else {
656 op := compound_to_op(node.op)
657 signed := !g.var_unsigned[lhs.value]
658 g.cur.local_get(idx)
659 if op in [.left_shift, .right_shift, .right_shift_unsigned] {
660 // Keep the lhs width; the count may be wider (`x <<= u64(n)`).
661 if op == .right_shift_unsigned && w == .i32 {
662 // `>>>=` shifts the narrow bit pattern; a narrow signed local
663 // is stored sign-extended, so mask it to its width first.
664 lw := g.var_widths[lhs.value]
665 if lw == 8 || lw == 16 {
666 g.emit_narrow(lw, true)
667 }
668 }
669 g.emit_shift_with_count(op, w, rhs_id, signed)
670 } else {
671 g.gen_expr_as(rhs_id, w)
672 g.emit_arith(op, w, signed)
673 }
674 }
675 g.narrow_for_local(lhs.value)
676 g.cur.local_set(idx)
677 } else if lhs.kind == .ident && g.global_key(lhs.value) != '' {
678 gkey := g.global_key(lhs.value)
679 gidx := g.global_index[gkey]
680 w := g.global_wtype[gkey]
681 if node.op == .assign {
682 g.gen_expr_as(rhs_id, w)
683 } else {
684 op := compound_to_op(node.op)
685 signed := !g.global_unsigned[gkey]
686 g.cur.global_get(gidx)
687 if op in [.left_shift, .right_shift, .right_shift_unsigned] {
688 if op == .right_shift_unsigned && w == .i32 {
689 lw := g.global_widths[gkey]
690 if lw == 8 || lw == 16 {
691 g.emit_narrow(lw, true)
692 }
693 }
694 g.emit_shift_with_count(op, w, rhs_id, signed)
695 } else {
696 g.gen_expr_as(rhs_id, w)
697 g.emit_arith(op, w, signed)
698 }
699 }
700 g.narrow_for_global(gkey)
701 g.cur.global_set(gidx)
702 } else {
703 g.warn('unsupported assign target')
704 }
705 i += 2
706 }
707}
708
709// gen_multi_assign evaluates all RHS values into temporaries first, then stores
710// each into its target, so `a, b = b, a` swaps instead of clobbering.
711fn (mut g Gen) gen_multi_assign(node flat.Node) {
712 mut temps := []int{}
713 mut targets := []string{} // local name, or global key when is_global
714 mut is_global := []bool{}
715 mut j := 0
716 for j + 1 < node.children_count {
717 lhs := g.a.child_node(&node, j)
718 rhs_id := g.a.child(&node, j + 1)
719 if lhs.kind == .ident && lhs.value != '_' && lhs.value in g.var_index {
720 w := g.var_wtype[lhs.value]
721 g.gen_expr_as(rhs_id, w)
722 t := g.alloc_temp(w)
723 g.cur.local_set(t)
724 temps << t
725 targets << lhs.value
726 is_global << false
727 } else if lhs.kind == .ident && lhs.value != '_' && g.global_key(lhs.value) != '' {
728 gkey := g.global_key(lhs.value)
729 w := g.global_wtype[gkey]
730 g.gen_expr_as(rhs_id, w)
731 t := g.alloc_temp(w)
732 g.cur.local_set(t)
733 temps << t
734 targets << gkey
735 is_global << true
736 } else {
737 // blank or unsupported target: evaluate for side effects, discard
738 rw := g.gen_expr(rhs_id)
739 if rw != .void {
740 g.cur.drop()
741 }
742 }
743 j += 2
744 }
745 for k, name in targets {
746 g.cur.local_get(temps[k])
747 if is_global[k] {
748 g.narrow_for_global(name)
749 g.cur.global_set(g.global_index[name])
750 } else {
751 g.narrow_for_local(name)
752 g.cur.local_set(g.var_index[name])
753 }
754 }
755}
756
757// narrow_for_local masks/sign-extends the value on the stack to the local's
758// declared sub-32-bit width before it is stored.
759fn (mut g Gen) narrow_for_local(name string) {
760 width := g.var_widths[name]
761 if width == 8 || width == 16 {
762 g.emit_narrow(width, g.var_unsigned[name])
763 }
764}
765
766fn (mut g Gen) narrow_for_global(key string) {
767 width := g.global_widths[key]
768 if width == 8 || width == 16 {
769 g.emit_narrow(width, g.global_unsigned[key])
770 }
771}
772
773// global_key resolves a `__global` referenced by bare name from the current
774// function to its metadata key, preferring a same-module global and falling
775// back to a main-module one, mirroring resolve_call_keys. Globals are keyed by
776// module-qualified name, so two modules declaring the same name stay distinct.
777// Returns '' when no such global exists.
778fn (g &Gen) global_key(name string) string {
779 if g.cur_fn_module != '' && g.cur_fn_module != 'main' {
780 k := '${g.cur_fn_module}.${name}'
781 if k in g.global_index {
782 return k
783 }
784 }
785 if name in g.global_index {
786 return name
787 }
788 return ''
789}
790
791// decl_width resolves the declared sub-32-bit width (8/16) of an initializer.
792fn (mut g Gen) decl_width(rhs_id flat.NodeId, fallback_typ string) int {
793 w := narrow_width(g.tc.resolve_type(rhs_id))
794 if w != 32 {
795 return w
796 }
797 if fallback_typ.len > 0 {
798 return narrow_width(g.tc.parse_type(fallback_typ))
799 }
800 return 32
801}
802
803fn (mut g Gen) gen_if(node flat.Node) {
804 cond_id := g.a.child(&node, 0)
805 g.gen_expr_as_bool(cond_id)
806 g.cur.if_void()
807 g.frames << Frame{
808 tag: .plain
809 }
810 then_block := g.a.child_node(&node, 1)
811 saved_then := g.snapshot_scope()
812 for i in 0 .. then_block.children_count {
813 g.gen_stmt(g.a.child(then_block, i))
814 }
815 g.restore_scope(saved_then)
816 if node.children_count > 2 {
817 else_id := g.a.child(&node, 2)
818 if int(else_id) >= 0 {
819 else_node := g.a.nodes[int(else_id)]
820 g.cur.else_()
821 saved_else := g.snapshot_scope()
822 if else_node.kind == .if_expr {
823 g.gen_if(else_node)
824 } else if else_node.kind == .block {
825 for i in 0 .. else_node.children_count {
826 g.gen_stmt(g.a.child(&else_node, i))
827 }
828 }
829 g.restore_scope(saved_else)
830 }
831 }
832 g.cur.end()
833 g.frames.pop()
834}
835
836fn (mut g Gen) gen_for(node flat.Node, label string) {
837 // The initializer and body are scoped to the loop; restore outer bindings
838 // afterwards so a shadowing `for i := ...` or body-local does not leak.
839 saved := g.snapshot_scope()
840 defer {
841 g.restore_scope(saved)
842 }
843 init_node := g.a.child_node(&node, 0)
844 cond_id := g.a.child(&node, 1)
845 cond_node := g.a.nodes[int(cond_id)]
846 post_node := g.a.child_node(&node, 2)
847
848 if init_node.kind != .empty {
849 g.gen_stmt(g.a.child(&node, 0))
850 }
851 // Bindings visible to the condition and post: the loop variable from the
852 // initializer, but no body-local shadows.
853 header_scope := g.snapshot_scope()
854 g.cur.block_void() // break target
855 g.frames << Frame{
856 tag: .brk
857 label: label
858 }
859 g.cur.loop_void() // back-edge target
860 g.frames << Frame{
861 tag: .plain
862 }
863
864 if cond_node.kind != .empty {
865 g.gen_expr_as_bool(cond_id)
866 g.cur.raw(0x45) // i32.eqz
867 g.cur.br_if(g.depth_to(.brk))
868 }
869
870 g.cur.block_void() // continue target
871 g.frames << Frame{
872 tag: .cont
873 label: label
874 }
875 for i in 3 .. node.children_count {
876 g.gen_stmt(g.a.child(&node, i))
877 }
878 g.cur.end()
879 g.frames.pop()
880
881 // Drop body-local shadows so the post statement rebinds to the loop var.
882 g.restore_scope(header_scope)
883 if post_node.kind != .empty {
884 g.gen_stmt(g.a.child(&node, 2))
885 }
886 g.cur.br(g.depth_to_loop())
887 g.cur.end() // loop
888 g.frames.pop()
889 g.cur.end() // block
890 g.frames.pop()
891}
892
893fn (mut g Gen) gen_branch(node flat.Node, tag FrameTag) {
894 if node.value.len > 0 {
895 // `break label` / `continue label`: target the labeled loop's frame.
896 g.cur.br(g.depth_to_labeled(tag, node.value))
897 } else {
898 g.cur.br(g.depth_to(tag))
899 }
900}
901
902fn (mut g Gen) gen_assert(node flat.Node) {
903 if node.children_count == 0 {
904 return
905 }
906 g.gen_expr_as_bool(g.a.child(&node, 0))
907 g.cur.raw(0x45) // i32.eqz
908 g.cur.if_void()
909 g.cur.raw(0x00) // unreachable -> trap
910 g.cur.end()
911}
912
913// depth_to returns the relative br depth to the nearest enclosing frame tagged
914// `tag`, counted from the current (innermost) frame.
915fn (g &Gen) depth_to(tag FrameTag) int {
916 for i := g.frames.len - 1; i >= 0; i-- {
917 if g.frames[i].tag == tag {
918 return (g.frames.len - 1) - i
919 }
920 }
921 return 0
922}
923
924// depth_to_labeled finds the nearest frame with the given tag and loop label
925// (for `break label` / `continue label`), falling back to the nearest tag.
926fn (g &Gen) depth_to_labeled(tag FrameTag, label string) int {
927 for i := g.frames.len - 1; i >= 0; i-- {
928 if g.frames[i].tag == tag && g.frames[i].label == label {
929 return (g.frames.len - 1) - i
930 }
931 }
932 return g.depth_to(tag)
933}
934
935// depth_to_loop returns the depth of the nearest `.plain` loop frame. Loop and
936// if frames are both `.plain`; the back-edge target is the most recent one that
937// is not the continue block, which by construction is the loop directly below
938// the continue frame. We resolve it as the nearest `.plain` frame.
939fn (g &Gen) depth_to_loop() int {
940 for i := g.frames.len - 1; i >= 0; i-- {
941 if g.frames[i].tag == .plain {
942 return (g.frames.len - 1) - i
943 }
944 }
945 return 0
946}
947
948// ---- expressions ----
949
950// gen_expr emits code that pushes the value of `id` and returns its WType
951// (`.void` if nothing was pushed).
952fn (mut g Gen) gen_expr(id flat.NodeId) WType {
953 if int(id) < 0 {
954 g.cur.i32_const(0)
955 return .i32
956 }
957 node := g.a.nodes[int(id)]
958 match node.kind {
959 .int_literal {
960 val := parse_int_literal(node.value)
961 mut w := g.expr_wtype(id, '')
962 // Promote to i64 when the literal does not fit in i32: the checker
963 // types untyped literals as plain `int`, which would truncate.
964 if w == .i32 && (val > 2147483647 || val < -2147483648) {
965 w = .i64
966 }
967 if w == .i64 {
968 g.cur.i64_const(val)
969 return .i64
970 }
971 g.cur.i32_const(val)
972 return .i32
973 }
974 .bool_literal {
975 g.cur.i32_const(if node.value == 'true' { 1 } else { 0 })
976 return .i32
977 }
978 .char_literal {
979 // `A` etc. is a scalar (u8/rune); emit its code value.
980 g.cur.i32_const(char_literal_value(node.value))
981 return .i32
982 }
983 .float_literal {
984 w := g.expr_wtype(id, '')
985 fv := node.value.replace('_', '').f64()
986 if w == .f32 {
987 g.cur.f32_const(f32(fv))
988 return .f32
989 }
990 g.cur.f64_const(fv)
991 return .f64
992 }
993 .ident {
994 if node.value in g.var_index {
995 g.cur.local_get(g.var_index[node.value])
996 return g.var_wtype[node.value]
997 }
998 gkey := g.global_key(node.value)
999 if gkey != '' {
1000 g.cur.global_get(g.global_index[gkey])
1001 return g.global_wtype[gkey]
1002 }
1003 // Top-level consts are inlined from their recorded value expression.
1004 if cid := g.const_value_node(node.value) {
1005 return g.gen_expr(cid)
1006 }
1007 g.warn('unknown ident: ${node.value}')
1008 g.cur.i32_const(0)
1009 return .i32
1010 }
1011 .paren {
1012 return g.gen_expr(g.a.child(&node, 0))
1013 }
1014 .cast_expr {
1015 return g.gen_cast(node)
1016 }
1017 .infix {
1018 return g.gen_infix(id, node)
1019 }
1020 .prefix {
1021 return g.gen_prefix(id, node)
1022 }
1023 .postfix {
1024 g.gen_postfix(node)
1025 return .void
1026 }
1027 .call {
1028 return g.gen_call(node)
1029 }
1030 .if_expr {
1031 return g.gen_if_value(id, node)
1032 }
1033 .selector {
1034 // An imported-module const is accessed as `mod.name`; inline it.
1035 if cid := g.selector_const_node(node) {
1036 return g.gen_expr(cid)
1037 }
1038 g.warn('unsupported selector: ${node.value}')
1039 g.cur.i32_const(0)
1040 return .i32
1041 }
1042 else {
1043 g.warn('unsupported expr: ${node.kind}')
1044 g.cur.i32_const(0)
1045 return .i32
1046 }
1047 }
1048}
1049
1050// selector_const_node resolves a `module.name` selector to a top-level const's
1051// value expression. v3 keys module consts by the module declaration name, so we
1052// try the literal base, the resolved import path and its last component, then
1053// the bare field name as an unambiguous suffix.
1054fn (g &Gen) selector_const_node(node flat.Node) ?flat.NodeId {
1055 if node.children_count == 0 {
1056 return none
1057 }
1058 base := g.a.child_node(&node, 0)
1059 if base.kind != .ident {
1060 return none
1061 }
1062 field := node.value
1063 real := g.resolve_module_alias(g.cur_fn_file, base.value)
1064 for modname in [base.value, real, real.all_after_last('.')] {
1065 if cid := g.const_value_node('${modname}.${field}') {
1066 return cid
1067 }
1068 }
1069 return g.const_value_node(field)
1070}
1071
1072// const_value_node resolves an identifier to the value expression of a
1073// top-level const, if any (numeric/bool consts are inlined at the use site).
1074fn (g &Gen) const_value_node(name string) ?flat.NodeId {
1075 if cid := g.tc.const_exprs[name] {
1076 return cid
1077 }
1078 if key := g.tc.const_suffixes[name] {
1079 if key.len > 0 {
1080 if cid := g.tc.const_exprs[key] {
1081 return cid
1082 }
1083 }
1084 }
1085 return none
1086}
1087
1088// gen_if_value emits an `if` used as an expression, leaving the selected
1089// branch's value on the stack (e.g. `x := if c { 1 } else { 2 }`).
1090fn (mut g Gen) gen_if_value(id flat.NodeId, node flat.Node) WType {
1091 result_w := g.expr_wtype(id, '')
1092 g.gen_if_value_typed(node, result_w)
1093 return result_w
1094}
1095
1096fn (mut g Gen) gen_if_value_typed(node flat.Node, result_w WType) {
1097 g.gen_expr_as_bool(g.a.child(&node, 0))
1098 g.cur.raw(0x04) // if
1099 g.cur.raw(wt_valtype(result_w)) // result type
1100 g.frames << Frame{
1101 tag: .plain
1102 }
1103 g.gen_block_value(g.a.child_node(&node, 1), result_w)
1104 g.cur.else_()
1105 if node.children_count > 2 {
1106 else_id := g.a.child(&node, 2)
1107 else_node := g.a.nodes[int(else_id)]
1108 if else_node.kind == .if_expr {
1109 g.gen_if_value_typed(else_node, result_w)
1110 } else if else_node.kind == .block {
1111 g.gen_block_value(else_node, result_w)
1112 } else {
1113 g.push_zero(result_w)
1114 }
1115 } else {
1116 // A value `if` requires an else; keep the stack typed otherwise.
1117 g.push_zero(result_w)
1118 }
1119 g.cur.end()
1120 g.frames.pop()
1121}
1122
1123// gen_block_value emits a block's statements, leaving the value of its trailing
1124// expression on the stack, coerced to result_w.
1125fn (mut g Gen) gen_block_value(block flat.Node, result_w WType) {
1126 saved := g.snapshot_scope()
1127 n := int(block.children_count)
1128 for i in 0 .. n - 1 {
1129 g.gen_stmt(g.a.child(&block, i))
1130 }
1131 if n > 0 {
1132 last_id := g.a.child(&block, n - 1)
1133 last := g.a.nodes[int(last_id)]
1134 if last.kind == .expr_stmt {
1135 g.gen_expr_as(g.a.child(&last, 0), result_w)
1136 } else if last.kind == .if_expr {
1137 g.gen_if_value_typed(last, result_w)
1138 } else {
1139 g.gen_stmt(last_id)
1140 g.push_zero(result_w)
1141 }
1142 } else {
1143 g.push_zero(result_w)
1144 }
1145 g.restore_scope(saved)
1146}
1147
1148fn (mut g Gen) push_zero(w WType) {
1149 match w {
1150 .i64 { g.cur.i64_const(0) }
1151 .f64 { g.cur.f64_const(0) }
1152 .f32 { g.cur.f32_const(0) }
1153 else { g.cur.i32_const(0) }
1154 }
1155}
1156
1157// gen_expr_as emits `id` coerced to the target WType.
1158fn (mut g Gen) gen_expr_as(id flat.NodeId, target WType) {
1159 w := g.gen_expr(id)
1160 if target == .void || w == .void {
1161 return
1162 }
1163 g.coerce(w, target, g.is_signed(id))
1164}
1165
1166// gen_expr_as_bool emits `id` leaving an i32 truth value on the stack.
1167fn (mut g Gen) gen_expr_as_bool(id flat.NodeId) {
1168 w := g.gen_expr(id)
1169 if w == .i64 {
1170 g.cur.i64_const(0)
1171 g.cur.raw(0x52) // i64.ne -> i32
1172 } else if w == .f64 {
1173 g.cur.f64_const(0)
1174 g.cur.raw(0x62) // f64.ne
1175 } else if w == .f32 {
1176 g.cur.f32_const(0)
1177 g.cur.raw(0x5c) // f32.ne
1178 }
1179 // i32 already a truth value
1180}
1181
1182fn (mut g Gen) gen_infix(id flat.NodeId, node flat.Node) WType {
1183 lhs_id := g.a.child(&node, 0)
1184 rhs_id := g.a.child(&node, 1)
1185 op := node.op
1186 if op == .logical_and || op == .logical_or {
1187 return g.gen_logical(node)
1188 }
1189 if op in [.eq, .ne, .lt, .gt, .le, .ge] {
1190 ow := g.operand_wtype(lhs_id, rhs_id)
1191 // Mixed signed/unsigned integer comparisons compare mathematical values,
1192 // not bit patterns, so a negative signed operand needs special handling.
1193 if ow in [WType.i32, WType.i64] && g.is_unsigned(lhs_id) != g.is_unsigned(rhs_id) {
1194 g.gen_mixed_cmp(lhs_id, rhs_id, op, ow)
1195 return .i32
1196 }
1197 signed := !g.is_unsigned(lhs_id) && !g.is_unsigned(rhs_id)
1198 g.gen_expr_as(lhs_id, ow)
1199 g.gen_expr_as(rhs_id, ow)
1200 g.cur.raw(cmp_op(op, ow, signed))
1201 return .i32
1202 }
1203 if op in [.left_shift, .right_shift, .right_shift_unsigned] {
1204 // V keeps the result type/width from the left operand and permits a
1205 // wider shift count, so the width comes from the lhs, not both operands.
1206 mut value_w := g.expr_wtype(lhs_id, '')
1207 if value_w !in [WType.i32, WType.i64] {
1208 value_w = .i32
1209 }
1210 signed := !g.is_unsigned(lhs_id)
1211 g.gen_expr_as(lhs_id, value_w)
1212 if op == .right_shift_unsigned && value_w == .i32 {
1213 // `>>>` operates on the narrow bit pattern; a narrow signed lhs is
1214 // stored sign-extended, so mask it to its width first.
1215 lw := narrow_width(g.tc.resolve_type(lhs_id))
1216 if lw != 32 {
1217 g.emit_narrow(lw, true)
1218 }
1219 }
1220 g.emit_shift_with_count(op, value_w, rhs_id, signed)
1221 if value_w == .i32 {
1222 g.narrow_result(id)
1223 }
1224 return value_w
1225 }
1226 // arithmetic / bitwise: in V both operands share the result type, so combine
1227 // the node's own type with the operand-derived width (the latter recovers
1228 // i64/float when the node type is missing).
1229 ow := combine_wtype(g.expr_wtype(id, node.typ), g.operand_wtype(lhs_id, rhs_id))
1230 // div/rem opcode signedness must follow the promoted result, so an unsigned
1231 // operand (e.g. `4000000000 / u32(2)`) selects div_u/rem_u, not the lhs.
1232 signed := !g.is_unsigned(id) && !g.is_unsigned(lhs_id) && !g.is_unsigned(rhs_id)
1233 g.gen_expr_as(lhs_id, ow)
1234 g.gen_expr_as(rhs_id, ow)
1235 g.emit_arith(op, ow, signed)
1236 // Sub-32-bit results (e.g. u8 + u8) wrap to their declared width in V.
1237 if ow == .i32 {
1238 g.narrow_result(id)
1239 }
1240 return ow
1241}
1242
1243// narrow_result masks/sign-extends the i32 on the stack to the resolved type's
1244// sub-32-bit width (a no-op for 32/64-bit types).
1245fn (mut g Gen) narrow_result(id flat.NodeId) {
1246 rt := g.tc.resolve_type(id)
1247 g.emit_narrow(narrow_width(rt), type_is_unsigned(rt))
1248}
1249
1250fn (mut g Gen) gen_logical(node flat.Node) WType {
1251 lhs_id := g.a.child(&node, 0)
1252 rhs_id := g.a.child(&node, 1)
1253 g.gen_expr_as_bool(lhs_id)
1254 g.cur.raw(0x04) // if
1255 g.cur.raw(valtype_i32) // -> i32 result
1256 g.frames << Frame{
1257 tag: .plain
1258 }
1259 if node.op == .logical_and {
1260 g.gen_expr_as_bool(rhs_id)
1261 g.cur.else_()
1262 g.cur.i32_const(0)
1263 } else {
1264 g.cur.i32_const(1)
1265 g.cur.else_()
1266 g.gen_expr_as_bool(rhs_id)
1267 }
1268 g.cur.end()
1269 g.frames.pop()
1270 return .i32
1271}
1272
1273// gen_mixed_cmp emits a comparison of a signed and an unsigned integer operand
1274// by mathematical value. The two operands are buffered, then: if the signed
1275// operand is negative it is smaller than the (non-negative) unsigned operand,
1276// otherwise both are non-negative and an unsigned comparison is exact.
1277fn (mut g Gen) gen_mixed_cmp(lhs_id flat.NodeId, rhs_id flat.NodeId, op flat.Op, ow WType) {
1278 lhs_signed := !g.is_unsigned(lhs_id)
1279 lhs_tmp := g.alloc_temp(ow)
1280 rhs_tmp := g.alloc_temp(ow)
1281 g.gen_expr_as(lhs_id, ow)
1282 g.cur.local_set(lhs_tmp)
1283 g.gen_expr_as(rhs_id, ow)
1284 g.cur.local_set(rhs_tmp)
1285 // is the signed operand negative?
1286 g.cur.local_get(if lhs_signed { lhs_tmp } else { rhs_tmp })
1287 if ow == .i64 {
1288 g.cur.i64_const(0)
1289 g.cur.raw(0x53) // i64.lt_s
1290 } else {
1291 g.cur.i32_const(0)
1292 g.cur.raw(0x48) // i32.lt_s
1293 }
1294 g.cur.raw(0x04) // if
1295 g.cur.raw(valtype_i32) // -> i32
1296 g.frames << Frame{
1297 tag: .plain
1298 }
1299 g.cur.i32_const(neg_signed_cmp_result(op, lhs_signed))
1300 g.cur.else_()
1301 g.cur.local_get(lhs_tmp)
1302 g.cur.local_get(rhs_tmp)
1303 g.cur.raw(cmp_op(op, ow, false)) // both non-negative: unsigned comparison
1304 g.cur.end()
1305 g.frames.pop()
1306}
1307
1308// neg_signed_cmp_result is the comparison result when the signed operand is
1309// negative and the other operand is unsigned (hence non-negative), so the
1310// signed operand is strictly the smaller value.
1311fn neg_signed_cmp_result(op flat.Op, signed_is_lhs bool) int {
1312 return match op {
1313 .lt, .le {
1314 if signed_is_lhs {
1315 1
1316 } else {
1317 0
1318 }
1319 }
1320 .gt, .ge {
1321 if signed_is_lhs {
1322 0
1323 } else {
1324 1
1325 }
1326 }
1327 .eq {
1328 0
1329 }
1330 .ne {
1331 1
1332 }
1333 else {
1334 0
1335 }
1336 }
1337}
1338
1339fn (mut g Gen) gen_prefix(id flat.NodeId, node flat.Node) WType {
1340 child_id := g.a.child(&node, 0)
1341 match node.op {
1342 .minus {
1343 child := g.a.nodes[int(child_id)]
1344 if child.kind == .int_literal {
1345 // Fold `-literal` directly: the checker may type a large literal
1346 // as plain int, which would wrap the magnitude to 32 bits before
1347 // negating (e.g. i64(-9223372036854775808)).
1348 mag := u64(parse_int_literal(child.value))
1349 if mag > 2147483648 {
1350 g.cur.i64_const(i64(u64(0) - mag))
1351 return .i64
1352 }
1353 g.cur.i32_const(-i64(mag))
1354 g.narrow_result(id)
1355 return .i32
1356 }
1357 w := g.expr_wtype(child_id, '')
1358 match w {
1359 .f64 {
1360 g.gen_expr(child_id)
1361 g.cur.raw(0x9a) // f64.neg
1362 return .f64
1363 }
1364 .f32 {
1365 g.gen_expr(child_id)
1366 g.cur.raw(0x8c) // f32.neg
1367 return .f32
1368 }
1369 .i64 {
1370 g.cur.i64_const(0)
1371 g.gen_expr_as(child_id, .i64)
1372 g.cur.raw(0x7d) // i64.sub
1373 return .i64
1374 }
1375 else {
1376 g.cur.i32_const(0)
1377 g.gen_expr_as(child_id, .i32)
1378 g.cur.raw(0x6b) // i32.sub
1379 g.narrow_result(id) // negation of a narrow type wraps to its width
1380 return .i32
1381 }
1382 }
1383 }
1384 .not {
1385 g.gen_expr_as_bool(child_id)
1386 g.cur.raw(0x45) // i32.eqz
1387 return .i32
1388 }
1389 .bit_not {
1390 w := g.gen_expr(child_id)
1391 if w == .i64 {
1392 g.cur.i64_const(-1)
1393 g.cur.raw(0x85) // i64.xor
1394 return .i64
1395 }
1396 g.cur.i32_const(-1)
1397 g.cur.raw(0x73) // i32.xor
1398 g.narrow_result(id) // ~ of a narrow type keeps its width (e.g. ~u8(0)=255)
1399 return .i32
1400 }
1401 else {
1402 g.warn('unsupported prefix op: ${node.op}')
1403 return g.gen_expr(child_id)
1404 }
1405 }
1406}
1407
1408fn (mut g Gen) gen_postfix(node flat.Node) {
1409 target := g.a.child_node(&node, 0)
1410 inc := node.op == .inc
1411 if target.kind == .ident && target.value in g.var_index {
1412 idx := g.var_index[target.value]
1413 g.cur.local_get(idx)
1414 g.emit_inc(g.var_wtype[target.value], inc)
1415 g.narrow_for_local(target.value)
1416 g.cur.local_set(idx)
1417 return
1418 }
1419 if target.kind == .ident {
1420 gkey := g.global_key(target.value)
1421 if gkey != '' {
1422 gidx := g.global_index[gkey]
1423 g.cur.global_get(gidx)
1424 g.emit_inc(g.global_wtype[gkey], inc)
1425 g.narrow_for_global(gkey)
1426 g.cur.global_set(gidx)
1427 return
1428 }
1429 }
1430 g.warn('unsupported postfix target')
1431}
1432
1433// emit_inc pushes 1 of the given type and adds (inc) or subtracts it from the
1434// value already on the stack, used by `x++`/`x--` for locals and globals.
1435fn (mut g Gen) emit_inc(w WType, inc bool) {
1436 match w {
1437 .i64 {
1438 g.cur.i64_const(1)
1439 g.cur.raw(if inc { u8(0x7c) } else { u8(0x7d) }) // i64.add/sub
1440 }
1441 .f64 {
1442 g.cur.f64_const(1)
1443 g.cur.raw(if inc { u8(0xa0) } else { u8(0xa1) }) // f64.add/sub
1444 }
1445 .f32 {
1446 g.cur.f32_const(1)
1447 g.cur.raw(if inc { u8(0x92) } else { u8(0x93) }) // f32.add/sub
1448 }
1449 else {
1450 g.cur.i32_const(1)
1451 g.cur.raw(if inc { u8(0x6a) } else { u8(0x6b) }) // i32.add/sub
1452 }
1453 }
1454}
1455
1456fn (mut g Gen) gen_cast(node flat.Node) WType {
1457 child_id := g.a.child(&node, 0)
1458 tt := g.tc.parse_type(node.value)
1459 target := prim_wtype(tt) or { WType.i32 }
1460 child := g.a.nodes[int(child_id)]
1461 if target in [WType.f32, WType.f64] && child.kind == .int_literal {
1462 // Materialize the literal directly for the float target: an integer
1463 // path would wrap large non-negative values (e.g.
1464 // f64(9223372036854775808)) before the conversion and flip the sign.
1465 uval := u64(parse_int_literal(child.value))
1466 if target == .f32 {
1467 g.cur.f32_const(f32(uval))
1468 } else {
1469 g.cur.f64_const(f64(uval))
1470 }
1471 return target
1472 }
1473 src := g.gen_expr(child_id)
1474 if src in [WType.f32, WType.f64] && target in [WType.i32, WType.i64] {
1475 // float -> int: signedness comes from the target, not the source.
1476 g.emit_float_to_int(src, target, !type_is_unsigned(tt))
1477 } else {
1478 g.coerce(src, target, g.is_signed(child_id))
1479 }
1480 // Casting to a sub-32-bit type wraps/sign-extends to that width.
1481 g.emit_narrow(narrow_width(tt), type_is_unsigned(tt))
1482 return target
1483}
1484
1485fn (mut g Gen) gen_call(node flat.Node) WType {
1486 callee := g.a.child_node(&node, 0)
1487 if callee.kind == .ident && callee.value in ['println', 'print', 'eprintln', 'eprint'] {
1488 g.gen_print_call(node, callee.value)
1489 return .void
1490 }
1491 for key in g.resolve_call_keys(callee, g.cur_fn_module, g.cur_fn_file) {
1492 if key in g.fn_index {
1493 params := g.fn_params[key]
1494 for i in 1 .. node.children_count {
1495 pw := if i - 1 < params.len { params[i - 1] } else { WType.i32 }
1496 g.gen_expr_as(g.a.child(&node, i), pw)
1497 }
1498 g.cur.call(g.fn_index[key])
1499 return g.fn_ret[key]
1500 }
1501 }
1502 if callee.kind == .ident {
1503 g.warn('unsupported call: ${callee.value}')
1504 } else {
1505 g.warn('unsupported call target')
1506 }
1507 // keep the stack balanced for value contexts
1508 g.cur.i32_const(0)
1509 return .i32
1510}
1511
1512// collect_imports records every import path and, per source file, the
1513// selector/alias -> full import path mapping. Candidates are keyed by the full
1514// import path (see module_path_for_file), so two nested modules with the same
1515// leaf (`import foo.util as fu`, `import bar.util as bu`) stay distinct.
1516fn (mut g Gen) collect_imports() {
1517 // Pass 1: import paths + per-file aliases.
1518 mut cur_file := ''
1519 for node in g.a.nodes {
1520 if node.kind == .file {
1521 cur_file = node.value
1522 } else if node.kind == .import_decl && node.value.len > 0 {
1523 g.import_paths << node.value
1524 if node.typ.len > 0 {
1525 if cur_file !in g.file_aliases {
1526 g.file_aliases[cur_file] = map[string]string{}
1527 }
1528 g.file_aliases[cur_file][node.typ] = node.value
1529 }
1530 }
1531 }
1532 // Pass 2: the module import graph (module_path_for_file now works), keyed by
1533 // the importing file's module path ('' for main), used to order init calls.
1534 cur_file = ''
1535 mut cur_mod := ''
1536 for node in g.a.nodes {
1537 if node.kind == .file {
1538 cur_file = node.value
1539 cur_mod = ''
1540 } else if node.kind == .module_decl {
1541 cur_mod = if node.value != '' && node.value != 'main' {
1542 g.module_path_for_file(cur_file)
1543 } else {
1544 ''
1545 }
1546 } else if node.kind == .import_decl && node.value.len > 0 {
1547 g.module_imports[cur_mod] << node.value
1548 }
1549 }
1550}
1551
1552// collect_globals lowers primitive `__global` declarations to mutable wasm
1553// globals, recording each name's index/type and emitting a constant initializer.
1554fn (mut g Gen) collect_globals() {
1555 mut cur_module := ''
1556 mut cur_file := ''
1557 mut cur_module_path := ''
1558 for i in 0 .. g.a.nodes.len {
1559 node := g.a.nodes[i]
1560 if node.kind == .file {
1561 cur_module = ''
1562 cur_file = node.value
1563 cur_module_path = ''
1564 continue
1565 }
1566 if node.kind == .module_decl {
1567 cur_module = node.value
1568 cur_module_path = if cur_module != '' && cur_module != 'main' {
1569 g.module_path_for_file(cur_file)
1570 } else {
1571 ''
1572 }
1573 continue
1574 }
1575 if node.kind != .global_decl || i < g.a.user_code_start {
1576 continue
1577 }
1578 // Qualify the global by its declaring module so a name declared in two
1579 // modules maps to two distinct wasm globals (see global_key for lookup).
1580 mod_key := if cur_module_path != '' {
1581 cur_module_path
1582 } else if cur_module != '' && cur_module != 'main' {
1583 cur_module
1584 } else {
1585 ''
1586 }
1587 for fi in 0 .. node.children_count {
1588 f := g.a.child_node(&node, fi)
1589 if f.kind != .field_decl || f.value.len == 0 || f.value.starts_with('C.') {
1590 continue
1591 }
1592 // Type from the declaration, or inferred from the initializer.
1593 mut t := types.Type(types.Void{})
1594 if f.typ.len > 0 {
1595 t = g.tc.parse_type(f.typ)
1596 } else if f.children_count > 0 {
1597 t = g.tc.resolve_type(g.a.child(f, 0))
1598 }
1599 w := prim_wtype(t) or { continue } // only numeric/bool globals
1600 width := narrow_width(t)
1601 unsigned := type_is_unsigned(t)
1602 mut init := Code{}
1603 if f.children_count > 0 {
1604 g.emit_global_init(g.a.child(f, 0), w, width, unsigned, mut init)
1605 } else {
1606 init_push_zero(w, mut init)
1607 }
1608 init.end()
1609 gidx := g.mod.add_global(wt_valtype(w), init.bytes)
1610 key := qualified_fn_key(mod_key, f.value)
1611 g.global_index[key] = gidx
1612 g.global_wtype[key] = w
1613 g.global_unsigned[key] = unsigned
1614 g.global_widths[key] = width
1615 }
1616 }
1617}
1618
1619// emit_global_init writes a constant initializer expression for a global. Only
1620// constant literals (optionally negated/cast) are folded; others default to 0.
1621fn (g &Gen) emit_global_init(init_id flat.NodeId, w WType, width int, unsigned bool, mut c Code) {
1622 if w in [WType.f32, WType.f64] {
1623 fv := g.fold_const_float(init_id)
1624 if w == .f32 {
1625 c.f32_const(f32(fv))
1626 } else {
1627 c.f64_const(fv)
1628 }
1629 return
1630 }
1631 iv := g.fold_const_int(init_id)
1632 if w == .i64 {
1633 c.i64_const(iv)
1634 } else {
1635 // Apply the global's declared sub-32-bit width so an out-of-range cast
1636 // initializer (e.g. `u8(300)` -> 44, `i8(128)` -> -128) matches what a
1637 // later store would narrow it to via narrow_for_global.
1638 c.i32_const(narrow_const(iv, width, unsigned))
1639 }
1640}
1641
1642// narrow_const wraps/sign-extends a constant to a declared 8/16-bit width; the
1643// compile-time counterpart of emit_narrow.
1644fn narrow_const(v i64, width int, unsigned bool) i64 {
1645 return match width {
1646 8 {
1647 if unsigned {
1648 i64(u8(v))
1649 } else {
1650 i64(i8(v))
1651 }
1652 }
1653 16 {
1654 if unsigned {
1655 i64(u16(v))
1656 } else {
1657 i64(i16(v))
1658 }
1659 }
1660 else {
1661 v
1662 }
1663 }
1664}
1665
1666fn (g &Gen) fold_const_int(id flat.NodeId) i64 {
1667 if int(id) < 0 {
1668 return 0
1669 }
1670 n := g.a.nodes[int(id)]
1671 match n.kind {
1672 .int_literal {
1673 return parse_int_literal(n.value)
1674 }
1675 .char_literal {
1676 return char_literal_value(n.value)
1677 }
1678 .bool_literal {
1679 return if n.value == 'true' { i64(1) } else { i64(0) }
1680 }
1681 .ident {
1682 // A constant referenced by name folds to its own constant value.
1683 if cid := g.const_value_node(n.value) {
1684 return g.fold_const_int(cid)
1685 }
1686 }
1687 .prefix {
1688 if n.children_count > 0 {
1689 match n.op {
1690 .minus { return -g.fold_const_int(g.a.child(&n, 0)) }
1691 .bit_not { return ~g.fold_const_int(g.a.child(&n, 0)) }
1692 else {}
1693 }
1694 }
1695 }
1696 .infix {
1697 if n.children_count >= 2 {
1698 // Narrow to the infix's own resolved type so any wrapping happens
1699 // before a wider outer cast can observe it, matching normal codegen
1700 // (`int(u8(250) + u8(10))` -> 4, not 260).
1701 return narrow_to_type(g.fold_const_infix(n), g.tc.resolve_type(id))
1702 }
1703 }
1704 .paren {
1705 if n.children_count > 0 {
1706 return g.fold_const_int(g.a.child(&n, 0))
1707 }
1708 }
1709 .cast_expr {
1710 if n.children_count > 0 {
1711 cv := g.fold_const_int(g.a.child(&n, 0))
1712 // Apply the cast target's own width/signedness so a nested narrow
1713 // cast keeps its value even when the enclosing global is wider
1714 // (e.g. `int(u8(300))` -> 44, not 300).
1715 return if n.value.len > 0 { g.fold_cast_int(cv, n.value) } else { cv }
1716 }
1717 }
1718 else {}
1719 }
1720
1721 return 0
1722}
1723
1724// fold_const_infix folds a binary constant expression (`1 + 2`, `base << 3`, ...)
1725// over its already-folded integer operands. Division, remainder, right shift and
1726// comparisons use unsigned semantics when either operand resolves to an unsigned
1727// type, since folded operands are stored as i64 bit patterns (u64(max) -> -1) and
1728// signed ops would otherwise mis-fold, e.g. `u64(max) / u64(2)`.
1729fn (g &Gen) fold_const_infix(n flat.Node) i64 {
1730 la := g.a.child(&n, 0)
1731 lb := g.a.child(&n, 1)
1732 a := g.fold_const_int(la)
1733 b := g.fold_const_int(lb)
1734 unsigned := type_is_unsigned(g.tc.resolve_type(la)) || type_is_unsigned(g.tc.resolve_type(lb))
1735 return match n.op {
1736 .plus {
1737 a + b
1738 }
1739 .minus {
1740 a - b
1741 }
1742 .mul {
1743 a * b
1744 }
1745 .div {
1746 if b == 0 {
1747 i64(0)
1748 } else if unsigned {
1749 i64(u64(a) / u64(b))
1750 } else {
1751 a / b
1752 }
1753 }
1754 .mod {
1755 if b == 0 {
1756 i64(0)
1757 } else if unsigned {
1758 i64(u64(a) % u64(b))
1759 } else {
1760 a % b
1761 }
1762 }
1763 .amp {
1764 a & b
1765 }
1766 .pipe {
1767 a | b
1768 }
1769 .xor {
1770 a ^ b
1771 }
1772 .left_shift {
1773 i64(u64(a) << b)
1774 }
1775 .right_shift {
1776 if unsigned {
1777 i64(u64(a) >> b)
1778 } else {
1779 a >> b
1780 }
1781 }
1782 .right_shift_unsigned {
1783 i64(u64(a) >> b)
1784 }
1785 .eq {
1786 bool_to_i64(a == b)
1787 }
1788 .ne {
1789 bool_to_i64(a != b)
1790 }
1791 .lt {
1792 if unsigned {
1793 bool_to_i64(u64(a) < u64(b))
1794 } else {
1795 bool_to_i64(a < b)
1796 }
1797 }
1798 .gt {
1799 if unsigned {
1800 bool_to_i64(u64(a) > u64(b))
1801 } else {
1802 bool_to_i64(a > b)
1803 }
1804 }
1805 .le {
1806 if unsigned {
1807 bool_to_i64(u64(a) <= u64(b))
1808 } else {
1809 bool_to_i64(a <= b)
1810 }
1811 }
1812 .ge {
1813 if unsigned {
1814 bool_to_i64(u64(a) >= u64(b))
1815 } else {
1816 bool_to_i64(a >= b)
1817 }
1818 }
1819 else {
1820 i64(0)
1821 }
1822 }
1823}
1824
1825fn bool_to_i64(b bool) i64 {
1826 return if b { i64(1) } else { i64(0) }
1827}
1828
1829// fold_cast_int wraps a folded constant to a numeric cast target's width and
1830// signedness, the compile-time form of gen_cast's narrowing.
1831fn (g &Gen) fold_cast_int(v i64, type_name string) i64 {
1832 return narrow_to_type(v, g.tc.parse_type(type_name))
1833}
1834
1835// narrow_to_type wraps a folded constant to an integer type's width and
1836// signedness (i64 targets keep the full value; i32-family targets wrap to
1837// 8/16/32 bits; non-integer targets are left unchanged).
1838fn narrow_to_type(v i64, t types.Type) i64 {
1839 w := prim_wtype(t) or { return v }
1840 if w != .i32 {
1841 return v
1842 }
1843 unsigned := type_is_unsigned(t)
1844 return match narrow_width(t) {
1845 8 {
1846 if unsigned {
1847 i64(u8(v))
1848 } else {
1849 i64(i8(v))
1850 }
1851 }
1852 16 {
1853 if unsigned {
1854 i64(u16(v))
1855 } else {
1856 i64(i16(v))
1857 }
1858 }
1859 else {
1860 if unsigned {
1861 i64(u32(v))
1862 } else {
1863 i64(i32(v))
1864 }
1865 }
1866 }
1867}
1868
1869fn (g &Gen) fold_const_float(id flat.NodeId) f64 {
1870 if int(id) < 0 {
1871 return 0
1872 }
1873 n := g.a.nodes[int(id)]
1874 match n.kind {
1875 .float_literal {
1876 return n.value.replace('_', '').f64()
1877 }
1878 .int_literal {
1879 return f64(parse_int_literal(n.value))
1880 }
1881 .ident {
1882 if cid := g.const_value_node(n.value) {
1883 return g.fold_const_float(cid)
1884 }
1885 }
1886 .prefix {
1887 if n.op == .minus && n.children_count > 0 {
1888 return -g.fold_const_float(g.a.child(&n, 0))
1889 }
1890 }
1891 .infix {
1892 if n.children_count >= 2 {
1893 a := g.fold_const_float(g.a.child(&n, 0))
1894 b := g.fold_const_float(g.a.child(&n, 1))
1895 return match n.op {
1896 .plus {
1897 a + b
1898 }
1899 .minus {
1900 a - b
1901 }
1902 .mul {
1903 a * b
1904 }
1905 .div {
1906 if b != 0 {
1907 a / b
1908 } else {
1909 f64(0)
1910 }
1911 }
1912 else {
1913 f64(0)
1914 }
1915 }
1916 }
1917 }
1918 .paren {
1919 if n.children_count > 0 {
1920 return g.fold_const_float(g.a.child(&n, 0))
1921 }
1922 }
1923 .cast_expr {
1924 if n.children_count > 0 {
1925 cid := g.a.child(&n, 0)
1926 tw := prim_wtype(g.tc.parse_type(n.value)) or { WType.f64 }
1927 if tw !in [WType.f32, WType.f64] {
1928 // Cast to an integer type inside a float context: fold and
1929 // narrow as an int, then widen to float.
1930 return f64(g.fold_cast_int(g.fold_const_int(cid), n.value))
1931 }
1932 child := g.a.nodes[int(cid)]
1933 mut fv := if child.kind == .int_literal {
1934 // Match gen_cast: reinterpret the literal's wrapped i64 bits as
1935 // u64 so a large non-negative literal (e.g.
1936 // f64(9223372036854775808)) stays positive instead of flipping
1937 // sign through parse_int_literal.
1938 f64(u64(parse_int_literal(child.value)))
1939 } else {
1940 g.fold_const_float(cid)
1941 }
1942 if tw == .f32 {
1943 fv = f64(f32(fv)) // round to f32 precision like an f32 cast
1944 }
1945 return fv
1946 }
1947 }
1948 else {}
1949 }
1950
1951 return 0
1952}
1953
1954fn init_push_zero(w WType, mut c Code) {
1955 match w {
1956 .i64 { c.i64_const(0) }
1957 .f64 { c.f64_const(0) }
1958 .f32 { c.f32_const(0) }
1959 else { c.i32_const(0) }
1960 }
1961}
1962
1963// order_module_inits appends init function indices in dependency order via a
1964// post-order walk of the import graph: a module's imports' inits run first, and
1965// since main ('') is the root, its own init is appended last.
1966fn (mut g Gen) order_module_inits(mod string, module_init map[string]string, mut visited map[string]bool) {
1967 if mod in visited {
1968 return
1969 }
1970 visited[mod] = true
1971 for dep in g.module_imports[mod] {
1972 g.order_module_inits(dep, module_init, mut visited)
1973 }
1974 if init_key := module_init[mod] {
1975 if idx := g.fn_index[init_key] {
1976 g.init_fns << idx
1977 }
1978 }
1979}
1980
1981// module_path_for_file returns the full import path whose directory form is a
1982// suffix of the file's directory (longest match), or '' for main/local code.
1983fn (g &Gen) module_path_for_file(file string) string {
1984 if file.len == 0 {
1985 return ''
1986 }
1987 dir := os.dir(file)
1988 mut best := ''
1989 for p in g.import_paths {
1990 if p.len <= best.len {
1991 continue
1992 }
1993 df := p.replace('.', os.path_separator)
1994 if dir == df || dir.ends_with(os.path_separator + df) {
1995 best = p
1996 }
1997 }
1998 return best
1999}
2000
2001// resolve_call_keys returns the candidate fn_index keys for a callee, in
2002// priority order. A bare ident inside an imported module resolves to the
2003// same-module function first (`mod.name`) and falls back to a main-module name;
2004// a `module.fn()` selector resolves to that module's qualified name, with any
2005// import alias mapped back to the real module.
2006fn (g &Gen) resolve_call_keys(callee &flat.Node, cur_mod string, cur_file string) []string {
2007 if callee.kind == .ident {
2008 if cur_mod != '' && cur_mod != 'main' {
2009 return ['${cur_mod}.${callee.value}', callee.value]
2010 }
2011 return [callee.value]
2012 }
2013 if callee.kind == .selector && callee.children_count > 0 {
2014 base := g.a.child_node(callee, 0)
2015 if base.kind == .ident {
2016 real := g.resolve_module_alias(cur_file, base.value)
2017 return ['${real}.${callee.value}']
2018 }
2019 }
2020 return []
2021}
2022
2023// resolve_module_alias maps an import alias to its real module using the
2024// aliases declared in the call site's source file, or returns the name as-is.
2025fn (g &Gen) resolve_module_alias(cur_file string, name string) string {
2026 if aliases := g.file_aliases[cur_file] {
2027 if real := aliases[name] {
2028 return real
2029 }
2030 }
2031 return name
2032}
2033
2034// ---- print intrinsics ----
2035
2036fn (mut g Gen) gen_print_call(node flat.Node, name string) {
2037 fd := if name.starts_with('e') { 2 } else { 1 }
2038 newline := name.ends_with('ln')
2039 if node.children_count < 2 {
2040 if newline {
2041 g.emit_write_const(nl_ptr, 1, fd)
2042 }
2043 return
2044 }
2045 arg_id := g.a.child(&node, 1)
2046 arg := g.a.nodes[int(arg_id)]
2047 if arg.kind == .string_literal {
2048 // arg.value is already unescaped by the parser (strip_quotes ->
2049 // unescape_string); use the bytes as-is, no second escape pass.
2050 bytes := arg.value.bytes()
2051 off := g.intern_data(bytes)
2052 g.emit_write_const(off, bytes.len, fd)
2053 if newline {
2054 g.emit_write_const(nl_ptr, 1, fd)
2055 }
2056 return
2057 }
2058 if arg.kind == .call {
2059 inner := g.a.child_node(&arg, 0)
2060 if inner.kind == .ident && arg.children_count >= 2 {
2061 if inner.value in signed_int_format_fns {
2062 g.emit_print_int(g.a.child(&arg, 1), fd, newline, true)
2063 return
2064 }
2065 if inner.value in unsigned_int_format_fns {
2066 g.emit_print_int(g.a.child(&arg, 1), fd, newline, false)
2067 return
2068 }
2069 if inner.value in bool_format_fns {
2070 g.gen_print_bool(g.a.child(&arg, 1), fd, newline)
2071 return
2072 }
2073 }
2074 }
2075 // integer expression passed directly (e.g. print(x))
2076 w := g.expr_wtype(arg_id, '')
2077 if w in [WType.i32, WType.i64] {
2078 g.emit_print_int(arg_id, fd, newline, !g.is_unsigned(arg_id))
2079 return
2080 }
2081 g.warn('unsupported ${name} argument: ${arg.kind}')
2082}
2083
2084// gen_print_bool prints "true"/"false" based on the i32 truth value of arg.
2085fn (mut g Gen) gen_print_bool(arg_id flat.NodeId, fd int, newline bool) {
2086 true_off := g.intern_data('true'.bytes())
2087 false_off := g.intern_data('false'.bytes())
2088 g.gen_expr_as_bool(arg_id)
2089 g.cur.if_void()
2090 g.frames << Frame{
2091 tag: .plain
2092 }
2093 g.emit_write_const(true_off, 4, fd)
2094 g.cur.else_()
2095 g.emit_write_const(false_off, 5, fd)
2096 g.cur.end()
2097 g.frames.pop()
2098 if newline {
2099 g.emit_write_const(nl_ptr, 1, fd)
2100 }
2101}
2102
2103// emit_print_int evaluates an integer expression and prints it via the runtime
2104// helper, choosing signed or unsigned decimal conversion.
2105fn (mut g Gen) emit_print_int(int_arg_id flat.NodeId, fd int, newline bool, signed bool) {
2106 w := g.gen_expr(int_arg_id)
2107 // Widen to i64 using the formatter's signedness (an unsigned formatter must
2108 // zero-extend, e.g. `1 + u64(x)` or a u32 result with the high bit set).
2109 g.coerce(w, .i64, signed)
2110 g.cur.i32_const(fd)
2111 g.cur.i32_const(if newline { 1 } else { 0 })
2112 g.cur.i32_const(if signed { 1 } else { 0 })
2113 g.cur.call(g.print_int_idx)
2114}
2115
2116fn (mut g Gen) emit_write_const(ptr int, len int, fd int) {
2117 g.cur.i32_const(ptr)
2118 g.cur.i32_const(len)
2119 g.cur.i32_const(fd)
2120 g.cur.call(g.write_idx)
2121}
2122
2123// emit_write_helper builds `__v_write(ptr, len, fd)` -> calls WASI fd_write.
2124fn (mut g Gen) emit_write_helper(fd_write_idx int) {
2125 mut c := Code{}
2126 // iov[0] = ptr (mem @ iov_ptr)
2127 c.i32_const(iov_ptr)
2128 c.local_get(0)
2129 c.i32_store(0)
2130 // iov[1] = len (mem @ iov_ptr+4)
2131 c.i32_const(iov_ptr)
2132 c.local_get(1)
2133 c.i32_store(4)
2134 // fd_write(fd, iov_ptr, 1, nwritten_ptr)
2135 c.local_get(2)
2136 c.i32_const(iov_ptr)
2137 c.i32_const(1)
2138 c.i32_const(nwritten_ptr)
2139 c.call(fd_write_idx)
2140 c.drop()
2141 c.end()
2142 ti := g.mod.add_type([valtype_i32, valtype_i32, valtype_i32], [])
2143 g.mod.add_func(ti, [], c.bytes)
2144}
2145
2146// emit_print_int_helper builds `__v_print_int(val: i64, fd: i32, nl: i32,
2147// signed: i32)`. The digit loop uses unsigned div/rem, so passing signed=0
2148// prints the full u64 range; signed=1 adds two's-complement sign handling.
2149fn (mut g Gen) emit_print_int_helper() {
2150 // params: val(0,i64) fd(1,i32) nl(2,i32) signed(3,i32)
2151 // locals: p(4,i32) v(5,i64) neg(6,i32) digit(7,i64)
2152 signed := 3
2153 p := 4
2154 v := 5
2155 neg := 6
2156 digit := 7
2157 mut c := Code{}
2158 // neg = signed && val < 0
2159 c.local_get(0)
2160 c.i64_const(0)
2161 c.raw(0x53) // i64.lt_s
2162 c.local_get(signed)
2163 c.raw(0x71) // i32.and
2164 c.local_set(neg)
2165 // v = val
2166 c.local_get(0)
2167 c.local_set(v)
2168 // if neg { v = 0 - v }
2169 c.local_get(neg)
2170 c.if_void()
2171 c.i64_const(0)
2172 c.local_get(v)
2173 c.raw(0x7d) // i64.sub
2174 c.local_set(v)
2175 c.end()
2176 // p = buf_end
2177 c.i32_const(buf_end)
2178 c.local_set(p)
2179 // do { ... } while v != 0
2180 c.loop_void()
2181 c.local_get(p)
2182 c.i32_const(1)
2183 c.raw(0x6b) // i32.sub
2184 c.local_set(p)
2185 // digit = v % 10
2186 c.local_get(v)
2187 c.i64_const(10)
2188 c.raw(0x82) // i64.rem_u
2189 c.local_set(digit)
2190 // mem8[p] = '0' + digit
2191 c.local_get(p)
2192 c.local_get(digit)
2193 c.raw(0xa7) // i32.wrap_i64
2194 c.i32_const(48)
2195 c.raw(0x6a) // i32.add
2196 c.i32_store8(0)
2197 // v = v / 10
2198 c.local_get(v)
2199 c.i64_const(10)
2200 c.raw(0x80) // i64.div_u
2201 c.local_set(v)
2202 // continue if v != 0
2203 c.local_get(v)
2204 c.i64_const(0)
2205 c.raw(0x52) // i64.ne
2206 c.br_if(0)
2207 c.end() // loop
2208 // if neg { p--; mem8[p]='-' }
2209 c.local_get(neg)
2210 c.if_void()
2211 c.local_get(p)
2212 c.i32_const(1)
2213 c.raw(0x6b)
2214 c.local_set(p)
2215 c.local_get(p)
2216 c.i32_const(45) // '-'
2217 c.i32_store8(0)
2218 c.end()
2219 // __v_write(p, buf_end - p, fd)
2220 c.local_get(p)
2221 c.i32_const(buf_end)
2222 c.local_get(p)
2223 c.raw(0x6b) // i32.sub
2224 c.local_get(1) // fd
2225 c.call(g.write_idx)
2226 // if nl { __v_write(nl_ptr, 1, fd) }
2227 c.local_get(2)
2228 c.if_void()
2229 c.i32_const(nl_ptr)
2230 c.i32_const(1)
2231 c.local_get(1)
2232 c.call(g.write_idx)
2233 c.end()
2234 c.end() // function
2235 locals := [valtype_i32, valtype_i64, valtype_i32, valtype_i64]
2236 ti := g.mod.add_type([valtype_i64, valtype_i32, valtype_i32, valtype_i32], [])
2237 g.mod.add_func(ti, locals, c.bytes)
2238}
2239
2240fn (mut g Gen) intern_data(bytes []u8) int {
2241 key := bytes.bytestr()
2242 if key in g.data_off {
2243 return g.data_off[key]
2244 }
2245 off := data_base + g.data_pool.len
2246 g.data_pool << bytes
2247 g.data_off[key] = off
2248 return off
2249}
2250
2251// ---- type helpers ----
2252
2253fn (mut g Gen) expr_wtype(id flat.NodeId, fallback_typ string) WType {
2254 if int(id) >= 0 {
2255 node := g.a.nodes[int(id)]
2256 // Locals are authoritative: our own table is more reliable than the
2257 // shared checker scope, which the backend does not repopulate.
2258 if node.kind == .ident && node.value in g.var_wtype {
2259 return g.var_wtype[node.value]
2260 }
2261 if node.kind == .ident {
2262 gkey := g.global_key(node.value)
2263 if gkey != '' {
2264 return g.global_wtype[gkey]
2265 }
2266 }
2267 if node.kind == .paren && node.children_count > 0 {
2268 return g.expr_wtype(g.a.child(&node, 0), fallback_typ)
2269 }
2270 if node.kind == .cast_expr && node.value.len > 0 {
2271 if w := prim_wtype(g.tc.parse_type(node.value)) {
2272 return w
2273 }
2274 }
2275 if node.kind == .infix && node.children_count >= 2 {
2276 if node.op in [.eq, .ne, .lt, .gt, .le, .ge, .logical_and, .logical_or] {
2277 return .i32
2278 }
2279 if node.op in [.left_shift, .right_shift, .right_shift_unsigned] {
2280 // Shift result width follows the lhs, not the (possibly wider) count.
2281 return g.expr_wtype(g.a.child(&node, 0), '')
2282 }
2283 return combine_wtype(g.expr_wtype(g.a.child(&node, 0), ''), g.expr_wtype(g.a.child(&node,
2284 1), ''))
2285 }
2286 if node.kind == .prefix && node.children_count >= 1 {
2287 if node.op == .not {
2288 return .i32
2289 }
2290 return g.expr_wtype(g.a.child(&node, 0), fallback_typ)
2291 }
2292 }
2293 t := g.tc.resolve_type(id)
2294 if w := prim_wtype(t) {
2295 if t !is types.Void {
2296 return w
2297 }
2298 }
2299 if fallback_typ.len > 0 && fallback_typ != 'void' {
2300 if w := prim_wtype(g.tc.parse_type(fallback_typ)) {
2301 return w
2302 }
2303 }
2304 return .i32
2305}
2306
2307fn (mut g Gen) operand_wtype(lhs_id flat.NodeId, rhs_id flat.NodeId) WType {
2308 return combine_wtype(g.expr_wtype(lhs_id, ''), g.expr_wtype(rhs_id, ''))
2309}
2310
2311// combine_wtype picks the widest of two value types (float beats int, 64 beats
2312// 32), which is the shared operand/result width for a binary operation.
2313fn combine_wtype(a WType, b WType) WType {
2314 if a == .f64 || b == .f64 {
2315 return .f64
2316 }
2317 if a == .f32 || b == .f32 {
2318 return .f32
2319 }
2320 if a == .i64 || b == .i64 {
2321 return .i64
2322 }
2323 return .i32
2324}
2325
2326fn (mut g Gen) is_unsigned(id flat.NodeId) bool {
2327 if int(id) >= 0 {
2328 node := g.a.nodes[int(id)]
2329 if node.kind == .ident && node.value in g.var_unsigned {
2330 return g.var_unsigned[node.value]
2331 }
2332 if node.kind == .ident {
2333 gkey := g.global_key(node.value)
2334 if gkey != '' {
2335 return g.global_unsigned[gkey]
2336 }
2337 }
2338 if node.kind == .paren && node.children_count > 0 {
2339 return g.is_unsigned(g.a.child(&node, 0))
2340 }
2341 if node.kind == .cast_expr && node.value.len > 0 {
2342 return type_is_unsigned(g.tc.parse_type(node.value))
2343 }
2344 }
2345 return type_is_unsigned(g.tc.resolve_type(id))
2346}
2347
2348// decl_is_unsigned resolves the signedness of a declared variable's initializer.
2349fn (mut g Gen) decl_is_unsigned(rhs_id flat.NodeId, fallback_typ string) bool {
2350 if g.is_unsigned(rhs_id) {
2351 return true
2352 }
2353 if fallback_typ.len > 0 {
2354 return type_is_unsigned(g.tc.parse_type(fallback_typ))
2355 }
2356 return false
2357}
2358
2359fn type_is_unsigned(t_ types.Type) bool {
2360 t := unalias(t_)
2361 if t is types.Primitive {
2362 return t.props.has(.unsigned)
2363 }
2364 return t is types.USize
2365}
2366
2367// narrow_width returns 8 or 16 for sub-32-bit integer types (which the backend
2368// stores in an i32 and must mask/sign-extend), or 32 otherwise.
2369fn narrow_width(t_ types.Type) int {
2370 t := unalias(t_)
2371 if t is types.Primitive && t.props.has(.integer) {
2372 if t.size == 8 {
2373 return 8
2374 }
2375 if t.size == 16 {
2376 return 16
2377 }
2378 }
2379 return 32
2380}
2381
2382fn (mut g Gen) is_signed(id flat.NodeId) bool {
2383 return !g.is_unsigned(id)
2384}
2385
2386fn (mut g Gen) coerce(from WType, to WType, signed bool) {
2387 if from == to || from == .void || to == .void {
2388 return
2389 }
2390 match to {
2391 .i64 {
2392 if from == .i32 {
2393 g.cur.raw(if signed { u8(0xac) } else { u8(0xad) }) // i64.extend_i32_s/u
2394 } else if from == .f32 || from == .f64 {
2395 g.emit_float_to_int(from, .i64, signed)
2396 }
2397 }
2398 .i32 {
2399 if from == .i64 {
2400 g.cur.raw(0xa7) // i32.wrap_i64
2401 } else if from == .f32 || from == .f64 {
2402 g.emit_float_to_int(from, .i32, signed)
2403 }
2404 }
2405 .f64 {
2406 match from {
2407 .i32 { g.cur.raw(if signed { u8(0xb7) } else { u8(0xb8) }) }
2408 .i64 { g.cur.raw(if signed { u8(0xb9) } else { u8(0xba) }) }
2409 .f32 { g.cur.raw(0xbb) } // f64.promote_f32
2410 else {}
2411 }
2412 }
2413 .f32 {
2414 match from {
2415 .i32 { g.cur.raw(if signed { u8(0xb2) } else { u8(0xb3) }) }
2416 .i64 { g.cur.raw(if signed { u8(0xb4) } else { u8(0xb5) }) }
2417 .f64 { g.cur.raw(0xb6) } // f32.demote_f64
2418 else {}
2419 }
2420 }
2421 else {}
2422 }
2423}
2424
2425fn (mut g Gen) emit_arith(op flat.Op, w WType, signed bool) {
2426 if op in [.left_shift, .right_shift, .right_shift_unsigned] {
2427 g.emit_shift_op(op, w, w, signed)
2428 return
2429 }
2430 g.cur.raw(arith_op(op, w, signed))
2431}
2432
2433// emit_shift_with_count generates the (already-on-stack value's) shift count at
2434// its natural width and emits the shift. The result keeps the value's width
2435// while V permits a wider shift count, so value and count widths may differ.
2436fn (mut g Gen) emit_shift_with_count(op flat.Op, value_w WType, rhs_id flat.NodeId, signed bool) {
2437 count_w := if g.expr_wtype(rhs_id, '') == .i64 { WType.i64 } else { WType.i32 }
2438 g.gen_expr_as(rhs_id, count_w)
2439 g.emit_shift_op(op, value_w, count_w, signed)
2440}
2441
2442// emit_shift_op emits a shift with V's over-width semantics: WASM masks the
2443// count modulo the value width, but V yields 0 once the (full-width) count
2444// reaches the value width. Stack on entry: [value(value_w), count(count_w)];
2445// on exit: [result(value_w)].
2446fn (mut g Gen) emit_shift_op(op flat.Op, value_w WType, count_w WType, signed bool) {
2447 tmp := g.alloc_temp(count_w)
2448 g.cur.local_tee(tmp) // keep the original count, also store it
2449 // The WASM shift opcode needs the count at the value width.
2450 if count_w == .i64 && value_w == .i32 {
2451 g.cur.raw(0xa7) // i32.wrap_i64
2452 } else if count_w == .i32 && value_w == .i64 {
2453 g.cur.raw(0xad) // i64.extend_i32_u
2454 }
2455 g.cur.raw(arith_op(op, value_w, signed)) // value << (count mod value width)
2456 // result = (count < value_width) ? shifted : 0, comparing the full count.
2457 // V promotes narrow types to int for shifts, so the threshold is the
2458 // computation (WASM) width, not the declared 8/16 (matches v1: i8(-1) >> 8
2459 // stays -1, while u32 << 32 and u64 << 64 become 0).
2460 width := if value_w == .i64 { 64 } else { 32 }
2461 if value_w == .i64 {
2462 g.cur.i64_const(0)
2463 } else {
2464 g.cur.i32_const(0)
2465 }
2466 g.cur.local_get(tmp)
2467 if count_w == .i64 {
2468 g.cur.i64_const(width)
2469 g.cur.raw(0x54) // i64.lt_u
2470 } else {
2471 g.cur.i32_const(width)
2472 g.cur.raw(0x49) // i32.lt_u
2473 }
2474 g.cur.raw(0x1b) // select
2475}
2476
2477// emit_float_to_int truncates a float to an integer with V's saturating cast
2478// semantics (NaN -> 0, out-of-range saturates to the target min/max), using the
2479// target type's signedness rather than the source's.
2480fn (mut g Gen) emit_float_to_int(from WType, to WType, signed bool) {
2481 g.cur.raw(0xfc) // saturating-truncation prefix
2482 op2 := if to == .i64 {
2483 if from == .f32 {
2484 if signed { u8(0x04) } else { u8(0x05) }
2485 } else {
2486 if signed { u8(0x06) } else { u8(0x07) }
2487 }
2488 } else {
2489 if from == .f32 {
2490 if signed { u8(0x00) } else { u8(0x01) }
2491 } else {
2492 if signed { u8(0x02) } else { u8(0x03) }
2493 }
2494 }
2495 g.cur.raw(op2)
2496}
2497
2498// emit_narrow masks (unsigned) or sign-extends (signed) the i32 on the stack to
2499// a sub-32-bit width; a no-op for 32/64-bit values.
2500fn (mut g Gen) emit_narrow(width int, unsigned bool) {
2501 match width {
2502 8 {
2503 if unsigned {
2504 g.cur.i32_const(0xff)
2505 g.cur.raw(0x71) // i32.and
2506 } else {
2507 g.cur.raw(0xc0) // i32.extend8_s
2508 }
2509 }
2510 16 {
2511 if unsigned {
2512 g.cur.i32_const(0xffff)
2513 g.cur.raw(0x71) // i32.and
2514 } else {
2515 g.cur.raw(0xc1) // i32.extend16_s
2516 }
2517 }
2518 else {}
2519 }
2520}
2521
2522fn (mut g Gen) warn(msg string) {
2523 g.warnings << msg
2524}
2525
2526// ---- free helpers ----
2527
2528fn wt_valtype(w WType) u8 {
2529 return match w {
2530 .i32, .void { valtype_i32 }
2531 .i64 { valtype_i64 }
2532 .f32 { valtype_f32 }
2533 .f64 { valtype_f64 }
2534 }
2535}
2536
2537// qualified_fn_key keys fn_index/fn_ret/fn_params: a bare name for the main
2538// module, `module.name` for imported modules (matching call-site selectors).
2539fn qualified_fn_key(mod string, name string) string {
2540 if mod == '' || mod == 'main' {
2541 return name
2542 }
2543 return '${mod}.${name}'
2544}
2545
2546// export_fn_name is the wasm export name for a function (`.` is replaced so the
2547// name is convenient to reference from a host like JS).
2548fn export_fn_name(mod string, name string) string {
2549 if mod == '' || mod == 'main' {
2550 return name
2551 }
2552 return '${mod.replace('.', '__')}__${name}'
2553}
2554
2555// unalias resolves a (possibly chained) numeric type alias to its base type so
2556// scalar aliases like `type Byte = u8` classify as their underlying type.
2557fn unalias(t types.Type) types.Type {
2558 mut cur := t
2559 for {
2560 if cur is types.Alias {
2561 cur = cur.base_type
2562 } else {
2563 break
2564 }
2565 }
2566 return cur
2567}
2568
2569// prim_wtype maps a primitive/numeric/bool V type to a WASM value type, or none
2570// for aggregate/reference types the backend cannot yet represent inline.
2571fn prim_wtype(t_ types.Type) ?WType {
2572 t := unalias(t_)
2573 if t is types.Primitive {
2574 if t.props.has(.float) {
2575 return if t.size == 32 { WType.f32 } else { WType.f64 }
2576 }
2577 if t.props.has(.integer) {
2578 return if t.size == 64 { WType.i64 } else { WType.i32 }
2579 }
2580 if t.props.has(.boolean) {
2581 return WType.i32
2582 }
2583 return WType.i32
2584 }
2585 if t is types.Char || t is types.Rune || t is types.ISize || t is types.USize {
2586 return WType.i32
2587 }
2588 return none
2589}
2590
2591const signed_int_format_fns = ['strconv__format_int', 'int_str', 'i64_str', 'i8_str', 'i16_str',
2592 'i32_str', 'isize_str']
2593
2594const unsigned_int_format_fns = ['strconv__format_uint', 'u8_str', 'u16_str', 'u32_str', 'u64_str',
2595 'usize_str']
2596
2597const bool_format_fns = ['bool.str', 'bool__str', 'bool_str']
2598
2599fn compound_to_op(op flat.Op) flat.Op {
2600 return match op {
2601 .plus_assign { flat.Op.plus }
2602 .minus_assign { flat.Op.minus }
2603 .mul_assign { flat.Op.mul }
2604 .div_assign { flat.Op.div }
2605 .mod_assign { flat.Op.mod }
2606 .amp_assign { flat.Op.amp }
2607 .pipe_assign { flat.Op.pipe }
2608 .xor_assign { flat.Op.xor }
2609 .left_shift_assign { flat.Op.left_shift }
2610 .right_shift_assign { flat.Op.right_shift }
2611 .right_shift_unsigned_assign { flat.Op.right_shift_unsigned }
2612 else { flat.Op.plus }
2613 }
2614}
2615
2616fn arith_op(op flat.Op, w WType, signed bool) u8 {
2617 match w {
2618 .i64 {
2619 return match op {
2620 .plus {
2621 0x7c
2622 }
2623 .minus {
2624 0x7d
2625 }
2626 .mul {
2627 0x7e
2628 }
2629 .div {
2630 if signed {
2631 u8(0x7f)
2632 } else {
2633 u8(0x80)
2634 }
2635 }
2636 .mod {
2637 if signed {
2638 u8(0x81)
2639 } else {
2640 u8(0x82)
2641 }
2642 }
2643 .amp {
2644 0x83
2645 }
2646 .pipe {
2647 0x84
2648 }
2649 .xor {
2650 0x85
2651 }
2652 .left_shift {
2653 0x86
2654 }
2655 .right_shift {
2656 0x87
2657 }
2658 .right_shift_unsigned {
2659 0x88
2660 }
2661 else {
2662 0x7c
2663 }
2664 }
2665 }
2666 .f64 {
2667 return match op {
2668 .plus { 0xa0 }
2669 .minus { 0xa1 }
2670 .mul { 0xa2 }
2671 .div { 0xa3 }
2672 else { 0xa0 }
2673 }
2674 }
2675 .f32 {
2676 return match op {
2677 .plus { 0x92 }
2678 .minus { 0x93 }
2679 .mul { 0x94 }
2680 .div { 0x95 }
2681 else { 0x92 }
2682 }
2683 }
2684 else {
2685 return match op {
2686 .plus {
2687 0x6a
2688 }
2689 .minus {
2690 0x6b
2691 }
2692 .mul {
2693 0x6c
2694 }
2695 .div {
2696 if signed {
2697 u8(0x6d)
2698 } else {
2699 u8(0x6e)
2700 }
2701 }
2702 .mod {
2703 if signed {
2704 u8(0x6f)
2705 } else {
2706 u8(0x70)
2707 }
2708 }
2709 .amp {
2710 0x71
2711 }
2712 .pipe {
2713 0x72
2714 }
2715 .xor {
2716 0x73
2717 }
2718 .left_shift {
2719 0x74
2720 }
2721 .right_shift {
2722 if signed {
2723 u8(0x75)
2724 } else {
2725 u8(0x76)
2726 }
2727 }
2728 .right_shift_unsigned {
2729 0x76
2730 }
2731 else {
2732 0x6a
2733 }
2734 }
2735 }
2736 }
2737}
2738
2739fn cmp_op(op flat.Op, w WType, signed bool) u8 {
2740 match w {
2741 .i64 {
2742 return match op {
2743 .eq {
2744 0x51
2745 }
2746 .ne {
2747 0x52
2748 }
2749 .lt {
2750 if signed {
2751 u8(0x53)
2752 } else {
2753 u8(0x54)
2754 }
2755 }
2756 .gt {
2757 if signed {
2758 u8(0x55)
2759 } else {
2760 u8(0x56)
2761 }
2762 }
2763 .le {
2764 if signed {
2765 u8(0x57)
2766 } else {
2767 u8(0x58)
2768 }
2769 }
2770 .ge {
2771 if signed {
2772 u8(0x59)
2773 } else {
2774 u8(0x5a)
2775 }
2776 }
2777 else {
2778 0x51
2779 }
2780 }
2781 }
2782 .f64 {
2783 return match op {
2784 .eq { 0x61 }
2785 .ne { 0x62 }
2786 .lt { 0x63 }
2787 .gt { 0x64 }
2788 .le { 0x65 }
2789 .ge { 0x66 }
2790 else { 0x61 }
2791 }
2792 }
2793 .f32 {
2794 return match op {
2795 .eq { 0x5b }
2796 .ne { 0x5c }
2797 .lt { 0x5d }
2798 .gt { 0x5e }
2799 .le { 0x5f }
2800 .ge { 0x60 }
2801 else { 0x5b }
2802 }
2803 }
2804 else {
2805 return match op {
2806 .eq {
2807 0x46
2808 }
2809 .ne {
2810 0x47
2811 }
2812 .lt {
2813 if signed {
2814 u8(0x48)
2815 } else {
2816 u8(0x49)
2817 }
2818 }
2819 .gt {
2820 if signed {
2821 u8(0x4a)
2822 } else {
2823 u8(0x4b)
2824 }
2825 }
2826 .le {
2827 if signed {
2828 u8(0x4c)
2829 } else {
2830 u8(0x4d)
2831 }
2832 }
2833 .ge {
2834 if signed {
2835 u8(0x4e)
2836 } else {
2837 u8(0x4f)
2838 }
2839 }
2840 else {
2841 0x46
2842 }
2843 }
2844 }
2845 }
2846}
2847
2848fn parse_int_literal(s_ string) i64 {
2849 mut s := s_.replace('_', '')
2850 mut neg := false
2851 if s.starts_with('-') {
2852 neg = true
2853 s = s[1..]
2854 }
2855 mut val := i64(0)
2856 if s.starts_with('0x') || s.starts_with('0X') {
2857 val = i64(s[2..].parse_uint(16, 64) or { 0 })
2858 } else if s.starts_with('0o') || s.starts_with('0O') {
2859 val = i64(s[2..].parse_uint(8, 64) or { 0 })
2860 } else if s.starts_with('0b') || s.starts_with('0B') {
2861 val = i64(s[2..].parse_uint(2, 64) or { 0 })
2862 } else {
2863 // parse_uint preserves the full u64 bit pattern; string.i64() would
2864 // saturate values above i64 max (e.g. u64(18446744073709551615)).
2865 val = i64(s.parse_uint(10, 64) or { 0 })
2866 }
2867 return if neg { -val } else { val }
2868}
2869
2870// char_literal_value decodes a char-literal node value to its code point.
2871fn char_literal_value(s string) i64 {
2872 if s.len == 0 {
2873 return 0
2874 }
2875 if s[0] == `\\` && s.len >= 2 {
2876 return match s[1] {
2877 `n` { i64(10) }
2878 `t` { i64(9) }
2879 `r` { i64(13) }
2880 `\\` { i64(92) }
2881 `'` { i64(39) }
2882 `"` { i64(34) }
2883 `0` { i64(0) }
2884 `a` { i64(7) }
2885 `b` { i64(8) }
2886 `f` { i64(12) }
2887 `v` { i64(11) }
2888 else { i64(s[1]) }
2889 }
2890 }
2891 if s.len == 1 {
2892 return i64(s[0])
2893 }
2894 // multi-byte UTF-8 rune: use the first code point
2895 runes := s.runes()
2896 return if runes.len > 0 { i64(runes[0]) } else { i64(s[0]) }
2897}
2898