From: "cosgroveb (Brian Cosgrove) via ruby-core" Date: 2026-08-04T23:13:19+00:00 Subject: [ruby-core:126255] [Ruby Bug#22225] YJIT regenerates a branch while a duplicate target still points to the invalidated block Issue #22225 has been updated by cosgroveb (Brian Cosgrove). alanwu (Alan Wu) wrote in #note-5: > > That can leave generated code jumping back into code YJIT just invalidated. > > This alone isn't problematic since `invalidate_block_version` patches in an exit at the invalidated block. > > > This does result in an infinite loop/self-jump in a much more elaborate repro and in my CI environment (a large test suite > 100k examples often re-defining things. > > This symptom sounds familiar to a bug we've fixed. Are you running into this issue with the latest release? Not the latest release in CI. I'm willing to backport this bug fix to the release we are using (privately) in our CI to see if it resolves our issue if you can direct me to a commit? ---------------------------------------- Bug #22225: YJIT regenerates a branch while a duplicate target still points to the invalidated block https://bugs.ruby-lang.org/issues/22225#change-118340 * Author: cosgroveb (Brian Cosgrove) * Status: Feedback * Assignee: jit * ruby -v: ruby 4.1.0dev (2026-08-03T03:11:21Z master f622e12503) +YJIT dev +PRISM [aarch64-linux] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- 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: ```patch 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: ```ruby # 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`: ```sh 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`: ```text [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. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/