Skip to content

Commit a5c98a7

Browse files
authored
ZJIT: Incorporate bb0-prologue and PC check into HIR (#14643)
* ZJIT: Incorporate bb0-prologue and PC check into HIR * Fix an outdated comment * Use shallow clone for LoadPC and EntryPoint * Reproduce the actual HIR graph to pass validation * Fill out param types for jit_entry_block * Add Type::from_cptr * Add a TODO comment about Const::CPtr printing
1 parent 44d0b01 commit a5c98a7

File tree

7 files changed

+3209
-1415
lines changed

7 files changed

+3209
-1415
lines changed

test/ruby/test_zjit.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,8 @@ def test
540540
end
541541

542542
def test_invokebuiltin
543-
assert_compiles '["."]', %q{
543+
# Not using assert_compiles due to register spill
544+
assert_runs '["."]', %q{
544545
def test = Dir.glob(".")
545546
test
546547
}
@@ -1519,7 +1520,9 @@ def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8]
15191520
def test_forty_param_method
15201521
# This used to a trigger a miscomp on A64 due
15211522
# to a memory displacement larger than 9 bits.
1522-
assert_compiles '1', %Q{
1523+
# Using assert_runs again due to register spill.
1524+
# TODO: It should be fixed by register spill support.
1525+
assert_runs '1', %Q{
15231526
def foo(#{'_,' * 39} n40) = n40
15241527
15251528
foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1)

zjit/src/backend/lir.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,9 +1607,12 @@ impl Assembler
16071607
self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd);
16081608
}
16091609

1610-
asm_comment!(self, "save cfp->pc");
1611-
self.load_into(SCRATCH_OPND, Opnd::const_ptr(pc));
1612-
self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), SCRATCH_OPND);
1610+
// Avoid setting cfp->pc when exiting entry_block with optional arguments
1611+
if !pc.is_null() {
1612+
asm_comment!(self, "save cfp->pc");
1613+
self.load_into(SCRATCH_OPND, Opnd::const_ptr(pc));
1614+
self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), SCRATCH_OPND);
1615+
}
16131616

16141617
asm_comment!(self, "save cfp->sp");
16151618
self.lea_into(SCRATCH_OPND, Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32));

zjit/src/codegen.rs

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::slice;
1010
use crate::asm::Label;
1111
use crate::backend::current::{Reg, ALLOC_REGS};
1212
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption};
13-
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqPayload, IseqStatus};
13+
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus};
1414
use crate::state::ZJITState;
1515
use crate::stats::{exit_counter_for_compile_error, incr_counter, incr_counter_by, CompileError};
1616
use crate::stats::{counter_ptr, with_time_stat, Counter, send_fallback_counter, Counter::{compile_time_ns, exit_compile_error}};
@@ -33,6 +33,9 @@ struct JITState {
3333
/// Labels for each basic block indexed by the BlockId
3434
labels: Vec<Option<Target>>,
3535

36+
/// JIT entry point for the `iseq`
37+
jit_entry: Option<Rc<RefCell<JITEntry>>>,
38+
3639
/// ISEQ calls that need to be compiled later
3740
iseq_calls: Vec<Rc<RefCell<IseqCall>>>,
3841

@@ -47,6 +50,7 @@ impl JITState {
4750
iseq,
4851
opnds: vec![None; num_insns],
4952
labels: vec![None; num_blocks],
53+
jit_entry: None,
5054
iseq_calls: Vec::default(),
5155
c_stack_slots,
5256
}
@@ -117,7 +121,7 @@ fn gen_iseq_entry_point(cb: &mut CodeBlock, iseq: IseqPtr, jit_exception: bool)
117121
})?;
118122

