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.rs932
1 files changed, 863 insertions, 69 deletions
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index c93b603f53..7a9bb132aa 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
@@ -138,6 +138,40 @@ impl Invariant {
}
}
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum SpecialObjectType {
+ VMCore = 1,
+ CBase = 2,
+ ConstBase = 3,
+}
+
+impl From<u32> for SpecialObjectType {
+ fn from(value: u32) -> Self {
+ match value {
+ VM_SPECIAL_OBJECT_VMCORE => SpecialObjectType::VMCore,
+ VM_SPECIAL_OBJECT_CBASE => SpecialObjectType::CBase,
+ VM_SPECIAL_OBJECT_CONST_BASE => SpecialObjectType::ConstBase,
+ _ => panic!("Invalid special object type: {}", value),
+ }
+ }
+}
+
+impl From<SpecialObjectType> for u64 {
+ fn from(special_type: SpecialObjectType) -> Self {
+ special_type as u64
+ }
+}
+
+impl std::fmt::Display for SpecialObjectType {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ SpecialObjectType::VMCore => write!(f, "VMCore"),
+ SpecialObjectType::CBase => write!(f, "CBase"),
+ SpecialObjectType::ConstBase => write!(f, "ConstBase"),
+ }
+ }
+}
+
/// Print adaptor for [`Invariant`]. See [`PtrPrintMap`].
pub struct InvariantPrinter<'a> {
inner: Invariant,
@@ -151,7 +185,9 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
write!(f, "BOPRedefined(")?;
match klass {
INTEGER_REDEFINED_OP_FLAG => write!(f, "INTEGER_REDEFINED_OP_FLAG")?,
+ STRING_REDEFINED_OP_FLAG => write!(f, "STRING_REDEFINED_OP_FLAG")?,
ARRAY_REDEFINED_OP_FLAG => write!(f, "ARRAY_REDEFINED_OP_FLAG")?,
+ HASH_REDEFINED_OP_FLAG => write!(f, "HASH_REDEFINED_OP_FLAG")?,
_ => write!(f, "{klass}")?,
}
write!(f, ", ")?;
@@ -167,6 +203,8 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> {
BOP_LE => write!(f, "BOP_LE")?,
BOP_GT => write!(f, "BOP_GT")?,
BOP_GE => write!(f, "BOP_GE")?,
+ BOP_FREEZE => write!(f, "BOP_FREEZE")?,
+ BOP_UMINUS => write!(f, "BOP_UMINUS")?,
BOP_MAX => write!(f, "BOP_MAX")?,
_ => write!(f, "{bop}")?,
}
@@ -362,9 +400,12 @@ pub enum Insn {
/// SSA block parameter. Also used for function parameters in the function's entry block.
Param { idx: usize },
- StringCopy { val: InsnId },
+ StringCopy { val: InsnId, chilled: bool },
StringIntern { val: InsnId },
+ /// Put special object (VMCORE, CBASE, etc.) based on value_type
+ PutSpecialObject { value_type: SpecialObjectType },
+
/// Call `to_a` on `val` if the method is defined, or make a new array `[val]` otherwise.
ToArray { val: InsnId, state: InsnId },
/// Call `to_a` on `val` if the method is defined, or make a new array `[val]` otherwise. If we
@@ -390,7 +431,12 @@ pub enum Insn {
/// Return C `true` if `val` is `Qnil`, else `false`.
IsNil { val: InsnId },
Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId },
- GetConstantPath { ic: *const iseq_inline_constant_cache },
+ GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId },
+
+ /// Get a global variable named `id`
+ GetGlobal { id: ID, state: InsnId },
+ /// Set a global variable named `id` to `val`
+ SetGlobal { id: ID, val: InsnId, state: InsnId },
//NewObject?
/// Get an instance variable `id` from `self_val`
@@ -421,7 +467,18 @@ pub enum Insn {
/// Ignoring keyword arguments etc for now
SendWithoutBlock { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
Send { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
- SendWithoutBlockDirect { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, iseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
+ SendWithoutBlockDirect {
+ self_val: InsnId,
+ call_info: CallInfo,
+ cd: *const rb_call_data,
+ cme: *const rb_callable_method_entry_t,
+ iseq: IseqPtr,
+ args: Vec<InsnId>,
+ state: InsnId,
+ },
+
+ // Invoke a builtin function
+ InvokeBuiltin { bf: rb_builtin_function, args: Vec<InsnId>, state: InsnId },
/// Control flow instructions
Return { val: InsnId },
@@ -439,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.
@@ -459,7 +519,7 @@ impl Insn {
Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
- | Insn::ArrayPush { .. } | Insn::SideExit { .. } => false,
+ | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } => false,
_ => true,
}
}
@@ -552,7 +612,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}") }
Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") }
Insn::HashDup { val, .. } => { write!(f, "HashDup {val}") }
- Insn::StringCopy { val } => { write!(f, "StringCopy {val}") }
+ Insn::StringCopy { val, .. } => { write!(f, "StringCopy {val}") }
Insn::Test { val } => { write!(f, "Test {val}") }
Insn::IsNil { val } => { write!(f, "IsNil {val}") }
Insn::Jump(target) => { write!(f, "Jump {target}") }
@@ -582,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}") },
@@ -597,7 +664,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {}", guard_type.print(self.ptr_map)) },
Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) },
Insn::PatchPoint(invariant) => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) },
- Insn::GetConstantPath { ic } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) },
+ Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) },
Insn::CCall { cfun, args, name, return_type: _, elidable: _ } => {
write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfun))?;
for arg in args {
@@ -625,11 +692,17 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::DefinedIvar { self_val, id, .. } => write!(f, "DefinedIvar {self_val}, :{}", id.contents_lossy().into_owned()),
Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy().into_owned()),
Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy().into_owned()),
+ Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy().into_owned()),
+ Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy().into_owned()),
Insn::ToArray { val, .. } => write!(f, "ToArray {val}"),
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)
+ }
insn => { write!(f, "{insn:?}") }
}
}
@@ -923,7 +996,7 @@ impl Function {
}
},
Return { val } => Return { val: find!(*val) },
- StringCopy { val } => StringCopy { val: find!(*val) },
+ StringCopy { val, chilled } => StringCopy { val: find!(*val), chilled: *chilled },
StringIntern { val } => StringIntern { val: find!(*val) },
Test { val } => Test { val: find!(*val) },
&IsNil { val } => IsNil { val: find!(val) },
@@ -943,6 +1016,13 @@ impl Function {
FixnumGe { left, right } => FixnumGe { left: find!(*left), right: find!(*right) },
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(),
@@ -950,10 +1030,11 @@ impl Function {
args: args.iter().map(|arg| find!(*arg)).collect(),
state: *state,
},
- SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state } => SendWithoutBlockDirect {
+ SendWithoutBlockDirect { self_val, call_info, cd, cme, iseq, args, state } => SendWithoutBlockDirect {
self_val: find!(*self_val),
call_info: call_info.clone(),
cd: *cd,
+ cme: *cme,
iseq: *iseq,
args: args.iter().map(|arg| find!(*arg)).collect(),
state: *state,
@@ -966,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 },
@@ -982,6 +1064,8 @@ impl Function {
}
&NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) },
ArrayMax { elements, state } => ArrayMax { elements: find_vec!(*elements), state: find!(*state) },
+ &GetGlobal { id, state } => GetGlobal { id, state },
+ &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
&GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state },
&SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val, state },
&ToArray { val, state } => ToArray { val: find!(val), state },
@@ -1012,7 +1096,7 @@ impl Function {
assert!(self.insns[insn.0].has_output());
match &self.insns[insn.0] {
Insn::Param { .. } => unimplemented!("params should not be present in block.insns"),
- Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
+ Insn::SetGlobal { .. } | Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } =>
@@ -1056,16 +1140,20 @@ impl Function {
Insn::FixnumLe { .. } => types::BoolExact,
Insn::FixnumGt { .. } => types::BoolExact,
Insn::FixnumGe { .. } => types::BoolExact,
+ Insn::PutSpecialObject { .. } => types::BasicObject,
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,
Insn::ArrayMax { .. } => types::BasicObject,
+ Insn::GetGlobal { .. } => types::BasicObject,
Insn::GetIvar { .. } => types::BasicObject,
Insn::ToNewArray { .. } => types::ArrayExact,
Insn::ToArray { .. } => types::ArrayExact,
+ Insn::ObjToString { .. } => types::BasicObject,
}
}
@@ -1189,6 +1277,38 @@ impl Function {
}
}
+ fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32) {
+ let self_type = self.type_of(self_val);
+ if let Some(obj) = self_type.ruby_object() {
+ if obj.is_frozen() {
+ self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass, bop }));
+ self.make_equal_to(orig_insn_id, self_val);
+ return;
+ }
+ }
+ self.push_insn_id(block, orig_insn_id);
+ }
+
+ fn try_rewrite_freeze(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId) {
+ if self.is_a(self_val, types::StringExact) {
+ self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_FREEZE);
+ } else if self.is_a(self_val, types::ArrayExact) {
+ self.rewrite_if_frozen(block, orig_insn_id, self_val, ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE);
+ } else if self.is_a(self_val, types::HashExact) {
+ self.rewrite_if_frozen(block, orig_insn_id, self_val, HASH_REDEFINED_OP_FLAG, BOP_FREEZE);
+ } else {
+ self.push_insn_id(block, orig_insn_id);
+ }
+ }
+
+ fn try_rewrite_uminus(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId) {
+ if self.is_a(self_val, types::StringExact) {
+ self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_UMINUS);
+ } else {
+ self.push_insn_id(block, orig_insn_id);
+ }
+ }
+
/// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target
/// ISEQ statically. This removes run-time method lookups and opens the door for inlining.
fn optimize_direct_sends(&mut self) {
@@ -1219,6 +1339,10 @@ impl Function {
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGt { left, right }, BOP_GT, self_val, args[0], state),
Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == ">=" && args.len() == 1 =>
self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGe { left, right }, BOP_GE, self_val, args[0], state),
+ Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "freeze" && args.len() == 0 =>
+ self.try_rewrite_freeze(block, insn_id, self_val),
+ Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "-@" && args.len() == 0 =>
+ self.try_rewrite_uminus(block, insn_id, self_val),
Insn::SendWithoutBlock { mut self_val, call_info, cd, args, state } => {
let frame_state = self.frame_state(state);
let (klass, guard_equal_to) = if let Some(klass) = self.type_of(self_val).runtime_exact_ruby_class() {
@@ -1251,10 +1375,10 @@ impl Function {
if let Some(expected) = guard_equal_to {
self_val = self.push_insn(block, Insn::GuardBitEquals { val: self_val, expected, state });
}
- let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state });
+ let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, cme, iseq, args, state });
self.make_equal_to(insn_id, send_direct);
}
- Insn::GetConstantPath { ic } => {
+ Insn::GetConstantPath { ic, .. } => {
let idlist: *const ID = unsafe { (*ic).segments };
let ice = unsafe { (*ic).entry };
if ice.is_null() {
@@ -1273,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); }
}
}
@@ -1541,9 +1674,14 @@ impl Function {
if necessary[insn_id.0] { continue; }
necessary[insn_id.0] = true;
match self.find(insn_id) {
- Insn::Const { .. } | Insn::Param { .. }
- | Insn::PatchPoint(..) | Insn::GetConstantPath { .. } =>
+ Insn::Const { .. }
+ | Insn::Param { .. }
+ | Insn::PatchPoint(..)
+ | Insn::PutSpecialObject { .. } =>
{}
+ Insn::GetConstantPath { ic: _, state } => {
+ worklist.push_back(state);
+ }
Insn::ArrayMax { elements, state }
| Insn::NewArray { elements, state } => {
worklist.extend(elements);
@@ -1561,14 +1699,15 @@ impl Function {
worklist.push_back(high);
worklist.push_back(state);
}
- Insn::StringCopy { val }
+ Insn::StringCopy { val, .. }
| Insn::StringIntern { val }
| Insn::Return { val }
| Insn::Defined { v: val, .. }
| Insn::Test { val }
| Insn::IsNil { val } =>
worklist.push_back(val),
- Insn::GuardType { val, state, .. }
+ Insn::SetGlobal { val, state, .. }
+ | Insn::GuardType { val, state, .. }
| Insn::GuardBitEquals { val, state, .. }
| Insn::ToArray { val, state }
| Insn::ToNewArray { val, state } => {
@@ -1620,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);
@@ -1635,6 +1778,11 @@ 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),
}
}
@@ -1644,6 +1792,67 @@ impl Function {
}
}
+ fn absorb_dst_block(&mut self, num_in_edges: &Vec<u32>, block: BlockId) -> bool {
+ let Some(terminator_id) = self.blocks[block.0].insns.last()
+ else { return false };
+ let Insn::Jump(BranchEdge { target, args }) = self.find(*terminator_id)
+ else { return false };
+ if target == block {
+ // Can't absorb self
+ return false;
+ }
+ if num_in_edges[target.0] != 1 {
+ // Can't absorb block if it's the target of more than one branch
+ return false;
+ }
+ // Link up params with block args
+ let params = std::mem::take(&mut self.blocks[target.0].params);
+ assert_eq!(args.len(), params.len());
+ for (arg, param) in args.iter().zip(params) {
+ self.make_equal_to(param, *arg);
+ }
+ // Remove branch instruction
+ self.blocks[block.0].insns.pop();
+ // Move target instructions into block
+ let target_insns = std::mem::take(&mut self.blocks[target.0].insns);
+ self.blocks[block.0].insns.extend(target_insns);
+ true
+ }
+
+ /// Clean up linked lists of blocks A -> B -> C into A (with B's and C's instructions).
+ fn clean_cfg(&mut self) {
+ // num_in_edges is invariant throughout cleaning the CFG:
+ // * we don't allocate new blocks
+ // * blocks that get absorbed are not in RPO anymore
+ // * blocks pointed to by blocks that get absorbed retain the same number of in-edges
+ let mut num_in_edges = vec![0; self.blocks.len()];
+ for block in self.rpo() {
+ for &insn in &self.blocks[block.0].insns {
+ if let Insn::IfTrue { target, .. } | Insn::IfFalse { target, .. } | Insn::Jump(target) = self.find(insn) {
+ num_in_edges[target.target.0] += 1;
+ }
+ }
+ }
+ let mut changed = false;
+ loop {
+ let mut iter_changed = false;
+ for block in self.rpo() {
+ // Ignore transient empty blocks
+ if self.blocks[block.0].insns.is_empty() { continue; }
+ loop {
+ let absorbed = self.absorb_dst_block(&num_in_edges, block);
+ if !absorbed { break; }
+ iter_changed = true;
+ }
+ }
+ if !iter_changed { break; }
+ changed = true;
+ }
+ if changed {
+ self.infer_types();
+ }
+ }
+
/// Return a traversal of the `Function`'s `BlockId`s in reverse post-order.
pub fn rpo(&self) -> Vec<BlockId> {
let mut result = self.po_from(self.entry_block);
@@ -1683,6 +1892,7 @@ impl Function {
self.optimize_direct_sends();
self.optimize_c_calls();
self.fold_constants();
+ self.clean_cfg();
self.eliminate_dead_code();
// Dump HIR after optimization
@@ -2074,10 +2284,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_nop => {},
YARVINSN_putnil => { state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(Qnil) })); },
YARVINSN_putobject => { state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) })); },
- YARVINSN_putstring | YARVINSN_putchilledstring => {
- // TODO(max): Do something different for chilled string
+ YARVINSN_putspecialobject => {
+ let value_type = SpecialObjectType::from(get_arg(pc, 0).as_u32());
+ state.stack_push(fun.push_insn(block, Insn::PutSpecialObject { value_type }));
+ }
+ YARVINSN_putstring => {
let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
- let insn_id = fun.push_insn(block, Insn::StringCopy { val });
+ let insn_id = fun.push_insn(block, Insn::StringCopy { val, chilled: false });
+ state.stack_push(insn_id);
+ }
+ YARVINSN_putchilledstring => {
+ let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
+ let insn_id = fun.push_insn(block, Insn::StringCopy { val, chilled: true });
state.stack_push(insn_id);
}
YARVINSN_putself => { state.stack_push(self_param); }
@@ -2197,7 +2415,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
YARVINSN_opt_getconstant_path => {
let ic = get_arg(pc, 0).as_ptr();
- state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic }));
+ let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic, state: snapshot }));
}
YARVINSN_branchunless => {
let offset = get_arg(pc, 0).as_i64();
@@ -2272,6 +2491,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()?;
@@ -2349,6 +2576,34 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id });
state.stack_push(send);
}
+ YARVINSN_opt_hash_freeze |
+ YARVINSN_opt_ary_freeze |
+ YARVINSN_opt_str_freeze |
+ YARVINSN_opt_str_uminus => {
+ // NB: these instructions have the recv for the call at get_arg(0)
+ let cd: *const rb_call_data = get_arg(pc, 1).as_ptr();
+ let call_info = unsafe { rb_get_call_data_ci(cd) };
+ if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) {
+ // Unknown call type; side-exit into the interpreter
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ fun.push_insn(block, Insn::SideExit { state: exit_id });
+ break; // End the block
+ }
+ let argc = unsafe { vm_ci_argc((*cd).ci) };
+ let name = insn_name(opcode as usize);
+ assert_eq!(0, argc, "{name} should not have args");
+ let args = vec![];
+
+ let method_name = unsafe {
+ let mid = rb_vm_ci_mid(call_info);
+ mid.contents_lossy().into_owned()
+ };
+
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let recv = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
+ let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id });
+ state.stack_push(send);
+ }
YARVINSN_leave => {
fun.push_insn(block, Insn::Return { val: state.stack_pop()? });
@@ -2435,6 +2690,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let send = fun.push_insn(block, Insn::Send { self_val: recv, call_info: CallInfo { method_name }, cd, blockiseq, args, state: exit_id });
state.stack_push(send);
}
+ YARVINSN_getglobal => {
+ let id = ID(get_arg(pc, 0).as_u64());
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let result = fun.push_insn(block, Insn::GetGlobal { id, state: exit_id });
+ state.stack_push(result);
+ }
+ YARVINSN_setglobal => {
+ let id = ID(get_arg(pc, 0).as_u64());
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let val = state.stack_pop()?;
+ fun.push_insn(block, Insn::SetGlobal { id, val, state: exit_id });
+ }
YARVINSN_getinstancevariable => {
let id = ID(get_arg(pc, 0).as_u64());
// ic is in arg 1
@@ -2449,6 +2716,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()?;
@@ -2457,6 +2734,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 });
@@ -2750,7 +3076,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);
@@ -2777,7 +3103,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));
}
@@ -2799,7 +3125,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()));
@@ -3015,6 +3341,62 @@ mod tests {
}
#[test]
+ fn test_opt_hash_freeze() {
+ eval("
+ def test = {}.freeze
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_opt_hash_freeze, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v4:BasicObject = SendWithoutBlock v3, :freeze
+ Return v4
+ "#]]);
+ }
+
+ #[test]
+ fn test_opt_ary_freeze() {
+ eval("
+ def test = [].freeze
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_opt_ary_freeze, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v4:BasicObject = SendWithoutBlock v3, :freeze
+ Return v4
+ "#]]);
+ }
+
+ #[test]
+ fn test_opt_str_freeze() {
+ eval("
+ def test = ''.freeze
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_opt_str_freeze, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v4:BasicObject = SendWithoutBlock v3, :freeze
+ Return v4
+ "#]]);
+ }
+
+ #[test]
+ fn test_opt_str_uminus() {
+ eval("
+ def test = -''
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_opt_str_uminus, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v4:BasicObject = SendWithoutBlock v3, :-@
+ Return v4
+ "#]]);
+ }
+
+ #[test]
fn test_setlocal_getlocal() {
eval("
def test
@@ -3490,6 +3872,13 @@ mod tests {
assert_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject, v1:BasicObject):
+ v3:BasicObject = PutSpecialObject VMCore
+ v5:HashExact = NewHash
+ v7:BasicObject = SendWithoutBlock v3, :core#hash_merge_kwd, v5, v1
+ v8:BasicObject = PutSpecialObject VMCore
+ v9:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v10:Fixnum[1] = Const Value(1)
+ v12:BasicObject = SendWithoutBlock v8, :core#hash_merge_ptr, v7, v9, v10
SideExit
"#]]);
}
@@ -3530,14 +3919,14 @@ mod tests {
assert_method_hir_with_opcode("test", YARVINSN_opt_new, expect![[r#"
fn test:
bb0(v0:BasicObject):
- v2:BasicObject = GetConstantPath 0x1000
- v3:NilClassExact = Const Value(nil)
- Jump bb1(v0, v3, v2)
- bb1(v5:BasicObject, v6:NilClassExact, v7:BasicObject):
- v10:BasicObject = SendWithoutBlock v7, :new
- Jump bb2(v5, v10, v6)
- bb2(v12:BasicObject, v13:BasicObject, v14:NilClassExact):
- Return v13
+ v3:BasicObject = GetConstantPath 0x1000
+ v4:NilClassExact = Const Value(nil)
+ Jump bb1(v0, v4, v3)
+ bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject):
+ v11:BasicObject = SendWithoutBlock v8, :new
+ Jump bb2(v6, v11, v7)
+ bb2(v13:BasicObject, v14:BasicObject, v15:NilClassExact):
+ Return v14
"#]]);
}
@@ -3712,6 +4101,35 @@ mod tests {
}
#[test]
+ fn test_setglobal() {
+ eval("
+ def test = $foo = 1
+ test
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_setglobal, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:Fixnum[1] = Const Value(1)
+ SetGlobal :$foo, v2
+ Return v2
+ "#]]);
+ }
+
+ #[test]
+ fn test_getglobal() {
+ eval("
+ def test = $foo
+ test
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_getglobal, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:BasicObject = GetGlobal :$foo
+ Return v3
+ "#]]);
+ }
+
+ #[test]
fn test_splatarray_mut() {
eval("
def test(a) = [*a]
@@ -3895,6 +4313,68 @@ mod tests {
}
#[test]
+ // Tests for ConstBase requires either constant or class definition, both
+ // of which can't be performed inside a method.
+ fn test_putspecialobject_vm_core_and_cbase() {
+ eval("
+ def test
+ alias aliased __callee__
+ end
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_putspecialobject, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:BasicObject = PutSpecialObject VMCore
+ v3:BasicObject = PutSpecialObject CBase
+ v4:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v5:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v7:BasicObject = SendWithoutBlock v2, :core#set_method_alias, v3, v4, v5
+ Return v7
+ "#]]);
+ }
+
+ #[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
@@ -3910,6 +4390,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)]
@@ -3920,7 +4475,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();
@@ -3962,9 +4517,6 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
- v3:FalseClassExact = Const Value(false)
- Jump bb1(v0, v3)
- bb1(v8:BasicObject, v9:FalseClassExact):
v11:Fixnum[4] = Const Value(4)
Return v11
"#]]);
@@ -4141,8 +4693,6 @@ mod opt_tests {
fn test:
bb0(v0:BasicObject):
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
- Jump bb1(v0)
- bb1(v10:BasicObject):
v12:Fixnum[4] = Const Value(4)
Return v12
"#]]);
@@ -4205,8 +4755,6 @@ mod opt_tests {
bb0(v0:BasicObject):
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
- Jump bb1(v0)
- bb1(v10:BasicObject):
v12:Fixnum[4] = Const Value(4)
Return v12
"#]]);
@@ -4890,9 +5438,9 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
- v2:BasicObject = GetConstantPath 0x1000
- v3:Fixnum[5] = Const Value(5)
- Return v3
+ v3:BasicObject = GetConstantPath 0x1000
+ v4:Fixnum[5] = Const Value(5)
+ Return v4
"#]]);
}
@@ -4961,8 +5509,8 @@ mod opt_tests {
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, M)
PatchPoint MethodRedefined(Module@0x1008, name@0x1010)
- v6:Fixnum[1] = Const Value(1)
- Return v6
+ v7:Fixnum[1] = Const Value(1)
+ Return v7
"#]]);
}
@@ -5079,8 +5627,8 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
- v2:BasicObject = GetConstantPath 0x1000
- Return v2
+ v3:BasicObject = GetConstantPath 0x1000
+ Return v3
"#]]);
}
@@ -5094,8 +5642,8 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
- v2:BasicObject = GetConstantPath 0x1000
- Return v2
+ v3:BasicObject = GetConstantPath 0x1000
+ Return v3
"#]]);
}
@@ -5110,8 +5658,8 @@ mod opt_tests {
bb0(v0:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Kernel)
- v6:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- Return v6
+ v7:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Return v7
"#]]);
}
@@ -5132,8 +5680,8 @@ mod opt_tests {
bb0(v0:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Foo::Bar::C)
- v6:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- Return v6
+ v7:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ Return v7
"#]]);
}
@@ -5149,14 +5697,10 @@ mod opt_tests {
bb0(v0:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v19:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- v3:NilClassExact = Const Value(nil)
- Jump bb1(v0, v3, v19)
- bb1(v5:BasicObject, v6:NilClassExact, v7:BasicObject[VALUE(0x1008)]):
- v10:BasicObject = SendWithoutBlock v7, :new
- Jump bb2(v5, v10, v6)
- bb2(v12:BasicObject, v13:BasicObject, v14:NilClassExact):
- Return v13
+ v20:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v4:NilClassExact = Const Value(nil)
+ v11:BasicObject = SendWithoutBlock v20, :new
+ Return v11
"#]]);
}
@@ -5176,15 +5720,11 @@ mod opt_tests {
bb0(v0:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v21:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- v3:NilClassExact = Const Value(nil)
- v4:Fixnum[1] = Const Value(1)
- Jump bb1(v0, v3, v21, v4)
- bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject[VALUE(0x1008)], v9:Fixnum[1]):
- v12:BasicObject = SendWithoutBlock v8, :new, v9
- Jump bb2(v6, v12, v7)
- bb2(v14:BasicObject, v15:BasicObject, v16:NilClassExact):
- Return v15
+ v22:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v4:NilClassExact = Const Value(nil)
+ v5:Fixnum[1] = Const Value(1)
+ v13:BasicObject = SendWithoutBlock v22, :new, v5
+ Return v13
"#]]);
}
@@ -5244,4 +5784,258 @@ mod opt_tests {
Return v2
"#]]);
}
+
+ #[test]
+ fn test_elide_freeze_with_frozen_hash() {
+ eval("
+ def test = {}.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_freeze_with_refrozen_hash() {
+ eval("
+ def test = {}.freeze.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)
+ PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_freeze_with_unfrozen_hash() {
+ eval("
+ def test = {}.dup.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:HashExact = NewHash
+ v5:BasicObject = SendWithoutBlock v3, :dup
+ v7:BasicObject = SendWithoutBlock v5, :freeze
+ Return v7
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_freeze_hash_with_args() {
+ eval("
+ def test = {}.freeze(nil)
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:HashExact = NewHash
+ v4:NilClassExact = Const Value(nil)
+ v6:BasicObject = SendWithoutBlock v3, :freeze, v4
+ Return v6
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_freeze_with_frozen_ary() {
+ eval("
+ def test = [].freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_freeze_with_refrozen_ary() {
+ eval("
+ def test = [].freeze.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
+ PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_freeze_with_unfrozen_ary() {
+ eval("
+ def test = [].dup.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:ArrayExact = NewArray
+ v5:BasicObject = SendWithoutBlock v3, :dup
+ v7:BasicObject = SendWithoutBlock v5, :freeze
+ Return v7
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_freeze_ary_with_args() {
+ eval("
+ def test = [].freeze(nil)
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:ArrayExact = NewArray
+ v4:NilClassExact = Const Value(nil)
+ v6:BasicObject = SendWithoutBlock v3, :freeze, v4
+ Return v6
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_freeze_with_frozen_str() {
+ eval("
+ def test = ''.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_freeze_with_refrozen_str() {
+ eval("
+ def test = ''.freeze.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)
+ PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_freeze_with_unfrozen_str() {
+ eval("
+ def test = ''.dup.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:StringExact = StringCopy v2
+ v5:BasicObject = SendWithoutBlock v3, :dup
+ v7:BasicObject = SendWithoutBlock v5, :freeze
+ Return v7
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_freeze_str_with_args() {
+ eval("
+ def test = ''.freeze(nil)
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:StringExact = StringCopy v2
+ v4:NilClassExact = Const Value(nil)
+ v6:BasicObject = SendWithoutBlock v3, :freeze, v4
+ Return v6
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_uminus_with_frozen_str() {
+ eval("
+ def test = -''
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_elide_uminus_with_refrozen_str() {
+ eval("
+ def test = -''.freeze
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE)
+ PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS)
+ Return v3
+ "#]]);
+ }
+
+ #[test]
+ fn test_no_elide_uminus_with_unfrozen_str() {
+ eval("
+ def test = -''.dup
+ ");
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:StringExact = StringCopy v2
+ v5:BasicObject = SendWithoutBlock v3, :dup
+ v7:BasicObject = SendWithoutBlock v5, :-@
+ 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
+ "#]]);
+ }
}