Skip to content

Commit 40968e5

Browse files
committed
test(#204): native-lane backfill for fusion pass trigger decisions
Adds bytecode-shape tests confirming the exact trigger source for every previously-zero-native-coverage fused op: get_global_const_{eq,add,lt,sub}, add_ret, get_local_get_field, get_local_ret, get_global_const_lt_jif_pop, get_local_const_gt_jif_pop, the C-for quint (get_local_const_lt_jif_pop_jump), close_upvalue_loop, inc_global_const, local_add_const(_loop), set_global_loop, local_add_local, local_add_field — plus one legality boundary (get_local_const_add refuses to fuse into local_add_const across mismatched read/write slots). Closes the fusion-pass half of #204.
1 parent 6685657 commit 40968e5

1 file changed

Lines changed: 238 additions & 0 deletions

File tree

src/compiler_test.zig

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3340,3 +3340,241 @@ test "compiler: multi named-return with mixed erased and boxed types constructs
33403340
try std.testing.expectEqual(@as(usize, 3), countOp(c, .call));
33413341
try std.testing.expect(countOp(c, .validate_named_range) >= 1);
33423342
}
3343+
3344+
// ── #204 native-lane backfill: fusion pass trigger decisions ──────────────
3345+
//
3346+
// See lang/fusion_pass.zig's module doc and dev-docs/design/vm-architecture.md
3347+
// §6: every fusion is a legality-checked rewrite of adjacent core ops into a
3348+
// VM-private fused op, run by `compile()`/`compileWithSession()` (both call
3349+
// fusion_pass.fuse() already, so every test in this file already observes
3350+
// post-fusion bytecode). These tests assert the *specific* trigger shape for
3351+
// each fused op that had zero direct native coverage before this pass, plus
3352+
// one legality-boundary case (same_slot). Had zero native coverage (#204).
3353+
3354+
test "fusion: global read + constant binop fuses to get_global_const_X" {
3355+
var rt = try setup();
3356+
defer rt.deinit();
3357+
try compile(&rt,
3358+
\\g := 10
3359+
\\func geqc() bool { return g == 5 }
3360+
\\func gaddc() int { return g + 5 }
3361+
\\func gltc() bool { return g < 5 }
3362+
\\func gsubc() int { return g - 5 }
3363+
);
3364+
const c = rt.chunk_state;
3365+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_global_const_eq));
3366+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_global_const_add));
3367+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_global_const_lt));
3368+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_global_const_sub));
3369+
}
3370+
3371+
test "fusion: add immediately before ret fuses to add_ret" {
3372+
// Two different locals summed and returned directly: neither get_local
3373+
// pairs with the other (no pairFusion rule joins two get_locals), so the
3374+
// only fusable pair left is the trailing add+ret.
3375+
var rt = try setup();
3376+
defer rt.deinit();
3377+
try compile(&rt,
3378+
\\func addTwo(a int, b int) int { return a + b }
3379+
);
3380+
const c = rt.chunk_state;
3381+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .add_ret));
3382+
try std.testing.expectEqual(@as(usize, 0), countOp(c, .add));
3383+
}
3384+
3385+
test "fusion: local struct field read fuses to get_local_get_field" {
3386+
var rt = try setup();
3387+
defer rt.deinit();
3388+
try compile(&rt,
3389+
\\type Point struct { x int, y int }
3390+
\\func fieldRead(p Point) int { return p.x }
3391+
);
3392+
const c = rt.chunk_state;
3393+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_local_get_field));
3394+
}
3395+
3396+
test "fusion: bare local return fuses to get_local_ret" {
3397+
var rt = try setup();
3398+
defer rt.deinit();
3399+
try compile(&rt,
3400+
\\func identity(x int) int { return x }
3401+
);
3402+
const c = rt.chunk_state;
3403+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_local_ret));
3404+
}
3405+
3406+
test "fusion: while-loop condition on a global with a constant fuses to get_global_const_lt_jif_pop" {
3407+
var rt = try setup();
3408+
defer rt.deinit();
3409+
try compile(&rt,
3410+
\\g := 10
3411+
\\func gforlt() int {
3412+
\\ i := 0
3413+
\\ for g < 100 {
3414+
\\ i = i + 1
3415+
\\ }
3416+
\\ return i
3417+
\\}
3418+
);
3419+
const c = rt.chunk_state;
3420+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_global_const_lt_jif_pop));
3421+
}
3422+
3423+
test "fusion: while-loop condition on a local with > and a constant fuses to get_local_const_gt_jif_pop" {
3424+
var rt = try setup();
3425+
defer rt.deinit();
3426+
try compile(&rt,
3427+
\\func countdown() int {
3428+
\\ i := 5
3429+
\\ for i > 0 {
3430+
\\ i = i - 1
3431+
\\ }
3432+
\\ return i
3433+
\\}
3434+
);
3435+
const c = rt.chunk_state;
3436+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_local_const_gt_jif_pop));
3437+
}
3438+
3439+
test "fusion: C-style for-loop header (local < constant) fuses to the quint get_local_const_lt_jif_pop_jump" {
3440+
// The jump-to-body-first layout a C-for uses (skip the post-statement on
3441+
// the loop's first iteration) is the one shape that places an
3442+
// unconditional jump directly after the get_local_const_lt_jif_pop quad,
3443+
// letting it grow into the 13-byte quint.
3444+
var rt = try setup();
3445+
defer rt.deinit();
3446+
try compile(&rt,
3447+
\\func cfor() int {
3448+
\\ x := 0
3449+
\\ for i := 0; i < 5; i++ {
3450+
\\ x = x + i
3451+
\\ }
3452+
\\ return x
3453+
\\}
3454+
);
3455+
const c = rt.chunk_state;
3456+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_local_const_lt_jif_pop_jump));
3457+
}
3458+
3459+
test "fusion: C-style for-loop's per-iteration capture close fuses to close_upvalue_loop" {
3460+
// Every iteration of a C-for rebinds the loop variable (for capture
3461+
// correctness), closing it right before the loop's back-edge — this is
3462+
// what supplies close_upvalue_loop's trigger, independent of whether the
3463+
// loop body actually captures the variable in a closure.
3464+
var rt = try setup();
3465+
defer rt.deinit();
3466+
try compile(&rt,
3467+
\\func cfor() int {
3468+
\\ x := 0
3469+
\\ for i := 0; i < 5; i++ {
3470+
\\ x = x + i
3471+
\\ }
3472+
\\ return x
3473+
\\}
3474+
);
3475+
const c = rt.chunk_state;
3476+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .close_upvalue_loop));
3477+
}
3478+
3479+
test "fusion: top-level global compound-add fuses to inc_global_const" {
3480+
var rt = try setup();
3481+
defer rt.deinit();
3482+
try compile(&rt,
3483+
\\g := 10
3484+
\\func incGlobal() { g += 5 }
3485+
);
3486+
const c = rt.chunk_state;
3487+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .inc_global_const));
3488+
}
3489+
3490+
test "fusion: loop-body compound-add on a local fuses to local_add_const, and to local_add_const_loop when it directly precedes the back-edge" {
3491+
var rt = try setup();
3492+
defer rt.deinit();
3493+
try compile(&rt,
3494+
\\func loopLocalAdd(n int) int {
3495+
\\ x := 0
3496+
\\ i := 0
3497+
\\ for i < n {
3498+
\\ x += 3
3499+
\\ i = i + 1
3500+
\\ }
3501+
\\ return x
3502+
\\}
3503+
);
3504+
const c = rt.chunk_state;
3505+
// x += 3 isn't the loop's last statement (i = i + 1 follows it), so it
3506+
// fuses only as far as local_add_const; the trailing i = i + 1 IS last,
3507+
// so it grows the extra step to local_add_const_loop.
3508+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .local_add_const));
3509+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .local_add_const_loop));
3510+
}
3511+
3512+
test "fusion: global assignment as a loop's last statement fuses to set_global_loop" {
3513+
var rt = try setup();
3514+
defer rt.deinit();
3515+
try compile(&rt,
3516+
\\g := 0
3517+
\\func loopGlobalAssign(n int) int {
3518+
\\ i := 0
3519+
\\ for i < n {
3520+
\\ i = i + 1
3521+
\\ g = i
3522+
\\ }
3523+
\\ return g
3524+
\\}
3525+
);
3526+
const c = rt.chunk_state;
3527+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .set_global_loop));
3528+
}
3529+
3530+
test "fusion: local = local + local fuses to the 4-window local_add_local" {
3531+
var rt = try setup();
3532+
defer rt.deinit();
3533+
try compile(&rt,
3534+
\\func addLocal() int {
3535+
\\ a := 1
3536+
\\ b := 2
3537+
\\ a += b
3538+
\\ return a
3539+
\\}
3540+
);
3541+
const c = rt.chunk_state;
3542+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .local_add_local));
3543+
}
3544+
3545+
test "fusion: local += struct field fuses to the 4-window local_add_field" {
3546+
var rt = try setup();
3547+
defer rt.deinit();
3548+
try compile(&rt,
3549+
\\type Point struct { x int, y int }
3550+
\\func addField() int {
3551+
\\ x := 1
3552+
\\ p := Point{x: 1, y: 2}
3553+
\\ x += p.y
3554+
\\ return x
3555+
\\}
3556+
);
3557+
const c = rt.chunk_state;
3558+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .local_add_field));
3559+
}
3560+
3561+
test "fusion: get_local_const_add does not fuse into local_add_const across mismatched slots" {
3562+
// Legality boundary: local_add_const additionally requires the read slot
3563+
// and the write slot to match (same_slot in pairFusionFull) — a plain
3564+
// reassignment into a *different* local must leave get_local_const_add
3565+
// and set_local as two separate instructions, not one fused op.
3566+
var rt = try setup();
3567+
defer rt.deinit();
3568+
try compile(&rt,
3569+
\\func f() int {
3570+
\\ x := 10
3571+
\\ y := 0
3572+
\\ y = x + 5
3573+
\\ return y
3574+
\\}
3575+
);
3576+
const c = rt.chunk_state;
3577+
try std.testing.expectEqual(@as(usize, 1), countOp(c, .get_local_const_add));
3578+
try std.testing.expectEqual(@as(usize, 0), countOp(c, .local_add_const));
3579+
try std.testing.expect(try hasOp(c, .set_local));
3580+
}

0 commit comments

Comments
 (0)