From ece14b61f505eea1ebefb3b8295df0fcf4d22567 Mon Sep 17 00:00:00 2001
From: Daichi Kamiyama <32436625+dak2@users.noreply.github.com>
Date: Wed, 13 May 2026 05:39:11 +0900
Subject: [PATCH 1/4] ZJIT: Drop redundant type guards via block-local HIR
canonicalize (#16828)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
`GuardType` narrows only its result id, not the original input.
A later guard on the same input therefore looks unfoldable to `fold_constants` even though the side-exit semantics already prove the narrower type.
Canonicalize rewrites later uses to the most recent `Guard*` result, making the redundant guard foldable.
For example, in this CFG-join shape:
```ruby
def test(n, cond)
if cond
a = n + 1
else
a = n + 2
end
n + a # `n` gets a redundant Fixnum guard here
end
```
This PR adds a block-local HIR `canonicalize` pass that walks each block in RPO and rewrites every operand through union-find plus a per-block `rewrite_map` keyed on the most recent `Guard*` for that value.
After canonicalization, `infer_types` can narrow merge-block parameter types and `fold_constants` can then drop the redundant guards in both shapes above.
Inspired by Cranelift's canonicalize https://cfallin.org/blog/2026/04/09/aegraph/
Fixes: https://github.com/Shopify/ruby/issues/978
## Benchmarks
Bench (arm64 linux devcontainer, ruby/ruby-bench, warmup=10 bench=20)
```
Throughput master/staged
lobsters 1.021 (+2.1%, within ±3-6% noise)
railsbench 1.012 (+1.2%, within ±3-4% noise)
--zjit-stats lobsters / railsbench
code_region_bytes -1.1% / -1.0% (redundant CFG-join guards still removed)
guard_type_count -29.4% / +30.1% (railsbench likely single-run noise) ⚠
compile_hir_time +14.6% / +13.7% (canonicalize_time: 67ms / 31ms)
invalidation_time ±0% / ±0%
```
---
zjit.rb | 1 +
zjit/src/hir.rs | 63 ++++++++++++++++++++++++++++
zjit/src/hir/opt_tests.rs | 87 +++++++++++++++++++++++++++++++++------
zjit/src/stats.rs | 1 +
4 files changed, 140 insertions(+), 12 deletions(-)
diff --git a/zjit.rb b/zjit.rb
index 89a4a15cfd5d95..480ffa15441f64 100644
--- a/zjit.rb
+++ b/zjit.rb
@@ -148,6 +148,7 @@ def stats_string
:compile_hir_time_ns,
:compile_hir_build_time_ns,
:compile_hir_strength_reduce_time_ns,
+ :compile_hir_canonicalize_time_ns,
:compile_hir_fold_constants_time_ns,
:compile_hir_clean_cfg_time_ns,
:compile_hir_eliminate_dead_code_time_ns,
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 6b2d9ee7e3c046..31fd50c16f037f 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -4144,6 +4144,9 @@ impl Function {
}
}
}
+ crate::stats::trace_compile_phase("canonicalize", ||
+ crate::stats::with_time_stat(Counter::compile_hir_canonicalize_time_ns, || self.canonicalize())
+ );
crate::stats::trace_compile_phase("infer_types", || self.infer_types());
}
@@ -4198,6 +4201,9 @@ impl Function {
}
}
}
+ crate::stats::trace_compile_phase("canonicalize", ||
+ crate::stats::with_time_stat(Counter::compile_hir_canonicalize_time_ns, || self.canonicalize())
+ );
crate::stats::trace_compile_phase("infer_types", || self.infer_types());
}
@@ -4486,6 +4492,9 @@ impl Function {
}
}
}
+ crate::stats::trace_compile_phase("canonicalize", ||
+ crate::stats::with_time_stat(Counter::compile_hir_canonicalize_time_ns, || self.canonicalize())
+ );
crate::stats::trace_compile_phase("infer_types", || self.infer_types());
}
@@ -4816,6 +4825,9 @@ impl Function {
self.push_insn_id(block, insn_id);
}
}
+ crate::stats::trace_compile_phase("canonicalize", ||
+ crate::stats::with_time_stat(Counter::compile_hir_canonicalize_time_ns, || self.canonicalize())
+ );
crate::stats::trace_compile_phase("infer_types", || self.infer_types());
}
@@ -4927,6 +4939,54 @@ impl Function {
.unwrap_or(insn_id)
}
+ /// Block-local canonicalize: rewrite each operand through union-find and a
+ /// per-block map of the most recent `Guard*` for that value. Forwards
+ /// guarded values into branch-edge args (so `infer_types` narrows merge-block
+ /// parameters and `fold_constants` drops redundant CFG-join guards) and
+ /// ordinary in-block uses.
+ ///
+ /// `Guard*` substitutions are unconditional within a block: a guard's
+ /// side-exit semantics guarantee the substituted value type holds for every
+ /// downstream use in the same block.
+ ///
+ /// `RefineType` is intentionally skipped: its narrowing is only valid on one
+ /// branch arm, which would require dropping refine-derived rewrites at each
+ /// `IfTrue`/`IfFalse`. Cross-arm refine forwarding is left for a follow-up
+ /// dominator-scoped pass.
+ ///
+ /// Inspired by Cranelift's aegraph canonicalize step
+ /// ().
+ fn canonicalize(&mut self) {
+ let mut rewrite_map: HashMap = HashMap::new();
+ for block in self.rpo() {
+ rewrite_map.clear();
+ for i in 0..self.blocks[block.0].insns.len() {
+ let insn_id = self.blocks[block.0].insns[i];
+ let canonical_id = self.union_find.borrow().find_const(insn_id);
+
+ let union_find = &self.union_find;
+ self.insns[canonical_id.0].for_each_operand_mut(|operand| {
+ let canon = union_find.borrow().find_const(*operand);
+ *operand = rewrite_map.get(&canon).copied().unwrap_or(canon);
+ });
+
+ // For the binary guards only `left` is registered because their infer_type is
+ // type_of(left).
+ match &self.insns[canonical_id.0] {
+ Insn::GuardType { val: src, .. }
+ | Insn::GuardBitEquals { val: src, .. }
+ | Insn::GuardAnyBitSet { val: src, .. }
+ | Insn::GuardNoBitsSet { val: src, .. }
+ | Insn::GuardGreaterEq { left: src, .. }
+ | Insn::GuardLess { left: src, .. } => {
+ rewrite_map.insert(*src, canonical_id);
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+
/// Use type information left by `infer_types` to fold away operations that can be evaluated at compile-time.
///
/// It can fold fixnum math, truthiness tests, and branches with constant conditionals.
@@ -5315,6 +5375,9 @@ impl Function {
changed = true;
}
if changed {
+ crate::stats::trace_compile_phase("canonicalize", ||
+ crate::stats::with_time_stat(Counter::compile_hir_canonicalize_time_ns, || self.canonicalize())
+ );
crate::stats::trace_compile_phase("infer_types", || self.infer_types());
}
}
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 41fcfb16986ed4..61b85e3a76dc45 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -1129,7 +1129,7 @@ mod hir_opt_tests {
PatchPoint NoSingletonClass(CustomEq@0x1008)
PatchPoint MethodRedefined(CustomEq@0x1008, !=@0x1010, cme:0x1018)
v30:ObjectSubclass[class_exact:CustomEq] = GuardType v10, ObjectSubclass[class_exact:CustomEq]
- v31:BoolExact = CCallWithFrame v30, :BasicObject#!=@0x1040, v10
+ v31:BoolExact = CCallWithFrame v30, :BasicObject#!=@0x1040, v30
v21:NilClass = Const Value(nil)
CheckInterrupts
Return v21
@@ -1752,7 +1752,7 @@ mod hir_opt_tests {
v28:Fixnum[30] = Const Value(30)
v30:Fixnum[40] = Const Value(40)
v32:Fixnum[50] = Const Value(50)
- v34:BasicObject = Send v6, :target, v24, v26, v28, v30, v32 # SendFallbackReason: Argument count does not match parameter count
+ v34:BasicObject = Send v44, :target, v24, v26, v28, v30, v32 # SendFallbackReason: Argument count does not match parameter count
v37:ArrayExact = NewArray v45, v48, v34
CheckInterrupts
Return v37
@@ -4046,7 +4046,7 @@ mod hir_opt_tests {
v33:Fixnum[40] = Const Value(40)
v35:Fixnum[50] = Const Value(50)
v37:Fixnum[60] = Const Value(60)
- v39:BasicObject = Send v6, :target, v27, v29, v31, v33, v35, v37 # SendFallbackReason: Too many arguments for LIR
+ v39:BasicObject = Send v48, :target, v27, v29, v31, v33, v35, v37 # SendFallbackReason: Too many arguments for LIR
v41:ArrayExact = NewArray v49, v52, v39
CheckInterrupts
Return v41
@@ -4651,7 +4651,7 @@ mod hir_opt_tests {
v49:SetExact = GuardType v17, SetExact
v50:BasicObject = CCallVariadic v49, :Set#initialize@0x1068
CheckInterrupts
- Return v17
+ Return v49
");
}
@@ -5683,7 +5683,7 @@ mod hir_opt_tests {
WriteBarrier v28, v10
v33:CShape[0x1003] = Const CShape(0x1003)
StoreField v28, :_shape_id@0x1000, v33
- v14:HeapBasicObject = RefineType v6, HeapBasicObject
+ v14:HeapBasicObject = RefineType v28, HeapBasicObject
v17:Fixnum[2] = Const Value(2)
PatchPoint SingleRactorMode
StoreField v14, :@bar@0x1004, v17
@@ -6359,7 +6359,7 @@ mod hir_opt_tests {
PatchPoint NoSingletonClass(Array@0x1010)
PatchPoint MethodRedefined(Array@0x1010, to_s@0x1018, cme:0x1020)
v33:BasicObject = CCallWithFrame v28, :Array#to_s@0x1048
- v20:String = AnyToString v10, str: v33
+ v20:String = AnyToString v28, str: v33
v22:StringExact = StringConcat v14, v20
CheckInterrupts
Return v22
@@ -10089,7 +10089,7 @@ mod hir_opt_tests {
v33:CInt64 = AdjustBounds v32, v31
v34:CInt64[0] = Const CInt64(0)
v35:CInt64 = GuardGreaterEq v33, v34
- v36:Fixnum = StringGetbyte v28, v33
+ v36:Fixnum = StringGetbyte v28, v35
CheckInterrupts
Return v36
");
@@ -14643,7 +14643,7 @@ mod hir_opt_tests {
SetLocal :other_block, l0, EP@3, v40
v27:CPtr = GetEP 0
v28:BasicObject = LoadField v27, :other_block@0x1051
- v30:BasicObject = InvokeSuper v11, 0x1058, v28 # SendFallbackReason: super: complex argument passing to `super` call
+ v30:BasicObject = InvokeSuper v39, 0x1058, v28 # SendFallbackReason: super: complex argument passing to `super` call
CheckInterrupts
Return v30
");
@@ -15257,7 +15257,7 @@ mod hir_opt_tests {
v78:BasicObject = LoadField v75, :iter_method@0x1058
v79:BasicObject = LoadField v75, :kwsplat@0x1059
SetLocal :sep, l0, EP@5, v119
- Jump bb8(v58, v76, v119, v78, v79)
+ Jump bb8(v118, v76, v119, v78, v79)
bb8(v83:BasicObject, v84:BasicObject, v85:BasicObject, v86:BasicObject, v87:BasicObject):
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1060, CONST)
@@ -15360,7 +15360,7 @@ mod hir_opt_tests {
WriteBarrier v35, v13
v40:CShape[0x1003] = Const CShape(0x1003)
StoreField v35, :_shape_id@0x1000, v40
- v20:HeapBasicObject = RefineType v8, HeapBasicObject
+ v20:HeapBasicObject = RefineType v35, HeapBasicObject
PatchPoint NoEPEscape(initialize)
PatchPoint SingleRactorMode
WriteBarrier v20, v13
@@ -15408,7 +15408,7 @@ mod hir_opt_tests {
WriteBarrier v49, v16
v54:CShape[0x1003] = Const CShape(0x1003)
StoreField v49, :_shape_id@0x1000, v54
- v23:HeapBasicObject = RefineType v10, HeapBasicObject
+ v23:HeapBasicObject = RefineType v49, HeapBasicObject
v26:Fixnum[5] = Const Value(5)
PatchPoint NoEPEscape(initialize)
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
@@ -15456,7 +15456,7 @@ mod hir_opt_tests {
WriteBarrier v43, v13
v48:CShape[0x1003] = Const CShape(0x1003)
StoreField v43, :_shape_id@0x1000, v48
- v20:HeapBasicObject = RefineType v8, HeapBasicObject
+ v20:HeapBasicObject = RefineType v43, HeapBasicObject
PatchPoint NoEPEscape(initialize)
PatchPoint SingleRactorMode
WriteBarrier v20, v13
@@ -15722,6 +15722,69 @@ mod hir_opt_tests {
");
}
+ #[test]
+ fn test_dedup_guard_type_across_cfg_join() {
+ eval("
+ def test(n, cond)
+ if cond
+ a = n + 1
+ else
+ a = n + 2
+ end
+ n + a
+ end
+ test(1, true); test(1, false)
+ ");
+ let hir = hir_string("test");
+ let guard_count = hir.matches("GuardType").count();
+ assert_eq!(
+ guard_count, 2,
+ "expected 2 GuardType instructions after cross-block dedup, found {guard_count}\n\nHIR:\n{hir}"
+ );
+ }
+
+ #[test]
+ fn test_forward_guard_through_conditional_branch() {
+ eval("
+ def test(n, a, b)
+ if a
+ if b
+ n + 1
+ else
+ n + 2
+ end
+ else
+ n + 3
+ end
+ end
+ test(1, true, true); test(1, true, false); test(1, false, false)
+ ");
+ let hir = hir_string("test");
+ let guard_count = hir.matches("GuardType").count();
+ assert!(
+ guard_count <= 3,
+ "expected at most 3 GuardType instructions (one per leaf branch) after forwarding through conditional branches, found {guard_count}\n\nHIR:\n{hir}"
+ );
+ }
+
+ #[test]
+ fn test_no_forward_when_no_guard_in_branches() {
+ let src = "
+ def test(n, cond)
+ a = if cond then 1 else 2 end
+ n + a
+ end
+ test(1, true); test(1, false)
+ ";
+ eval(src);
+ let hir = hir_string("test");
+ let guard_count = hir.matches("GuardType").count();
+ assert_eq!(
+ guard_count, 1,
+ "expected 1 GuardType (merge block only), found {guard_count}\n\nHIR:\n{hir}"
+ );
+ }
+
#[test]
fn test_infer_types_across_non_maximal_basic_blocks() {
// Previous worklist-based type inference only worked for maximal SSA. This is a regression
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index 522b74c48afcba..587dde06603bab 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -171,6 +171,7 @@ make_counters! {
compile_hir_build_time_ns,
compile_hir_strength_reduce_time_ns,
compile_hir_optimize_load_store_time_ns,
+ compile_hir_canonicalize_time_ns,
compile_hir_fold_constants_time_ns,
compile_hir_clean_cfg_time_ns,
compile_hir_remove_redundant_patch_points_time_ns,
From be557259b71f70df6950f3140c25f251cde0c447 Mon Sep 17 00:00:00 2001
From: Aaron Patterson
Date: Tue, 12 May 2026 16:16:13 -0700
Subject: [PATCH 2/4] ZJIT: Convert ZJIT HIR Extended basic blocks to
traditional basic blocks (#16888)
* Swap conditionals so HIR -> LIR is easier
We're going to give HIR regular BBs which means each block can end with
up to two jump instructions. IfTrue/IfFalse will always be followed by
a Jump, so when we lower to LIR this change will make the lowering more
natural. We'll only have to emit a jz/jnz for the IfTrue/IfFalse cases,
and the Jump instructions will be naturally handled
* Convert HIR EBB to regular BB
This commit converts HIR EBB to regular BB. It ensures that all basic
blocks in HIR end with 1 or 2 jump instructions, and that there are no
jump instructions that appear mid-block.
* implement condbranch
* remove num_successors (we do not need it)
* oops!
* oops!
* Do not use clone
* fix insta
* remove comment
* check for terminator in rpo loop
* peek at next instruction for new blocks in the jit entry
* add comment
---
zjit/src/codegen.rs | 89 +-----
zjit/src/hir.rs | 491 ++++++++++++++++-------------
zjit/src/hir/opt_tests.rs | 529 +++++++++++++++++--------------
zjit/src/hir/tests.rs | 645 +++++++++++++++++++-------------------
4 files changed, 909 insertions(+), 845 deletions(-)
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index d89d3900fc02b4..77ffae0cb2cecb 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -172,7 +172,7 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, ec: EcPtr, jit_exc
// We assert only `jit_exception: false` cases until we support exception handlers.
if ZJITState::assert_compiles_enabled() && !jit_exception {
let iseq_location = iseq_get_location(iseq, 0);
- panic!("Failed to compile: {iseq_location}");
+ panic!("Failed to compile: {iseq_location}: {err:?}");
}
// For --zjit-stats, generate an entry that just increments exit_compilation_failure and exits
@@ -394,7 +394,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
}
// Compile each basic block
- for (rpo_idx, &block_id) in reverse_post_order.iter().enumerate() {
+ for &block_id in reverse_post_order.iter() {
// Skip the entries superblock — it's an internal CFG artifact
if block_id == function.entries_block { continue; }
// Set the current block to the LIR block that corresponds to this
@@ -437,62 +437,27 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
for (insn_idx, &insn_id) in block.insns().enumerate() {
let insn = function.find(insn_id);
- // IfTrue and IfFalse should never be terminators
- if matches!(insn, Insn::IfTrue {..} | Insn::IfFalse {..}) {
- assert!(!insn.is_terminator(), "IfTrue/IfFalse should not be terminators");
- }
-
match insn {
- Insn::IfFalse { val, target } => {
-
+ Insn::CondBranch { val, if_true, if_false } => {
let val_opnd = jit.get_opnd(val);
+ let true_target = hir_to_lir[if_true.target.0].unwrap();
+ let false_target = hir_to_lir[if_false.target.0].unwrap();
- let lir_target = hir_to_lir[target.target.0].unwrap();
-
- let fall_through_target = asm.new_block(block_id, false, rpo_idx);
-
- let branch_edge = lir::BranchEdge {
- target: lir_target,
- args: target.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
+ let true_branch = lir::BranchEdge {
+ target: true_target,
+ args: if_true.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
};
- let fall_through_edge = lir::BranchEdge {
- target: fall_through_target,
- args: vec![]
+ let false_branch = lir::BranchEdge {
+ target: false_target,
+ args: if_false.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
};
- gen_if_false(&mut asm, val_opnd, branch_edge, fall_through_edge);
- assert!(asm.current_block().insns.last().unwrap().is_terminator());
-
- asm.set_current_block(fall_through_target);
-
- let label = jit.get_label(&mut asm, fall_through_target, block_id);
- asm.write_label(label);
- },
- Insn::IfTrue { val, target } => {
- let val_opnd = jit.get_opnd(val);
-
- let lir_target = hir_to_lir[target.target.0].unwrap();
-
- let fall_through_target = asm.new_block(block_id, false, rpo_idx);
+ asm.test(val_opnd, val_opnd);
+ asm.push_insn(lir::Insn::Jnz(Target::Block(true_branch)));
+ asm.jmp(Target::Block(false_branch));
- let branch_edge = lir::BranchEdge {
- target: lir_target,
- args: target.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
- };
-
- let fall_through_edge = lir::BranchEdge {
- target: fall_through_target,
- args: vec![]
- };
-
- gen_if_true(&mut asm, val_opnd, branch_edge, fall_through_edge);
assert!(asm.current_block().insns.last().unwrap().is_terminator());
-
- asm.set_current_block(fall_through_target);
-
- let label = jit.get_label(&mut asm, fall_through_target, block_id);
- asm.write_label(label);
}
Insn::Jump(target) => {
let lir_target = hir_to_lir[target.target.0].unwrap();
@@ -500,7 +465,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
target: lir_target,
args: target.args.iter().map(|insn_id| jit.get_opnd(*insn_id)).collect()
};
- gen_jump(&mut asm, branch_edge);
+ asm.jmp(Target::Block(branch_edge));
assert!(asm.current_block().insns.last().unwrap().is_terminator());
// Jump should always be the last instruction in an HIR block
@@ -779,7 +744,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
&Insn::ArrayMax { ref elements, state } => gen_array_max(jit, asm, opnds!(elements), &function.frame_state(state)),
&Insn::ArrayMin { ref elements, state } => gen_array_min(jit, asm, opnds!(elements), &function.frame_state(state)),
&Insn::Throw { state, .. } => return Err(state),
- &Insn::IfFalse { .. } | Insn::IfTrue { .. }
+ &Insn::CondBranch { .. }
| &Insn::Jump { .. } | Insn::Entries { .. } => unreachable!(),
};
@@ -1467,28 +1432,6 @@ fn gen_param(asm: &mut Assembler, _idx: usize) -> lir::Opnd {
vreg
}
-/// Compile a jump to a basic block
-fn gen_jump(asm: &mut Assembler, branch: lir::BranchEdge) {
- // Jump to the basic block
- asm.jmp(Target::Block(branch));
-}
-
-/// Compile a conditional branch to a basic block
-fn gen_if_true(asm: &mut Assembler, val: lir::Opnd, branch: lir::BranchEdge, fall_through: lir::BranchEdge) {
- // If val is zero, move on to the next instruction.
- asm.test(val, val);
- asm.push_insn(lir::Insn::Jz(Target::Block(fall_through)));
- asm.jmp(Target::Block(branch));
-}
-
-/// Compile a conditional branch to a basic block
-fn gen_if_false(asm: &mut Assembler, val: lir::Opnd, branch: lir::BranchEdge, fall_through: lir::BranchEdge) {
- // If val is not zero, move on to the next instruction.
- asm.test(val, val);
- asm.push_insn(lir::Insn::Jnz(Target::Block(fall_through)));
- asm.jmp(Target::Block(branch));
-}
-
/// Compile a dynamic dispatch with block
fn gen_send(
jit: &mut JITState,
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 31fd50c16f037f..f3f6f73e3f6b38 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -11,7 +11,7 @@ use crate::{
state,
};
use std::{
- 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,
+ cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter,
sync::atomic::Ordering,
};
use crate::hir_type::{Type, types};
@@ -952,9 +952,8 @@ pub enum Insn {
/// Unconditional jump
Jump(BranchEdge),
- /// Conditional branch instructions
- IfTrue { val: InsnId, target: BranchEdge },
- IfFalse { val: InsnId, target: BranchEdge },
+ /// Conditional branch
+ CondBranch { val: InsnId, if_true: BranchEdge, if_false: BranchEdge },
/// Call a C function without pushing a frame
/// `name` and `owner` are for printing purposes only
@@ -1328,10 +1327,10 @@ macro_rules! for_each_operand_impl {
Insn::Jump(BranchEdge { args, .. }) => {
$visit_many!(args);
}
- Insn::IfTrue { val, target: BranchEdge { args, .. } }
- | Insn::IfFalse { val, target: BranchEdge { args, .. } } => {
+ Insn::CondBranch { val, if_true: BranchEdge { args: true_args, .. }, if_false: BranchEdge { args: false_args, .. } } => {
$visit_one!(val);
- $visit_many!(args);
+ $visit_many!(true_args);
+ $visit_many!(false_args);
}
Insn::ArrayDup { val, state }
| Insn::Throw { val, state, .. }
@@ -1468,7 +1467,7 @@ impl Insn {
match self {
Insn::Jump(_)
| Insn::Entries { .. }
- | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
+ | Insn::CondBranch { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. }
@@ -1482,7 +1481,7 @@ impl Insn {
/// Return true if the instruction ends a basic block and false otherwise.
pub fn is_terminator(&self) -> bool {
match self {
- Insn::Jump(_) | Insn::Entries { .. } | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => true,
+ Insn::Unreachable | Insn::CondBranch { .. } | Insn::Jump(_) | Insn::Entries { .. } | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => true,
_ => false,
}
}
@@ -1490,7 +1489,7 @@ impl Insn {
/// Return true if the instruction is a jump (has successor blocks in the CFG).
pub fn is_jump(&self) -> bool {
match self {
- Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Jump(_) | Insn::Entries { .. } => true,
+ Insn::CondBranch { .. } | Insn::Jump(_) | Insn::Entries { .. } => true,
_ => false,
}
}
@@ -1616,8 +1615,7 @@ impl Insn {
Insn::GetBlockParam { .. } => effects::Any,
Insn::Snapshot { .. } => effects::Empty,
Insn::Jump(_) => effects::Any,
- Insn::IfTrue { .. } => effects::Any,
- Insn::IfFalse { .. } => effects::Any,
+ Insn::CondBranch { .. } => effects::Any,
Insn::CCall { elidable, .. } => {
if *elidable {
Effect::write(abstract_heaps::Allocator)
@@ -1949,8 +1947,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::UnboxFixnum { val } => write!(f, "UnboxFixnum {val}"),
Insn::FixnumAref { recv, index } => write!(f, "FixnumAref {recv}, {index}"),
Insn::Jump(target) => { write!(f, "Jump {target}") }
- Insn::IfTrue { val, target } => { write!(f, "IfTrue {val}, {target}") }
- Insn::IfFalse { val, target } => { write!(f, "IfFalse {val}, {target}") }
+ Insn::CondBranch { val, if_true, if_false } => { write!(f, "CondBranch {val}, {if_true}, {if_false}") },
Insn::SendDirect { recv, cd, iseq, args, block, .. } => {
let blockiseq = block.map(|bh| match bh { BlockHandler::BlockIseq(iseq) => iseq, BlockHandler::BlockArg => unreachable!() });
write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(&blockiseq), ruby_call_method_name(*cd), self.ptr_map.map_ptr(iseq))?;
@@ -2690,6 +2687,23 @@ impl Function {
self.blocks.pop();
}
+ fn successors(&self, block: BlockId) -> Vec {
+ let insns = &self.blocks[block.0].insns;
+ let last = self.find(*insns.last().unwrap());
+ match last {
+ Insn::CondBranch { if_true, if_false, .. } => vec![if_true.target, if_false.target],
+ Insn::Jump(edge) => vec![edge.target],
+ Insn::Entries { targets } => targets,
+ Insn::Unreachable | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => vec![],
+ // Blocks that don't end with terminators are technically errors,
+ // every block in the CFG should end with a terminator. But we
+ // want to be able to iterate over poorly constructed CFG when
+ // debugging, so we'll return an empty vec. The validation
+ // routines check for terminators, so we should catch CFG errors there.
+ _ => vec![]
+ }
+ }
+
/// Return a reference to the Block at the given index.
pub fn block(&self, block_id: BlockId) -> &Block {
&self.blocks[block_id.0]
@@ -2829,7 +2843,7 @@ impl Function {
Insn::Param => unimplemented!("params should not be present in block.insns"),
Insn::LoadArg { val_type, .. } => *val_type,
Insn::SetGlobal { .. } | Insn::Jump(_) | Insn::Entries { .. } | Insn::EntryPoint { .. }
- | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. }
+ | Insn::CondBranch { .. } | Insn::Return { .. } | Insn::Throw { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. }
| Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. }
@@ -3012,14 +3026,22 @@ impl Function {
// Assign `new_type` to `insn` if it differs from the recorded type.
// Returns `true` if a write actually happened, `false` if the type
- // was already equal.
- let set_type = |this: &mut Function, insn: InsnId, new_type: Type| -> bool {
- if this.type_of(insn).bit_equal(new_type) {
- return false;
- }
- this.insn_types[insn.0] = new_type;
- true
- };
+ // Macro instead of closure so the borrow checker sees individual field
+ // accesses rather than an `&mut self` borrow that conflicts with
+ // `&self.insns` held by an outer match.
+ macro_rules! set_type {
+ ($insn:expr, $new_type:expr) => {{
+ let insn = $insn;
+ let new_type = $new_type;
+ let old_type = self.insn_types[self.union_find.borrow_mut().find(insn).0];
+ if old_type.bit_equal(new_type) {
+ false
+ } else {
+ self.insn_types[insn.0] = new_type;
+ true
+ }
+ }};
+ }
let mut reachable = BlockSet::with_capacity(self.blocks.len());
reachable.insert(self.entries_block);
@@ -3035,29 +3057,25 @@ impl Function {
// Instructions without output, including branch instructions, can't be targets
// of make_equal_to, so we don't need find() here.
let insn_type = match &self.insns[insn_id.0] {
- &Insn::IfTrue { val, target: BranchEdge { target, ref args } } => {
- assert!(!self.type_of(val).bit_equal(types::Empty));
- if self.type_of(val).could_be(Type::from_cbool(true)) {
- reachable.insert(target);
+ Insn::CondBranch { val, if_true, if_false } => {
+ assert!(!self.type_of(*val).bit_equal(types::Empty));
+ if self.type_of(*val).could_be(Type::from_cbool(true)) {
+ reachable.insert(if_true.target);
// Snapshot arg types before any param updates so phi-style
// updates happen in parallel (the args of a self-loop may name
// params of `target` itself).
- let arg_types: Vec = args.iter().map(|a| self.type_of(*a)).collect();
+ let arg_types: Vec = if_true.args.iter().map(|a| self.type_of(*a)).collect();
for (idx, arg_type) in arg_types.into_iter().enumerate() {
- let param = self.blocks[target.0].params[idx];
- changed |= set_type(self, param, self.type_of(param).union(arg_type));
+ let param = self.blocks[if_true.target.0].params[idx];
+ changed |= set_type!(param, self.type_of(param).union(arg_type));
}
}
- continue;
- }
- &Insn::IfFalse { val, target: BranchEdge { target, ref args } } => {
- assert!(!self.type_of(val).bit_equal(types::Empty));
- if self.type_of(val).could_be(Type::from_cbool(false)) {
- reachable.insert(target);
- let arg_types: Vec = args.iter().map(|a| self.type_of(*a)).collect();
+ if self.type_of(*val).could_be(Type::from_cbool(false)) {
+ reachable.insert(if_false.target);
+ let arg_types: Vec = if_false.args.iter().map(|a| self.type_of(*a)).collect();
for (idx, arg_type) in arg_types.into_iter().enumerate() {
- let param = self.blocks[target.0].params[idx];
- changed |= set_type(self, param, self.type_of(param).union(arg_type));
+ let param = self.blocks[if_false.target.0].params[idx];
+ changed |= set_type!(param, self.type_of(param).union(arg_type));
}
}
continue;
@@ -3067,7 +3085,7 @@ impl Function {
let arg_types: Vec = args.iter().map(|a| self.type_of(*a)).collect();
for (idx, arg_type) in arg_types.into_iter().enumerate() {
let param = self.blocks[target.0].params[idx];
- changed |= set_type(self, param, self.type_of(param).union(arg_type));
+ changed |= set_type!(param, self.type_of(param).union(arg_type));
}
continue;
}
@@ -3080,7 +3098,7 @@ impl Function {
insn if insn.has_output() => self.infer_type(insn_id),
_ => continue,
};
- changed |= set_type(self, insn_id, insn_type);
+ changed |= set_type!(insn_id, insn_type);
}
}
if !changed {
@@ -5247,16 +5265,12 @@ impl Function {
Insn::Test { val } if self.type_of(val).is_known_truthy() => {
self.new_insn(Insn::Const { val: Const::CBool(true) })
}
- Insn::IfTrue { val, target } if self.is_a(val, Type::from_cbool(true)) => {
- self.new_insn(Insn::Jump(target))
+ Insn::CondBranch { val, if_true, .. } if self.is_a(val, Type::from_cbool(true)) => {
+ self.new_insn(Insn::Jump(if_true))
}
- Insn::IfFalse { val, target } if self.is_a(val, Type::from_cbool(false)) => {
- self.new_insn(Insn::Jump(target))
+ Insn::CondBranch { val, if_false, .. } if self.is_a(val, Type::from_cbool(false)) => {
+ self.new_insn(Insn::Jump(if_false))
}
- // If we know that the branch condition is never going to cause a branch,
- // completely drop the branch from the block.
- Insn::IfTrue { val, .. } if self.is_a(val, Type::from_cbool(false)) => continue,
- Insn::IfFalse { val, .. } if self.is_a(val, Type::from_cbool(true)) => continue,
_ => insn_id,
};
// If we're adding a new instruction, mark the two equivalent in the union-find and
@@ -5341,22 +5355,8 @@ impl Function {
// * blocks pointed to by blocks that get absorbed retain the same number of in-edges
let mut num_in_edges = vec![0; self.blocks.len()];
for block in self.rpo() {
- for &insn in &self.blocks[block.0].insns {
- // Instructions without output, including branch instructions, can't be targets of
- // make_equal_to, so we don't need find() here.
- match &self.insns[insn.0] {
- Insn::IfTrue { target: BranchEdge { target, .. }, .. }
- | Insn::IfFalse { target: BranchEdge { target, .. }, .. }
- | Insn::Jump(BranchEdge { target, .. }) => {
- num_in_edges[target.0] += 1;
- }
- Insn::Entries { targets } => {
- for target in targets {
- num_in_edges[target.0] += 1;
- }
- }
- _ => {}
- }
+ for target in self.successors(block) {
+ num_in_edges[target.0] += 1;
}
}
let mut changed = false;
@@ -5468,22 +5468,8 @@ impl Function {
}
if !seen.insert(block) { continue; }
stack.push((block, Action::VisitSelf));
- for insn_id in &self.blocks[block.0].insns {
- // Instructions without output, including branch instructions, can't be targets of
- // make_equal_to, so we don't need find() here.
- match &self.insns[insn_id.0] {
- Insn::IfTrue { target, .. } | Insn::IfFalse { target, .. } | Insn::Jump(target) => {
- stack.push((target.target, Action::VisitEdges));
- }
- Insn::Entries { targets } => {
- for target in targets {
- stack.push((*target, Action::VisitEdges));
- }
- }
- _ => {
- debug_assert!(!self.find(*insn_id).is_jump(), "Instruction {:?} should not be in union-find; it has no output", insn_id);
- }
- }
+ for target in self.successors(block) {
+ stack.push((target, Action::VisitEdges));
}
}
result
@@ -5731,35 +5717,45 @@ impl Function {
/// 2. Every terminator must be in the last position.
/// 3. Every block must have a terminator.
fn validate_block_terminators_and_jumps(&self) -> Result<(), ValidationError> {
+ let check_edge = |block_id: BlockId, edge: &BranchEdge| -> Result<(), ValidationError> {
+ let target_len = self.blocks[edge.target.0].params.len();
+ let args_len = edge.args.len();
+ if target_len != args_len {
+ return Err(ValidationError::MismatchedBlockArity(block_id, target_len, args_len));
+ }
+ Ok(())
+ };
+
for block_id in self.rpo() {
- let mut block_has_terminator = false;
let insns = &self.blocks[block_id.0].insns;
for (idx, insn_id) in insns.iter().enumerate() {
let insn = self.find(*insn_id);
+ // Validate arity for all branch edges
match &insn {
- Insn::Jump(BranchEdge{target, args})
- | Insn::IfTrue { val: _, target: BranchEdge{target, args} }
- | Insn::IfFalse { val: _, target: BranchEdge{target, args}} => {
- let target_block = &self.blocks[target.0];
- let target_len = target_block.params.len();
- let args_len = args.len();
- if target_len != args_len {
- return Err(ValidationError::MismatchedBlockArity(block_id, target_len, args_len))
- }
+ Insn::Jump(edge) => {
+ check_edge(block_id, edge)?;
+ }
+ Insn::CondBranch { if_true, if_false, .. } => {
+ check_edge(block_id, if_true)?;
+ check_edge(block_id, if_false)?;
}
_ => {}
}
- if !insn.is_terminator() {
- continue;
+
+ if insn.is_terminator() {
+ // Blow up if we have a terminator that isn't at the end
+ // of the block.
+ if idx != insns.len() - 1 {
+ return Err(ValidationError::TerminatorNotAtEnd(block_id, *insn_id, idx))
+ }
}
- block_has_terminator = true;
- if idx != insns.len() - 1 {
- return Err(ValidationError::TerminatorNotAtEnd(block_id, *insn_id, idx));
+ // If the last instruction isn't a terminator, return an error
+ if idx == insns.len() - 1 {
+ if !insn.is_terminator() {
+ return Err(ValidationError::BlockHasNoTerminator(block_id));
+ }
}
}
- if !block_has_terminator {
- return Err(ValidationError::BlockHasNoTerminator(block_id));
- }
}
Ok(())
}
@@ -5792,25 +5788,25 @@ impl Function {
}
for &insn_id in &self.blocks[block.0].insns {
let insn_id = self.union_find.borrow().find_const(insn_id);
- match self.find(insn_id) {
- Insn::Jump(target) | Insn::IfTrue { target, .. } | Insn::IfFalse { target, .. } => {
- let Some(block_in) = assigned_in[target.target.0].as_mut() else {
- return Err(ValidationError::JumpTargetNotInRPO(target.target));
- };
- // jump target's block_in was modified, we need to queue the block for processing.
- if block_in.intersect_with(&assigned) {
- worklist.push_back(target.target);
- }
+ let insn = self.find(insn_id);
+ let mut propagate = |target: BlockId| -> Result<(), ValidationError> {
+ let Some(block_in) = assigned_in[target.0].as_mut() else {
+ return Err(ValidationError::JumpTargetNotInRPO(target));
+ };
+ if block_in.intersect_with(&assigned) {
+ worklist.push_back(target);
+ }
+ Ok(())
+ };
+ match insn {
+ Insn::Jump(edge) => propagate(edge.target)?,
+ Insn::CondBranch { if_true, if_false, .. } => {
+ propagate(if_true.target)?;
+ propagate(if_false.target)?;
}
Insn::Entries { ref targets } => {
for &target in targets {
- let Some(block_in) = assigned_in[target.0].as_mut() else {
- return Err(ValidationError::JumpTargetNotInRPO(target));
- };
- // jump target's block_in was modified, we need to queue the block for processing.
- if block_in.intersect_with(&assigned) {
- worklist.push_back(target);
- }
+ propagate(target)?;
}
}
insn if insn.has_output() => {
@@ -6068,8 +6064,7 @@ impl Function {
}
}
Insn::BoxBool { val }
- | Insn::IfTrue { val, .. }
- | Insn::IfFalse { val, .. } => {
+ | Insn::CondBranch { val, .. } => {
self.assert_subtype(insn_id, val, types::CBool)
}
Insn::BoxFixnum { val, .. } => self.assert_subtype(insn_id, val, types::CInt64),
@@ -7125,10 +7120,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let nil_false = fun.push_insn(block, Insn::RefineType { val, new_type: nil_false_type });
let mut iffalse_state = state.clone();
iffalse_state.replace(val, nil_false);
- let _branch_id = fun.push_insn(block, Insn::IfFalse {
+ let fall_through = fun.new_block(insn_idx);
+
+ fun.push_insn(block, Insn::CondBranch {
val: test_id,
- target: BranchEdge { target, args: iffalse_state.as_args(self_param) }
+ if_true: BranchEdge { target: fall_through, args: vec![] },
+ if_false: BranchEdge { target, args: iffalse_state.as_args(self_param) }
});
+
+ block = fall_through;
+
let not_nil_false_type = types::Truthy;
let not_nil_false = fun.push_insn(block, Insn::RefineType { val, new_type: not_nil_false_type });
state.replace(val, not_nil_false);
@@ -7147,10 +7148,17 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let not_nil_false = fun.push_insn(block, Insn::RefineType { val, new_type: not_nil_false_type });
let mut iftrue_state = state.clone();
iftrue_state.replace(val, not_nil_false);
- let _branch_id = fun.push_insn(block, Insn::IfTrue {
+
+ let fall_through = fun.new_block(insn_idx);
+
+ fun.push_insn(block, Insn::CondBranch {
val: test_id,
- target: BranchEdge { target, args: iftrue_state.as_args(self_param) }
+ if_true: BranchEdge { target, args: iftrue_state.as_args(self_param) },
+ if_false: BranchEdge { target: fall_through, args: vec![] }
});
+
+ block = fall_through;
+
let nil_false_type = types::Falsy;
let nil_false = fun.push_insn(block, Insn::RefineType { val, new_type: nil_false_type });
state.replace(val, nil_false);
@@ -7168,10 +7176,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let nil = fun.push_insn(block, Insn::Const { val: Const::Value(Qnil) });
let mut iftrue_state = state.clone();
iftrue_state.replace(val, nil);
- let _branch_id = fun.push_insn(block, Insn::IfTrue {
+
+ let fall_through = fun.new_block(insn_idx);
+
+ fun.push_insn(block, Insn::CondBranch {
val: test_id,
- target: BranchEdge { target, args: iftrue_state.as_args(self_param) }
+ if_true: BranchEdge { target, args: iftrue_state.as_args(self_param) },
+ if_false: BranchEdge { target: fall_through, args: vec![] }
});
+
+ block = fall_through;
let new_type = types::NotNil;
let not_nil = fun.push_insn(block, Insn::RefineType { val, new_type });
state.replace(val, not_nil);
@@ -7197,10 +7211,13 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
// Skip CheckInterrupts since the #new call will do it very soon anyway.
let target_idx = insn_idx_at_offset(insn_idx, dst);
let target = insn_idx_to_block[&target_idx];
- let _branch_id = fun.push_insn(block, Insn::IfFalse {
+ let fall_through = fun.new_block(insn_idx);
+ fun.push_insn(block, Insn::CondBranch {
val: test_id,
- target: BranchEdge { target, args: state.as_args(self_param) }
+ if_true: BranchEdge { target: fall_through, args: vec![] },
+ if_false: BranchEdge { target, args: state.as_args(self_param) }
});
+ block = fall_through;
queue.push_back((state.clone(), target, target_idx, local_inval));
// Move on to the fast path
@@ -7358,8 +7375,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let flags = fun.load_ep_flags(block, ep);
let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { flags });
- fun.push_insn(block, Insn::IfTrue { val: is_modified, target: BranchEdge { target: modified_block, args: vec![] }});
- fun.push_insn(block, Insn::Jump(BranchEdge { target: unmodified_block, args: vec![] }));
+ fun.push_insn(block, Insn::CondBranch {
+ val: is_modified,
+ if_true: BranchEdge { target: modified_block, args: vec![] },
+ if_false: BranchEdge { target: unmodified_block, args: vec![] }
+ });
// Push modified block: load the block local via EP.
let modified_val = fun.get_local_from_ep(modified_block, ep, ep_offset, level, types::BasicObject);
@@ -7452,20 +7472,29 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
.map(|&kind| (kind, fun.new_block(branch_insn_idx)))
.collect::>();
+ let mut current_block = unmodified_block;
+
for &(kind, profiled_block) in &profiled_blocks {
match kind {
ProfiledBlockHandlerFamily::Nil => {
- let none_handler = fun.push_insn(unmodified_block, Insn::Const {
+ let none_handler = fun.push_insn(current_block, Insn::Const {
val: Const::CInt64(VM_BLOCK_HANDLER_NONE.into()),
});
- let is_none = fun.push_insn(unmodified_block, Insn::IsBitEqual {
+ let is_none = fun.push_insn(current_block, Insn::IsBitEqual {
left: block_handler,
right: none_handler,
});
- fun.push_insn(unmodified_block, Insn::IfTrue {
+
+ let next_block = fun.new_block(branch_insn_idx);
+
+ fun.push_insn(current_block, Insn::CondBranch {
val: is_none,
- target: BranchEdge { target: profiled_block, args: vec![] },
+ if_true: BranchEdge { target: profiled_block, args: vec![] },
+ if_false: BranchEdge { target: next_block, args: vec![] },
});
+
+ current_block = next_block;
+
let val = fun.push_insn(profiled_block, Insn::Const { val: Const::Value(Qnil) });
let mut args = vec![val];
if let Some(local) = original_local { args.push(local); }
@@ -7478,19 +7507,23 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
// VM_BH_ISEQ_BLOCK_P(): block_handler & 0x03 == 0x01
// VM_BH_IFUNC_P(): block_handler & 0x03 == 0x03
// So to check for either of those cases we can use: val & 0x1 == 0x1
- let tag_mask = fun.push_insn(unmodified_block, Insn::Const { val: Const::CInt64(0x1) });
- let tag_bits = fun.push_insn(unmodified_block, Insn::IntAnd {
+ let tag_mask = fun.push_insn(current_block, Insn::Const { val: Const::CInt64(0x1) });
+ let tag_bits = fun.push_insn(current_block, Insn::IntAnd {
left: block_handler,
right: tag_mask,
});
- let is_iseq_or_ifunc = fun.push_insn(unmodified_block, Insn::IsBitEqual {
+ let is_iseq_or_ifunc = fun.push_insn(current_block, Insn::IsBitEqual {
left: tag_bits,
right: tag_mask,
});
- fun.push_insn(unmodified_block, Insn::IfTrue {
+ let next_block = fun.new_block(branch_insn_idx);
+ fun.push_insn(current_block, Insn::CondBranch {
val: is_iseq_or_ifunc,
- target: BranchEdge { target: profiled_block, args: vec![] },
+ if_true: BranchEdge { target: profiled_block, args: vec![] },
+ if_false: BranchEdge { target: next_block, args: vec![] },
});
+ current_block = next_block;
+
// TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing
let val = fun.push_insn(profiled_block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) });
let mut args = vec![val];
@@ -7500,7 +7533,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
}
}
- fun.push_insn(unmodified_block, Insn::SideExit { state: exit_id, reason: SideExitReason::BlockParamProxyProfileNotCovered, recompile: None });
+ fun.push_insn(current_block, Insn::SideExit { state: exit_id, reason: SideExitReason::BlockParamProxyProfileNotCovered, recompile: None });
}
}
@@ -7528,14 +7561,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let flags = fun.load_ep_flags(block, ep);
let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { flags });
- fun.push_insn(block, Insn::IfTrue {
+ fun.push_insn(block, Insn::CondBranch {
val: is_modified,
- target: BranchEdge { target: modified_block, args: vec![] },
+ if_true: BranchEdge { target: modified_block, args: vec![] },
+ if_false: BranchEdge { target: unmodified_block, args: vec![] }
});
- fun.push_insn(block, Insn::Jump(BranchEdge {
- target: unmodified_block,
- args: vec![],
- }));
// Push modified block: read Proc from EP.
let modified_val = fun.get_local_from_ep(modified_block, ep, ep_offset, level, types::BasicObject);
@@ -7792,7 +7822,13 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let iftrue_block =
new_branch_block(&mut fun, cd, argc as usize, opcode, expected, branch_insn_idx, &exit_state, locals_count, stack_count, join_block);
let target = BranchEdge { target: iftrue_block, args: entry_args.clone() };
- fun.push_insn(block, Insn::IfTrue { val: has_type, target });
+ let fall_through = fun.new_block(insn_idx);
+ fun.push_insn(block, Insn::CondBranch {
+ val: has_type,
+ if_true: target,
+ if_false: BranchEdge { target: fall_through, args: vec![] }
+ });
+ block = fall_through;
}
// Continue compilation from the join block at the next instruction.
// Make a copy of the current state without the args (pop the receiver
@@ -8059,7 +8095,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let join_block = fun.new_block(insn_idx);
let join_param = fun.push_insn(join_block, Insn::Param);
let ifunc_block = fun.new_block(insn_idx);
- fun.push_insn(block, Insn::IfTrue { val: is_ifunc_match, target: BranchEdge { target: ifunc_block, args: vec![] } });
+ let fall_through = fun.new_block(insn_idx);
+
+ fun.push_insn(block, Insn::CondBranch {
+ val: is_ifunc_match,
+ if_true: BranchEdge { target: ifunc_block, args: vec![] },
+ if_false: BranchEdge { target: fall_through, args: vec![] },
+ });
+
+ block = fall_through;
+
let ifunc_result = fun.push_insn(ifunc_block, Insn::InvokeBlockIfunc {
cd,
block_handler,
@@ -8135,7 +8180,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
let has_shape_and_type = fun.push_insn(block, Insn::IsBitEqual { left: masked, right: expected_rbasic_flags });
let iftrue_block = fun.new_block(insn_idx);
let target = BranchEdge { target: iftrue_block, args: vec![] };
- fun.push_insn(block, Insn::IfTrue { val: has_shape_and_type, target });
+ let fall_through = fun.new_block(insn_idx);
+
+ fun.push_insn(block, Insn::CondBranch { val: has_shape_and_type,
+ if_true: target,
+ if_false: BranchEdge { target: fall_through, args: vec![] }
+ });
+
+ block = fall_through;
let result = fun.load_ivar(iftrue_block, self_param, profiled_type, id, exit_id);
fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![result] }));
}
@@ -8356,13 +8408,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result {
/// Compile an entry_block for the interpreter
fn compile_entry_block(fun: &mut Function, jit_entry_insns: &[u32], insn_idx_to_block: &HashMap) {
- let entry_block = fun.entry_block;
+ let mut entry_block = fun.entry_block;
let (self_param, entry_state) = compile_entry_state(fun);
let mut pc: Option = None;
let &all_opts_passed_insn_idx = jit_entry_insns.last().unwrap();
// Check-and-jump for each missing optional PC
- for &jit_entry_insn in jit_entry_insns.iter() {
+ let mut iter = jit_entry_insns.iter().peekable();
+ while let Some(&jit_entry_insn) = iter.next() {
if jit_entry_insn == all_opts_passed_insn_idx {
continue;
}
@@ -8376,10 +8429,16 @@ fn compile_entry_block(fun: &mut Function, jit_entry_insns: &[u32], insn_idx_to_
val: Const::CPtr(unsafe { rb_iseq_pc_at_idx(fun.iseq, jit_entry_insn) } as *const u8),
});
let test_id = fun.push_insn(entry_block, Insn::IsBitEqual { left: pc, right: expected_pc });
- fun.push_insn(entry_block, Insn::IfTrue {
+
+ let next_insn_idx = **iter.peek().expect("last entry is skipped so there is always a next");
+ let fall_through = fun.new_block(next_insn_idx);
+
+ fun.push_insn(entry_block, Insn::CondBranch {
val: test_id,
- target: BranchEdge { target: target_block, args: entry_state.as_args(self_param) },
+ if_true: BranchEdge { target: target_block, args: entry_state.as_args(self_param) },
+ if_false: BranchEdge { target: fall_through, args: vec![] }
});
+ entry_block = fall_through;
}
// Terminate the block with a jump to the block with all optionals passed
@@ -8632,34 +8691,10 @@ impl<'a> ControlFlowInfo<'a> {
pub fn new(function: &'a Function) -> Self {
let mut successor_map: HashMap> = HashMap::new();
let mut predecessor_map: HashMap> = HashMap::new();
- let uf = function.union_find.borrow();
for block_id in function.rpo() {
- let block = &function.blocks[block_id.0];
-
- // 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.
- //
- // Use BTreeSet to avoid duplicates and maintain an ordering. Also
- // `BTreeSet` provides conversion trivially back to an `Vec`.
- // 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`
- let mut successors: BTreeSet = BTreeSet::new();
- for &insn_id in &block.insns {
- let insn_id = uf.find_const(insn_id);
- match &function.insns[insn_id.0] {
- Insn::Entries { targets } => {
- successors.extend(targets);
- }
- insn => {
- if let Some(target) = Self::extract_jump_target(insn) {
- successors.insert(target);
- }
- }
- }
- }
+ let mut successors = function.successors(block_id);
+ successors.dedup();
// Update predecessors for successor blocks.
for &succ_id in &successors {
@@ -8670,8 +8705,7 @@ impl<'a> ControlFlowInfo<'a> {
}
// Store successors for this block.
- // Convert successors from a `BTreeSet` to a `Vec`.
- successor_map.insert(block_id, successors.iter().copied().collect());
+ successor_map.insert(block_id, successors);
}
Self {
@@ -8696,16 +8730,6 @@ impl<'a> ControlFlowInfo<'a> {
pub fn successors(&self, block: BlockId) -> impl Iterator- {
self.successor_map.get(&block).into_iter().flatten().copied()
}
-
- /// Helper function to extract the target of a jump instruction.
- fn extract_jump_target(insn: &Insn) -> Option {
- match insn {
- Insn::Jump(target)
- | Insn::IfTrue { target, .. }
- | Insn::IfFalse { target, .. } => Some(target.target),
- _ => None,
- }
- }
}
pub struct LoopInfo<'a> {
@@ -8856,8 +8880,8 @@ mod rpo_tests {
let entry = function.entry_block;
let exit = function.new_block(0);
function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
- let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::Return { val });
+ let val = function.push_insn(exit, Insn::Const { val: Const::Value(Qnil) });
+ function.push_insn(exit, Insn::Return { val });
function.seal_entries();
assert_eq!(function.rpo(), vec![entries, entry, exit]);
}
@@ -8871,10 +8895,13 @@ mod rpo_tests {
let exit = function.new_block(0);
function.push_insn(side, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::IfTrue { val, target: BranchEdge { target: side, args: vec![] } });
- function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
- let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::Return { val });
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: side, args: vec![] },
+ if_false: BranchEdge { target: exit, args: vec![] }
+ });
+ let val = function.push_insn(exit, Insn::Const { val: Const::Value(Qnil) });
+ function.push_insn(exit, Insn::Return { val });
function.seal_entries();
assert_eq!(function.rpo(), vec![entries, entry, side, exit]);
}
@@ -8888,10 +8915,13 @@ mod rpo_tests {
let exit = function.new_block(0);
function.push_insn(side, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![] } });
- function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
- let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::Return { val });
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: exit, args: vec![] },
+ if_false: BranchEdge { target: side, args: vec![] },
+ });
+ let val = function.push_insn(exit, Insn::Const { val: Const::Value(Qnil) });
+ function.push_insn(exit, Insn::Return { val });
function.seal_entries();
assert_eq!(function.rpo(), vec![entries, entry, side, exit]);
}
@@ -8937,6 +8967,7 @@ mod validation_tests {
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
let insn_id = function.push_insn(entry, Insn::Return { val });
function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
+ function.push_insn(entry, Insn::Unreachable);
function.seal_entries();
assert_matches_err(function.validate(), ValidationError::TerminatorNotAtEnd(entry, insn_id, 1));
}
@@ -8947,7 +8978,14 @@ mod validation_tests {
let entry = function.entry_block;
let side = function.new_block(0);
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::IfTrue { val, target: BranchEdge { target: side, args: vec![val, val, val] } });
+ let fall_through = function.new_block(1);
+ function.push_insn(fall_through, Insn::Unreachable);
+ function.push_insn(side, Insn::Unreachable);
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: side, args: vec![val, val, val] },
+ if_false: BranchEdge { target: fall_through, args: vec![] }
+ });
function.seal_entries();
assert_matches_err(function.validate(), ValidationError::MismatchedBlockArity(entry, 0, 3));
}
@@ -8958,7 +8996,14 @@ mod validation_tests {
let entry = function.entry_block;
let side = function.new_block(0);
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
- function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![val, val, val] } });
+ let fall_through = function.new_block(1);
+ function.push_insn(fall_through, Insn::Unreachable);
+ function.push_insn(side, Insn::Unreachable);
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: fall_through, args: vec![] },
+ if_false: BranchEdge { target: side, args: vec![val, val, val] },
+ });
function.seal_entries();
assert_matches_err(function.validate(), ValidationError::MismatchedBlockArity(entry, 0, 3));
}
@@ -8970,6 +9015,7 @@ mod validation_tests {
let side = function.new_block(0);
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
function.push_insn(entry, Insn::Jump ( BranchEdge { target: side, args: vec![val, val, val] } ));
+ function.push_insn(side, Insn::Unreachable);
function.seal_entries();
assert_matches_err(function.validate(), ValidationError::MismatchedBlockArity(entry, 0, 3));
}
@@ -8981,6 +9027,7 @@ mod validation_tests {
// Create an instruction without making it belong to anything.
let dangling = function.new_insn(Insn::Const{val: Const::CBool(true)});
let val = function.push_insn(function.entry_block, Insn::ArrayDup { val: dangling, state: InsnId(0usize) });
+ function.push_insn(function.entry_block, Insn::Unreachable);
function.seal_entries();
assert_matches_err(function.validate_definite_assignment(), ValidationError::OperandNotDefined(entry, val, dangling));
}
@@ -8993,6 +9040,7 @@ mod validation_tests {
// Ret is a non-output instruction.
let ret = function.push_insn(function.entry_block, Insn::Return { val: const_ });
let val = function.push_insn(function.entry_block, Insn::ArrayDup { val: ret, state: InsnId(0usize) });
+ function.push_insn(function.entry_block, Insn::Unreachable);
function.seal_entries();
assert_matches_err(function.validate_definite_assignment(), ValidationError::OperandNotDefined(entry, val, ret));
}
@@ -9007,9 +9055,15 @@ mod validation_tests {
let v0 = function.push_insn(side, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(3)) });
function.push_insn(side, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
let val1 = function.push_insn(entry, Insn::Const { val: Const::CBool(false) });
- function.push_insn(entry, Insn::IfFalse { val: val1, target: BranchEdge { target: side, args: vec![] } });
- function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
+ function.push_insn(entry, Insn::CondBranch {
+ val: val1,
+ if_true: BranchEdge { target: exit, args: vec![] },
+ if_false: BranchEdge { target: side, args: vec![] },
+ });
let val2 = function.push_insn(exit, Insn::ArrayDup { val: v0, state: v0 });
+ let const_ = function.push_insn(exit, Insn::Const{val: Const::CBool(true)});
+ function.push_insn(exit, Insn::Return { val: const_ });
+
function.seal_entries();
crate::cruby::with_rubyvm(|| {
function.infer_types();
@@ -9027,9 +9081,14 @@ mod validation_tests {
let v0 = function.push_insn(entry, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(3)) });
function.push_insn(side, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
let val = function.push_insn(entry, Insn::Const { val: Const::CBool(false) });
- function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![] } });
- function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![] }));
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: exit, args: vec![] },
+ if_false: BranchEdge { target: side, args: vec![] }
+ });
let _val = function.push_insn(exit, Insn::ArrayDup { val: v0, state: v0 });
+ let const_ = function.push_insn(exit, Insn::Const{val: Const::CBool(true)});
+ function.push_insn(exit, Insn::Return { val: const_ });
function.seal_entries();
crate::cruby::with_rubyvm(|| {
function.infer_types();
@@ -9096,6 +9155,7 @@ mod infer_tests {
fn test_const() {
let mut function = Function::new(std::ptr::null());
let val = function.push_insn(function.entry_block, Insn::Const { val: Const::Value(Qnil) });
+ function.push_insn(function.entry_block, Insn::Unreachable);
assert_bit_equal(function.infer_type(val), types::NilClass);
}
@@ -9105,6 +9165,7 @@ mod infer_tests {
let mut function = Function::new(std::ptr::null());
let nil = function.push_insn(function.entry_block, Insn::Const { val: Const::Value(Qnil) });
let val = function.push_insn(function.entry_block, Insn::Test { val: nil });
+ function.push_insn(function.entry_block, Insn::Unreachable);
function.seal_entries();
function.infer_types();
assert_bit_equal(function.type_of(val), Type::from_cbool(false));
@@ -9117,6 +9178,7 @@ mod infer_tests {
let mut function = Function::new(std::ptr::null());
let false_ = function.push_insn(function.entry_block, Insn::Const { val: Const::Value(Qfalse) });
let val = function.push_insn(function.entry_block, Insn::Test { val: false_ });
+ function.push_insn(function.entry_block, Insn::Unreachable);
function.seal_entries();
function.infer_types();
assert_bit_equal(function.type_of(val), Type::from_cbool(false));
@@ -9129,6 +9191,7 @@ mod infer_tests {
let mut function = Function::new(std::ptr::null());
let true_ = function.push_insn(function.entry_block, Insn::Const { val: Const::Value(Qtrue) });
let val = function.push_insn(function.entry_block, Insn::Test { val: true_ });
+ function.push_insn(function.entry_block, Insn::Unreachable);
function.seal_entries();
function.infer_types();
assert_bit_equal(function.type_of(val), Type::from_cbool(true));
@@ -9161,15 +9224,19 @@ mod infer_tests {
let v0 = function.push_insn(side, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(3)) });
function.push_insn(side, Insn::Jump(BranchEdge { target: exit, args: vec![v0] }));
let val = function.push_insn(entry, Insn::Const { val: Const::CBool(false) });
- function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![] } });
let v1 = function.push_insn(entry, Insn::Const { val: Const::Value(VALUE::fixnum_from_usize(4)) });
- function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![v1] }));
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: exit, args: vec![v1] },
+ if_false: BranchEdge { target: side, args: vec![] },
+ });
let param = function.push_insn(exit, Insn::Param);
+ function.push_insn(exit, Insn::Unreachable);
function.seal_entries();
crate::cruby::with_rubyvm(|| {
function.infer_types();
});
- assert_bit_equal(function.type_of(param), types::Fixnum);
+ assert_bit_equal(function.type_of(param), Type::fixnum(3));
}
#[test]
@@ -9228,14 +9295,18 @@ mod infer_tests {
let v0 = function.push_insn(side, Insn::Const { val: Const::Value(Qtrue) });
function.push_insn(side, Insn::Jump(BranchEdge { target: exit, args: vec![v0] }));
let val = function.push_insn(entry, Insn::Const { val: Const::CBool(false) });
- function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![] } });
let v1 = function.push_insn(entry, Insn::Const { val: Const::Value(Qfalse) });
- function.push_insn(entry, Insn::Jump(BranchEdge { target: exit, args: vec![v1] }));
+ function.push_insn(entry, Insn::CondBranch {
+ val,
+ if_true: BranchEdge { target: exit, args: vec![v1] },
+ if_false: BranchEdge { target: side, args: vec![] },
+ });
let param = function.push_insn(exit, Insn::Param);
+ function.push_insn(exit, Insn::Unreachable);
function.seal_entries();
crate::cruby::with_rubyvm(|| {
function.infer_types();
- assert_bit_equal(function.type_of(param), types::TrueClass.union(types::FalseClass));
+ assert_bit_equal(function.type_of(param), types::TrueClass);
});
}
}
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 61b85e3a76dc45..0976ae659e6260 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -4617,9 +4617,9 @@ mod hir_opt_tests {
v15:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Array@0x1008, new@0x1009, cme:0x1010)
PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010)
- v56:BasicObject = CCallVariadic v46, :Array.new@0x1040, v15
+ v52:BasicObject = CCallVariadic v46, :Array.new@0x1040, v15
CheckInterrupts
- Return v56
+ Return v52
");
}
@@ -4889,14 +4889,15 @@ mod hir_opt_tests {
v17:CPtr = GetEP 0
v18:CUInt64 = LoadField v17, :_ep_flags@0x1001
v19:CBool = IsBlockParamModified v18
- IfTrue v19, bb4()
- v24:CInt64 = LoadField v17, :_env_data_index_specval@0x1002
- v25:CInt64 = GuardAnyBitSet v24, CUInt64(1)
- v26:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v26, v10)
+ CondBranch v19, bb4(), bb5()
bb4():
- v22:BasicObject = LoadField v17, :block@0x1010
- Jump bb6(v22, v22)
+ v21:BasicObject = LoadField v17, :block@0x1002
+ Jump bb6(v21, v21)
+ bb5():
+ v23:CInt64 = LoadField v17, :_env_data_index_specval@0x1003
+ v24:CInt64 = GuardAnyBitSet v23, CUInt64(1)
+ v25:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v25, v10)
bb6(v15:BasicObject, v16:BasicObject):
SideExit NoProfileSend recompile
");
@@ -4930,25 +4931,27 @@ mod hir_opt_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- v25:BasicObject = GetBlockParam :block, l0, EP@4
- Jump bb6(v25)
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1002
- Jump bb6(v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22)
+ bb5():
+ v24:BasicObject = GetBlockParam :block, l0, EP@4
+ Jump bb6(v24)
bb6(v17:BasicObject):
- v33:CPtr = GetEP 0
- v34:CUInt64 = LoadField v33, :_ep_flags@0x1001
- v35:CBool = IsBlockParamModified v34
- IfTrue v35, bb7()
- v40:CInt64 = LoadField v33, :_env_data_index_specval@0x1003
- v41:CInt64 = GuardAnyBitSet v40, CUInt64(1)
- v42:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb9(v42, v17)
+ v32:CPtr = GetEP 0
+ v33:CUInt64 = LoadField v32, :_ep_flags@0x1001
+ v34:CBool = IsBlockParamModified v33
+ CondBranch v34, bb7(), bb8()
bb7():
- v38:BasicObject = LoadField v33, :block@0x1002
- Jump bb9(v38, v38)
- bb9(v31:BasicObject, v32:BasicObject):
+ v36:BasicObject = LoadField v32, :block@0x1002
+ Jump bb9(v36, v36)
+ bb8():
+ v38:CInt64 = LoadField v32, :_env_data_index_specval@0x1003
+ v39:CInt64 = GuardAnyBitSet v38, CUInt64(1)
+ v40:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb9(v40, v17)
+ bb9(v30:BasicObject, v31:BasicObject):
SideExit NoProfileSend recompile
");
}
@@ -4979,25 +4982,27 @@ mod hir_opt_tests {
v14:CPtr = GetEP 1
v15:CUInt64 = LoadField v14, :_ep_flags@0x1000
v16:CBool = IsBlockParamModified v15
- IfTrue v16, bb4()
- v21:BasicObject = GetBlockParam :block, l1, EP@3
- Jump bb6(v21)
+ CondBranch v16, bb4(), bb5()
bb4():
- v19:BasicObject = LoadField v14, :block@0x1001
- Jump bb6(v19)
+ v18:BasicObject = LoadField v14, :block@0x1001
+ Jump bb6(v18)
+ bb5():
+ v20:BasicObject = GetBlockParam :block, l1, EP@3
+ Jump bb6(v20)
bb6(v13:BasicObject):
- v28:CPtr = GetEP 1
- v29:CUInt64 = LoadField v28, :_ep_flags@0x1000
- v30:CBool = IsBlockParamModified v29
- IfTrue v30, bb7()
- v35:CInt64 = LoadField v28, :_env_data_index_specval@0x1002
- v36:CInt64 = GuardAnyBitSet v35, CUInt64(1)
- v37:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb9(v37)
+ v27:CPtr = GetEP 1
+ v28:CUInt64 = LoadField v27, :_ep_flags@0x1000
+ v29:CBool = IsBlockParamModified v28
+ CondBranch v29, bb7(), bb8()
bb7():
- v33:BasicObject = LoadField v28, :block@0x1001
- Jump bb9(v33)
- bb9(v27:BasicObject):
+ v31:BasicObject = LoadField v27, :block@0x1001
+ Jump bb9(v31)
+ bb8():
+ v33:CInt64 = LoadField v27, :_env_data_index_specval@0x1002
+ v34:CInt64 = GuardAnyBitSet v33, CUInt64(1)
+ v35:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb9(v35)
+ bb9(v26:BasicObject):
SideExit NoProfileSend recompile
");
}
@@ -5032,29 +5037,32 @@ mod hir_opt_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- v25:CInt64 = LoadField v18, :_env_data_index_specval@0x1002
- v26:CInt64[1] = Const CInt64(1)
- v27:CInt64 = IntAnd v25, v26
- v28:CBool = IsBitEqual v27, v26
- IfTrue v28, bb7()
- v32:CInt64[0] = Const CInt64(0)
- v33:CBool = IsBitEqual v25, v32
- IfTrue v33, bb8()
- SideExit BlockParamProxyProfileNotCovered
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1003
- Jump bb6(v23, v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22, v22)
+ bb5():
+ v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1003
+ v25:CInt64[1] = Const CInt64(1)
+ v26:CInt64 = IntAnd v24, v25
+ v27:CBool = IsBitEqual v26, v25
+ CondBranch v27, bb7(), bb9()
bb7():
- v30:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v30, v10)
+ v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v29, v10)
+ bb9():
+ v31:CInt64[0] = Const CInt64(0)
+ v32:CBool = IsBitEqual v24, v31
+ CondBranch v32, bb8(), bb10()
bb8():
- v35:NilClass = Const Value(nil)
- Jump bb6(v35, v10)
+ v34:NilClass = Const Value(nil)
+ Jump bb6(v34, v10)
bb6(v16:BasicObject, v17:BasicObject):
- v39:BasicObject = Send v14, &block, :then, v16 # SendFallbackReason: Complex argument passing
+ v38:BasicObject = Send v14, &block, :then, v16 # SendFallbackReason: Complex argument passing
CheckInterrupts
- Return v39
+ Return v38
+ bb10():
+ SideExit BlockParamProxyProfileNotCovered
");
}
@@ -5080,12 +5088,13 @@ mod hir_opt_tests {
v15:CPtr = GetEP 0
v16:CUInt64 = LoadField v15, :_ep_flags@0x1001
v17:CBool = IsBlockParamModified v16
- IfTrue v17, bb4()
- v22:BasicObject = GetBlockParam :block, l0, EP@3
- Jump bb6(v22)
+ CondBranch v17, bb4(), bb5()
bb4():
- v20:BasicObject = LoadField v15, :block@0x1002
- Jump bb6(v20)
+ v19:BasicObject = LoadField v15, :block@0x1002
+ Jump bb6(v19)
+ bb5():
+ v21:BasicObject = GetBlockParam :block, l0, EP@3
+ Jump bb6(v21)
bb6(v14:BasicObject):
CheckInterrupts
Return v14
@@ -5115,12 +5124,13 @@ mod hir_opt_tests {
v11:CPtr = GetEP 1
v12:CUInt64 = LoadField v11, :_ep_flags@0x1000
v13:CBool = IsBlockParamModified v12
- IfTrue v13, bb4()
- v18:BasicObject = GetBlockParam :block, l1, EP@3
- Jump bb6(v18)
+ CondBranch v13, bb4(), bb5()
bb4():
- v16:BasicObject = LoadField v11, :block@0x1001
- Jump bb6(v16)
+ v15:BasicObject = LoadField v11, :block@0x1001
+ Jump bb6(v15)
+ bb5():
+ v17:BasicObject = GetBlockParam :block, l1, EP@3
+ Jump bb6(v17)
bb6(v10:BasicObject):
CheckInterrupts
Return v10
@@ -7259,7 +7269,8 @@ mod hir_opt_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb4(v9, v17)
+ CondBranch v16, bb6(), bb4(v9, v17)
+ bb6():
v19:Truthy = RefineType v10, Truthy
v21:FalseClass = Const Value(false)
CheckInterrupts
@@ -7590,21 +7601,23 @@ mod hir_opt_tests {
v16 = RefineType v15, CUInt64
v17:CInt64 = IntAnd v12, v14
v18:CBool = IsBitEqual v17, v16
- IfTrue v18, bb5()
+ CondBranch v18, bb5(), bb6()
+ bb5():
+ v20:BasicObject = LoadField v11, :@foo@0x1002
+ Jump bb4(v20)
+ bb6():
v22:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
- v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
+ v23:CPtr[CPtr(0x1003)] = Const CPtr(0x1003)
v24 = RefineType v23, CUInt64
v25:CInt64 = IntAnd v12, v22
v26:CBool = IsBitEqual v25, v24
- IfTrue v26, bb6()
- v30:BasicObject = GetIvar v11, :@foo
- Jump bb4(v30)
- bb5():
- v20:BasicObject = LoadField v11, :@foo@0x1003
- Jump bb4(v20)
- bb6():
+ CondBranch v26, bb7(), bb8()
+ bb7():
v28:BasicObject = LoadField v11, :@foo@0x1004
Jump bb4(v28)
+ bb8():
+ v30:BasicObject = GetIvar v11, :@foo
+ Jump bb4(v30)
bb4(v13:BasicObject):
CheckInterrupts
Return v13
@@ -7947,22 +7960,24 @@ mod hir_opt_tests {
v16 = RefineType v15, CUInt64
v17:CInt64 = IntAnd v12, v14
v18:CBool = IsBitEqual v17, v16
- IfTrue v18, bb5()
+ CondBranch v18, bb5(), bb6()
+ bb5():
+ v20:BasicObject = LoadField v11, :@foo@0x1002
+ Jump bb4(v20)
+ bb6():
v22:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
- v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
+ v23:CPtr[CPtr(0x1003)] = Const CPtr(0x1003)
v24 = RefineType v23, CUInt64
v25:CInt64 = IntAnd v12, v22
v26:CBool = IsBitEqual v25, v24
- IfTrue v26, bb6()
- v31:BasicObject = GetIvar v11, :@foo
- Jump bb4(v31)
- bb5():
- v20:BasicObject = LoadField v11, :@foo@0x1003
- Jump bb4(v20)
- bb6():
+ CondBranch v26, bb7(), bb8()
+ bb7():
v28:CPtr = LoadField v11, :_as_heap@0x1004
v29:BasicObject = LoadField v28, :@foo@0x1000
Jump bb4(v29)
+ bb8():
+ v31:BasicObject = GetIvar v11, :@foo
+ Jump bb4(v31)
bb4(v13:BasicObject):
v34:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
@@ -8024,25 +8039,27 @@ mod hir_opt_tests {
v16 = RefineType v15, CUInt64
v17:CInt64 = IntAnd v12, v14
v18:CBool = IsBitEqual v17, v16
- IfTrue v18, bb5()
+ CondBranch v18, bb5(), bb6()
+ bb5():
+ v20:CPtr = LoadField v11, :_as_heap@0x1002
+ v21:BasicObject = LoadField v20, :@foo@0x1000
+ Jump bb4(v21)
+ bb6():
v23:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
- v24:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
+ v24:CPtr[CPtr(0x1003)] = Const CPtr(0x1003)
v25 = RefineType v24, CUInt64
v26:CInt64 = IntAnd v12, v23
v27:CBool = IsBitEqual v26, v25
- IfTrue v27, bb6()
- v44:CShape = LoadField v11, :_shape_id@0x1003
- v45:CShape[0x1004] = GuardBitEquals v44, CShape(0x1004)
- v46:CPtr = LoadField v11, :_as_heap@0x1005
+ CondBranch v27, bb7(), bb8()
+ bb7():
+ v29:BasicObject = LoadField v11, :@foo@0x1004
+ Jump bb4(v29)
+ bb8():
+ v44:CShape = LoadField v11, :_shape_id@0x1005
+ v45:CShape[0x1006] = GuardBitEquals v44, CShape(0x1006)
+ v46:CPtr = LoadField v11, :_as_heap@0x1002
v47:BasicObject = LoadField v46, :@foo@0x1000
Jump bb4(v47)
- bb5():
- v20:CPtr = LoadField v11, :_as_heap@0x1005
- v21:BasicObject = LoadField v20, :@foo@0x1000
- Jump bb4(v21)
- bb6():
- v29:BasicObject = LoadField v11, :@foo@0x1006
- Jump bb4(v29)
bb4(v13:BasicObject):
v34:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
@@ -8096,21 +8113,23 @@ mod hir_opt_tests {
v16 = RefineType v15, CUInt64
v17:CInt64 = IntAnd v12, v14
v18:CBool = IsBitEqual v17, v16
- IfTrue v18, bb5()
+ CondBranch v18, bb5(), bb6()
+ bb5():
+ v20:BasicObject = LoadField v11, :@foo@0x1002
+ Jump bb4(v20)
+ bb6():
v22:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
- v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
+ v23:CPtr[CPtr(0x1003)] = Const CPtr(0x1003)
v24 = RefineType v23, CUInt64
v25:CInt64 = IntAnd v12, v22
v26:CBool = IsBitEqual v25, v24
- IfTrue v26, bb6()
+ CondBranch v26, bb7(), bb8()
+ bb7():
+ v28:BasicObject = LoadField v11, :@foo@0x1002
+ Jump bb4(v28)
+ bb8():
v30:BasicObject = GetIvar v11, :@foo
Jump bb4(v30)
- bb5():
- v20:BasicObject = LoadField v11, :@foo@0x1003
- Jump bb4(v20)
- bb6():
- v28:BasicObject = LoadField v11, :@foo@0x1003
- Jump bb4(v28)
bb4(v13:BasicObject):
v33:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018)
@@ -8162,24 +8181,26 @@ mod hir_opt_tests {
v16 = RefineType v15, CUInt64
v17:CInt64 = IntAnd v12, v14
v18:CBool = IsBitEqual v17, v16
- IfTrue v18, bb5()
+ CondBranch v18, bb5(), bb6()
+ bb5():
+ v20:RubyValue = LoadField v11, :_fields_obj@0x1002
+ v21:BasicObject = LoadField v20, :@a@0x1002
+ Jump bb4(v21)
+ bb6():
v23:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
- v24:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
+ v24:CPtr[CPtr(0x1003)] = Const CPtr(0x1003)
v25 = RefineType v24, CUInt64
v26:CInt64 = IntAnd v12, v23
v27:CBool = IsBitEqual v26, v25
- IfTrue v27, bb6()
- v33:BasicObject = GetIvar v11, :@a
- Jump bb4(v33)
- bb5():
- v20:RubyValue = LoadField v11, :_fields_obj@0x1003
- v21:BasicObject = LoadField v20, :@a@0x1003
- Jump bb4(v21)
- bb6():
+ CondBranch v27, bb7(), bb8()
+ bb7():
PatchPoint RootBoxOnly
v30:RubyValue = LoadField v11, :_fields_obj@0x1004
- v31:BasicObject = LoadField v30, :@a@0x1003
+ v31:BasicObject = LoadField v30, :@a@0x1002
Jump bb4(v31)
+ bb8():
+ v33:BasicObject = GetIvar v11, :@a
+ Jump bb4(v33)
bb4(v13:BasicObject):
CheckInterrupts
Return v13
@@ -8227,15 +8248,16 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, ObjectSubclass[class_exact:C]
- IfTrue v15, bb5(v9, v10, v10)
- v24:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v24)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
v20:ObjectSubclass[class_exact:C] = RefineType v18, ObjectSubclass[class_exact:C]
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
v37:BasicObject = GetIvar v20, :@foo
Jump bb4(v16, v17, v37)
+ bb6():
+ v24:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v24)
bb4(v26:BasicObject, v27:BasicObject, v28:BasicObject):
CheckInterrupts
Return v28
@@ -8376,18 +8398,19 @@ mod hir_opt_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- v25:CInt64 = LoadField v18, :_env_data_index_specval@0x1002
- v26:CInt64 = GuardAnyBitSet v25, CUInt64(1)
- v27:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v27, v10)
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1010
- Jump bb6(v23, v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22, v22)
+ bb5():
+ v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1003
+ v25:CInt64 = GuardAnyBitSet v24, CUInt64(1)
+ v26:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v26, v10)
bb6(v16:BasicObject, v17:BasicObject):
- v30:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing
+ v29:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing
CheckInterrupts
- Return v30
+ Return v29
");
}
@@ -8415,18 +8438,19 @@ mod hir_opt_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- v25:CInt64 = LoadField v18, :_env_data_index_specval@0x1002
- v26:CInt64[0] = GuardBitEquals v25, CInt64(0)
- v27:NilClass = Const Value(nil)
- Jump bb6(v27, v10)
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1003
- Jump bb6(v23, v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22, v22)
+ bb5():
+ v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1003
+ v25:CInt64[0] = GuardBitEquals v24, CInt64(0)
+ v26:NilClass = Const Value(nil)
+ Jump bb6(v26, v10)
bb6(v16:BasicObject, v17:BasicObject):
- v30:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing
+ v29:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing
CheckInterrupts
- Return v30
+ Return v29
");
}
@@ -8455,18 +8479,19 @@ mod hir_opt_tests {
v13:CPtr = GetEP 1
v14:CUInt64 = LoadField v13, :_ep_flags@0x1000
v15:CBool = IsBlockParamModified v14
- IfTrue v15, bb4()
- v20:CInt64 = LoadField v13, :_env_data_index_specval@0x1001
- v21:CInt64 = GuardAnyBitSet v20, CUInt64(1)
- v22:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v22)
+ CondBranch v15, bb4(), bb5()
bb4():
- v18:BasicObject = LoadField v13, :block@0x1010
- Jump bb6(v18)
+ v17:BasicObject = LoadField v13, :block@0x1001
+ Jump bb6(v17)
+ bb5():
+ v19:CInt64 = LoadField v13, :_env_data_index_specval@0x1002
+ v20:CInt64 = GuardAnyBitSet v19, CUInt64(1)
+ v21:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v21)
bb6(v12:BasicObject):
- v25:BasicObject = Send v10, &block, :map, v12 # SendFallbackReason: Complex argument passing
+ v24:BasicObject = Send v10, &block, :map, v12 # SendFallbackReason: Complex argument passing
CheckInterrupts
- Return v25
+ Return v24
");
}
@@ -11950,18 +11975,19 @@ mod hir_opt_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- v25:CInt64 = LoadField v18, :_env_data_index_specval@0x1002
- v26:CInt64 = GuardAnyBitSet v25, CUInt64(1)
- v27:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v27, v10)
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1010
- Jump bb6(v23, v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22, v22)
+ bb5():
+ v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1003
+ v25:CInt64 = GuardAnyBitSet v24, CUInt64(1)
+ v26:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v26, v10)
bb6(v16:BasicObject, v17:BasicObject):
- v30:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing
+ v29:BasicObject = Send v14, &block, :map, v16 # SendFallbackReason: Complex argument passing
CheckInterrupts
- Return v30
+ Return v29
");
}
@@ -14565,7 +14591,8 @@ mod hir_opt_tests {
v5:CPtr = LoadPC
v6:CPtr[CPtr(0x1002)] = Const CPtr(0x1002)
v7:CBool = IsBitEqual v5, v6
- IfTrue v7, bb3(v1, v3, v4)
+ CondBranch v7, bb3(v1, v3, v4), bb6()
+ bb6():
Jump bb5(v1, v3, v4)
bb2():
EntryPoint JIT(0)
@@ -14723,7 +14750,8 @@ mod hir_opt_tests {
v4:CPtr = LoadPC
v5:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
v6:CBool = IsBitEqual v4, v5
- IfTrue v6, bb3(v1, v3)
+ CondBranch v6, bb3(v1, v3), bb6()
+ bb6():
Jump bb5(v1, v3)
bb2():
EntryPoint JIT(0)
@@ -14782,7 +14810,8 @@ mod hir_opt_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb6(v9, v17)
+ CondBranch v16, bb7(), bb6(v9, v17)
+ bb7():
v19:Truthy = RefineType v10, Truthy
CheckInterrupts
v38:Fixnum[3] = Const Value(3)
@@ -14827,21 +14856,23 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, ObjectSubclass[class_exact:C]
- IfTrue v15, bb5(v9, v10, v10)
- v24:CBool = HasType v10, ObjectSubclass[class_exact:D]
- IfTrue v24, bb6(v9, v10, v10)
- v33:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v33)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
v55:Fixnum[3] = Const Value(3)
Jump bb4(v16, v17, v55)
- bb6(v25:BasicObject, v26:BasicObject, v27:BasicObject):
+ bb6():
+ v24:CBool = HasType v10, ObjectSubclass[class_exact:D]
+ CondBranch v24, bb7(v9, v10, v10), bb8()
+ bb7(v25:BasicObject, v26:BasicObject, v27:BasicObject):
PatchPoint NoSingletonClass(D@0x1040)
PatchPoint MethodRedefined(D@0x1040, foo@0x1010, cme:0x1048)
v56:Fixnum[4] = Const Value(4)
Jump bb4(v25, v26, v56)
+ bb8():
+ v33:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v33)
bb4(v35:BasicObject, v36:BasicObject, v37:BasicObject):
v40:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Integer@0x1070, +@0x1078, cme:0x1080)
@@ -14879,20 +14910,22 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, ObjectSubclass[class_exact:C]
- IfTrue v15, bb5(v9, v10, v10)
- v24:CBool = HasType v10, Fixnum
- IfTrue v24, bb6(v9, v10, v10)
- v33:BasicObject = Send v10, :itself # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v33)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
v20:ObjectSubclass[class_exact:C] = RefineType v18, ObjectSubclass[class_exact:C]
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, itself@0x1010, cme:0x1018)
Jump bb4(v16, v17, v20)
- bb6(v25:BasicObject, v26:BasicObject, v27:BasicObject):
+ bb6():
+ v24:CBool = HasType v10, Fixnum
+ CondBranch v24, bb7(v9, v10, v10), bb8()
+ bb7(v25:BasicObject, v26:BasicObject, v27:BasicObject):
v29:Fixnum = RefineType v27, Fixnum
PatchPoint MethodRedefined(Integer@0x1040, itself@0x1010, cme:0x1018)
Jump bb4(v25, v26, v29)
+ bb8():
+ v33:BasicObject = Send v10, :itself # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v33)
bb4(v35:BasicObject, v36:BasicObject, v37:BasicObject):
CheckInterrupts
Return v37
@@ -14932,21 +14965,23 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, Fixnum
- IfTrue v15, bb5(v9, v10, v10)
- v24:CBool = HasType v10, Bignum
- IfTrue v24, bb6(v9, v10, v10)
- v33:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v33)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
v20:Fixnum = RefineType v18, Fixnum
PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018)
v46:StringExact = CCallVariadic v20, :Integer#to_s@0x1040
Jump bb4(v16, v17, v46)
- bb6(v25:BasicObject, v26:BasicObject, v27:BasicObject):
+ bb6():
+ v24:CBool = HasType v10, Bignum
+ CondBranch v24, bb7(v9, v10, v10), bb8()
+ bb7(v25:BasicObject, v26:BasicObject, v27:BasicObject):
v29:Bignum = RefineType v27, Bignum
PatchPoint MethodRedefined(Integer@0x1008, to_s@0x1010, cme:0x1018)
v49:StringExact = CCallVariadic v29, :Integer#to_s@0x1040
Jump bb4(v25, v26, v49)
+ bb8():
+ v33:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v33)
bb4(v35:BasicObject, v36:BasicObject, v37:BasicObject):
CheckInterrupts
Return v37
@@ -14983,21 +15018,23 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, Flonum
- IfTrue v15, bb5(v9, v10, v10)
- v24:CBool = HasType v10, HeapFloat
- IfTrue v24, bb6(v9, v10, v10)
- v33:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v33)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
v20:Flonum = RefineType v18, Flonum
PatchPoint MethodRedefined(Float@0x1008, to_s@0x1010, cme:0x1018)
v46:BasicObject = CCallWithFrame v20, :Float#to_s@0x1040
Jump bb4(v16, v17, v46)
- bb6(v25:BasicObject, v26:BasicObject, v27:BasicObject):
+ bb6():
+ v24:CBool = HasType v10, HeapFloat
+ CondBranch v24, bb7(v9, v10, v10), bb8()
+ bb7(v25:BasicObject, v26:BasicObject, v27:BasicObject):
v29:HeapFloat = RefineType v27, HeapFloat
PatchPoint MethodRedefined(Float@0x1008, to_s@0x1010, cme:0x1018)
v49:BasicObject = CCallWithFrame v29, :Float#to_s@0x1040
Jump bb4(v25, v26, v49)
+ bb8():
+ v33:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v33)
bb4(v35:BasicObject, v36:BasicObject, v37:BasicObject):
CheckInterrupts
Return v37
@@ -15034,21 +15071,23 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, StaticSymbol
- IfTrue v15, bb5(v9, v10, v10)
- v24:CBool = HasType v10, DynamicSymbol
- IfTrue v24, bb6(v9, v10, v10)
- v33:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v33)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
v20:StaticSymbol = RefineType v18, StaticSymbol
PatchPoint MethodRedefined(Symbol@0x1008, to_s@0x1010, cme:0x1018)
v48:StringExact = InvokeBuiltin leaf , v20
Jump bb4(v16, v17, v48)
- bb6(v25:BasicObject, v26:BasicObject, v27:BasicObject):
+ bb6():
+ v24:CBool = HasType v10, DynamicSymbol
+ CondBranch v24, bb7(v9, v10, v10), bb8()
+ bb7(v25:BasicObject, v26:BasicObject, v27:BasicObject):
v29:DynamicSymbol = RefineType v27, DynamicSymbol
PatchPoint MethodRedefined(Symbol@0x1008, to_s@0x1010, cme:0x1018)
v49:StringExact = InvokeBuiltin leaf , v29
Jump bb4(v25, v26, v49)
+ bb8():
+ v33:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v33)
bb4(v35:BasicObject, v36:BasicObject, v37:BasicObject):
CheckInterrupts
Return v37
@@ -15089,14 +15128,15 @@ mod hir_opt_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v15:CBool = HasType v10, ObjectSubclass[class_exact:C]
- IfTrue v15, bb5(v9, v10, v10)
- v24:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback
- Jump bb4(v9, v10, v24)
+ CondBranch v15, bb5(v9, v10, v10), bb6()
bb5(v16:BasicObject, v17:BasicObject, v18:BasicObject):
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018)
v38:Fixnum[3] = Const Value(3)
Jump bb4(v16, v17, v38)
+ bb6():
+ v24:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback
+ Jump bb4(v9, v10, v24)
bb4(v26:BasicObject, v27:BasicObject, v28:BasicObject):
CheckInterrupts
Return v28
@@ -15206,10 +15246,12 @@ mod hir_opt_tests {
v7:CPtr = LoadPC
v8:CPtr[CPtr(0x1003)] = Const CPtr(0x1003)
v9:CBool = IsBitEqual v7, v8
- IfTrue v9, bb3(v1, v3, v4, v5, v6)
+ CondBranch v9, bb3(v1, v3, v4, v5, v6), bb9()
+ bb9():
v11:CPtr[CPtr(0x1004)] = Const CPtr(0x1004)
v12:CBool = IsBitEqual v7, v11
- IfTrue v12, bb5(v1, v3, v4, v5, v6)
+ CondBranch v12, bb5(v1, v3, v4, v5, v6), bb10()
+ bb10():
Jump bb7(v1, v3, v4, v5, v6)
bb2():
EntryPoint JIT(0)
@@ -15247,7 +15289,8 @@ mod hir_opt_tests {
CheckInterrupts
v68:CBool = Test v60
v69:Truthy = RefineType v60, Truthy
- IfTrue v68, bb8(v58, v59, v69, v61, v62)
+ CondBranch v68, bb8(v58, v59, v69, v61, v62), bb11()
+ bb11():
v71:Falsy = RefineType v60, Falsy
PatchPoint MethodRedefined(Object@0x1010, lambda@0x1018, cme:0x1020)
v118:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v58, ObjectSubclass[class_exact*:Object@VALUE(0x1010)]
@@ -15297,20 +15340,18 @@ mod hir_opt_tests {
v13:NilClass = Const Value(nil)
v15:TrueClass|NilClass = Defined yield, v13
v17:CBool = Test v15
- IfFalse v17, bb4(v8, v9)
+ CondBranch v17, bb9(), bb4(v8, v9)
+ bb9():
v35:Fixnum[0] = Const Value(0)
Jump bb8(v8, v35)
- bb4(v23:BasicObject, v24:NilClass):
- v28:BasicObject = InvokeBuiltin , v23
- CheckInterrupts
- Return v28
bb8(v48:BasicObject, v49:Fixnum):
v84:Array = RefineType v48, Array
v85:CInt64 = ArrayLength v84
v86:Fixnum = BoxFixnum v85
v87:BoolExact = FixnumGe v49, v86
v54:CBool = Test v87
- IfFalse v54, bb7(v48, v49)
+ CondBranch v54, bb10(), bb7(v48, v49)
+ bb10():
CheckInterrupts
Return v48
bb7(v67:BasicObject, v68:Fixnum):
@@ -15322,6 +15363,10 @@ mod hir_opt_tests {
v92:Fixnum = FixnumAdd v68, v91
PatchPoint NoEPEscape(each)
Jump bb8(v67, v92)
+ bb4(v23:BasicObject, v24:NilClass):
+ v28:BasicObject = InvokeBuiltin , v23
+ CheckInterrupts
+ Return v28
");
}
@@ -15556,7 +15601,8 @@ mod hir_opt_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb4(v9, v17)
+ CondBranch v16, bb5(), bb4(v9, v17)
+ bb5():
v19:Truthy = RefineType v10, Truthy
v23:Fixnum[42] = Const Value(42)
PatchPoint MethodRedefined(Object@0x1008, greet_recompile@0x1010, cme:0x1018)
@@ -15614,7 +15660,8 @@ mod hir_opt_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb4(v9, v17)
+ CondBranch v16, bb5(), bb4(v9, v17)
+ bb5():
v19:Truthy = RefineType v10, Truthy
v23:Fixnum[42] = Const Value(42)
v25:BasicObject = Send v9, :greet_final, v23 # SendFallbackReason: SendWithoutBlock: no profile data available
@@ -15658,12 +15705,13 @@ mod hir_opt_tests {
v15:CInt64 = IntAnd v13, v14
v16:CInt64[3] = Const CInt64(3)
v17:CBool = IsBitEqual v15, v16
- IfTrue v17, bb5()
- v22:BasicObject = InvokeBlock, v10 # SendFallbackReason: InvokeBlock: not yet specialized
- Jump bb4(v22)
+ CondBranch v17, bb5(), bb6()
bb5():
v20:BasicObject = InvokeBlockIfunc v13, v10
Jump bb4(v20)
+ bb6():
+ v22:BasicObject = InvokeBlock, v10 # SendFallbackReason: InvokeBlock: not yet specialized
+ Jump bb4(v22)
bb4(v18:BasicObject):
v27:Fixnum[2] = Const Value(2)
v29:CPtr = GetEP 0
@@ -15672,13 +15720,14 @@ mod hir_opt_tests {
v32:CInt64 = IntAnd v30, v31
v33:CInt64[3] = Const CInt64(3)
v34:CBool = IsBitEqual v32, v33
- IfTrue v34, bb7()
- v39:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized
- Jump bb6(v39)
- bb7():
+ CondBranch v34, bb8(), bb9()
+ bb8():
v37:BasicObject = InvokeBlockIfunc v30, v27
- Jump bb6(v37)
- bb6(v35:BasicObject):
+ Jump bb7(v37)
+ bb9():
+ v39:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized
+ Jump bb7(v39)
+ bb7(v35:BasicObject):
CheckInterrupts
Return v35
");
@@ -15825,10 +15874,7 @@ mod hir_opt_tests {
v110:BoolExact = FixnumLt v20, v24
CheckInterrupts
v30:CBool = Test v110
- IfTrue v30, bb4(v19, v20)
- v35:NilClass = Const Value(nil)
- CheckInterrupts
- Return v35
+ CondBranch v30, bb4(v19, v20), bb7()
bb4(v40:BasicObject, v41:Fixnum):
PatchPoint SingleRactorMode
v46:HeapBasicObject = GuardType v40, HeapBasicObject
@@ -15838,35 +15884,38 @@ mod hir_opt_tests {
v51 = RefineType v50, CUInt64
v52:CInt64 = IntAnd v47, v49
v53:CBool = IsBitEqual v52, v51
- IfTrue v53, bb8()
+ CondBranch v53, bb9(), bb10()
+ bb9():
+ v55:BasicObject = LoadField v46, :@levar@0x103a
+ Jump bb8(v55)
+ bb10():
v57:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f)
- v58:CPtr[CPtr(0x103a)] = Const CPtr(0x103a)
+ v58:CPtr[CPtr(0x103b)] = Const CPtr(0x103b)
v59 = RefineType v58, CUInt64
v60:CInt64 = IntAnd v47, v57
v61:CBool = IsBitEqual v60, v59
- IfTrue v61, bb9()
- v97:CShape = LoadField v46, :_shape_id@0x103b
- v98:CShape[0x103c] = GuardBitEquals v97, CShape(0x103c)
- v99:BasicObject = LoadField v46, :@levar@0x103d
- Jump bb7(v99)
- bb8():
- v55:BasicObject = LoadField v46, :@levar@0x103d
- Jump bb7(v55)
- bb9():
+ CondBranch v61, bb11(), bb12()
+ bb11():
v63:NilClass = Const Value(nil)
- Jump bb7(v63)
- bb7(v48:BasicObject):
+ Jump bb8(v63)
+ bb12():
+ v97:CShape = LoadField v46, :_shape_id@0x103c
+ v98:CShape[0x103d] = GuardBitEquals v97, CShape(0x103d)
+ v99:BasicObject = LoadField v46, :@levar@0x103a
+ Jump bb8(v99)
+ bb8(v48:BasicObject):
CheckInterrupts
v69:CBool = Test v48
- IfTrue v69, bb5(v46, v41)
+ CondBranch v69, bb5(v46, v41), bb13()
+ bb13():
PatchPoint NoEPEscape(set_value_loop)
PatchPoint SingleRactorMode
- v101:CShape = LoadField v46, :_shape_id@0x103b
+ v101:CShape = LoadField v46, :_shape_id@0x103c
v102:CShape[0x103e] = GuardBitEquals v101, CShape(0x103e)
- StoreField v46, :@levar@0x103d, v41
+ StoreField v46, :@levar@0x103a, v41
WriteBarrier v46, v41
- v105:CShape[0x103c] = Const CShape(0x103c)
- StoreField v46, :_shape_id@0x103b, v105
+ v105:CShape[0x103d] = Const CShape(0x103d)
+ StoreField v46, :_shape_id@0x103c, v105
v79:HeapBasicObject = RefineType v46, HeapBasicObject
Jump bb5(v79, v41)
bb5(v81:HeapBasicObject, v82:Fixnum):
@@ -15875,6 +15924,10 @@ mod hir_opt_tests {
PatchPoint MethodRedefined(Integer@0x1000, +@0x103f, cme:0x1040)
v114:Fixnum = FixnumAdd v82, v89
Jump bb6(v81, v114)
+ bb7():
+ v35:NilClass = Const Value(nil)
+ CheckInterrupts
+ Return v35
");
}
diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs
index 1a836a561ffe5c..838a9b8808efe4 100644
--- a/zjit/src/hir/tests.rs
+++ b/zjit/src/hir/tests.rs
@@ -296,7 +296,8 @@ pub(crate) mod hir_build_tests {
v4:CPtr = LoadPC
v5:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
v6:CBool = IsBitEqual v4, v5
- IfTrue v6, bb3(v1, v3)
+ CondBranch v6, bb3(v1, v3), bb6()
+ bb6():
Jump bb5(v1, v3)
bb2():
EntryPoint JIT(0)
@@ -373,15 +374,16 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v23:CBool = Test v20
v24:Truthy = RefineType v20, Truthy
- IfTrue v23, bb4(v9, v10, v14, v10)
- v26:Falsy = RefineType v20, Falsy
- v31:Fixnum[2] = Const Value(2)
- CheckInterrupts
- Return v31
+ CondBranch v23, bb4(v9, v10, v14, v10), bb5()
bb4(v36:BasicObject, v37:BasicObject, v38:NilClass, v39:BasicObject):
v44:Fixnum[1] = Const Value(1)
CheckInterrupts
Return v44
+ bb5():
+ v26:Falsy = RefineType v20, Falsy
+ v31:Fixnum[2] = Const Value(2)
+ CheckInterrupts
+ Return v31
");
}
@@ -419,15 +421,16 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v22:CBool = Test v19
v23:Truthy = RefineType v19, Truthy
- IfTrue v22, bb4(v9, v10, v10)
- v25:Falsy = RefineType v19, Falsy
- v29:Fixnum[2] = Const Value(2)
- CheckInterrupts
- Return v29
+ CondBranch v22, bb4(v9, v10, v10), bb5()
bb4(v34:BasicObject, v35:BasicObject, v36:BasicObject):
v41:Fixnum[1] = Const Value(1)
CheckInterrupts
Return v41
+ bb5():
+ v25:Falsy = RefineType v19, Falsy
+ v29:Fixnum[2] = Const Value(2)
+ CheckInterrupts
+ Return v29
");
}
@@ -463,15 +466,16 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v18:CBool = Test v15
v19:Truthy = RefineType v15, Truthy
- IfTrue v18, bb4(v6)
- v21:Falsy = RefineType v15, Falsy
- v24:Fixnum[2] = Const Value(2)
- CheckInterrupts
- Return v24
+ CondBranch v18, bb4(v6), bb5()
bb4(v29:BasicObject):
v33:Fixnum[1] = Const Value(1)
CheckInterrupts
Return v33
+ bb5():
+ v21:Falsy = RefineType v15, Falsy
+ v24:Fixnum[2] = Const Value(2)
+ CheckInterrupts
+ Return v24
");
}
@@ -1161,7 +1165,8 @@ pub(crate) mod hir_build_tests {
v5:CPtr = LoadPC
v6:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
v7:CBool = IsBitEqual v5, v6
- IfTrue v7, bb3(v1, v3, v4)
+ CondBranch v7, bb3(v1, v3, v4), bb6()
+ bb6():
Jump bb5(v1, v3, v4)
bb2():
EntryPoint JIT(0)
@@ -1203,7 +1208,8 @@ pub(crate) mod hir_build_tests {
v5:CPtr = LoadPC
v6:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
v7:CBool = IsBitEqual v5, v6
- IfTrue v7, bb3(v1, v3, v4)
+ CondBranch v7, bb3(v1, v3, v4), bb6()
+ bb6():
Jump bb5(v1, v3, v4)
bb2():
EntryPoint JIT(0)
@@ -1241,7 +1247,8 @@ pub(crate) mod hir_build_tests {
v4:CPtr = LoadPC
v5:CPtr[CPtr(0x1001)] = Const CPtr(0x1001)
v6:CBool = IsBitEqual v4, v5
- IfTrue v6, bb3(v1, v3)
+ CondBranch v6, bb3(v1, v3), bb6()
+ bb6():
Jump bb5(v1, v3)
bb2():
EntryPoint JIT(0)
@@ -1340,7 +1347,8 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v13:CBool = Test v10
v14:NilClass = RefineType v10, Falsy
- IfFalse v13, bb4(v6)
+ CondBranch v13, bb5(), bb4(v6)
+ bb5():
v16:TrueClass = RefineType v10, Truthy
v19:Fixnum[3] = Const Value(3)
CheckInterrupts
@@ -1457,7 +1465,8 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb4(v9, v17)
+ CondBranch v16, bb5(), bb4(v9, v17)
+ bb5():
v19:Truthy = RefineType v10, Truthy
v22:Fixnum[3] = Const Value(3)
CheckInterrupts
@@ -1500,7 +1509,8 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v19:CBool = Test v12
v20:Falsy = RefineType v12, Falsy
- IfFalse v19, bb4(v11, v20, v13)
+ CondBranch v19, bb6(), bb4(v11, v20, v13)
+ bb6():
v22:Truthy = RefineType v12, Truthy
v25:Fixnum[3] = Const Value(3)
CheckInterrupts
@@ -1843,17 +1853,18 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v38:CBool = Test v35
v39:Truthy = RefineType v35, Truthy
- IfTrue v38, bb4(v26, v27, v28)
- v41:Falsy = RefineType v35, Falsy
- v43:NilClass = Const Value(nil)
- CheckInterrupts
- Return v27
+ CondBranch v38, bb4(v26, v27, v28), bb6()
bb4(v51:BasicObject, v52:BasicObject, v53:BasicObject):
v58:Fixnum[1] = Const Value(1)
v61:BasicObject = Send v52, :+, v58 # SendFallbackReason: Uncategorized(opt_plus)
v66:Fixnum[1] = Const Value(1)
v69:BasicObject = Send v53, :-, v66 # SendFallbackReason: Uncategorized(opt_minus)
Jump bb5(v51, v61, v69)
+ bb6():
+ v41:Falsy = RefineType v35, Falsy
+ v43:NilClass = Const Value(nil)
+ CheckInterrupts
+ Return v27
");
}
@@ -1915,7 +1926,8 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v19:CBool[true] = Test v13
v20 = RefineType v13, Falsy
- IfFalse v19, bb4(v8, v20)
+ CondBranch v19, bb5(), bb4(v8, v20)
+ bb5():
v22:TrueClass = RefineType v13, Truthy
v25:Fixnum[3] = Const Value(3)
CheckInterrupts
@@ -2473,16 +2485,15 @@ pub(crate) mod hir_build_tests {
v36:CPtr = GetEP 0
v37:CUInt64 = LoadField v36, :_ep_flags@0x1004
v38:CBool = IsBlockParamModified v37
- IfTrue v38, bb4()
- Jump bb5()
+ CondBranch v38, bb4(), bb5()
bb4():
- v41:BasicObject = LoadField v36, :&@0x1005
- Jump bb6(v41, v41)
+ v40:BasicObject = LoadField v36, :&@0x1005
+ Jump bb6(v40, v40)
bb5():
- v43:CInt64 = LoadField v36, :_env_data_index_specval@0x1006
- v44:CInt64 = GuardAnyBitSet v43, CUInt64(1)
- v45:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v45, v21)
+ v42:CInt64 = LoadField v36, :_env_data_index_specval@0x1006
+ v43:CInt64 = GuardAnyBitSet v42, CUInt64(1)
+ v44:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v44, v21)
bb6(v34:BasicObject, v35:BasicObject):
SideExit SplatKwNotProfiled
");
@@ -2509,7 +2520,8 @@ pub(crate) mod hir_build_tests {
v10:BasicObject = GetConstantPath 0x1000
v12:NilClass = Const Value(nil)
v15:CBool = IsMethodCFunc v10, :new
- IfFalse v15, bb4(v6, v12, v10)
+ CondBranch v15, bb6(), bb4(v6, v12, v10)
+ bb6():
v17:HeapBasicObject = ObjectAlloc v10
v19:BasicObject = Send v17, :initialize # SendFallbackReason: Uncategorized(opt_send_without_block)
CheckInterrupts
@@ -3393,14 +3405,13 @@ pub(crate) mod hir_build_tests {
v15:CPtr = GetEP 0
v16:CUInt64 = LoadField v15, :_ep_flags@0x1001
v17:CBool = IsBlockParamModified v16
- IfTrue v17, bb4()
- Jump bb5()
+ CondBranch v17, bb4(), bb5()
bb4():
- v20:BasicObject = LoadField v15, :block@0x1002
- Jump bb6(v20)
+ v19:BasicObject = LoadField v15, :block@0x1002
+ Jump bb6(v19)
bb5():
- v22:BasicObject = GetBlockParam :block, l0, EP@3
- Jump bb6(v22)
+ v21:BasicObject = GetBlockParam :block, l0, EP@3
+ Jump bb6(v21)
bb6(v14:BasicObject):
CheckInterrupts
Return v14
@@ -3430,20 +3441,19 @@ pub(crate) mod hir_build_tests {
v17:CPtr = GetEP 0
v18:CUInt64 = LoadField v17, :_ep_flags@0x1001
v19:CBool = IsBlockParamModified v18
- IfTrue v19, bb4()
- Jump bb5()
+ CondBranch v19, bb4(), bb5()
bb4():
- v22:BasicObject = LoadField v17, :block@0x1002
- Jump bb6(v22, v22)
+ v21:BasicObject = LoadField v17, :block@0x1002
+ Jump bb6(v21, v21)
bb5():
- v24:CInt64 = LoadField v17, :_env_data_index_specval@0x1003
- v25:CInt64 = GuardAnyBitSet v24, CUInt64(1)
- v26:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v26, v10)
+ v23:CInt64 = LoadField v17, :_env_data_index_specval@0x1003
+ v24:CInt64 = GuardAnyBitSet v23, CUInt64(1)
+ v25:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v25, v10)
bb6(v15:BasicObject, v16:BasicObject):
- v29:BasicObject = Send v9, &block, :tap, v15 # SendFallbackReason: Uncategorized(send)
+ v28:BasicObject = Send v9, &block, :tap, v15 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -3475,32 +3485,30 @@ pub(crate) mod hir_build_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- Jump bb5()
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1002
- Jump bb6(v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22)
bb5():
- v25:BasicObject = GetBlockParam :block, l0, EP@4
- Jump bb6(v25)
+ v24:BasicObject = GetBlockParam :block, l0, EP@4
+ Jump bb6(v24)
bb6(v17:BasicObject):
- v33:CPtr = GetEP 0
- v34:CUInt64 = LoadField v33, :_ep_flags@0x1001
- v35:CBool = IsBlockParamModified v34
- IfTrue v35, bb7()
- Jump bb8()
+ v32:CPtr = GetEP 0
+ v33:CUInt64 = LoadField v32, :_ep_flags@0x1001
+ v34:CBool = IsBlockParamModified v33
+ CondBranch v34, bb7(), bb8()
bb7():
- v38:BasicObject = LoadField v33, :block@0x1002
- Jump bb9(v38, v38)
+ v36:BasicObject = LoadField v32, :block@0x1002
+ Jump bb9(v36, v36)
bb8():
- v40:CInt64 = LoadField v33, :_env_data_index_specval@0x1003
- v41:CInt64 = GuardAnyBitSet v40, CUInt64(1)
- v42:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb9(v42, v17)
- bb9(v31:BasicObject, v32:BasicObject):
- v45:BasicObject = Send v11, &block, :tap, v31 # SendFallbackReason: Uncategorized(send)
+ v38:CInt64 = LoadField v32, :_env_data_index_specval@0x1003
+ v39:CInt64 = GuardAnyBitSet v38, CUInt64(1)
+ v40:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb9(v40, v17)
+ bb9(v30:BasicObject, v31:BasicObject):
+ v43:BasicObject = Send v11, &block, :tap, v30 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v45
+ Return v43
");
}
@@ -3530,32 +3538,30 @@ pub(crate) mod hir_build_tests {
v14:CPtr = GetEP 1
v15:CUInt64 = LoadField v14, :_ep_flags@0x1000
v16:CBool = IsBlockParamModified v15
- IfTrue v16, bb4()
- Jump bb5()
+ CondBranch v16, bb4(), bb5()
bb4():
- v19:BasicObject = LoadField v14, :block@0x1001
- Jump bb6(v19)
+ v18:BasicObject = LoadField v14, :block@0x1001
+ Jump bb6(v18)
bb5():
- v21:BasicObject = GetBlockParam :block, l1, EP@3
- Jump bb6(v21)
+ v20:BasicObject = GetBlockParam :block, l1, EP@3
+ Jump bb6(v20)
bb6(v13:BasicObject):
- v28:CPtr = GetEP 1
- v29:CUInt64 = LoadField v28, :_ep_flags@0x1000
- v30:CBool = IsBlockParamModified v29
- IfTrue v30, bb7()
- Jump bb8()
+ v27:CPtr = GetEP 1
+ v28:CUInt64 = LoadField v27, :_ep_flags@0x1000
+ v29:CBool = IsBlockParamModified v28
+ CondBranch v29, bb7(), bb8()
bb7():
- v33:BasicObject = LoadField v28, :block@0x1001
- Jump bb9(v33)
+ v31:BasicObject = LoadField v27, :block@0x1001
+ Jump bb9(v31)
bb8():
- v35:CInt64 = LoadField v28, :_env_data_index_specval@0x1002
- v36:CInt64 = GuardAnyBitSet v35, CUInt64(1)
- v37:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb9(v37)
- bb9(v27:BasicObject):
- v40:BasicObject = Send v8, &block, :tap, v27 # SendFallbackReason: Uncategorized(send)
+ v33:CInt64 = LoadField v27, :_env_data_index_specval@0x1002
+ v34:CInt64 = GuardAnyBitSet v33, CUInt64(1)
+ v35:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb9(v35)
+ bb9(v26:BasicObject):
+ v38:BasicObject = Send v8, &block, :tap, v26 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v40
+ Return v38
");
}
@@ -3589,31 +3595,32 @@ pub(crate) mod hir_build_tests {
v18:CPtr = GetEP 0
v19:CUInt64 = LoadField v18, :_ep_flags@0x1001
v20:CBool = IsBlockParamModified v19
- IfTrue v20, bb4()
- Jump bb5()
+ CondBranch v20, bb4(), bb5()
bb4():
- v23:BasicObject = LoadField v18, :block@0x1002
- Jump bb6(v23, v23)
+ v22:BasicObject = LoadField v18, :block@0x1002
+ Jump bb6(v22, v22)
bb5():
- v25:CInt64 = LoadField v18, :_env_data_index_specval@0x1003
- v26:CInt64[1] = Const CInt64(1)
- v27:CInt64 = IntAnd v25, v26
- v28:CBool = IsBitEqual v27, v26
- IfTrue v28, bb7()
- v32:CInt64[0] = Const CInt64(0)
- v33:CBool = IsBitEqual v25, v32
- IfTrue v33, bb8()
- SideExit BlockParamProxyProfileNotCovered
+ v24:CInt64 = LoadField v18, :_env_data_index_specval@0x1003
+ v25:CInt64[1] = Const CInt64(1)
+ v26:CInt64 = IntAnd v24, v25
+ v27:CBool = IsBitEqual v26, v25
+ CondBranch v27, bb7(), bb9()
bb7():
- v30:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v30, v10)
+ v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v29, v10)
+ bb9():
+ v31:CInt64[0] = Const CInt64(0)
+ v32:CBool = IsBitEqual v24, v31
+ CondBranch v32, bb8(), bb10()
bb8():
- v35:NilClass = Const Value(nil)
- Jump bb6(v35, v10)
+ v34:NilClass = Const Value(nil)
+ Jump bb6(v34, v10)
bb6(v16:BasicObject, v17:BasicObject):
- v39:BasicObject = Send v14, &block, :then, v16 # SendFallbackReason: Uncategorized(send)
+ v38:BasicObject = Send v14, &block, :then, v16 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v39
+ Return v38
+ bb10():
+ SideExit BlockParamProxyProfileNotCovered
");
}
@@ -3640,14 +3647,13 @@ pub(crate) mod hir_build_tests {
v11:CPtr = GetEP 1
v12:CUInt64 = LoadField v11, :_ep_flags@0x1000
v13:CBool = IsBlockParamModified v12
- IfTrue v13, bb4()
- Jump bb5()
+ CondBranch v13, bb4(), bb5()
bb4():
- v16:BasicObject = LoadField v11, :block@0x1001
- Jump bb6(v16)
+ v15:BasicObject = LoadField v11, :block@0x1001
+ Jump bb6(v15)
bb5():
- v18:BasicObject = GetBlockParam :block, l1, EP@3
- Jump bb6(v18)
+ v17:BasicObject = GetBlockParam :block, l1, EP@3
+ Jump bb6(v17)
bb6(v10:BasicObject):
CheckInterrupts
Return v10
@@ -3746,16 +3752,15 @@ pub(crate) mod hir_build_tests {
v21:CPtr = GetEP 0
v22:CUInt64 = LoadField v21, :_ep_flags@0x1002
v23:CBool = IsBlockParamModified v22
- IfTrue v23, bb4()
- Jump bb5()
+ CondBranch v23, bb4(), bb5()
bb4():
- v26:BasicObject = LoadField v21, :b@0x1003
- Jump bb6(v26, v26)
+ v25:BasicObject = LoadField v21, :b@0x1003
+ Jump bb6(v25, v25)
bb5():
- v28:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
- v29:CInt64 = GuardAnyBitSet v28, CUInt64(1)
- v30:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v30, v13)
+ v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
+ v28:CInt64 = GuardAnyBitSet v27, CUInt64(1)
+ v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v29, v13)
bb6(v19:BasicObject, v20:BasicObject):
SideExit SplatKwNotProfiled
");
@@ -3796,21 +3801,20 @@ pub(crate) mod hir_build_tests {
v36:CPtr = GetEP 0
v37:CUInt64 = LoadField v36, :_ep_flags@0x1004
v38:CBool = IsBlockParamModified v37
- IfTrue v38, bb4()
- Jump bb5()
+ CondBranch v38, bb4(), bb5()
bb4():
- v41:BasicObject = LoadField v36, :&@0x1005
- Jump bb6(v41, v41)
+ v40:BasicObject = LoadField v36, :&@0x1005
+ Jump bb6(v40, v40)
bb5():
- v43:CInt64 = LoadField v36, :_env_data_index_specval@0x1006
- v44:CInt64[0] = GuardBitEquals v43, CInt64(0)
- v45:NilClass = Const Value(nil)
- Jump bb6(v45, v21)
+ v42:CInt64 = LoadField v36, :_env_data_index_specval@0x1006
+ v43:CInt64[0] = GuardBitEquals v42, CInt64(0)
+ v44:NilClass = Const Value(nil)
+ Jump bb6(v44, v21)
bb6(v34:BasicObject, v35:BasicObject):
- v48:NilClass = GuardType v20, NilClass
- v50:BasicObject = Send v17, &block, :foo, v18, v29, v48, v34 # SendFallbackReason: Uncategorized(send)
+ v47:NilClass = GuardType v20, NilClass
+ v49:BasicObject = Send v17, &block, :foo, v18, v29, v47, v34 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v50
+ Return v49
");
}
@@ -3841,21 +3845,20 @@ pub(crate) mod hir_build_tests {
v21:CPtr = GetEP 0
v22:CUInt64 = LoadField v21, :_ep_flags@0x1002
v23:CBool = IsBlockParamModified v22
- IfTrue v23, bb4()
- Jump bb5()
+ CondBranch v23, bb4(), bb5()
bb4():
- v26:BasicObject = LoadField v21, :b@0x1003
- Jump bb6(v26, v26)
+ v25:BasicObject = LoadField v21, :b@0x1003
+ Jump bb6(v25, v25)
bb5():
- v28:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
- v29:CInt64 = GuardAnyBitSet v28, CUInt64(1)
- v30:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v30, v13)
+ v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
+ v28:CInt64 = GuardAnyBitSet v27, CUInt64(1)
+ v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v29, v13)
bb6(v19:BasicObject, v20:BasicObject):
- v33:HashExact = GuardType v12, HashExact
- v35:BasicObject = Send v11, &block, :foo, v33, v19 # SendFallbackReason: Uncategorized(send)
+ v32:HashExact = GuardType v12, HashExact
+ v34:BasicObject = Send v11, &block, :foo, v32, v19 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v35
+ Return v34
");
}
@@ -3886,21 +3889,20 @@ pub(crate) mod hir_build_tests {
v21:CPtr = GetEP 0
v22:CUInt64 = LoadField v21, :_ep_flags@0x1002
v23:CBool = IsBlockParamModified v22
- IfTrue v23, bb4()
- Jump bb5()
+ CondBranch v23, bb4(), bb5()
bb4():
- v26:BasicObject = LoadField v21, :b@0x1003
- Jump bb6(v26, v26)
+ v25:BasicObject = LoadField v21, :b@0x1003
+ Jump bb6(v25, v25)
bb5():
- v28:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
- v29:CInt64 = GuardAnyBitSet v28, CUInt64(1)
- v30:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v30, v13)
+ v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
+ v28:CInt64 = GuardAnyBitSet v27, CUInt64(1)
+ v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v29, v13)
bb6(v19:BasicObject, v20:BasicObject):
- v33:HashExact = GuardType v12, HashExact
- v35:BasicObject = Send v11, &block, :foo, v33, v19 # SendFallbackReason: Uncategorized(send)
+ v32:HashExact = GuardType v12, HashExact
+ v34:BasicObject = Send v11, &block, :foo, v32, v19 # SendFallbackReason: Uncategorized(send)
CheckInterrupts
- Return v35
+ Return v34
");
}
@@ -3941,16 +3943,15 @@ pub(crate) mod hir_build_tests {
v36:CPtr = GetEP 0
v37:CUInt64 = LoadField v36, :_ep_flags@0x1004
v38:CBool = IsBlockParamModified v37
- IfTrue v38, bb4()
- Jump bb5()
+ CondBranch v38, bb4(), bb5()
bb4():
- v41:BasicObject = LoadField v36, :&@0x1005
- Jump bb6(v41, v41)
+ v40:BasicObject = LoadField v36, :&@0x1005
+ Jump bb6(v40, v40)
bb5():
- v43:CInt64 = LoadField v36, :_env_data_index_specval@0x1006
- v44:CInt64[0] = GuardBitEquals v43, CInt64(0)
- v45:NilClass = Const Value(nil)
- Jump bb6(v45, v21)
+ v42:CInt64 = LoadField v36, :_env_data_index_specval@0x1006
+ v43:CInt64[0] = GuardBitEquals v42, CInt64(0)
+ v44:NilClass = Const Value(nil)
+ Jump bb6(v44, v21)
bb6(v34:BasicObject, v35:BasicObject):
SideExit SplatKwPolymorphic
");
@@ -3985,16 +3986,15 @@ pub(crate) mod hir_build_tests {
v21:CPtr = GetEP 0
v22:CUInt64 = LoadField v21, :_ep_flags@0x1002
v23:CBool = IsBlockParamModified v22
- IfTrue v23, bb4()
- Jump bb5()
+ CondBranch v23, bb4(), bb5()
bb4():
- v26:BasicObject = LoadField v21, :block@0x1003
- Jump bb6(v26, v26)
+ v25:BasicObject = LoadField v21, :block@0x1003
+ Jump bb6(v25, v25)
bb5():
- v28:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
- v29:CInt64 = GuardAnyBitSet v28, CUInt64(1)
- v30:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb6(v30, v13)
+ v27:CInt64 = LoadField v21, :_env_data_index_specval@0x1004
+ v28:CInt64 = GuardAnyBitSet v27, CUInt64(1)
+ v29:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb6(v29, v13)
bb6(v19:BasicObject, v20:BasicObject):
SideExit SplatKwNotNilOrHash
");
@@ -4469,7 +4469,8 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v17:CBool = IsNil v10
v18:NilClass = Const Value(nil)
- IfTrue v17, bb4(v9, v18, v18)
+ CondBranch v17, bb4(v9, v18, v18), bb5()
+ bb5():
v20:NotNil = RefineType v10, NotNil
v22:BasicObject = Send v20, :itself # SendFallbackReason: Uncategorized(opt_send_without_block)
Jump bb4(v9, v20, v22)
@@ -4509,12 +4510,14 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb4(v9, v17)
+ CondBranch v16, bb6(), bb4(v9, v17)
+ bb6():
v19:Truthy = RefineType v10, Truthy
CheckInterrupts
v25:CBool[false] = IsNil v19
v26:NilClass = Const Value(nil)
- IfTrue v25, bb5(v9, v26, v26)
+ CondBranch v25, bb5(v9, v26, v26), bb7()
+ bb7():
v28:Truthy = RefineType v19, NotNil
v30:BasicObject = Send v28, :itself # SendFallbackReason: Uncategorized(opt_send_without_block)
CheckInterrupts
@@ -4564,33 +4567,36 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v16:CBool = Test v10
v17:Falsy = RefineType v10, Falsy
- IfFalse v16, bb6(v9, v17)
+ CondBranch v16, bb7(), bb6(v9, v17)
+ bb7():
v19:Truthy = RefineType v10, Truthy
CheckInterrupts
v24:CBool[true] = Test v19
v25 = RefineType v19, Falsy
- IfFalse v24, bb5(v9, v25)
+ CondBranch v24, bb8(), bb5(v9, v25)
+ bb8():
v27:Truthy = RefineType v19, Truthy
CheckInterrupts
v32:CBool[true] = Test v27
v33 = RefineType v27, Falsy
- IfFalse v32, bb4(v9, v33)
+ CondBranch v32, bb9(), bb4(v9, v33)
+ bb9():
v35:Truthy = RefineType v27, Truthy
v38:Fixnum[3] = Const Value(3)
CheckInterrupts
Return v38
- bb6(v43:BasicObject, v44:Falsy):
- v48:Fixnum[6] = Const Value(6)
+ bb4(v63, v64):
+ v68 = Const Value(4)
CheckInterrupts
- Return v48
+ Return v68
bb5(v53, v54):
v58 = Const Value(5)
CheckInterrupts
Return v58
- bb4(v63, v64):
- v68 = Const Value(4)
+ bb6(v43:BasicObject, v44:Falsy):
+ v48:Fixnum[6] = Const Value(6)
CheckInterrupts
- Return v68
+ Return v48
");
}
@@ -4680,29 +4686,29 @@ pub(crate) mod hir_build_tests {
v35:CPtr = GetEP 0
v36:CUInt64 = LoadField v35, :_ep_flags@0x1004
v37:CBool = IsBlockParamModified v36
- IfTrue v37, bb5()
- Jump bb6()
+ CondBranch v37, bb5(), bb6()
bb5():
- v40:BasicObject = LoadField v35, :block@0x1005
- Jump bb7(v40, v40)
+ v39:BasicObject = LoadField v35, :block@0x1005
+ Jump bb7(v39, v39)
bb6():
- v42:CInt64 = LoadField v35, :_env_data_index_specval@0x1006
- v43:CInt64 = GuardAnyBitSet v42, CUInt64(1)
- v44:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
- Jump bb7(v44, v22)
+ v41:CInt64 = LoadField v35, :_env_data_index_specval@0x1006
+ v42:CInt64 = GuardAnyBitSet v41, CUInt64(1)
+ v43:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1008))
+ Jump bb7(v43, v22)
bb7(v33:BasicObject, v34:BasicObject):
CheckInterrupts
- v48:CBool = Test v33
- v49:Falsy = RefineType v33, Falsy
- IfFalse v48, bb4(v18, v19, v20, v21, v34, v27)
- v51:Truthy = RefineType v33, Truthy
- v55:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized
- v58:BasicObject = InvokeBuiltin dir_s_close, v18, v27
+ v47:CBool = Test v33
+ v48:Falsy = RefineType v33, Falsy
+ CondBranch v47, bb8(), bb4(v18, v19, v20, v21, v34, v27)
+ bb8():
+ v50:Truthy = RefineType v33, Truthy
+ v54:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized
+ v57:BasicObject = InvokeBuiltin dir_s_close, v18, v27
CheckInterrupts
- Return v55
- bb4(v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject, v69:BasicObject):
+ Return v54
+ bb4(v63:BasicObject, v64:BasicObject, v65:BasicObject, v66:BasicObject, v67:BasicObject, v68:BasicObject):
CheckInterrupts
- Return v69
+ Return v68
");
}
@@ -4836,15 +4842,16 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v26:CBool = Test v22
v27:Truthy = RefineType v22, Truthy
- IfTrue v26, bb4(v9, v10, v14, v10, v17, v19, v27)
+ CondBranch v26, bb4(v9, v10, v14, v10, v17, v19, v27), bb5()
+ bb4(v41:BasicObject, v42:BasicObject, v43:NilClass, v44:BasicObject, v45:Fixnum[0], v46:Fixnum[1], v47:Truthy):
+ CheckInterrupts
+ Return v47
+ bb5():
v29:Falsy = RefineType v22, Falsy
v32:Fixnum[2] = Const Value(2)
v35:BasicObject = Send v10, :[]=, v17, v19, v32 # SendFallbackReason: Uncategorized(opt_send_without_block)
CheckInterrupts
Return v32
- bb4(v41:BasicObject, v42:BasicObject, v43:NilClass, v44:BasicObject, v45:Fixnum[0], v46:Fixnum[1], v47:Truthy):
- CheckInterrupts
- Return v47
");
}
@@ -5214,7 +5221,8 @@ pub(crate) mod hir_build_tests {
CheckInterrupts
v20:CBool = Test v17
v21:TrueClass = RefineType v17, Truthy
- IfTrue v20, bb4(v12, v13, v14)
+ CondBranch v20, bb4(v12, v13, v14), bb5()
+ bb5():
v23:FalseClass = RefineType v17, Falsy
v25:Fixnum[1] = Const Value(1)
v27:Fixnum[1] = Const Value(1)
@@ -5342,15 +5350,10 @@ pub(crate) mod hir_build_tests {
v15:TrueClass|NilClass = Defined yield, v13
v17:CBool = Test v15
v18:NilClass = RefineType v15, Falsy
- IfFalse v17, bb4(v8, v9)
+ CondBranch v17, bb9(), bb4(v8, v9)
+ bb9():
v20:TrueClass = RefineType v15, Truthy
Jump bb6(v8, v9)
- bb4(v23:BasicObject, v24:NilClass):
- v28:BasicObject = InvokeBuiltin , v23
- Jump bb5(v23, v24, v28)
- bb5(v40:BasicObject, v41:NilClass, v42:BasicObject):
- CheckInterrupts
- Return v42
bb6(v30:BasicObject, v31:NilClass):
v35:Fixnum[0] = Const Value(0)
Jump bb8(v30, v35)
@@ -5358,7 +5361,8 @@ pub(crate) mod hir_build_tests {
v52:BoolExact = InvokeBuiltin rb_jit_ary_at_end, v48, v49
v54:CBool = Test v52
v55:FalseClass = RefineType v52, Falsy
- IfFalse v54, bb7(v48, v49)
+ CondBranch v54, bb10(), bb7(v48, v49)
+ bb10():
v57:TrueClass = RefineType v52, Truthy
v59:NilClass = Const Value(nil)
CheckInterrupts
@@ -5369,6 +5373,12 @@ pub(crate) mod hir_build_tests {
v78:Fixnum = InvokeBuiltin rb_jit_fixnum_inc, v67, v68
PatchPoint NoEPEscape(each)
Jump bb8(v67, v78)
+ bb4(v23:BasicObject, v24:NilClass):
+ v28:BasicObject = InvokeBuiltin , v23
+ Jump bb5(v23, v24, v28)
+ bb5(v40:BasicObject, v41:NilClass, v42:BasicObject):
+ CheckInterrupts
+ Return v42
");
}
@@ -5560,8 +5570,7 @@ pub(crate) mod hir_build_tests {
let bb3 = function.new_block(0);
let v1 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb0, Insn::IfTrue { val: v1, target: edge(bb2)});
- function.push_insn(bb0, Insn::Jump(edge(bb1)));
+ let _ = function.push_insn(bb0, Insn::CondBranch { val: v1, if_true: edge(bb2), if_false: edge(bb1) });
function.push_insn(bb1, Insn::Jump(edge(bb3)));
function.push_insn(bb2, Insn::Jump(edge(bb3)));
@@ -5587,8 +5596,7 @@ pub(crate) mod hir_build_tests {
// 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 _ = function.push_insn(bb0, Insn::CondBranch { val: v1, if_true: edge(bb1), if_false: edge(bb1)});
let retval = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb1, Insn::Return { val: retval });
@@ -5668,8 +5676,7 @@ pub(crate) mod hir_build_tests {
let bb3 = function.new_block(0);
let val = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb0, Insn::IfTrue { val, target: edge(bb1)});
- function.push_insn(bb0, Insn::Jump(edge(bb2)));
+ let _ = function.push_insn(bb0, Insn::CondBranch { val, if_true: edge(bb1), if_false: edge(bb2) });
function.push_insn(bb2, Insn::Jump(edge(bb3)));
function.push_insn(bb1, Insn::Jump(edge(bb3)));
@@ -5682,15 +5689,14 @@ pub(crate) mod hir_build_tests {
fn :
bb1():
v0:Any = Const Value(false)
- IfTrue v0, bb2()
- Jump bb3()
+ CondBranch v0, bb2(), bb3()
bb2():
Jump bb4()
bb3():
Jump bb4()
bb4():
- v5:Any = Const CBool(true)
- Return v5
+ v4:Any = Const CBool(true)
+ Return v4
");
let dominators = Dominators::new(&function);
@@ -5718,14 +5724,12 @@ pub(crate) mod hir_build_tests {
function.push_insn(bb0, Insn::Jump(edge(bb1)));
let v0 = function.push_insn(bb1, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb1, Insn::IfTrue { val: v0, target: edge(bb2)});
- function.push_insn(bb1, Insn::Jump(edge(bb4)));
+ let _ = function.push_insn(bb1, Insn::CondBranch { val: v0, if_true: edge(bb2), if_false: edge(bb4) });
function.push_insn(bb2, Insn::Jump(edge(bb3)));
let v1 = function.push_insn(bb3, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb3, Insn::IfTrue { val: v1, target: edge(bb5)});
- function.push_insn(bb3, Insn::Jump(edge(bb7)));
+ let _ = function.push_insn(bb3, Insn::CondBranch { val: v1, if_true: edge(bb5), if_false: edge(bb7) });
function.push_insn(bb4, Insn::Jump(edge(bb5)));
@@ -5743,14 +5747,12 @@ pub(crate) mod hir_build_tests {
Jump bb2()
bb2():
v1:Any = Const Value(false)
- IfTrue v1, bb3()
- Jump bb5()
+ CondBranch v1, bb3(), bb5()
bb3():
Jump bb4()
bb4():
- v5:Any = Const Value(false)
- IfTrue v5, bb6()
- Jump bb8()
+ v4:Any = Const Value(false)
+ CondBranch v4, bb6(), bb8()
bb5():
Jump bb6()
bb6():
@@ -5758,8 +5760,8 @@ pub(crate) mod hir_build_tests {
bb7():
Jump bb8()
bb8():
- v11:Any = Const CBool(true)
- Return v11
+ v9:Any = Const CBool(true)
+ Return v9
");
let dominators = Dominators::new(&function);
@@ -5787,20 +5789,17 @@ pub(crate) mod hir_build_tests {
let bb5 = function.new_block(0);
let v0 = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb0, Insn::IfTrue { val: v0, target: edge(bb1)});
- function.push_insn(bb0, Insn::Jump(edge(bb4)));
+ let _ = function.push_insn(bb0, Insn::CondBranch { val: v0, if_true: edge(bb1), if_false: edge(bb4) });
let v1 = function.push_insn(bb1, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb1, Insn::IfTrue { val: v1, target: edge(bb2)});
- function.push_insn(bb1, Insn::Jump(edge(bb3)));
+ let _ = function.push_insn(bb1, Insn::CondBranch { val: v1, if_true: edge(bb2), if_false: edge(bb3) });
function.push_insn(bb2, Insn::Jump(edge(bb3)));
function.push_insn(bb4, Insn::Jump(edge(bb5)));
let v2 = function.push_insn(bb5, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb5, Insn::IfTrue { val: v2, target: edge(bb3)});
- function.push_insn(bb5, Insn::Jump(edge(bb4)));
+ let _ = function.push_insn(bb5, Insn::CondBranch { val: v2, if_true: edge(bb3), if_false: edge(bb4) });
let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb3, Insn::Return { val: retval });
@@ -5810,23 +5809,20 @@ pub(crate) mod hir_build_tests {
fn :
bb1():
v0:Any = Const Value(false)
- IfTrue v0, bb2()
- Jump bb5()
+ CondBranch v0, bb2(), bb5()
bb2():
- v3:Any = Const Value(false)
- IfTrue v3, bb3()
- Jump bb4()
+ v2:Any = Const Value(false)
+ CondBranch v2, bb3(), bb4()
bb3():
Jump bb4()
bb5():
Jump bb6()
bb6():
- v8:Any = Const Value(false)
- IfTrue v8, bb4()
- Jump bb5()
+ v6:Any = Const Value(false)
+ CondBranch v6, bb4(), bb5()
bb4():
- v11:Any = Const CBool(true)
- Return v11
+ v8:Any = Const CBool(true)
+ Return v8
");
let dominators = Dominators::new(&function);
@@ -5891,26 +5887,31 @@ mod loop_info_tests {
#[test]
fn test_loop_depth() {
- // ┌─────┐
- // │ bb0 │
- // └──┬──┘
- // │
- // ┌──▼──┐ ┌─────┐
- // │ bb2 ◄──────┼ bb1 ◄─┐
- // └──┬──┘ └─────┘ │
- // └─────────────────┘
+ // ┌─────┐
+ // │ bb0 │
+ // └──┬──┘
+ // │
+ // ┌──▼──┐ ┌─────┐
+ // ┌►│ bb2 ├─────►│ bb1 │
+ // │ └──┬──┘ T └──┬──┘
+ // │ F│ │
+ // │ ┌──▼──┐ │
+ // │ │ bb3 │ │
+ // │ └─────┘ │
+ // └─────────────────┘
let mut function = Function::new(std::ptr::null());
let bb0 = function.entry_block;
let bb1 = function.new_block(0);
let bb2 = function.new_block(0);
+ let bb3 = function.new_block(0);
function.push_insn(bb0, Insn::Jump(edge(bb2)));
- let val = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb2, Insn::IfTrue { val, target: edge(bb1)});
- let retval = function.push_insn(bb2, Insn::Const { val: Const::CBool(true) });
- let _ = function.push_insn(bb2, Insn::Return { val: retval });
+ let val = function.push_insn(bb2, Insn::Const { val: Const::Value(Qfalse) });
+ let _ = function.push_insn(bb2, Insn::CondBranch { val, if_true: edge(bb1), if_false: edge(bb3) });
+ let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) });
+ let _ = function.push_insn(bb3, Insn::Return { val: retval });
function.push_insn(bb1, Insn::Jump(edge(bb2)));
@@ -5923,13 +5924,14 @@ mod loop_info_tests {
fn :
bb1():
Jump bb3()
- v1:Any = Const Value(false)
bb3():
- IfTrue v1, bb2()
- v3:Any = Const CBool(true)
- Return v3
+ v1:Any = Const Value(false)
+ CondBranch v1, bb2(), bb4()
bb2():
Jump bb3()
+ bb4():
+ v3:Any = Const CBool(true)
+ Return v3
");
assert!(loop_info.is_loop_header(bb2));
@@ -5971,12 +5973,10 @@ mod loop_info_tests {
function.push_insn(bb1, Insn::Jump(edge(bb2)));
let cond = function.push_insn(bb2, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb2, Insn::IfTrue { val: cond, target: edge(bb1) });
- function.push_insn(bb2, Insn::Jump(edge(bb3)));
+ let _ = function.push_insn(bb2, Insn::CondBranch { val: cond, if_true: edge(bb1), if_false: edge(bb3) });
let cond = function.push_insn(bb3, Insn::Const { val: Const::Value(Qtrue) });
- let _ = function.push_insn(bb3, Insn::IfTrue { val: cond, target: edge(bb0) });
- function.push_insn(bb3, Insn::Jump(edge(bb4)));
+ let _ = function.push_insn(bb3, Insn::CondBranch { val: cond, if_true: edge(bb0), if_false: edge(bb4) });
let retval = function.push_insn(bb4, Insn::Const { val: Const::CBool(true) });
let _ = function.push_insn(bb4, Insn::Return { val: retval });
@@ -5994,15 +5994,13 @@ mod loop_info_tests {
Jump bb3()
bb3():
v2:Any = Const Value(false)
- IfTrue v2, bb2()
- Jump bb4()
+ CondBranch v2, bb2(), bb4()
bb4():
- v5:Any = Const Value(true)
- IfTrue v5, bb1()
- Jump bb5()
+ v4:Any = Const Value(true)
+ CondBranch v4, bb1(), bb5()
bb5():
- v8:Any = Const CBool(true)
- Return v8
+ v6:Any = Const CBool(true)
+ Return v6
");
assert!(loop_info.is_loop_header(bb0));
@@ -6050,21 +6048,17 @@ mod loop_info_tests {
let bb6 = function.new_block(0);
let cond = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb0, Insn::IfTrue { val: cond, target: edge(bb1) });
- function.push_insn(bb0, Insn::Jump(edge(bb3)));
+ let _ = function.push_insn(bb0, Insn::CondBranch { val: cond, if_true: edge(bb1), if_false: edge(bb3) });
function.push_insn(bb1, Insn::Jump(edge(bb2)));
- let _ = function.push_insn(bb2, Insn::IfTrue { val: cond, target: edge(bb1) });
- function.push_insn(bb2, Insn::Jump(edge(bb5)));
+ let _ = function.push_insn(bb2, Insn::CondBranch { val: cond, if_true: edge(bb1), if_false: edge(bb5) });
function.push_insn(bb3, Insn::Jump(edge(bb4)));
- let _ = function.push_insn(bb4, Insn::IfTrue { val: cond, target: edge(bb3) });
- function.push_insn(bb4, Insn::Jump(edge(bb5)));
+ let _ = function.push_insn(bb4, Insn::CondBranch { val: cond, if_true: edge(bb3), if_false: edge(bb5) });
- let _ = function.push_insn(bb5, Insn::IfTrue { val: cond, target: edge(bb0) });
- function.push_insn(bb5, Insn::Jump(edge(bb6)));
+ let _ = function.push_insn(bb5, Insn::CondBranch { val: cond, if_true: edge(bb0), if_false: edge(bb6) });
let retval = function.push_insn(bb6, Insn::Const { val: Const::CBool(true) });
let _ = function.push_insn(bb6, Insn::Return { val: retval });
@@ -6078,24 +6072,20 @@ mod loop_info_tests {
fn :
bb1():
v0:Any = Const Value(false)
- IfTrue v0, bb2()
- Jump bb4()
+ CondBranch v0, bb2(), bb4()
bb2():
Jump bb3()
bb3():
- IfTrue v0, bb2()
- Jump bb6()
+ CondBranch v0, bb2(), bb6()
bb4():
Jump bb5()
bb5():
- IfTrue v0, bb4()
- Jump bb6()
+ CondBranch v0, bb4(), bb6()
bb6():
- IfTrue v0, bb1()
- Jump bb7()
+ CondBranch v0, bb1(), bb7()
bb7():
- v11:Any = Const CBool(true)
- Return v11
+ v7:Any = Const CBool(true)
+ Return v7
");
assert!(loop_info.is_loop_header(bb0));
@@ -6206,16 +6196,16 @@ mod loop_info_tests {
let bb3 = function.new_block(0);
let bb4 = function.new_block(0);
let bb5 = function.new_block(0);
+ let bb6 = function.new_block(0);
let cond = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
let _ = function.push_insn(bb0, Insn::Jump(edge(bb1)));
let _ = function.push_insn(bb1, Insn::Jump(edge(bb2)));
let _ = function.push_insn(bb2, Insn::Jump(edge(bb3)));
- let _ = function.push_insn(bb3, Insn::Jump(edge(bb4)));
- let _ = function.push_insn(bb3, Insn::IfTrue {val: cond, target: edge(bb2)});
- let _ = function.push_insn(bb4, Insn::Jump(edge(bb5)));
- let _ = function.push_insn(bb4, Insn::IfTrue {val: cond, target: edge(bb1)});
- let _ = function.push_insn(bb5, Insn::IfTrue {val: cond, target: edge(bb0)});
+ let _ = function.push_insn(bb3, Insn::CondBranch {val: cond, if_true: edge(bb2), if_false: edge(bb4) });
+ let _ = function.push_insn(bb4, Insn::CondBranch {val: cond, if_true: edge(bb1), if_false: edge(bb5) });
+ let _ = function.push_insn(bb5, Insn::CondBranch {val: cond, if_true: edge(bb0), if_false: edge(bb6) });
+ function.push_insn(bb6, Insn::Unreachable);
function.seal_entries();
assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @"
@@ -6228,13 +6218,13 @@ mod loop_info_tests {
bb3():
Jump bb4()
bb4():
- Jump bb5()
- IfTrue v0, bb3()
+ CondBranch v0, bb3(), bb5()
bb5():
- Jump bb6()
- IfTrue v0, bb2()
+ CondBranch v0, bb2(), bb6()
bb6():
- IfTrue v0, bb1()
+ CondBranch v0, bb1(), bb7()
+ bb7():
+ Unreachable
");
let cfi = ControlFlowInfo::new(&function);
@@ -6281,9 +6271,10 @@ mod iongraph_tests {
let retval = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb0, Insn::Return { val: retval });
+ function.seal_entries();
let json = function.to_iongraph_pass("simple");
- assert_snapshot!(json.to_string(), @r#"{"name":"simple", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[]}]}, "lir":{"blocks":[]}}"#);
+ assert_snapshot!(json.to_string(), @r#"{"name":"simple", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4098, "id":2, "opcode":"Entries bb1", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"Return v0", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#);
}
#[test]
@@ -6297,8 +6288,9 @@ mod iongraph_tests {
let retval = function.push_insn(bb1, Insn::Const { val: Const::CBool(false) });
function.push_insn(bb1, Insn::Return { val: retval });
+ function.seal_entries();
let json = function.to_iongraph_pass("two_blocks");
- assert_snapshot!(json.to_string(), @r#"{"name":"two_blocks", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[]}]}, "lir":{"blocks":[]}}"#);
+ assert_snapshot!(json.to_string(), @r#"{"name":"two_blocks", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4099, "id":3, "opcode":"Entries bb1", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[2], "instructions":[{"ptr":4096, "id":0, "opcode":"Jump bb2()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4098, "id":2, "loopDepth":0, "attributes":[], "predecessors":[1], "successors":[], "instructions":[{"ptr":4097, "id":1, "opcode":"Const CBool(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4098, "id":2, "opcode":"Return v1", "attributes":[], "inputs":[1], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#);
}
#[test]
@@ -6309,8 +6301,9 @@ mod iongraph_tests {
let val1 = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb0, Insn::Return { val: val1 });
+ function.seal_entries();
let json = function.to_iongraph_pass("multiple_instructions");
- assert_snapshot!(json.to_string(), @r#"{"name":"multiple_instructions", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[]}]}, "lir":{"blocks":[]}}"#);
+ assert_snapshot!(json.to_string(), @r#"{"name":"multiple_instructions", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4098, "id":2, "opcode":"Entries bb1", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"Return v0", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#);
}
#[test]
@@ -6318,18 +6311,20 @@ mod iongraph_tests {
let mut function = Function::new(std::ptr::null());
let bb0 = function.entry_block;
let bb1 = function.new_block(0);
+ let bb2 = function.new_block(0);
let cond = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) });
- function.push_insn(bb0, Insn::IfTrue { val: cond, target: edge(bb1) });
+ function.push_insn(bb0, Insn::CondBranch { val: cond, if_true: edge(bb1), if_false: edge(bb2) });
- let retval1 = function.push_insn(bb0, Insn::Const { val: Const::CBool(false) });
- function.push_insn(bb0, Insn::Return { val: retval1 });
+ let retval1 = function.push_insn(bb2, Insn::Const { val: Const::CBool(false) });
+ function.push_insn(bb2, Insn::Return { val: retval1 });
let retval2 = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb1, Insn::Return { val: retval2 });
+ function.seal_entries();
let json = function.to_iongraph_pass("conditional_branch");
- assert_snapshot!(json.to_string(), @r#"{"name":"conditional_branch", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[]}]}, "lir":{"blocks":[]}}"#);
+ assert_snapshot!(json.to_string(), @r#"{"name":"conditional_branch", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4102, "id":6, "opcode":"Entries bb1", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[2, 3], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"CondBranch v0, bb2(), bb3()", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4098, "id":2, "loopDepth":0, "attributes":[], "predecessors":[1], "successors":[], "instructions":[{"ptr":4100, "id":4, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4101, "id":5, "opcode":"Return v4", "attributes":[], "inputs":[4], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4099, "id":3, "loopDepth":0, "attributes":[], "predecessors":[1], "successors":[], "instructions":[{"ptr":4098, "id":2, "opcode":"Const CBool(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4099, "id":3, "opcode":"Return v2", "attributes":[], "inputs":[2], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#);
}
#[test]
@@ -6339,18 +6334,20 @@ mod iongraph_tests {
let bb0 = function.entry_block;
let bb1 = function.new_block(0);
let bb2 = function.new_block(0);
+ let bb3 = function.new_block(0);
function.push_insn(bb0, Insn::Jump(edge(bb2)));
- let val = function.push_insn(bb0, Insn::Const { val: Const::Value(Qfalse) });
- let _ = function.push_insn(bb2, Insn::IfTrue { val, target: edge(bb1)});
- let retval = function.push_insn(bb2, Insn::Const { val: Const::CBool(true) });
- let _ = function.push_insn(bb2, Insn::Return { val: retval });
+ let val = function.push_insn(bb2, Insn::Const { val: Const::Value(Qfalse) });
+ let _ = function.push_insn(bb2, Insn::CondBranch { val, if_true: edge(bb1), if_false: edge(bb3) });
+ let retval = function.push_insn(bb3, Insn::Const { val: Const::CBool(true) });
+ let _ = function.push_insn(bb3, Insn::Return { val: retval });
function.push_insn(bb1, Insn::Jump(edge(bb2)));
+ function.seal_entries();
let json = function.to_iongraph_pass("loop_structure");
- assert_snapshot!(json.to_string(), @r#"{"name":"loop_structure", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[]}]}, "lir":{"blocks":[]}}"#);
+ assert_snapshot!(json.to_string(), @r#"{"name":"loop_structure", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4102, "id":6, "opcode":"Entries bb1", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[3], "instructions":[{"ptr":4096, "id":0, "opcode":"Jump bb3()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4099, "id":3, "loopDepth":1, "attributes":["loopheader"], "predecessors":[1, 2], "successors":[2, 4], "instructions":[{"ptr":4097, "id":1, "opcode":"Const Value(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4098, "id":2, "opcode":"CondBranch v1, bb2(), bb4()", "attributes":[], "inputs":[1], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4098, "id":2, "loopDepth":1, "attributes":["backedge"], "predecessors":[3], "successors":[3], "instructions":[{"ptr":4101, "id":5, "opcode":"Jump bb3()", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4100, "id":4, "loopDepth":0, "attributes":[], "predecessors":[3], "successors":[], "instructions":[{"ptr":4099, "id":3, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4100, "id":4, "opcode":"Return v3", "attributes":[], "inputs":[3], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#);
}
#[test]
@@ -6361,8 +6358,7 @@ mod iongraph_tests {
let bb2 = function.new_block(0);
let cond = function.push_insn(bb0, Insn::Const { val: Const::CBool(true) });
- function.push_insn(bb0, Insn::IfTrue { val: cond, target: edge(bb1) });
- function.push_insn(bb0, Insn::Jump(edge(bb2)));
+ function.push_insn(bb0, Insn::CondBranch { val: cond, if_true: edge(bb1), if_false: edge(bb2) });
let retval1 = function.push_insn(bb1, Insn::Const { val: Const::CBool(true) });
function.push_insn(bb1, Insn::Return { val: retval1 });
@@ -6370,7 +6366,8 @@ mod iongraph_tests {
let retval2 = function.push_insn(bb2, Insn::Const { val: Const::CBool(false) });
function.push_insn(bb2, Insn::Return { val: retval2 });
+ function.seal_entries();
let json = function.to_iongraph_pass("multiple_successors");
- assert_snapshot!(json.to_string(), @r#"{"name":"multiple_successors", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[], "instructions":[]}]}, "lir":{"blocks":[]}}"#);
+ assert_snapshot!(json.to_string(), @r#"{"name":"multiple_successors", "mir":{"blocks":[{"ptr":4096, "id":0, "loopDepth":0, "attributes":[], "predecessors":[], "successors":[1], "instructions":[{"ptr":4102, "id":6, "opcode":"Entries bb1", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4097, "id":1, "loopDepth":0, "attributes":[], "predecessors":[0], "successors":[2, 3], "instructions":[{"ptr":4096, "id":0, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4097, "id":1, "opcode":"CondBranch v0, bb2(), bb3()", "attributes":[], "inputs":[0], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4098, "id":2, "loopDepth":0, "attributes":[], "predecessors":[1], "successors":[], "instructions":[{"ptr":4098, "id":2, "opcode":"Const CBool(true)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4099, "id":3, "opcode":"Return v2", "attributes":[], "inputs":[2], "uses":[], "memInputs":[], "type":""}]}, {"ptr":4099, "id":3, "loopDepth":0, "attributes":[], "predecessors":[1], "successors":[], "instructions":[{"ptr":4100, "id":4, "opcode":"Const CBool(false)", "attributes":[], "inputs":[], "uses":[], "memInputs":[], "type":"Any"}, {"ptr":4101, "id":5, "opcode":"Return v4", "attributes":[], "inputs":[4], "uses":[], "memInputs":[], "type":""}]}]}, "lir":{"blocks":[]}}"#);
}
}
From 6297af9883baa274a316eca77a9333e7c8b440d3 Mon Sep 17 00:00:00 2001
From: Max Bernstein
Date: Tue, 12 May 2026 19:19:15 -0400
Subject: [PATCH 3/4] ZJIT: Remove GuardType deduplication (#16927)
Now that ece14b61f505eea1ebefb3b8295df0fcf4d22567 has landed, we get
this for free.
We will eventually add value numbering, which will do more, but for now
remove this ad-hoc code.
---
zjit/src/hir.rs | 19 -------------------
1 file changed, 19 deletions(-)
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index f3f6f73e3f6b38..2e72737ca1ec8e 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -5018,10 +5018,6 @@ impl Function {
for block in self.rpo() {
let old_insns = std::mem::take(&mut self.blocks[block.0].insns);
let mut new_insns = vec![];
- // Track guards seen so far in this block: (val, guard_type, result_insn_id).
- // When we encounter a GuardType whose (val, guard_type) pair is already covered
- // by a previous guard, we can eliminate it by reusing the earlier result.
- let mut seen_guards: Vec<(InsnId, Type, InsnId)> = vec![];
for insn_id in old_insns {
let replacement_id = match self.find(insn_id) {
Insn::GuardType { val, guard_type, .. } if self.is_a(val, guard_type) => {
@@ -5029,21 +5025,6 @@ impl Function {
// Don't bother re-inferring the type of val; we already know it.
continue;
}
- // Deduplicate GuardType: if we already guarded the same val with a
- // type that is the same or narrower, the new guard is redundant.
- // e.g. if we already proved val is Fixnum, a later Fixnum or
- // BasicObject guard on the same val is guaranteed to pass.
- // TODO: Move into global value numbering
- Insn::GuardType { val, guard_type, .. } => {
- if let Some(&(_, _, prev_result)) = seen_guards.iter().find(
- |&&(prev_val, prev_type, _)| prev_val == val && prev_type.is_subtype(guard_type)
- ) {
- self.make_equal_to(insn_id, prev_result);
- continue;
- }
- seen_guards.push((val, guard_type, insn_id));
- insn_id
- }
Insn::LoadField { recv, offset, return_type, .. } if return_type.is_subtype(types::BasicObject) &&
u32::try_from(offset).is_ok() => {
let offset = (offset as u32).to_usize();
From baec5bbfffb38083b4180240a0319d20c9afc2a7 Mon Sep 17 00:00:00 2001
From: Peter Zhu
Date: Mon, 11 May 2026 19:50:07 -0400
Subject: [PATCH 4/4] Fix GC compaction for compare-by-identity sets
[Bug #22064]
Compare-by-identity sets use the address for hashing, so we must pin it
so the object does not move in GC compaction. Objects in a compare-by-identity
set is not currently pinned, causing the set to be broken if the object
is moved.
For example:
set = Set.new.compare_by_identity
o = Object.new
set.add(o)
puts set.include?(o)
GC.verify_compaction_references(expand_heap: true, toward: :empty)
puts set.include?(o)
It should output true twice, but it outputs true and false.
---
set.c | 17 ++++++++++++++++-
test/ruby/test_set.rb | 19 +++++++++++++++++++
2 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/set.c b/set.c
index 4d2bc997fa24b6..1a5d0e26fa4925 100644
--- a/set.c
+++ b/set.c
@@ -123,6 +123,14 @@ struct set_object {
set_table table;
};
+static int
+mark_and_pin_key(st_data_t key, st_data_t data)
+{
+ rb_gc_mark((VALUE)key);
+
+ return ST_CONTINUE;
+}
+
static int
mark_key(st_data_t key, st_data_t data)
{
@@ -135,7 +143,14 @@ static void
set_mark(void *ptr)
{
struct set_object *sobj = ptr;
- if (sobj->table.entries) set_table_foreach(&sobj->table, mark_key, 0);
+ if (sobj->table.entries) {
+ if (sobj->table.type == &identhash) {
+ set_table_foreach(&sobj->table, mark_and_pin_key, 0);
+ }
+ else {
+ set_table_foreach(&sobj->table, mark_key, 0);
+ }
+ }
}
static void
diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb
index 46d649ee737b0a..427dd4b6b0977b 100644
--- a/test/ruby/test_set.rb
+++ b/test/ruby/test_set.rb
@@ -902,6 +902,25 @@ def test_compare_by_identity
assert_equal(array.uniq.sort, set.sort)
end
+ def test_compare_by_identity_compact
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ # [Bug #22064]
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ set = Set.new.compare_by_identity
+
+ o = Object.new
+ set.add(o)
+
+ assert_include(set, o)
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_include(set, o)
+ end;
+ end
+
def test_reset
[Set, Class.new(Set)].each { |klass|
a = [1, 2]