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] 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/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 6b2d9ee7e3c046..2e72737ca1ec8e 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 { @@ -4144,6 +4162,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 +4219,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 +4510,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 +4843,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 +4957,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. @@ -4940,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) => { @@ -4951,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(); @@ -5187,16 +5246,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 @@ -5281,22 +5336,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; @@ -5315,6 +5356,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()); } } @@ -5405,22 +5449,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 @@ -5668,35 +5698,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(()) } @@ -5729,25 +5769,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() => { @@ -6005,8 +6045,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), @@ -7062,10 +7101,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); @@ -7084,10 +7129,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); @@ -7105,10 +7157,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); @@ -7134,10 +7192,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 @@ -7295,8 +7356,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); @@ -7389,20 +7453,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); } @@ -7415,19 +7488,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]; @@ -7437,7 +7514,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 }); } } @@ -7465,14 +7542,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); @@ -7729,7 +7803,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 @@ -7996,7 +8076,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, @@ -8072,7 +8161,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] })); } @@ -8293,13 +8389,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; } @@ -8313,10 +8410,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 @@ -8569,34 +8672,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 { @@ -8607,8 +8686,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 { @@ -8633,16 +8711,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> { @@ -8793,8 +8861,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]); } @@ -8808,10 +8876,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]); } @@ -8825,10 +8896,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]); } @@ -8874,6 +8948,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)); } @@ -8884,7 +8959,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)); } @@ -8895,7 +8977,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)); } @@ -8907,6 +8996,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)); } @@ -8918,6 +9008,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)); } @@ -8930,6 +9021,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)); } @@ -8944,9 +9036,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(); @@ -8964,9 +9062,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(); @@ -9033,6 +9136,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); } @@ -9042,6 +9146,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)); @@ -9054,6 +9159,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)); @@ -9066,6 +9172,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)); @@ -9098,15 +9205,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] @@ -9165,14 +9276,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 41fcfb16986ed4..0976ae659e6260 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 @@ -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 "); } @@ -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 "); } @@ -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 @@ -5683,7 +5693,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 +6369,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 @@ -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 "); } @@ -10089,7 +10114,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 "); @@ -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) @@ -14643,7 +14670,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 "); @@ -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)] @@ -15257,7 +15300,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) @@ -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 "); } @@ -15360,7 +15405,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 +15453,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 +15501,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 @@ -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 "); @@ -15722,6 +15771,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 @@ -15762,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 @@ -15775,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): @@ -15812,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":[]}}"#); } } 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,