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 block;
|
||||||
mod body;
|
mod body;
|
||||||
mod dfg;
|
|
||||||
mod expr;
|
mod expr;
|
||||||
mod frontend;
|
mod frontend;
|
||||||
mod layout;
|
|
||||||
mod stmt;
|
mod stmt;
|
||||||
mod ty;
|
mod ty;
|
||||||
mod verify;
|
mod verify;
|
||||||
|
|
@ -15,17 +13,12 @@ pub use block::*;
|
||||||
pub use body::*;
|
pub use body::*;
|
||||||
pub use expr::*;
|
pub use expr::*;
|
||||||
pub use frontend::*;
|
pub use frontend::*;
|
||||||
pub use layout::*;
|
|
||||||
pub use stmt::*;
|
pub use stmt::*;
|
||||||
pub use ty::*;
|
pub use ty::*;
|
||||||
pub use verify::*;
|
pub use verify::*;
|
||||||
pub use write::*;
|
pub use write::*;
|
||||||
|
|
||||||
/// Defines a thin `u32` entity handle.
|
/// 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 {
|
macro_rules! entity {
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
|
|
@ -47,28 +40,10 @@ entity! {
|
||||||
/// A handle to a basic block in a `Body`.
|
/// A handle to a basic block in a `Body`.
|
||||||
pub struct Block = "block";
|
pub struct Block = "block";
|
||||||
|
|
||||||
/// A handle to a pure expression node.
|
|
||||||
///
|
|
||||||
/// Expressions compute values and have no direct side effects.
|
|
||||||
/// They are the primary source of SSA values in the normalized IR.
|
|
||||||
pub struct Expr = "expr";
|
|
||||||
|
|
||||||
|
|
||||||
/// A handle to an ordered statement node.
|
/// 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";
|
pub struct Stmt = "stmt";
|
||||||
|
|
||||||
/// A handle to an instruction in a `Body`.
|
|
||||||
pub struct Inst = "inst";
|
|
||||||
|
|
||||||
/// A handle to an SSA value.
|
/// A handle to an SSA value.
|
||||||
///
|
|
||||||
/// Values are either:
|
|
||||||
///
|
|
||||||
/// - the result of an expression
|
|
||||||
/// - a block parameter
|
|
||||||
pub struct Value = "v";
|
pub struct Value = "v";
|
||||||
|
|
||||||
/// A frontend-level source variable.
|
/// 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.
|
//! 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::{
|
use crate::ir::{
|
||||||
Block, BlockCall, BlockData, Expr, ExprData, MemSize, ParamList, Stmt, StmtData, StmtList,
|
Block, BlockCall, BlockData, ParamList, Stmt, StmtData, StmtList, Type, Value, ValueData,
|
||||||
Type, Value, ValueData, ValueDef, ValueList,
|
ValueDef, ValueList,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Per-expression storage owned by a [`Body`].
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct ExprNode {
|
|
||||||
/// The semantic payload of the expression.
|
|
||||||
pub data: ExprData,
|
|
||||||
|
|
||||||
/// The SSA value produced by this expression.
|
|
||||||
pub value: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A normalized SSA body.
|
/// A normalized SSA body.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Body {
|
pub struct Body {
|
||||||
/// All blocks in the body.
|
/// All blocks in the body.
|
||||||
pub blocks: PrimaryMap<Block, BlockData>,
|
pub blocks: PrimaryMap<Block, BlockData>,
|
||||||
|
|
||||||
/// All expressions in the body.
|
/// All statements in the body, in creation order.
|
||||||
pub exprs: PrimaryMap<Expr, ExprNode>,
|
|
||||||
|
|
||||||
/// All statements in the body.
|
|
||||||
pub stmts: PrimaryMap<Stmt, StmtData>,
|
pub stmts: PrimaryMap<Stmt, StmtData>,
|
||||||
|
|
||||||
/// All SSA values in the body.
|
/// All SSA values in the body.
|
||||||
|
|
@ -45,12 +32,9 @@ impl Body {
|
||||||
Self::default()
|
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 {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.blocks.is_empty()
|
self.blocks.is_empty() && self.stmts.is_empty() && self.values.is_empty()
|
||||||
&& self.exprs.is_empty()
|
|
||||||
&& self.stmts.is_empty()
|
|
||||||
&& self.values.is_empty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of blocks in the body.
|
/// Returns the number of blocks in the body.
|
||||||
|
|
@ -58,11 +42,6 @@ impl Body {
|
||||||
self.blocks.len()
|
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.
|
/// Returns the number of statements in the body.
|
||||||
pub fn num_stmts(&self) -> usize {
|
pub fn num_stmts(&self) -> usize {
|
||||||
self.stmts.len()
|
self.stmts.len()
|
||||||
|
|
@ -129,13 +108,36 @@ impl Body {
|
||||||
ValueList::from_slice(values, &mut self.value_lists)
|
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 {
|
pub fn append_stmt(&mut self, block: Block, data: StmtData) -> Stmt {
|
||||||
let stmt = self.stmts.push(data);
|
let stmt = self.stmts.push(data);
|
||||||
self.blocks[block].stmts.push(stmt, &mut self.stmt_lists);
|
self.blocks[block].stmts.push(stmt, &mut self.stmt_lists);
|
||||||
stmt
|
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`.
|
/// Returns the data for `stmt`.
|
||||||
pub fn stmt_data(&self, stmt: Stmt) -> &StmtData {
|
pub fn stmt_data(&self, stmt: Stmt) -> &StmtData {
|
||||||
&self.stmts[stmt]
|
&self.stmts[stmt]
|
||||||
|
|
@ -146,32 +148,9 @@ impl Body {
|
||||||
&mut self.stmts[stmt]
|
&mut self.stmts[stmt]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new expression producing one SSA value of type `ty`.
|
/// Returns the result value of `stmt`, if it produces one.
|
||||||
pub fn create_expr(&mut self, data: ExprData, ty: Type) -> Expr {
|
pub fn inst_result(&self, stmt: Stmt) -> Option<Value> {
|
||||||
let expr = self.exprs.next_key();
|
self.stmts[stmt].result()
|
||||||
let value = self.values.push(ValueData {
|
|
||||||
ty,
|
|
||||||
def: ValueDef::Expr(expr),
|
|
||||||
});
|
|
||||||
|
|
||||||
let expr = self.exprs.push(ExprNode { data, value });
|
|
||||||
debug_assert_eq!(self.expr_value(expr), value);
|
|
||||||
expr
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the data for `expr`.
|
|
||||||
pub fn expr_data(&self, expr: Expr) -> &ExprData {
|
|
||||||
&self.exprs[expr].data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the mutable data for `expr`.
|
|
||||||
pub fn expr_data_mut(&mut self, expr: Expr) -> &mut ExprData {
|
|
||||||
&mut self.exprs[expr].data
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the SSA value produced by `expr`.
|
|
||||||
pub fn expr_value(&self, expr: Expr) -> Value {
|
|
||||||
self.exprs[expr].value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the full value record for `value`.
|
/// 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.
|
/// Returns the terminator statement of `block`, if the block ends in one.
|
||||||
pub fn block_terminator(&self, block: Block) -> Option<Stmt> {
|
pub fn block_terminator(&self, block: Block) -> Option<Stmt> {
|
||||||
let stmt = self.last_stmt(block)?;
|
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.
|
/// 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)?;
|
let stmt = self.block_terminator(block)?;
|
||||||
Some(self.stmt_data(stmt))
|
Some(self.stmts[stmt])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the CFG successors of `block`.
|
/// 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 {
|
pub fn block_successors(&self, block: Block) -> BlockSuccessors {
|
||||||
let Some(term) = self.block_terminator_data(block) else {
|
let Some(term) = self.block_terminator_data(block) else {
|
||||||
return BlockSuccessors::Empty;
|
return BlockSuccessors::Empty;
|
||||||
|
|
@ -221,7 +198,7 @@ impl Body {
|
||||||
|
|
||||||
StmtData::Return { .. } => BlockSuccessors::Empty,
|
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.
|
/// Iterator over the CFG successors of a block.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BlockSuccessors {
|
pub enum BlockSuccessors {
|
||||||
/// No successors.
|
|
||||||
Empty,
|
Empty,
|
||||||
|
|
||||||
/// Exactly one successor.
|
|
||||||
One(Option<Block>),
|
One(Option<Block>),
|
||||||
|
|
||||||
/// Exactly two successors.
|
|
||||||
Two {
|
Two {
|
||||||
first: Option<Block>,
|
first: Option<Block>,
|
||||||
second: 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 core::fmt;
|
||||||
|
|
||||||
use crate::ir::{Block, Expr, Type, Value};
|
use crate::ir::{Block, Stmt, Type, Value};
|
||||||
|
|
||||||
/// Memory access width in bytes.
|
/// Memory access width in bytes.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -39,8 +39,6 @@ impl fmt::Display for MemSize {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Integer comparison condition codes.
|
/// Integer comparison condition codes.
|
||||||
///
|
|
||||||
/// These are used by [`ExprData::Icmp`].
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum IntCC {
|
pub enum IntCC {
|
||||||
Eq,
|
Eq,
|
||||||
|
|
@ -117,8 +115,6 @@ impl fmt::Display for IntCC {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Floating-point comparison condition codes.
|
/// Floating-point comparison condition codes.
|
||||||
///
|
|
||||||
/// These are used by [`ExprData::Fcmp`].
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum FloatCC {
|
pub enum FloatCC {
|
||||||
Eq,
|
Eq,
|
||||||
|
|
@ -181,7 +177,7 @@ impl fmt::Display for FloatCC {
|
||||||
/// Unary expression operators.
|
/// Unary expression operators.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum UnaryOp {
|
pub enum UnaryOp {
|
||||||
/// Integer or bitvector negation.
|
/// Integer negation.
|
||||||
Neg,
|
Neg,
|
||||||
|
|
||||||
/// Bitwise not.
|
/// 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.
|
/// The definition site of an SSA value.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ValueDef {
|
pub enum ValueDef {
|
||||||
/// A value defined by an expression.
|
/// A value defined by a statement.
|
||||||
Expr(Expr),
|
Inst(Stmt),
|
||||||
|
|
||||||
/// A block parameter at position `index`.
|
/// A block parameter at position `index`.
|
||||||
Param(Block, u16),
|
Param(Block, u16),
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::ir::{
|
use crate::ir::{
|
||||||
BinaryOp, Block, Body, CastOp, ExprData, FloatCC, IntCC, MemSize, Stmt, StmtData, Type,
|
BinaryOp, Block, Body, CastOp, FloatCC, IntCC, MemSize, Stmt, StmtData, Type, UnaryOp, Value,
|
||||||
UnaryOp, Value, Variable,
|
Variable,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -150,8 +150,6 @@ impl<'a> FrontendBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks `block` as sealed.
|
/// Marks `block` as sealed.
|
||||||
///
|
|
||||||
/// Sealing a block means all of its predecessors are now known.
|
|
||||||
pub fn seal_block(&mut self, block: Block) {
|
pub fn seal_block(&mut self, block: Block) {
|
||||||
self.ctx.blocks.entry(block).or_default().sealed = true;
|
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.
|
/// 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) {
|
pub fn def_var(&mut self, var: Variable, value: Value) {
|
||||||
let block = self
|
let block = self
|
||||||
.cur_block
|
.cur_block
|
||||||
|
|
@ -194,16 +184,6 @@ impl<'a> FrontendBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the current SSA value for frontend variable `var`.
|
/// 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 {
|
pub fn use_var(&mut self, var: Variable) -> Value {
|
||||||
let block = self
|
let block = self
|
||||||
.cur_block
|
.cur_block
|
||||||
|
|
@ -230,17 +210,12 @@ impl<'a> FrontendBuilder<'a> {
|
||||||
.map(|state| state.preds.clone())
|
.map(|state| state.preds.clone())
|
||||||
.unwrap_or_default();
|
.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() {
|
if self.is_sealed(block) && preds.is_empty() {
|
||||||
panic!(
|
panic!(
|
||||||
"variable {var} used in sealed block {block} with no local definition and no predecessors"
|
"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)
|
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()
|
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 {
|
fn append_stmt_in_current_block(&mut self, data: StmtData) -> Stmt {
|
||||||
let block = self
|
let block = self
|
||||||
.cur_block
|
.cur_block
|
||||||
|
|
@ -350,138 +346,167 @@ impl<'a> FrontendBuilder<'a> {
|
||||||
stmt
|
stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_expr_value(&mut self, data: ExprData, ty: Type) -> Value {
|
// ------------------------------------------------------------------ //
|
||||||
let expr = self.body.create_expr(data, ty);
|
// Value-producing instructions //
|
||||||
self.body.expr_value(expr)
|
// ------------------------------------------------------------------ //
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an integer constant expression.
|
/// Appends an integer constant instruction.
|
||||||
pub fn iconst(&mut self, ty: Type, imm: i64) -> Value {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
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 {
|
pub fn select(&mut self, cond: Value, if_true: Value, if_false: Value, ty: Type) -> Value {
|
||||||
self.create_expr_value(
|
self.append_value_inst_in_current_block(ty, |result| StmtData::Select {
|
||||||
ExprData::Select {
|
|
||||||
cond,
|
cond,
|
||||||
if_true,
|
if_true,
|
||||||
if_false,
|
if_false,
|
||||||
},
|
result,
|
||||||
ty,
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a memory load expression.
|
/// Appends a memory load instruction.
|
||||||
pub fn load(&mut self, addr: Value, size: MemSize, ty: Type) -> Value {
|
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 {
|
pub fn iadd(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::IAdd, lhs, rhs, ty)
|
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 {
|
pub fn isub(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::ISub, lhs, rhs, ty)
|
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 {
|
pub fn imul(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::IMul, lhs, rhs, ty)
|
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 {
|
pub fn and(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::And, lhs, rhs, ty)
|
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 {
|
pub fn or(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::Or, lhs, rhs, ty)
|
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 {
|
pub fn xor(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::Xor, lhs, rhs, ty)
|
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 {
|
pub fn shl(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::Shl, lhs, rhs, ty)
|
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 {
|
pub fn lshr(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::LShr, lhs, rhs, ty)
|
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 {
|
pub fn ashr(&mut self, lhs: Value, rhs: Value, ty: Type) -> Value {
|
||||||
self.binary(BinaryOp::AShr, lhs, rhs, ty)
|
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 {
|
pub fn zext(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
self.cast(CastOp::Zext, arg, ty)
|
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 {
|
pub fn sext(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
self.cast(CastOp::Sext, arg, ty)
|
self.cast(CastOp::Sext, arg, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a truncation expression.
|
/// Appends a truncation instruction.
|
||||||
pub fn trunc(&mut self, arg: Value, ty: Type) -> Value {
|
pub fn trunc(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
self.cast(CastOp::Trunc, arg, ty)
|
self.cast(CastOp::Trunc, arg, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a bitcast expression.
|
/// Appends a bitcast instruction.
|
||||||
pub fn bitcast(&mut self, arg: Value, ty: Type) -> Value {
|
pub fn bitcast(&mut self, arg: Value, ty: Type) -> Value {
|
||||||
self.cast(CastOp::Bitcast, arg, ty)
|
self.cast(CastOp::Bitcast, arg, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
// Side-effecting and terminator statements //
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
|
||||||
/// Appends a memory store statement.
|
/// Appends a memory store statement.
|
||||||
pub fn store(&mut self, addr: Value, value: Value, size: MemSize) -> Stmt {
|
pub fn store(&mut self, addr: Value, value: Value, size: MemSize) -> Stmt {
|
||||||
self.append_stmt_in_current_block(StmtData::Store { addr, value, size })
|
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`.
|
/// 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 {
|
pub fn jump(&mut self, dst: Block) -> Stmt {
|
||||||
let pred = self
|
let pred = self
|
||||||
.cur_block
|
.cur_block
|
||||||
|
|
@ -511,9 +533,6 @@ impl<'a> FrontendBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends a conditional branch.
|
/// 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 {
|
pub fn br_if(&mut self, cond: Value, then_block: Block, else_block: Block) -> Stmt {
|
||||||
let pred = self
|
let pred = self
|
||||||
.cur_block
|
.cur_block
|
||||||
|
|
|
||||||
129
src/ir/stmt.rs
129
src/ir/stmt.rs
|
|
@ -1,6 +1,12 @@
|
||||||
//! Ordered block-local statements for Slonik IR.
|
//! 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;
|
use cranelift_entity::EntityList;
|
||||||
|
|
||||||
/// A compact list of SSA values.
|
/// A compact list of SSA values.
|
||||||
|
|
@ -16,25 +22,72 @@ pub struct BlockCall {
|
||||||
pub args: ValueList,
|
pub args: ValueList,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An ordered block-local statement.
|
/// An ordered statement inside a basic block.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum StmtData {
|
|
||||||
/// Store `value` to memory at `addr`.
|
|
||||||
///
|
///
|
||||||
/// `size` is the access width in bytes.
|
/// Variants that carry a `result` field produce exactly one SSA value.
|
||||||
Store {
|
/// The `result` value's [`ValueDef`] points back to this statement.
|
||||||
addr: Value,
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
value: Value,
|
pub enum StmtData {
|
||||||
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 },
|
Call { callee: Value, args: ValueList },
|
||||||
|
|
||||||
/// Unconditional transfer of control to another block.
|
// ------------------------------------------------------------------ //
|
||||||
|
// Terminators //
|
||||||
|
// ------------------------------------------------------------------ //
|
||||||
|
|
||||||
|
/// Unconditional jump.
|
||||||
Jump { dst: BlockCall },
|
Jump { dst: BlockCall },
|
||||||
|
|
||||||
/// Conditional transfer of control.
|
/// Conditional branch.
|
||||||
BrIf {
|
BrIf {
|
||||||
cond: Value,
|
cond: Value,
|
||||||
then_dst: BlockCall,
|
then_dst: BlockCall,
|
||||||
|
|
@ -46,33 +99,41 @@ pub enum StmtData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StmtData {
|
impl StmtData {
|
||||||
/// Returns whether this statement is a terminator.
|
/// Returns the result value produced by this statement, if any.
|
||||||
pub const fn is_terminator(&self) -> bool {
|
pub const fn result(self) -> Option<Value> {
|
||||||
matches!(
|
match self {
|
||||||
self,
|
Self::IConst { result, .. }
|
||||||
Self::Jump { .. } | Self::BrIf { .. } | Self::Return { .. }
|
| 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.
|
/// Returns whether this statement may read memory.
|
||||||
pub const fn may_read_memory(&self) -> bool {
|
pub const fn may_read_memory(self) -> bool {
|
||||||
matches!(self, Self::Call { .. })
|
matches!(self, Self::Load { .. } | Self::Call { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this statement may write memory.
|
/// 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 { .. })
|
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.
|
/// A semantic type in Slonik.
|
||||||
///
|
///
|
||||||
/// `Type` describes the meaning and shape of an SSA value.
|
/// `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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
/// The absence of a value.
|
/// The absence of a value.
|
||||||
|
|
@ -15,15 +17,12 @@ pub enum Type {
|
||||||
/// A one-bit boolean.
|
/// A one-bit boolean.
|
||||||
Bool,
|
Bool,
|
||||||
|
|
||||||
/// A signedness-agnostic integer bitvector.
|
/// A signedness-agnostic integer of the given bit width.
|
||||||
Int(IntType),
|
I(u16),
|
||||||
|
|
||||||
/// A floating-point scalar.
|
/// A floating-point scalar.
|
||||||
Float(FloatType),
|
Float(FloatType),
|
||||||
|
|
||||||
/// A raw bitvector with no numeric interpretation yet.
|
|
||||||
Bits(BitsType),
|
|
||||||
|
|
||||||
/// A pointer value.
|
/// A pointer value.
|
||||||
Ptr(PtrType),
|
Ptr(PtrType),
|
||||||
|
|
||||||
|
|
@ -31,29 +30,6 @@ pub enum Type {
|
||||||
Vector(VectorType),
|
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.
|
/// A floating-point scalar type.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum FloatType {
|
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.
|
/// A pointer type.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct PtrType {
|
pub struct PtrType {
|
||||||
|
|
@ -139,14 +92,11 @@ impl PtrType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A scalar lane type for a vector.
|
/// A scalar lane type for a vector.
|
||||||
///
|
|
||||||
/// Vector lanes are semantic scalar elements, not storage slots.
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum LaneType {
|
pub enum LaneType {
|
||||||
Bool,
|
Bool,
|
||||||
Int(IntType),
|
I(u16),
|
||||||
Float(FloatType),
|
Float(FloatType),
|
||||||
Bits(BitsType),
|
|
||||||
Ptr(PtrType),
|
Ptr(PtrType),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,9 +105,8 @@ impl LaneType {
|
||||||
pub const fn bits(self) -> u16 {
|
pub const fn bits(self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
Self::Bool => 1,
|
Self::Bool => 1,
|
||||||
Self::Int(ty) => ty.bits(),
|
Self::I(bits) => bits,
|
||||||
Self::Float(ty) => ty.bits(),
|
Self::Float(ty) => ty.bits(),
|
||||||
Self::Bits(ty) => ty.bits(),
|
|
||||||
Self::Ptr(ty) => ty.bits(),
|
Self::Ptr(ty) => ty.bits(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,9 +115,8 @@ impl LaneType {
|
||||||
pub const fn as_type(self) -> Type {
|
pub const fn as_type(self) -> Type {
|
||||||
match self {
|
match self {
|
||||||
Self::Bool => Type::Bool,
|
Self::Bool => Type::Bool,
|
||||||
Self::Int(ty) => Type::Int(ty),
|
Self::I(bits) => Type::I(bits),
|
||||||
Self::Float(ty) => Type::Float(ty),
|
Self::Float(ty) => Type::Float(ty),
|
||||||
Self::Bits(ty) => Type::Bits(ty),
|
|
||||||
Self::Ptr(ty) => Type::Ptr(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.
|
/// Returns the number of lanes.
|
||||||
pub const fn lanes(self) -> u16 {
|
pub const fn lanes(self) -> u16 {
|
||||||
self.lanes
|
self.lanes
|
||||||
|
|
@ -232,8 +166,6 @@ impl VectorType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the minimum number of bits in the vector.
|
/// 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 {
|
pub const fn min_bits(self) -> u32 {
|
||||||
(self.lanes as u32) * (self.lane.bits() as u32)
|
(self.lanes as u32) * (self.lane.bits() as u32)
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +184,7 @@ impl Type {
|
||||||
|
|
||||||
/// Returns an integer type with the given width.
|
/// Returns an integer type with the given width.
|
||||||
pub const fn int(bits: u16) -> Self {
|
pub const fn int(bits: u16) -> Self {
|
||||||
Self::Int(IntType::new(bits))
|
Self::I(bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a floating-point type.
|
/// Returns a floating-point type.
|
||||||
|
|
@ -260,11 +192,6 @@ impl Type {
|
||||||
Self::Float(kind)
|
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.
|
/// Returns a pointer type in address space 0.
|
||||||
pub const fn ptr(bits: u16) -> Self {
|
pub const fn ptr(bits: u16) -> Self {
|
||||||
Self::Ptr(PtrType::new(bits))
|
Self::Ptr(PtrType::new(bits))
|
||||||
|
|
@ -280,11 +207,6 @@ impl Type {
|
||||||
Self::Vector(VectorType::new(lane, lanes))
|
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`.
|
/// Returns whether this is `void`.
|
||||||
pub const fn is_void(self) -> bool {
|
pub const fn is_void(self) -> bool {
|
||||||
matches!(self, Self::Void)
|
matches!(self, Self::Void)
|
||||||
|
|
@ -297,7 +219,7 @@ impl Type {
|
||||||
|
|
||||||
/// Returns whether this is an integer.
|
/// Returns whether this is an integer.
|
||||||
pub const fn is_int(self) -> bool {
|
pub const fn is_int(self) -> bool {
|
||||||
matches!(self, Self::Int(_))
|
matches!(self, Self::I(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this is a float.
|
/// Returns whether this is a float.
|
||||||
|
|
@ -305,11 +227,6 @@ impl Type {
|
||||||
matches!(self, Self::Float(_))
|
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.
|
/// Returns whether this is a pointer.
|
||||||
pub const fn is_ptr(self) -> bool {
|
pub const fn is_ptr(self) -> bool {
|
||||||
matches!(self, Self::Ptr(_))
|
matches!(self, Self::Ptr(_))
|
||||||
|
|
@ -330,9 +247,8 @@ impl Type {
|
||||||
match self {
|
match self {
|
||||||
Self::Void => None,
|
Self::Void => None,
|
||||||
Self::Bool => Some(1),
|
Self::Bool => Some(1),
|
||||||
Self::Int(ty) => Some(ty.bits()),
|
Self::I(bits) => Some(bits),
|
||||||
Self::Float(ty) => Some(ty.bits()),
|
Self::Float(ty) => Some(ty.bits()),
|
||||||
Self::Bits(ty) => Some(ty.bits()),
|
|
||||||
Self::Ptr(ty) => Some(ty.bits()),
|
Self::Ptr(ty) => Some(ty.bits()),
|
||||||
Self::Vector(_) => None,
|
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.
|
/// Returns the number of lanes if this is a vector.
|
||||||
pub const fn lanes(self) -> Option<u16> {
|
pub const fn lanes(self) -> Option<u16> {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -364,27 +272,27 @@ impl Type {
|
||||||
|
|
||||||
/// Convenience constructor for `i8`.
|
/// Convenience constructor for `i8`.
|
||||||
pub const fn i8() -> Self {
|
pub const fn i8() -> Self {
|
||||||
Self::int(8)
|
Self::I(8)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience constructor for `i16`.
|
/// Convenience constructor for `i16`.
|
||||||
pub const fn i16() -> Self {
|
pub const fn i16() -> Self {
|
||||||
Self::int(16)
|
Self::I(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience constructor for `i32`.
|
/// Convenience constructor for `i32`.
|
||||||
pub const fn i32() -> Self {
|
pub const fn i32() -> Self {
|
||||||
Self::int(32)
|
Self::I(32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience constructor for `i64`.
|
/// Convenience constructor for `i64`.
|
||||||
pub const fn i64() -> Self {
|
pub const fn i64() -> Self {
|
||||||
Self::int(64)
|
Self::I(64)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience constructor for `i128`.
|
/// Convenience constructor for `i128`.
|
||||||
pub const fn i128() -> Self {
|
pub const fn i128() -> Self {
|
||||||
Self::int(128)
|
Self::I(128)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience constructor for `f32`.
|
/// Convenience constructor for `f32`.
|
||||||
|
|
@ -403,13 +311,12 @@ impl fmt::Display for Type {
|
||||||
match self {
|
match self {
|
||||||
Type::Void => f.write_str("void"),
|
Type::Void => f.write_str("void"),
|
||||||
Type::Bool => f.write_str("bool"),
|
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::F16) => f.write_str("f16"),
|
||||||
Type::Float(FloatType::F32) => f.write_str("f32"),
|
Type::Float(FloatType::F32) => f.write_str("f32"),
|
||||||
Type::Float(FloatType::F64) => f.write_str("f64"),
|
Type::Float(FloatType::F64) => f.write_str("f64"),
|
||||||
Type::Float(FloatType::F80) => f.write_str("f80"),
|
Type::Float(FloatType::F80) => f.write_str("f80"),
|
||||||
Type::Float(FloatType::F128) => f.write_str("f128"),
|
Type::Float(FloatType::F128) => f.write_str("f128"),
|
||||||
Type::Bits(ty) => write!(f, "b{}", ty.bits()),
|
|
||||||
Type::Ptr(ty) => {
|
Type::Ptr(ty) => {
|
||||||
if ty.addr_space() == 0 {
|
if ty.addr_space() == 0 {
|
||||||
write!(f, "ptr{}", ty.bits())
|
write!(f, "ptr{}", ty.bits())
|
||||||
|
|
@ -432,13 +339,12 @@ impl fmt::Display for LaneType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
LaneType::Bool => f.write_str("bool"),
|
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::F16) => f.write_str("f16"),
|
||||||
LaneType::Float(FloatType::F32) => f.write_str("f32"),
|
LaneType::Float(FloatType::F32) => f.write_str("f32"),
|
||||||
LaneType::Float(FloatType::F64) => f.write_str("f64"),
|
LaneType::Float(FloatType::F64) => f.write_str("f64"),
|
||||||
LaneType::Float(FloatType::F80) => f.write_str("f80"),
|
LaneType::Float(FloatType::F80) => f.write_str("f80"),
|
||||||
LaneType::Float(FloatType::F128) => f.write_str("f128"),
|
LaneType::Float(FloatType::F128) => f.write_str("f128"),
|
||||||
LaneType::Bits(ty) => write!(f, "b{}", ty.bits()),
|
|
||||||
LaneType::Ptr(ty) => {
|
LaneType::Ptr(ty) => {
|
||||||
if ty.addr_space() == 0 {
|
if ty.addr_space() == 0 {
|
||||||
write!(f, "ptr{}", ty.bits())
|
write!(f, "ptr{}", ty.bits())
|
||||||
|
|
|
||||||
273
src/ir/verify.rs
273
src/ir/verify.rs
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use core::fmt;
|
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.
|
/// A verification error.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -37,7 +37,6 @@ impl Verifier<'_> {
|
||||||
fn run(self) -> Result<(), VerifyError> {
|
fn run(self) -> Result<(), VerifyError> {
|
||||||
self.verify_values()?;
|
self.verify_values()?;
|
||||||
self.verify_blocks()?;
|
self.verify_blocks()?;
|
||||||
self.verify_exprs()?;
|
|
||||||
self.verify_stmts()?;
|
self.verify_stmts()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -45,15 +44,20 @@ impl Verifier<'_> {
|
||||||
fn verify_values(&self) -> Result<(), VerifyError> {
|
fn verify_values(&self) -> Result<(), VerifyError> {
|
||||||
for (value, data) in self.body.values.iter() {
|
for (value, data) in self.body.values.iter() {
|
||||||
match data.def {
|
match data.def {
|
||||||
ValueDef::Expr(expr) => {
|
ValueDef::Inst(stmt) => {
|
||||||
self.ensure_valid_expr(expr, format!("value {value}"))?;
|
self.ensure_valid_stmt(stmt, format!("value {value}"))?;
|
||||||
|
|
||||||
let owner = &self.body.exprs[expr];
|
let result = self.body.stmts[stmt].result().ok_or_else(|| {
|
||||||
if owner.value != value {
|
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!(
|
return Err(VerifyError::new(format!(
|
||||||
"value {value} claims to be defined by expression {expr}, \
|
"value {value} claims to be defined by statement {stmt}, \
|
||||||
but that expression produces {}",
|
but that statement's result is {result}"
|
||||||
owner.value
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,128 +122,6 @@ impl Verifier<'_> {
|
||||||
"terminator statement {stmt} in {block} is not the final statement"
|
"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> {
|
fn verify_stmts(&self) -> Result<(), VerifyError> {
|
||||||
for (stmt, data) in self.body.stmts.iter() {
|
for (stmt, &data) in self.body.stmts.iter() {
|
||||||
match data {
|
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, .. } => {
|
StmtData::Store { addr, value, .. } => {
|
||||||
self.ensure_valid_value(*addr, format!("statement {stmt} store address"))?;
|
self.ensure_valid_value(addr, format!("statement {stmt} store address"))?;
|
||||||
self.ensure_valid_value(*value, format!("statement {stmt} store value"))?;
|
self.ensure_valid_value(value, format!("statement {stmt} store value"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
StmtData::Call { callee, args } => {
|
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(
|
self.verify_value_list(
|
||||||
args.as_slice(&self.body.value_lists),
|
args.as_slice(&self.body.value_lists),
|
||||||
format!("statement {stmt} call arguments"),
|
format!("statement {stmt} call arguments"),
|
||||||
|
|
@ -263,7 +225,7 @@ impl Verifier<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
StmtData::Jump { dst } => {
|
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 {
|
StmtData::BrIf {
|
||||||
|
|
@ -271,18 +233,17 @@ impl Verifier<'_> {
|
||||||
then_dst,
|
then_dst,
|
||||||
else_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!(
|
return Err(VerifyError::new(format!(
|
||||||
"statement {stmt} branch condition {} must have type bool, got {}",
|
"statement {stmt} branch condition must have type bool, got {}",
|
||||||
cond,
|
self.body.value_type(cond)
|
||||||
self.body.value_type(*cond)
|
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.verify_block_call(*then_dst, format!("statement {stmt} then-target"))?;
|
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(else_dst, format!("statement {stmt} else-target"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
StmtData::Return { values } => {
|
StmtData::Return { values } => {
|
||||||
|
|
@ -297,6 +258,20 @@ impl Verifier<'_> {
|
||||||
Ok(())
|
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> {
|
fn verify_block_call(&self, call: BlockCall, context: String) -> Result<(), VerifyError> {
|
||||||
self.ensure_valid_block(call.block, context.clone())?;
|
self.ensure_valid_block(call.block, context.clone())?;
|
||||||
|
|
||||||
|
|
@ -320,7 +295,8 @@ impl Verifier<'_> {
|
||||||
|
|
||||||
if arg_ty != param_ty {
|
if arg_ty != param_ty {
|
||||||
return Err(VerifyError::new(format!(
|
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
|
call.block, arg_ty, param_ty
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
@ -345,15 +321,6 @@ impl Verifier<'_> {
|
||||||
Ok(())
|
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> {
|
fn ensure_valid_stmt(&self, stmt: Stmt, context: String) -> Result<(), VerifyError> {
|
||||||
if !self.body.stmts.is_valid(stmt) {
|
if !self.body.stmts.is_valid(stmt) {
|
||||||
return Err(VerifyError::new(format!(
|
return Err(VerifyError::new(format!(
|
||||||
|
|
|
||||||
332
src/ir/write.rs
332
src/ir/write.rs
|
|
@ -1,17 +1,12 @@
|
||||||
//! Text formatting for Slonik IR.
|
//! 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 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.
|
/// Writes `body` in textual form.
|
||||||
pub fn write_body(f: &mut fmt::Formatter<'_>, body: &Body) -> fmt::Result {
|
pub fn write_body(f: &mut fmt::Formatter<'_>, body: &Body) -> fmt::Result {
|
||||||
let mut printer = Printer::new(body);
|
Printer { body }.write_body(f)
|
||||||
printer.write_body(f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Body {
|
impl fmt::Display for Body {
|
||||||
|
|
@ -22,63 +17,26 @@ impl fmt::Display for Body {
|
||||||
|
|
||||||
struct Printer<'a> {
|
struct Printer<'a> {
|
||||||
body: &'a Body,
|
body: &'a Body,
|
||||||
emitted: HashSet<Value>,
|
|
||||||
visiting: HashSet<Expr>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Printer<'a> {
|
impl Printer<'_> {
|
||||||
fn new(body: &'a Body) -> Self {
|
fn write_body(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
Self {
|
|
||||||
body,
|
|
||||||
emitted: HashSet::new(),
|
|
||||||
visiting: HashSet::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_body(&mut self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
writeln!(f, "body {{")?;
|
writeln!(f, "body {{")?;
|
||||||
|
|
||||||
let mut first_block = true;
|
let mut first = true;
|
||||||
for block in self.body.blocks() {
|
for block in self.body.blocks() {
|
||||||
if !first_block {
|
if !first {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
}
|
}
|
||||||
first_block = false;
|
first = false;
|
||||||
self.write_block(f, block)?;
|
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, "}}")
|
writeln!(f, "}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_block(&mut self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result {
|
fn write_block(&self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result {
|
||||||
self.write_block_header(f, block)?;
|
write!(f, " ^{block}")?;
|
||||||
|
|
||||||
for &stmt in self.body.block_stmts(block) {
|
|
||||||
self.emit_stmt_deps(f, stmt, 2)?;
|
|
||||||
self.write_stmt(f, stmt, 2)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_block_header(&mut self, f: &mut fmt::Formatter<'_>, block: Block) -> fmt::Result {
|
|
||||||
write!(f, " ^{}", block)?;
|
|
||||||
|
|
||||||
let params = self.body.block_params(block);
|
let params = self.body.block_params(block);
|
||||||
if !params.is_empty() {
|
if !params.is_empty() {
|
||||||
|
|
@ -87,226 +45,120 @@ impl<'a> Printer<'a> {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
}
|
}
|
||||||
|
write!(f, "%{}: {}", param, self.body.value_type(param))?;
|
||||||
let ty = self.body.value_type(param);
|
|
||||||
write!(f, "%{}: {}", param, ty)?;
|
|
||||||
}
|
}
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f, ":")
|
writeln!(f, ":")?;
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_stmt_deps(
|
for &stmt in self.body.block_stmts(block) {
|
||||||
&mut self,
|
self.write_stmt(f, stmt)?;
|
||||||
f: &mut fmt::Formatter<'_>,
|
|
||||||
stmt: Stmt,
|
|
||||||
indent: usize,
|
|
||||||
) -> fmt::Result {
|
|
||||||
match self.body.stmt_data(stmt) {
|
|
||||||
StmtData::Store { addr, value, .. } => {
|
|
||||||
self.emit_value(f, *addr, indent)?;
|
|
||||||
self.emit_value(f, *value, indent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
StmtData::Call { callee, args } => {
|
|
||||||
self.emit_value(f, *callee, indent)?;
|
|
||||||
for &arg in args.as_slice(&self.body.value_lists) {
|
|
||||||
self.emit_value(f, arg, indent)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StmtData::Jump { dst } => {
|
|
||||||
self.emit_block_call_args(f, *dst, indent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
StmtData::BrIf {
|
|
||||||
cond,
|
|
||||||
then_dst,
|
|
||||||
else_dst,
|
|
||||||
} => {
|
|
||||||
self.emit_value(f, *cond, indent)?;
|
|
||||||
self.emit_block_call_args(f, *then_dst, indent)?;
|
|
||||||
self.emit_block_call_args(f, *else_dst, indent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
StmtData::Return { values } => {
|
|
||||||
for &value in values.as_slice(&self.body.value_lists) {
|
|
||||||
self.emit_value(f, value, indent)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_block_call_args(
|
fn write_stmt(&self, f: &mut fmt::Formatter<'_>, stmt: Stmt) -> fmt::Result {
|
||||||
&mut self,
|
write!(f, " ")?;
|
||||||
f: &mut fmt::Formatter<'_>,
|
|
||||||
call: BlockCall,
|
match *self.body.stmt_data(stmt) {
|
||||||
indent: usize,
|
StmtData::IConst { imm, result } => {
|
||||||
) -> fmt::Result {
|
writeln!(f, "%{result} = iconst {imm} : {}", self.body.value_type(result))
|
||||||
for &arg in call.args.as_slice(&self.body.value_lists) {
|
|
||||||
self.emit_value(f, arg, indent)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_value(
|
StmtData::F32Const { bits, result } => {
|
||||||
&mut self,
|
writeln!(f, "%{result} = f32const 0x{bits:08x} : f32")
|
||||||
f: &mut fmt::Formatter<'_>,
|
|
||||||
value: Value,
|
|
||||||
indent: usize,
|
|
||||||
) -> fmt::Result {
|
|
||||||
if self.emitted.contains(&value) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.body.value_def(value) {
|
StmtData::F64Const { bits, result } => {
|
||||||
ValueDef::Param(_, _) => Ok(()),
|
writeln!(f, "%{result} = f64const 0x{bits:016x} : f64")
|
||||||
ValueDef::Expr(expr) => self.emit_expr(f, expr, indent),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_expr(&mut self, f: &mut fmt::Formatter<'_>, expr: Expr, indent: usize) -> fmt::Result {
|
StmtData::BConst { value, result } => {
|
||||||
let value = self.body.expr_value(expr);
|
writeln!(f, "%{result} = bconst {value} : bool")
|
||||||
if self.emitted.contains(&value) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.visiting.insert(expr) {
|
StmtData::Unary { op, arg, result } => {
|
||||||
return Err(fmt::Error);
|
writeln!(
|
||||||
|
f,
|
||||||
|
"%{result} = {op} %{arg} : {}",
|
||||||
|
self.body.value_type(result)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.body.expr_data(expr) {
|
StmtData::Binary { op, lhs, rhs, result } => {
|
||||||
ExprData::IConst { .. }
|
writeln!(
|
||||||
| ExprData::F32Const { .. }
|
f,
|
||||||
| ExprData::F64Const { .. }
|
"%{result} = {op} %{lhs}, %{rhs} : {}",
|
||||||
| ExprData::BConst { .. } => {}
|
self.body.value_type(result)
|
||||||
|
)
|
||||||
ExprData::Unary { arg, .. } | ExprData::Cast { arg, .. } => {
|
|
||||||
self.emit_value(f, *arg, indent)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprData::Binary { lhs, rhs, .. }
|
StmtData::Cast { op, arg, result } => {
|
||||||
| ExprData::Icmp { lhs, rhs, .. }
|
writeln!(
|
||||||
| ExprData::Fcmp { lhs, rhs, .. } => {
|
f,
|
||||||
self.emit_value(f, *lhs, indent)?;
|
"%{result} = {op} %{arg} : {} -> {}",
|
||||||
self.emit_value(f, *rhs, indent)?;
|
self.body.value_type(arg),
|
||||||
|
self.body.value_type(result)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprData::Select {
|
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,
|
cond,
|
||||||
if_true,
|
if_true,
|
||||||
if_false,
|
if_false,
|
||||||
|
result,
|
||||||
} => {
|
} => {
|
||||||
self.emit_value(f, *cond, indent)?;
|
writeln!(
|
||||||
self.emit_value(f, *if_true, indent)?;
|
f,
|
||||||
self.emit_value(f, *if_false, indent)?;
|
"%{result} = select %{cond}, %{if_true}, %{if_false} : {}",
|
||||||
|
self.body.value_type(result)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprData::Load { addr, .. } => {
|
StmtData::Load { addr, size, result } => {
|
||||||
self.emit_value(f, *addr, indent)?;
|
writeln!(
|
||||||
}
|
f,
|
||||||
|
"%{result} = load %{addr} : {size} -> {}",
|
||||||
|
self.body.value_type(result)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.write_indent(f, indent)?;
|
|
||||||
self.write_expr_binding(f, expr)?;
|
|
||||||
self.visiting.remove(&expr);
|
|
||||||
self.emitted.insert(value);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_expr_binding(&mut self, f: &mut fmt::Formatter<'_>, expr: Expr) -> fmt::Result {
|
|
||||||
let value = self.body.expr_value(expr);
|
|
||||||
let ty = self.body.value_type(value);
|
|
||||||
|
|
||||||
write!(f, "%{} = ", value)?;
|
|
||||||
|
|
||||||
match self.body.expr_data(expr) {
|
|
||||||
ExprData::IConst { imm } => {
|
|
||||||
write!(f, "iconst {}", imm)?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::F32Const { bits } => {
|
|
||||||
write!(f, "f32const 0x{bits:08x}")?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::F64Const { bits } => {
|
|
||||||
write!(f, "f64const 0x{bits:016x}")?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::BConst { value } => {
|
|
||||||
write!(f, "bconst {}", value)?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Unary { op, arg } => {
|
|
||||||
write!(f, "{} %{}", op, arg)?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Binary { op, lhs, rhs } => {
|
|
||||||
write!(f, "{} %{}, %{}", op, lhs, rhs)?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Cast { op, arg } => {
|
|
||||||
let src_ty = self.body.value_type(*arg);
|
|
||||||
writeln!(f, "{} %{} : {} -> {}", op, arg, src_ty, ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Icmp { cc, lhs, rhs } => {
|
|
||||||
let operand_ty = self.body.value_type(*lhs);
|
|
||||||
writeln!(f, "icmp {} %{}, %{} : {}", cc, lhs, rhs, operand_ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Fcmp { cc, lhs, rhs } => {
|
|
||||||
let operand_ty = self.body.value_type(*lhs);
|
|
||||||
writeln!(f, "fcmp {} %{}, %{} : {}", cc, lhs, rhs, operand_ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Select {
|
|
||||||
cond,
|
|
||||||
if_true,
|
|
||||||
if_false,
|
|
||||||
} => {
|
|
||||||
write!(f, "select %{}, %{}, %{}", cond, if_true, if_false)?;
|
|
||||||
writeln!(f, " : {}", ty)
|
|
||||||
}
|
|
||||||
|
|
||||||
ExprData::Load { addr, size } => {
|
|
||||||
writeln!(f, "load %{} : {} -> {}", addr, size, ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_stmt(&mut self, f: &mut fmt::Formatter<'_>, stmt: Stmt, indent: usize) -> fmt::Result {
|
|
||||||
self.write_indent(f, indent)?;
|
|
||||||
|
|
||||||
match self.body.stmt_data(stmt) {
|
|
||||||
StmtData::Store { addr, value, size } => {
|
StmtData::Store { addr, value, size } => {
|
||||||
writeln!(f, "store %{}, %{} : {}", value, addr, size)
|
writeln!(f, "store %{value}, %{addr} : {size}")
|
||||||
}
|
}
|
||||||
|
|
||||||
StmtData::Call { callee, args } => {
|
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() {
|
if !args.is_empty() {
|
||||||
write!(f, "(")?;
|
write!(f, "(")?;
|
||||||
self.write_value_list(f, args.as_slice(&self.body.value_lists))?;
|
self.write_value_list(f, args)?;
|
||||||
writeln!(f, ")")?;
|
write!(f, ")")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
writeln!(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
StmtData::Jump { dst } => {
|
StmtData::Jump { dst } => {
|
||||||
write!(f, "br ")?;
|
write!(f, "br ")?;
|
||||||
self.write_block_call(f, *dst)?;
|
self.write_block_call(f, dst)?;
|
||||||
writeln!(f)
|
writeln!(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,10 +167,10 @@ impl<'a> Printer<'a> {
|
||||||
then_dst,
|
then_dst,
|
||||||
else_dst,
|
else_dst,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "cond_br %{}, ", cond)?;
|
write!(f, "cond_br %{cond}, ")?;
|
||||||
self.write_block_call(f, *then_dst)?;
|
self.write_block_call(f, then_dst)?;
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
self.write_block_call(f, *else_dst)?;
|
self.write_block_call(f, else_dst)?;
|
||||||
writeln!(f)
|
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)?;
|
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, "(")?;
|
write!(f, "(")?;
|
||||||
self.write_value_list(f, call.args.as_slice(&self.body.value_lists))?;
|
self.write_value_list(f, args)?;
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
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() {
|
for (i, value) in values.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
}
|
}
|
||||||
write!(f, "%{}", value)?;
|
write!(f, "%{value}")?;
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_indent(&mut self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
|
|
||||||
for _ in 0..indent {
|
|
||||||
write!(f, " ")?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue