diff options
| author | Max Bernstein <max@bernsteinbear.com> | 2025-07-16 18:12:19 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-16 22:12:19 +0000 |
| commit | 15cf72dadea5cb3c96d1dca96c57308bce680a3b (patch) | |
| tree | 52c655d81d52c1a98f727b925fc957f16eb2f7de | |
| parent | 616df508c796e98a6ca112627103c2e833c51619 (diff) | |
ZJIT: Check if BOP is redefined before rewriting (#13916)
Fix https://github.com/Shopify/ruby/issues/592
| -rw-r--r-- | zjit/src/hir.rs | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ce89501afe..fa72b569b4 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1382,6 +1382,11 @@ impl Function { } fn try_rewrite_fixnum_op(&mut self, block: BlockId, orig_insn_id: InsnId, f: &dyn Fn(InsnId, InsnId) -> Insn, bop: u32, left: InsnId, right: InsnId, state: InsnId) { + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, INTEGER_REDEFINED_OP_FLAG) } { + // If the basic operation is already redefined, we cannot optimize it. + self.push_insn_id(block, orig_insn_id); + return; + } if self.arguments_likely_fixnums(left, right, state) { if bop == BOP_NEQ { // For opt_neq, the interpreter checks that both neq and eq are unchanged. @@ -1399,6 +1404,11 @@ impl Function { } fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32, state: InsnId) { + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, klass) } { + // If the basic operation is already redefined, we cannot optimize it. + self.push_insn_id(block, orig_insn_id); + return; + } let self_type = self.type_of(self_val); if let Some(obj) = self_type.ruby_object() { if obj.is_frozen() { @@ -1431,6 +1441,11 @@ impl Function { } fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId, state: InsnId) { + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) } { + // If the basic operation is already redefined, we cannot optimize it. + self.push_insn_id(block, orig_insn_id); + return; + } let self_type = self.type_of(self_val); let idx_type = self.type_of(idx_val); if self_type.is_subtype(types::ArrayExact) { @@ -2667,6 +2682,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { break; // End the block }, }; + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, ARRAY_REDEFINED_OP_FLAG) } { + // If the basic operation is already redefined, we cannot optimize it. + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }) }); + break; // End the block + } fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }, state: exit_id }); state.stack_push(fun.push_insn(block, insn)); } @@ -5591,6 +5611,25 @@ mod opt_tests { } #[test] + fn test_dont_optimize_fixnum_add_if_redefined() { + eval(" + class Integer + def +(other) + 100 + end + end + def test(a, b) = a + b + test(1,2); test(3,4) + "); + assert_optimized_method_hir("test", expect![[r#" + fn test@<compiled>:7: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + v5:BasicObject = SendWithoutBlock v1, :+, v2 + Return v5 + "#]]); + } + + #[test] fn test_optimize_send_into_fixnum_add_both_profiled() { eval(" def test(a, b) = a + b @@ -6629,6 +6668,23 @@ mod opt_tests { } #[test] + fn test_dont_optimize_hash_freeze_if_redefined() { + eval(" + class Hash + def freeze; end + end + def test = {}.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test@<compiled>:5: + bb0(v0:BasicObject): + v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:BasicObject = SendWithoutBlock v3, :freeze + Return v4 + "#]]); + } + + #[test] fn test_elide_freeze_with_refrozen_hash() { eval(" def test = {}.freeze.freeze @@ -6974,6 +7030,44 @@ mod opt_tests { } #[test] + fn test_dont_optimize_array_aref_if_redefined() { + eval(r##" + class Array + def [](index); end + end + def test = [4,5,6].freeze[10] + "##); + assert_optimized_method_hir("test", expect![[r#" + fn test@<compiled>:5: + bb0(v0:BasicObject): + v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v5:Fixnum[10] = Const Value(10) + v7:BasicObject = SendWithoutBlock v3, :[], v5 + Return v7 + "#]]); + } + + #[test] + fn test_dont_optimize_array_max_if_redefined() { + eval(r##" + class Array + def max = 10 + end + def test = [4,5,6].max + "##); + assert_optimized_method_hir("test", expect![[r#" + fn test@<compiled>:5: + bb0(v0:BasicObject): + v2:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:ArrayExact = ArrayDup v2 + PatchPoint MethodRedefined(Array@0x1008, max@0x1010) + v9:BasicObject = SendWithoutBlockDirect v4, :max (0x1018) + Return v9 + "#]]); + } + + #[test] fn test_set_type_from_constant() { eval(" MY_SET = Set.new |
