summaryrefslogtreecommitdiff
path: root/zjit/src/hir.rs
diff options
context:
space:
mode:
Diffstat (limited to 'zjit/src/hir.rs')
-rw-r--r--zjit/src/hir.rs263
1 files changed, 258 insertions, 5 deletions
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index fbca1f4418..2bd7174f2d 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 },
@@ -493,6 +496,9 @@ pub enum Insn {
FixnumGt { left: InsnId, right: InsnId },
FixnumGe { left: InsnId, right: InsnId },
+ // Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined
+ ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId },
+
/// Side-exit if val doesn't have the expected type.
GuardType { val: InsnId, guard_type: Type, state: InsnId },
/// Side-exit if val is not the expected VALUE.
@@ -636,6 +642,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}") },
@@ -685,6 +698,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"),
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
+ Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") },
Insn::SideExit { .. } => write!(f, "SideExit"),
Insn::PutSpecialObject { value_type } => {
write!(f, "PutSpecialObject {}", value_type)
@@ -1003,6 +1017,12 @@ impl Function {
FixnumLt { left, right } => FixnumLt { left: find!(*left), right: find!(*right) },
FixnumLe { left, right } => FixnumLe { left: find!(*left), right: find!(*right) },
PutSpecialObject { value_type } => PutSpecialObject { value_type: *value_type },
+ ObjToString { val, call_info, cd, state } => ObjToString {
+ val: find!(*val),
+ call_info: call_info.clone(),
+ cd: *cd,
+ state: *state,
+ },
SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock {
self_val: find!(*self_val),
call_info: call_info.clone(),
@@ -1027,6 +1047,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 +1144,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,
@@ -1131,6 +1153,7 @@ impl Function {
Insn::GetIvar { .. } => types::BasicObject,
Insn::ToNewArray { .. } => types::ArrayExact,
Insn::ToArray { .. } => types::ArrayExact,
+ Insn::ObjToString { .. } => types::BasicObject,
}
}
@@ -1374,6 +1397,15 @@ impl Function {
let replacement = self.push_insn(block, Insn::Const { val: Const::Value(unsafe { (*ice).value }) });
self.make_equal_to(insn_id, replacement);
}
+ Insn::ObjToString { val, call_info, cd, state, .. } => {
+ if self.is_a(val, types::StringExact) {
+ // behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
+ self.make_equal_to(insn_id, val);
+ } else {
+ let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, call_info, cd, args: vec![], state });
+ self.make_equal_to(insn_id, replacement)
+ }
+ }
_ => { self.push_insn_id(block, insn_id); }
}
}
@@ -1727,6 +1759,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);
@@ -1742,6 +1778,10 @@ impl Function {
worklist.push_back(val);
worklist.push_back(state);
}
+ Insn::ObjToString { val, state, .. } => {
+ worklist.push_back(val);
+ worklist.push_back(state);
+ }
Insn::GetGlobal { state, .. } |
Insn::SideExit { state } => worklist.push_back(state),
}
@@ -2389,6 +2429,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
YARVINSN_pop => { state.stack_pop()?; }
YARVINSN_dup => { state.stack_push(state.stack_top()?); }
+ YARVINSN_dupn => {
+ // Duplicate the top N element of the stack. As we push, n-1 naturally
+ // points higher in the original stack.
+ let n = get_arg(pc, 0).as_usize();
+ for _ in 0..n {
+ state.stack_push(state.stack_topn(n-1)?);
+ }
+ }
YARVINSN_swap => {
let right = state.stack_pop()?;
let left = state.stack_pop()?;
@@ -2606,6 +2654,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let val = state.stack_pop()?;
fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, val, state: exit_id });
}
+ YARVINSN_opt_reverse => {
+ // Reverse the order of the top N stack items.
+ let n = get_arg(pc, 0).as_usize();
+ for i in 0..n/2 {
+ let bottom = state.stack_topn(n - 1 - i)?;
+ let top = state.stack_topn(i)?;
+ state.stack_setn(i, bottom);
+ state.stack_setn(n - 1 - i, top);
+ }
+ }
YARVINSN_newrange => {
let flag = RangeType::from(get_arg(pc, 0).as_u32());
let high = state.stack_pop()?;
@@ -2614,6 +2672,55 @@ 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);
+ }
+ YARVINSN_objtostring => {
+ let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
+ let call_info = unsafe { rb_get_call_data_ci(cd) };
+
+ if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) {
+ assert!(false, "objtostring should not have unknown call type");
+ }
+ let argc = unsafe { vm_ci_argc((*cd).ci) };
+ assert_eq!(0, argc, "objtostring should not have args");
+
+ let method_name: String = unsafe {
+ let mid = rb_vm_ci_mid(call_info);
+ mid.contents_lossy().into_owned()
+ };
+
+ let recv = state.stack_pop()?;
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let objtostring = fun.push_insn(block, Insn::ObjToString { val: recv, call_info: CallInfo { method_name }, cd, state: exit_id });
+ state.stack_push(objtostring)
+ }
_ => {
// Unknown opcode; side-exit into the interpreter
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -2907,7 +3014,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 +3041,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 +3063,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()));
@@ -4165,6 +4272,47 @@ mod tests {
}
#[test]
+ fn opt_reverse() {
+ eval("
+ def reverse_odd
+ a, b, c = @a, @b, @c
+ [a, b, c]
+ end
+
+ def reverse_even
+ a, b, c, d = @a, @b, @c, @d
+ [a, b, c, d]
+ end
+ ");
+ assert_method_hir_with_opcode("reverse_odd", YARVINSN_opt_reverse, expect![[r#"
+ fn reverse_odd:
+ bb0(v0:BasicObject):
+ v1:NilClassExact = Const Value(nil)
+ v2:NilClassExact = Const Value(nil)
+ v3:NilClassExact = Const Value(nil)
+ v6:BasicObject = GetIvar v0, :@a
+ v8:BasicObject = GetIvar v0, :@b
+ v10:BasicObject = GetIvar v0, :@c
+ v12:ArrayExact = NewArray v6, v8, v10
+ Return v12
+ "#]]);
+ assert_method_hir_with_opcode("reverse_even", YARVINSN_opt_reverse, expect![[r#"
+ fn reverse_even:
+ bb0(v0:BasicObject):
+ v1:NilClassExact = Const Value(nil)
+ v2:NilClassExact = Const Value(nil)
+ v3:NilClassExact = Const Value(nil)
+ v4:NilClassExact = Const Value(nil)
+ v7:BasicObject = GetIvar v0, :@a
+ v9:BasicObject = GetIvar v0, :@b
+ v11:BasicObject = GetIvar v0, :@c
+ v13:BasicObject = GetIvar v0, :@d
+ v15:ArrayExact = NewArray v7, v9, v11, v13
+ Return v15
+ "#]]);
+ }
+
+ #[test]
fn test_branchnil() {
eval("
def test(x) = x&.itself
@@ -4180,6 +4328,81 @@ 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
+ "#]]);
+ }
+
+ #[test]
+ fn dupn() {
+ eval("
+ def test(x) = (x[0, 1] ||= 2)
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_dupn, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject, v1:BasicObject):
+ v3:NilClassExact = Const Value(nil)
+ v4:Fixnum[0] = Const Value(0)
+ v5:Fixnum[1] = Const Value(1)
+ v7:BasicObject = SendWithoutBlock v1, :[], v4, v5
+ v8:CBool = Test v7
+ IfTrue v8, bb1(v0, v1, v3, v1, v4, v5, v7)
+ v10:Fixnum[2] = Const Value(2)
+ v12:BasicObject = SendWithoutBlock v1, :[]=, v4, v5, v10
+ Return v10
+ bb1(v14:BasicObject, v15:BasicObject, v16:NilClassExact, v17:BasicObject, v18:Fixnum[0], v19:Fixnum[1], v20:BasicObject):
+ Return v20
+ "#]]);
+ }
+
+ #[test]
+ fn test_objtostring() {
+ eval("
+ def test = \"#{1}\"
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_objtostring, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:Fixnum[1] = Const Value(1)
+ v5:BasicObject = ObjToString v3
+ SideExit
+ "#]]);
+ }
}
#[cfg(test)]
@@ -4190,7 +4413,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();
@@ -5738,4 +5961,34 @@ mod opt_tests {
Return v7
"#]]);
}
+
+ #[test]
+ fn test_objtostring_string() {
+ eval(r##"
+ def test = "#{('foo')}"
+ "##);
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v4:StringExact = StringCopy v3
+ SideExit
+ "#]]);
+ }
+
+ #[test]
+ fn test_objtostring_with_non_string() {
+ eval(r##"
+ def test = "#{1}"
+ "##);
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:Fixnum[1] = Const Value(1)
+ v8:BasicObject = SendWithoutBlock v3, :to_s
+ SideExit
+ "#]]);
+ }
}