cranelift: Add a conditional branch instruction with two targets (#5446)

Add a conditional branch instruction with two targets: brif. This instruction will eventually replace brz and brnz, as it encompasses the behavior of both.

This PR also changes the InstructionData layout for instruction formats that hold BlockCall values, taking the same approach we use for Value arguments. This allows branch_destination to return a slice to the BlockCall values held in the instruction, rather than requiring that we pattern match on InstructionData to fetch the then/else blocks.

Function generation for fuzzing has been updated to generate uses of brif, and I've run the cranelift-fuzzgen target locally for hours without triggering any new failures.
This commit is contained in:
Trevor Elliott
2023-01-24 14:37:16 -08:00
committed by GitHub
parent ec6922ff24
commit b58a197d33
35 changed files with 943 additions and 159 deletions

View File

@@ -356,6 +356,10 @@ impl DominatorTree {
BranchInfo::SingleDest(succ) => {
self.push_if_unseen(succ.block(&func.dfg.value_lists))
}
BranchInfo::Conditional(block_then, block_else) => {
self.push_if_unseen(block_then.block(&func.dfg.value_lists));
self.push_if_unseen(block_else.block(&func.dfg.value_lists));
}
BranchInfo::Table(jt, dest) => {
for succ in func.jump_tables[jt].iter() {
self.push_if_unseen(*succ);

View File

@@ -125,6 +125,10 @@ impl ControlFlowGraph {
BranchInfo::SingleDest(dest) => {
self.add_edge(block, inst, dest.block(&func.dfg.value_lists));
}
BranchInfo::Conditional(block_then, block_else) => {
self.add_edge(block, inst, block_then.block(&func.dfg.value_lists));
self.add_edge(block, inst, block_else.block(&func.dfg.value_lists));
}
BranchInfo::Table(jt, dest) => {
self.add_edge(block, inst, dest);

View File

@@ -176,25 +176,23 @@ pub(crate) fn visit_block_succs<F: FnMut(Inst, Block, bool)>(
mut visit: F,
) {
for inst in f.layout.block_likely_branches(block) {
if f.dfg.insts[inst].opcode().is_branch() {
visit_branch_targets(f, inst, &mut visit);
}
}
}
match f.dfg.insts[inst].analyze_branch() {
BranchInfo::NotABranch => {}
BranchInfo::SingleDest(dest) => {
visit(inst, dest.block(&f.dfg.value_lists), false);
}
BranchInfo::Conditional(block_then, block_else) => {
visit(inst, block_then.block(&f.dfg.value_lists), false);
visit(inst, block_else.block(&f.dfg.value_lists), false);
}
BranchInfo::Table(table, dest) => {
// The default block is reached via a direct conditional branch,
// so it is not part of the table.
visit(inst, dest, false);
fn visit_branch_targets<F: FnMut(Inst, Block, bool)>(f: &Function, inst: Inst, visit: &mut F) {
match f.dfg.insts[inst].analyze_branch() {
BranchInfo::NotABranch => {}
BranchInfo::SingleDest(dest) => {
visit(inst, dest.block(&f.dfg.value_lists), false);
}
BranchInfo::Table(table, dest) => {
// The default block is reached via a direct conditional branch,
// so it is not part of the table.
visit(inst, dest, false);
for &dest in f.jump_tables[table].as_slice() {
visit(inst, dest, true);
for &dest in f.jump_tables[table].as_slice() {
visit(inst, dest, true);
}
}
}
}

View File

@@ -691,9 +691,10 @@ impl DataFlowGraph {
self.inst_args_mut(inst)[i] = body(self, arg);
}
for mut block in self.insts[inst].branch_destination().into_iter() {
for block_ix in 0..self.insts[inst].branch_destination().len() {
// We aren't changing the size of the args list, so we won't need to write the branch
// back to the instruction.
let mut block = self.insts[inst].branch_destination()[block_ix];
for i in 0..block.args_slice(&self.value_lists).len() {
let arg = block.args_slice(&self.value_lists)[i];
block.args_slice_mut(&mut self.value_lists)[i] = body(self, arg);
@@ -712,7 +713,8 @@ impl DataFlowGraph {
*arg = values.next().unwrap();
}
for mut block in self.insts[inst].branch_destination().into_iter() {
for block_ix in 0..self.insts[inst].branch_destination().len() {
let mut block = self.insts[inst].branch_destination()[block_ix];
for arg in block.args_slice_mut(&mut self.value_lists) {
*arg = values.next().unwrap();
}

View File

@@ -277,27 +277,41 @@ impl FunctionStencil {
self.dfg.collect_debug_info();
}
/// Changes the destination of a jump or branch instruction.
/// Does nothing if called with a non-jump or non-branch instruction.
///
/// Note that this method ignores multi-destination branches like `br_table`.
pub fn change_branch_destination(&mut self, inst: Inst, new_dest: Block) {
match self.dfg.insts[inst].branch_destination_mut() {
None => (),
Some(inst_dest) => inst_dest.set_block(new_dest, &mut self.dfg.value_lists),
}
}
/// Rewrite the branch destination to `new_dest` if the destination matches `old_dest`.
/// Does nothing if called with a non-jump or non-branch instruction.
///
/// Unlike [change_branch_destination](FunctionStencil::change_branch_destination), this method
/// rewrite the destinations of multi-destination branches like `br_table`.
pub fn rewrite_branch_destination(&mut self, inst: Inst, old_dest: Block, new_dest: Block) {
match self.dfg.analyze_branch(inst) {
BranchInfo::SingleDest(dest) => {
if dest.block(&self.dfg.value_lists) == old_dest {
self.change_branch_destination(inst, new_dest);
for block in self.dfg.insts[inst].branch_destination_mut() {
block.set_block(new_dest, &mut self.dfg.value_lists)
}
}
}
BranchInfo::Conditional(block_then, block_else) => {
if block_then.block(&self.dfg.value_lists) == old_dest {
if let InstructionData::Brif {
blocks: [block_then, _],
..
} = &mut self.dfg.insts[inst]
{
block_then.set_block(new_dest, &mut self.dfg.value_lists);
} else {
unreachable!();
}
}
if block_else.block(&self.dfg.value_lists) == old_dest {
if let InstructionData::Brif {
blocks: [_, block_else],
..
} = &mut self.dfg.insts[inst]
{
block_else.set_block(new_dest, &mut self.dfg.value_lists);
} else {
unreachable!();
}
}
}

View File

@@ -271,6 +271,10 @@ impl InstructionData {
match *self {
Self::Jump { destination, .. } => BranchInfo::SingleDest(destination),
Self::Branch { destination, .. } => BranchInfo::SingleDest(destination),
Self::Brif {
blocks: [block_then, block_else],
..
} => BranchInfo::Conditional(block_then, block_else),
Self::BranchTable {
table, destination, ..
} => BranchInfo::Table(table, destination),
@@ -281,27 +285,31 @@ impl InstructionData {
}
}
/// Get the single destination of this branch instruction, if it is a single destination
/// branch or jump.
/// Get the destinations of this instruction, if it's a branch.
///
/// Multi-destination branches like `br_table` return `None`.
pub fn branch_destination(&self) -> Option<BlockCall> {
match *self {
Self::Jump { destination, .. } | Self::Branch { destination, .. } => Some(destination),
Self::BranchTable { .. } => None,
/// `br_table` returns the empty slice.
pub fn branch_destination(&self) -> &[BlockCall] {
match self {
Self::Jump {
ref destination, ..
}
| Self::Branch {
ref destination, ..
} => std::slice::from_ref(destination),
Self::Brif { blocks, .. } => blocks,
Self::BranchTable { .. } => &[],
_ => {
debug_assert!(!self.opcode().is_branch());
None
&[]
}
}
}
/// Get a mutable reference to the single destination of this branch instruction, if it is a
/// single destination branch or jump.
/// Get a mutable slice of the destinations of this instruction, if it's a branch.
///
/// Multi-destination branches like `br_table` return `None`.
pub fn branch_destination_mut(&mut self) -> Option<&mut BlockCall> {
match *self {
/// `br_table` returns the empty slice.
pub fn branch_destination_mut(&mut self) -> &mut [BlockCall] {
match self {
Self::Jump {
ref mut destination,
..
@@ -309,11 +317,12 @@ impl InstructionData {
| Self::Branch {
ref mut destination,
..
} => Some(destination),
Self::BranchTable { .. } => None,
} => std::slice::from_mut(destination),
Self::Brif { blocks, .. } => blocks,
Self::BranchTable { .. } => &mut [],
_ => {
debug_assert!(!self.opcode().is_branch());
None
&mut []
}
}
}
@@ -456,6 +465,9 @@ pub enum BranchInfo {
/// This is a branch or jump to a single destination block, possibly taking value arguments.
SingleDest(BlockCall),
/// This is a conditional branch
Conditional(BlockCall, BlockCall),
/// This is a jump table branch which can have many destination blocks and one default block.
Table(JumpTable, Block),
}

View File

@@ -2461,6 +2461,52 @@
(rule (lower (fvpromote_low val))
(vec_rr_long (VecRRLongOp.Fcvtl32) val $false))
;;; Rules for `brif` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; `brif` following `icmp`
(rule (lower_branch (brif (maybe_uextend (icmp cc x @ (value_type ty) y)) _ _) targets)
(let ((comparison FlagsAndCC (lower_icmp_into_flags cc x y ty))
(cond Cond (cond_code (flags_and_cc_cc comparison)))
(taken BranchTarget (branch_target targets 0))
(not_taken BranchTarget (branch_target targets 1)))
(emit_side_effect
(with_flags_side_effect (flags_and_cc_flags comparison)
(cond_br taken
not_taken
(cond_br_cond cond))))))
;; `brif` following `fcmp`
(rule (lower_branch (brif (maybe_uextend (fcmp cc x @ (value_type (ty_scalar_float ty)) y)) _ _) targets)
(let ((cond Cond (fp_cond_code cc))
(taken BranchTarget (branch_target targets 0))
(not_taken BranchTarget (branch_target targets 1)))
(emit_side_effect
(with_flags_side_effect (fpu_cmp (scalar_size ty) x y)
(cond_br taken not_taken
(cond_br_cond cond))))))
;; standard `brif`
(rule -1 (lower_branch (brif c @ (value_type $I128) _ _) targets)
(let ((flags ProducesFlags (flags_to_producesflags c))
(c ValueRegs (put_in_regs c))
(c_lo Reg (value_regs_get c 0))
(c_hi Reg (value_regs_get c 1))
(rt Reg (orr $I64 c_lo c_hi))
(taken BranchTarget (branch_target targets 0))
(not_taken BranchTarget (branch_target targets 1)))
(emit_side_effect
(with_flags_side_effect flags
(cond_br taken not_taken (cond_br_not_zero rt))))))
(rule -2 (lower_branch (brif c @ (value_type ty) _ _) targets)
(if (ty_int_ref_scalar_64 ty))
(let ((flags ProducesFlags (flags_to_producesflags c))
(rt Reg (put_in_reg_zext64 c))
(taken BranchTarget (branch_target targets 0))
(not_taken BranchTarget (branch_target targets 1)))
(emit_side_effect
(with_flags_side_effect flags
(cond_br taken not_taken (cond_br_not_zero rt))))))
;;; Rules for `brz`/`brnz` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; `brz` following `icmp`

View File

@@ -24,8 +24,8 @@ use crate::machinst::{isle::*, InputSourceInst};
use crate::{
binemit::CodeOffset,
ir::{
immediates::*, types::*, AtomicRmwOp, ExternalName, Inst, InstructionData, MemFlags,
TrapCode, Value, ValueList,
immediates::*, types::*, AtomicRmwOp, BlockCall, ExternalName, Inst, InstructionData,
MemFlags, TrapCode, Value, ValueList,
},
isa::aarch64::abi::AArch64Caller,
isa::aarch64::inst::args::{ShiftOp, ShiftOpShiftImm},

View File

@@ -1898,6 +1898,30 @@
(hi Reg (value_regs_get regs 1)))
(alu_rrr (AluOPRRR.Or) lo hi)))
;; Default behavior for branching based on an input value.
(rule
(lower_branch (brif v @ (value_type ty) _ _) targets)
(lower_brz_or_nz (IntCC.NotEqual) (normalize_cmp_value ty v) targets ty))
;; Special case for SI128 to reify the comparison value and branch on it.
(rule 2
(lower_branch (brif v @ (value_type $I128) _ _) targets)
(let ((zero ValueRegs (value_regs (zero_reg) (zero_reg)))
(cmp Reg (gen_icmp (IntCC.NotEqual) v zero $I128)))
(lower_brz_or_nz (IntCC.NotEqual) cmp targets $I64)))
;; Branching on the result of an icmp
(rule 1
(lower_branch (brif (icmp cc a @ (value_type ty) b) _ _) targets)
(lower_br_icmp cc a b targets ty))
;; Branching on the result of an fcmp
(rule 1
(lower_branch (brif (fcmp cc a @ (value_type ty) b) _ _) targets)
(let ((then BranchTarget (label_to_br_target (vec_label_get targets 0)))
(else BranchTarget (label_to_br_target (vec_label_get targets 1))))
(emit_side_effect (cond_br (emit_fcmp cc ty a b) then else))))
;;;;;
(rule
(lower_branch (brz v @ (value_type ty) _) targets)

View File

@@ -14,8 +14,8 @@ use crate::machinst::{isle::*, MachInst, SmallInstVec};
use crate::machinst::{VCodeConstant, VCodeConstantData};
use crate::{
ir::{
immediates::*, types::*, AtomicRmwOp, ExternalName, Inst, InstructionData, MemFlags,
StackSlot, TrapCode, Value, ValueList,
immediates::*, types::*, AtomicRmwOp, BlockCall, ExternalName, Inst, InstructionData,
MemFlags, StackSlot, TrapCode, Value, ValueList,
},
isa::riscv64::inst::*,
machinst::{ArgPair, InstOutput, Lower},

View File

@@ -3750,6 +3750,17 @@
(emit_side_effect (jt_sequence (lshl_imm $I64 idx 2) targets))))
;;;; Rules for `brif` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Two-way conditional branch on nonzero. `targets` contains:
;; - element 0: target if the condition is true (i.e. value is nonzero)
;; - element 1: target if the condition is false (i.e. value is zero)
(rule (lower_branch (brif val_cond _ _) targets)
(emit_side_effect (cond_br_bool (value_nonzero val_cond)
(vec_element targets 0)
(vec_element targets 1))))
;;;; Rules for `brz` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Two-way conditional branch on zero. `targets` contains:

View File

@@ -16,8 +16,8 @@ use crate::machinst::isle::*;
use crate::machinst::{MachLabel, Reg};
use crate::{
ir::{
condcodes::*, immediates::*, types::*, ArgumentPurpose, AtomicRmwOp, Endianness, Inst,
InstructionData, KnownSymbol, LibCall, MemFlags, Opcode, TrapCode, Value, ValueList,
condcodes::*, immediates::*, types::*, ArgumentPurpose, AtomicRmwOp, BlockCall, Endianness,
Inst, InstructionData, KnownSymbol, LibCall, MemFlags, Opcode, TrapCode, Value, ValueList,
},
isa::unwind::UnwindInst,
isa::CallConv,

View File

@@ -2885,6 +2885,25 @@
(rule (lower_branch (jump _) (single_target target))
(emit_side_effect (jmp_known target)))
;; Rules for `brif` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(rule 2 (lower_branch (brif (maybe_uextend (icmp cc a b)) _ _) (two_targets then else))
(emit_side_effect (jmp_cond_icmp (emit_cmp cc a b) then else)))
(rule 2 (lower_branch (brif (maybe_uextend (fcmp cc a b)) _ _) (two_targets then else))
(emit_side_effect (jmp_cond_fcmp (emit_fcmp cc a b) then else)))
(rule 1 (lower_branch (brif val @ (value_type $I128) _ _)
(two_targets then else))
(emit_side_effect (jmp_cond_icmp (cmp_zero_i128 (CC.Z) val) then else)))
(rule (lower_branch (brif val @ (value_type (ty_int_bool_or_ref)) _ _)
(two_targets then else))
(emit_side_effect (with_flags_side_effect
(cmp_zero_int_bool_ref val)
(jmp_cond (CC.NZ) then else))))
;; Rules for `brz` and `brnz` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(rule 2 (lower_branch (brz (maybe_uextend (icmp cc a b)) _) (two_targets taken not_taken))

View File

@@ -20,7 +20,7 @@ use crate::{
condcodes::{CondCode, FloatCC, IntCC},
immediates::*,
types::*,
Inst, InstructionData, MemFlags, Opcode, TrapCode, Value, ValueList,
BlockCall, Inst, InstructionData, MemFlags, Opcode, TrapCode, Value, ValueList,
},
isa::{
unwind::UnwindInst,

View File

@@ -637,5 +637,16 @@ macro_rules! isle_common_prelude_methods {
fn pack_value_array_3(&mut self, a: Value, b: Value, c: Value) -> ValueArray3 {
[a, b, c]
}
#[inline]
fn unpack_block_array_2(&mut self, arr: &BlockArray2) -> (BlockCall, BlockCall) {
let [a, b] = *arr;
(a, b)
}
#[inline]
fn pack_block_array_2(&mut self, a: BlockCall, b: BlockCall) -> BlockArray2 {
[a, b]
}
};
}

View File

@@ -1,4 +1,4 @@
use crate::ir::{Value, ValueList};
use crate::ir::{BlockCall, Value, ValueList};
use alloc::boxed::Box;
use alloc::vec::Vec;
use smallvec::SmallVec;
@@ -22,6 +22,7 @@ pub type Unit = ();
pub type ValueSlice = (ValueList, usize);
pub type ValueArray2 = [Value; 2];
pub type ValueArray3 = [Value; 3];
pub type BlockArray2 = [BlockCall; 2];
pub type WritableReg = Writable<Reg>;
pub type VecRetPair = Vec<RetPair>;
pub type VecMask = Vec<u8>;

View File

@@ -9,9 +9,9 @@ use crate::entity::SecondaryMap;
use crate::fx::{FxHashMap, FxHashSet};
use crate::inst_predicates::{has_lowering_side_effect, is_constant_64bit};
use crate::ir::{
ArgumentPurpose, Block, Constant, ConstantData, DataFlowGraph, ExternalName, Function,
GlobalValue, GlobalValueData, Immediate, Inst, InstructionData, MemFlags, Opcode, RelSourceLoc,
Type, Value, ValueDef, ValueLabelAssignments, ValueLabelStart,
instructions, ArgumentPurpose, Block, Constant, ConstantData, DataFlowGraph, ExternalName,
Function, GlobalValue, GlobalValueData, Immediate, Inst, InstructionData, MemFlags, Opcode,
RelSourceLoc, Type, Value, ValueDef, ValueLabelAssignments, ValueLabelStart,
};
use crate::machinst::{
writable_value_regs, BlockIndex, BlockLoweringOrder, Callee, LoweredBlock, MachLabel, Reg,
@@ -941,12 +941,28 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
for succ_idx in 0..self.vcode.block_order().succ_indices(block).len() {
// Avoid immutable borrow by explicitly indexing.
let (inst, succ) = self.vcode.block_order().succ_indices(block)[succ_idx];
// Get branch args and convert to Regs.
let branch_args = self.f.dfg.insts[inst]
.branch_destination()
.into_iter()
.flat_map(|block| block.args_slice(&self.f.dfg.value_lists));
// Get branch args and convert to Regs.
let branch_args = match self.f.dfg.analyze_branch(inst) {
instructions::BranchInfo::NotABranch => unreachable!(),
instructions::BranchInfo::SingleDest(block) => {
block.args_slice(&self.f.dfg.value_lists)
}
instructions::BranchInfo::Conditional(then_block, else_block) => {
// NOTE: `succ_idx == 0` implying that we're traversing the `then_block` is
// enforced by the traversal order defined in `visit_block_succs`. Eventually
// we should traverse the `branch_destination` slice instead of the result of
// analyze_branch there, which would simplify computing the branch args
// significantly.
if succ_idx == 0 {
then_block.args_slice(&self.f.dfg.value_lists)
} else {
assert!(succ_idx == 1);
else_block.args_slice(&self.f.dfg.value_lists)
}
}
instructions::BranchInfo::Table(_, _) => &[],
};
let mut branch_arg_vregs: SmallVec<[Reg; 16]> = smallvec![];
for &arg in branch_args {
let arg = self.f.dfg.resolve_aliases(arg);
@@ -976,7 +992,10 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
if last_inst != Some(inst) {
branches.push(inst);
} else {
debug_assert!(self.f.dfg.insts[inst].opcode() == Opcode::BrTable);
debug_assert!(
self.f.dfg.insts[inst].opcode() == Opcode::BrTable
|| self.f.dfg.insts[inst].opcode() == Opcode::Brif
);
debug_assert!(branches.len() == 1);
}
last_inst = Some(inst);

View File

@@ -34,6 +34,7 @@
(type Type (primitive Type))
(type Value (primitive Value))
(type ValueList (primitive ValueList))
(type BlockCall (primitive BlockCall))
;; ISLE representation of `&[Value]`.
(type ValueSlice (primitive ValueSlice))

View File

@@ -4,7 +4,6 @@ use crate::dominator_tree::DominatorTree;
use crate::fx::FxHashMap;
use crate::fx::FxHashSet;
use crate::ir;
use crate::ir::instructions::BranchInfo;
use crate::ir::Function;
use crate::ir::{Block, BlockCall, Inst, Value};
use crate::timing;
@@ -112,6 +111,9 @@ impl AbstractValue {
struct OutEdge<'a> {
/// An instruction that transfers control.
inst: Inst,
/// The index into branch_destinations for this instruction that corresponds
/// to this edge.
branch_index: u32,
/// The block that control is transferred to.
block: Block,
/// The arguments to that block.
@@ -126,7 +128,13 @@ impl<'a> OutEdge<'a> {
/// Returns `None` if this is an edge without any block arguments, which
/// means we can ignore it for this analysis's purposes.
#[inline]
fn new(bump: &'a Bump, dfg: &ir::DataFlowGraph, inst: Inst, block: BlockCall) -> Option<Self> {
fn new(
bump: &'a Bump,
dfg: &ir::DataFlowGraph,
inst: Inst,
branch_index: usize,
block: BlockCall,
) -> Option<Self> {
let inst_var_args = block.args_slice(&dfg.value_lists);
// Skip edges without params.
@@ -136,6 +144,7 @@ impl<'a> OutEdge<'a> {
Some(OutEdge {
inst,
branch_index: branch_index as u32,
block: block.block(&dfg.value_lists),
args: bump.alloc_slice_fill_iter(
inst_var_args
@@ -231,12 +240,8 @@ pub fn do_remove_constant_phis(func: &mut Function, domtree: &mut DominatorTree)
let mut summary = BlockSummary::new(&bump, formals);
for inst in func.layout.block_insts(b) {
let idetails = &func.dfg.insts[inst];
// Note that multi-dest transfers (i.e., branch tables) don't
// carry parameters in our IR, so we only have to care about
// `SingleDest` here.
if let BranchInfo::SingleDest(dest) = idetails.analyze_branch() {
if let Some(edge) = OutEdge::new(&bump, &func.dfg, inst, dest) {
for (ix, dest) in func.dfg.insts[inst].branch_destination().iter().enumerate() {
if let Some(edge) = OutEdge::new(&bump, &func.dfg, inst, ix, *dest) {
summary.dests.push(edge);
}
}
@@ -378,7 +383,9 @@ pub fn do_remove_constant_phis(func: &mut Function, domtree: &mut DominatorTree)
}
let dfg = &mut func.dfg;
let block = dfg.insts[edge.inst].branch_destination_mut().unwrap();
let block =
&mut dfg.insts[edge.inst].branch_destination_mut()[edge.branch_index as usize];
old_actuals.extend(block.args_slice(&dfg.value_lists));
// Check that the numbers of arguments make sense.

View File

@@ -587,6 +587,15 @@ impl<'a> Verifier<'a> {
Jump { destination, .. } | Branch { destination, .. } => {
self.verify_block(inst, destination.block(&self.func.dfg.value_lists), errors)?;
}
Brif {
arg,
blocks: [block_then, block_else],
..
} => {
self.verify_value(inst, arg, errors)?;
self.verify_block(inst, block_then.block(&self.func.dfg.value_lists), errors)?;
self.verify_block(inst, block_else.block(&self.func.dfg.value_lists), errors)?;
}
BranchTable {
table, destination, ..
} => {
@@ -1303,6 +1312,25 @@ impl<'a> Verifier<'a> {
let args = block.args_slice(&self.func.dfg.value_lists);
self.typecheck_variable_args_iterator(inst, iter, args, errors)?;
}
BranchInfo::Conditional(block_then, block_else) => {
let iter = self
.func
.dfg
.block_params(block_then.block(&self.func.dfg.value_lists))
.iter()
.map(|&v| self.func.dfg.value_type(v));
let args_then = block_then.args_slice(&self.func.dfg.value_lists);
self.typecheck_variable_args_iterator(inst, iter, args_then, errors)?;
let iter = self
.func
.dfg
.block_params(block_else.block(&self.func.dfg.value_lists))
.iter()
.map(|&v| self.func.dfg.value_type(v));
let args_else = block_else.args_slice(&self.func.dfg.value_lists);
self.typecheck_variable_args_iterator(inst, iter, args_else, errors)?;
}
BranchInfo::Table(table, block) => {
let arg_count = self.func.dfg.num_block_params(block);
if arg_count != 0 {

View File

@@ -418,6 +418,16 @@ pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt
write!(w, " {}", destination.block(pool))?;
write_block_args(w, destination.args_slice(pool))
}
Brif {
arg,
blocks: [block_then, block_else],
..
} => {
write!(w, " {}, {}", arg, block_then.block(pool))?;
write_block_args(w, block_then.args_slice(pool))?;
write!(w, ", {}", block_else.block(pool))?;
write_block_args(w, block_else.args_slice(pool))
}
Branch {
arg, destination, ..
} => {