119123
// Compile the High-level IR
120-
let start_ptr = gen_iseq(cb, iseq, Some(&function)).inspect_err(|err| {
124+
let IseqCodePtrs { start_ptr, .. } = gen_iseq(cb, iseq, Some(&function)).inspect_err(|err| {
121125
debug!("{err:?}: gen_iseq failed: {}", iseq_get_location(iseq, 0));
122126
})?;
123127

@@ -164,7 +168,7 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt
164168
// Set up registers for CFP, EC, SP, and basic block arguments
165169
let mut asm = Assembler::new();
166170
gen_entry_prologue(&mut asm, iseq);
167-
gen_entry_params(&mut asm, iseq, function.block(BlockId(0)));
171+
gen_entry_params(&mut asm, iseq, function.entry_block());
168172

169173
// Jump to the first block using a call instruction
170174
asm.ccall(function_ptr.raw_ptr(cb), vec![]);
@@ -191,40 +195,40 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt
191195
}
192196

193197
/// Compile an ISEQ into machine code if not compiled yet
194-
fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> Result<CodePtr, CompileError> {
198+
fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> Result<IseqCodePtrs, CompileError> {
195199
// Return an existing pointer if it's already compiled
196200
let payload = get_or_create_iseq_payload(iseq);
197201
match &payload.status {
198-
IseqStatus::Compiled(start_ptr) => return Ok(*start_ptr),
202+
IseqStatus::Compiled(code_ptrs) => return Ok(code_ptrs.clone()),
199203
IseqStatus::CantCompile(err) => return Err(err.clone()),
200204
IseqStatus::NotCompiled => {},
201205
}
202206

203207
// Compile the ISEQ
204-
let code_ptr = gen_iseq_body(cb, iseq, function, payload);
205-
match &code_ptr {
206-
Ok(start_ptr) => {
207-
payload.status = IseqStatus::Compiled(*start_ptr);
208+
let code_ptrs = gen_iseq_body(cb, iseq, function, payload);
209+
match &code_ptrs {
210+
Ok(code_ptrs) => {
211+
payload.status = IseqStatus::Compiled(code_ptrs.clone());
208212
incr_counter!(compiled_iseq_count);
209213
}
210214
Err(err) => {
211215
payload.status = IseqStatus::CantCompile(err.clone());
212216
incr_counter!(failed_iseq_count);
213217
}
214218
}
215-
code_ptr
219+
code_ptrs
216220
}
217221

218222
/// Compile an ISEQ into machine code
219-
fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, payload: &mut IseqPayload) -> Result<CodePtr, CompileError> {
223+
fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, payload: &mut IseqPayload) -> Result<IseqCodePtrs, CompileError> {
220224
// Convert ISEQ into optimized High-level IR if not given
221225
let function = match function {
222226
Some(function) => function,
223227
None => &compile_iseq(iseq)?,
224228
};
225229

226230
// Compile the High-level IR
227-
let (start_ptr, gc_offsets, iseq_calls) = gen_function(cb, iseq, function)?;
231+
let (start_ptr, jit_entry_ptr, gc_offsets, iseq_calls) = gen_function(cb, iseq, function)?;
228232

229233
// Stub callee ISEQs for JIT-to-JIT calls
230234
for iseq_call in iseq_calls.iter() {
@@ -234,11 +238,11 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>,
234238
// Prepare for GC
235239
payload.iseq_calls.extend(iseq_calls.clone());
236240
append_gc_offsets(iseq, &gc_offsets);
237-
Ok(start_ptr)
241+
Ok(IseqCodePtrs { start_ptr, jit_entry_ptr })
238242
}
239243

240244
/// Compile a function
241-
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(CodePtr, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> {
245+
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(CodePtr, CodePtr, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> {
242246
let c_stack_slots = max_num_params(function).saturating_sub(ALLOC_REGS.len());
243247
let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_slots);
244248
let mut asm = Assembler::new();
@@ -257,11 +261,6 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul
257261
let label = jit.get_label(&mut asm, block_id);
258262
asm.write_label(label);
259263

260-
// Set up the frame at the first block. :bb0-prologue:
261-
if block_id == BlockId(0) {
262-
asm.frame_setup(&[], jit.c_stack_slots);
263-
}
264-
265264
// Compile all parameters
266265
for &insn_id in block.params() {
267266
match function.find(insn_id) {
@@ -307,7 +306,10 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul
307306
ZJITState::log_compile(iseq_name);
308307
}
309308
}
310-
result.map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit.iseq_calls))
309+
result.map(|(start_ptr, gc_offsets)| {
310+
let jit_entry_ptr = jit.jit_entry.unwrap().borrow().start_addr.get().unwrap();
311+
(start_ptr, jit_entry_ptr, gc_offsets, jit.iseq_calls)
312+
})
311313
}
312314

313315
/// Compile an instruction
@@ -338,7 +340,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
338340
}
339341

340342
let out_opnd = match insn {
341-
Insn::Const { val: Const::Value(val) } => gen_const(*val),
343+
&Insn::Const { val: Const::Value(val) } => gen_const_value(val),
344+
&Insn::Const { val: Const::CPtr(val) } => gen_const_cptr(val),
342345
Insn::Const { .. } => panic!("Unexpected Const in gen_insn: {insn}"),
343346
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
344347
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, opnds!(elements), &function.frame_state(*state)),
@@ -372,6 +375,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
372375
// TODO remove this check when we have stack args (we can use Time.new to test it)
373376
Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state),
374377
Insn::InvokeBuiltin { bf, args, state, .. } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, opnds!(args)),
378+
&Insn::EntryPoint { jit_entry } => no_output!(gen_entry_point(jit, asm, jit_entry)),
375379
Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))),
376380
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
377381
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
@@ -386,6 +390,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
386390
Insn::FixnumOr { left, right } => gen_fixnum_or(asm, opnd!(left), opnd!(right)),
387391
Insn::IsNil { val } => gen_isnil(asm, opnd!(val)),
388392
&Insn::IsMethodCfunc { val, cd, cfunc, state: _ } => gen_is_method_cfunc(jit, asm, opnd!(val), cd, cfunc),
393+
&Insn::IsBitEqual { left, right } => gen_is_bit_equal(asm, opnd!(left), opnd!(right)),
389394
Insn::Test { val } => gen_test(asm, opnd!(val)),
390395
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
391396
Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)),
@@ -419,6 +424,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
419424
&Insn::DefinedIvar { self_val, id, pushval, .. } => { gen_defined_ivar(asm, opnd!(self_val), id, pushval) },
420425
&Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) },
421426
&Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)),
427+
Insn::LoadPC => gen_load_pc(),
422428
&Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index),
423429
&Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index),
424430
&Insn::ArrayMax { state, .. }
@@ -819,6 +825,10 @@ fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: Sh
819825
val
820826
}
821827

828+
fn gen_load_pc() -> Opnd {
829+
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC)
830+
}
831+
822832
fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd {
823833
// See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h
824834

@@ -854,23 +864,6 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) {
854864

855865
// Load the current SP from the CFP into REG_SP
856866
asm.mov(SP, Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP));
857-
858-
// Currently, we support only the case that no optional arguments are given.
859-
// Bail out if any optional argument is supplied.
860-
let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) };
861-
if opt_num > 0 {
862-
asm_comment!(asm, "guard no optional arguments");
863-
let no_opts_pc = unsafe { rb_iseq_pc_at_idx(iseq, 0) };
864-
asm.cmp(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(no_opts_pc));
865-
let no_opts_label = asm.new_label("no_opts");
866-
asm.je(no_opts_label.clone());
867-
868-
gen_incr_counter(asm, Counter::exit_optional_arguments);
869-
asm.frame_teardown(lir::JIT_PRESERVED_REGS);
870-
asm.cret(Qundef.into());
871-
872-
asm.write_label(no_opts_label);
873-
}
874867
}
875868

876869
/// Assign method arguments to basic block arguments at JIT entry
@@ -966,11 +959,16 @@ fn gen_entry_param(asm: &mut Assembler, iseq: IseqPtr, local_idx: usize) -> lir:
966959
}
967960

968961
/// Compile a constant
969-
fn gen_const(val: VALUE) -> lir::Opnd {
962+
fn gen_const_value(val: VALUE) -> lir::Opnd {
970963
// Just propagate the constant value and generate nothing
971964
Opnd::Value(val)
972965
}
973966

967+
/// Compile Const::CPtr
968+
fn gen_const_cptr(val: *mut u8) -> lir::Opnd {
969+
Opnd::UImm(val as u64)
970+
}
971+
974972
/// Compile a basic block argument
975973
fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd {
976974
// Allocate a register or a stack slot
@@ -1315,6 +1313,19 @@ fn gen_object_alloc_class(asm: &mut Assembler, class: VALUE, state: &FrameState)
13151313
}
13161314
}
13171315

1316+
/// Compile a frame setup. If is_jit_entry is true, remember the address of it as a JIT entry.
1317+
fn gen_entry_point(jit: &mut JITState, asm: &mut Assembler, is_jit_entry: bool) {
1318+
if is_jit_entry {
1319+
assert!(jit.jit_entry.is_none(), "only one jit_entry is expected");
1320+
let jit_entry = JITEntry::new();
1321+
jit.jit_entry = Some(jit_entry.clone());
1322+
asm.pos_marker(move |code_ptr, _| {
1323+
jit_entry.borrow_mut().start_addr.set(Some(code_ptr));
1324+
});
1325+
}
1326+
asm.frame_setup(&[], jit.c_stack_slots);
1327+
}
1328+
13181329
/// Compile code that exits from JIT code with a return value
13191330
fn gen_return(asm: &mut Assembler, val: lir::Opnd) {
13201331
// Pop the current frame (ec->cfp++)
@@ -1329,7 +1340,7 @@ fn gen_return(asm: &mut Assembler, val: lir::Opnd) {
13291340
asm.load_into(C_RET_OPND, val);
13301341

13311342
// Return from the function
1332-
asm.frame_teardown(&[]); // matching the setup in :bb0-prologue:
1343+
asm.frame_teardown(&[]); // matching the setup in gen_entry_point()
13331344
asm.cret(C_RET_OPND);
13341345
}
13351346

@@ -1424,6 +1435,11 @@ fn gen_is_method_cfunc(jit: &JITState, asm: &mut Assembler, val: lir::Opnd, cd:
14241435
asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE(jit.iseq as usize).into(), (cd as usize).into(), val, (cfunc as usize).into())
14251436
}
14261437

