Project

General

Profile

Actions

Bug #22225

closed

YJIT regenerates a branch while a duplicate target still points to the invalidated block

Bug #22225: YJIT regenerates a branch while a duplicate target still points to the invalidated block

Added by cosgroveb (Brian Cosgrove) 4 days ago. Updated 2 days ago.

Status:
Feedback
Assignee:
Target version:
-
ruby -v:
ruby 4.1.0dev (2026-08-03T03:11:21Z master f622e12503) +YJIT dev +PRISM [aarch64-linux]
[ruby-core:126227]

Description

YJIT adds one incoming-list entry for each branch target that points to a block. When both targets point to the same block, that block's incoming list contains the same BranchRef twice.

invalidate_block_version processes those entries separately. For the first entry, it redirects one target and immediately calls regenerate_branch. The other target still references
the invalidated block.

The following assertion fails immediately before that call:

diff --git a/yjit/src/core.rs b/yjit/src/core.rs
index d08cd1fb26..4555081ae2 100644
--- a/yjit/src/core.rs
+++ b/yjit/src/core.rs
@@ -4282,6 +4282,18 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
             }
         }

+        assert!(
+            branch.targets.iter().all(|target| unsafe {
+                // SAFETY: no mutation.
+                target
+                    .ref_unchecked()
+                    .as_ref()
+                    .and_then(|target| target.get_block())
+                    != Some(*blockref)
+            }),
+            "regeneration started while a branch target still referenced the invalidated block"
+        );
+
         // Rewrite the branch with the new jump target address
         let old_branch_size = branch.code_size();
         regenerate_branch(cb, branch);

This Ruby script triggers the assertion:

# duplicate_target_invalidation.rb
module DuplicateTargetInvalidation
  TARGET = 1

  def self.call(condition)
    if condition
      1
    else
      2
    end

    TARGET
  end
end

3.times do |index|
  DuplicateTargetInvalidation.call(index.even?)
end

DuplicateTargetInvalidation.send(:remove_const, :TARGET)

puts "completed"

Save the patch as assert-no-invalidated-target-before-regeneration.patch and the script as duplicate_target_invalidation.rb. From CRuby ruby 4.1.0dev:

git apply assert-no-invalidated-target-before-regeneration.patch
./autogen.sh
mkdir build
cd build
../configure --enable-yjit=dev
make -j"$(nproc)"
./ruby --yjit-call-threshold=1 ../duplicate_target_invalidation.rb

The process aborts before printing completed:

[BUG] YJIT: panicked at yjit/src/core.rs:4285:9:
regeneration started while a branch target still referenced the invalidated block

regenerate_branch runs with one target redirected to a stub and the other still pointing at the invalidated block. That can leave generated code jumping back into code YJIT just invalidated.

Actions

Also available in: PDF Atom