From fa19fab985087d295afd78f959e33a900e4babde Mon Sep 17 00:00:00 2001 From: Igor kehrazy Date: Tue, 7 Apr 2026 14:18:53 +0300 Subject: [PATCH] ir: remove expr, keep stmt only --- src/ir.rs | 25 ---- src/ir/body.rs | 102 +++++-------- src/ir/expr.rs | 82 +---------- src/ir/frontend.rs | 177 +++++++++++++---------- src/ir/stmt.rs | 129 ++++++++++++----- src/ir/ty.rs | 128 +++-------------- src/ir/verify.rs | 273 ++++++++++++++++------------------- src/ir/write.rs | 350 +++++++++++++-------------------------------- 8 files changed, 470 insertions(+), 796 deletions(-) diff --git a/src/ir.rs b/src/ir.rs index 65c9f9c..7f80bd7 100644 --- a/src/ir.rs +++ b/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. diff --git a/src/ir/body.rs b/src/ir/body.rs index b29e142..5b8434f 100644 --- a/src/ir/body.rs +++ b/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, - /// All expressions in the body. - pub exprs: PrimaryMap, - - /// All statements in the body. + /// All statements in the body, in creation order. pub stmts: PrimaryMap, /// 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 { + 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 { 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 { 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), - - /// Exactly two successors. Two { first: Option, second: Option, diff --git a/src/ir/expr.rs b/src/ir/expr.rs index 0e92406..522f0c1 100644 --- a/src/ir/expr.rs +++ b/src/ir/expr.rs @@ -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), diff --git a/src/ir/frontend.rs b/src/ir/frontend.rs index 42bb242..1b4016d 100644 --- a/src/ir/frontend.rs +++ b/src/ir/frontend.rs @@ -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 diff --git a/src/ir/stmt.rs b/src/ir/stmt.rs index 91747d2..2180b1c 100644 --- a/src/ir/stmt.rs +++ b/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 { + 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, } diff --git a/src/ir/ty.rs b/src/ir/ty.rs index 6912fd5..c2067c7 100644 --- a/src/ir/ty.rs +++ b/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 { - 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 { 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()) diff --git a/src/ir/verify.rs b/src/ir/verify.rs index 72ed384..b73de4b 100644 --- a/src/ir/verify.rs +++ b/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!( diff --git a/src/ir/write.rs b/src/ir/write.rs index 7df621f..a604faa 100644 --- a/src/ir/write.rs +++ b/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, - visiting: HashSet, } -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(()) }