ir: remove expr, keep stmt only

This commit is contained in:
Igor kehrazy 2026-04-07 14:18:53 +03:00
parent 2a414d38e1
commit fa19fab985
8 changed files with 470 additions and 796 deletions

View file

@ -2,10 +2,8 @@
mod block;
mod body;
mod dfg;
mod expr;
mod frontend;
mod layout;
mod stmt;
mod ty;
mod verify;
@ -15,17 +13,12 @@ pub use block::*;
pub use body::*;
pub use expr::*;
pub use frontend::*;
pub use layout::*;
pub use stmt::*;
pub use ty::*;
pub use verify::*;
pub use write::*;
/// Defines a thin `u32` entity handle.
///
/// Slonik uses dense numeric handles instead of pointer-heavy nodes. This keeps
/// the IR compact, stable under mutation, and easy to store in `PrimaryMap` /
/// `SecondaryMap`.
macro_rules! entity {
(
$(
@ -47,28 +40,10 @@ entity! {
/// A handle to a basic block in a `Body`.
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`.
pub struct Inst = "inst";
/// A handle to an SSA value.
///
/// Values are either:
///
/// - the result of an expression
/// - a block parameter
pub struct Value = "v";
/// A frontend-level source variable.

View file

@ -1,32 +1,19 @@
//! The owning container for a normalized Slonik IR body.
use cranelift_entity::{EntityRef, ListPool, PrimaryMap, SecondaryMap};
use cranelift_entity::{EntityRef, ListPool, PrimaryMap};
use crate::ir::{
Block, BlockCall, BlockData, Expr, ExprData, MemSize, ParamList, Stmt, StmtData, StmtList,
Type, Value, ValueData, ValueDef, ValueList,
Block, BlockCall, BlockData, 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.
#[derive(Default)]
pub struct Body {
/// All blocks in the body.
pub blocks: PrimaryMap<Block, BlockData>,
/// All expressions in the body.
pub exprs: PrimaryMap<Expr, ExprNode>,
/// All statements in the body.
/// All statements in the body, in creation order.
pub stmts: PrimaryMap<Stmt, StmtData>,
/// All SSA values in the body.
@ -45,12 +32,9 @@ impl Body {
Self::default()
}
/// Returns whether the body contains no blocks, expressions, statements, or values.
/// Returns whether the body contains no blocks, statements, or values.
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
&& self.exprs.is_empty()
&& self.stmts.is_empty()
&& self.values.is_empty()
self.blocks.is_empty() && self.stmts.is_empty() && self.values.is_empty()
}
/// Returns the number of blocks in the body.
@ -58,11 +42,6 @@ impl Body {
self.blocks.len()
}
/// Returns the number of expressions in the body.
pub fn num_exprs(&self) -> usize {
self.exprs.len()
}
/// Returns the number of statements in the body.
pub fn num_stmts(&self) -> usize {
self.stmts.len()
@ -129,13 +108,36 @@ impl Body {
ValueList::from_slice(values, &mut self.value_lists)
}
/// Creates and appends a statement to `block`.
/// Appends a non-value-producing statement to `block` and returns its handle.
pub fn append_stmt(&mut self, block: Block, data: StmtData) -> Stmt {
let stmt = self.stmts.push(data);
self.blocks[block].stmts.push(stmt, &mut self.stmt_lists);
stmt
}
/// Appends a value-producing statement to `block`.
///
/// `make` receives the pre-allocated result `Value` and must embed it in
/// the returned `StmtData`. The allocation uses `next_key` so no other
/// statement may be pushed between `next_key` and `push` — call sites must
/// be careful not to interleave insertions.
pub fn append_value_inst(
&mut self,
block: Block,
ty: Type,
make: impl FnOnce(Value) -> StmtData,
) -> Value {
let stmt_key = self.stmts.next_key();
let value = self.values.push(ValueData {
ty,
def: ValueDef::Inst(stmt_key),
});
let stmt = self.stmts.push(make(value));
debug_assert_eq!(stmt, stmt_key);
self.blocks[block].stmts.push(stmt, &mut self.stmt_lists);
value
}
/// Returns the data for `stmt`.
pub fn stmt_data(&self, stmt: Stmt) -> &StmtData {
&self.stmts[stmt]
@ -146,32 +148,9 @@ impl Body {
&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 result value of `stmt`, if it produces one.
pub fn inst_result(&self, stmt: Stmt) -> Option<Value> {
self.stmts[stmt].result()
}
/// Returns the full value record for `value`.
@ -192,18 +171,16 @@ impl Body {
/// 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)
self.stmts[stmt].is_terminator().then_some(stmt)
}
/// Returns the terminator data for `block`, if present.
pub fn block_terminator_data(&self, block: Block) -> Option<&StmtData> {
pub fn block_terminator_data(&self, block: Block) -> Option<StmtData> {
let stmt = self.block_terminator(block)?;
Some(self.stmt_data(stmt))
Some(self.stmts[stmt])
}
/// Returns an iterator over the CFG successors of `block`.
///
/// Successors are derived from the block's terminator statement.
pub fn block_successors(&self, block: Block) -> BlockSuccessors {
let Some(term) = self.block_terminator_data(block) else {
return BlockSuccessors::Empty;
@ -221,7 +198,7 @@ impl Body {
StmtData::Return { .. } => BlockSuccessors::Empty,
StmtData::Store { .. } | StmtData::Call { .. } => BlockSuccessors::Empty,
_ => BlockSuccessors::Empty,
}
}
@ -263,13 +240,8 @@ impl Iterator for Blocks {
/// Iterator over the CFG successors of a block.
#[derive(Clone, Debug)]
pub enum BlockSuccessors {
/// No successors.
Empty,
/// Exactly one successor.
One(Option<Block>),
/// Exactly two successors.
Two {
first: Option<Block>,
second: Option<Block>,

View file

@ -1,8 +1,8 @@
//! Pure expression nodes and SSA value definitions for Slonik IR.
//! Value-producing operation types and SSA value metadata for Slonik IR.
use core::fmt;
use crate::ir::{Block, Expr, Type, Value};
use crate::ir::{Block, Stmt, Type, Value};
/// Memory access width in bytes.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@ -39,8 +39,6 @@ impl fmt::Display for MemSize {
}
/// Integer comparison condition codes.
///
/// These are used by [`ExprData::Icmp`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum IntCC {
Eq,
@ -117,8 +115,6 @@ impl fmt::Display for IntCC {
}
/// Floating-point comparison condition codes.
///
/// These are used by [`ExprData::Fcmp`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum FloatCC {
Eq,
@ -181,7 +177,7 @@ impl fmt::Display for FloatCC {
/// Unary expression operators.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum UnaryOp {
/// Integer or bitvector negation.
/// Integer negation.
Neg,
/// Bitwise not.
@ -307,79 +303,11 @@ impl fmt::Display for CastOp {
}
}
/// 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 value defined by a statement.
Inst(Stmt),
/// A block parameter at position `index`.
Param(Block, u16),

View file

@ -3,8 +3,8 @@
use std::collections::HashMap;
use crate::ir::{
BinaryOp, Block, Body, CastOp, ExprData, FloatCC, IntCC, MemSize, Stmt, StmtData, Type,
UnaryOp, Value, Variable,
BinaryOp, Block, Body, CastOp, FloatCC, IntCC, MemSize, Stmt, StmtData, Type, UnaryOp, Value,
Variable,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -150,8 +150,6 @@ impl<'a> FrontendBuilder<'a> {
}
/// 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;
}
@ -164,14 +162,6 @@ impl<'a> FrontendBuilder<'a> {
}
/// 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
@ -194,16 +184,6 @@ impl<'a> FrontendBuilder<'a> {
}
/// 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
@ -230,17 +210,12 @@ impl<'a> FrontendBuilder<'a> {
.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)
}
@ -330,6 +305,27 @@ impl<'a> FrontendBuilder<'a> {
vars.into_iter().map(|var| self.use_var(var)).collect()
}
// ------------------------------------------------------------------ //
// Instruction emission helpers //
// ------------------------------------------------------------------ //
fn append_value_inst_in_current_block(
&mut self,
ty: Type,
make: impl FnOnce(Value) -> StmtData,
) -> Value {
let block = self
.cur_block
.expect("attempted to build an instruction without a current block");
assert!(
!self.is_filled(block),
"attempted to append an instruction after the block was already terminated"
);
self.body.append_value_inst(block, ty, make)
}
fn append_stmt_in_current_block(&mut self, data: StmtData) -> Stmt {
let block = self
.cur_block
@ -350,138 +346,167 @@ impl<'a> FrontendBuilder<'a> {
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)
}
// ------------------------------------------------------------------ //
// Value-producing instructions //
// ------------------------------------------------------------------ //
/// Creates an integer constant expression.
/// Appends an integer constant instruction.
pub fn iconst(&mut self, ty: Type, imm: i64) -> Value {
self.create_expr_value(ExprData::IConst { imm }, ty)
self.append_value_inst_in_current_block(ty, |result| StmtData::IConst { imm, result })
}
/// Creates a 32-bit floating-point constant expression from raw IEEE-754 bits.
/// Appends a 32-bit float constant instruction from raw IEEE-754 bits.
pub fn f32const_bits(&mut self, bits: u32) -> Value {
self.create_expr_value(ExprData::F32Const { bits }, Type::f32())
self.append_value_inst_in_current_block(Type::f32(), |result| StmtData::F32Const {
bits,
result,
})
}
/// Creates a 64-bit floating-point constant expression from raw IEEE-754 bits.
/// Appends a 64-bit float constant instruction from raw IEEE-754 bits.
pub fn f64const_bits(&mut self, bits: u64) -> Value {
self.create_expr_value(ExprData::F64Const { bits }, Type::f64())
self.append_value_inst_in_current_block(Type::f64(), |result| StmtData::F64Const {
bits,
result,
})
}
/// Creates a boolean constant expression.
/// Appends a boolean constant instruction.
pub fn bconst(&mut self, value: bool) -> Value {
self.create_expr_value(ExprData::BConst { value }, Type::bool())
self.append_value_inst_in_current_block(Type::bool(), |result| StmtData::BConst {
value,
result,
})
}
/// Creates a unary expression.
fn unary(&mut self, op: UnaryOp, arg: Value, ty: Type) -> Value {
self.create_expr_value(ExprData::Unary { op, arg }, ty)
self.append_value_inst_in_current_block(ty, |result| StmtData::Unary { op, arg, result })
}
/// 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)
self.append_value_inst_in_current_block(ty, |result| StmtData::Binary {
op,
lhs,
rhs,
result,
})
}
/// Creates a cast expression.
/// Appends a cast instruction.
pub fn cast(&mut self, op: CastOp, arg: Value, ty: Type) -> Value {
self.create_expr_value(ExprData::Cast { op, arg }, ty)
self.append_value_inst_in_current_block(ty, |result| StmtData::Cast { op, arg, result })
}
/// Creates an integer comparison expression with boolean result.
/// Appends a bitwise not. Result type is inferred from the operand.
pub fn not(&mut self, arg: Value) -> Value {
let ty = self.body.value_type(arg);
self.unary(UnaryOp::Not, arg, ty)
}
/// Appends an integer comparison.
pub fn icmp(&mut self, cc: IntCC, lhs: Value, rhs: Value) -> Value {
self.create_expr_value(ExprData::Icmp { cc, lhs, rhs }, Type::bool())
self.append_value_inst_in_current_block(Type::bool(), |result| StmtData::Icmp {
cc,
lhs,
rhs,
result,
})
}
/// Creates a floating-point comparison expression with boolean result.
/// Appends a floating-point comparison.
pub fn fcmp(&mut self, cc: FloatCC, lhs: Value, rhs: Value) -> Value {
self.create_expr_value(ExprData::Fcmp { cc, lhs, rhs }, Type::bool())
self.append_value_inst_in_current_block(Type::bool(), |result| StmtData::Fcmp {
cc,
lhs,
rhs,
result,
})
}
/// Creates a `select` expression.
/// Appends a select instruction.
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,
)
self.append_value_inst_in_current_block(ty, |result| StmtData::Select {
cond,
if_true,
if_false,
result,
})
}
/// Creates a memory load expression.
/// Appends a memory load instruction.
pub fn load(&mut self, addr: Value, size: MemSize, ty: Type) -> Value {
self.create_expr_value(ExprData::Load { addr, size }, ty)
self.append_value_inst_in_current_block(ty, |result| StmtData::Load { addr, size, result })
}
/// Creates an integer add expression.
/// Appends an integer add instruction.
pub fn iadd(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
self.binary(BinaryOp::IAdd, lhs, rhs, ty)
}
/// Creates an integer subtract expression.
/// Appends an integer subtract instruction.
pub fn isub(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
self.binary(BinaryOp::ISub, lhs, rhs, ty)
}
/// Creates an integer multiply expression.
/// Appends an integer multiply instruction.
pub fn imul(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
self.binary(BinaryOp::IMul, lhs, rhs, ty)
}
/// Creates a bitwise and expression.
/// Appends a bitwise and instruction.
pub fn and(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
self.binary(BinaryOp::And, lhs, rhs, ty)
}
/// Creates a bitwise or expression.
/// Appends a bitwise or instruction.
pub fn or(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
self.binary(BinaryOp::Or, lhs, rhs, ty)
}
/// Creates a bitwise xor expression.
/// Appends a bitwise xor instruction.
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.
/// Appends a logical shift-left instruction.
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.
/// Appends a logical shift-right instruction.
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.
/// Appends an arithmetic shift-right instruction.
pub fn ashr(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
self.binary(BinaryOp::AShr, lhs, rhs, ty)
}
/// Creates a zero-extension expression.
/// Appends a zero-extension instruction.
pub fn zext(&mut self, arg: Value, ty: Type) -> Value {
self.cast(CastOp::Zext, arg, ty)
}
/// Creates a sign-extension expression.
/// Appends a sign-extension instruction.
pub fn sext(&mut self, arg: Value, ty: Type) -> Value {
self.cast(CastOp::Sext, arg, ty)
}
/// Creates a truncation expression.
/// Appends a truncation instruction.
pub fn trunc(&mut self, arg: Value, ty: Type) -> Value {
self.cast(CastOp::Trunc, arg, ty)
}
/// Creates a bitcast expression.
/// Appends a bitcast instruction.
pub fn bitcast(&mut self, arg: Value, ty: Type) -> Value {
self.cast(CastOp::Bitcast, arg, ty)
}
// ------------------------------------------------------------------ //
// Side-effecting and terminator statements //
// ------------------------------------------------------------------ //
/// 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 })
@ -494,9 +519,6 @@ impl<'a> FrontendBuilder<'a> {
}
/// 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
@ -511,9 +533,6 @@ impl<'a> FrontendBuilder<'a> {
}
/// 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

View file

@ -1,6 +1,12 @@
//! Ordered block-local statements for Slonik IR.
//!
//! In the lift IR every value-producing operation is a statement anchored in a
//! block. This ensures memory order is always explicit in the statement stream
//! rather than floating in a sea of pure expressions.
use crate::ir::{Block, Stmt, Value};
use crate::ir::{
BinaryOp, Block, CastOp, FloatCC, IntCC, MemSize, Stmt, UnaryOp, Value,
};
use cranelift_entity::EntityList;
/// A compact list of SSA values.
@ -16,25 +22,72 @@ pub struct BlockCall {
pub args: ValueList,
}
/// An ordered block-local statement.
#[derive(Clone, Debug, PartialEq)]
/// An ordered statement inside a basic block.
///
/// Variants that carry a `result` field produce exactly one SSA value.
/// The `result` value's [`ValueDef`] points back to this statement.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
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,
// ------------------------------------------------------------------ //
// Value-producing statements //
// ------------------------------------------------------------------ //
/// Signed integer literal. Result type determines the width.
IConst { imm: i64, result: Value },
/// 32-bit float literal stored as raw IEEE-754 bits.
F32Const { bits: u32, result: Value },
/// 64-bit float literal stored as raw IEEE-754 bits.
F64Const { bits: u64, result: Value },
/// Boolean literal.
BConst { value: bool, result: Value },
/// Unary operation.
Unary { op: UnaryOp, arg: Value, result: Value },
/// Binary operation.
Binary { op: BinaryOp, lhs: Value, rhs: Value, result: Value },
/// Cast or conversion.
Cast { op: CastOp, arg: Value, result: Value },
/// Integer comparison. Result type is always `bool`.
Icmp { cc: IntCC, lhs: Value, rhs: Value, result: Value },
/// Floating-point comparison. Result type is always `bool`.
Fcmp { cc: FloatCC, lhs: Value, rhs: Value, result: Value },
/// Conditional select. `cond` must be `bool`.
Select {
cond: Value,
if_true: Value,
if_false: Value,
result: Value,
},
/// Call a callee value with SSA arguments.
/// Memory load. Anchored in the statement stream to preserve memory order.
Load { addr: Value, size: MemSize, result: Value },
// ------------------------------------------------------------------ //
// Side-effecting statements (no result value) //
// ------------------------------------------------------------------ //
/// Memory store.
Store { addr: Value, value: Value, size: MemSize },
/// Effectful call.
Call { callee: Value, args: ValueList },
/// Unconditional transfer of control to another block.
// ------------------------------------------------------------------ //
// Terminators //
// ------------------------------------------------------------------ //
/// Unconditional jump.
Jump { dst: BlockCall },
/// Conditional transfer of control.
/// Conditional branch.
BrIf {
cond: Value,
then_dst: BlockCall,
@ -46,33 +99,41 @@ pub enum StmtData {
}
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 the result value produced by this statement, if any.
pub const fn result(self) -> Option<Value> {
match self {
Self::IConst { result, .. }
| Self::F32Const { result, .. }
| Self::F64Const { result, .. }
| Self::BConst { result, .. }
| Self::Unary { result, .. }
| Self::Binary { result, .. }
| Self::Cast { result, .. }
| Self::Icmp { result, .. }
| Self::Fcmp { result, .. }
| Self::Select { result, .. }
| Self::Load { result, .. } => Some(result),
Self::Store { .. }
| Self::Call { .. }
| Self::Jump { .. }
| Self::BrIf { .. }
| Self::Return { .. } => None,
}
}
/// Returns whether this statement is a block 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 { .. })
pub const fn may_read_memory(self) -> bool {
matches!(self, Self::Load { .. } | Self::Call { .. })
}
/// Returns whether this statement may write memory.
pub const fn may_write_memory(&self) -> bool {
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,
}

View file

@ -5,6 +5,8 @@ use core::fmt;
/// A semantic type in Slonik.
///
/// `Type` describes the meaning and shape of an SSA value.
/// Integers are signedness-agnostic; signedness is carried by the operation
/// (e.g. `sdiv` vs `udiv`, `icmp slt` vs `icmp ult`).
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Type {
/// The absence of a value.
@ -15,15 +17,12 @@ pub enum Type {
/// A one-bit boolean.
Bool,
/// A signedness-agnostic integer bitvector.
Int(IntType),
/// A signedness-agnostic integer of the given bit width.
I(u16),
/// A floating-point scalar.
Float(FloatType),
/// A raw bitvector with no numeric interpretation yet.
Bits(BitsType),
/// A pointer value.
Ptr(PtrType),
@ -31,29 +30,6 @@ pub enum Type {
Vector(VectorType),
}
/// A fixed-width integer type.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct IntType {
bits: u16,
}
impl IntType {
/// Creates a new integer type with the given width in bits.
///
/// # Panics
///
/// Panics if `bits == 0`.
pub const fn new(bits: u16) -> Self {
assert!(bits != 0, "integer type must have nonzero width");
Self { bits }
}
/// Returns the integer width in bits.
pub const fn bits(self) -> u16 {
self.bits
}
}
/// A floating-point scalar type.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum FloatType {
@ -77,29 +53,6 @@ impl FloatType {
}
}
/// A raw uninterpreted bitvector type.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct BitsType {
bits: u16,
}
impl BitsType {
/// Creates a new raw bitvector type with the given width.
///
/// # Panics
///
/// Panics if `bits == 0`.
pub const fn new(bits: u16) -> Self {
assert!(bits != 0, "bitvector type must have nonzero width");
Self { bits }
}
/// Returns the width in bits.
pub const fn bits(self) -> u16 {
self.bits
}
}
/// A pointer type.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PtrType {
@ -139,14 +92,11 @@ impl PtrType {
}
/// A scalar lane type for a vector.
///
/// Vector lanes are semantic scalar elements, not storage slots.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum LaneType {
Bool,
Int(IntType),
I(u16),
Float(FloatType),
Bits(BitsType),
Ptr(PtrType),
}
@ -155,9 +105,8 @@ impl LaneType {
pub const fn bits(self) -> u16 {
match self {
Self::Bool => 1,
Self::Int(ty) => ty.bits(),
Self::I(bits) => bits,
Self::Float(ty) => ty.bits(),
Self::Bits(ty) => ty.bits(),
Self::Ptr(ty) => ty.bits(),
}
}
@ -166,9 +115,8 @@ impl LaneType {
pub const fn as_type(self) -> Type {
match self {
Self::Bool => Type::Bool,
Self::Int(ty) => Type::Int(ty),
Self::I(bits) => Type::I(bits),
Self::Float(ty) => Type::Float(ty),
Self::Bits(ty) => Type::Bits(ty),
Self::Ptr(ty) => Type::Ptr(ty),
}
}
@ -197,20 +145,6 @@ impl VectorType {
}
}
/// Creates a new vector type with explicit scalability.
///
/// # Panics
///
/// Panics if `lanes == 0`.
pub const fn with_scalable(lane: LaneType, lanes: u16, scalable: bool) -> Self {
assert!(lanes != 0, "vector type must have at least one lane");
Self {
lanes,
lane,
scalable,
}
}
/// Returns the number of lanes.
pub const fn lanes(self) -> u16 {
self.lanes
@ -232,8 +166,6 @@ impl VectorType {
}
/// Returns the minimum number of bits in the vector.
///
/// For scalable vectors, this is the known minimum width.
pub const fn min_bits(self) -> u32 {
(self.lanes as u32) * (self.lane.bits() as u32)
}
@ -252,7 +184,7 @@ impl Type {
/// Returns an integer type with the given width.
pub const fn int(bits: u16) -> Self {
Self::Int(IntType::new(bits))
Self::I(bits)
}
/// Returns a floating-point type.
@ -260,11 +192,6 @@ impl Type {
Self::Float(kind)
}
/// Returns a raw bitvector type with the given width.
pub const fn bits(bits: u16) -> Self {
Self::Bits(BitsType::new(bits))
}
/// Returns a pointer type in address space 0.
pub const fn ptr(bits: u16) -> Self {
Self::Ptr(PtrType::new(bits))
@ -280,11 +207,6 @@ impl Type {
Self::Vector(VectorType::new(lane, lanes))
}
/// Returns a vector type with explicit scalability.
pub const fn vector_with_scalable(lane: LaneType, lanes: u16, scalable: bool) -> Self {
Self::Vector(VectorType::with_scalable(lane, lanes, scalable))
}
/// Returns whether this is `void`.
pub const fn is_void(self) -> bool {
matches!(self, Self::Void)
@ -297,7 +219,7 @@ impl Type {
/// Returns whether this is an integer.
pub const fn is_int(self) -> bool {
matches!(self, Self::Int(_))
matches!(self, Self::I(_))
}
/// Returns whether this is a float.
@ -305,11 +227,6 @@ impl Type {
matches!(self, Self::Float(_))
}
/// Returns whether this is a raw bitvector.
pub const fn is_bits(self) -> bool {
matches!(self, Self::Bits(_))
}
/// Returns whether this is a pointer.
pub const fn is_ptr(self) -> bool {
matches!(self, Self::Ptr(_))
@ -330,9 +247,8 @@ impl Type {
match self {
Self::Void => None,
Self::Bool => Some(1),
Self::Int(ty) => Some(ty.bits()),
Self::I(bits) => Some(bits),
Self::Float(ty) => Some(ty.bits()),
Self::Bits(ty) => Some(ty.bits()),
Self::Ptr(ty) => Some(ty.bits()),
Self::Vector(_) => None,
}
@ -346,14 +262,6 @@ impl Type {
}
}
/// Returns the scalar lane type if this is a vector, otherwise `None`.
pub const fn lane_type(self) -> Option<LaneType> {
match self {
Self::Vector(ty) => Some(ty.lane()),
_ => None,
}
}
/// Returns the number of lanes if this is a vector.
pub const fn lanes(self) -> Option<u16> {
match self {
@ -364,27 +272,27 @@ impl Type {
/// Convenience constructor for `i8`.
pub const fn i8() -> Self {
Self::int(8)
Self::I(8)
}
/// Convenience constructor for `i16`.
pub const fn i16() -> Self {
Self::int(16)
Self::I(16)
}
/// Convenience constructor for `i32`.
pub const fn i32() -> Self {
Self::int(32)
Self::I(32)
}
/// Convenience constructor for `i64`.
pub const fn i64() -> Self {
Self::int(64)
Self::I(64)
}
/// Convenience constructor for `i128`.
pub const fn i128() -> Self {
Self::int(128)
Self::I(128)
}
/// Convenience constructor for `f32`.
@ -403,13 +311,12 @@ impl fmt::Display for Type {
match self {
Type::Void => f.write_str("void"),
Type::Bool => f.write_str("bool"),
Type::Int(ty) => write!(f, "i{}", ty.bits()),
Type::I(bits) => write!(f, "i{bits}"),
Type::Float(FloatType::F16) => f.write_str("f16"),
Type::Float(FloatType::F32) => f.write_str("f32"),
Type::Float(FloatType::F64) => f.write_str("f64"),
Type::Float(FloatType::F80) => f.write_str("f80"),
Type::Float(FloatType::F128) => f.write_str("f128"),
Type::Bits(ty) => write!(f, "b{}", ty.bits()),
Type::Ptr(ty) => {
if ty.addr_space() == 0 {
write!(f, "ptr{}", ty.bits())
@ -432,13 +339,12 @@ impl fmt::Display for LaneType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LaneType::Bool => f.write_str("bool"),
LaneType::Int(ty) => write!(f, "i{}", ty.bits()),
LaneType::I(bits) => write!(f, "i{bits}"),
LaneType::Float(FloatType::F16) => f.write_str("f16"),
LaneType::Float(FloatType::F32) => f.write_str("f32"),
LaneType::Float(FloatType::F64) => f.write_str("f64"),
LaneType::Float(FloatType::F80) => f.write_str("f80"),
LaneType::Float(FloatType::F128) => f.write_str("f128"),
LaneType::Bits(ty) => write!(f, "b{}", ty.bits()),
LaneType::Ptr(ty) => {
if ty.addr_space() == 0 {
write!(f, "ptr{}", ty.bits())

View file

@ -2,7 +2,7 @@
use core::fmt;
use crate::ir::{Block, BlockCall, Body, Expr, ExprData, Stmt, StmtData, Type, Value, ValueDef};
use crate::ir::{Block, BlockCall, Body, Stmt, StmtData, Type, Value, ValueDef};
/// A verification error.
#[derive(Clone, Debug, PartialEq, Eq)]
@ -37,7 +37,6 @@ impl Verifier<'_> {
fn run(self) -> Result<(), VerifyError> {
self.verify_values()?;
self.verify_blocks()?;
self.verify_exprs()?;
self.verify_stmts()?;
Ok(())
}
@ -45,15 +44,20 @@ impl Verifier<'_> {
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}"))?;
ValueDef::Inst(stmt) => {
self.ensure_valid_stmt(stmt, format!("value {value}"))?;
let owner = &self.body.exprs[expr];
if owner.value != value {
let result = self.body.stmts[stmt].result().ok_or_else(|| {
VerifyError::new(format!(
"value {value} claims to be defined by statement {stmt}, \
but that statement produces no result"
))
})?;
if result != value {
return Err(VerifyError::new(format!(
"value {value} claims to be defined by expression {expr}, \
but that expression produces {}",
owner.value
"value {value} claims to be defined by statement {stmt}, \
but that statement's result is {result}"
)));
}
}
@ -118,128 +122,6 @@ impl Verifier<'_> {
"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"))?;
}
}
}
@ -247,15 +129,95 @@ impl Verifier<'_> {
}
fn verify_stmts(&self) -> Result<(), VerifyError> {
for (stmt, data) in self.body.stmts.iter() {
for (stmt, &data) in self.body.stmts.iter() {
match data {
StmtData::IConst { result, .. }
| StmtData::F32Const { result, .. }
| StmtData::F64Const { result, .. }
| StmtData::BConst { result, .. } => {
self.verify_result(stmt, result)?;
}
StmtData::Unary { arg, result, .. } => {
self.ensure_valid_value(arg, format!("statement {stmt} unary operand"))?;
self.verify_result(stmt, result)?;
}
StmtData::Binary { lhs, rhs, result, .. } => {
self.ensure_valid_value(lhs, format!("statement {stmt} binary lhs"))?;
self.ensure_valid_value(rhs, format!("statement {stmt} binary rhs"))?;
self.verify_result(stmt, result)?;
}
StmtData::Cast { arg, result, .. } => {
self.ensure_valid_value(arg, format!("statement {stmt} cast operand"))?;
self.verify_result(stmt, result)?;
}
StmtData::Icmp { lhs, rhs, result, .. }
| StmtData::Fcmp { lhs, rhs, result, .. } => {
self.ensure_valid_value(lhs, format!("statement {stmt} compare lhs"))?;
self.ensure_valid_value(rhs, format!("statement {stmt} compare rhs"))?;
self.verify_result(stmt, result)?;
if self.body.value_type(result) != Type::bool() {
return Err(VerifyError::new(format!(
"comparison statement {stmt} must produce bool, got {}",
self.body.value_type(result)
)));
}
}
StmtData::Select {
cond,
if_true,
if_false,
result,
} => {
self.ensure_valid_value(cond, format!("statement {stmt} select condition"))?;
self.ensure_valid_value(if_true, format!("statement {stmt} select true arm"))?;
self.ensure_valid_value(
if_false,
format!("statement {stmt} select false arm"),
)?;
self.verify_result(stmt, result)?;
if self.body.value_type(cond) != Type::bool() {
return Err(VerifyError::new(format!(
"select statement {stmt} condition must have type bool, got {}",
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(result);
if t_ty != f_ty {
return Err(VerifyError::new(format!(
"select statement {stmt} arms have mismatched types: {t_ty} vs {f_ty}"
)));
}
if out_ty != t_ty {
return Err(VerifyError::new(format!(
"select statement {stmt} result type {out_ty} does not match arm type {t_ty}"
)));
}
}
StmtData::Load { addr, result, .. } => {
self.ensure_valid_value(addr, format!("statement {stmt} load address"))?;
self.verify_result(stmt, result)?;
}
StmtData::Store { addr, value, .. } => {
self.ensure_valid_value(*addr, format!("statement {stmt} store address"))?;
self.ensure_valid_value(*value, format!("statement {stmt} store 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.ensure_valid_value(callee, format!("statement {stmt} callee"))?;
self.verify_value_list(
args.as_slice(&self.body.value_lists),
format!("statement {stmt} call arguments"),
@ -263,7 +225,7 @@ impl Verifier<'_> {
}
StmtData::Jump { dst } => {
self.verify_block_call(*dst, format!("statement {stmt} jump target"))?;
self.verify_block_call(dst, format!("statement {stmt} jump target"))?;
}
StmtData::BrIf {
@ -271,18 +233,17 @@ impl Verifier<'_> {
then_dst,
else_dst,
} => {
self.ensure_valid_value(*cond, format!("statement {stmt} branch condition"))?;
self.ensure_valid_value(cond, format!("statement {stmt} branch condition"))?;
if self.body.value_type(*cond) != Type::bool() {
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)
"statement {stmt} branch condition must have type bool, got {}",
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"))?;
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 } => {
@ -297,6 +258,20 @@ impl Verifier<'_> {
Ok(())
}
fn verify_result(&self, stmt: Stmt, result: Value) -> Result<(), VerifyError> {
self.ensure_valid_value(result, format!("statement {stmt} result"))?;
let def = self.body.value_def(result);
if def != ValueDef::Inst(stmt) {
return Err(VerifyError::new(format!(
"statement {stmt} result {result} is recorded as {:?} instead of Inst({stmt})",
def
)));
}
Ok(())
}
fn verify_block_call(&self, call: BlockCall, context: String) -> Result<(), VerifyError> {
self.ensure_valid_block(call.block, context.clone())?;
@ -320,7 +295,8 @@ impl Verifier<'_> {
if arg_ty != param_ty {
return Err(VerifyError::new(format!(
"{context} argument #{index} to {} has type {}, but destination parameter has type {}",
"{context} argument #{index} to {} has type {}, \
but destination parameter has type {}",
call.block, arg_ty, param_ty
)));
}
@ -345,15 +321,6 @@ impl Verifier<'_> {
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!(

View file

@ -1,17 +1,12 @@
//! 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};
use crate::ir::{Block, BlockCall, Body, Stmt, StmtData, Value};
/// 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)
Printer { body }.write_body(f)
}
impl fmt::Display for Body {
@ -22,63 +17,26 @@ impl fmt::Display for Body {
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 {
impl Printer<'_> {
fn write_body(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "body {{")?;
let mut first_block = true;
let mut first = true;
for block in self.body.blocks() {
if !first_block {
if !first {
writeln!(f)?;
}
first_block = false;
first = 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)?;
fn write_block(&self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result {
write!(f, " ^{block}")?;
let params = self.body.block_params(block);
if !params.is_empty() {
@ -87,226 +45,120 @@ impl<'a> Printer<'a> {
if i != 0 {
write!(f, ", ")?;
}
let ty = self.body.value_type(param);
write!(f, "%{}: {}", param, ty)?;
write!(f, "%{}: {}", param, self.body.value_type(param))?;
}
write!(f, ")")?;
}
writeln!(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)?;
}
}
for &stmt in self.body.block_stmts(block) {
self.write_stmt(f, stmt)?;
}
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 write_stmt(&self, f: &mut fmt::Formatter<'_>, stmt: Stmt) -> fmt::Result {
write!(f, " ")?;
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)?;
match *self.body.stmt_data(stmt) {
StmtData::IConst { imm, result } => {
writeln!(f, "%{result} = iconst {imm} : {}", self.body.value_type(result))
}
ExprData::Binary { lhs, rhs, .. }
| ExprData::Icmp { lhs, rhs, .. }
| ExprData::Fcmp { lhs, rhs, .. } => {
self.emit_value(f, *lhs, indent)?;
self.emit_value(f, *rhs, indent)?;
StmtData::F32Const { bits, result } => {
writeln!(f, "%{result} = f32const 0x{bits:08x} : f32")
}
ExprData::Select {
StmtData::F64Const { bits, result } => {
writeln!(f, "%{result} = f64const 0x{bits:016x} : f64")
}
StmtData::BConst { value, result } => {
writeln!(f, "%{result} = bconst {value} : bool")
}
StmtData::Unary { op, arg, result } => {
writeln!(
f,
"%{result} = {op} %{arg} : {}",
self.body.value_type(result)
)
}
StmtData::Binary { op, lhs, rhs, result } => {
writeln!(
f,
"%{result} = {op} %{lhs}, %{rhs} : {}",
self.body.value_type(result)
)
}
StmtData::Cast { op, arg, result } => {
writeln!(
f,
"%{result} = {op} %{arg} : {} -> {}",
self.body.value_type(arg),
self.body.value_type(result)
)
}
StmtData::Icmp { cc, lhs, rhs, result } => {
writeln!(
f,
"%{result} = icmp {cc} %{lhs}, %{rhs} : {}",
self.body.value_type(lhs)
)
}
StmtData::Fcmp { cc, lhs, rhs, result } => {
writeln!(
f,
"%{result} = fcmp {cc} %{lhs}, %{rhs} : {}",
self.body.value_type(lhs)
)
}
StmtData::Select {
cond,
if_true,
if_false,
result,
} => {
self.emit_value(f, *cond, indent)?;
self.emit_value(f, *if_true, indent)?;
self.emit_value(f, *if_false, indent)?;
writeln!(
f,
"%{result} = select %{cond}, %{if_true}, %{if_false} : {}",
self.body.value_type(result)
)
}
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)
StmtData::Load { addr, size, result } => {
writeln!(
f,
"%{result} = load %{addr} : {size} -> {}",
self.body.value_type(result)
)
}
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)
writeln!(f, "store %{value}, %{addr} : {size}")
}
StmtData::Call { callee, args } => {
write!(f, "call %{}", callee)?;
write!(f, "call %{callee}")?;
let args = args.as_slice(&self.body.value_lists);
if !args.is_empty() {
write!(f, "(")?;
self.write_value_list(f, args.as_slice(&self.body.value_lists))?;
writeln!(f, ")")?;
self.write_value_list(f, args)?;
write!(f, ")")?;
}
Ok(())
writeln!(f)
}
StmtData::Jump { dst } => {
write!(f, "br ")?;
self.write_block_call(f, *dst)?;
self.write_block_call(f, dst)?;
writeln!(f)
}
@ -315,10 +167,10 @@ impl<'a> Printer<'a> {
then_dst,
else_dst,
} => {
write!(f, "cond_br %{}, ", cond)?;
self.write_block_call(f, *then_dst)?;
write!(f, "cond_br %{cond}, ")?;
self.write_block_call(f, then_dst)?;
write!(f, ", ")?;
self.write_block_call(f, *else_dst)?;
self.write_block_call(f, else_dst)?;
writeln!(f)
}
@ -334,29 +186,23 @@ impl<'a> Printer<'a> {
}
}
fn write_block_call(&mut self, f: &mut fmt::Formatter<'_>, call: BlockCall) -> fmt::Result {
fn write_block_call(&self, f: &mut fmt::Formatter<'_>, call: BlockCall) -> fmt::Result {
write!(f, "^{}", call.block)?;
if !call.args.is_empty() {
let args = call.args.as_slice(&self.body.value_lists);
if !args.is_empty() {
write!(f, "(")?;
self.write_value_list(f, call.args.as_slice(&self.body.value_lists))?;
self.write_value_list(f, args)?;
write!(f, ")")?;
}
Ok(())
}
fn write_value_list(&mut self, f: &mut fmt::Formatter<'_>, values: &[Value]) -> fmt::Result {
fn write_value_list(&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, " ")?;
write!(f, "%{value}")?;
}
Ok(())
}