summaryrefslogtreecommitdiff
path: root/zjit
diff options
context:
space:
mode:
Diffstat (limited to 'zjit')
-rw-r--r--zjit/src/codegen.rs21
-rw-r--r--zjit/src/cruby.rs4
-rw-r--r--zjit/src/hir.rs93
3 files changed, 111 insertions, 7 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index b1869f71c0..90c3ce640e 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -260,6 +260,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::IfFalse { val, target } => return gen_if_false(jit, asm, opnd!(val), target),
Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?,
Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), args, &function.frame_state(*state))?,
+ Insn::InvokeBuiltin { bf, args, state } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, args)?,
Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?),
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
@@ -311,6 +312,26 @@ fn gen_get_constant_path(asm: &mut Assembler, ic: *const iseq_inline_constant_ca
val
}
+fn gen_invokebuiltin(jit: &mut JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: &Vec<InsnId>) -> Option<lir::Opnd> {
+ // Ensure we have enough room fit ec, self, and arguments
+ // TODO remove this check when we have stack args (we can use Time.new to test it)
+ if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) {
+ return None;
+ }
+
+ gen_save_pc(asm, state);
+
+ let mut cargs = vec![EC];
+ for &arg in args.iter() {
+ let opnd = jit.get_opnd(arg)?;
+ cargs.push(opnd);
+ }
+
+ let val = asm.ccall(bf.func_ptr as *const u8, cargs);
+
+ Some(val)
+}
+
/// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know
/// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere.
fn gen_ccall(jit: &mut JITState, asm: &mut Assembler, cfun: *const u8, args: &[InsnId]) -> Option<lir::Opnd> {
diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs
index e0334ed44d..3a1c45ffd3 100644
--- a/zjit/src/cruby.rs
+++ b/zjit/src/cruby.rs
@@ -1038,8 +1038,8 @@ pub mod test_utils {
}
/// Get the ISeq of a specified method
- pub fn get_method_iseq(name: &str) -> *const rb_iseq_t {
- let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of(method(:{}))", name));
+ pub fn get_method_iseq(recv: &str, name: &str) -> *const rb_iseq_t {
+ let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of({}.method(:{}))", recv, name));
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
}
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index fbca1f4418..276e14a639 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -9,7 +9,7 @@ use crate::{
use std::{
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
- ffi::{c_int, c_void},
+ ffi::{c_int, c_void, CStr},
mem::{align_of, size_of},
ptr,
slice::Iter
@@ -477,6 +477,9 @@ pub enum Insn {
state: InsnId,
},
+ // Invoke a builtin function
+ InvokeBuiltin { bf: rb_builtin_function, args: Vec<InsnId>, state: InsnId },
+
/// Control flow instructions
Return { val: InsnId },
@@ -636,6 +639,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
+ Insn::InvokeBuiltin { bf, args, .. } => {
+ write!(f, "InvokeBuiltin {}", unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap())?;
+ for arg in args {
+ write!(f, ", {arg}")?;
+ }
+ Ok(())
+ }
Insn::Return { val } => { write!(f, "Return {val}") }
Insn::FixnumAdd { left, right, .. } => { write!(f, "FixnumAdd {left}, {right}") },
Insn::FixnumSub { left, right, .. } => { write!(f, "FixnumSub {left}, {right}") },
@@ -1027,6 +1037,7 @@ impl Function {
args: args.iter().map(|arg| find!(*arg)).collect(),
state: *state,
},
+ InvokeBuiltin { bf, args, state } => InvokeBuiltin { bf: *bf, args: find_vec!(*args), state: *state },
ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) },
ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state },
&HashDup { val , state } => HashDup { val: find!(val), state },
@@ -1123,6 +1134,7 @@ impl Function {
Insn::SendWithoutBlock { .. } => types::BasicObject,
Insn::SendWithoutBlockDirect { .. } => types::BasicObject,
Insn::Send { .. } => types::BasicObject,
+ Insn::InvokeBuiltin { .. } => types::BasicObject,
Insn::Defined { .. } => types::BasicObject,
Insn::DefinedIvar { .. } => types::BasicObject,
Insn::GetConstantPath { .. } => types::BasicObject,
@@ -1727,6 +1739,10 @@ impl Function {
worklist.extend(args);
worklist.push_back(state);
}
+ Insn::InvokeBuiltin { args, state, .. } => {
+ worklist.extend(args);
+ worklist.push_back(state)
+ }
Insn::CCall { args, .. } => worklist.extend(args),
Insn::GetIvar { self_val, state, .. } | Insn::DefinedIvar { self_val, state, .. } => {
worklist.push_back(self_val);
@@ -2614,6 +2630,35 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let insn_id = fun.push_insn(block, Insn::NewRange { low, high, flag, state: exit_id });
state.stack_push(insn_id);
}
+ YARVINSN_invokebuiltin => {
+ let bf: rb_builtin_function = unsafe { *get_arg(pc, 0).as_ptr() };
+
+ let mut args = vec![];
+ for _ in 0..bf.argc {
+ args.push(state.stack_pop()?);
+ }
+ args.push(self_param);
+ args.reverse();
+
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id });
+ state.stack_push(insn_id);
+ }
+ YARVINSN_opt_invokebuiltin_delegate |
+ YARVINSN_opt_invokebuiltin_delegate_leave => {
+ let bf: rb_builtin_function = unsafe { *get_arg(pc, 0).as_ptr() };
+ let index = get_arg(pc, 1).as_usize();
+ let argc = bf.argc as usize;
+
+ let mut args = vec![self_param];
+ for &local in state.locals().skip(index).take(argc) {
+ args.push(local);
+ }
+
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id });
+ state.stack_push(insn_id);
+ }
_ => {
// Unknown opcode; side-exit into the interpreter
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -2907,7 +2952,7 @@ mod tests {
#[track_caller]
fn assert_method_hir(method: &str, hir: Expect) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, hir);
@@ -2934,7 +2979,7 @@ mod tests {
#[track_caller]
fn assert_method_hir_with_opcodes(method: &str, opcodes: &[u32], hir: Expect) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
for &opcode in opcodes {
assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize));
}
@@ -2956,7 +3001,7 @@ mod tests {
#[track_caller]
fn assert_compile_fails(method: &str, reason: ParseError) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let result = iseq_to_hir(iseq);
assert!(result.is_err(), "Expected an error but succesfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap()));
@@ -4180,6 +4225,44 @@ mod tests {
Return v10
"#]]);
}
+
+ #[test]
+ fn test_invokebuiltin_delegate_with_args() {
+ assert_method_hir_with_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave, expect![[r#"
+ fn Float:
+ bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject):
+ v6:BasicObject = InvokeBuiltin rb_f_float, v0, v1, v2
+ Jump bb1(v0, v1, v2, v3, v6)
+ bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject):
+ Return v12
+ "#]]);
+ }
+
+ #[test]
+ fn test_invokebuiltin_delegate_without_args() {
+ assert_method_hir_with_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave, expect![[r#"
+ fn class:
+ bb0(v0:BasicObject):
+ v3:BasicObject = InvokeBuiltin _bi20, v0
+ Jump bb1(v0, v3)
+ bb1(v5:BasicObject, v6:BasicObject):
+ Return v6
+ "#]]);
+ }
+
+ #[test]
+ fn test_invokebuiltin_with_args() {
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "start"));
+ assert!(iseq_contains_opcode(iseq, YARVINSN_invokebuiltin), "iseq GC.start does not contain invokebuiltin");
+ let function = iseq_to_hir(iseq).unwrap();
+ assert_function_hir(function, expect![[r#"
+ fn start:
+ bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject):
+ v6:FalseClassExact = Const Value(false)
+ v8:BasicObject = InvokeBuiltin gc_start_internal, v0, v1, v2, v3, v6
+ Return v8
+ "#]]);
+ }
}
#[cfg(test)]
@@ -4190,7 +4273,7 @@ mod opt_tests {
#[track_caller]
fn assert_optimized_method_hir(method: &str, hir: Expect) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let mut function = iseq_to_hir(iseq).unwrap();
function.optimize();