Skip to content

Commit 609bbad

Browse files
committed
ZJIT: Fn stub: Move args to create appropriate unfilled optional param gap
Fixes the included test case. It returned junk previously. To keep the IseqCall struct 24 bytes, I've imposed a ~65K limit on the number of arguments that can be codegen compiles. Should be fine.
1 parent b78a84e commit 609bbad

3 files changed

Lines changed: 61 additions & 15 deletions

File tree

zjit/src/codegen.rs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
612612
&Insn::Send { cd, blockiseq: None, state, reason, .. } => gen_send_without_block(jit, asm, cd, &function.frame_state(state), reason),
613613
&Insn::Send { cd, blockiseq: Some(blockiseq), state, reason, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
614614
&Insn::SendForward { cd, blockiseq, state, reason, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
615-
Insn::SendDirect { cme, iseq, recv, args, kw_bits, blockiseq, state, .. } => gen_send_iseq_direct(cb, jit, asm, *cme, *iseq, opnd!(recv), opnds!(args), *kw_bits, &function.frame_state(*state), *blockiseq),
615+
&Insn::SendDirect { cme, iseq, recv, ref args, kw_bits, blockiseq, state, .. } => gen_send_iseq_direct(
616+
cb, jit, asm, cme, iseq, opnd!(recv), opnds!(args),
617+
kw_bits, &function.frame_state(state), blockiseq,
618+
).ok_or(state)?,
616619
&Insn::InvokeSuper { cd, blockiseq, state, reason, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
617620
&Insn::InvokeSuperForward { cd, blockiseq, state, reason, .. } => gen_invokesuperforward(jit, asm, cd, blockiseq, &function.frame_state(state), reason),
618621
&Insn::InvokeBlock { cd, state, reason, .. } => gen_invokeblock(jit, asm, cd, &function.frame_state(state), reason),
@@ -1535,7 +1538,7 @@ fn gen_send_iseq_direct(
15351538
kw_bits: u32,
15361539
state: &FrameState,
15371540
blockiseq: Option<IseqPtr>,
1538-
) -> lir::Opnd {
1541+
) -> Option<lir::Opnd> {
15391542
gen_incr_counter(asm, Counter::iseq_optimized_send_count);
15401543

15411544
let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.to_usize();
@@ -1651,7 +1654,7 @@ fn gen_send_iseq_direct(
16511654
};
16521655

16531656
// Make a method call. The target address will be rewritten once compiled.
1654-
let iseq_call = IseqCall::new(iseq, num_optionals_passed);
1657+
let iseq_call = IseqCall::new(iseq, num_optionals_passed.try_into().ok()?, args.len().try_into().ok()?);
16551658
let dummy_ptr = cb.get_write_ptr().raw_ptr(cb);
16561659
jit.iseq_calls.push(iseq_call.clone());
16571660
let ret = asm.ccall_with_iseq_call(dummy_ptr, c_args, &iseq_call);
@@ -1668,7 +1671,7 @@ fn gen_send_iseq_direct(
16681671
let new_sp = asm.sub(SP, sp_offset.into());
16691672
asm.mov(SP, new_sp);
16701673

1671-
ret
1674+
Some(ret)
16721675
}
16731676

16741677
/// Compile for invokeblock
@@ -2937,22 +2940,39 @@ c_callable! {
29372940
// function_stub_hit_body() may allocate and call gc_validate_pc(), so we always set PC.
29382941
let iseq_call = unsafe { Rc::from_raw(iseq_call_ptr as *const IseqCall) };
29392942
let iseq = iseq_call.iseq.get();
2943+
let argc = iseq_call.argc;
2944+
let num_opts_filled = iseq_call.jit_entry_idx;
29402945
let entry_insn_idxs = crate::hir::jit_entry_insns(iseq);
29412946
let pc = unsafe { rb_iseq_pc_at_idx(iseq, entry_insn_idxs[iseq_call.jit_entry_idx.to_usize()]) };
29422947
unsafe { rb_set_cfp_pc(cfp, pc) };
29432948

29442949
// JIT-to-JIT calls don't eagerly fill nils to non-parameter locals.
29452950
// If we side-exit from function_stub_hit (before JIT code runs), we need to set them here.
2946-
fn prepare_for_exit(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE, compile_error: &CompileError) {
2951+
fn prepare_for_exit(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE, argc: u16, num_opts_filled: u16, compile_error: &CompileError) {
29472952
unsafe {
29482953
// Set SP which gen_push_frame() doesn't set
29492954
rb_set_cfp_sp(cfp, sp);
29502955

2951-
// Fill nils to uninitialized (non-argument) locals
29522956
let local_size = get_iseq_body_local_table_size(iseq).to_usize();
2953-
let num_params = iseq.params().size.to_usize();
2954-
let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize);
2955-
slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil);
2957+
let params = iseq.params();
2958+
let params_size = params.size.to_usize();
2959+
let frame_base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, 0) as isize);
2960+
let locals = slice::from_raw_parts_mut(frame_base, local_size);
2961+
// Fill nils to uninitialized (non-parameter) locals
2962+
locals.get_mut(params_size..).unwrap_or_default().fill(Qnil);
2963+
2964+
// When there are unfilled optionals
2965+
let opt_num: usize = params.opt_num.try_into().expect("ISEQ opt_num should be non-negative");
2966+
let opts_unfilled = opt_num - num_opts_filled.to_usize();
2967+
if opts_unfilled > 0 {
2968+
let lead_num: usize = params.lead_num.try_into().expect("should be non-negative");
2969+
let after_opts = lead_num + opt_num;
2970+
let param_locals = &mut locals[..params_size];
2971+
// Right-justify params that are after the unfilled optionals
2972+
param_locals.copy_within(num_opts_filled.to_usize()..argc.to_usize(), after_opts);
2973+
// Nil-fill unfilled optionals
2974+
(&mut param_locals[num_opts_filled.to_usize()..after_opts]).fill(Qnil);
2975+
}
29562976
}
29572977

29582978
// Increment a compile error counter for --zjit-stats
@@ -2976,7 +2996,7 @@ c_callable! {
29762996
// We'll use this Rc again, so increment the ref count decremented by from_raw.
29772997
unsafe { Rc::increment_strong_count(iseq_call_ptr as *const IseqCall); }
29782998

2979-
prepare_for_exit(iseq, cfp, sp, compile_error);
2999+
prepare_for_exit(iseq, cfp, sp, argc, num_opts_filled, compile_error);
29803000
return ZJITState::get_exit_trampoline_with_counter().raw_ptr(cb);
29813001
}
29823002

@@ -2991,7 +3011,7 @@ c_callable! {
29913011
// We'll use this Rc again, so increment the ref count decremented by from_raw.
29923012
unsafe { Rc::increment_strong_count(iseq_call_ptr as *const IseqCall); }
29933013

2994-
prepare_for_exit(iseq, cfp, sp, &compile_error);
3014+
prepare_for_exit(iseq, cfp, sp, argc, num_opts_filled, &compile_error);
29953015
ZJITState::get_exit_trampoline_with_counter()
29963016
});
29973017
cb.mark_all_executable();
@@ -3316,7 +3336,10 @@ pub struct IseqCall {
33163336
pub iseq: Cell<IseqPtr>,
33173337

33183338
/// Index that corresponds to [crate::hir::jit_entry_insns]
3319-
jit_entry_idx: u32,
3339+
jit_entry_idx: u16,
3340+
3341+
/// Argument count passing to the HIR function
3342+
argc: u16,
33203343

33213344
/// Position where the call instruction starts
33223345
start_addr: Cell<Option<CodePtr>>,
@@ -3329,12 +3352,13 @@ pub type IseqCallRef = Rc<IseqCall>;
33293352

33303353
impl IseqCall {
33313354
/// Allocate a new IseqCall
3332-
fn new(iseq: IseqPtr, jit_entry_idx: u32) -> IseqCallRef {
3355+
fn new(iseq: IseqPtr, jit_entry_idx: u16, argc: u16) -> IseqCallRef {
33333356
let iseq_call = IseqCall {
33343357
iseq: Cell::new(iseq),
33353358
start_addr: Cell::new(None),
33363359
end_addr: Cell::new(None),
33373360
jit_entry_idx,
3361+
argc,
33383362
};
33393363
Rc::new(iseq_call)
33403364
}

zjit/src/hir/opt_tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14857,6 +14857,28 @@ mod hir_opt_tests {
1485714857
");
1485814858
}
1485914859

14860+
#[test]
14861+
fn test_exit_from_function_stub_for_opt_keyword_callee() {
14862+
// We have a SendDirect to a callee that fails to compile,
14863+
// so the function stub has to take care of exiting to
14864+
// interpreter.
14865+
eval("
14866+
def target(a = binding.local_variable_get(:a), b: nil)
14867+
::RubyVM::ZJIT.induce_compile_failure!
14868+
[a, b]
14869+
end
14870+
14871+
def entry = target(b: -1)
14872+
14873+
raise 'wrong' unless [nil, -1] == entry
14874+
raise 'wrong' unless [nil, -1] == entry
14875+
");
14876+
14877+
crate::hir::tests::hir_build_tests::assert_compile_fails("target", ParseError::DirectiveInduced);
14878+
let hir = hir_string("entry");
14879+
assert!(hir.contains("SendDirect"), "{hir}");
14880+
}
14881+
1486014882
#[test]
1486114883
fn test_recompile_no_profile_send() {
1486214884
// Define a callee method and a test method that calls it

zjit/src/hir/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ mod snapshot_tests {
207207
}
208208

209209
#[cfg(test)]
210-
pub mod hir_build_tests {
210+
pub(crate) mod hir_build_tests {
211211
use super::*;
212212
use crate::options::set_call_threshold;
213213
use insta::assert_snapshot;
@@ -275,7 +275,7 @@ pub mod hir_build_tests {
275275
}
276276

277277
#[track_caller]
278-
fn assert_compile_fails(method: &str, reason: ParseError) {
278+
pub fn assert_compile_fails(method: &str, reason: ParseError) {
279279
let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
280280
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
281281
let result = iseq_to_hir(iseq);

0 commit comments

Comments
 (0)