1438+
fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd {
1439+
asm.cmp(left, right);
1440+
asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))
1441+
}
1442+
14271443
fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> lir::Opnd {
14281444
gen_prepare_leaf_call_with_gc(asm, state);
14291445

@@ -1912,19 +1928,19 @@ c_callable! {
19121928
/// Compile an ISEQ for a function stub
19131929
fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc<RefCell<IseqCall>>) -> Result<CodePtr, CompileError> {
19141930
// Compile the stubbed ISEQ
1915-
let code_ptr = gen_iseq(cb, iseq_call.borrow().iseq, None).inspect_err(|err| {
1931+
let IseqCodePtrs { jit_entry_ptr, .. } = gen_iseq(cb, iseq_call.borrow().iseq, None).inspect_err(|err| {
19161932
debug!("{err:?}: gen_iseq failed: {}", iseq_get_location(iseq_call.borrow().iseq, 0));
19171933
})?;
19181934

19191935
// Update the stub to call the code pointer
1920-
let code_addr = code_ptr.raw_ptr(cb);
1936+
let code_addr = jit_entry_ptr.raw_ptr(cb);
19211937
let iseq = iseq_call.borrow().iseq;
19221938
iseq_call.borrow_mut().regenerate(cb, |asm| {
19231939
asm_comment!(asm, "call compiled function: {}", iseq_get_location(iseq, 0));
19241940
asm.ccall(code_addr, vec![]);
19251941
});
19261942

1927-
Ok(code_ptr)
1943+
Ok(jit_entry_ptr)
19281944
}
19291945

19301946
/// Compile a stub for an ISEQ called by SendWithoutBlockDirect
@@ -1983,7 +1999,7 @@ pub fn gen_exit_trampoline(cb: &mut CodeBlock) -> Result<CodePtr, CompileError>
19831999
let mut asm = Assembler::new();
19842000

19852001
asm_comment!(asm, "side-exit trampoline");
1986-
asm.frame_teardown(&[]); // matching the setup in :bb0-prologue:
2002+
asm.frame_teardown(&[]); // matching the setup in gen_entry_point()
19872003
asm.cret(Qundef.into());
19882004

19892005
asm.compile(cb).map(|(code_ptr, gc_offsets)| {
@@ -2113,6 +2129,22 @@ impl Assembler {
21132129
}
21142130
}
21152131

2132+
/// Store info about a JIT entry point
2133+
pub struct JITEntry {
2134+
/// Position where the entry point starts
2135+
start_addr: Cell<Option<CodePtr>>,
2136+
}
2137+
2138+
impl JITEntry {
2139+
/// Allocate a new JITEntry
2140+
fn new() -> Rc<RefCell<Self>> {
2141+
let jit_entry = JITEntry {
2142+
start_addr: Cell::new(None),
2143+
};
2144+
Rc::new(RefCell::new(jit_entry))
2145+
}
2146+
}
2147+
21162148
/// Store info about a JIT-to-JIT call
21172149
#[derive(Debug)]
21182150
pub struct IseqCall {

zjit/src/gc.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,18 @@ impl IseqPayload {
3535
}
3636
}
3737

38+
/// Set of CodePtrs for an ISEQ
39+
#[derive(Clone, Debug, PartialEq)]
40+
pub struct IseqCodePtrs {
41+
/// Entry for the interpreter
42+
pub start_ptr: CodePtr,
43+
/// Entry for JIT-to-JIT calls
44+
pub jit_entry_ptr: CodePtr,
45+
}
46+
3847
#[derive(Debug, PartialEq)]
3948
pub enum IseqStatus {
40-
/// CodePtr has the JIT code address of the first block
41-
Compiled(CodePtr),
49+
Compiled(IseqCodePtrs),
4250
CantCompile(CompileError),
4351
NotCompiled,
4452
}

0 commit comments

Comments
 (0)