/* * Released under the terms of the Apache 2.0 license with LLVM * exception. See `LICENSE` for details. */ use crate::{ domtree, postorder, Allocation, Block, Function, Inst, InstRange, MachineEnv, Operand, OperandConstraint, OperandKind, OperandPos, PReg, RegClass, VReg, }; use arbitrary::Result as ArbitraryResult; use arbitrary::{Arbitrary, Unstructured}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum InstOpcode { Phi, Op, Call, Ret, Branch, } #[derive(Clone, Debug)] pub struct InstData { op: InstOpcode, operands: Vec, clobbers: Vec, is_safepoint: bool, } impl InstData { pub fn op(def: usize, uses: &[usize]) -> InstData { let mut operands = vec![Operand::reg_def(VReg::new(def, RegClass::Int))]; for &u in uses { operands.push(Operand::reg_use(VReg::new(u, RegClass::Int))); } InstData { op: InstOpcode::Op, operands, clobbers: vec![], is_safepoint: false, } } pub fn branch(uses: &[usize]) -> InstData { let mut operands = vec![]; for &u in uses { operands.push(Operand::reg_use(VReg::new(u, RegClass::Int))); } InstData { op: InstOpcode::Branch, operands, clobbers: vec![], is_safepoint: false, } } pub fn ret() -> InstData { InstData { op: InstOpcode::Ret, operands: vec![], clobbers: vec![], is_safepoint: false, } } } #[derive(Clone)] pub struct Func { insts: Vec, blocks: Vec, block_preds: Vec>, block_succs: Vec>, block_params: Vec>, num_vregs: usize, reftype_vregs: Vec, } impl Function for Func { fn insts(&self) -> usize { self.insts.len() } fn blocks(&self) -> usize { self.blocks.len() } fn entry_block(&self) -> Block { assert!(self.blocks.len() > 0); Block::new(0) } fn block_insns(&self, block: Block) -> InstRange { self.blocks[block.index()] } fn block_succs(&self, block: Block) -> &[Block] { &self.block_succs[block.index()][..] } fn block_preds(&self, block: Block) -> &[Block] { &self.block_preds[block.index()][..] } fn block_params(&self, block: Block) -> &[VReg] { &self.block_params[block.index()][..] } fn is_call(&self, insn: Inst) -> bool { self.insts[insn.index()].op == InstOpcode::Call } fn is_ret(&self, insn: Inst) -> bool { self.insts[insn.index()].op == InstOpcode::Ret } fn is_branch(&self, insn: Inst) -> bool { self.insts[insn.index()].op == InstOpcode::Branch } fn branch_blockparam_arg_offset(&self, _: Block, _: Inst) -> usize { // Branch blockparam args always start at zero for this // Function implementation. 0 } fn is_safepoint(&self, insn: Inst) -> bool { self.insts[insn.index()].is_safepoint } fn reftype_vregs(&self) -> &[VReg] { &self.reftype_vregs[..] } fn is_move(&self, _: Inst) -> Option<(Operand, Operand)> { None } fn inst_operands(&self, insn: Inst) -> &[Operand] { &self.insts[insn.index()].operands[..] } fn inst_clobbers(&self, insn: Inst) -> &[PReg] { &self.insts[insn.index()].clobbers[..] } fn num_vregs(&self) -> usize { self.num_vregs } fn spillslot_size(&self, regclass: RegClass) -> usize { match regclass { RegClass::Int => 1, RegClass::Float => 2, } } } struct FuncBuilder { postorder: Vec, idom: Vec, f: Func, insts_per_block: Vec>, } impl FuncBuilder { fn new() -> Self { FuncBuilder { postorder: vec![], idom: vec![], f: Func { block_preds: vec![], block_succs: vec![], block_params: vec![], insts: vec![], blocks: vec![], num_vregs: 0, reftype_vregs: vec![], }, insts_per_block: vec![], } } pub fn add_block(&mut self) -> Block { let b = Block::new(self.f.blocks.len()); self.f .blocks .push(InstRange::forward(Inst::new(0), Inst::new(0))); self.f.block_preds.push(vec![]); self.f.block_succs.push(vec![]); self.f.block_params.push(vec![]); self.insts_per_block.push(vec![]); b } pub fn add_inst(&mut self, block: Block, data: InstData) { self.insts_per_block[block.index()].push(data); } pub fn add_edge(&mut self, from: Block, to: Block) { self.f.block_succs[from.index()].push(to); self.f.block_preds[to.index()].push(from); } pub fn set_block_params(&mut self, block: Block, params: &[VReg]) { self.f.block_params[block.index()] = params.iter().cloned().collect(); } fn compute_doms(&mut self) { self.postorder = postorder::calculate(self.f.blocks.len(), Block::new(0), |block| { &self.f.block_succs[block.index()][..] }); self.idom = domtree::calculate( self.f.blocks.len(), |block| &self.f.block_preds[block.index()][..], &self.postorder[..], Block::new(0), ); } fn finalize(mut self) -> Func { for (blocknum, blockrange) in self.f.blocks.iter_mut().enumerate() { let begin_inst = self.f.insts.len(); for inst in &self.insts_per_block[blocknum] { self.f.insts.push(inst.clone()); } let end_inst = self.f.insts.len(); *blockrange = InstRange::forward(Inst::new(begin_inst), Inst::new(end_inst)); } self.f } } impl Arbitrary for OperandConstraint { fn arbitrary(u: &mut Unstructured) -> ArbitraryResult { Ok(*u.choose(&[OperandConstraint::Any, OperandConstraint::Reg])?) } } fn choose_dominating_block( idom: &[Block], mut block: Block, allow_self: bool, u: &mut Unstructured, ) -> ArbitraryResult { assert!(block.is_valid()); let orig_block = block; loop { if (allow_self || block != orig_block) && bool::arbitrary(u)? { break; } if idom[block.index()].is_invalid() { break; } block = idom[block.index()]; } let block = if block != orig_block || allow_self { block } else { Block::invalid() }; Ok(block) } #[derive(Clone, Copy, Debug)] pub struct Options { pub reused_inputs: bool, pub fixed_regs: bool, pub clobbers: bool, pub control_flow: bool, pub reducible: bool, pub block_params: bool, pub always_local_uses: bool, pub reftypes: bool, } impl std::default::Default for Options { fn default() -> Self { Options { reused_inputs: false, fixed_regs: false, clobbers: false, control_flow: true, reducible: false, block_params: true, always_local_uses: false, reftypes: false, } } } impl Arbitrary for Func { fn arbitrary(u: &mut Unstructured) -> ArbitraryResult { Func::arbitrary_with_options(u, &Options::default()) } } impl Func { pub fn arbitrary_with_options(u: &mut Unstructured, opts: &Options) -> ArbitraryResult { // General strategy: // 1. Create an arbitrary CFG. // 2. Create a list of vregs to define in each block. // 3. Define some of those vregs in each block as blockparams.f. // 4. Populate blocks with ops that define the rest of the vregs. // - For each use, choose an available vreg: either one // already defined (via blockparam or inst) in this block, // or one defined in a dominating block. let mut builder = FuncBuilder::new(); for _ in 0..u.int_in_range(1..=100)? { builder.add_block(); } let num_blocks = builder.f.blocks.len(); // Generate a CFG. Create a "spine" of either single blocks, // with links to the next; or fork patterns, with the left // fork linking to the next and the right fork in `out_blocks` // to be connected below. This creates an arbitrary CFG with // split critical edges, which is a property that we require // for the regalloc. let mut from = 0; let mut out_blocks = vec![]; let mut in_blocks = vec![]; // For reducibility, if selected: enforce strict nesting of backedges let mut max_backedge_src = 0; let mut min_backedge_dest = num_blocks; while from < num_blocks { in_blocks.push(from); if num_blocks > 3 && from < num_blocks - 3 && bool::arbitrary(u)? && opts.control_flow { // To avoid critical edges, we use from+1 as an edge // block, and advance `from` an extra block; `from+2` // will be the next normal iteration. builder.add_edge(Block::new(from), Block::new(from + 1)); builder.add_edge(Block::new(from), Block::new(from + 2)); builder.add_edge(Block::new(from + 2), Block::new(from + 3)); out_blocks.push(from + 1); from += 2; } else if from < num_blocks - 1 { builder.add_edge(Block::new(from), Block::new(from + 1)); } from += 1; } for pred in out_blocks { let mut succ = *u.choose(&in_blocks[..])?; if opts.reducible && (pred >= succ) { if pred < max_backedge_src || succ > min_backedge_dest { // If the chosen edge would result in an // irreducible CFG, just make this a diamond // instead. succ = pred + 2; } else { max_backedge_src = pred; min_backedge_dest = succ; } } builder.add_edge(Block::new(pred), Block::new(succ)); } builder.compute_doms(); for block in 0..num_blocks { builder.f.block_preds[block].clear(); } for block in 0..num_blocks { for &succ in &builder.f.block_succs[block] { builder.f.block_preds[succ.index()].push(Block::new(block)); } } builder.compute_doms(); let mut vregs_by_block = vec![]; let mut vregs_by_block_to_be_defined = vec![]; let mut block_params = vec![vec![]; num_blocks]; for block in 0..num_blocks { let mut vregs = vec![]; for _ in 0..u.int_in_range(5..=15)? { let vreg = VReg::new(builder.f.num_vregs, RegClass::Int); builder.f.num_vregs += 1; vregs.push(vreg); if opts.reftypes && bool::arbitrary(u)? { builder.f.reftype_vregs.push(vreg); } } vregs_by_block.push(vregs.clone()); vregs_by_block_to_be_defined.push(vec![]); let mut max_block_params = u.int_in_range(0..=std::cmp::min(3, vregs.len() / 3))?; for &vreg in &vregs { if block > 0 && opts.block_params && bool::arbitrary(u)? && max_block_params > 0 { block_params[block].push(vreg); max_block_params -= 1; } else { vregs_by_block_to_be_defined.last_mut().unwrap().push(vreg); } } vregs_by_block_to_be_defined.last_mut().unwrap().reverse(); builder.set_block_params(Block::new(block), &block_params[block][..]); } for block in 0..num_blocks { let mut avail = block_params[block].clone(); let mut remaining_nonlocal_uses = u.int_in_range(0..=3)?; while let Some(vreg) = vregs_by_block_to_be_defined[block].pop() { let def_constraint = OperandConstraint::arbitrary(u)?; let def_pos = if bool::arbitrary(u)? { OperandPos::Before } else { OperandPos::After }; let mut operands = vec![Operand::new( vreg, def_constraint, OperandKind::Def, def_pos, )]; let mut allocations = vec![Allocation::none()]; for _ in 0..u.int_in_range(0..=3)? { let vreg = if avail.len() > 0 && (opts.always_local_uses || remaining_nonlocal_uses == 0 || bool::arbitrary(u)?) { *u.choose(&avail[..])? } else if !opts.always_local_uses { let def_block = choose_dominating_block( &builder.idom[..], Block::new(block), /* allow_self = */ false, u, )?; if !def_block.is_valid() { // No vregs already defined, and no pred blocks that dominate us // (perhaps we are the entry block): just stop generating inputs. break; } remaining_nonlocal_uses -= 1; *u.choose(&vregs_by_block[def_block.index()])? } else { break; }; let use_constraint = OperandConstraint::arbitrary(u)?; operands.push(Operand::new( vreg, use_constraint, OperandKind::Use, OperandPos::Before, )); allocations.push(Allocation::none()); } let mut clobbers: Vec = vec![]; if operands.len() > 1 && opts.reused_inputs && bool::arbitrary(u)? { // Make the def a reused input. let op = operands[0]; assert_eq!(op.kind(), OperandKind::Def); let reused = u.int_in_range(1..=(operands.len() - 1))?; operands[0] = Operand::new( op.vreg(), OperandConstraint::Reuse(reused), op.kind(), OperandPos::After, ); // Make sure reused input is a Reg. let op = operands[reused]; operands[reused] = Operand::new( op.vreg(), OperandConstraint::Reg, op.kind(), OperandPos::Before, ); } else if opts.fixed_regs && bool::arbitrary(u)? { let mut fixed = vec![]; for _ in 0..u.int_in_range(0..=operands.len() - 1)? { // Pick an operand and make it a fixed reg. let fixed_reg = PReg::new(u.int_in_range(0..=30)?, RegClass::Int); if fixed.contains(&fixed_reg) { break; } fixed.push(fixed_reg); let i = u.int_in_range(0..=(operands.len() - 1))?; let op = operands[i]; operands[i] = Operand::new( op.vreg(), OperandConstraint::FixedReg(fixed_reg), op.kind(), op.pos(), ); } } else if opts.clobbers && bool::arbitrary(u)? { for _ in 0..u.int_in_range(0..=5)? { let reg = u.int_in_range(0..=30)?; if clobbers.iter().any(|r| r.hw_enc() == reg) { break; } clobbers.push(PReg::new(reg, RegClass::Int)); } } let is_safepoint = opts.reftypes && operands .iter() .all(|op| !builder.f.reftype_vregs.contains(&op.vreg())) && bool::arbitrary(u)?; let op = *u.choose(&[InstOpcode::Op, InstOpcode::Call])?; builder.add_inst( Block::new(block), InstData { op, operands, clobbers, is_safepoint, }, ); avail.push(vreg); } // Define the branch with blockparam args that must end // the block. if builder.f.block_succs[block].len() > 0 { let mut args = vec![]; for &succ in &builder.f.block_succs[block] { for _ in 0..builder.f.block_params[succ.index()].len() { let dom_block = choose_dominating_block( &builder.idom[..], Block::new(block), false, u, )?; let vreg = if dom_block.is_valid() && bool::arbitrary(u)? { u.choose(&vregs_by_block[dom_block.index()][..])? } else { u.choose(&avail[..])? }; args.push(vreg.vreg()); } } builder.add_inst(Block::new(block), InstData::branch(&args[..])); } else { builder.add_inst(Block::new(block), InstData::ret()); } } Ok(builder.finalize()) } } impl std::fmt::Debug for Func { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{{\n")?; for vreg in self.reftype_vregs() { write!(f, " REF: {}\n", vreg)?; } for (i, blockrange) in self.blocks.iter().enumerate() { let succs = self.block_succs[i] .iter() .map(|b| b.index()) .collect::>(); let preds = self.block_preds[i] .iter() .map(|b| b.index()) .collect::>(); let params = self.block_params[i] .iter() .map(|v| format!("v{}", v.vreg())) .collect::>() .join(", "); write!( f, " block{}({}): # succs:{:?} preds:{:?}\n", i, params, succs, preds )?; for inst in blockrange.iter() { if self.is_safepoint(inst) { write!(f, " -- SAFEPOINT --\n")?; } write!( f, " inst{}: {:?} ops:{:?} clobber:{:?}\n", inst.index(), self.insts[inst.index()].op, self.insts[inst.index()].operands, self.insts[inst.index()].clobbers )?; } } write!(f, "}}\n")?; Ok(()) } } pub fn machine_env() -> MachineEnv { // Reg 31 is the scratch reg. let regs: Vec = (0..31).map(|i| PReg::new(i, RegClass::Int)).collect(); let preferred_regs_by_class: [Vec; 2] = [regs.iter().cloned().take(24).collect(), vec![]]; let non_preferred_regs_by_class: [Vec; 2] = [regs.iter().cloned().skip(24).collect(), vec![]]; let scratch_by_class: [PReg; 2] = [PReg::new(31, RegClass::Int), PReg::new(0, RegClass::Float)]; MachineEnv { regs, preferred_regs_by_class, non_preferred_regs_by_class, scratch_by_class, } }