ZJIT: Deduplicate successor and predecessor sets (#15263)

Fixes https://github.com/Shopify/ruby/issues/877

I didn't consider the ability to have the successor or predecessor sets having duplicates when originally crafting the Iongraph support PR, but have added this to prevent that happening in the future.

I don't think it interferes with the underlying Iongraph implementation, but it doesn't really make sense.

I think this kind of behaviour happens when there are multiple jump instructions that go to the same basic block within a given block.
This commit is contained in:
Aiden Fox Ivey 2025-11-20 14:47:01 -05:00 committed by GitHub
parent 48027256cf
commit a8f269a2c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2025-11-20 19:47:31 +00:00
Merged-By: tekknolagi <donotemailthisaddress@bernsteinbear.com>
2 changed files with 31 additions and 3 deletions

View File

@ -9,7 +9,7 @@ use crate::{
cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json
};
use std::{
cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
cell::RefCell, collections::{BTreeSet, HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
};
use crate::hir_type::{Type, types};
use crate::bitset::BitSet;
@ -5849,7 +5849,13 @@ impl<'a> ControlFlowInfo<'a> {
// Since ZJIT uses extended basic blocks, one must check all instructions
// for their ability to jump to another basic block, rather than just
// the instructions at the end of a given basic block.
let successors: Vec<BlockId> = block
//
// Use BTreeSet to avoid duplicates and maintain an ordering. Also
// `BTreeSet<BlockId>` provides conversion trivially back to an `Vec<BlockId>`.
// Ordering is important so that the expect tests that serialize the predecessors
// and successors don't fail intermittently.
// todo(aidenfoxivey): Use `BlockSet` in lieu of `BTreeSet<BlockId>`
let successors: BTreeSet<BlockId> = block
.insns
.iter()
.map(|&insn_id| uf.find_const(insn_id))
@ -5867,7 +5873,8 @@ impl<'a> ControlFlowInfo<'a> {
}
// Store successors for this block.
successor_map.insert(block_id, successors);
// Convert successors from a `BTreeSet<BlockId>` to a `Vec<BlockId>`.
successor_map.insert(block_id, successors.iter().copied().collect());
}
Self {

View File

@ -3484,6 +3484,27 @@ pub mod hir_build_tests {
assert!(cfi.is_succeeded_by(bb1, bb0));
assert!(cfi.is_succeeded_by(bb3, bb1));
}
#[test]
fn test_cfi_deduplicated_successors_and_predecessors() {
let mut function = Function::new(std::ptr::null());
let bb0 = function.entry_block;
let bb1 = function.new_block(0);
// Construct two separate jump instructions.
let v1 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
let _ = function.push_insn(bb0, Insn::IfTrue { val: v1, target: edge(bb1)});
function.push_insn(bb0, Insn::Jump(edge(bb1)));
let retval = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb1, Insn::Return { val: retval });
let cfi = ControlFlowInfo::new(&function);
assert_eq!(cfi.predecessors(bb1).collect::<Vec<_>>().len(), 1);
assert_eq!(cfi.successors(bb0).collect::<Vec<_>>().len(), 1);
}
}
/// Test dominator set computations.