Skip to content

Commit 61a0de1

Browse files
authored
ZJIT: Compile ISEQ with optional arguments (ruby#14653)
1 parent a1a1c90 commit 61a0de1

File tree

6 files changed

+1031
-794
lines changed

6 files changed

+1031
-794
lines changed

test/ruby/test_zjit.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,39 @@ def test_setlocal_on_eval
308308
}
309309
end
310310

311+
def test_optional_arguments
312+
assert_compiles '[[1, 2, 3], [10, 20, 3], [100, 200, 300]]', %q{
313+
def test(a, b = 2, c = 3)
314+
[a, b, c]
315+
end
316+
[test(1), test(10, 20), test(100, 200, 300)]
317+
}
318+
end
319+
320+
def test_optional_arguments_setlocal
321+
assert_compiles '[[2, 2], [1, nil]]', %q{
322+
def test(a = (b = 2))
323+
[a, b]
324+
end
325+
[test, test(1)]
326+
}
327+
end
328+
329+
def test_optional_arguments_cyclic
330+
assert_compiles '[nil, 1]', %q{
331+
test = proc { |a=a| a }
332+
[test.call, test.call(1)]
333+
}
334+
end
335+
336+
def test_optional_arguments_side_exit
337+
# This leads to FailedOptionalArguments, so not using assert_compiles
338+
assert_runs '[:foo, nil, 1]', %q{
339+
def test(a = (def foo = nil)) = a
340+
[test, (undef :foo), test(1)]
341+
}
342+
end
343+
311344
def test_getblockparamproxy
312345
assert_compiles '1', %q{
313346
def test(&block)

zjit/src/codegen.rs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::stats::{exit_counter_for_compile_error, incr_counter, incr_counter_by
1616
use crate::stats::{counter_ptr, with_time_stat, Counter, send_fallback_counter, Counter::{compile_time_ns, exit_compile_error}};
1717
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
1818
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP};
19-
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, MethodType, SpecialObjectType, SpecialBackrefSymbol, SELF_PARAM_IDX};
19+
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, MethodType, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType, SELF_PARAM_IDX};
2020
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
2121
use crate::hir_type::{types, Type};
2222
use crate::options::get_option;
@@ -34,7 +34,7 @@ struct JITState {
3434
labels: Vec<Option<Target>>,
3535

3636
/// JIT entry point for the `iseq`
37-
jit_entry: Option<Rc<RefCell<JITEntry>>>,
37+
jit_entries: Vec<Rc<RefCell<JITEntry>>>,
3838

3939
/// ISEQ calls that need to be compiled later
4040
iseq_calls: Vec<Rc<RefCell<IseqCall>>>,
@@ -50,7 +50,7 @@ impl JITState {
5050
iseq,
5151
opnds: vec![None; num_insns],
5252
labels: vec![None; num_blocks],
53-
jit_entry: None,
53+
jit_entries: Vec::default(),
5454
iseq_calls: Vec::default(),
5555
c_stack_slots,
5656
}
@@ -228,7 +228,7 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>,
228228
};
229229

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

233233
// Stub callee ISEQs for JIT-to-JIT calls
234234
for iseq_call in iseq_calls.iter() {
@@ -238,11 +238,11 @@ fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>,
238238
// Prepare for GC
239239
payload.iseq_calls.extend(iseq_calls.clone());
240240
append_gc_offsets(iseq, &gc_offsets);
241-
Ok(IseqCodePtrs { start_ptr, jit_entry_ptr })
241+
Ok(iseq_code_ptrs)
242242
}
243243

244244
/// Compile a function
245-
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(CodePtr, CodePtr, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> {
245+
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Result<(IseqCodePtrs, Vec<CodePtr>, Vec<IseqCallRef>), CompileError> {
246246
let c_stack_slots = max_num_params(function).saturating_sub(ALLOC_REGS.len());
247247
let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_slots);
248248
let mut asm = Assembler::new();
@@ -307,8 +307,13 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul
307307
}
308308
}
309309
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)
310+
// Make sure jit_entry_ptrs can be used as a parallel vector to jit_entry_insns()
311+
jit.jit_entries.sort_by_key(|jit_entry| jit_entry.borrow().jit_entry_idx);
312+
313+
let jit_entry_ptrs = jit.jit_entries.iter().map(|jit_entry|
314+
jit_entry.borrow().start_addr.get().expect("start_addr should have been set by pos_marker in gen_entry_point")
315+
).collect();
316+
(IseqCodePtrs { start_ptr, jit_entry_ptrs }, gc_offsets, jit.iseq_calls)
312317
})
313318
}
314319

@@ -375,7 +380,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
375380
// TODO remove this check when we have stack args (we can use Time.new to test it)
376381
Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state),
377382
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)),
383+
&Insn::EntryPoint { jit_entry_idx } => no_output!(gen_entry_point(jit, asm, jit_entry_idx)),
379384
Insn::Return { val } => no_output!(gen_return(asm, opnd!(val))),
380385
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
381386
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state)),
@@ -424,7 +429,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
424429
&Insn::DefinedIvar { self_val, id, pushval, .. } => { gen_defined_ivar(asm, opnd!(self_val), id, pushval) },
425430
&Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) },
426431
&Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)),
427-
Insn::LoadPC => gen_load_pc(),
432+
Insn::LoadPC => gen_load_pc(asm),
428433
&Insn::LoadIvarEmbedded { self_val, id, index } => gen_load_ivar_embedded(asm, opnd!(self_val), id, index),
429434
&Insn::LoadIvarExtended { self_val, id, index } => gen_load_ivar_extended(asm, opnd!(self_val), id, index),
430435
&Insn::ArrayMax { state, .. }
@@ -825,8 +830,8 @@ fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: Sh
825830
val
826831
}
827832

828-
fn gen_load_pc() -> Opnd {
829-
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC)
833+
fn gen_load_pc(asm: &mut Assembler) -> Opnd {
834+
asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC))
830835
}
831836

832837
fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u16) -> Opnd {
@@ -1313,12 +1318,11 @@ fn gen_object_alloc_class(asm: &mut Assembler, class: VALUE, state: &FrameState)
13131318
}
13141319
}
13151320

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());
1321+
/// Compile a frame setup. If jit_entry_idx is Some, remember the address of it as a JIT entry.
1322+
fn gen_entry_point(jit: &mut JITState, asm: &mut Assembler, jit_entry_idx: Option<usize>) {
1323+
if let Some(jit_entry_idx) = jit_entry_idx {
1324+
let jit_entry = JITEntry::new(jit_entry_idx);
1325+
jit.jit_entries.push(jit_entry.clone());
13221326
asm.pos_marker(move |code_ptr, _| {
13231327
jit_entry.borrow_mut().start_addr.set(Some(code_ptr));
13241328
});
@@ -1776,21 +1780,14 @@ fn compile_iseq(iseq: IseqPtr) -> Result<Function, CompileError> {
17761780
let mut function = match iseq_to_hir(iseq) {
17771781
Ok(function) => function,
17781782
Err(err) => {
1779-
let name = crate::cruby::iseq_get_location(iseq, 0);
1780-
debug!("ZJIT: iseq_to_hir: {err:?}: {name}");
1783+
debug!("ZJIT: iseq_to_hir: {err:?}: {}", iseq_get_location(iseq, 0));
17811784
return Err(CompileError::ParseError(err));
17821785
}
17831786
};
17841787
if !get_option!(disable_hir_opt) {
17851788
function.optimize();
17861789
}
17871790
function.dump_hir();
1788-
#[cfg(debug_assertions)]
1789-
if let Err(err) = function.validate() {
1790-
debug!("ZJIT: compile_iseq: {err:?}");
1791-
use crate::hir::ParseError;
1792-
return Err(CompileError::ParseError(ParseError::Validation(err)));
1793-
}
17941791
Ok(function)
17951792
}
17961793

@@ -1928,10 +1925,15 @@ c_callable! {
19281925
/// Compile an ISEQ for a function stub
19291926
fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc<RefCell<IseqCall>>) -> Result<CodePtr, CompileError> {
19301927
// Compile the stubbed ISEQ
1931-
let IseqCodePtrs { jit_entry_ptr, .. } = gen_iseq(cb, iseq_call.borrow().iseq, None).inspect_err(|err| {
1928+
let IseqCodePtrs { jit_entry_ptrs, .. } = gen_iseq(cb, iseq_call.borrow().iseq, None).inspect_err(|err| {
19321929
debug!("{err:?}: gen_iseq failed: {}", iseq_get_location(iseq_call.borrow().iseq, 0));
19331930
})?;
19341931

1932+
// We currently don't support JIT-to-JIT calls for ISEQs with optional arguments.
1933+
// So we only need to use jit_entry_ptrs[0] for now. TODO: Support optional arguments.
1934+
assert_eq!(1, jit_entry_ptrs.len());
1935+
let jit_entry_ptr = jit_entry_ptrs[0];
1936+
19351937
// Update the stub to call the code pointer
19361938
let code_addr = jit_entry_ptr.raw_ptr(cb);
19371939
let iseq = iseq_call.borrow().iseq;
@@ -2131,14 +2133,17 @@ impl Assembler {
21312133

21322134
/// Store info about a JIT entry point
21332135
pub struct JITEntry {
2136+
/// Index that corresponds to jit_entry_insns()
2137+
jit_entry_idx: usize,
21342138
/// Position where the entry point starts
21352139
start_addr: Cell<Option<CodePtr>>,
21362140
}
21372141

21382142
impl JITEntry {
21392143
/// Allocate a new JITEntry
2140-
fn new() -> Rc<RefCell<Self>> {
2144+
fn new(jit_entry_idx: usize) -> Rc<RefCell<Self>> {
21412145
let jit_entry = JITEntry {
2146+
jit_entry_idx,
21422147
start_addr: Cell::new(None),
21432148
};
21442149
Rc::new(RefCell::new(jit_entry))

zjit/src/cruby.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,9 +1105,14 @@ pub mod test_utils {
11051105
ruby_str_to_rust_string(eval(&inspect))
11061106
}
11071107

1108-
/// Get the ISeq of a specified method
1108+
/// Get IseqPtr for a specified method
11091109
pub fn get_method_iseq(recv: &str, name: &str) -> *const rb_iseq_t {
1110-
let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of({}.method(:{}))", recv, name));
1110+
get_proc_iseq(&format!("{}.method(:{})", recv, name))
1111+
}
1112+
1113+
/// Get IseqPtr for a specified Proc object
1114+
pub fn get_proc_iseq(obj: &str) -> *const rb_iseq_t {
1115+
let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of({obj})"));
11111116
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
11121117
}
11131118

zjit/src/gc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ impl IseqPayload {
4040
pub struct IseqCodePtrs {
4141
/// Entry for the interpreter
4242
pub start_ptr: CodePtr,
43-
/// Entry for JIT-to-JIT calls
44-
pub jit_entry_ptr: CodePtr,
43+
/// Entries for JIT-to-JIT calls
44+
pub jit_entry_ptrs: Vec<CodePtr>,
4545
}
4646

4747
#[derive(Debug, PartialEq)]

0 commit comments

Comments
 (0)