Skip to content

ZJIT: Keep a frame pointer and use it for memory params #14019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2025

Conversation

XrXr
Copy link
Member

@XrXr XrXr commented Jul 26, 2025

ZJIT: Keep a frame pointer and use it for memory params

Previously, ZJIT miscompiled the following because of native SP
interference.

def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
a(0,0,0,0,0,0,0, :ok)

Commented problematic disassembly:

; call rb_ary_new_capa
mov x0, #1
mov x16, #0x1278
movk x16, #0x4bc, lsl #16
movk x16, #1, lsl #32
blr x16
; call rb_ary_push
mov x1, x0
str x1, [sp, #-0x10]! ; c_push() from alloc_regs()
mov x0, x1            ; arg0, the array
ldur x1, [sp]         ; meant to be arg1=n8, but sp just moved!
mov x16, #0x3968
movk x16, #0x4bc, lsl #16
movk x16, #1, lsl #32
blr x16

Since the frame pointer stays constant in the body of the function,
static offsets based on it don't run the risk of being invalidated by SP
movements.

Pass the registers to preserve through Insn::FrameSetup. This allows ARM
to use STP and waste no gaps between EC, SP, and CFP.

x86 now preserves and restores RBP since we use it as the frame pointer.
Since all arches now have a frame pointer, remove offset based SP
movement in the epilogue and restore registers using the frame pointer.


Closes #14009.

Frame pointers are in fashion. This opens up a couple future optimizations. We can jump into instead of call into the body of functions in gen_entry(), which gets rid of the extra frame that bb0 sets up. It also completely gets rid of the asm.frame_setup(&[]) frame type, and the funky param write adjustment code in gen_entry(). Also, since frames modulo preserved_regs now have identical epilogue, we can tail dedup them to save memory.

@matzbot matzbot requested a review from a team July 26, 2025 02:34
Previously, ZJIT miscompiled the following because of native SP
interference.

    def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
    a(0,0,0,0,0,0,0, :ok)

Commented problematic disassembly:

    ; call rb_ary_new_capa
    mov x0, #1
    mov x16, #0x1278
    movk x16, #0x4bc, lsl #16
    movk x16, #1, lsl #32
    blr x16
    ; call rb_ary_push
    mov x1, x0
    str x1, [sp, #-0x10]! ; c_push() from alloc_regs()
    mov x0, x1            ; arg0, the array
    ldur x1, [sp]         ; meant to be arg1=n8, but sp just moved!
    mov x16, #0x3968
    movk x16, #0x4bc, lsl #16
    movk x16, #1, lsl #32
    blr x16

Since the frame pointer stays constant in the body of the function,
static offsets based on it don't run the risk of being invalidated by SP
movements.

Pass the registers to preserve through Insn::FrameSetup. This allows ARM
to use STP and waste no gaps between EC, SP, and CFP.

x86 now preserves and restores RBP since we use it as the frame pointer.
Since all arches now have a frame pointer, remove offset based SP
movement in the epilogue and restore registers using the frame pointer.
@XrXr XrXr force-pushed the zjit-welcome-back-frame-pointers branch from 81fc63c to cbd7445 Compare July 26, 2025 16:59
mov(cb, RBP, RSP);
push(cb, RBP);
// and to allow push and pops in the function.
&Insn::FrameSetup { preserved, mut slot_count } => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it a bit more consistent with total_slots, I'm tempted to call it num_slots instead of slot_count.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ayy unless you feel strongly about this I'm going to keep it as is since I prefer noun adjective form for public-ish things like function names and field names.

Copy link
Member

@k0kubun k0kubun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks pretty good. thank you!

@k0kubun
Copy link
Member

k0kubun commented Jul 28, 2025

This opens up a couple future optimizations.

Could you file issues for them? I'm particularly interested in how the removal of the extra frame setup works, but this PR is probably not the right place to discuss that.

@XrXr XrXr merged commit ff428b4 into ruby:master Jul 28, 2025
84 checks passed
@XrXr XrXr deleted the zjit-welcome-back-frame-pointers branch July 28, 2025 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants