From 673c37d76ac2767b13a1ab7ba74b0ea8cda17c81 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:31:32 +0100 Subject: [PATCH 1/4] [ruby/prism] Rewrite "version: nearest" to require no maintenance Currently I see myself not updating this when a new version is added. Instead, rewrite it to just work with new versions: * Try to set the version * If that doesn't succeed, check if it is lower * If it isn't lower, it must be higher We can then use `PM_OPTIONS_VERSION_LATEST` which doesn't need to change. Also added a very basic test. https://github.com/ruby/prism/commit/443e9b9959 --- lib/prism/ffi.rb | 43 ++++++++++++++++-------------------- prism/extension.c | 20 +++++++---------- test/prism/api/parse_test.rb | 4 ++++ 3 files changed, 31 insertions(+), 36 deletions(-) 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]]) From 540ea720e8fbe730d4b6afb68cefe6fb2cf7848e Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:52:44 +0100 Subject: [PATCH 2/4] Add myself as a prism maintainer --- doc/maintainers.md | 2 ++ 1 file changed, 2 insertions(+) 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 From 5571212fd363437b70772facf0c1d2281dc9a46a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 4 Feb 2026 10:42:26 -0500 Subject: [PATCH 3/4] ZJIT: Remove specialized instructions (#16034) * ZJIT: Fix codegen for GuardSuperMethodEntry We need to register the VALUE in the generated code so that it is visible to the GC. This may be the cause of a crash in the test suite that Alan found. * ZJIT: Use LoadField+GuardBitEquals instead of GuardSuperMethodEntry We have modular instructions. Use them. * ZJIT: Use LoadField instead of GetBlockHandler We have modular instructions. Use them. * ZJIT: Remove unused HIR instructions We don't use GuardSuperMethodEntry, GuardShape, or GetBlockHandler anymore. * ZJIT: Replace GuardNotFrozen with LoadField+GuardNoBitsSet * ZJIT: Replace GuardNotShared with LoadField+GuardNoBitsSet * ZJIT: Remove unused HIR instructions * ZJIT: Remove comment --- zjit/src/codegen.rs | 56 ----------------- zjit/src/cruby.rs | 3 + zjit/src/cruby_methods.rs | 11 ++-- zjit/src/hir.rs | 78 +++++++---------------- zjit/src/hir/opt_tests.rs | 129 +++++++++++++++++++++----------------- 5 files changed, 101 insertions(+), 176 deletions(-) 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..7423ef2df85da3 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), @@ -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, .. } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 8dec65fed634e6..3a3df7100d9724 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -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 @@ -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 "); } From 9167262940804951127b74aa3012c577a9b8ae09 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 4 Feb 2026 13:04:01 -0500 Subject: [PATCH 4/4] ZJIT: Upgrade self to HeapBasicObject after setinstancevariable (#16065) If self is an immediate, setinstancevariable would have raised. Therefore we can conclude that self must not be an immediate. This helps us match YJIT type upgrades (YJIT will upgrade to UnknownHeap or something similar). --- zjit/src/hir.rs | 7 +- zjit/src/hir/opt_tests.rs | 142 +++++++++++++++++++++++++++----------- zjit/src/hir/tests.rs | 1 + 3 files changed, 106 insertions(+), 44 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7423ef2df85da3..eba3edee9904a6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2884,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 }; @@ -6292,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() }; @@ -7306,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 3a3df7100d9724..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 "); } @@ -10561,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 "); } @@ -11829,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 ");