ir: remove expr, keep stmt only
This commit is contained in:
parent
2a414d38e1
commit
fa19fab985
8 changed files with 470 additions and 796 deletions
25
src/ir.rs
25
src/ir.rs
|
|
@ -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.
|
||||
|
|
|
|||
102
src/ir/body.rs
102
src/ir/body.rs
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
129
src/ir/stmt.rs
129
src/ir/stmt.rs
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
128
src/ir/ty.rs
128
src/ir/ty.rs
|
|
@ -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())
|
||||
|
|
|
|||
273
src/ir/verify.rs
273
src/ir/verify.rs
|
|
@ -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!(
|
||||
|
|
|
|||
350
src/ir/write.rs
350
src/ir/write.rs
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue