From: "cosgroveb (Brian Cosgrove) via ruby-core" Date: 2026-08-04T23:12:04+00:00 Subject: [ruby-core:126254] [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). luke-gru (Luke Gruber) wrote in #note-3: > I think this is one of those instances I *should* have used an LLM, sorry! It has me thinking that this is not a bug after all. The JIT team will still take a look at it, but this may not be the repro to your bug that you were hoping for. I should be clear that this script doesn't reproduce the self-jump itself. Calling it a "repro" is maybe a bit strong. Obviously this intermediate state happens inside the VM lock. I simply want to make sure that it's intentional that the branch is rewritten after updating only one of its' targets. ---------------------------------------- Bug #22225: YJIT regenerates a branch while a duplicate target still points to the invalidated block https://bugs.ruby-lang.org/issues/22225#change-118339 * 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/