cranelift: Rework block instructions to use BlockCall (#5464)
Add a new type BlockCall that represents the pair of a block name with arguments to be passed to it. (The mnemonic here is that it looks a bit like a function call.) Rework the implementation of jump, brz, and brnz to use BlockCall instead of storing the block arguments as varargs in the instruction's ValueList. To ensure that we're processing block arguments from BlockCall values in instructions, three new functions have been introduced on DataFlowGraph that both sets of arguments: inst_values - returns an iterator that traverses values in the instruction and block arguments map_inst_values - applies a function to each value in the instruction and block arguments overwrite_inst_values - overwrite all values in an instruction and block arguments with values from the iterator Co-authored-by: Jamey Sharp <jamey@minilop.net>
This commit is contained in:
@@ -7,8 +7,8 @@ use crate::ir::dynamic_type::{DynamicTypeData, DynamicTypes};
|
||||
use crate::ir::instructions::{BranchInfo, CallInfo, InstructionData};
|
||||
use crate::ir::{types, ConstantData, ConstantPool, Immediate};
|
||||
use crate::ir::{
|
||||
Block, DynamicType, FuncRef, Inst, SigRef, Signature, Type, Value, ValueLabelAssignments,
|
||||
ValueList, ValueListPool,
|
||||
Block, BlockCall, DynamicType, FuncRef, Inst, SigRef, Signature, Type, Value,
|
||||
ValueLabelAssignments, ValueList, ValueListPool,
|
||||
};
|
||||
use crate::ir::{ExtFuncData, RelSourceLoc};
|
||||
use crate::packed_option::ReservedValue;
|
||||
@@ -167,6 +167,11 @@ impl DataFlowGraph {
|
||||
self.blocks.is_valid(block)
|
||||
}
|
||||
|
||||
/// Make a BlockCall, bundling together the block and its arguments.
|
||||
pub fn block_call(&mut self, block: Block, args: &[Value]) -> BlockCall {
|
||||
BlockCall::new(block, args, &mut self.value_lists)
|
||||
}
|
||||
|
||||
/// Get the total number of values.
|
||||
pub fn num_values(&self) -> usize {
|
||||
self.values.len()
|
||||
@@ -328,12 +333,7 @@ impl DataFlowGraph {
|
||||
/// For each argument of inst which is defined by an alias, replace the
|
||||
/// alias with the aliased value.
|
||||
pub fn resolve_aliases_in_arguments(&mut self, inst: Inst) {
|
||||
for arg in self.insts[inst].arguments_mut(&mut self.value_lists) {
|
||||
let resolved = resolve_aliases(&self.values, *arg);
|
||||
if resolved != *arg {
|
||||
*arg = resolved;
|
||||
}
|
||||
}
|
||||
self.map_inst_values(inst, |dfg, arg| resolve_aliases(&dfg.values, arg));
|
||||
}
|
||||
|
||||
/// Turn a value into an alias of another.
|
||||
@@ -665,6 +665,60 @@ impl DataFlowGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a read-only visitor context for the values of this instruction.
|
||||
pub fn inst_values<'dfg>(
|
||||
&'dfg self,
|
||||
inst: Inst,
|
||||
) -> impl DoubleEndedIterator<Item = Value> + 'dfg {
|
||||
self.inst_args(inst)
|
||||
.iter()
|
||||
.chain(
|
||||
self.insts[inst]
|
||||
.branch_destination()
|
||||
.into_iter()
|
||||
.flat_map(|branch| branch.args_slice(&self.value_lists).iter()),
|
||||
)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Map a function over the values of the instruction.
|
||||
pub fn map_inst_values<F>(&mut self, inst: Inst, mut body: F)
|
||||
where
|
||||
F: FnMut(&mut DataFlowGraph, Value) -> Value,
|
||||
{
|
||||
for i in 0..self.inst_args(inst).len() {
|
||||
let arg = self.inst_args(inst)[i];
|
||||
self.inst_args_mut(inst)[i] = body(self, arg);
|
||||
}
|
||||
|
||||
for mut block in self.insts[inst].branch_destination().into_iter() {
|
||||
// We aren't changing the size of the args list, so we won't need to write the branch
|
||||
// back to the instruction.
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Overwrite the instruction's value references with values from the iterator.
|
||||
/// NOTE: the iterator provided is expected to yield at least as many values as the instruction
|
||||
/// currently has.
|
||||
pub fn overwrite_inst_values<I>(&mut self, inst: Inst, mut values: I)
|
||||
where
|
||||
I: Iterator<Item = Value>,
|
||||
{
|
||||
for arg in self.inst_args_mut(inst) {
|
||||
*arg = values.next().unwrap();
|
||||
}
|
||||
|
||||
for mut block in self.insts[inst].branch_destination().into_iter() {
|
||||
for arg in block.args_slice_mut(&mut self.value_lists) {
|
||||
*arg = values.next().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all value arguments on `inst` as a slice.
|
||||
pub fn inst_args(&self, inst: Inst) -> &[Value] {
|
||||
self.insts[inst].arguments(&self.value_lists)
|
||||
@@ -866,16 +920,6 @@ impl DataFlowGraph {
|
||||
})
|
||||
}
|
||||
|
||||
/// Append a new value argument to an instruction.
|
||||
///
|
||||
/// Panics if the instruction doesn't support arguments.
|
||||
pub fn append_inst_arg(&mut self, inst: Inst, new_arg: Value) {
|
||||
self.insts[inst]
|
||||
.value_list_mut()
|
||||
.expect("the instruction doesn't have value arguments")
|
||||
.push(new_arg, &mut self.value_lists);
|
||||
}
|
||||
|
||||
/// Clone an instruction, attaching new result `Value`s and
|
||||
/// returning them.
|
||||
pub fn clone_inst(&mut self, inst: Inst) -> Inst {
|
||||
@@ -933,7 +977,7 @@ impl DataFlowGraph {
|
||||
|
||||
/// Check if `inst` is a branch.
|
||||
pub fn analyze_branch(&self, inst: Inst) -> BranchInfo {
|
||||
self.insts[inst].analyze_branch(&self.value_lists)
|
||||
self.insts[inst].analyze_branch()
|
||||
}
|
||||
|
||||
/// Compute the type of an instruction result from opcode constraints and call signatures.
|
||||
|
||||
@@ -284,7 +284,7 @@ impl FunctionStencil {
|
||||
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 = new_dest,
|
||||
Some(inst_dest) => inst_dest.set_block(new_dest, &mut self.dfg.value_lists),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +295,8 @@ impl FunctionStencil {
|
||||
/// 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 == old_dest {
|
||||
BranchInfo::SingleDest(dest) => {
|
||||
if dest.block(&self.dfg.value_lists) == old_dest {
|
||||
self.change_branch_destination(inst, new_dest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,95 @@ pub type ValueList = entity::EntityList<Value>;
|
||||
/// Memory pool for holding value lists. See `ValueList`.
|
||||
pub type ValueListPool = entity::ListPool<Value>;
|
||||
|
||||
/// A pair of a Block and its arguments, stored in a single EntityList internally.
|
||||
///
|
||||
/// NOTE: We don't expose either value_to_block or block_to_value outside of this module because
|
||||
/// this operation is not generally safe. However, as the two share the same underlying layout,
|
||||
/// they can be stored in the same value pool.
|
||||
///
|
||||
/// BlockCall makes use of this shared layout by storing all of its contents (a block and its
|
||||
/// argument) in a single EntityList. This is a bit better than introducing a new entity type for
|
||||
/// the pair of a block name and the arguments entity list, as we don't pay any indirection penalty
|
||||
/// to get to the argument values -- they're stored in-line with the block in the same list.
|
||||
///
|
||||
/// The BlockCall::new function guarantees this layout by requiring a block argument that's written
|
||||
/// in as the first element of the EntityList. Any subsequent entries are always assumed to be real
|
||||
/// Values.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
||||
pub struct BlockCall {
|
||||
/// The underlying storage for the BlockCall. The first element of the values EntityList is
|
||||
/// guaranteed to always be a Block encoded as a Value via BlockCall::block_to_value.
|
||||
/// Consequently, the values entity list is never empty.
|
||||
values: entity::EntityList<Value>,
|
||||
}
|
||||
|
||||
impl BlockCall {
|
||||
// NOTE: the only uses of this function should be internal to BlockCall. See the block comment
|
||||
// on BlockCall for more context.
|
||||
fn value_to_block(val: Value) -> Block {
|
||||
Block::from_u32(val.as_u32())
|
||||
}
|
||||
|
||||
// NOTE: the only uses of this function should be internal to BlockCall. See the block comment
|
||||
// on BlockCall for more context.
|
||||
fn block_to_value(block: Block) -> Value {
|
||||
Value::from_u32(block.as_u32())
|
||||
}
|
||||
|
||||
/// Construct a BlockCall with the given block and arguments.
|
||||
pub fn new(block: Block, args: &[Value], pool: &mut ValueListPool) -> Self {
|
||||
let mut values = ValueList::default();
|
||||
values.push(Self::block_to_value(block), pool);
|
||||
values.extend(args.iter().copied(), pool);
|
||||
Self { values }
|
||||
}
|
||||
|
||||
/// Return the block for this BlockCall.
|
||||
pub fn block(&self, pool: &ValueListPool) -> Block {
|
||||
let val = self.values.first(pool).unwrap();
|
||||
Self::value_to_block(val)
|
||||
}
|
||||
|
||||
/// Replace the block for this BlockCall.
|
||||
pub fn set_block(&mut self, block: Block, pool: &mut ValueListPool) {
|
||||
*self.values.get_mut(0, pool).unwrap() = Self::block_to_value(block);
|
||||
}
|
||||
|
||||
/// Append an argument to the block args.
|
||||
pub fn append_argument(&mut self, arg: Value, pool: &mut ValueListPool) {
|
||||
self.values.push(arg, pool);
|
||||
}
|
||||
|
||||
/// Return a slice for the arguments of this block.
|
||||
pub fn args_slice<'a>(&self, pool: &'a ValueListPool) -> &'a [Value] {
|
||||
&self.values.as_slice(pool)[1..]
|
||||
}
|
||||
|
||||
/// Return a slice for the arguments of this block.
|
||||
pub fn args_slice_mut<'a>(&'a mut self, pool: &'a mut ValueListPool) -> &'a mut [Value] {
|
||||
&mut self.values.as_mut_slice(pool)[1..]
|
||||
}
|
||||
|
||||
/// Remove the argument at ix from the argument list.
|
||||
pub fn remove(&mut self, ix: usize, pool: &mut ValueListPool) {
|
||||
self.values.remove(1 + ix, pool)
|
||||
}
|
||||
|
||||
/// Clear out the arguments list.
|
||||
pub fn clear(&mut self, pool: &mut ValueListPool) {
|
||||
self.values.truncate(1, pool)
|
||||
}
|
||||
|
||||
/// Appends multiple elements to the arguments.
|
||||
pub fn extend<I>(&mut self, elements: I, pool: &mut ValueListPool)
|
||||
where
|
||||
I: IntoIterator<Item = Value>,
|
||||
{
|
||||
self.values.extend(elements, pool)
|
||||
}
|
||||
}
|
||||
|
||||
// Include code generated by `cranelift-codegen/meta/src/gen_inst.rs`. This file contains:
|
||||
//
|
||||
// - The `pub enum InstructionFormat` enum with all the instruction formats.
|
||||
@@ -178,18 +267,10 @@ impl InstructionData {
|
||||
///
|
||||
/// Any instruction that can transfer control to another block reveals its possible destinations
|
||||
/// here.
|
||||
pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> {
|
||||
pub fn analyze_branch(&self) -> BranchInfo {
|
||||
match *self {
|
||||
Self::Jump {
|
||||
destination,
|
||||
ref args,
|
||||
..
|
||||
} => BranchInfo::SingleDest(destination, args.as_slice(pool)),
|
||||
Self::Branch {
|
||||
destination,
|
||||
ref args,
|
||||
..
|
||||
} => BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]),
|
||||
Self::Jump { destination, .. } => BranchInfo::SingleDest(destination),
|
||||
Self::Branch { destination, .. } => BranchInfo::SingleDest(destination),
|
||||
Self::BranchTable {
|
||||
table, destination, ..
|
||||
} => BranchInfo::Table(table, Some(destination)),
|
||||
@@ -204,7 +285,7 @@ impl InstructionData {
|
||||
/// branch or jump.
|
||||
///
|
||||
/// Multi-destination branches like `br_table` return `None`.
|
||||
pub fn branch_destination(&self) -> Option<Block> {
|
||||
pub fn branch_destination(&self) -> Option<BlockCall> {
|
||||
match *self {
|
||||
Self::Jump { destination, .. } | Self::Branch { destination, .. } => Some(destination),
|
||||
Self::BranchTable { .. } => None,
|
||||
@@ -219,7 +300,7 @@ impl InstructionData {
|
||||
/// single destination branch or jump.
|
||||
///
|
||||
/// Multi-destination branches like `br_table` return `None`.
|
||||
pub fn branch_destination_mut(&mut self) -> Option<&mut Block> {
|
||||
pub fn branch_destination_mut(&mut self) -> Option<&mut BlockCall> {
|
||||
match *self {
|
||||
Self::Jump {
|
||||
ref mut destination,
|
||||
@@ -366,14 +447,14 @@ impl InstructionData {
|
||||
}
|
||||
|
||||
/// Information about branch and jump instructions.
|
||||
pub enum BranchInfo<'a> {
|
||||
pub enum BranchInfo {
|
||||
/// This is not a branch or jump instruction.
|
||||
/// This instruction will not transfer control to another block in the function, but it may still
|
||||
/// affect control flow by returning or trapping.
|
||||
NotABranch,
|
||||
|
||||
/// This is a branch or jump to a single destination block, possibly taking value arguments.
|
||||
SingleDest(Block, &'a [Value]),
|
||||
SingleDest(BlockCall),
|
||||
|
||||
/// This is a jump table branch which can have many destination blocks and maybe one default block.
|
||||
Table(JumpTable, Option<Block>),
|
||||
|
||||
@@ -46,7 +46,7 @@ pub use crate::ir::extname::{ExternalName, UserExternalName, UserFuncName};
|
||||
pub use crate::ir::function::{DisplayFunctionAnnotations, Function};
|
||||
pub use crate::ir::globalvalue::GlobalValueData;
|
||||
pub use crate::ir::instructions::{
|
||||
InstructionData, Opcode, ValueList, ValueListPool, VariableArgs,
|
||||
BlockCall, InstructionData, Opcode, ValueList, ValueListPool, VariableArgs,
|
||||
};
|
||||
pub use crate::ir::jumptable::JumpTableData;
|
||||
pub use crate::ir::known_symbol::KnownSymbol;
|
||||
|
||||
Reference in New Issue
Block a user