Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions src/ir/printer.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
//! Textual printer for teac's IR (the `--emit ir` dump).
//!
//! [`IrPrinter`] is the single source of truth for what a teac IR file
//! looks like: target header, struct types, globals, then every function
//! as a definition or declaration. All presentation policy lives here
//! rather than on the model types: the printer owns the leading `\t` that
//! indents instructions inside a function body (statement `Display`
//! impls in [`super::stmt`] deliberately emit bare text), and it consumes
//! the fallible [`GepStmt::render`] directly so that a GEP with an
//! ill-typed base surfaces as a proper [`Error`] through the
//! `Result`-returning emit path instead of as an opaque `fmt::Error`
//! buried inside a `writeln!`.
//!
//! [`GepStmt::render`]: super::stmt::GepStmt::render

use super::error::Error;
use super::function::FunctionBody;
use super::module::{Module, Registry};
use super::stmt::{Stmt, StmtInner};
use super::types::{Dtype, FunctionType, StructType};
use super::value::GlobalDef;
use std::io::Write;

/// LLVM-style target triple baked into every IR dump. Kept alongside
/// the printer rather than on `IrGenerator` because the printer is the
/// component that actually writes it, and `Optimizer::output` now needs
/// the same constant — making it a free constant in `ir::printer`
/// component that writes it; both `IrGenerator::output` and
/// `Optimizer::output` consume it, so a free constant in `ir::printer`
/// avoids introducing an `ir::gen`-to-`opt` dependency.
pub const TARGET_TRIPLE: &str = "aarch64-unknown-linux-gnu";

Expand Down Expand Up @@ -140,14 +156,36 @@ impl<W: Write> IrPrinter<W> {
for block in &body.blocks {
writeln!(self.writer, "{}:", block.label)?;
for stmt in &block.stmts {
writeln!(self.writer, "{stmt}")?;
self.emit_stmt(stmt)?;
}
}
writeln!(self.writer, "}}")?;
writeln!(self.writer)?;
Ok(())
}

/// Emit a single instruction line inside a function body, owning the
/// leading `\t` indent that [`Stmt`]'s `Display` deliberately leaves
/// to the printer. Label statements never reach this path: the flat
/// IR is split into `BasicBlock`s that each carry their label, and
/// [`emit_function_def`] prints a label unindented above its block,
/// so every statement here is an indented instruction.
///
/// GEPs take a special route because their rendering is fallible —
/// [`GepStmt::render`] resolves the base operand's layout and reports
/// ill-typed bases as an [`Error`]; every other instruction renders
/// through its infallible `Display`.
///
/// [`emit_function_def`]: Self::emit_function_def
/// [`GepStmt::render`]: super::stmt::GepStmt::render
fn emit_stmt(&mut self, stmt: &Stmt) -> Result<(), Error> {
match &stmt.inner {
StmtInner::Gep(gep) => writeln!(self.writer, "\t{}", gep.render()?)?,
_ => writeln!(self.writer, "\t{stmt}")?,
}
Ok(())
}

pub fn emit_function_decl(
&mut self,
link_name: &str,
Expand Down
102 changes: 76 additions & 26 deletions src/ir/stmt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
//! IR statement (instruction) definitions for teac's LLVM-like IR.
//!
//! Each instruction is a small struct (`GepStmt`, `CallStmt`, …) wrapped
//! into a [`Stmt`] by the [`StmtInner`] enum. Instructions implement
//! [`Display`] as *bare* instruction text — no leading indentation, no
//! trailing newline — because laying out a function body (indent column,
//! label placement) is the printer's business, not the model's: that is
//! owned by [`super::printer::IrPrinter`]. The one instruction that
//! cannot be rendered infallibly is the GEP — its text depends on the
//! base operand's layout and an ill-typed base has no rendering — so it
//! carries no `Display` impl at all; instead the fallible logic lives in
//! the explicit [`GepStmt::render`] helper, which the printer consumes
//! through its `Result`-returning emit path.

use crate::ast;

use super::error::Error;
use super::function::BlockLabel;
use super::types::Dtype;
use super::value::Operand;
Expand Down Expand Up @@ -213,21 +228,36 @@ impl Stmt {
}
}

