@@ -43,6 +43,15 @@ struct Frame {
4343 tag FrameTag
4444}
4545
46+ // VarScope snapshots the local-name bindings so an inner block or for-loop can
47+ // shadow an outer local and have the outer binding restored on scope exit.
48+ struct VarScope {
49+ index map [string ]int
50+ wtype map [string ]WType
51+ unsigned map [string ]bool
52+ widths map [string ]int
53+ }
54+
4655enum FrameTag {
4756 plain
4857 brk
6372 var_index map [string ]int
6473 var_wtype map [string ]WType
6574 var_unsigned map [string ]bool
75+ var_widths map [string ]int // sub-32-bit int locals: 8 or 16; else 32
6676 frames []Frame
6777 cur_ret WType
6878 cur_fn_module string
@@ -327,6 +337,7 @@ fn (mut g Gen) emit_user_fn(f FnInfo) {
327337 g.var_index = map [string ]int {}
328338 g.var_wtype = map [string ]WType{}
329339 g.var_unsigned = map [string ]bool {}
340+ g.var_widths = map [string ]int {}
330341 g.frames = []Frame{}
331342 g.cur_ret = f.ret
332343 g.cur_fn_module = f.module
@@ -340,9 +351,11 @@ fn (mut g Gen) emit_user_fn(f FnInfo) {
340351 continue
341352 }
342353 w := f.params[pi]
354+ pt := g.tc.parse_type (p.typ)
343355 g.var_index[p.value] = pi
344356 g.var_wtype[p.value] = w
345- g.var_unsigned[p.value] = type_is_unsigned (g.tc.parse_type (p.typ))
357+ g.var_unsigned[p.value] = type_is_unsigned (pt)
358+ g.var_widths[p.value] = narrow_width (pt)
346359 wparams << wt_valtype (w)
347360 pi++
348361 }
@@ -377,15 +390,42 @@ fn (mut g Gen) emit_start() int {
377390}
378391
379392// new_local allocates a fresh local beyond the params and records its type.
380- fn (mut g Gen) new_local (name string , w WType, unsigned bool ) int {
393+ fn (mut g Gen) new_local (name string , w WType, unsigned bool , width int ) int {
381394 idx := g.nparams + g.local_types.len
382395 g.local_types << wt_valtype (w)
383396 g.var_index[name] = idx
384397 g.var_wtype[name] = w
385398 g.var_unsigned[name] = unsigned
399+ g.var_widths[name] = width
400+ return idx
401+ }
402+
403+ // alloc_temp allocates an anonymous function-level local (e.g. for the shift
404+ // over-width guard) and returns its index.
405+ fn (mut g Gen) alloc_temp (w WType) int {
406+ idx := g.nparams + g.local_types.len
407+ g.local_types << wt_valtype (w)
386408 return idx
387409}
388410
411+ // snapshot_scope / restore_scope save and restore local-name bindings so inner
412+ // declarations that shadow an outer local do not leak past their scope.
413+ fn (g &Gen) snapshot_scope () VarScope {
414+ return VarScope{
415+ index: g.var_index.clone ()
416+ wtype: g.var_wtype.clone ()
417+ unsigned: g.var_unsigned.clone ()
418+ widths: g.var_widths.clone ()
419+ }
420+ }
421+
422+ fn (mut g Gen) restore_scope (s VarScope) {
423+ g.var_index = s.index.clone ()
424+ g.var_wtype = s.wtype.clone ()
425+ g.var_unsigned = s.unsigned.clone ()
426+ g.var_widths = s.widths.clone ()
427+ }
428+
389429// ---- statements ----
390430
391431fn (mut g Gen) gen_stmt (id flat.NodeId) {
@@ -396,9 +436,11 @@ fn (mut g Gen) gen_stmt(id flat.NodeId) {
396436 match node.kind {
397437 .fn_decl, .c_fn_decl {}
398438 .block {
439+ saved := g.snapshot_scope ()
399440 for i in 0 .. node.children_count {
400441 g.gen_stmt (g.a.child (& node, i))
401442 }
443+ g.restore_scope (saved)
402444 }
403445 .expr_stmt {
404446 child_id := g.a.child (& node, 0 )
@@ -447,9 +489,11 @@ fn (mut g Gen) gen_decl_assign(node flat.Node) {
447489 rhs_id := g.a.child (& node, i + 1 )
448490 w := g.expr_wtype (rhs_id, node.typ)
449491 uns := g.decl_is_unsigned (rhs_id, node.typ)
492+ width := g.decl_width (rhs_id, node.typ)
450493 if lhs.kind == .ident {
451- idx := g.new_local (lhs.value, w, uns)
494+ idx := g.new_local (lhs.value, w, uns, width )
452495 g.gen_expr_as (rhs_id, w)
496+ g.narrow_for_local (lhs.value)
453497 g.cur.local_set (idx)
454498 }
455499 i + = 2
@@ -472,6 +516,7 @@ fn (mut g Gen) gen_assign(node flat.Node) {
472516 signed := ! g.var_unsigned[lhs.value]
473517 g.emit_arith (compound_to_op (node.op), w, signed)
474518 }
519+ g.narrow_for_local (lhs.value)
475520 g.cur.local_set (idx)
476521 } else {
477522 g.warn ('unsupported assign target' )
@@ -480,34 +525,65 @@ fn (mut g Gen) gen_assign(node flat.Node) {
480525 }
481526}
482527
528+ // narrow_for_local masks/sign-extends the value on the stack to the local's
529+ // declared sub-32-bit width before it is stored.
530+ fn (mut g Gen) narrow_for_local (name string ) {
531+ width := g.var_widths[name]
532+ if width == 8 || width == 16 {
533+ g.emit_narrow (width, g.var_unsigned[name])
534+ }
535+ }
536+
537+ // decl_width resolves the declared sub-32-bit width (8/16) of an initializer.
538+ fn (mut g Gen) decl_width (rhs_id flat.NodeId, fallback_typ string ) int {
539+ w := narrow_width (g.tc.resolve_type (rhs_id))
540+ if w != 32 {
541+ return w
542+ }
543+ if fallback_typ.len > 0 {
544+ return narrow_width (g.tc.parse_type (fallback_typ))
545+ }
546+ return 32
547+ }
548+
483549fn (mut g Gen) gen_if (node flat.Node) {
484550 cond_id := g.a.child (& node, 0 )
485551 g.gen_expr_as_bool (cond_id)
486552 g.cur.if_void ()
487553 g.frames << Frame{.plain}
488554 then_block := g.a.child_node (& node, 1 )
555+ saved_then := g.snapshot_scope ()
489556 for i in 0 .. then_block.children_count {
490557 g.gen_stmt (g.a.child (then_block, i))
491558 }
559+ g.restore_scope (saved_then)
492560 if node.children_count > 2 {
493561 else_id := g.a.child (& node, 2 )
494562 if int (else_id) > = 0 {
495563 else_node := g.a.nodes[int (else_id)]
496564 g.cur.else_ ()
565+ saved_else := g.snapshot_scope ()
497566 if else_node.kind == .if_expr {
498567 g.gen_if (else_node)
499568 } else if else_node.kind == .block {
500569 for i in 0 .. else_node.children_count {
501570 g.gen_stmt (g.a.child (& else_node, i))
502571 }
503572 }
573+ g.restore_scope (saved_else)
504574 }
505575 }
506576 g.cur.end ()
507577 g.frames.pop ()
508578}
509579
510580fn (mut g Gen) gen_for (node flat.Node) {
581+ // The initializer and body are scoped to the loop; restore outer bindings
582+ // afterwards so a shadowing `for i := ...` or body-local does not leak.
583+ saved := g.snapshot_scope ()
584+ defer {
585+ g.restore_scope (saved)
586+ }
511587 init_node := g.a.child_node (& node, 0 )
512588 cond_id := g.a.child (& node, 1 )
513589 cond_node := g.a.nodes[int (cond_id)]
@@ -710,6 +786,11 @@ fn (mut g Gen) gen_infix(id flat.NodeId, node flat.Node) WType {
710786 g.gen_expr_as (lhs_id, ow)
711787 g.gen_expr_as (rhs_id, ow)
712788 g.emit_arith (op, ow, signed)
789+ // Sub-32-bit results (e.g. u8 + u8) wrap to their declared width in V.
790+ if ow == .i32 {
791+ rt := g.tc.resolve_type (id)
792+ g.emit_narrow (narrow_width (rt), type_is_unsigned (rt))
793+ }
713794 return ow
714795}
715796
@@ -815,14 +896,18 @@ fn (mut g Gen) gen_postfix(node flat.Node) {
815896 g.cur.raw (if inc { u8 (0x6a ) } else { u8 (0x6b ) }) // i32.add/sub
816897 }
817898 }
899+ g.narrow_for_local (target.value)
818900 g.cur.local_set (idx)
819901}
820902
821903fn (mut g Gen) gen_cast (node flat.Node) WType {
822904 child_id := g.a.child (& node, 0 )
823- target := prim_wtype (g.tc.parse_type (node.value)) or { WType.i32 }
905+ tt := g.tc.parse_type (node.value)
906+ target := prim_wtype (tt) or { WType.i32 }
824907 src := g.gen_expr (child_id)
825908 g.coerce (src, target, g.is_signed (child_id))
909+ // Casting to a sub-32-bit type wraps/sign-extends to that width.
910+ g.emit_narrow (narrow_width (tt), type_is_unsigned (tt))
826911 return target
827912}
828913
@@ -1185,6 +1270,20 @@ fn type_is_unsigned(t types.Type) bool {
11851270 return t is types.USize
11861271}
11871272
1273+ // narrow_width returns 8 or 16 for sub-32-bit integer types (which the backend
1274+ // stores in an i32 and must mask/sign-extend), or 32 otherwise.
1275+ fn narrow_width (t types.Type) int {
1276+ if t is types.Primitive && t.props.has (.integer) {
1277+ if t.size == 8 {
1278+ return 8
1279+ }
1280+ if t.size == 16 {
1281+ return 16
1282+ }
1283+ }
1284+ return 32
1285+ }
1286+
11881287fn (mut g Gen) is_signed (id flat.NodeId) bool {
11891288 return ! g.is_unsigned (id)
11901289}
@@ -1233,9 +1332,60 @@ fn (mut g Gen) coerce(from WType, to WType, signed bool) {
12331332}
12341333
12351334fn (mut g Gen) emit_arith (op flat.Op, w WType, signed bool ) {
1335+ if op in [.left_shift, .right_shift, .right_shift_unsigned] {
1336+ g.emit_shift (op, w, signed)
1337+ return
1338+ }
12361339 g.cur.raw (arith_op (op, w, signed))
12371340}
12381341
1342+ // emit_shift emits a shift with V's over-width semantics: WASM masks the count
1343+ // modulo the operand width, but V yields 0 when the count >= the width, so the
1344+ // raw result is selected only while count < width. Stack on entry: [value,
1345+ // count]; on exit: [result].
1346+ fn (mut g Gen) emit_shift (op flat.Op, w WType, signed bool ) {
1347+ count_t := if w == .i64 { WType.i64 } else { WType.i32 }
1348+ tmp := g.alloc_temp (count_t)
1349+ g.cur.local_tee (tmp) // keep the count, also store it
1350+ g.cur.raw (arith_op (op, w, signed)) // value << (count mod width)
1351+ if w == .i64 {
1352+ g.cur.i64_const (0 )
1353+ g.cur.local_get (tmp)
1354+ g.cur.i64_const (64 )
1355+ g.cur.raw (0x54 ) // i64.lt_u
1356+ } else {
1357+ g.cur.i32_const (0 )
1358+ g.cur.local_get (tmp)
1359+ g.cur.i32_const (32 )
1360+ g.cur.raw (0x49 ) // i32.lt_u
1361+ }
1362+ g.cur.raw (0x1b ) // select: (count < width) ? shifted : 0
1363+ }
1364+
1365+ // emit_narrow masks (unsigned) or sign-extends (signed) the i32 on the stack to
1366+ // a sub-32-bit width; a no-op for 32/64-bit values.
1367+ fn (mut g Gen) emit_narrow (width int , unsigned bool ) {
1368+ match width {
1369+ 8 {
1370+ if unsigned {
1371+ g.cur.i32_const (0xff )
1372+ g.cur.raw (0x71 ) // i32.and
1373+ } else {
1374+ g.cur.raw (0xc0 ) // i32.extend8_s
1375+ }
1376+ }
1377+ 16 {
1378+ if unsigned {
1379+ g.cur.i32_const (0xffff )
1380+ g.cur.raw (0x71 ) // i32.and
1381+ } else {
1382+ g.cur.raw (0xc1 ) // i32.extend16_s
1383+ }
1384+ }
1385+ else {}
1386+ }
1387+ }
1388+
12391389fn (mut g Gen) warn (msg string ) {
12401390 g.warnings << msg
12411391}
0 commit comments