Files
regalloc2/src/fuzzing/func.rs
2021-08-12 14:27:20 -07:00

604 lines
20 KiB
Rust

/*
* 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<Operand>,
clobbers: Vec<PReg>,
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<InstData>,
blocks: Vec<InstRange>,
block_preds: Vec<Vec<Block>>,
block_succs: Vec<Vec<Block>>,
block_params: Vec<Vec<VReg>>,
num_vregs: usize,
reftype_vregs: Vec<VReg>,
}
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<Block>,
idom: Vec<Block>,
f: Func,
insts_per_block: Vec<Vec<InstData>>,
}
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<Self> {
Ok(*u.choose(&[OperandConstraint::Any, OperandConstraint::Reg])?)
}
}
fn choose_dominating_block(
idom: &[Block],
mut block: Block,
allow_self: bool,
u: &mut Unstructured,
) -> ArbitraryResult<Block> {
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> {
Func::arbitrary_with_options(u, &Options::default())
}
}
impl Func {
pub fn arbitrary_with_options(u: &mut Unstructured, opts: &Options) -> ArbitraryResult<Func> {
// 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<PReg> = 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::<Vec<_>>();
let preds = self.block_preds[i]
.iter()
.map(|b| b.index())
.collect::<Vec<_>>();
let params = self.block_params[i]
.iter()
.map(|v| format!("v{}", v.vreg()))
.collect::<Vec<_>>()
.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<PReg> = (0..31).map(|i| PReg::new(i, RegClass::Int)).collect();
let preferred_regs_by_class: [Vec<PReg>; 2] = [regs.iter().cloned().take(24).collect(), vec![]];
let non_preferred_regs_by_class: [Vec<PReg>; 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,
}
}