Skip to content

Commit 2aba035

Browse files
committed
Fix register preservation around ccall
We need to push pairs of registers so that code is more compact. Also don't try to preserve the return value of a ccall if the live range is dead
1 parent 02c42a5 commit 2aba035

2 files changed

Lines changed: 68 additions & 57 deletions

File tree

zjit/src/backend/arm64/mod.rs

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2741,17 +2741,16 @@ mod tests {
27412741
0x4: mov x1, #2
27422742
0x8: mov x2, #3
27432743
0xc: mov x3, #4
2744-
0x10: mov x4, x0
2745-
0x14: stp x2, x1, [sp, #-0x10]!
2746-
0x18: stp x4, x3, [sp, #-0x10]!
2747-
0x1c: mov x16, #0
2748-
0x20: blr x16
2749-
0x24: ldp x4, x3, [sp], #0x10
2750-
0x28: ldp x2, x1, [sp], #0x10
2751-
0x2c: adds x4, x4, x1
2752-
0x30: adds x2, x2, x3
2744+
0x10: stp x1, x0, [sp, #-0x10]!
2745+
0x14: stp x3, x2, [sp, #-0x10]!
2746+
0x18: mov x16, #0
2747+
0x1c: blr x16
2748+
0x20: ldp x3, x2, [sp], #0x10
2749+
0x24: ldp x1, x0, [sp], #0x10
2750+
0x28: adds x0, x0, x1
2751+
0x2c: adds x0, x2, x3
27532752
");
2754-
assert_snapshot!(cb.hexdump(), @"200080d2410080d2620080d2830080d2e40300aae207bfa9e40fbfa9100080d200023fd6e40fc1a8e207c1a8840001ab420003ab");
2753+
assert_snapshot!(cb.hexdump(), @"200080d2410080d2620080d2830080d2e103bfa9e30bbfa9100080d200023fd6e30bc1a8e103c1a8000001ab400003ab");
27552754
}
27562755

27572756
#[test]
@@ -2776,20 +2775,19 @@ mod tests {
27762775
0x8: mov x2, #3
27772776
0xc: mov x3, #4
27782777
0x10: mov x4, #5
2779-
0x14: mov x5, x0
2780-
0x18: stp x2, x1, [sp, #-0x10]!
2781-
0x1c: stp x4, x3, [sp, #-0x10]!
2782-
0x20: str x5, [sp, #-0x10]!
2783-
0x24: mov x16, #0
2784-
0x28: blr x16
2785-
0x2c: ldr x5, [sp], #0x10
2786-
0x30: ldp x4, x3, [sp], #0x10
2787-
0x34: ldp x2, x1, [sp], #0x10
2788-
0x38: adds x5, x5, x1
2789-
0x3c: adds x0, x2, x3
2790-
0x40: adds x2, x2, x4
2778+
0x14: stp x1, x0, [sp, #-0x10]!
2779+
0x18: stp x3, x2, [sp, #-0x10]!
2780+
0x1c: str x4, [sp, #-0x10]!
2781+
0x20: mov x16, #0
2782+
0x24: blr x16
2783+
0x28: ldr x4, [sp], #0x10
2784+
0x2c: ldp x3, x2, [sp], #0x10
2785+
0x30: ldp x1, x0, [sp], #0x10
2786+
0x34: adds x0, x0, x1
2787+
0x38: adds x0, x2, x3
2788+
0x3c: adds x0, x2, x4
27912789
");
2792-
assert_snapshot!(cb.hexdump(), @"200080d2410080d2620080d2830080d2a40080d2e50300aae207bfa9e40fbfa9e50f1ff8100080d200023fd6e50741f8e40fc1a8e207c1a8a50001ab400003ab420004ab");
2790+
assert_snapshot!(cb.hexdump(), @"200080d2410080d2620080d2830080d2a40080d2e103bfa9e30bbfa9e40f1ff8100080d200023fd6e40741f8e30bc1a8e103c1a8000001ab400003ab400004ab");
27932791
}
27942792

27952793
#[test]

zjit/src/backend/lir.rs

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,11 @@ impl Assembler
24522452
for (insn, insn_id) in old_insns.into_iter().zip(old_ids.into_iter()) {
24532453
if let Insn::CCall { opnds, out, start_marker, end_marker, fptr } = insn {
24542454
let insn_number = insn_id.map(|id| id.0).unwrap_or(0);
2455+
let call_result_live = out.is_vreg()
2456+
&& intervals[out.vreg_idx().0]
2457+
.range
2458+
.end
2459+
.is_some_and(|end| end > insn_number);
24552460

24562461
// Find survivors: intervals that survive this Call instruction
24572462
// We need to preserve the "surviving" registers past the ccall,
@@ -2467,19 +2472,26 @@ impl Assembler
24672472
.map(|interval| interval.id)
24682473
.collect();
24692474

2470-
// Push all survivors on the stack
2475+
let survivor_regs: Vec<Opnd> = survivors.iter()
2476+
.map(|&s| match assignments[s].unwrap() {
2477+
Allocation::Reg(n) => Opnd::Reg(ALLOC_REGS[n]),
2478+
Allocation::Fixed(reg) => Opnd::Reg(reg),
2479+
_ => unreachable!(),
2480+
})
2481+
.collect();
2482+
let survivor_push_groups: Vec<Vec<Opnd>> = survivor_regs
2483+
.chunks(2)
2484+
.map(|group| group.to_vec())
2485+
.collect();
2486+
2487+
// Push all survivors on the stack, pairing adjacent pushes when possible.
24712488
let needs_alignment = cfg!(target_arch = "x86_64") && survivors.len() % 2 == 1;
2472-
for &s in &survivors {
2473-
let reg_n = match assignments[s].unwrap() {
2474-
Allocation::Reg(n) => n,
2475-
Allocation::Fixed(reg) => {
2476-
new_insns.push(Insn::CPush(Opnd::Reg(reg)));
2477-
new_ids.push(None);
2478-
continue;
2479-
}
2489+
for group in &survivor_push_groups {
2490+
match group.as_slice() {
2491+
[left, right] => new_insns.push(Insn::CPushPair(*left, *right)),
2492+
[reg] => new_insns.push(Insn::CPush(*reg)),
24802493
_ => unreachable!(),
2481-
};
2482-
new_insns.push(Insn::CPush(Opnd::Reg(ALLOC_REGS[reg_n])));
2494+
}
24832495
new_ids.push(None);
24842496
}
24852497
// Maintain 16-byte stack alignment for x86_64
@@ -2542,41 +2554,42 @@ impl Assembler
25422554
}
25432555

25442556
if survivors.is_empty() {
2545-
// No survivors to restore — move result directly to output
2546-
let out = Self::rewritten_opnd(out, assignments);
2547-
new_insns.push(Insn::Mov { dest: out, src: C_RET_OPND });
2548-
new_ids.push(None);
2557+
if call_result_live {
2558+
// No survivors to restore — move result directly to output.
2559+
let out = Self::rewritten_opnd(out, assignments);
2560+
new_insns.push(Insn::Mov { dest: out, src: C_RET_OPND });
2561+
new_ids.push(None);
2562+
}
25492563
} else {
2550-
// Save CCall result to scratch immediately, before pops
2551-
// can clobber either C_RET or the output register.
2552-
new_insns.push(Insn::Mov { dest: Opnd::Reg(SCRATCH_REG), src: C_RET_OPND });
2553-
new_ids.push(None);
2564+
if call_result_live {
2565+
// Save CCall result to scratch immediately, before pops
2566+
// can clobber either C_RET or the output register.
2567+
new_insns.push(Insn::Mov { dest: Opnd::Reg(SCRATCH_REG), src: C_RET_OPND });
2568+
new_ids.push(None);
2569+
}
25542570

25552571
// Pop alignment padding (if needed)
25562572
if needs_alignment {
25572573
new_insns.push(Insn::CPopInto(Opnd::Reg(ALLOC_REGS[0])));
25582574
new_ids.push(None);
25592575
}
25602576

2561-
// Restore all survivors
2562-
for &s in survivors.iter().rev() {
2563-
let reg_n = match assignments[s].unwrap() {
2564-
Allocation::Reg(n) => n,
2565-
Allocation::Fixed(reg) => {
2566-
new_insns.push(Insn::CPopInto(Opnd::Reg(reg)));
2567-
new_ids.push(None);
2568-
continue;
2569-
}
2577+
// Restore all survivors in reverse stack order, pairing adjacent pops when possible.
2578+
for group in survivor_push_groups.iter().rev() {
2579+
match group.as_slice() {
2580+
[left, right] => new_insns.push(Insn::CPopPairInto(*right, *left)),
2581+
[reg] => new_insns.push(Insn::CPopInto(*reg)),
25702582
_ => unreachable!(),
2571-
};
2572-
new_insns.push(Insn::CPopInto(Opnd::Reg(ALLOC_REGS[reg_n])));
2583+
}
25732584
new_ids.push(None);
25742585
}
25752586

2576-
// Move result from scratch to output AFTER all pops
2577-
let out = Self::rewritten_opnd(out, assignments);
2578-
new_insns.push(Insn::Mov { dest: out, src: Opnd::Reg(SCRATCH_REG) });
2579-
new_ids.push(None);
2587+
if call_result_live {
2588+
// Move result from scratch to output AFTER all pops.
2589+
let out = Self::rewritten_opnd(out, assignments);
2590+
new_insns.push(Insn::Mov { dest: out, src: Opnd::Reg(SCRATCH_REG) });
2591+
new_ids.push(None);
2592+
}
25802593
}
25812594
} else {
25822595
new_insns.push(insn);

0 commit comments

Comments
 (0)