/// Emits the bare instruction text — no leading indentation, no
/// trailing newline.
///
/// Indentation inside a function body is the printer's concern
/// ([`super::printer::IrPrinter`]), so the model types carry no
/// presentation policy and other consumers (diagnostics, debugging) get
/// unadorned text.
impl Display for Stmt {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.inner {
StmtInner::Alloca(s) => write!(f, "\t{s}"),
StmtInner::BiOp(s) => write!(f, "\t{s}"),
StmtInner::CJump(s) => write!(f, "\t{s}"),
StmtInner::Call(s) => write!(f, "\t{s}"),
StmtInner::Cmp(s) => write!(f, "\t{s}"),
StmtInner::Gep(s) => write!(f, "\t{s}"),
StmtInner::Alloca(s) => write!(f, "{s}"),
StmtInner::BiOp(s) => write!(f, "{s}"),
StmtInner::CJump(s) => write!(f, "{s}"),
StmtInner::Call(s) => write!(f, "{s}"),
StmtInner::Cmp(s) => write!(f, "{s}"),
// `GepStmt` has no `Display`: its rendering is fallible
// (`GepStmt::render`), and the printer consumes that
// fallibility through its `Result`-returning emit path.
// This arm keeps direct `Display` calls working: text for
// well-typed GEPs, `fmt::Error` for ill-typed ones.
StmtInner::Gep(s) => match s.render() {
Ok(text) => write!(f, "{text}"),
Err(_) => Err(fmt::Error),
},
StmtInner::Label(s) => write!(f, "{s}"),
StmtInner::Load(s) => write!(f, "\t{s}"),
StmtInner::Phi(s) => write!(f, "\t{s}"),
StmtInner::Return(s) => write!(f, "\t{s}"),
StmtInner::Store(s) => write!(f, "\t{s}"),
StmtInner::Jump(s) => write!(f, "\t{s}"),
StmtInner::Load(s) => write!(f, "{s}"),
StmtInner::Phi(s) => write!(f, "{s}"),
StmtInner::Return(s) => write!(f, "{s}"),
StmtInner::Store(s) => write!(f, "{s}"),
StmtInner::Jump(s) => write!(f, "{s}"),
}
}
}
Expand Down Expand Up @@ -420,8 +450,24 @@ impl Display for LabelStmt {
}
}

impl Display for GepStmt {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
impl GepStmt {
/// Renders this GEP as teac IR text, choosing the `getelementptr`
/// form from the base operand's layout:
///
/// - pointer to a fixed-size array or struct → two-index form
/// (`i32 0, i32 <index>`), i.e. aggregate/field addressing;
/// - pointer to an unsized array (a decayed `&[T]` argument) →
/// element-typed single-index form;
/// - pointer to a scalar → single-index form;
/// - first-class array operand (e.g. a global array) → two-index form.
///
/// The lookup is fallible: a base that is neither a pointer nor an
/// array is ill-formed IR with no valid rendering. Keeping that
/// fallibility in an explicit helper — rather than inside a `Display`
/// impl where it could only surface as an opaque `fmt::Error` — lets
/// the printer report it through its own `Result`-returning emit path.
/// This is why [`GepStmt`] deliberately has no `Display` impl.
pub fn render(&self) -> Result<String, Error> {
let Self {
new_ptr,
base_ptr,
Expand All @@ -432,27 +478,31 @@ impl Display for GepStmt {
Dtype::Array {
length: Some(_), ..
}
| Dtype::Struct { .. } => write!(
f,
| Dtype::Struct { .. } => Ok(format!(
"{new_ptr} = getelementptr {pointee}, ptr {base_ptr}, i32 0, i32 {index}",
),
)),
Dtype::Array {
element,
length: None,
} => write!(
f,
} => Ok(format!(
"{new_ptr} = getelementptr {element}, ptr {base_ptr}, i32 {index}",
),
_ => write!(
f,
)),
_ => Ok(format!(
"{new_ptr} = getelementptr {pointee}, ptr {base_ptr}, i32 {index}",
),
)),
},
dtype @ Dtype::Array { .. } => write!(
f,
dtype @ Dtype::Array { .. } => Ok(format!(
"{new_ptr} = getelementptr {dtype}, ptr {base_ptr}, i32 0, i32 {index}",
),
_ => Err(fmt::Error),
)),
// Ill-formed IR that the front end never produces; surfaced as
// an I/O error so the failure shares the printer's
// `io::Write`-based error channel — `writeln!` failures reach
// the caller as `std::io::Error` wrapped in `Error::Io`,
// exactly like this one.
other => Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("getelementptr on non-indexable base of type '{other}'"),
))),
}
}
}
Expand Down