mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
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().
This commit is contained in:
parent
50e2d58af8
commit
b2ef33b3c5
4 changed files with 50 additions and 18 deletions
|
@ -107,14 +107,14 @@ fn gen_iseq_entry_point(iseq: IseqPtr) -> *const u8 {
|
|||
// Compile the High-level IR
|
||||
let cb = ZJITState::get_code_block();
|
||||
let (start_ptr, mut branch_iseqs) = match gen_function(cb, iseq, &function) {
|
||||
Some((start_ptr, gc_offsets, branch_iseqs)) => {
|
||||
Some((start_ptr, gc_offsets, jit)) => {
|
||||
// Remember the block address to reuse it later
|
||||
let payload = get_or_create_iseq_payload(iseq);
|
||||
payload.start_ptr = Some(start_ptr);
|
||||
payload.gc_offsets.extend(gc_offsets);
|
||||
|
||||
// Compile an entry point to the JIT code
|
||||
(gen_entry(cb, iseq, &function, start_ptr), branch_iseqs)
|
||||
(gen_entry(cb, iseq, &function, start_ptr, jit.c_stack_bytes), jit.branch_iseqs)
|
||||
},
|
||||
None => (None, vec![]),
|
||||
};
|
||||
|
@ -145,11 +145,11 @@ fn gen_iseq_entry_point(iseq: IseqPtr) -> *const u8 {
|
|||
}
|
||||
|
||||
/// Compile a JIT entry
|
||||
fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_ptr: CodePtr) -> Option<CodePtr> {
|
||||
fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_ptr: CodePtr, c_stack_bytes: usize) -> Option<CodePtr> {
|
||||
// Set up registers for CFP, EC, SP, and basic block arguments
|
||||
let mut asm = Assembler::new();
|
||||
gen_entry_prologue(&mut asm, iseq);
|
||||
gen_entry_params(&mut asm, iseq, function.block(BlockId(0)));
|
||||
gen_entry_params(&mut asm, iseq, function.block(BlockId(0)), c_stack_bytes);
|
||||
|
||||
// Jump to the first block using a call instruction
|
||||
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
|
|||
|
||||
// Compile the High-level IR
|
||||
let result = gen_function(cb, iseq, &function);
|
||||
if let Some((start_ptr, gc_offsets, branch_iseqs)) = result {
|
||||
if let Some((start_ptr, gc_offsets, jit)) = result {
|
||||
payload.start_ptr = Some(start_ptr);
|
||||
payload.gc_offsets.extend(gc_offsets);
|
||||
Some((start_ptr, branch_iseqs))
|
||||
Some((start_ptr, jit.branch_iseqs))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile a function
|
||||
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec<CodePtr>, Vec<(Rc<Branch>, IseqPtr)>)> {
|
||||
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec<CodePtr>, JITState)> {
|
||||
let c_stack_bytes = aligned_stack_bytes(max_num_params(function).saturating_sub(ALLOC_REGS.len()));
|
||||
let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_bytes);
|
||||
let mut asm = Assembler::new();
|
||||
|
@ -249,7 +249,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
|
|||
}
|
||||
|
||||
// Generate code if everything can be compiled
|
||||
asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit.branch_iseqs))
|
||||
asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit))
|
||||
}
|
||||
|
||||
/// Compile an instruction
|
||||
|
@ -527,7 +527,7 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
|
|||
}
|
||||
|
||||
/// Assign method arguments to basic block arguments at JIT entry
|
||||
fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) {
|
||||
fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block, c_stack_bytes: usize) {
|
||||
let self_param = gen_param(asm, SELF_PARAM_IDX);
|
||||
asm.mov(self_param, Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF));
|
||||
|
||||
|
@ -536,12 +536,32 @@ fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) {
|
|||
asm_comment!(asm, "set method params: {num_params}");
|
||||
|
||||
// Allocate registers for basic block arguments
|
||||
let params: Vec<Opnd> = (0..num_params).map(|idx|
|
||||
gen_param(asm, idx + 1) // +1 for self
|
||||
).collect();
|
||||
for idx in 0..num_params {
|
||||
let param = gen_param(asm, idx + 1); // +1 for self
|
||||
|
||||
// Assign local variables to the basic block arguments
|
||||
for (idx, ¶m) in params.iter().enumerate() {
|
||||
// Funky offset adjustment to write into the native stack frame of the
|
||||
// HIR function we'll be calling into. This only makes sense in context
|
||||
// of the schedule of instructions in gen_entry() for the JIT entry point.
|
||||
//
|
||||
// The entry point needs to load VALUEs into native stack slots _before_ the
|
||||
// frame containing the slots exists. So, we anticipate the stack frame size
|
||||
// of the Function and subtract offsets based on that.
|
||||
//
|
||||
// native SP at entry point ─────►┌────────────┐ Native SP grows downwards
|
||||
// │ │ ↓ on all arches we support.
|
||||
// SP-0x8 ├────────────┤
|
||||
// │ │
|
||||
// where native SP SP-0x10├────────────┤
|
||||
// would be while │ │
|
||||
// the HIR function ────────────► └────────────┘
|
||||
// is running
|
||||
let param = if let Opnd::Mem(lir::Mem { base, disp, num_bits }) = param {
|
||||
Opnd::Mem(lir::Mem { num_bits, base, disp: disp - c_stack_bytes as i32 - Assembler::frame_size() })
|
||||
} else {
|
||||
param
|
||||
};
|
||||
|
||||
// Assign local variables to the basic block arguments
|
||||
let local = gen_entry_param(asm, iseq, idx);
|
||||
asm.mov(param, local);
|
||||
}
|
||||
|
@ -1045,11 +1065,12 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C
|
|||
/// Return an operand we use for the basic block argument at a given index
|
||||
fn param_opnd(idx: usize) -> Opnd {
|
||||
// To simplify the implementation, allocate a fixed register or a stack slot for each basic block argument for now.
|
||||
// Note that this is implemented here as opposed to automatically inside LIR machineries.
|
||||
// TODO: Allow allocating arbitrary registers for basic block arguments
|
||||
if idx < ALLOC_REGS.len() {
|
||||
Opnd::Reg(ALLOC_REGS[idx])
|
||||
} else {
|
||||
Opnd::mem(64, NATIVE_STACK_PTR, -((idx - ALLOC_REGS.len() + 1) as i32) * SIZEOF_VALUE_I32)
|
||||
Opnd::mem(64, NATIVE_STACK_PTR, (idx - ALLOC_REGS.len()) as i32 * SIZEOF_VALUE_I32)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue