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.rs534
1 files changed, 483 insertions, 51 deletions
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 45a9024ca9..fbca1f4418 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -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,7 @@ 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 },
@@ -565,7 +606,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}") }
@@ -610,7 +651,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 {
@@ -645,6 +686,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
Insn::SideExit { .. } => write!(f, "SideExit"),
+ Insn::PutSpecialObject { value_type } => {
+ write!(f, "PutSpecialObject {}", value_type)
+ }
insn => { write!(f, "{insn:?}") }
}
}
@@ -938,7 +982,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) },
@@ -958,6 +1002,7 @@ 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 },
SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock {
self_val: find!(*self_val),
call_info: call_info.clone(),
@@ -1074,6 +1119,7 @@ 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,
@@ -1208,6 +1254,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) {
@@ -1238,6 +1316,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() {
@@ -1273,7 +1355,7 @@ impl Function {
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() {
@@ -1560,9 +1642,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);
@@ -1580,7 +1667,7 @@ 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, .. }
@@ -2095,10 +2182,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); }
@@ -2218,7 +2313,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();
@@ -2370,6 +2466,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()? });
@@ -3048,6 +3172,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
@@ -3523,6 +3703,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
"#]]);
}
@@ -3563,14 +3750,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
"#]]);
}
@@ -3957,6 +4144,27 @@ 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 test_branchnil() {
eval("
def test(x) = x&.itself
@@ -4952,9 +5160,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
"#]]);
}
@@ -5023,8 +5231,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
"#]]);
}
@@ -5141,8 +5349,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
"#]]);
}
@@ -5156,8 +5364,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
"#]]);
}
@@ -5172,8 +5380,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
"#]]);
}
@@ -5194,8 +5402,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
"#]]);
}
@@ -5211,14 +5419,14 @@ 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)
+ Jump bb1(v0, v4, v20)
+ bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject[VALUE(0x1008)]):
+ v11:BasicObject = SendWithoutBlock v8, :new
+ Jump bb2(v6, v11, v7)
+ bb2(v13:BasicObject, v14:BasicObject, v15:NilClassExact):
+ Return v14
"#]]);
}
@@ -5238,15 +5446,15 @@ 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)
+ Jump bb1(v0, v4, v22, v5)
+ bb1(v7:BasicObject, v8:NilClassExact, v9:BasicObject[VALUE(0x1008)], v10:Fixnum[1]):
+ v13:BasicObject = SendWithoutBlock v9, :new, v10
+ Jump bb2(v7, v13, v8)
+ bb2(v15:BasicObject, v16:BasicObject, v17:NilClassExact):
+ Return v16
"#]]);
}
@@ -5306,4 +5514,228 @@ 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
+ "#]]);
+ }
}