Skip to content

Commit b2ef33b

Browse files
committed
ZJIT: Redo JIT function native stack frame layout
Previously, gen_param() access slots at `SP-x` for `x≥0` after subtracting from SP, so it was accessing slots from above the top of the stack. Also, the slots gen_entry_params() wrote to at entry point did not correspond to the slots access inside the JIT function. Redo the stack frame layout so that inside the function slots are at `SP+x`. Write to those slots in the entry point by anticipating the size of the frame. Fixes test_spilled_method_args().
1 parent 50e2d58 commit b2ef33b

File tree

4 files changed

+50
-18
lines changed

4 files changed

+50
-18
lines changed

zjit/src/backend/arm64/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ impl Assembler
206206
vec![X1_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG]
207207
}
208208

209+
/// How many bytes a call and a [Self::frame_setup] would change native SP
210+
pub fn frame_size() -> i32 {
211+
0x10
212+
}
213+
209214
/// Split platform-specific instructions
210215
/// The transformations done here are meant to make our lives simpler in later
211216
/// stages of the compilation pipeline.

zjit/src/backend/lir.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ pub enum MemBase
3232
pub struct Mem
3333
{
3434
// Base register number or instruction index
35-
pub(super) base: MemBase,
35+
pub base: MemBase,
3636

3737
// Offset relative to the base pointer
38-
pub(super) disp: i32,
38+
pub disp: i32,
3939

4040
// Size in bits
41-
pub(super) num_bits: u8,
41+
pub num_bits: u8,
4242
}
4343

4444
impl fmt::Debug for Mem {

zjit/src/backend/x86_64/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ impl Assembler
110110
vec![RAX_REG, RCX_REG, RDX_REG, RSI_REG, RDI_REG, R8_REG, R9_REG, R10_REG, R11_REG]
111111
}
112112

113+
/// How many bytes a call and a [Self::frame_setup] would change native SP
114+
pub fn frame_size() -> i32 {
115+
0x8
116+
}
117+
113118
// These are the callee-saved registers in the x86-64 SysV ABI
114119
// RBX, RSP, RBP, and R12–R15
115120

@@ -475,6 +480,7 @@ impl Assembler
475480
// (e.g. with Linux `perf record --call-graph fp`)
476481
Insn::FrameSetup => {
477482
if false { // We don't support --zjit-perf yet
483+
// TODO(alan): Change Assembler::frame_size() when adding --zjit-perf support
478484
push(cb, RBP);
479485
mov(cb, RBP, RSP);
480486
push(cb, RBP);

zjit/src/codegen.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ fn gen_iseq_entry_point(iseq: IseqPtr) -> *const u8 {
107107
// Compile the High-level IR
108108
let cb = ZJITState::get_code_block();
109109
let (start_ptr, mut branch_iseqs) = match gen_function(cb, iseq, &function) {
110-
Some((start_ptr, gc_offsets, branch_iseqs)) => {
110+
Some((start_ptr, gc_offsets, jit)) => {
111111
// Remember the block address to reuse it later
112112
let payload = get_or_create_iseq_payload(iseq);
113113
payload.start_ptr = Some(start_ptr);
114114
payload.gc_offsets.extend(gc_offsets);
115115

116116
// Compile an entry point to the JIT code
117-
(gen_entry(cb, iseq, &function, start_ptr), branch_iseqs)
117+
(gen_entry(cb, iseq, &function, start_ptr, jit.c_stack_bytes), jit.branch_iseqs)
118118
},
119119
None => (None, vec![]),
120120
};
@@ -145,11 +145,11 @@ fn gen_iseq_entry_point(iseq: IseqPtr) -> *const u8 {
145145
}
146146

147147
/// Compile a JIT entry
148-
fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_ptr: CodePtr) -> Option<CodePtr> {
148+
fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_ptr: CodePtr, c_stack_bytes: usize) -> Option<CodePtr> {
149149
// Set up registers for CFP, EC, SP, and basic block arguments
150150
let mut asm = Assembler::new();
151151
gen_entry_prologue(&mut asm, iseq);
152-
gen_entry_params(&mut asm, iseq, function.block(BlockId(0)));
152+
gen_entry_params(&mut asm, iseq, function.block(BlockId(0)), c_stack_bytes);
153153

154154
// Jump to the first block using a call instruction
155155
asm.ccall(function_ptr.raw_ptr(cb) as *const u8, vec![]);
@@ -185,17 +185,17 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<(CodePtr, Vec<(Rc<Branc
185185

186186
// Compile the High-level IR
187187
let result = gen_function(cb, iseq, &function);
188-
if let Some((start_ptr, gc_offsets, branch_iseqs)) = result {
188+
if let Some((start_ptr, gc_offsets, jit)) = result {
189189
payload.start_ptr = Some(start_ptr);
190190
payload.gc_offsets.extend(gc_offsets);
191-
Some((start_ptr, branch_iseqs))
191+
Some((start_ptr, jit.branch_iseqs))
192192
} else {
193193
None
194194
}
195195
}
196196

197197
/// Compile a function
198-
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec<CodePtr>, Vec<(Rc<Branch>, IseqPtr)>)> {
198+
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec<CodePtr>, JITState)> {
199199
let c_stack_bytes = aligned_stack_bytes(max_num_params(function).saturating_sub(ALLOC_REGS.len()));
200200
let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_bytes);
201201
let mut asm = Assembler::new();
@@ -249,7 +249,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
249249
}
250250

251251
// Generate code if everything can be compiled
252-
asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit.branch_iseqs))
252+
asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit))
253253
}
254254

