diff --git a/doc/maintainers.md b/doc/maintainers.md index d1855cdbcbf5cd..0ccc6cbb473887 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -222,6 +222,7 @@ consensus on ruby-core/ruby-dev. * Kevin Newton ([kddnewton]) * Eileen Uchitelle ([eileencodes]) * Aaron Patterson ([tenderlove]) +* Earlopain ([earlopain]) * https://github.com/ruby/prism * https://rubygems.org/gems/prism @@ -673,6 +674,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes [colby-swandale]: https://github.com/colby-swandale [drbrain]: https://github.com/drbrain [duerst]: https://github.com/duerst +[earlopain]: https://github.com/earlopain [eban]: https://github.com/eban [eileencodes]: https://github.com/eileencodes [hasumikin]: https://github.com/hasumikin diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 57d878a33fa299..618324ded6efa0 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -423,27 +423,28 @@ def dump_options_command_line(options) # Return the value that should be dumped for the version option. def dump_options_version(version) - checking = - case version - when "current" - RUBY_VERSION - when "latest" - nil - when "nearest" - if RUBY_VERSION <= "3.3" - "3.3" - elsif RUBY_VERSION >= "4.1" - "4.1" - else - RUBY_VERSION - end + case version + when "current" + version_string_to_number(RUBY_VERSION) || raise(CurrentVersionError, RUBY_VERSION) + when "latest", nil + 0 # Handled in pm_parser_init + when "nearest" + dump = version_string_to_number(RUBY_VERSION) + return dump if dump + if RUBY_VERSION < "3.3" + version_string_to_number("3.3") else - version + 0 # Handled in pm_parser_init end + else + version_string_to_number(version) || raise(ArgumentError, "invalid version: #{version}") + end + end - case checking - when nil - 0 # Handled in pm_parser_init + # Converts a version string like "4.0.0" or "4.0" into a number. + # Returns nil if the version is unknown. + def version_string_to_number(version) + case version when /\A3\.3(\.\d+)?\z/ 1 when /\A3\.4(\.\d+)?\z/ @@ -452,12 +453,6 @@ def dump_options_version(version) 3 when /\A4\.1(\.\d+)?\z/ 4 - else - if version == "current" - raise CurrentVersionError, RUBY_VERSION - else - raise ArgumentError, "invalid version: #{version}" - end end end diff --git a/prism/extension.c b/prism/extension.c index 363144970c8c97..fe3cd6a8f38cb1 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -205,18 +205,14 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { rb_exc_raise(rb_exc_new_cstr(rb_cPrismCurrentVersionError, ruby_version)); } } else if (RSTRING_LEN(value) == 7 && strncmp(version, "nearest", 7) == 0) { - const char *nearest_version; - - if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) { - nearest_version = "3.3"; - } else if (ruby_version[0] > '4' || (ruby_version[0] == '4' && ruby_version[2] > '1')) { - nearest_version = "4.1"; - } else { - nearest_version = ruby_version; - } - - if (!pm_options_version_set(options, nearest_version, 3)) { - rb_raise(rb_eArgError, "invalid nearest version: %s", nearest_version); + if (!pm_options_version_set(options, ruby_version, 3)) { + // Prism doesn't know this specific version. Is it lower? + if (ruby_version[0] < '3' || (ruby_version[0] == '3' && ruby_version[2] < '3')) { + options->version == PM_OPTIONS_VERSION_CRUBY_3_3; + } else { + // Must be higher. + options->version == PM_OPTIONS_VERSION_LATEST; + } } } else if (!pm_options_version_set(options, version, RSTRING_LEN(value))) { rb_raise(rb_eArgError, "invalid version: %" PRIsVALUE, value); diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index bbf28201ffe2b4..c9a47c1a61e03b 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -154,6 +154,10 @@ def test_version_current end end + def test_nearest + assert Prism.parse_success?("1 + 1", version: "nearest") + end + def test_scopes assert_kind_of Prism::CallNode, Prism.parse_statement("foo") assert_kind_of Prism::LocalVariableReadNode, Prism.parse_statement("foo", scopes: [[:foo]]) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index a3068ff23dfea4..a1ac23311b71db 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -538,12 +538,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardBitEquals { val, expected, reason, state } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, &function.frame_state(state)), &Insn::GuardAnyBitSet { val, mask, reason, state } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), &Insn::GuardNoBitsSet { val, mask, reason, state } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), - Insn::GuardNotFrozen { recv, state } => gen_guard_not_frozen(jit, asm, opnd!(recv), &function.frame_state(*state)), - Insn::GuardNotShared { recv, state } => gen_guard_not_shared(jit, asm, opnd!(recv), &function.frame_state(*state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), - &Insn::GuardSuperMethodEntry { lep, cme, state } => no_output!(gen_guard_super_method_entry(jit, asm, opnd!(lep), cme, &function.frame_state(state))), - Insn::GetBlockHandler { lep } => gen_get_block_handler(asm, opnd!(lep)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args). @@ -585,7 +581,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::ToArray { val, state } => { gen_to_array(jit, asm, opnd!(val), &function.frame_state(state)) }, &Insn::DefinedIvar { self_val, id, pushval, .. } => { gen_defined_ivar(asm, opnd!(self_val), id, pushval) }, &Insn::ArrayExtend { left, right, state } => { no_output!(gen_array_extend(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state))) }, - &Insn::GuardShape { val, shape, state } => gen_guard_shape(jit, asm, opnd!(val), shape, &function.frame_state(state)), Insn::LoadPC => gen_load_pc(asm), Insn::LoadEC => gen_load_ec(), &Insn::GetEP { level } => gen_get_ep(asm, level), @@ -795,25 +790,6 @@ fn gen_getblockparam(jit: &mut JITState, asm: &mut Assembler, ep_offset: u32, le asm.load(Opnd::mem(VALUE_BITS, ep, offset)) } -fn gen_guard_not_frozen(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { - let recv = asm.load(recv); - // It's a heap object, so check the frozen flag - let flags = asm.load(Opnd::mem(64, recv, RUBY_OFFSET_RBASIC_FLAGS)); - asm.test(flags, (RUBY_FL_FREEZE as u64).into()); - // Side-exit if frozen - asm.jnz(side_exit(jit, state, GuardNotFrozen)); - recv -} - -fn gen_guard_not_shared(jit: &JITState, asm: &mut Assembler, recv: Opnd, state: &FrameState) -> Opnd { - let recv = asm.load(recv); - // It's a heap object, so check the shared flag - let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS)); - asm.test(flags, (RUBY_ELTS_SHARED as u64).into()); - asm.jnz(side_exit(jit, state, SideExitReason::GuardNotShared)); - recv -} - fn gen_guard_less(jit: &JITState, asm: &mut Assembler, left: Opnd, right: Opnd, state: &FrameState) -> Opnd { asm.cmp(left, right); asm.jge(side_exit(jit, state, SideExitReason::GuardLess)); @@ -826,28 +802,6 @@ fn gen_guard_greater_eq(jit: &JITState, asm: &mut Assembler, left: Opnd, right: left } -/// Guard that the method entry at ep[VM_ENV_DATA_INDEX_ME_CREF] matches the expected CME. -/// This ensures we're calling super from the expected method context. -fn gen_guard_super_method_entry( - jit: &JITState, - asm: &mut Assembler, - lep: Opnd, - cme: *const rb_callable_method_entry_t, - state: &FrameState, -) { - asm_comment!(asm, "guard super method entry"); - let ep_me_opnd = Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF); - let ep_me = asm.load(ep_me_opnd); - asm.cmp(ep_me, Opnd::UImm(cme as u64)); - asm.jne(side_exit(jit, state, SideExitReason::GuardSuperMethodEntry)); -} - -/// Get the block handler from ep[VM_ENV_DATA_INDEX_SPECVAL] at the local EP (LEP). -fn gen_get_block_handler(asm: &mut Assembler, lep: Opnd) -> Opnd { - asm_comment!(asm, "get block handler from LEP"); - asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)) -} - fn gen_get_constant_path(jit: &JITState, 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; @@ -1243,16 +1197,6 @@ fn gen_array_extend(jit: &mut JITState, asm: &mut Assembler, left: Opnd, right: asm_ccall!(asm, rb_ary_concat, left, right); } -fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: ShapeId, state: &FrameState) -> Opnd { - gen_incr_counter(asm, Counter::guard_shape_count); - let shape_id_offset = unsafe { rb_shape_id_offset() }; - let val = asm.load(val); - let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, val, shape_id_offset); - asm.cmp(shape_opnd, Opnd::UImm(shape.0 as u64)); - asm.jne(side_exit(jit, state, SideExitReason::GuardShape(shape))); - val -} - fn gen_load_pc(asm: &mut Assembler) -> Opnd { asm.load(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC)) } diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 94b2a443c8b043..8c57e4cc7178e1 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -1398,6 +1398,9 @@ pub(crate) mod ids { name: _shape_id name: _env_data_index_flags name: _env_data_index_specval + name: _ep_method_entry + name: _ep_specval + name: _rbasic_flags } /// Get an CRuby `ID` to an interned string, e.g. a particular method name. diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 8121b0065f593a..8596472f5a1443 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -351,8 +351,8 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In { let recv = fun.coerce_to(block, recv, types::ArrayExact, state); let index = fun.coerce_to(block, index, types::Fixnum, state); - let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); - let recv = fun.push_insn(block, hir::Insn::GuardNotShared { recv, state }); + fun.guard_not_frozen(block, recv, state); + fun.guard_not_shared(block, recv, state); // Bounds check: unbox Fixnum index and guard 0 <= idx < length. let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index }); @@ -381,9 +381,8 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { // Only inline the case of no arguments. let &[] = args else { return None; }; - // We know that all Array are HeapObject, so no need to insert a GuardType(HeapObject). - let arr = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); - Some(fun.push_insn(block, hir::Insn::ArrayPop { array: arr, state })) + fun.guard_not_shared(block, recv, state); + Some(fun.push_insn(block, hir::Insn::ArrayPop { array: recv, state })) } fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { @@ -476,7 +475,7 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); // We know that all String are HeapObject, so no need to insert a GuardType(HeapObject). - let recv = fun.push_insn(block, hir::Insn::GuardNotFrozen { recv, state }); + fun.guard_not_frozen(block, recv, state); let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); // String#setbyte returns the fixnum provided as its `value` argument back to the caller. Some(value) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9aa70b5d34d12e..eba3edee9904a6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1030,23 +1030,10 @@ pub enum Insn { GuardAnyBitSet { val: InsnId, mask: Const, reason: SideExitReason, state: InsnId }, /// Side-exit if (val & mask) != 0 GuardNoBitsSet { val: InsnId, mask: Const, reason: SideExitReason, state: InsnId }, - /// Side-exit if val doesn't have the expected shape. - GuardShape { val: InsnId, shape: ShapeId, state: InsnId }, - /// Side-exit if val is frozen. Does *not* check if the val is an immediate; assumes that it is - /// a heap object. - GuardNotFrozen { recv: InsnId, state: InsnId }, - /// Side-exit if val is shared. Does *not* check if the val is an immediate; assumes - /// that it is a heap object. - GuardNotShared { recv: InsnId, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). GuardLess { left: InsnId, right: InsnId, state: InsnId }, - /// Side-exit if the method entry at ep[VM_ENV_DATA_INDEX_ME_CREF] doesn't match the expected CME. - /// Used to ensure super calls are made from the expected method context. - GuardSuperMethodEntry { lep: InsnId, cme: *const rb_callable_method_entry_t, state: InsnId }, - /// Get the block handler from ep[VM_ENV_DATA_INDEX_SPECVAL] at the local EP (LEP). - GetBlockHandler { lep: InsnId }, /// Generate no code (or padding if necessary) and insert a patch point /// that can be rewritten to a side exit when the Invariant is broken. @@ -1075,7 +1062,7 @@ impl Insn { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardSuperMethodEntry { .. } + | Insn::CheckInterrupts { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } | Insn::ArrayAset { .. } => false, _ => true, @@ -1232,12 +1219,7 @@ impl Insn { Insn::GuardBitEquals { .. } => effects::Any, Insn::GuardAnyBitSet { .. } => effects::Any, Insn::GuardNoBitsSet { .. } => effects::Any, - Insn::GuardShape { .. } => effects::Any, - Insn::GuardNotFrozen { .. } => effects::Any, - Insn::GuardNotShared { .. } => effects::Any, Insn::GuardGreaterEq { .. } => effects::Any, - Insn::GuardSuperMethodEntry { .. } => effects::Any, - Insn::GetBlockHandler { .. } => effects::Any, Insn::GuardLess { .. } => effects::Any, Insn::PatchPoint { .. } => effects::Any, Insn::SideExit { .. } => effects::Any, @@ -1570,13 +1552,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, Insn::GuardAnyBitSet { val, mask, .. } => { write!(f, "GuardAnyBitSet {val}, {}", mask.print(self.ptr_map)) }, Insn::GuardNoBitsSet { val, mask, .. } => { write!(f, "GuardNoBitsSet {val}, {}", mask.print(self.ptr_map)) }, - &Insn::GuardShape { val, shape, .. } => { write!(f, "GuardShape {val}, {:p}", self.ptr_map.map_shape(shape)) }, - Insn::GuardNotFrozen { recv, .. } => write!(f, "GuardNotFrozen {recv}"), - Insn::GuardNotShared { recv, .. } => write!(f, "GuardNotShared {recv}"), Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), - Insn::GuardSuperMethodEntry { lep, cme, .. } => write!(f, "GuardSuperMethodEntry {lep}, {:p}", self.ptr_map.map_ptr(cme)), - Insn::GetBlockHandler { lep } => write!(f, "GetBlockHandler {lep}"), &Insn::GetBlockParam { level, ep_offset, .. } => { let name = get_local_var_name_for_printer(self.iseq, level, ep_offset) .map_or(String::new(), |x| format!("{x}, ")); @@ -2261,13 +2238,8 @@ impl Function { &GuardBitEquals { val, expected, reason, state } => GuardBitEquals { val: find!(val), expected, reason, state }, &GuardAnyBitSet { val, mask, reason, state } => GuardAnyBitSet { val: find!(val), mask, reason, state }, &GuardNoBitsSet { val, mask, reason, state } => GuardNoBitsSet { val: find!(val), mask, reason, state }, - &GuardShape { val, shape, state } => GuardShape { val: find!(val), shape, state }, - &GuardNotFrozen { recv, state } => GuardNotFrozen { recv: find!(recv), state }, - &GuardNotShared { recv, state } => GuardNotShared { recv: find!(recv), state }, &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, - &GuardSuperMethodEntry { lep, cme, state } => GuardSuperMethodEntry { lep: find!(lep), cme, state }, - &GetBlockHandler { lep } => GetBlockHandler { lep: find!(lep) }, &IsBlockGiven { lep } => IsBlockGiven { lep: find!(lep) }, &GetBlockParam { level, ep_offset, state } => GetBlockParam { level, ep_offset, state: find!(state) }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, @@ -2461,7 +2433,7 @@ impl Function { | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. } - | Insn::CheckInterrupts { .. } | Insn::GuardSuperMethodEntry { .. } + | Insn::CheckInterrupts { .. } | Insn::StoreField { .. } | Insn::WriteBarrier { .. } | Insn::HashAset { .. } | Insn::ArrayAset { .. } => panic!("Cannot infer type of instruction with no output: {}. See Insn::has_output().", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), @@ -2523,8 +2495,6 @@ impl Function { Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_const(*expected)), Insn::GuardAnyBitSet { val, .. } => self.type_of(*val), Insn::GuardNoBitsSet { val, .. } => self.type_of(*val), - Insn::GuardShape { val, .. } => self.type_of(*val), - Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => self.type_of(*recv), Insn::GuardLess { left, .. } => self.type_of(*left), Insn::GuardGreaterEq { left, .. } => self.type_of(*left), Insn::FixnumAdd { .. } => types::Fixnum, @@ -2582,7 +2552,6 @@ impl Function { Insn::GetLocal { .. } => types::BasicObject, Insn::IsBlockParamModified { .. } => types::CBool, Insn::GetBlockParam { .. } => types::BasicObject, - Insn::GetBlockHandler { .. } => types::RubyValue, // The type of Snapshot doesn't really matter; it's never materialized. It's used only // as a reference for FrameState, which we use to generate side-exit code. Insn::Snapshot { .. } => types::Any, @@ -2694,7 +2663,6 @@ impl Function { match self.insns[id.0] { Insn::GuardType { val, .. } | Insn::GuardTypeNot { val, .. } - | Insn::GuardShape { val, .. } | Insn::GuardBitEquals { val, .. } | Insn::GuardAnyBitSet { val, .. } | Insn::GuardNoBitsSet { val, .. } => self.chase_insn(val), @@ -2916,7 +2884,7 @@ impl Function { let recv = self.chase_insn(recv); for (entry_insn, entry_type_summary) in entries { - if self.union_find.borrow().find_const(*entry_insn) == recv { + if self.chase_insn(*entry_insn) == recv { if entry_type_summary.is_monomorphic() { let profiled_type = entry_type_summary.bucket(0); return ReceiverTypeResolution::Monomorphic { profiled_type }; @@ -3030,6 +2998,20 @@ impl Function { } } + pub fn load_rbasic_flags(&mut self, block: BlockId, recv: InsnId) -> InsnId { + self.push_insn(block, Insn::LoadField { recv, id: ID!(_rbasic_flags), offset: RUBY_OFFSET_RBASIC_FLAGS, return_type: types::CUInt64 }) + } + + pub fn guard_not_frozen(&mut self, block: BlockId, recv: InsnId, state: InsnId) { + let flags = self.load_rbasic_flags(block, recv); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), reason: SideExitReason::GuardNotFrozen, state }); + } + + pub fn guard_not_shared(&mut self, block: BlockId, recv: InsnId, state: InsnId) { + let flags = self.load_rbasic_flags(block, recv); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), reason: SideExitReason::GuardNotShared, state }); + } + /// Rewrite eligible Send/SendWithoutBlock opcodes into SendDirect /// opcodes if we know the target ISEQ statically. This removes run-time method lookups and /// opens the door for inlining. @@ -3278,8 +3260,7 @@ impl Function { // // No need for a GuardShape. if let OptimizedMethodType::StructAset = opt_type { - // We know that all Struct are HeapObject, so no need to insert a GuardType(HeapObject). - recv = self.push_insn(block, Insn::GuardNotFrozen { recv, state }); + self.guard_not_frozen(block, recv, state); } let (target, offset) = if is_embedded { @@ -3537,13 +3518,12 @@ impl Function { }); let lep = fun.push_insn(block, Insn::GetLEP); - fun.push_insn(block, Insn::GuardSuperMethodEntry { - lep, - cme: current_cme, - state - }); + // Load ep[VM_ENV_DATA_INDEX_ME_CREF] + let method_entry = fun.push_insn(block, Insn::LoadField { recv: lep, id: ID!(_ep_method_entry), offset: SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_ME_CREF, return_type: types::RubyValue }); + // Guard that it matches the expected CME + fun.push_insn(block, Insn::GuardBitEquals { val: method_entry, expected: Const::Value(current_cme.into()), reason: SideExitReason::GuardSuperMethodEntry, state }); - let block_handler = fun.push_insn(block, Insn::GetBlockHandler { lep }); + let block_handler = fun.push_insn(block, Insn::LoadField { recv: lep, id: ID!(_ep_specval), offset: SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL, return_type: types::RubyValue }); fun.push_insn(block, Insn::GuardBitEquals { val: block_handler, expected: Const::Value(VALUE(VM_BLOCK_HANDLER_NONE as usize)), @@ -4704,7 +4684,6 @@ impl Function { | &Insn::IncrCounter(_) | &Insn::IncrCounterPtr { .. } => {} - &Insn::GetBlockHandler { lep } | &Insn::IsBlockGiven { lep } => { worklist.push_back(lep); } @@ -4787,9 +4766,6 @@ impl Function { | &Insn::GuardBitEquals { val, state, .. } | &Insn::GuardAnyBitSet { val, state, .. } | &Insn::GuardNoBitsSet { val, state, .. } - | &Insn::GuardShape { val, state, .. } - | &Insn::GuardNotFrozen { recv: val, state } - | &Insn::GuardNotShared { recv: val, state } | &Insn::ToArray { val, state } | &Insn::IsMethodCfunc { val, state, .. } | &Insn::ToNewArray { val, state } @@ -4942,10 +4918,6 @@ impl Function { &Insn::GetSpecialNumber { state, .. } | &Insn::ObjectAllocClass { state, .. } | &Insn::SideExit { state, .. } => worklist.push_back(state), - &Insn::GuardSuperMethodEntry { lep, state, .. } => { - worklist.push_back(lep); - worklist.push_back(state); - } &Insn::UnboxFixnum { val } => worklist.push_back(val), &Insn::FixnumAref { recv, index } => { worklist.push_back(recv); @@ -5488,8 +5460,6 @@ impl Function { | Insn::Snapshot { .. } | Insn::Jump { .. } | Insn::EntryPoint { .. } - | Insn::GuardSuperMethodEntry { .. } - | Insn::GetBlockHandler { .. } | Insn::PatchPoint { .. } | Insn::SideExit { .. } | Insn::IncrCounter { .. } @@ -5508,7 +5478,6 @@ impl Function { Insn::Test { val } | Insn::IsNil { val } | Insn::IsMethodCfunc { val, .. } - | Insn::GuardShape { val, .. } | Insn::SetGlobal { val, .. } | Insn::SetLocal { val, .. } | Insn::SetClassVar { val, .. } @@ -5528,9 +5497,6 @@ impl Function { | Insn::DefinedIvar { self_val: val, .. } => { self.assert_subtype(insn_id, val, types::BasicObject) } - Insn::GuardNotFrozen { recv, .. } | Insn::GuardNotShared { recv, .. } => { - self.assert_subtype(insn_id, recv, types::HeapBasicObject) - } // Instructions with 2 Ruby object operands Insn::SetIvar { self_val: left, val: right, .. } | Insn::NewRange { low: left, high: right, .. } @@ -6326,7 +6292,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { visited.insert(block); // Load basic block params first - let self_param = fun.push_insn(block, Insn::Param); + let mut self_param = fun.push_insn(block, Insn::Param); let mut state = { let mut result = FrameState::new(iseq); let local_size = if jit_entry_insns.contains(&insn_idx) { num_locals(iseq) } else { incoming_state.locals.len() }; @@ -7340,6 +7306,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } let val = state.stack_pop()?; fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id }); + // SetIvar will raise if self is an immediate. If it raises, we will have + // exited JIT code. So upgrade the type within JIT code to a heap object. + self_param = fun.push_insn(block, Insn::RefineType { val: self_param, new_type: types::HeapBasicObject }); } YARVINSN_getclassvariable => { let id = ID(get_arg(pc, 0).as_u64()); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 8dec65fed634e6..e80175c6794d56 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4349,11 +4349,11 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode - v19:HeapBasicObject = GuardType v6, HeapBasicObject - v20:CShape = LoadField v19, :_shape_id@0x1000 - v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) - StoreField v19, :@foo@0x1002, v10 - WriteBarrier v19, v10 + v20:HeapBasicObject = GuardType v6, HeapBasicObject + v21:CShape = LoadField v20, :_shape_id@0x1000 + v22:CShape[0x1001] = GuardBitEquals v21, CShape(0x1001) + StoreField v20, :@foo@0x1002, v10 + WriteBarrier v20, v10 CheckInterrupts Return v10 "); @@ -4377,13 +4377,13 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode - v19:HeapBasicObject = GuardType v6, HeapBasicObject - v20:CShape = LoadField v19, :_shape_id@0x1000 - v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) - StoreField v19, :@foo@0x1002, v10 - WriteBarrier v19, v10 - v24:CShape[0x1003] = Const CShape(0x1003) - StoreField v19, :_shape_id@0x1000, v24 + v20:HeapBasicObject = GuardType v6, HeapBasicObject + v21:CShape = LoadField v20, :_shape_id@0x1000 + v22:CShape[0x1001] = GuardBitEquals v21, CShape(0x1001) + StoreField v20, :@foo@0x1002, v10 + WriteBarrier v20, v10 + v25:CShape[0x1003] = Const CShape(0x1003) + StoreField v20, :_shape_id@0x1000, v25 CheckInterrupts Return v10 "); @@ -4410,24 +4410,24 @@ mod hir_opt_tests { bb2(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - v25:HeapBasicObject = GuardType v6, HeapBasicObject - v26:CShape = LoadField v25, :_shape_id@0x1000 - v27:CShape[0x1001] = GuardBitEquals v26, CShape(0x1001) - StoreField v25, :@foo@0x1002, v10 - WriteBarrier v25, v10 - v30:CShape[0x1003] = Const CShape(0x1003) - StoreField v25, :_shape_id@0x1000, v30 - v16:Fixnum[2] = Const Value(2) + v27:HeapBasicObject = GuardType v6, HeapBasicObject + v28:CShape = LoadField v27, :_shape_id@0x1000 + v29:CShape[0x1001] = GuardBitEquals v28, CShape(0x1001) + StoreField v27, :@foo@0x1002, v10 + WriteBarrier v27, v10 + v32:CShape[0x1003] = Const CShape(0x1003) + StoreField v27, :_shape_id@0x1000, v32 + v14:HeapBasicObject = RefineType v6, HeapBasicObject + v17:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - v32:HeapBasicObject = GuardType v6, HeapBasicObject - v33:CShape = LoadField v32, :_shape_id@0x1000 - v34:CShape[0x1003] = GuardBitEquals v33, CShape(0x1003) - StoreField v32, :@bar@0x1004, v16 - WriteBarrier v32, v16 - v37:CShape[0x1005] = Const CShape(0x1005) - StoreField v32, :_shape_id@0x1000, v37 + v35:CShape = LoadField v14, :_shape_id@0x1000 + v36:CShape[0x1003] = GuardBitEquals v35, CShape(0x1003) + StoreField v14, :@bar@0x1004, v17 + WriteBarrier v14, v17 + v39:CShape[0x1005] = Const CShape(0x1005) + StoreField v14, :_shape_id@0x1000, v39 CheckInterrupts - Return v16 + Return v17 "); } @@ -6986,9 +6986,10 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v30:HeapObject[class_exact:C] = GuardNotFrozen v29 - StoreField v30, :foo=@0x1038, v12 - WriteBarrier v30, v12 + v30:CUInt64 = LoadField v29, :_rbasic_flags@0x1038 + v31:CUInt64 = GuardNoBitsSet v30, CUInt64(2048) + StoreField v29, :foo=@0x1039, v12 + WriteBarrier v29, v12 CheckInterrupts Return v12 "); @@ -7018,10 +7019,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] - v30:HeapObject[class_exact:C] = GuardNotFrozen v29 - v31:CPtr = LoadField v30, :_as_heap@0x1038 - StoreField v31, :foo=@0x1039, v12 - WriteBarrier v30, v12 + v30:CUInt64 = LoadField v29, :_rbasic_flags@0x1038 + v31:CUInt64 = GuardNoBitsSet v30, CUInt64(2048) + v32:CPtr = LoadField v29, :_as_heap@0x1039 + StoreField v32, :foo=@0x103a, v12 + WriteBarrier v29, v12 CheckInterrupts Return v12 "); @@ -7638,15 +7640,17 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) v31:ArrayExact = GuardType v9, ArrayExact - v32:ArrayExact = GuardNotFrozen v31 - v33:ArrayExact = GuardNotShared v32 - v34:CInt64[1] = UnboxFixnum v16 - v35:CInt64 = ArrayLength v33 - v36:CInt64[1] = GuardLess v34, v35 - v37:CInt64[0] = Const CInt64(0) - v38:CInt64[1] = GuardGreaterEq v36, v37 - ArrayAset v33, v38, v18 - WriteBarrier v33, v18 + v32:CUInt64 = LoadField v31, :_rbasic_flags@0x1038 + v33:CUInt64 = GuardNoBitsSet v32, CUInt64(2048) + v34:CUInt64 = LoadField v31, :_rbasic_flags@0x1038 + v35:CUInt64 = GuardNoBitsSet v34, CUInt64(4096) + v36:CInt64[1] = UnboxFixnum v16 + v37:CInt64 = ArrayLength v31 + v38:CInt64[1] = GuardLess v36, v37 + v39:CInt64[0] = Const CInt64(0) + v40:CInt64[1] = GuardGreaterEq v38, v39 + ArrayAset v31, v40, v18 + WriteBarrier v31, v18 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v18 @@ -7678,15 +7682,17 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) v35:ArrayExact = GuardType v13, ArrayExact v36:Fixnum = GuardType v14, Fixnum - v37:ArrayExact = GuardNotFrozen v35 - v38:ArrayExact = GuardNotShared v37 - v39:CInt64 = UnboxFixnum v36 - v40:CInt64 = ArrayLength v38 - v41:CInt64 = GuardLess v39, v40 - v42:CInt64[0] = Const CInt64(0) - v43:CInt64 = GuardGreaterEq v41, v42 - ArrayAset v38, v43, v15 - WriteBarrier v38, v15 + v37:CUInt64 = LoadField v35, :_rbasic_flags@0x1038 + v38:CUInt64 = GuardNoBitsSet v37, CUInt64(2048) + v39:CUInt64 = LoadField v35, :_rbasic_flags@0x1038 + v40:CUInt64 = GuardNoBitsSet v39, CUInt64(4096) + v41:CInt64 = UnboxFixnum v36 + v42:CInt64 = ArrayLength v35 + v43:CInt64 = GuardLess v41, v42 + v44:CInt64[0] = Const CInt64(0) + v45:CInt64 = GuardGreaterEq v43, v44 + ArrayAset v35, v45, v15 + WriteBarrier v35, v15 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v15 @@ -8004,8 +8010,9 @@ mod hir_opt_tests { v35:CInt64 = GuardLess v33, v34 v36:CInt64[0] = Const CInt64(0) v37:CInt64 = GuardGreaterEq v35, v36 - v38:StringExact = GuardNotFrozen v30 - v39:Fixnum = StringSetbyteFixnum v38, v31, v32 + v38:CUInt64 = LoadField v30, :_rbasic_flags@0x1039 + v39:CUInt64 = GuardNoBitsSet v38, CUInt64(2048) + v40:Fixnum = StringSetbyteFixnum v30, v31, v32 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v32 @@ -8045,8 +8052,9 @@ mod hir_opt_tests { v35:CInt64 = GuardLess v33, v34 v36:CInt64[0] = Const CInt64(0) v37:CInt64 = GuardGreaterEq v35, v36 - v38:StringSubclass[class_exact:MyString] = GuardNotFrozen v30 - v39:Fixnum = StringSetbyteFixnum v38, v31, v32 + v38:CUInt64 = LoadField v30, :_rbasic_flags@0x1039 + v39:CUInt64 = GuardNoBitsSet v38, CUInt64(2048) + v40:Fixnum = StringSetbyteFixnum v30, v31, v32 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts Return v32 @@ -10553,23 +10561,23 @@ mod hir_opt_tests { CheckInterrupts SetLocal :formatted, l0, EP@3, v15 PatchPoint SingleRactorMode - v56:HeapBasicObject = GuardType v14, HeapBasicObject - v57:CShape = LoadField v56, :_shape_id@0x1000 - v58:CShape[0x1001] = GuardBitEquals v57, CShape(0x1001) - StoreField v56, :@formatted@0x1002, v15 - WriteBarrier v56, v15 - v61:CShape[0x1003] = Const CShape(0x1003) - StoreField v56, :_shape_id@0x1000, v61 - v45:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) + v57:HeapBasicObject = GuardType v14, HeapBasicObject + v58:CShape = LoadField v57, :_shape_id@0x1000 + v59:CShape[0x1001] = GuardBitEquals v58, CShape(0x1001) + StoreField v57, :@formatted@0x1002, v15 + WriteBarrier v57, v15 + v62:CShape[0x1003] = Const CShape(0x1003) + StoreField v57, :_shape_id@0x1000, v62 + v46:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) - v66:BasicObject = CCallWithFrame v45, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 - v48:BasicObject = GetLocal :a, l0, EP@6 - v49:BasicObject = GetLocal :_b, l0, EP@5 - v50:BasicObject = GetLocal :_c, l0, EP@4 - v51:BasicObject = GetLocal :formatted, l0, EP@3 + v67:BasicObject = CCallWithFrame v46, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 + v49:BasicObject = GetLocal :a, l0, EP@6 + v50:BasicObject = GetLocal :_b, l0, EP@5 + v51:BasicObject = GetLocal :_c, l0, EP@4 + v52:BasicObject = GetLocal :formatted, l0, EP@3 CheckInterrupts - Return v66 + Return v67 "); } @@ -11268,12 +11276,13 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint MethodRedefined(A@0x1000, foo@0x1008, cme:0x1010) v17:CPtr = GetLEP - GuardSuperMethodEntry v17, 0x1038 - v19:RubyValue = GetBlockHandler v17 - v20:FalseClass = GuardBitEquals v19, Value(false) - v21:BasicObject = SendDirect v6, 0x1040, :foo (0x1050) + v18:RubyValue = LoadField v17, :_ep_method_entry@0x1038 + v19:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v18, Value(VALUE(0x1040)) + v20:RubyValue = LoadField v17, :_ep_specval@0x1048 + v21:FalseClass = GuardBitEquals v20, Value(false) + v22:BasicObject = SendDirect v6, 0x1050, :foo (0x1060) CheckInterrupts - Return v21 + Return v22 "); } @@ -11312,17 +11321,18 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): PatchPoint MethodRedefined(A@0x1000, foo@0x1008, cme:0x1010) v26:CPtr = GetLEP - GuardSuperMethodEntry v26, 0x1038 - v28:RubyValue = GetBlockHandler v26 - v29:FalseClass = GuardBitEquals v28, Value(false) - v30:BasicObject = SendDirect v8, 0x1040, :foo (0x1050), v9 + v27:RubyValue = LoadField v26, :_ep_method_entry@0x1038 + v28:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v27, Value(VALUE(0x1040)) + v29:RubyValue = LoadField v26, :_ep_specval@0x1048 + v30:FalseClass = GuardBitEquals v29, Value(false) + v31:BasicObject = SendDirect v8, 0x1050, :foo (0x1060), v9 v17:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1058, +@0x1060, cme:0x1068) - v33:Fixnum = GuardType v30, Fixnum - v34:Fixnum = FixnumAdd v33, v17 + PatchPoint MethodRedefined(Integer@0x1068, +@0x1070, cme:0x1078) + v34:Fixnum = GuardType v31, Fixnum + v35:Fixnum = FixnumAdd v34, v17 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v34 + Return v35 "); } @@ -11432,13 +11442,14 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) v17:CPtr = GetLEP - GuardSuperMethodEntry v17, 0x1038 - v19:RubyValue = GetBlockHandler v17 - v20:FalseClass = GuardBitEquals v19, Value(false) + v18:RubyValue = LoadField v17, :_ep_method_entry@0x1038 + v19:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v18, Value(VALUE(0x1040)) + v20:RubyValue = LoadField v17, :_ep_specval@0x1048 + v21:FalseClass = GuardBitEquals v20, Value(false) IncrCounter inline_cfunc_optimized_send_count - v22:Fixnum = CCall v6, :Hash#size@0x1040 + v23:Fixnum = CCall v6, :Hash#size@0x1050 CheckInterrupts - Return v22 + Return v23 "); } @@ -11465,13 +11476,14 @@ mod hir_opt_tests { bb2(v6:BasicObject): PatchPoint MethodRedefined(BasicObject@0x1000, initialize@0x1008, cme:0x1010) v17:CPtr = GetLEP - GuardSuperMethodEntry v17, 0x1038 - v19:RubyValue = GetBlockHandler v17 - v20:FalseClass = GuardBitEquals v19, Value(false) - v21:NilClass = Const Value(nil) + v18:RubyValue = LoadField v17, :_ep_method_entry@0x1038 + v19:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v18, Value(VALUE(0x1040)) + v20:RubyValue = LoadField v17, :_ep_specval@0x1048 + v21:FalseClass = GuardBitEquals v20, Value(false) + v22:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v21 + Return v22 "); } @@ -11491,7 +11503,7 @@ mod hir_opt_tests { assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to CCallVariadic but got:\n{hir}"); assert!(hir.contains("CCallVariadic"), "Should optimize to CCallVariadic for variadic cfunc:\n{hir}"); - assert_snapshot!(hir, @" + assert_snapshot!(hir, @r" fn byteindex@:3: bb0(): EntryPoint interpreter @@ -11516,12 +11528,13 @@ mod hir_opt_tests { bb4(v27:BasicObject, v28:BasicObject, v29:BasicObject): PatchPoint MethodRedefined(String@0x1010, byteindex@0x1018, cme:0x1020) v42:CPtr = GetLEP - GuardSuperMethodEntry v42, 0x1008 - v44:RubyValue = GetBlockHandler v42 - v45:FalseClass = GuardBitEquals v44, Value(false) - v46:BasicObject = CCallVariadic v27, :String#byteindex@0x1048, v28, v29 + v43:RubyValue = LoadField v42, :_ep_method_entry@0x1048 + v44:CallableMethodEntry[VALUE(0x1050)] = GuardBitEquals v43, Value(VALUE(0x1050)) + v45:RubyValue = LoadField v42, :_ep_specval@0x1058 + v46:FalseClass = GuardBitEquals v45, Value(false) + v47:BasicObject = CCallVariadic v27, :String#byteindex@0x1060, v28, v29 CheckInterrupts - Return v46 + Return v47 "); } @@ -11816,4 +11829,62 @@ mod hir_opt_tests { Return v36 "); } + + #[test] + fn upgrade_self_type_to_heap_after_setivar() { + eval(" + def test + @a = 1 + @b = 2 + @c = 3 + @d = 4 + end + test + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + v41:HeapBasicObject = GuardType v6, HeapBasicObject + v42:CShape = LoadField v41, :_shape_id@0x1000 + v43:CShape[0x1001] = GuardBitEquals v42, CShape(0x1001) + StoreField v41, :@a@0x1002, v10 + WriteBarrier v41, v10 + v46:CShape[0x1003] = Const CShape(0x1003) + StoreField v41, :_shape_id@0x1000, v46 + v14:HeapBasicObject = RefineType v6, HeapBasicObject + v17:Fixnum[2] = Const Value(2) + PatchPoint SingleRactorMode + v49:CShape = LoadField v14, :_shape_id@0x1000 + v50:CShape[0x1003] = GuardBitEquals v49, CShape(0x1003) + StoreField v14, :@b@0x1004, v17 + WriteBarrier v14, v17 + v53:CShape[0x1005] = Const CShape(0x1005) + StoreField v14, :_shape_id@0x1000, v53 + v21:HeapBasicObject = RefineType v14, HeapBasicObject + v24:Fixnum[3] = Const Value(3) + PatchPoint SingleRactorMode + v56:CShape = LoadField v21, :_shape_id@0x1000 + v57:CShape[0x1005] = GuardBitEquals v56, CShape(0x1005) + StoreField v21, :@c@0x1006, v24 + WriteBarrier v21, v24 + v60:CShape[0x1007] = Const CShape(0x1007) + StoreField v21, :_shape_id@0x1000, v60 + v28:HeapBasicObject = RefineType v21, HeapBasicObject + v31:Fixnum[4] = Const Value(4) + PatchPoint SingleRactorMode + IncrCounter setivar_fallback_new_shape_needs_extension + SetIvar v28, :@d, v31 + CheckInterrupts + Return v31 + "); + } } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 5b97a61d80dd0d..17b156a663abba 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2627,6 +2627,7 @@ pub mod hir_build_tests { v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode SetIvar v6, :@foo, v10 + v15:HeapBasicObject = RefineType v6, HeapBasicObject CheckInterrupts Return v10 ");