diff options
Diffstat (limited to 'zjit/src')
-rw-r--r-- | zjit/src/codegen.rs | 45 | ||||
-rw-r--r-- | zjit/src/cruby.rs | 2 | ||||
-rw-r--r-- | zjit/src/cruby_bindings.inc.rs | 2 | ||||
-rw-r--r-- | zjit/src/hir.rs | 534 |
4 files changed, 530 insertions, 53 deletions
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f274a64ca6..b1869f71c0 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -7,7 +7,7 @@ use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption}; use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP}; -use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX}; +use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX, SpecialObjectType}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types::Fixnum, Type}; use crate::options::get_option; @@ -252,6 +252,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewArray { elements, state } => gen_new_array(jit, asm, elements, &function.frame_state(*state)), Insn::NewRange { low, high, flag, state } => gen_new_range(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), + Insn::StringCopy { val, chilled } => gen_string_copy(asm, opnd!(val), *chilled), Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"), Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment Insn::Jump(branch) => return gen_jump(jit, asm, branch), @@ -277,8 +278,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), Insn::SetGlobal { id, val, state: _ } => gen_setglobal(asm, *id, opnd!(val)), Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id), + Insn::GetConstantPath { ic, state } => gen_get_constant_path(asm, *ic, &function.frame_state(*state)), Insn::SetIvar { self_val, id, val, state: _ } => return gen_setivar(asm, opnd!(self_val), *id, opnd!(val)), Insn::SideExit { state } => return gen_side_exit(jit, asm, &function.frame_state(*state)), + Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), _ => { debug!("ZJIT: gen_function: unexpected insn {:?}", insn); return None; @@ -293,6 +296,21 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Some(()) } +fn gen_get_constant_path(asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd { + unsafe extern "C" { + fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE; + } + + // Save PC since the call can allocate an IC + gen_save_pc(asm, state); + + let val = asm.ccall( + rb_vm_opt_getconstant_path as *const u8, + vec![EC, CFP, Opnd::const_ptr(ic as *const u8)], + ); + 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> { @@ -347,6 +365,20 @@ fn gen_side_exit(jit: &mut JITState, asm: &mut Assembler, state: &FrameState) -> Some(()) } +/// Emit a special object lookup +fn gen_putspecialobject(asm: &mut Assembler, value_type: SpecialObjectType) -> Opnd { + asm_comment!(asm, "call rb_vm_get_special_object"); + + // Get the EP of the current CFP and load it into a register + let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP); + let ep_reg = asm.load(ep_opnd); + + asm.ccall( + rb_vm_get_special_object as *const u8, + vec![ep_reg, Opnd::UImm(u64::from(value_type))], + ) +} + /// Compile an interpreter entry block to be inserted into an ISEQ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); @@ -596,6 +628,17 @@ fn gen_send_without_block_direct( Some(ret) } +/// Compile a string resurrection +fn gen_string_copy(asm: &mut Assembler, recv: Opnd, chilled: bool) -> Opnd { + asm_comment!(asm, "call rb_ec_str_resurrect"); + // TODO: split rb_ec_str_resurrect into separate functions + let chilled = if chilled { Opnd::Imm(1) } else { Opnd::Imm(0) }; + asm.ccall( + rb_ec_str_resurrect as *const u8, + vec![EC, recv, chilled], + ) +} + /// Compile an array duplication instruction fn gen_array_dup( asm: &mut Assembler, diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index de1c86e8d6..e0334ed44d 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -133,6 +133,7 @@ unsafe extern "C" { pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; + pub fn rb_vm_get_special_object(reg_ep: *const VALUE, value_type: vm_special_object_type) -> VALUE; pub fn rb_vm_concat_to_array(ary1: VALUE, ary2st: VALUE) -> VALUE; pub fn rb_vm_defined( ec: EcPtr, @@ -213,6 +214,7 @@ pub use rb_vm_ci_flag as vm_ci_flag; pub use rb_vm_ci_kwarg as vm_ci_kwarg; pub use rb_METHOD_ENTRY_VISI as METHOD_ENTRY_VISI; pub use rb_RCLASS_ORIGIN as RCLASS_ORIGIN; +pub use rb_vm_get_special_object as vm_get_special_object; /// Helper so we can get a Rust string for insn_name() pub fn insn_name(opcode: usize) -> String { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5fb5c2ec02..d5e54955c8 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -227,7 +227,7 @@ pub const imemo_parser_strterm: imemo_type = 10; pub const imemo_callinfo: imemo_type = 11; pub const imemo_callcache: imemo_type = 12; pub const imemo_constcache: imemo_type = 13; -pub const imemo_class_fields: imemo_type = 14; +pub const imemo_fields: imemo_type = 14; pub type imemo_type = u32; pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0; pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1; 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 + "#]]); + } } |