255255
/// Compile an instruction
@@ -527,7 +527,7 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
527527
}
528528

529529
/// Assign method arguments to basic block arguments at JIT entry
530-
fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) {
530+
fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block, c_stack_bytes: usize) {
531531
let self_param = gen_param(asm, SELF_PARAM_IDX);
532532
asm.mov(self_param, Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF));
533533

@@ -536,12 +536,32 @@ fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) {
536536
asm_comment!(asm, "set method params: {num_params}");
537537

538538
// Allocate registers for basic block arguments
539-
let params: Vec<Opnd> = (0..num_params).map(|idx|
540-
gen_param(asm, idx + 1) // +1 for self
541-
).collect();
539+
for idx in 0..num_params {
540+
let param = gen_param(asm, idx + 1); // +1 for self
541+
542+
// Funky offset adjustment to write into the native stack frame of the
543+
// HIR function we'll be calling into. This only makes sense in context
544+
// of the schedule of instructions in gen_entry() for the JIT entry point.
545+
//
546+
// The entry point needs to load VALUEs into native stack slots _before_ the
547+
// frame containing the slots exists. So, we anticipate the stack frame size
548+
// of the Function and subtract offsets based on that.
549+
//
550+
// native SP at entry point ─────►┌────────────┐ Native SP grows downwards
551+
// │ │ ↓ on all arches we support.
552+
// SP-0x8 ├────────────┤
553+
// │ │
554+
// where native SP SP-0x10├────────────┤
555+
// would be while │ │
556+
// the HIR function ────────────► └────────────┘
557+
// is running
558+
let param = if let Opnd::Mem(lir::Mem { base, disp, num_bits }) = param {
559+
Opnd::Mem(lir::Mem { num_bits, base, disp: disp - c_stack_bytes as i32 - Assembler::frame_size() })
560+
} else {
561+
param
562+
};
542563

543-
// Assign local variables to the basic block arguments
544-
for (idx, &param) in params.iter().enumerate() {
564+
// Assign local variables to the basic block arguments
545565
let local = gen_entry_param(asm, iseq, idx);
546566
asm.mov(param, local);
547567
}
@@ -1045,11 +1065,12 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C
10451065
/// Return an operand we use for the basic block argument at a given index
10461066
fn param_opnd(idx: usize) -> Opnd {
10471067
// To simplify the implementation, allocate a fixed register or a stack slot for each basic block argument for now.
1068+
// Note that this is implemented here as opposed to automatically inside LIR machineries.
10481069
// TODO: Allow allocating arbitrary registers for basic block arguments
10491070
if idx < ALLOC_REGS.len() {
10501071
Opnd::Reg(ALLOC_REGS[idx])
10511072
} else {
1052-
Opnd::mem(64, NATIVE_STACK_PTR, -((idx - ALLOC_REGS.len() + 1) as i32) * SIZEOF_VALUE_I32)
1073+
Opnd::mem(64, NATIVE_STACK_PTR, (idx - ALLOC_REGS.len()) as i32 * SIZEOF_VALUE_I32)
10531074
}
10541075
}
10551076

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy