Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/asm.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//! This module provides the assembly code generation backend,
//! translating the compiler's IR into target-specific assembly.

pub mod aarch64;
pub mod common;
pub mod error;
Expand Down
21 changes: 21 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
//! Common utilities and shared abstractions used across the compiler,
//! including target platform detection and a generic code generator trait.

pub mod graph;

use std::io::Write;

/// Represents the compilation target operating system.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Target {
/// Compile for Linux.
Linux,
/// Compile for macOS.
Macos,
}

impl Target {
/// Detects the current host platform at compile time and returns the
/// corresponding `Target` variant.
pub fn host() -> Self {
if cfg!(target_os = "macos") {
Target::Macos
Expand All @@ -17,6 +25,11 @@ impl Target {
}
}

/// Applies platform-specific symbol name mangling.
///
/// On macOS, the Mach-O ABI requires a leading underscore prefix for C
/// symbols, so this method prepends `_` to the given name. On Linux the
/// name is returned unchanged.
pub fn mangle_symbol(&self, name: &str) -> String {
match self {
Target::Macos => format!("_{name}"),
Expand All @@ -25,8 +38,16 @@ impl Target {
}
}

/// A generic trait for code generators.
///
/// Implementors first call [`generate`](Generator::generate) to perform the
/// code-generation work and then call [`output`](Generator::output) to write
/// the generated result to any [`Write`] sink.
pub trait Generator {
type Error;
/// Performs the code-generation step, populating the generator's internal
/// state with the result.
fn generate(&mut self) -> Result<(), Self::Error>;
/// Writes the previously generated output to `w`.
fn output<W: Write>(&self, w: &mut W) -> Result<(), Self::Error>;
}
81 changes: 81 additions & 0 deletions src/common/graph.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
//! Graph data structures and dataflow analysis utilities for control-flow graphs.
//!
//! This module provides:
//! - [`CfgNode`]: a trait for nodes in a control-flow graph.
//! - [`Graph`]: a directed graph with successor and predecessor adjacency lists.
//! - [`Lattice`]: a trait defining a lattice for dataflow analysis.
//! - [`BackwardLiveness`]: backward liveness analysis using a worklist algorithm.

use std::collections::{HashMap, HashSet, VecDeque};

/// A node in a control-flow graph (CFG).
///
/// Implementors describe how each node connects to its successors and
/// optionally expose a label so that branch targets can be resolved by name.
pub trait CfgNode {
/// Returns an optional label for this node.
///
/// When present, the label is used to build a name-to-index map so that
/// other nodes can refer to this node as a branch target by name.
fn label(&self) -> Option<String>;

/// Computes the successor node indices for this node.
///
/// - `idx`: the index of this node in the owning node slice.
/// - `num_nodes`: total number of nodes in the graph.
/// - `label_map`: a map from label strings to node indices, used to
/// resolve named branch targets.
fn successors(
&self,
idx: usize,
Expand All @@ -10,12 +33,20 @@ pub trait CfgNode {
) -> Vec<usize>;
}

/// A directed graph represented as both successor and predecessor adjacency lists.
///
/// Both lists are indexed by node index and are derived from the same edge set,
/// so they are always consistent with each other.
pub struct Graph {
succs: Vec<Vec<usize>>,
preds: Vec<Vec<usize>>,
}

impl Graph {
/// Constructs a [`Graph`] from a pre-built successor adjacency list.
///
/// The predecessor adjacency list is derived automatically by inverting
/// the edges of `succs`.
pub fn new(succs: Vec<Vec<usize>>) -> Self {
let n = succs.len();
let mut preds = vec![Vec::new(); n];
Expand All @@ -27,6 +58,11 @@ impl Graph {
Self { succs, preds }
}

/// Builds a [`Graph`] from a slice of [`CfgNode`] implementors.
///
/// This method first collects all node labels into a name-to-index map,
/// then calls [`CfgNode::successors`] on each node to compute the full
/// successor adjacency list, and finally delegates to [`Graph::new`].
pub fn from_nodes<N: CfgNode>(nodes: &[N]) -> Self {
let n = nodes.len();
let label_map: HashMap<String, usize> = nodes
Expand All @@ -42,33 +78,60 @@ impl Graph {
Self::new(succs)
}

/// Returns the total number of nodes in the graph.
pub fn num_nodes(&self) -> usize {
self.succs.len()
}

/// Returns the successor indices of `node`.
pub fn successors(&self, node: usize) -> &[usize] {
&self.succs[node]
}

/// Returns the predecessor indices of `node`.
pub fn predecessors(&self, node: usize) -> &[usize] {
&self.preds[node]
}

/// Returns the full successor adjacency list.
pub fn succs_vec(&self) -> &[Vec<usize>] {
&self.succs
}

/// Returns the full predecessor adjacency list.
pub fn preds_vec(&self) -> &[Vec<usize>] {
&self.preds
}
}

/// A lattice used as the value domain for dataflow analysis.
///
/// Each implementor defines:
/// - a bottom element (the initial / most-conservative value),
/// - a join (least upper bound) operation for merging values at join points,
/// - a transfer function that computes the inflow from the outflow using
/// gen/kill sets.
pub trait Lattice: Clone + PartialEq {
/// Returns the bottom element of the lattice (the initial dataflow value).
fn bottom() -> Self;

/// Computes the least upper bound of `self` and `other` in place (join / merge).
fn join(&mut self, other: &Self);

/// Applies the transfer function: `gen ∪ (out ∖ kill)`.
///
/// Returns the lattice value that flows into a node given
/// - `gen`: values generated (defined / used) by the node,
/// - `kill`: values killed (overwritten) by the node,
/// - `out`: values live at the exit of the node.
fn transfer(gen: &Self, kill: &Self, out: &Self) -> Self;
}

/// Simple single-bit reachability lattice.
///
/// `false` is the bottom element. `join` is logical OR. The transfer function
/// propagates liveness if the value is generated or passes through (live-out
/// and not killed).
impl Lattice for bool {
fn bottom() -> Self {
false
Expand All @@ -83,9 +146,15 @@ impl Lattice for bool {
}
}

/// A set of virtual-register indices, used as the liveness lattice element
/// when tracking the live set of virtual registers.
#[derive(Clone, PartialEq, Eq)]
pub struct VregSet(pub HashSet<usize>);

/// Set-of-virtual-registers liveness lattice.
///
/// The bottom element is the empty set. `join` is set union. The transfer
/// function is `gen ∪ (out ∖ kill)`.
impl Lattice for VregSet {
fn bottom() -> Self {
VregSet(HashSet::new())
Expand All @@ -106,12 +175,24 @@ impl Lattice for VregSet {
}
}

/// Results of backward liveness (dataflow) analysis over a [`Graph`].
///
/// - `live_in[i]` holds the lattice value live at the **entry** of node `i`.
/// - `live_out[i]` holds the lattice value live at the **exit** of node `i`.
pub struct BackwardLiveness<L> {
/// Lattice values live at the entry of each node.
pub live_in: Vec<L>,
/// Lattice values live at the exit of each node.
pub live_out: Vec<L>,
}

impl<L: Lattice> BackwardLiveness<L> {
/// Performs backward liveness analysis using a worklist algorithm.
///
/// The worklist is initially seeded with all nodes in reverse order so
/// that nodes near the end of the CFG are processed first. Whenever
/// `live_in[i]` changes, all predecessors of `i` are added back to the
/// worklist to propagate the change backward until a fixed point is reached.
pub fn compute(gen: &[L], kill: &[L], graph: &Graph) -> Self {
let n = graph.num_nodes();

Expand Down
5 changes: 5 additions & 0 deletions src/ir.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! This module defines the compiler's intermediate representation (IR).
//!
//! The IR is the central data structure that bridges the front-end (parsing
//! and type-checking) and the back-end (optimization and code generation).

pub mod error;
pub mod function;
mod gen;
Expand Down
15 changes: 15 additions & 0 deletions src/opt.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! This module provides optimization passes that transform IR functions.
//!
//! Passes implement the [`FunctionPass`] trait and are composed into a
//! [`FunctionPassManager`] pipeline that runs them in registration order.

use crate::ir::function::Function;

pub mod cfg;
Expand All @@ -6,33 +11,43 @@ mod mem2reg;

pub use mem2reg::Mem2RegPass;

/// Interface that every function-level optimization pass must implement.
pub trait FunctionPass {
fn run(&self, func: &mut Function);
}

/// Manages a sequential pipeline of [`FunctionPass`] instances.
///
/// Passes are stored as boxed trait objects so that heterogeneous pass types
/// can be combined in a single pipeline.
#[derive(Default)]
pub struct FunctionPassManager {
passes: Vec<Box<dyn FunctionPass>>,
}

impl FunctionPassManager {
/// Creates an empty pass manager with no registered passes.
pub fn new() -> Self {
Self::default()
}

/// Creates a pass manager pre-loaded with the default optimization
/// pipeline (currently: [`Mem2RegPass`]).
pub fn with_default_pipeline() -> Self {
let mut pm = Self::new();
pm.add_pass(Mem2RegPass);
pm
}

/// Appends `pass` to the end of the optimization pipeline.
pub fn add_pass<P>(&mut self, pass: P)
where
P: FunctionPass + 'static,
{
self.passes.push(Box::new(pass));
}

/// Runs all registered passes sequentially on `func`.
pub fn run(&self, func: &mut Function) {
for pass in &self.passes {
pass.run(func);
Expand Down
File renamed without changes.
Loading