make ir expression based
This commit is contained in:
parent
73dd1215a8
commit
4a4d49f7de
12 changed files with 2220 additions and 873 deletions
38
src/ir.rs
38
src/ir.rs
|
|
@ -1,18 +1,25 @@
|
||||||
//! Slonik normalized SSA IR.
|
//! Slonik normalized SSA IR.
|
||||||
|
|
||||||
|
mod block;
|
||||||
mod body;
|
mod body;
|
||||||
mod dfg;
|
mod dfg;
|
||||||
mod inst;
|
mod expr;
|
||||||
|
mod frontend;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod petgraph;
|
mod stmt;
|
||||||
mod ty;
|
mod ty;
|
||||||
|
mod verify;
|
||||||
|
mod write;
|
||||||
|
|
||||||
|
pub use block::*;
|
||||||
pub use body::*;
|
pub use body::*;
|
||||||
pub use dfg::*;
|
pub use expr::*;
|
||||||
pub use inst::*;
|
pub use frontend::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
pub use petgraph::*;
|
pub use stmt::*;
|
||||||
pub use ty::*;
|
pub use ty::*;
|
||||||
|
pub use verify::*;
|
||||||
|
pub use write::*;
|
||||||
|
|
||||||
/// Defines a thin `u32` entity handle.
|
/// Defines a thin `u32` entity handle.
|
||||||
///
|
///
|
||||||
|
|
@ -40,9 +47,30 @@ entity! {
|
||||||
/// A handle to a basic block in a `Body`.
|
/// A handle to a basic block in a `Body`.
|
||||||
pub struct Block = "block";
|
pub struct Block = "block";
|
||||||
|
|
||||||
|
/// A handle to a pure expression node.
|
||||||
|
///
|
||||||
|
/// Expressions compute values and have no direct side effects.
|
||||||
|
/// They are the primary source of SSA values in the normalized IR.
|
||||||
|
pub struct Expr = "expr";
|
||||||
|
|
||||||
|
|
||||||
|
/// A handle to an ordered statement node.
|
||||||
|
///
|
||||||
|
/// Statements live inside blocks and represent side effects or control flow,
|
||||||
|
/// such as stores, branches, calls, and returns.
|
||||||
|
pub struct Stmt = "stmt";
|
||||||
|
|
||||||
/// A handle to an instruction in a `Body`.
|
/// A handle to an instruction in a `Body`.
|
||||||
pub struct Inst = "inst";
|
pub struct Inst = "inst";
|
||||||
|
|
||||||
/// A handle to an SSA value.
|
/// A handle to an SSA value.
|
||||||
|
///
|
||||||
|
/// Values are either:
|
||||||
|
///
|
||||||
|
/// - the result of an expression
|
||||||
|
/// - a block parameter
|
||||||
pub struct Value = "v";
|
pub struct Value = "v";
|
||||||
|
|
||||||
|
/// A frontend-level source variable.
|
||||||
|
pub struct Variable = "var";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
src/ir/block.rs
Normal file
63
src/ir/block.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
//! Basic-block storage for Slonik IR.
|
||||||
|
//!
|
||||||
|
//! A block is the unit of control-flow in the normalized IR.
|
||||||
|
//!
|
||||||
|
//! Each block owns:
|
||||||
|
//!
|
||||||
|
//! - a list of block parameters
|
||||||
|
//! - an ordered list of statements
|
||||||
|
//!
|
||||||
|
//! # Block parameters
|
||||||
|
//!
|
||||||
|
//! Slonik uses block arguments instead of phi nodes.
|
||||||
|
//! A predecessor transfers control to a block together with the incoming SSA
|
||||||
|
//! values for that block's parameters.
|
||||||
|
//!
|
||||||
|
//! # Statement order
|
||||||
|
//!
|
||||||
|
//! Statements inside a block are ordered.
|
||||||
|
//! Terminators, when present, must appear as the final statement in the block.
|
||||||
|
//!
|
||||||
|
//! # Scope
|
||||||
|
//!
|
||||||
|
//! This module only defines **per-block storage**.
|
||||||
|
//! It does not define:
|
||||||
|
//!
|
||||||
|
//! - global body ownership
|
||||||
|
//! - SSA value storage
|
||||||
|
//! - expression storage
|
||||||
|
//! - verification policy
|
||||||
|
//!
|
||||||
|
//! Those belong in other IR modules.
|
||||||
|
|
||||||
|
use cranelift_entity::EntityList;
|
||||||
|
|
||||||
|
use crate::ir::{Stmt, Value};
|
||||||
|
|
||||||
|
/// A compact list of block parameters, owned by the [`Body`].
|
||||||
|
pub type ParamList = EntityList<Value>;
|
||||||
|
|
||||||
|
/// A compact ordered list of statements, owned by the [`Body`].
|
||||||
|
pub type StmtList = EntityList<Stmt>;
|
||||||
|
|
||||||
|
/// Per-block storage.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct BlockData {
|
||||||
|
/// The SSA parameters accepted by this block.
|
||||||
|
pub params: ParamList,
|
||||||
|
|
||||||
|
/// The ordered statements inside this block.
|
||||||
|
pub stmts: StmtList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockData {
|
||||||
|
/// Creates an empty block.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the block has no parameters and no statements.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.params.is_empty() && self.stmts.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
260
src/ir/body.rs
260
src/ir/body.rs
|
|
@ -1,15 +1,42 @@
|
||||||
//! The owning container for a normalized Slonik IR body.
|
//! The owning container for a normalized Slonik IR body.
|
||||||
|
|
||||||
use crate::ir::{Block, DataFlowGraph, Inst, InstructionData, Layout, Type, Value};
|
use cranelift_entity::{EntityRef, ListPool, PrimaryMap, SecondaryMap};
|
||||||
|
|
||||||
|
use crate::ir::{
|
||||||
|
Block, BlockCall, BlockData, Expr, ExprData, MemSize, ParamList, Stmt, StmtData, StmtList,
|
||||||
|
Type, Value, ValueData, ValueDef, ValueList,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Per-expression storage owned by a [`Body`].
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct ExprNode {
|
||||||
|
/// The semantic payload of the expression.
|
||||||
|
pub data: ExprData,
|
||||||
|
|
||||||
|
/// The SSA value produced by this expression.
|
||||||
|
pub value: Value,
|
||||||
|
}
|
||||||
|
|
||||||
/// A normalized SSA body.
|
/// A normalized SSA body.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Body {
|
pub struct Body {
|
||||||
/// Semantic storage for blocks, instructions, values, and value lists.
|
/// All blocks in the body.
|
||||||
pub dfg: DataFlowGraph,
|
pub blocks: PrimaryMap<Block, BlockData>,
|
||||||
|
|
||||||
/// Program order and instruction/block containment.
|
/// All expressions in the body.
|
||||||
pub layout: Layout,
|
pub exprs: PrimaryMap<Expr, ExprNode>,
|
||||||
|
|
||||||
|
/// All statements in the body.
|
||||||
|
pub stmts: PrimaryMap<Stmt, StmtData>,
|
||||||
|
|
||||||
|
/// All SSA values in the body.
|
||||||
|
pub values: PrimaryMap<Value, ValueData>,
|
||||||
|
|
||||||
|
/// Pooled storage for compact value lists.
|
||||||
|
pub value_lists: ListPool<Value>,
|
||||||
|
|
||||||
|
/// Pooled storage for compact statement lists.
|
||||||
|
pub stmt_lists: ListPool<Stmt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
|
|
@ -18,102 +45,183 @@ impl Body {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the body contains no blocks.
|
/// Returns whether the body contains no blocks, expressions, statements, or values.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.dfg.num_blocks() == 0
|
self.blocks.is_empty()
|
||||||
|
&& self.exprs.is_empty()
|
||||||
|
&& self.stmts.is_empty()
|
||||||
|
&& self.values.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of blocks in the body.
|
/// Returns the number of blocks in the body.
|
||||||
pub fn block_count(&self) -> usize {
|
pub fn num_blocks(&self) -> usize {
|
||||||
self.dfg.num_blocks()
|
self.blocks.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of instructions in the body.
|
/// Returns the number of expressions in the body.
|
||||||
pub fn inst_count(&self) -> usize {
|
pub fn num_exprs(&self) -> usize {
|
||||||
self.dfg.num_insts()
|
self.exprs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of statements in the body.
|
||||||
|
pub fn num_stmts(&self) -> usize {
|
||||||
|
self.stmts.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of SSA values in the body.
|
/// Returns the number of SSA values in the body.
|
||||||
pub fn value_count(&self) -> usize {
|
pub fn num_values(&self) -> usize {
|
||||||
self.dfg.num_values()
|
self.values.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new block and appends it to the block layout order.
|
/// Creates a new empty block.
|
||||||
pub fn create_block(&mut self) -> Block {
|
pub fn create_block(&mut self) -> Block {
|
||||||
let block = self.dfg.create_block();
|
self.blocks.push(BlockData::default())
|
||||||
self.layout.append_block(block);
|
}
|
||||||
block
|
|
||||||
|
/// Returns the data for `block`.
|
||||||
|
pub fn block_data(&self, block: Block) -> &BlockData {
|
||||||
|
&self.blocks[block]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the mutable data for `block`.
|
||||||
|
pub fn block_data_mut(&mut self, block: Block) -> &mut BlockData {
|
||||||
|
&mut self.blocks[block]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over blocks in creation order.
|
||||||
|
pub fn blocks(&self) -> Blocks {
|
||||||
|
Blocks {
|
||||||
|
next: 0,
|
||||||
|
len: self.blocks.len(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends a block parameter of type `ty` to `block`.
|
/// Appends a block parameter of type `ty` to `block`.
|
||||||
///
|
|
||||||
/// The returned SSA value is defined as a block parameter.
|
|
||||||
pub fn append_block_param(&mut self, block: Block, ty: Type) -> Value {
|
pub fn append_block_param(&mut self, block: Block, ty: Type) -> Value {
|
||||||
self.dfg.append_block_param(block, ty)
|
let index = self.blocks[block].params.len(&self.value_lists) as u16;
|
||||||
|
|
||||||
|
let value = self.values.push(ValueData {
|
||||||
|
ty,
|
||||||
|
def: ValueDef::Param(block, index),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.blocks[block].params.push(value, &mut self.value_lists);
|
||||||
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an instruction with `data`, assigns SSA result values for
|
/// Returns the parameter list of `block`.
|
||||||
/// `result_tys`, and appends the instruction to the end of `block`.
|
pub fn block_params(&self, block: Block) -> &[Value] {
|
||||||
pub fn append_inst(
|
self.blocks[block].params.as_slice(&self.value_lists)
|
||||||
&mut self,
|
|
||||||
block: Block,
|
|
||||||
data: InstructionData,
|
|
||||||
result_tys: &[Type],
|
|
||||||
) -> Inst {
|
|
||||||
let inst = self.dfg.create_inst(data, result_tys);
|
|
||||||
self.layout.append_inst(block, inst);
|
|
||||||
inst
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the first block in layout order, if any.
|
/// Returns the ordered statement list of `block`.
|
||||||
pub fn first_block(&self) -> Option<Block> {
|
pub fn block_stmts(&self, block: Block) -> &[Stmt] {
|
||||||
self.layout.first_block()
|
self.blocks[block].stmts.as_slice(&self.stmt_lists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the last block in layout order, if any.
|
/// Returns the last statement in `block`, if any.
|
||||||
pub fn last_block(&self) -> Option<Block> {
|
pub fn last_stmt(&self, block: Block) -> Option<Stmt> {
|
||||||
self.layout.last_block()
|
self.block_stmts(block).last().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the last instruction in `block`, if any.
|
/// Creates a pooled value list from `values`.
|
||||||
pub fn last_inst(&self, block: Block) -> Option<Inst> {
|
pub fn make_value_list(&mut self, values: &[Value]) -> ValueList {
|
||||||
self.layout.last_inst(block)
|
ValueList::from_slice(values, &mut self.value_lists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the terminator instruction of `block`, if the block ends in one.
|
/// Creates and appends a statement to `block`.
|
||||||
pub fn block_terminator(&self, block: Block) -> Option<Inst> {
|
pub fn append_stmt(&mut self, block: Block, data: StmtData) -> Stmt {
|
||||||
let inst = self.layout.last_inst(block)?;
|
let stmt = self.stmts.push(data);
|
||||||
self.dfg.inst_data(inst).is_terminator().then_some(inst)
|
self.blocks[block].stmts.push(stmt, &mut self.stmt_lists);
|
||||||
|
stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the terminator data for `block`, if the block ends in a terminator.
|
/// Returns the data for `stmt`.
|
||||||
pub fn block_terminator_data(&self, block: Block) -> Option<&InstructionData> {
|
pub fn stmt_data(&self, stmt: Stmt) -> &StmtData {
|
||||||
let inst = self.block_terminator(block)?;
|
&self.stmts[stmt]
|
||||||
Some(self.dfg.inst_data(inst))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the successor blocks of `block`.
|
/// Returns the mutable data for `stmt`.
|
||||||
|
pub fn stmt_data_mut(&mut self, stmt: Stmt) -> &mut StmtData {
|
||||||
|
&mut self.stmts[stmt]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new expression producing one SSA value of type `ty`.
|
||||||
|
pub fn create_expr(&mut self, data: ExprData, ty: Type) -> Expr {
|
||||||
|
let expr = self.exprs.next_key();
|
||||||
|
let value = self.values.push(ValueData {
|
||||||
|
ty,
|
||||||
|
def: ValueDef::Expr(expr),
|
||||||
|
});
|
||||||
|
|
||||||
|
let expr = self.exprs.push(ExprNode { data, value });
|
||||||
|
debug_assert_eq!(self.expr_value(expr), value);
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the data for `expr`.
|
||||||
|
pub fn expr_data(&self, expr: Expr) -> &ExprData {
|
||||||
|
&self.exprs[expr].data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the mutable data for `expr`.
|
||||||
|
pub fn expr_data_mut(&mut self, expr: Expr) -> &mut ExprData {
|
||||||
|
&mut self.exprs[expr].data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the SSA value produced by `expr`.
|
||||||
|
pub fn expr_value(&self, expr: Expr) -> Value {
|
||||||
|
self.exprs[expr].value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full value record for `value`.
|
||||||
|
pub fn value_data(&self, value: Value) -> &ValueData {
|
||||||
|
&self.values[value]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the semantic type of `value`.
|
||||||
|
pub fn value_type(&self, value: Value) -> Type {
|
||||||
|
self.values[value].ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the definition site of `value`.
|
||||||
|
pub fn value_def(&self, value: Value) -> ValueDef {
|
||||||
|
self.values[value].def
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the terminator statement of `block`, if the block ends in one.
|
||||||
|
pub fn block_terminator(&self, block: Block) -> Option<Stmt> {
|
||||||
|
let stmt = self.last_stmt(block)?;
|
||||||
|
self.stmt_data(stmt).is_terminator().then_some(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the terminator data for `block`, if present.
|
||||||
|
pub fn block_terminator_data(&self, block: Block) -> Option<&StmtData> {
|
||||||
|
let stmt = self.block_terminator(block)?;
|
||||||
|
Some(self.stmt_data(stmt))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the CFG successors of `block`.
|
||||||
///
|
///
|
||||||
/// Successors are derived from the block's terminator instruction.
|
/// Successors are derived from the block's terminator statement.
|
||||||
/// Non-terminating blocks and blocks ending in `return` have no successors.
|
|
||||||
pub fn block_successors(&self, block: Block) -> BlockSuccessors {
|
pub fn block_successors(&self, block: Block) -> BlockSuccessors {
|
||||||
let Some(term) = self.block_terminator_data(block) else {
|
let Some(term) = self.block_terminator_data(block) else {
|
||||||
return BlockSuccessors::Empty;
|
return BlockSuccessors::Empty;
|
||||||
};
|
};
|
||||||
|
|
||||||
match term {
|
match term {
|
||||||
InstructionData::Jump { dst, .. } => BlockSuccessors::One(Some(dst.block)),
|
StmtData::Jump { dst } => BlockSuccessors::One(Some(dst.block)),
|
||||||
|
|
||||||
InstructionData::BrIf {
|
StmtData::BrIf {
|
||||||
then_dst, else_dst, ..
|
then_dst, else_dst, ..
|
||||||
} => BlockSuccessors::Two {
|
} => BlockSuccessors::Two {
|
||||||
first: Some(then_dst.block),
|
first: Some(then_dst.block),
|
||||||
second: Some(else_dst.block),
|
second: Some(else_dst.block),
|
||||||
},
|
},
|
||||||
|
|
||||||
InstructionData::Return { .. } => BlockSuccessors::Empty,
|
StmtData::Return { .. } => BlockSuccessors::Empty,
|
||||||
|
|
||||||
_ => BlockSuccessors::Empty,
|
StmtData::Store { .. } | StmtData::Call { .. } => BlockSuccessors::Empty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,16 +229,38 @@ impl Body {
|
||||||
pub fn block_successor_count(&self, block: Block) -> usize {
|
pub fn block_successor_count(&self, block: Block) -> usize {
|
||||||
self.block_successors(block).count()
|
self.block_successors(block).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a block call target with pooled SSA arguments.
|
||||||
|
pub fn block_call(&mut self, block: Block, args: &[Value]) -> BlockCall {
|
||||||
|
BlockCall {
|
||||||
|
block,
|
||||||
|
args: self.make_value_list(args),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over the successor blocks of a basic block.
|
/// Iterator over blocks in creation order.
|
||||||
///
|
#[derive(Clone, Debug)]
|
||||||
/// This is intentionally tiny because the normalized IR currently has very
|
pub struct Blocks {
|
||||||
/// simple terminators:
|
next: usize,
|
||||||
///
|
len: usize,
|
||||||
/// - `jump` has one successor
|
}
|
||||||
/// - `br_if` has two successors
|
|
||||||
/// - `return` has zero successors
|
impl Iterator for Blocks {
|
||||||
|
type Item = Block;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.next == self.len {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = Block::new(self.next);
|
||||||
|
self.next += 1;
|
||||||
|
Some(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator over the CFG successors of a block.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BlockSuccessors {
|
pub enum BlockSuccessors {
|
||||||
/// No successors.
|
/// No successors.
|
||||||
|
|
@ -152,9 +282,7 @@ impl Iterator for BlockSuccessors {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match self {
|
match self {
|
||||||
Self::Empty => None,
|
Self::Empty => None,
|
||||||
|
|
||||||
Self::One(slot) => slot.take(),
|
Self::One(slot) => slot.take(),
|
||||||
|
|
||||||
Self::Two { first, second } => {
|
Self::Two { first, second } => {
|
||||||
if let Some(block) = first.take() {
|
if let Some(block) = first.take() {
|
||||||
Some(block)
|
Some(block)
|
||||||
|
|
|
||||||
172
src/ir/dfg.rs
172
src/ir/dfg.rs
|
|
@ -1,173 +1 @@
|
||||||
//! Data-flow storage for Slonik IR.
|
|
||||||
|
|
||||||
use cranelift_entity::{ListPool, PrimaryMap};
|
|
||||||
|
|
||||||
use crate::ir::{Block, Inst, InstructionData, Type, Value, ValueData, ValueDef, ValueList};
|
|
||||||
|
|
||||||
/// Per-block data owned by the [`DataFlowGraph`].
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct BlockData {
|
|
||||||
/// The SSA parameters of this block.
|
|
||||||
pub params: ValueList,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Per-instruction data owned by the [`DataFlowGraph`].
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct InstData {
|
|
||||||
/// The semantic payload of the instruction.
|
|
||||||
pub data: InstructionData,
|
|
||||||
|
|
||||||
/// The SSA results defined by this instruction.
|
|
||||||
pub results: ValueList,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The semantic storage for a Slonik IR body.
|
|
||||||
///
|
|
||||||
/// The DFG owns all blocks, instructions, and SSA values, but does not own
|
|
||||||
/// their order. Program order is tracked by [`crate::ir::Layout`].
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DataFlowGraph {
|
|
||||||
/// All blocks in the body.
|
|
||||||
pub blocks: PrimaryMap<Block, BlockData>,
|
|
||||||
|
|
||||||
/// All instructions in the body.
|
|
||||||
pub insts: PrimaryMap<Inst, InstData>,
|
|
||||||
|
|
||||||
/// All SSA values in the body.
|
|
||||||
pub values: PrimaryMap<Value, ValueData>,
|
|
||||||
|
|
||||||
/// Pooled storage for compact value lists.
|
|
||||||
pub value_lists: ListPool<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataFlowGraph {
|
|
||||||
/// Creates an empty data-flow graph.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of blocks in the graph.
|
|
||||||
pub fn num_blocks(&self) -> usize {
|
|
||||||
self.blocks.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of instructions in the graph.
|
|
||||||
pub fn num_insts(&self) -> usize {
|
|
||||||
self.insts.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of SSA values in the graph.
|
|
||||||
pub fn num_values(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new empty block.
|
|
||||||
pub fn create_block(&mut self) -> Block {
|
|
||||||
self.blocks.push(BlockData::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the data for `block`.
|
|
||||||
pub fn block_data(&self, block: Block) -> &BlockData {
|
|
||||||
&self.blocks[block]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the mutable data for `block`.
|
|
||||||
pub fn block_data_mut(&mut self, block: Block) -> &mut BlockData {
|
|
||||||
&mut self.blocks[block]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the parameter values of `block`.
|
|
||||||
pub fn block_params(&self, block: Block) -> &[Value] {
|
|
||||||
self.blocks[block].params.as_slice(&self.value_lists)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Appends a block parameter of type `ty` to `block`.
|
|
||||||
///
|
|
||||||
/// The returned SSA value is defined by `ValueDef::Param(block, index)`.
|
|
||||||
pub fn append_block_param(&mut self, block: Block, ty: Type) -> Value {
|
|
||||||
let index = self.blocks[block].params.len(&self.value_lists) as u16;
|
|
||||||
|
|
||||||
let value = self.values.push(ValueData {
|
|
||||||
ty,
|
|
||||||
def: ValueDef::Param(block, index),
|
|
||||||
});
|
|
||||||
|
|
||||||
self.blocks[block].params.push(value, &mut self.value_lists);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a pooled value list from `values`.
|
|
||||||
///
|
|
||||||
/// This is mainly useful when constructing variable-arity instructions such
|
|
||||||
/// as calls or returns.
|
|
||||||
pub fn make_value_list(&mut self, values: &[Value]) -> ValueList {
|
|
||||||
ValueList::from_slice(values, &mut self.value_lists)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instruction with the given semantic payload and result
|
|
||||||
/// types.
|
|
||||||
///
|
|
||||||
/// One SSA result value is created for each entry in `result_tys`, in order.
|
|
||||||
/// Those values are recorded as `ValueDef::Result(inst, index)`.
|
|
||||||
pub fn create_inst(&mut self, data: InstructionData, result_tys: &[Type]) -> Inst {
|
|
||||||
let inst = self.insts.push(InstData {
|
|
||||||
data,
|
|
||||||
results: ValueList::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut results = ValueList::new();
|
|
||||||
for (index, ty) in result_tys.iter().copied().enumerate() {
|
|
||||||
let value = self.values.push(ValueData {
|
|
||||||
ty,
|
|
||||||
def: ValueDef::Result(inst, index as u16),
|
|
||||||
});
|
|
||||||
|
|
||||||
results.push(value, &mut self.value_lists);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.insts[inst].results = results;
|
|
||||||
inst
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the data for `inst`.
|
|
||||||
pub fn inst_data(&self, inst: Inst) -> &InstructionData {
|
|
||||||
&self.insts[inst].data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the mutable data for `inst`.
|
|
||||||
pub fn inst_data_mut(&mut self, inst: Inst) -> &mut InstructionData {
|
|
||||||
&mut self.insts[inst].data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replaces the semantic payload of `inst`.
|
|
||||||
///
|
|
||||||
/// This does not change the instruction’s existing result values.
|
|
||||||
pub fn replace_inst_data(&mut self, inst: Inst, data: InstructionData) {
|
|
||||||
self.insts[inst].data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the SSA results defined by `inst`.
|
|
||||||
pub fn inst_results(&self, inst: Inst) -> &[Value] {
|
|
||||||
self.insts[inst].results.as_slice(&self.value_lists)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the first SSA result of `inst`, if any.
|
|
||||||
pub fn first_result(&self, inst: Inst) -> Option<Value> {
|
|
||||||
self.insts[inst].results.first(&self.value_lists)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the full value record for `value`.
|
|
||||||
pub fn value_data(&self, value: Value) -> &ValueData {
|
|
||||||
&self.values[value]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the type of `value`.
|
|
||||||
pub fn value_type(&self, value: Value) -> Type {
|
|
||||||
self.values[value].ty
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the definition site of `value`.
|
|
||||||
pub fn value_def(&self, value: Value) -> ValueDef {
|
|
||||||
self.values[value].def
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
396
src/ir/expr.rs
Normal file
396
src/ir/expr.rs
Normal file
|
|
@ -0,0 +1,396 @@
|
||||||
|
//! Pure expression nodes and SSA value definitions for Slonik IR.
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use crate::ir::{Block, Expr, Type, Value};
|
||||||
|
|
||||||
|
/// Memory access width in bytes.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MemSize {
|
||||||
|
S1,
|
||||||
|
S2,
|
||||||
|
S4,
|
||||||
|
S8,
|
||||||
|
S16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemSize {
|
||||||
|
/// Returns the access width in bytes.
|
||||||
|
pub const fn bytes(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Self::S1 => 1,
|
||||||
|
Self::S2 => 2,
|
||||||
|
Self::S4 => 4,
|
||||||
|
Self::S8 => 8,
|
||||||
|
Self::S16 => 16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the access width in bits.
|
||||||
|
pub const fn bits(self) -> u16 {
|
||||||
|
(self.bytes() as u16) * 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MemSize {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}B", self.bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Integer comparison condition codes.
|
||||||
|
///
|
||||||
|
/// These are used by [`ExprData::Icmp`].
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum IntCC {
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
|
||||||
|
Ult,
|
||||||
|
Ule,
|
||||||
|
Ugt,
|
||||||
|
Uge,
|
||||||
|
|
||||||
|
Slt,
|
||||||
|
Sle,
|
||||||
|
Sgt,
|
||||||
|
Sge,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntCC {
|
||||||
|
/// Returns the logical inverse of this condition code.
|
||||||
|
pub const fn invert(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Eq => Self::Ne,
|
||||||
|
Self::Ne => Self::Eq,
|
||||||
|
|
||||||
|
Self::Ult => Self::Uge,
|
||||||
|
Self::Ule => Self::Ugt,
|
||||||
|
Self::Ugt => Self::Ule,
|
||||||
|
Self::Uge => Self::Ult,
|
||||||
|
|
||||||
|
Self::Slt => Self::Sge,
|
||||||
|
Self::Sle => Self::Sgt,
|
||||||
|
Self::Sgt => Self::Sle,
|
||||||
|
Self::Sge => Self::Slt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the condition code obtained by swapping the operands.
|
||||||
|
pub const fn swap_args(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Eq => Self::Eq,
|
||||||
|
Self::Ne => Self::Ne,
|
||||||
|
|
||||||
|
Self::Ult => Self::Ugt,
|
||||||
|
Self::Ule => Self::Uge,
|
||||||
|
Self::Ugt => Self::Ult,
|
||||||
|
Self::Uge => Self::Ule,
|
||||||
|
|
||||||
|
Self::Slt => Self::Sgt,
|
||||||
|
Self::Sle => Self::Sge,
|
||||||
|
Self::Sgt => Self::Slt,
|
||||||
|
Self::Sge => Self::Sle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for IntCC {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Self::Eq => "eq",
|
||||||
|
Self::Ne => "ne",
|
||||||
|
|
||||||
|
Self::Ult => "ult",
|
||||||
|
Self::Ule => "ule",
|
||||||
|
Self::Ugt => "ugt",
|
||||||
|
Self::Uge => "uge",
|
||||||
|
|
||||||
|
Self::Slt => "slt",
|
||||||
|
Self::Sle => "sle",
|
||||||
|
Self::Sgt => "sgt",
|
||||||
|
Self::Sge => "sge",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Floating-point comparison condition codes.
|
||||||
|
///
|
||||||
|
/// These are used by [`ExprData::Fcmp`].
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum FloatCC {
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
Lt,
|
||||||
|
Le,
|
||||||
|
Gt,
|
||||||
|
Ge,
|
||||||
|
Ordered,
|
||||||
|
Unordered,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FloatCC {
|
||||||
|
/// Returns the logical inverse of this condition code.
|
||||||
|
pub const fn invert(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Eq => Self::Ne,
|
||||||
|
Self::Ne => Self::Eq,
|
||||||
|
Self::Lt => Self::Ge,
|
||||||
|
Self::Le => Self::Gt,
|
||||||
|
Self::Gt => Self::Le,
|
||||||
|
Self::Ge => Self::Lt,
|
||||||
|
Self::Ordered => Self::Unordered,
|
||||||
|
Self::Unordered => Self::Ordered,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the condition code obtained by swapping the operands.
|
||||||
|
pub const fn swap_args(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Eq => Self::Eq,
|
||||||
|
Self::Ne => Self::Ne,
|
||||||
|
Self::Lt => Self::Gt,
|
||||||
|
Self::Le => Self::Ge,
|
||||||
|
Self::Gt => Self::Lt,
|
||||||
|
Self::Ge => Self::Le,
|
||||||
|
Self::Ordered => Self::Ordered,
|
||||||
|
Self::Unordered => Self::Unordered,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FloatCC {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Self::Eq => "eq",
|
||||||
|
Self::Ne => "ne",
|
||||||
|
Self::Lt => "lt",
|
||||||
|
Self::Le => "le",
|
||||||
|
Self::Gt => "gt",
|
||||||
|
Self::Ge => "ge",
|
||||||
|
Self::Ordered => "ord",
|
||||||
|
Self::Unordered => "uno",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unary expression operators.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum UnaryOp {
|
||||||
|
/// Integer or bitvector negation.
|
||||||
|
Neg,
|
||||||
|
|
||||||
|
/// Bitwise not.
|
||||||
|
Not,
|
||||||
|
|
||||||
|
/// Floating-point negation.
|
||||||
|
FNeg,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UnaryOp {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Self::Neg => "neg",
|
||||||
|
Self::Not => "not",
|
||||||
|
Self::FNeg => "fneg",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Binary expression operators.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum BinaryOp {
|
||||||
|
IAdd,
|
||||||
|
ISub,
|
||||||
|
IMul,
|
||||||
|
UDiv,
|
||||||
|
SDiv,
|
||||||
|
URem,
|
||||||
|
SRem,
|
||||||
|
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
Xor,
|
||||||
|
|
||||||
|
Shl,
|
||||||
|
LShr,
|
||||||
|
AShr,
|
||||||
|
|
||||||
|
FAdd,
|
||||||
|
FSub,
|
||||||
|
FMul,
|
||||||
|
FDiv,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryOp {
|
||||||
|
/// Returns whether this binary operator is commutative.
|
||||||
|
pub const fn is_commutative(self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::IAdd | Self::IMul | Self::And | Self::Or | Self::Xor | Self::FAdd | Self::FMul
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BinaryOp {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Self::IAdd => "iadd",
|
||||||
|
Self::ISub => "isub",
|
||||||
|
Self::IMul => "imul",
|
||||||
|
Self::UDiv => "udiv",
|
||||||
|
Self::SDiv => "sdiv",
|
||||||
|
Self::URem => "urem",
|
||||||
|
Self::SRem => "srem",
|
||||||
|
|
||||||
|
Self::And => "and",
|
||||||
|
Self::Or => "or",
|
||||||
|
Self::Xor => "xor",
|
||||||
|
|
||||||
|
Self::Shl => "shl",
|
||||||
|
Self::LShr => "lshr",
|
||||||
|
Self::AShr => "ashr",
|
||||||
|
|
||||||
|
Self::FAdd => "fadd",
|
||||||
|
Self::FSub => "fsub",
|
||||||
|
Self::FMul => "fmul",
|
||||||
|
Self::FDiv => "fdiv",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast and conversion operators.
|
||||||
|
///
|
||||||
|
/// The source type is taken from the operand value.
|
||||||
|
/// The destination type is taken from the result value.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CastOp {
|
||||||
|
/// Zero-extension.
|
||||||
|
Zext,
|
||||||
|
|
||||||
|
/// Sign-extension.
|
||||||
|
Sext,
|
||||||
|
|
||||||
|
/// Truncation.
|
||||||
|
Trunc,
|
||||||
|
|
||||||
|
/// Bit-preserving reinterpretation.
|
||||||
|
Bitcast,
|
||||||
|
|
||||||
|
/// Integer-to-pointer conversion.
|
||||||
|
IntToPtr,
|
||||||
|
|
||||||
|
/// Pointer-to-integer conversion.
|
||||||
|
PtrToInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CastOp {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
Self::Zext => "zext",
|
||||||
|
Self::Sext => "sext",
|
||||||
|
Self::Trunc => "trunc",
|
||||||
|
Self::Bitcast => "bitcast",
|
||||||
|
Self::IntToPtr => "inttoptr",
|
||||||
|
Self::PtrToInt => "ptrtoint",
|
||||||
|
};
|
||||||
|
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pure value-producing expression node.
|
||||||
|
///
|
||||||
|
/// Every expression defines exactly one SSA value.
|
||||||
|
/// The type of that value is stored in the corresponding [`ValueData`].
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ExprData {
|
||||||
|
/// A signed integer literal.
|
||||||
|
///
|
||||||
|
/// The result type determines the intended width.
|
||||||
|
IConst { imm: i64 },
|
||||||
|
|
||||||
|
/// A 32-bit floating-point literal stored as raw IEEE-754 bits.
|
||||||
|
F32Const { bits: u32 },
|
||||||
|
|
||||||
|
/// A 64-bit floating-point literal stored as raw IEEE-754 bits.
|
||||||
|
F64Const { bits: u64 },
|
||||||
|
|
||||||
|
/// A boolean literal.
|
||||||
|
BConst { value: bool },
|
||||||
|
|
||||||
|
/// A unary operation.
|
||||||
|
Unary { op: UnaryOp, arg: Value },
|
||||||
|
|
||||||
|
/// A binary operation.
|
||||||
|
Binary {
|
||||||
|
op: BinaryOp,
|
||||||
|
lhs: Value,
|
||||||
|
rhs: Value,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A cast or conversion.
|
||||||
|
Cast { op: CastOp, arg: Value },
|
||||||
|
|
||||||
|
/// An integer comparison.
|
||||||
|
///
|
||||||
|
/// The result type is expected to be `bool`.
|
||||||
|
Icmp { cc: IntCC, lhs: Value, rhs: Value },
|
||||||
|
|
||||||
|
/// A floating-point comparison.
|
||||||
|
///
|
||||||
|
/// The result type is expected to be `bool`.
|
||||||
|
Fcmp { cc: FloatCC, lhs: Value, rhs: Value },
|
||||||
|
|
||||||
|
/// A conditional select.
|
||||||
|
///
|
||||||
|
/// `cond` must be a boolean value.
|
||||||
|
Select {
|
||||||
|
cond: Value,
|
||||||
|
if_true: Value,
|
||||||
|
if_false: Value,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A memory load.
|
||||||
|
Load { addr: Value, size: MemSize },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExprData {
|
||||||
|
/// Returns whether this expression may observe memory.
|
||||||
|
pub const fn may_read_memory(&self) -> bool {
|
||||||
|
matches!(self, Self::Load { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this expression may trap.
|
||||||
|
pub const fn may_trap(&self) -> bool {
|
||||||
|
matches!(self, Self::Load { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The definition site of an SSA value.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ValueDef {
|
||||||
|
/// A value defined by an expression.
|
||||||
|
Expr(Expr),
|
||||||
|
|
||||||
|
/// A block parameter at position `index`.
|
||||||
|
Param(Block, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata attached to an SSA value.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ValueData {
|
||||||
|
/// The semantic type of the value.
|
||||||
|
pub ty: Type,
|
||||||
|
|
||||||
|
/// The definition site of the value.
|
||||||
|
pub def: ValueDef,
|
||||||
|
}
|
||||||
545
src/ir/frontend.rs
Normal file
545
src/ir/frontend.rs
Normal file
|
|
@ -0,0 +1,545 @@
|
||||||
|
//! A frontend-oriented SSA builder for Slonik IR.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::ir::{
|
||||||
|
BinaryOp, Block, Body, CastOp, ExprData, FloatCC, IntCC, MemSize, Stmt, StmtData, Type,
|
||||||
|
UnaryOp, Value, Variable,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
enum IncomingEdgeKind {
|
||||||
|
Jump,
|
||||||
|
Then,
|
||||||
|
Else,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct IncomingEdge {
|
||||||
|
pred: Block,
|
||||||
|
stmt: Stmt,
|
||||||
|
kind: IncomingEdgeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct BlockState {
|
||||||
|
/// Whether all predecessors of this block are known.
|
||||||
|
sealed: bool,
|
||||||
|
|
||||||
|
/// Whether a terminator has already been inserted into this block.
|
||||||
|
filled: bool,
|
||||||
|
|
||||||
|
/// Incoming CFG edges that target this block.
|
||||||
|
preds: Vec<IncomingEdge>,
|
||||||
|
|
||||||
|
/// Current SSA definitions visible at the end of this block.
|
||||||
|
defs: HashMap<Variable, Value>,
|
||||||
|
|
||||||
|
/// The variables that have synthesized block parameters in this block,
|
||||||
|
/// in block-parameter order.
|
||||||
|
param_order: Vec<Variable>,
|
||||||
|
|
||||||
|
/// Mapping from variable to its synthesized block parameter value.
|
||||||
|
params: HashMap<Variable, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reusable scratch state for [`FrontendBuilder`].
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FrontendBuilderContext {
|
||||||
|
vars: HashMap<Variable, Type>,
|
||||||
|
blocks: HashMap<Block, BlockState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrontendBuilderContext {
|
||||||
|
/// Creates an empty reusable frontend-builder context.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all stored frontend state.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.vars.clear();
|
||||||
|
self.blocks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A frontend-oriented builder for Slonik IR.
|
||||||
|
pub struct FrontendBuilder<'a> {
|
||||||
|
body: &'a mut Body,
|
||||||
|
ctx: &'a mut FrontendBuilderContext,
|
||||||
|
cur_block: Option<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FrontendBuilder<'a> {
|
||||||
|
/// Creates a new frontend-oriented builder.
|
||||||
|
pub fn new(body: &'a mut Body, ctx: &'a mut FrontendBuilderContext) -> Self {
|
||||||
|
Self {
|
||||||
|
body,
|
||||||
|
ctx,
|
||||||
|
cur_block: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a shared reference to the underlying body.
|
||||||
|
pub fn body(&self) -> &Body {
|
||||||
|
self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the underlying body.
|
||||||
|
pub fn body_mut(&mut self) -> &mut Body {
|
||||||
|
self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new block.
|
||||||
|
pub fn create_block(&mut self) -> Block {
|
||||||
|
let block = self.body.create_block();
|
||||||
|
self.ctx.blocks.entry(block).or_default();
|
||||||
|
block
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches insertion to `block`.
|
||||||
|
pub fn switch_to_block(&mut self, block: Block) {
|
||||||
|
self.ctx.blocks.entry(block).or_default();
|
||||||
|
self.cur_block = Some(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current insertion block, if any.
|
||||||
|
pub fn current_block(&self) -> Option<Block> {
|
||||||
|
self.cur_block
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declares a frontend variable with semantic type `ty`.
|
||||||
|
pub fn declare_var(&mut self, var: Variable, ty: Type) {
|
||||||
|
self.ctx.vars.insert(var, ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether `var` has been declared.
|
||||||
|
pub fn is_var_declared(&self, var: Variable) -> bool {
|
||||||
|
self.ctx.vars.contains_key(&var)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the declared type of `var`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `var` was not declared first.
|
||||||
|
pub fn var_type(&self, var: Variable) -> Type {
|
||||||
|
*self
|
||||||
|
.ctx
|
||||||
|
.vars
|
||||||
|
.get(&var)
|
||||||
|
.expect("attempted to query undeclared variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether `block` has been sealed.
|
||||||
|
pub fn is_sealed(&self, block: Block) -> bool {
|
||||||
|
self.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&block)
|
||||||
|
.map(|s| s.sealed)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether `block` already has a terminator.
|
||||||
|
pub fn is_filled(&self, block: Block) -> bool {
|
||||||
|
self.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&block)
|
||||||
|
.map(|s| s.filled)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks `block` as sealed.
|
||||||
|
///
|
||||||
|
/// Sealing a block means all of its predecessors are now known.
|
||||||
|
pub fn seal_block(&mut self, block: Block) {
|
||||||
|
self.ctx.blocks.entry(block).or_default().sealed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks every known block as sealed.
|
||||||
|
pub fn seal_all_blocks(&mut self) {
|
||||||
|
for state in self.ctx.blocks.values_mut() {
|
||||||
|
state.sealed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assigns SSA value `value` to frontend variable `var` in the current block.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if:
|
||||||
|
///
|
||||||
|
/// - no current block is selected
|
||||||
|
/// - `var` is undeclared
|
||||||
|
/// - the value's type does not match the variable's declared type
|
||||||
|
pub fn def_var(&mut self, var: Variable, value: Value) {
|
||||||
|
let block = self
|
||||||
|
.cur_block
|
||||||
|
.expect("attempted to define a variable without a current block");
|
||||||
|
|
||||||
|
let expected = self.var_type(var);
|
||||||
|
let actual = self.body.value_type(value);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
expected == actual,
|
||||||
|
"type mismatch in def_var: variable declared as {expected}, got value of type {actual}",
|
||||||
|
);
|
||||||
|
|
||||||
|
self.ctx
|
||||||
|
.blocks
|
||||||
|
.entry(block)
|
||||||
|
.or_default()
|
||||||
|
.defs
|
||||||
|
.insert(var, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the current SSA value for frontend variable `var`.
|
||||||
|
///
|
||||||
|
/// This may recursively resolve definitions across predecessor blocks and
|
||||||
|
/// may synthesize block parameters as needed.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if:
|
||||||
|
///
|
||||||
|
/// - no current block is selected
|
||||||
|
/// - `var` is undeclared
|
||||||
|
pub fn use_var(&mut self, var: Variable) -> Value {
|
||||||
|
let block = self
|
||||||
|
.cur_block
|
||||||
|
.expect("attempted to use a variable without a current block");
|
||||||
|
self.use_var_in_block(block, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_var_in_block(&mut self, block: Block, var: Variable) -> Value {
|
||||||
|
if let Some(value) = self
|
||||||
|
.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&block)
|
||||||
|
.and_then(|state| state.defs.get(&var).copied())
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ty = self.var_type(var);
|
||||||
|
|
||||||
|
let preds = self
|
||||||
|
.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&block)
|
||||||
|
.map(|state| state.preds.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// If the block is sealed and has no predecessors, this is a genuine
|
||||||
|
// unbound use: there is nowhere to get the value from.
|
||||||
|
if self.is_sealed(block) && preds.is_empty() {
|
||||||
|
panic!(
|
||||||
|
"variable {var} used in sealed block {block} with no local definition and no predecessors"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any non-local use becomes a block parameter, regardless of predecessor
|
||||||
|
// count. This keeps cross-block dataflow fully explicit in block arguments,
|
||||||
|
// even for single-predecessor blocks.
|
||||||
|
self.ensure_block_param(block, var, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_block_param(&mut self, block: Block, var: Variable, ty: Type) -> Value {
|
||||||
|
if let Some(value) = self
|
||||||
|
.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&block)
|
||||||
|
.and_then(|state| state.params.get(&var).copied())
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = self.body.append_block_param(block, ty);
|
||||||
|
|
||||||
|
{
|
||||||
|
let state = self.ctx.blocks.entry(block).or_default();
|
||||||
|
state.param_order.push(var);
|
||||||
|
state.params.insert(var, value);
|
||||||
|
state.defs.insert(var, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let preds = self
|
||||||
|
.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&block)
|
||||||
|
.map(|state| state.preds.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
for edge in preds {
|
||||||
|
let arg = self.use_var_in_block(edge.pred, var);
|
||||||
|
self.append_edge_arg(edge, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_edge_arg(&mut self, edge: IncomingEdge, arg: Value) {
|
||||||
|
let body = self.body_mut();
|
||||||
|
|
||||||
|
let Body {
|
||||||
|
stmts, value_lists, ..
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
match (&mut stmts[edge.stmt], edge.kind) {
|
||||||
|
(StmtData::Jump { dst }, IncomingEdgeKind::Jump) => {
|
||||||
|
dst.args.push(arg, value_lists);
|
||||||
|
}
|
||||||
|
|
||||||
|
(StmtData::BrIf { then_dst, .. }, IncomingEdgeKind::Then) => {
|
||||||
|
then_dst.args.push(arg, value_lists);
|
||||||
|
}
|
||||||
|
|
||||||
|
(StmtData::BrIf { else_dst, .. }, IncomingEdgeKind::Else) => {
|
||||||
|
else_dst.args.push(arg, value_lists);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
panic!("frontend predecessor bookkeeping became inconsistent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_incoming_edge(
|
||||||
|
&mut self,
|
||||||
|
dst: Block,
|
||||||
|
pred: Block,
|
||||||
|
stmt: Stmt,
|
||||||
|
kind: IncomingEdgeKind,
|
||||||
|
) {
|
||||||
|
self.ctx
|
||||||
|
.blocks
|
||||||
|
.entry(dst)
|
||||||
|
.or_default()
|
||||||
|
.preds
|
||||||
|
.push(IncomingEdge { pred, stmt, kind });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edge_args_to(&mut self, dst: Block) -> Vec<Value> {
|
||||||
|
let vars = self
|
||||||
|
.ctx
|
||||||
|
.blocks
|
||||||
|
.get(&dst)
|
||||||
|
.map(|state| state.param_order.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
vars.into_iter().map(|var| self.use_var(var)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_stmt_in_current_block(&mut self, data: StmtData) -> Stmt {
|
||||||
|
let block = self
|
||||||
|
.cur_block
|
||||||
|
.expect("attempted to append a statement without a current block");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!self.is_filled(block),
|
||||||
|
"attempted to append a statement after the block was already terminated"
|
||||||
|
);
|
||||||
|
|
||||||
|
let is_terminator = data.is_terminator();
|
||||||
|
let stmt = self.body.append_stmt(block, data);
|
||||||
|
|
||||||
|
if is_terminator {
|
||||||
|
self.ctx.blocks.entry(block).or_default().filled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_expr_value(&mut self, data: ExprData, ty: Type) -> Value {
|
||||||
|
let expr = self.body.create_expr(data, ty);
|
||||||
|
self.body.expr_value(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an integer constant expression.
|
||||||
|
pub fn iconst(&mut self, ty: Type, imm: i64) -> Value {
|
||||||
|
self.create_expr_value(ExprData::IConst { imm }, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a 32-bit floating-point constant expression from raw IEEE-754 bits.
|
||||||
|
pub fn f32const_bits(&mut self, bits: u32) -> Value {
|
||||||
|
self.create_expr_value(ExprData::F32Const { bits }, Type::f32())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a 64-bit floating-point constant expression from raw IEEE-754 bits.
|
||||||
|
pub fn f64const_bits(&mut self, bits: u64) -> Value {
|
||||||
|
self.create_expr_value(ExprData::F64Const { bits }, Type::f64())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a boolean constant expression.
|
||||||
|
pub fn bconst(&mut self, value: bool) -> Value {
|
||||||
|
self.create_expr_value(ExprData::BConst { value }, Type::bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a unary expression.
|
||||||
|
fn unary(&mut self, op: UnaryOp, arg: Value, ty: Type) -> Value {
|
||||||
|
self.create_expr_value(ExprData::Unary { op, arg }, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a binary expression.
|
||||||
|
fn binary(&mut self, op: BinaryOp, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.create_expr_value(ExprData::Binary { op, lhs, rhs }, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a cast expression.
|
||||||
|
pub fn cast(&mut self, op: CastOp, arg: Value, ty: Type) -> Value {
|
||||||
|
self.create_expr_value(ExprData::Cast { op, arg }, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an integer comparison expression with boolean result.
|
||||||
|
pub fn icmp(&mut self, cc: IntCC, lhs: Value, rhs: Value) -> Value {
|
||||||
|
self.create_expr_value(ExprData::Icmp { cc, lhs, rhs }, Type::bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a floating-point comparison expression with boolean result.
|
||||||
|
pub fn fcmp(&mut self, cc: FloatCC, lhs: Value, rhs: Value) -> Value {
|
||||||
|
self.create_expr_value(ExprData::Fcmp { cc, lhs, rhs }, Type::bool())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a `select` expression.
|
||||||
|
pub fn select(&mut self, cond: Value, if_true: Value, if_false: Value, ty: Type) -> Value {
|
||||||
|
self.create_expr_value(
|
||||||
|
ExprData::Select {
|
||||||
|
cond,
|
||||||
|
if_true,
|
||||||
|
if_false,
|
||||||
|
},
|
||||||
|
ty,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a memory load expression.
|
||||||
|
pub fn load(&mut self, addr: Value, size: MemSize, ty: Type) -> Value {
|
||||||
|
self.create_expr_value(ExprData::Load { addr, size }, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an integer add expression.
|
||||||
|
pub fn iadd(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::IAdd, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an integer subtract expression.
|
||||||
|
pub fn isub(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::ISub, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an integer multiply expression.
|
||||||
|
pub fn imul(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::IMul, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a bitwise and expression.
|
||||||
|
pub fn and(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::And, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a bitwise or expression.
|
||||||
|
pub fn or(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::Or, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a bitwise xor expression.
|
||||||
|
pub fn xor(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::Xor, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a logical shift-left expression.
|
||||||
|
pub fn shl(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::Shl, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a logical shift-right expression.
|
||||||
|
pub fn lshr(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::LShr, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an arithmetic shift-right expression.
|
||||||
|
pub fn ashr(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
|
self.binary(BinaryOp::AShr, lhs, rhs, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a zero-extension expression.
|
||||||
|
pub fn zext(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
|
self.cast(CastOp::Zext, arg, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a sign-extension expression.
|
||||||
|
pub fn sext(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
|
self.cast(CastOp::Sext, arg, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a truncation expression.
|
||||||
|
pub fn trunc(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
|
self.cast(CastOp::Trunc, arg, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a bitcast expression.
|
||||||
|
pub fn bitcast(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
|
self.cast(CastOp::Bitcast, arg, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a memory store statement.
|
||||||
|
pub fn store(&mut self, addr: Value, value: Value, size: MemSize) -> Stmt {
|
||||||
|
self.append_stmt_in_current_block(StmtData::Store { addr, value, size })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends an effectful call statement.
|
||||||
|
pub fn call(&mut self, callee: Value, args: &[Value]) -> Stmt {
|
||||||
|
let args = self.body.make_value_list(args);
|
||||||
|
self.append_stmt_in_current_block(StmtData::Call { callee, args })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends an unconditional jump to `dst`.
|
||||||
|
///
|
||||||
|
/// The builder automatically supplies block arguments for all synthesized
|
||||||
|
/// block parameters currently known on `dst`.
|
||||||
|
pub fn jump(&mut self, dst: Block) -> Stmt {
|
||||||
|
let pred = self
|
||||||
|
.cur_block
|
||||||
|
.expect("attempted to append jump without a current block");
|
||||||
|
|
||||||
|
let args = self.edge_args_to(dst);
|
||||||
|
let dst_call = self.body.block_call(dst, &args);
|
||||||
|
let stmt = self.append_stmt_in_current_block(StmtData::Jump { dst: dst_call });
|
||||||
|
|
||||||
|
self.record_incoming_edge(dst, pred, stmt, IncomingEdgeKind::Jump);
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a conditional branch.
|
||||||
|
///
|
||||||
|
/// The builder automatically supplies block arguments for all synthesized
|
||||||
|
/// block parameters currently known on each destination block.
|
||||||
|
pub fn br_if(&mut self, cond: Value, then_block: Block, else_block: Block) -> Stmt {
|
||||||
|
let pred = self
|
||||||
|
.cur_block
|
||||||
|
.expect("attempted to append br_if without a current block");
|
||||||
|
|
||||||
|
let then_args = self.edge_args_to(then_block);
|
||||||
|
let else_args = self.edge_args_to(else_block);
|
||||||
|
|
||||||
|
let then_dst = self.body.block_call(then_block, &then_args);
|
||||||
|
let else_dst = self.body.block_call(else_block, &else_args);
|
||||||
|
|
||||||
|
let stmt = self.append_stmt_in_current_block(StmtData::BrIf {
|
||||||
|
cond,
|
||||||
|
then_dst,
|
||||||
|
else_dst,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.record_incoming_edge(then_block, pred, stmt, IncomingEdgeKind::Then);
|
||||||
|
self.record_incoming_edge(else_block, pred, stmt, IncomingEdgeKind::Else);
|
||||||
|
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a return statement.
|
||||||
|
pub fn ret(&mut self, values: &[Value]) -> Stmt {
|
||||||
|
let values = self.body.make_value_list(values);
|
||||||
|
self.append_stmt_in_current_block(StmtData::Return { values })
|
||||||
|
}
|
||||||
|
}
|
||||||
413
src/ir/inst.rs
413
src/ir/inst.rs
|
|
@ -1,413 +0,0 @@
|
||||||
//! Instruction opcodes and instruction data formats for Slonik.
|
|
||||||
|
|
||||||
use core::fmt;
|
|
||||||
use cranelift_entity::EntityList;
|
|
||||||
|
|
||||||
use crate::ir::{Block, Inst, Type, Value};
|
|
||||||
|
|
||||||
/// A compact list of SSA values.
|
|
||||||
///
|
|
||||||
/// These lists are stored in a `ListPool` owned by the data-flow graph.
|
|
||||||
pub type ValueList = EntityList<Value>;
|
|
||||||
|
|
||||||
/// A compact list of blocks.
|
|
||||||
///
|
|
||||||
/// This is primarily useful for generic control-flow helpers and side tables.
|
|
||||||
pub type BlockList = EntityList<Block>;
|
|
||||||
|
|
||||||
/// A target block together with the SSA arguments passed to that block.
|
|
||||||
///
|
|
||||||
/// Slonik IR uses block arguments instead of phi nodes. A terminator that
|
|
||||||
/// transfers control to a block also provides the values for that block's
|
|
||||||
/// parameters.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct BlockCall {
|
|
||||||
/// The destination block.
|
|
||||||
pub block: Block,
|
|
||||||
|
|
||||||
/// The arguments passed to the destination block.
|
|
||||||
pub args: ValueList,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Integer comparison condition codes.
|
|
||||||
///
|
|
||||||
/// These are used by `Opcode::Icmp`.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum IntCC {
|
|
||||||
/// Equal.
|
|
||||||
Eq,
|
|
||||||
|
|
||||||
/// Not equal.
|
|
||||||
Ne,
|
|
||||||
|
|
||||||
/// Unsigned less than.
|
|
||||||
Ult,
|
|
||||||
|
|
||||||
/// Unsigned less than or equal.
|
|
||||||
Ule,
|
|
||||||
|
|
||||||
/// Unsigned greater than.
|
|
||||||
Ugt,
|
|
||||||
|
|
||||||
/// Unsigned greater than or equal.
|
|
||||||
Uge,
|
|
||||||
|
|
||||||
/// Signed less than.
|
|
||||||
Slt,
|
|
||||||
|
|
||||||
/// Signed less than or equal.
|
|
||||||
Sle,
|
|
||||||
|
|
||||||
/// Signed greater than.
|
|
||||||
Sgt,
|
|
||||||
|
|
||||||
/// Signed greater than or equal.
|
|
||||||
Sge,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntCC {
|
|
||||||
/// Returns the logical inverse of this condition code.
|
|
||||||
pub const fn invert(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Eq => Self::Ne,
|
|
||||||
Self::Ne => Self::Eq,
|
|
||||||
Self::Ult => Self::Uge,
|
|
||||||
Self::Ule => Self::Ugt,
|
|
||||||
Self::Ugt => Self::Ule,
|
|
||||||
Self::Uge => Self::Ult,
|
|
||||||
Self::Slt => Self::Sge,
|
|
||||||
Self::Sle => Self::Sgt,
|
|
||||||
Self::Sgt => Self::Sle,
|
|
||||||
Self::Sge => Self::Slt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the condition code with operands swapped.
|
|
||||||
pub const fn swap_args(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Eq => Self::Eq,
|
|
||||||
Self::Ne => Self::Ne,
|
|
||||||
Self::Ult => Self::Ugt,
|
|
||||||
Self::Ule => Self::Uge,
|
|
||||||
Self::Ugt => Self::Ult,
|
|
||||||
Self::Uge => Self::Ule,
|
|
||||||
Self::Slt => Self::Sgt,
|
|
||||||
Self::Sle => Self::Sge,
|
|
||||||
Self::Sgt => Self::Slt,
|
|
||||||
Self::Sge => Self::Sle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for IntCC {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
Self::Eq => "eq",
|
|
||||||
Self::Ne => "ne",
|
|
||||||
Self::Ult => "ult",
|
|
||||||
Self::Ule => "ule",
|
|
||||||
Self::Ugt => "ugt",
|
|
||||||
Self::Uge => "uge",
|
|
||||||
Self::Slt => "slt",
|
|
||||||
Self::Sle => "sle",
|
|
||||||
Self::Sgt => "sgt",
|
|
||||||
Self::Sge => "sge",
|
|
||||||
};
|
|
||||||
|
|
||||||
f.write_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The set of operations that generic analysis and decompilation passes
|
|
||||||
/// are expected to understand.
|
|
||||||
///
|
|
||||||
/// If a frontend encounters something that does not fit naturally here, that is
|
|
||||||
/// a sign it should stay in the lifted IR longer or be lowered through a helper
|
|
||||||
/// sequence before entering this IR.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Opcode {
|
|
||||||
Iconst,
|
|
||||||
F32const,
|
|
||||||
F64const,
|
|
||||||
Bconst,
|
|
||||||
|
|
||||||
Zext,
|
|
||||||
Sext,
|
|
||||||
Trunc,
|
|
||||||
Bitcast,
|
|
||||||
|
|
||||||
Iadd,
|
|
||||||
Isub,
|
|
||||||
Imul,
|
|
||||||
Udiv,
|
|
||||||
Sdiv,
|
|
||||||
Urem,
|
|
||||||
Srem,
|
|
||||||
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
Xor,
|
|
||||||
Not,
|
|
||||||
|
|
||||||
Shl,
|
|
||||||
Lshr,
|
|
||||||
Ashr,
|
|
||||||
|
|
||||||
Fadd,
|
|
||||||
Fsub,
|
|
||||||
Fmul,
|
|
||||||
Fdiv,
|
|
||||||
Fneg,
|
|
||||||
|
|
||||||
Icmp,
|
|
||||||
Select,
|
|
||||||
|
|
||||||
Load,
|
|
||||||
Store,
|
|
||||||
|
|
||||||
Jump,
|
|
||||||
BrIf,
|
|
||||||
Call,
|
|
||||||
Return,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opcode {
|
|
||||||
/// Returns the textual name of this opcode.
|
|
||||||
pub const fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Iconst => "iconst",
|
|
||||||
Self::F32const => "f32const",
|
|
||||||
Self::F64const => "f64const",
|
|
||||||
Self::Bconst => "bconst",
|
|
||||||
|
|
||||||
Self::Zext => "zext",
|
|
||||||
Self::Sext => "sext",
|
|
||||||
Self::Trunc => "trunc",
|
|
||||||
Self::Bitcast => "bitcast",
|
|
||||||
|
|
||||||
Self::Iadd => "iadd",
|
|
||||||
Self::Isub => "isub",
|
|
||||||
Self::Imul => "imul",
|
|
||||||
Self::Udiv => "udiv",
|
|
||||||
Self::Sdiv => "sdiv",
|
|
||||||
Self::Urem => "urem",
|
|
||||||
Self::Srem => "srem",
|
|
||||||
|
|
||||||
Self::And => "and",
|
|
||||||
Self::Or => "or",
|
|
||||||
Self::Xor => "xor",
|
|
||||||
Self::Not => "not",
|
|
||||||
|
|
||||||
Self::Shl => "shl",
|
|
||||||
Self::Lshr => "lshr",
|
|
||||||
Self::Ashr => "ashr",
|
|
||||||
|
|
||||||
Self::Fadd => "fadd",
|
|
||||||
Self::Fsub => "fsub",
|
|
||||||
Self::Fmul => "fmul",
|
|
||||||
Self::Fdiv => "fdiv",
|
|
||||||
Self::Fneg => "fneg",
|
|
||||||
|
|
||||||
Self::Icmp => "icmp",
|
|
||||||
Self::Select => "select",
|
|
||||||
|
|
||||||
Self::Load => "load",
|
|
||||||
Self::Store => "store",
|
|
||||||
|
|
||||||
Self::Jump => "jump",
|
|
||||||
Self::BrIf => "br_if",
|
|
||||||
Self::Call => "call",
|
|
||||||
Self::Return => "return",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this opcode is a terminator.
|
|
||||||
pub const fn is_terminator(self) -> bool {
|
|
||||||
matches!(self, Self::Jump | Self::BrIf | Self::Return)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this opcode may read memory.
|
|
||||||
pub const fn may_read_memory(self) -> bool {
|
|
||||||
matches!(self, Self::Load | Self::Call)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this opcode may write memory.
|
|
||||||
pub const fn may_write_memory(self) -> bool {
|
|
||||||
matches!(self, Self::Store | Self::Call)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this opcode is side-effecting.
|
|
||||||
pub const fn has_side_effects(self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Self::Store | Self::Jump | Self::BrIf | Self::Call | Self::Return
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Opcode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str(self.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A memory access size in bytes.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub enum MemSize {
|
|
||||||
S1,
|
|
||||||
S2,
|
|
||||||
S4,
|
|
||||||
S8,
|
|
||||||
S16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MemSize {
|
|
||||||
/// Returns the memory access width in bytes.
|
|
||||||
pub const fn bytes(self) -> u8 {
|
|
||||||
match self {
|
|
||||||
Self::S1 => 1,
|
|
||||||
Self::S2 => 2,
|
|
||||||
Self::S4 => 4,
|
|
||||||
Self::S8 => 8,
|
|
||||||
Self::S16 => 16,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the memory access width in bits.
|
|
||||||
pub const fn bits(self) -> u16 {
|
|
||||||
// TODO: Some machines may have different byte sizes.
|
|
||||||
(self.bytes() as u16) * 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for MemSize {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}B", self.bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The payload of an instruction.
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum InstructionData {
|
|
||||||
/// A zero-operand instruction with no embedded immediate.
|
|
||||||
Nullary { opcode: Opcode },
|
|
||||||
|
|
||||||
/// A constant instruction holding an `i64` immediate.
|
|
||||||
Iconst { opcode: Opcode, imm: i64 },
|
|
||||||
|
|
||||||
/// A constant instruction holding a 32-bit floating-point bit pattern.
|
|
||||||
///
|
|
||||||
/// The value is stored as raw IEEE-754 bits.
|
|
||||||
F32const { opcode: Opcode, bits: u32 },
|
|
||||||
|
|
||||||
/// A constant instruction holding a 64-bit floating-point bit pattern.
|
|
||||||
///
|
|
||||||
/// The value is stored as raw IEEE-754 bits.
|
|
||||||
F64const { opcode: Opcode, bits: u64 },
|
|
||||||
|
|
||||||
/// A boolean constant.
|
|
||||||
Bconst { opcode: Opcode, value: bool },
|
|
||||||
|
|
||||||
/// A one-argument instruction.
|
|
||||||
Unary { opcode: Opcode, arg: Value },
|
|
||||||
|
|
||||||
/// A two-argument instruction.
|
|
||||||
Binary { opcode: Opcode, args: [Value; 2] },
|
|
||||||
|
|
||||||
/// A three-argument instruction.
|
|
||||||
///
|
|
||||||
/// This is primarily useful for operations like `select`.
|
|
||||||
Ternary { opcode: Opcode, args: [Value; 3] },
|
|
||||||
|
|
||||||
/// An integer comparison instruction.
|
|
||||||
Icmp {
|
|
||||||
opcode: Opcode,
|
|
||||||
cc: IntCC,
|
|
||||||
args: [Value; 2],
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A load from memory.
|
|
||||||
Load {
|
|
||||||
opcode: Opcode,
|
|
||||||
addr: Value,
|
|
||||||
size: MemSize,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A store to memory.
|
|
||||||
Store {
|
|
||||||
opcode: Opcode,
|
|
||||||
addr: Value,
|
|
||||||
value: Value,
|
|
||||||
size: MemSize,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An unconditional branch to another block with block arguments.
|
|
||||||
Jump { opcode: Opcode, dst: BlockCall },
|
|
||||||
|
|
||||||
/// A conditional branch with explicit true/false targets.
|
|
||||||
// TODO: Introduce direct call references
|
|
||||||
BrIf {
|
|
||||||
opcode: Opcode,
|
|
||||||
cond: Value,
|
|
||||||
then_dst: BlockCall,
|
|
||||||
else_dst: BlockCall,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A call through a value.
|
|
||||||
Call {
|
|
||||||
opcode: Opcode,
|
|
||||||
callee: Value,
|
|
||||||
args: ValueList,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A return from the current body.
|
|
||||||
Return { opcode: Opcode, values: ValueList },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstructionData {
|
|
||||||
/// Returns the opcode of this instruction.
|
|
||||||
pub const fn opcode(&self) -> Opcode {
|
|
||||||
match *self {
|
|
||||||
Self::Nullary { opcode }
|
|
||||||
| Self::Iconst { opcode, .. }
|
|
||||||
| Self::F32const { opcode, .. }
|
|
||||||
| Self::F64const { opcode, .. }
|
|
||||||
| Self::Bconst { opcode, .. }
|
|
||||||
| Self::Unary { opcode, .. }
|
|
||||||
| Self::Binary { opcode, .. }
|
|
||||||
| Self::Ternary { opcode, .. }
|
|
||||||
| Self::Icmp { opcode, .. }
|
|
||||||
| Self::Load { opcode, .. }
|
|
||||||
| Self::Store { opcode, .. }
|
|
||||||
| Self::Jump { opcode, .. }
|
|
||||||
| Self::BrIf { opcode, .. }
|
|
||||||
| Self::Call { opcode, .. }
|
|
||||||
| Self::Return { opcode, .. } => opcode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this instruction is a terminator.
|
|
||||||
pub const fn is_terminator(&self) -> bool {
|
|
||||||
self.opcode().is_terminator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The definition site of an SSA value.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ValueDef {
|
|
||||||
/// A value produced as result `index` of an instruction.
|
|
||||||
Result(Inst, u16),
|
|
||||||
|
|
||||||
/// A block parameter at position `index`.
|
|
||||||
Param(Block, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type information attached to a value in the data-flow graph.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct ValueData {
|
|
||||||
/// The semantic type of the value.
|
|
||||||
pub ty: Type,
|
|
||||||
|
|
||||||
/// Where the value was defined.
|
|
||||||
pub def: ValueDef,
|
|
||||||
}
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
//! [`petgraph`] adapters for Slonik.
|
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use cranelift_entity::EntityRef;
|
|
||||||
use petgraph::{
|
|
||||||
Directed, Direction,
|
|
||||||
visit::{
|
|
||||||
GraphBase, GraphProp, GraphRef, IntoNeighbors, IntoNeighborsDirected, IntoNodeIdentifiers,
|
|
||||||
NodeCompactIndexable, NodeCount, NodeIndexable, Visitable,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::ir::{Block, BlockSuccessors, Body};
|
|
||||||
|
|
||||||
/// A borrowed control-flow graph view over a [`Body`].
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct CfgView<'a> {
|
|
||||||
body: &'a Body,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CfgView<'a> {
|
|
||||||
/// Creates a borrowed CFG view over `body`.
|
|
||||||
pub const fn new(body: &'a Body) -> Self {
|
|
||||||
Self { body }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying IR body.
|
|
||||||
pub const fn body(self) -> &'a Body {
|
|
||||||
self.body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Body {
|
|
||||||
/// Returns a borrowed `petgraph`-compatible CFG view.
|
|
||||||
pub const fn cfg(&self) -> CfgView<'_> {
|
|
||||||
CfgView::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator over blocks in layout order.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Blocks<'a> {
|
|
||||||
body: &'a Body,
|
|
||||||
cur: Option<Block>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Blocks<'a> {
|
|
||||||
fn new(body: &'a Body) -> Self {
|
|
||||||
Self {
|
|
||||||
body,
|
|
||||||
cur: body.layout.first_block.expand(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Blocks<'_> {
|
|
||||||
type Item = Block;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let cur = self.cur?;
|
|
||||||
self.cur = self.body.layout.blocks[cur].next.expand();
|
|
||||||
Some(cur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator over outgoing CFG neighbors.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct OutNeighbors {
|
|
||||||
iter: BlockSuccessors,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutNeighbors {
|
|
||||||
fn new(body: &Body, block: Block) -> Self {
|
|
||||||
Self {
|
|
||||||
iter: body.block_successors(block),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for OutNeighbors {
|
|
||||||
type Item = Block;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.iter.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: cached predecessor map?
|
|
||||||
/// Iterator over incoming CFG neighbors.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InNeighbors<'a> {
|
|
||||||
body: &'a Body,
|
|
||||||
cur: Option<Block>,
|
|
||||||
target: Block,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> InNeighbors<'a> {
|
|
||||||
fn new(body: &'a Body, target: Block) -> Self {
|
|
||||||
Self {
|
|
||||||
body,
|
|
||||||
cur: body.layout.first_block(),
|
|
||||||
target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for InNeighbors<'_> {
|
|
||||||
type Item = Block;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
while let Some(block) = self.cur {
|
|
||||||
self.cur = self.body.layout.next_block(block);
|
|
||||||
|
|
||||||
if self
|
|
||||||
.body
|
|
||||||
.block_successors(block)
|
|
||||||
.any(|succ| succ == self.target)
|
|
||||||
{
|
|
||||||
return Some(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator over neighbors in a requested direction.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Neighbors<'a> {
|
|
||||||
Out(OutNeighbors),
|
|
||||||
In(InNeighbors<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Neighbors<'_> {
|
|
||||||
type Item = Block;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
match self {
|
|
||||||
Self::Out(iter) => iter.next(),
|
|
||||||
Self::In(iter) => iter.next(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphBase for CfgView<'_> {
|
|
||||||
type NodeId = Block;
|
|
||||||
type EdgeId = (Block, Block);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphRef for CfgView<'_> {}
|
|
||||||
|
|
||||||
impl GraphProp for CfgView<'_> {
|
|
||||||
type EdgeType = Directed;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeCount for CfgView<'_> {
|
|
||||||
fn node_count(&self) -> usize {
|
|
||||||
self.body.block_count()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeIndexable for CfgView<'_> {
|
|
||||||
fn node_bound(&self) -> usize {
|
|
||||||
self.body.block_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_index(&self, a: Self::NodeId) -> usize {
|
|
||||||
a.index()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_index(&self, i: usize) -> Self::NodeId {
|
|
||||||
Block::new(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeCompactIndexable for CfgView<'_> {}
|
|
||||||
|
|
||||||
impl<'a> IntoNodeIdentifiers for CfgView<'a> {
|
|
||||||
type NodeIdentifiers = Blocks<'a>;
|
|
||||||
|
|
||||||
fn node_identifiers(self) -> Self::NodeIdentifiers {
|
|
||||||
Blocks::new(self.body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoNeighbors for CfgView<'a> {
|
|
||||||
type Neighbors = OutNeighbors;
|
|
||||||
|
|
||||||
fn neighbors(self, a: Self::NodeId) -> Self::Neighbors {
|
|
||||||
OutNeighbors::new(self.body, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoNeighborsDirected for CfgView<'a> {
|
|
||||||
type NeighborsDirected = Neighbors<'a>;
|
|
||||||
|
|
||||||
fn neighbors_directed(self, n: Self::NodeId, d: Direction) -> Self::NeighborsDirected {
|
|
||||||
match d {
|
|
||||||
Direction::Outgoing => Neighbors::Out(OutNeighbors::new(self.body, n)),
|
|
||||||
Direction::Incoming => Neighbors::In(InNeighbors::new(self.body, n)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visitable for CfgView<'_> {
|
|
||||||
type Map = HashSet<Block>;
|
|
||||||
|
|
||||||
fn visit_map(&self) -> Self::Map {
|
|
||||||
HashSet::with_capacity(self.body.block_count())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_map(&self, map: &mut Self::Map) {
|
|
||||||
map.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
78
src/ir/stmt.rs
Normal file
78
src/ir/stmt.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
//! Ordered block-local statements for Slonik IR.
|
||||||
|
|
||||||
|
use crate::ir::{Block, Stmt, Value};
|
||||||
|
use cranelift_entity::EntityList;
|
||||||
|
|
||||||
|
/// A compact list of SSA values.
|
||||||
|
pub type ValueList = EntityList<Value>;
|
||||||
|
|
||||||
|
/// A target block together with the SSA arguments passed to that block.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct BlockCall {
|
||||||
|
/// The destination block.
|
||||||
|
pub block: Block,
|
||||||
|
|
||||||
|
/// The arguments passed to the destination block.
|
||||||
|
pub args: ValueList,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ordered block-local statement.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum StmtData {
|
||||||
|
/// Store `value` to memory at `addr`.
|
||||||
|
///
|
||||||
|
/// `size` is the access width in bytes.
|
||||||
|
Store {
|
||||||
|
addr: Value,
|
||||||
|
value: Value,
|
||||||
|
size: crate::ir::MemSize,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Call a callee value with SSA arguments.
|
||||||
|
Call { callee: Value, args: ValueList },
|
||||||
|
|
||||||
|
/// Unconditional transfer of control to another block.
|
||||||
|
Jump { dst: BlockCall },
|
||||||
|
|
||||||
|
/// Conditional transfer of control.
|
||||||
|
BrIf {
|
||||||
|
cond: Value,
|
||||||
|
then_dst: BlockCall,
|
||||||
|
else_dst: BlockCall,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Return from the current body.
|
||||||
|
Return { values: ValueList },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StmtData {
|
||||||
|
/// Returns whether this statement is a terminator.
|
||||||
|
pub const fn is_terminator(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Self::Jump { .. } | Self::BrIf { .. } | Self::Return { .. }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this statement may read memory.
|
||||||
|
pub const fn may_read_memory(&self) -> bool {
|
||||||
|
matches!(self, Self::Call { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this statement may write memory.
|
||||||
|
pub const fn may_write_memory(&self) -> bool {
|
||||||
|
matches!(self, Self::Store { .. } | Self::Call { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this statement has side effects.
|
||||||
|
pub const fn has_side_effects(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structural data attached to a statement handle.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct StmtDataRef {
|
||||||
|
/// The statement handle this record describes.
|
||||||
|
pub stmt: Stmt,
|
||||||
|
}
|
||||||
374
src/ir/verify.rs
Normal file
374
src/ir/verify.rs
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
//! Structural verification for Slonik IR.
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use crate::ir::{Block, BlockCall, Body, Expr, ExprData, Stmt, StmtData, Type, Value, ValueDef};
|
||||||
|
|
||||||
|
/// A verification error.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct VerifyError {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerifyError {
|
||||||
|
fn new(msg: impl Into<String>) -> Self {
|
||||||
|
Self { msg: msg.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VerifyError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for VerifyError {}
|
||||||
|
|
||||||
|
/// Verifies the structural correctness of `body`.
|
||||||
|
pub fn verify(body: &Body) -> Result<(), VerifyError> {
|
||||||
|
Verifier { body }.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Verifier<'a> {
|
||||||
|
body: &'a Body,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Verifier<'_> {
|
||||||
|
fn run(self) -> Result<(), VerifyError> {
|
||||||
|
self.verify_values()?;
|
||||||
|
self.verify_blocks()?;
|
||||||
|
self.verify_exprs()?;
|
||||||
|
self.verify_stmts()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_values(&self) -> Result<(), VerifyError> {
|
||||||
|
for (value, data) in self.body.values.iter() {
|
||||||
|
match data.def {
|
||||||
|
ValueDef::Expr(expr) => {
|
||||||
|
self.ensure_valid_expr(expr, format!("value {value}"))?;
|
||||||
|
|
||||||
|
let owner = &self.body.exprs[expr];
|
||||||
|
if owner.value != value {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"value {value} claims to be defined by expression {expr}, \
|
||||||
|
but that expression produces {}",
|
||||||
|
owner.value
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueDef::Param(block, index) => {
|
||||||
|
self.ensure_valid_block(block, format!("value {value}"))?;
|
||||||
|
|
||||||
|
let params = self.body.block_params(block);
|
||||||
|
let Some(actual) = params.get(index as usize).copied() else {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"value {value} claims to be block parameter #{index} of {block}, \
|
||||||
|
but that block has only {} parameter(s)",
|
||||||
|
params.len()
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
if actual != value {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"value {value} claims to be block parameter #{index} of {block}, \
|
||||||
|
but that slot contains {actual}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ty.is_void() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"value {value} has type void, which is not a valid SSA value type"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_blocks(&self) -> Result<(), VerifyError> {
|
||||||
|
for block in self.body.blocks() {
|
||||||
|
let params = self.body.block_params(block);
|
||||||
|
for (index, ¶m) in params.iter().enumerate() {
|
||||||
|
self.ensure_valid_value(param, format!("block {block} parameter list"))?;
|
||||||
|
|
||||||
|
let data = self.body.value_data(param);
|
||||||
|
let expected = ValueDef::Param(block, index as u16);
|
||||||
|
if data.def != expected {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"block {block} parameter #{index} is {param}, \
|
||||||
|
but that value is recorded as {:?}",
|
||||||
|
data.def
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stmts = self.body.block_stmts(block);
|
||||||
|
for (index, &stmt) in stmts.iter().enumerate() {
|
||||||
|
self.ensure_valid_stmt(stmt, format!("block {block} statement list"))?;
|
||||||
|
|
||||||
|
let is_last = index + 1 == stmts.len();
|
||||||
|
let is_term = self.body.stmt_data(stmt).is_terminator();
|
||||||
|
|
||||||
|
if is_term && !is_last {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"terminator statement {stmt} in {block} is not the final statement"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_term && is_last {
|
||||||
|
// This is allowed: unterminated blocks may exist during construction.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_exprs(&self) -> Result<(), VerifyError> {
|
||||||
|
for (expr, node) in self.body.exprs.iter() {
|
||||||
|
let value = node.value;
|
||||||
|
self.ensure_valid_value(value, format!("expression {expr} result"))?;
|
||||||
|
|
||||||
|
let vdata = self.body.value_data(value);
|
||||||
|
if vdata.def != ValueDef::Expr(expr) {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"expression {expr} produces value {value}, \
|
||||||
|
but that value is recorded as {:?}",
|
||||||
|
vdata.def
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
match &node.data {
|
||||||
|
ExprData::IConst { .. } => {}
|
||||||
|
|
||||||
|
ExprData::F32Const { .. } => {
|
||||||
|
if self.body.value_type(value) != Type::f32() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"expression {expr} is an f32 constant but produces type {}",
|
||||||
|
self.body.value_type(value)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::F64Const { .. } => {
|
||||||
|
if self.body.value_type(value) != Type::f64() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"expression {expr} is an f64 constant but produces type {}",
|
||||||
|
self.body.value_type(value)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::BConst { .. } => {
|
||||||
|
if self.body.value_type(value) != Type::bool() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"expression {expr} is a boolean constant but produces type {}",
|
||||||
|
self.body.value_type(value)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Unary { arg, .. } => {
|
||||||
|
self.ensure_valid_value(*arg, format!("expression {expr} unary operand"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Binary { lhs, rhs, .. } => {
|
||||||
|
self.ensure_valid_value(*lhs, format!("expression {expr} binary lhs"))?;
|
||||||
|
self.ensure_valid_value(*rhs, format!("expression {expr} binary rhs"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Cast { arg, .. } => {
|
||||||
|
self.ensure_valid_value(*arg, format!("expression {expr} cast operand"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Icmp { lhs, rhs, .. } | ExprData::Fcmp { lhs, rhs, .. } => {
|
||||||
|
self.ensure_valid_value(*lhs, format!("expression {expr} compare lhs"))?;
|
||||||
|
self.ensure_valid_value(*rhs, format!("expression {expr} compare rhs"))?;
|
||||||
|
|
||||||
|
if self.body.value_type(value) != Type::bool() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"comparison expression {expr} must produce bool, got {}",
|
||||||
|
self.body.value_type(value)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Select {
|
||||||
|
cond,
|
||||||
|
if_true,
|
||||||
|
if_false,
|
||||||
|
} => {
|
||||||
|
self.ensure_valid_value(*cond, format!("expression {expr} select condition"))?;
|
||||||
|
self.ensure_valid_value(
|
||||||
|
*if_true,
|
||||||
|
format!("expression {expr} select true arm"),
|
||||||
|
)?;
|
||||||
|
self.ensure_valid_value(
|
||||||
|
*if_false,
|
||||||
|
format!("expression {expr} select false arm"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if self.body.value_type(*cond) != Type::bool() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"select expression {expr} condition {} must have type bool, got {}",
|
||||||
|
cond,
|
||||||
|
self.body.value_type(*cond)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let t_ty = self.body.value_type(*if_true);
|
||||||
|
let f_ty = self.body.value_type(*if_false);
|
||||||
|
let out_ty = self.body.value_type(value);
|
||||||
|
|
||||||
|
if t_ty != f_ty {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"select expression {expr} arms have mismatched types: {t_ty} vs {f_ty}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if out_ty != t_ty {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"select expression {expr} result type {out_ty} does not match arm type {t_ty}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Load { addr, .. } => {
|
||||||
|
self.ensure_valid_value(*addr, format!("expression {expr} load address"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_stmts(&self) -> Result<(), VerifyError> {
|
||||||
|
for (stmt, data) in self.body.stmts.iter() {
|
||||||
|
match data {
|
||||||
|
StmtData::Store { addr, value, .. } => {
|
||||||
|
self.ensure_valid_value(*addr, format!("statement {stmt} store address"))?;
|
||||||
|
self.ensure_valid_value(*value, format!("statement {stmt} store value"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Call { callee, args } => {
|
||||||
|
self.ensure_valid_value(*callee, format!("statement {stmt} callee"))?;
|
||||||
|
self.verify_value_list(
|
||||||
|
args.as_slice(&self.body.value_lists),
|
||||||
|
format!("statement {stmt} call arguments"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Jump { dst } => {
|
||||||
|
self.verify_block_call(*dst, format!("statement {stmt} jump target"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::BrIf {
|
||||||
|
cond,
|
||||||
|
then_dst,
|
||||||
|
else_dst,
|
||||||
|
} => {
|
||||||
|
self.ensure_valid_value(*cond, format!("statement {stmt} branch condition"))?;
|
||||||
|
|
||||||
|
if self.body.value_type(*cond) != Type::bool() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"statement {stmt} branch condition {} must have type bool, got {}",
|
||||||
|
cond,
|
||||||
|
self.body.value_type(*cond)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.verify_block_call(*then_dst, format!("statement {stmt} then-target"))?;
|
||||||
|
self.verify_block_call(*else_dst, format!("statement {stmt} else-target"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Return { values } => {
|
||||||
|
self.verify_value_list(
|
||||||
|
values.as_slice(&self.body.value_lists),
|
||||||
|
format!("statement {stmt} return values"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_block_call(&self, call: BlockCall, context: String) -> Result<(), VerifyError> {
|
||||||
|
self.ensure_valid_block(call.block, context.clone())?;
|
||||||
|
|
||||||
|
let args = call.args.as_slice(&self.body.value_lists);
|
||||||
|
let params = self.body.block_params(call.block);
|
||||||
|
|
||||||
|
if args.len() != params.len() {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"{context} passes {} argument(s) to {}, but that block expects {} parameter(s)",
|
||||||
|
args.len(),
|
||||||
|
call.block,
|
||||||
|
params.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index, (&arg, ¶m)) in args.iter().zip(params.iter()).enumerate() {
|
||||||
|
self.ensure_valid_value(arg, format!("{context} argument #{index}"))?;
|
||||||
|
|
||||||
|
let arg_ty = self.body.value_type(arg);
|
||||||
|
let param_ty = self.body.value_type(param);
|
||||||
|
|
||||||
|
if arg_ty != param_ty {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"{context} argument #{index} to {} has type {}, but destination parameter has type {}",
|
||||||
|
call.block, arg_ty, param_ty
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_value_list(&self, values: &[Value], context: String) -> Result<(), VerifyError> {
|
||||||
|
for (index, &value) in values.iter().enumerate() {
|
||||||
|
self.ensure_valid_value(value, format!("{context} #{index}"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_valid_block(&self, block: Block, context: String) -> Result<(), VerifyError> {
|
||||||
|
if !self.body.blocks.is_valid(block) {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"{context} references invalid block {block}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_valid_expr(&self, expr: Expr, context: String) -> Result<(), VerifyError> {
|
||||||
|
if !self.body.exprs.is_valid(expr) {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"{context} references invalid expression {expr}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_valid_stmt(&self, stmt: Stmt, context: String) -> Result<(), VerifyError> {
|
||||||
|
if !self.body.stmts.is_valid(stmt) {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"{context} references invalid statement {stmt}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_valid_value(&self, value: Value, context: String) -> Result<(), VerifyError> {
|
||||||
|
if !self.body.values.is_valid(value) {
|
||||||
|
return Err(VerifyError::new(format!(
|
||||||
|
"{context} references invalid value {value}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
363
src/ir/write.rs
Normal file
363
src/ir/write.rs
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
//! Text formatting for Slonik IR.
|
||||||
|
//!
|
||||||
|
//! Expressions that are never referenced by any statement are emitted in a
|
||||||
|
//! trailing `// dead expressions` section.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ir::{Block, BlockCall, Body, Expr, ExprData, Stmt, StmtData, Value, ValueDef};
|
||||||
|
|
||||||
|
/// Writes `body` in textual form.
|
||||||
|
pub fn write_body(f: &mut fmt::Formatter<'_>, body: &Body) -> fmt::Result {
|
||||||
|
let mut printer = Printer::new(body);
|
||||||
|
printer.write_body(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Body {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write_body(f, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Printer<'a> {
|
||||||
|
body: &'a Body,
|
||||||
|
emitted: HashSet<Value>,
|
||||||
|
visiting: HashSet<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Printer<'a> {
|
||||||
|
fn new(body: &'a Body) -> Self {
|
||||||
|
Self {
|
||||||
|
body,
|
||||||
|
emitted: HashSet::new(),
|
||||||
|
visiting: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_body(&mut self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "body {{")?;
|
||||||
|
|
||||||
|
let mut first_block = true;
|
||||||
|
for block in self.body.blocks() {
|
||||||
|
if !first_block {
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
first_block = false;
|
||||||
|
self.write_block(f, block)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wrote_dead_header = false;
|
||||||
|
for (expr, node) in self.body.exprs.iter() {
|
||||||
|
let value = node.value;
|
||||||
|
if !self.emitted.contains(&value) {
|
||||||
|
if !wrote_dead_header {
|
||||||
|
if !first_block {
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
writeln!(f, " // dead expressions")?;
|
||||||
|
wrote_dead_header = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit_expr(f, expr, 1)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_block(&mut self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result {
|
||||||
|
self.write_block_header(f, block)?;
|
||||||
|
|
||||||
|
for &stmt in self.body.block_stmts(block) {
|
||||||
|
self.emit_stmt_deps(f, stmt, 2)?;
|
||||||
|
self.write_stmt(f, stmt, 2)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_block_header(&mut self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result {
|
||||||
|
write!(f, " ^{}", block)?;
|
||||||
|
|
||||||
|
let params = self.body.block_params(block);
|
||||||
|
if !params.is_empty() {
|
||||||
|
write!(f, "(")?;
|
||||||
|
for (i, ¶m) in params.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ty = self.body.value_type(param);
|
||||||
|
write!(f, "%{}: {}", param, ty)?;
|
||||||
|
}
|
||||||
|
write!(f, ")")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_stmt_deps(
|
||||||
|
&mut self,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
stmt: Stmt,
|
||||||
|
indent: usize,
|
||||||
|
) -> fmt::Result {
|
||||||
|
match self.body.stmt_data(stmt) {
|
||||||
|
StmtData::Store { addr, value, .. } => {
|
||||||
|
self.emit_value(f, *addr, indent)?;
|
||||||
|
self.emit_value(f, *value, indent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Call { callee, args } => {
|
||||||
|
self.emit_value(f, *callee, indent)?;
|
||||||
|
for &arg in args.as_slice(&self.body.value_lists) {
|
||||||
|
self.emit_value(f, arg, indent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Jump { dst } => {
|
||||||
|
self.emit_block_call_args(f, *dst, indent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::BrIf {
|
||||||
|
cond,
|
||||||
|
then_dst,
|
||||||
|
else_dst,
|
||||||
|
} => {
|
||||||
|
self.emit_value(f, *cond, indent)?;
|
||||||
|
self.emit_block_call_args(f, *then_dst, indent)?;
|
||||||
|
self.emit_block_call_args(f, *else_dst, indent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Return { values } => {
|
||||||
|
for &value in values.as_slice(&self.body.value_lists) {
|
||||||
|
self.emit_value(f, value, indent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_block_call_args(
|
||||||
|
&mut self,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
call: BlockCall,
|
||||||
|
indent: usize,
|
||||||
|
) -> fmt::Result {
|
||||||
|
for &arg in call.args.as_slice(&self.body.value_lists) {
|
||||||
|
self.emit_value(f, arg, indent)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_value(
|
||||||
|
&mut self,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
value: Value,
|
||||||
|
indent: usize,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if self.emitted.contains(&value) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.body.value_def(value) {
|
||||||
|
ValueDef::Param(_, _) => Ok(()),
|
||||||
|
ValueDef::Expr(expr) => self.emit_expr(f, expr, indent),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_expr(&mut self, f: &mut fmt::Formatter<'_>, expr: Expr, indent: usize) -> fmt::Result {
|
||||||
|
let value = self.body.expr_value(expr);
|
||||||
|
if self.emitted.contains(&value) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.visiting.insert(expr) {
|
||||||
|
return Err(fmt::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.body.expr_data(expr) {
|
||||||
|
ExprData::IConst { .. }
|
||||||
|
| ExprData::F32Const { .. }
|
||||||
|
| ExprData::F64Const { .. }
|
||||||
|
| ExprData::BConst { .. } => {}
|
||||||
|
|
||||||
|
ExprData::Unary { arg, .. } | ExprData::Cast { arg, .. } => {
|
||||||
|
self.emit_value(f, *arg, indent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Binary { lhs, rhs, .. }
|
||||||
|
| ExprData::Icmp { lhs, rhs, .. }
|
||||||
|
| ExprData::Fcmp { lhs, rhs, .. } => {
|
||||||
|
self.emit_value(f, *lhs, indent)?;
|
||||||
|
self.emit_value(f, *rhs, indent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Select {
|
||||||
|
cond,
|
||||||
|
if_true,
|
||||||
|
if_false,
|
||||||
|
} => {
|
||||||
|
self.emit_value(f, *cond, indent)?;
|
||||||
|
self.emit_value(f, *if_true, indent)?;
|
||||||
|
self.emit_value(f, *if_false, indent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Load { addr, .. } => {
|
||||||
|
self.emit_value(f, *addr, indent)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_indent(f, indent)?;
|
||||||
|
self.write_expr_binding(f, expr)?;
|
||||||
|
self.visiting.remove(&expr);
|
||||||
|
self.emitted.insert(value);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_expr_binding(&mut self, f: &mut fmt::Formatter<'_>, expr: Expr) -> fmt::Result {
|
||||||
|
let value = self.body.expr_value(expr);
|
||||||
|
let ty = self.body.value_type(value);
|
||||||
|
|
||||||
|
write!(f, "%{} = ", value)?;
|
||||||
|
|
||||||
|
match self.body.expr_data(expr) {
|
||||||
|
ExprData::IConst { imm } => {
|
||||||
|
write!(f, "iconst {}", imm)?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::F32Const { bits } => {
|
||||||
|
write!(f, "f32const 0x{bits:08x}")?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::F64Const { bits } => {
|
||||||
|
write!(f, "f64const 0x{bits:016x}")?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::BConst { value } => {
|
||||||
|
write!(f, "bconst {}", value)?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Unary { op, arg } => {
|
||||||
|
write!(f, "{} %{}", op, arg)?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Binary { op, lhs, rhs } => {
|
||||||
|
write!(f, "{} %{}, %{}", op, lhs, rhs)?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Cast { op, arg } => {
|
||||||
|
let src_ty = self.body.value_type(*arg);
|
||||||
|
writeln!(f, "{} %{} : {} -> {}", op, arg, src_ty, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Icmp { cc, lhs, rhs } => {
|
||||||
|
let operand_ty = self.body.value_type(*lhs);
|
||||||
|
writeln!(f, "icmp {} %{}, %{} : {}", cc, lhs, rhs, operand_ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Fcmp { cc, lhs, rhs } => {
|
||||||
|
let operand_ty = self.body.value_type(*lhs);
|
||||||
|
writeln!(f, "fcmp {} %{}, %{} : {}", cc, lhs, rhs, operand_ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Select {
|
||||||
|
cond,
|
||||||
|
if_true,
|
||||||
|
if_false,
|
||||||
|
} => {
|
||||||
|
write!(f, "select %{}, %{}, %{}", cond, if_true, if_false)?;
|
||||||
|
writeln!(f, " : {}", ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprData::Load { addr, size } => {
|
||||||
|
writeln!(f, "load %{} : {} -> {}", addr, size, ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_stmt(&mut self, f: &mut fmt::Formatter<'_>, stmt: Stmt, indent: usize) -> fmt::Result {
|
||||||
|
self.write_indent(f, indent)?;
|
||||||
|
|
||||||
|
match self.body.stmt_data(stmt) {
|
||||||
|
StmtData::Store { addr, value, size } => {
|
||||||
|
writeln!(f, "store %{}, %{} : {}", value, addr, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Call { callee, args } => {
|
||||||
|
write!(f, "call %{}", callee)?;
|
||||||
|
|
||||||
|
if !args.is_empty() {
|
||||||
|
write!(f, "(")?;
|
||||||
|
self.write_value_list(f, args.as_slice(&self.body.value_lists))?;
|
||||||
|
writeln!(f, ")")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Jump { dst } => {
|
||||||
|
write!(f, "br ")?;
|
||||||
|
self.write_block_call(f, *dst)?;
|
||||||
|
writeln!(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::BrIf {
|
||||||
|
cond,
|
||||||
|
then_dst,
|
||||||
|
else_dst,
|
||||||
|
} => {
|
||||||
|
write!(f, "cond_br %{}, ", cond)?;
|
||||||
|
self.write_block_call(f, *then_dst)?;
|
||||||
|
write!(f, ", ")?;
|
||||||
|
self.write_block_call(f, *else_dst)?;
|
||||||
|
writeln!(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtData::Return { values } => {
|
||||||
|
write!(f, "return")?;
|
||||||
|
let values = values.as_slice(&self.body.value_lists);
|
||||||
|
if !values.is_empty() {
|
||||||
|
write!(f, " ")?;
|
||||||
|
self.write_value_list(f, values)?;
|
||||||
|
}
|
||||||
|
writeln!(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_block_call(&mut self, f: &mut fmt::Formatter<'_>, call: BlockCall) -> fmt::Result {
|
||||||
|
write!(f, "^{}", call.block)?;
|
||||||
|
if !call.args.is_empty() {
|
||||||
|
write!(f, "(")?;
|
||||||
|
self.write_value_list(f, call.args.as_slice(&self.body.value_lists))?;
|
||||||
|
write!(f, ")")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_value_list(&mut self, f: &mut fmt::Formatter<'_>, values: &[Value]) -> fmt::Result {
|
||||||
|
for (i, value) in values.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
write!(f, "%{}", value)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_indent(&mut self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
|
||||||
|
for _ in 0..indent {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
175
src/main.rs
175
src/main.rs
|
|
@ -8,6 +8,9 @@ use clap::{
|
||||||
styling::{AnsiColor, Effects},
|
styling::{AnsiColor, Effects},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use cranelift_entity::EntityRef;
|
||||||
|
|
||||||
|
use crate::ir::*;
|
||||||
|
|
||||||
const STYLES: Styles = Styles::styled()
|
const STYLES: Styles = Styles::styled()
|
||||||
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
|
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
|
||||||
|
|
@ -23,5 +26,175 @@ const STYLES: Styles = Styles::styled()
|
||||||
struct Args {}
|
struct Args {}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
// let args = Args::parse();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut body = Body::new();
|
||||||
|
let mut cx = FrontendBuilderContext::new();
|
||||||
|
{
|
||||||
|
let mut b = FrontendBuilder::new(&mut body, &mut cx);
|
||||||
|
|
||||||
|
let entry = b.create_block();
|
||||||
|
let then_block = b.create_block();
|
||||||
|
let else_block = b.create_block();
|
||||||
|
let merge_block = b.create_block();
|
||||||
|
|
||||||
|
let x = Variable::new(0);
|
||||||
|
let y = Variable::new(1);
|
||||||
|
let sum = Variable::new(2);
|
||||||
|
let acc = Variable::new(3);
|
||||||
|
|
||||||
|
b.declare_var(x, Type::i32());
|
||||||
|
b.declare_var(y, Type::i32());
|
||||||
|
b.declare_var(sum, Type::i32());
|
||||||
|
b.declare_var(acc, Type::i32());
|
||||||
|
|
||||||
|
// entry:
|
||||||
|
//
|
||||||
|
// x = 7
|
||||||
|
// y = 5
|
||||||
|
// sum = x + y
|
||||||
|
// if (sum > 10) goto then else goto else
|
||||||
|
//
|
||||||
|
b.switch_to_block(entry);
|
||||||
|
b.seal_block(entry);
|
||||||
|
|
||||||
|
let c7 = b.iconst(Type::i32(), 7);
|
||||||
|
let c5 = b.iconst(Type::i32(), 5);
|
||||||
|
let c10 = b.iconst(Type::i32(), 10);
|
||||||
|
|
||||||
|
b.def_var(x, c7);
|
||||||
|
b.def_var(y, c5);
|
||||||
|
|
||||||
|
let lhs = b.use_var(x);
|
||||||
|
let rhs = b.use_var(y);
|
||||||
|
let sum_val = b.iadd(lhs, rhs, Type::i32());
|
||||||
|
b.def_var(sum, sum_val);
|
||||||
|
|
||||||
|
let lhs1 = b.use_var(sum);
|
||||||
|
let cond = b.icmp(IntCC::Sgt, lhs1, c10);
|
||||||
|
b.br_if(cond, then_block, else_block);
|
||||||
|
|
||||||
|
// Both then/else now have their predecessor set.
|
||||||
|
b.seal_block(then_block);
|
||||||
|
b.seal_block(else_block);
|
||||||
|
|
||||||
|
// then:
|
||||||
|
//
|
||||||
|
// acc = x * 2
|
||||||
|
// goto merge
|
||||||
|
//
|
||||||
|
b.switch_to_block(then_block);
|
||||||
|
|
||||||
|
let c2 = b.iconst(Type::i32(), 2);
|
||||||
|
let lhs2 = b.use_var(x);
|
||||||
|
let doubled = b.imul(lhs2, c2, Type::i32());
|
||||||
|
b.def_var(acc, doubled);
|
||||||
|
b.jump(merge_block);
|
||||||
|
|
||||||
|
// else:
|
||||||
|
//
|
||||||
|
// acc = 0 - y
|
||||||
|
// goto merge
|
||||||
|
//
|
||||||
|
b.switch_to_block(else_block);
|
||||||
|
|
||||||
|
let c0 = b.iconst(Type::i32(), 0);
|
||||||
|
let rhs1 = b.use_var(y);
|
||||||
|
let neg_y = b.isub(c0, rhs1, Type::i32());
|
||||||
|
b.def_var(acc, neg_y);
|
||||||
|
b.jump(merge_block);
|
||||||
|
|
||||||
|
// merge:
|
||||||
|
//
|
||||||
|
// acc and sum should come in as block params synthesized by use_var()
|
||||||
|
// result = acc + sum
|
||||||
|
// return result
|
||||||
|
//
|
||||||
|
b.seal_block(merge_block);
|
||||||
|
b.switch_to_block(merge_block);
|
||||||
|
|
||||||
|
let merged_acc = b.use_var(acc);
|
||||||
|
let merged_sum = b.use_var(sum);
|
||||||
|
let result = b.iadd(merged_acc, merged_sum, Type::i32());
|
||||||
|
|
||||||
|
b.ret(&[result]);
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(&body).unwrap();
|
||||||
|
println!("{body}");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut body = Body::new();
|
||||||
|
let mut cx = FrontendBuilderContext::new();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut b = FrontendBuilder::new(&mut body, &mut cx);
|
||||||
|
|
||||||
|
let entry = b.create_block();
|
||||||
|
let loop_header = b.create_block();
|
||||||
|
let loop_body = b.create_block();
|
||||||
|
let exit = b.create_block();
|
||||||
|
|
||||||
|
let i = Variable::new(0);
|
||||||
|
b.declare_var(i, Type::i32());
|
||||||
|
|
||||||
|
// entry:
|
||||||
|
// i = 0
|
||||||
|
// br loop_header
|
||||||
|
b.switch_to_block(entry);
|
||||||
|
b.seal_block(entry);
|
||||||
|
|
||||||
|
let c0 = b.iconst(Type::i32(), 0);
|
||||||
|
b.def_var(i, c0);
|
||||||
|
b.jump(loop_header);
|
||||||
|
|
||||||
|
// loop_header:
|
||||||
|
// if (i < 4) goto loop_body else goto exit
|
||||||
|
//
|
||||||
|
// IMPORTANT:
|
||||||
|
// Do not seal this block yet. We still have a backedge coming from
|
||||||
|
// loop_body, and we want use_var(i) here to synthesize a block param.
|
||||||
|
b.switch_to_block(loop_header);
|
||||||
|
|
||||||
|
let iv = b.use_var(i);
|
||||||
|
let c4 = b.iconst(Type::i32(), 4);
|
||||||
|
let cond = b.icmp(IntCC::Slt, iv, c4);
|
||||||
|
b.br_if(cond, loop_body, exit);
|
||||||
|
|
||||||
|
// loop_body now has its predecessor.
|
||||||
|
b.seal_block(loop_body);
|
||||||
|
|
||||||
|
// loop_body:
|
||||||
|
// i = i + 1
|
||||||
|
// br loop_header
|
||||||
|
b.switch_to_block(loop_body);
|
||||||
|
|
||||||
|
let iv = b.use_var(i);
|
||||||
|
let c1 = b.iconst(Type::i32(), 1);
|
||||||
|
let next = b.iadd(iv, c1, Type::i32());
|
||||||
|
b.def_var(i, next);
|
||||||
|
b.jump(loop_header);
|
||||||
|
|
||||||
|
// Now loop_header has all of its predecessors:
|
||||||
|
// - entry
|
||||||
|
// - loop_body
|
||||||
|
b.seal_block(loop_header);
|
||||||
|
|
||||||
|
// exit:
|
||||||
|
// return i
|
||||||
|
//
|
||||||
|
// exit already has one predecessor from loop_header's br_if, so using
|
||||||
|
// `i` here should synthesize a block param and patch that edge.
|
||||||
|
b.seal_block(exit);
|
||||||
|
b.switch_to_block(exit);
|
||||||
|
|
||||||
|
let out = b.use_var(i);
|
||||||
|
b.ret(&[out]);
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(&body).unwrap();
|
||||||
|
println!("{body}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue