The mcc-driver crate provides a callback-based API for observing and controlling the compilation pipeline. This is useful for building tools, tests, and custom workflows.
Overview
The run function executes the full compilation pipeline and calls trait methods at each stage:
Preprocessing
Parsing → after_parse
Lowering → after_lower
Code generation → after_codegen
Rendering → after_render_assembly
Assembling & linking → after_compile
Callbacks can inspect intermediate results, collect diagnostics, or stop the pipeline early.
Callbacks Trait
pub trait Callbacks {
type Output ;
fn after_parse <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_source_file : mcc :: SourceFile ,
_ast : Ast <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_lower <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_tacky : tacky :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_codegen <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_asm : asm :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_render_assembly (
& mut self ,
_db : & dyn mcc :: Db ,
_asm : Text ,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_compile (
& mut self ,
_db : & dyn mcc :: Db ,
_binary : PathBuf ,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
}
Type Parameter: Output
The Output associated type specifies what value is returned when the pipeline stops early (via ControlFlow::Break).
Return Type: ControlFlow
Each callback returns std::ops::ControlFlow<Self::Output>:
ControlFlow::Continue(()) - Continue to the next stage
ControlFlow::Break(value) - Stop the pipeline and return value
Callback Methods
after_parse
fn after_parse <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_source_file : mcc :: SourceFile ,
_ast : Ast <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output >
Called after parsing the source file.
The source file that was parsed (after preprocessing)
Diagnostics accumulated during parsing
after_lower
fn after_lower <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_tacky : tacky :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output >
Called after lowering the AST to three-address code (TAC).
The three-address code representation
Diagnostics accumulated during lowering
after_codegen
fn after_codegen <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_asm : asm :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output >
Called after generating assembly IR from TAC.
The assembly IR representation
Diagnostics accumulated during code generation
after_render_assembly
fn after_render_assembly (
& mut self ,
_db : & dyn mcc :: Db ,
_asm : Text ,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output >
Called after rendering assembly IR to text.
The rendered assembly text
Diagnostics accumulated during rendering
after_compile
fn after_compile (
& mut self ,
_db : & dyn mcc :: Db ,
_binary : PathBuf ,
) -> ControlFlow < Self :: Output >
Called after assembling and linking the final binary.
Path to the compiled executable
Config
pub struct Config {
pub db : mcc :: Database ,
pub target : Triple ,
pub cc : OsString ,
pub output : Option < PathBuf >,
pub input : SourceFile ,
}
Configuration for a compilation session.
Target triple (e.g., x86_64-unknown-linux-gnu)
C compiler command for preprocessing/linking (e.g., "cc" or "gcc")
Output path for the binary. If None, uses input path without extension.
Outcome
pub enum Outcome < Ret > {
Ok ,
Err ( anyhow :: Error ),
EarlyReturn ( Ret ),
}
The result of running the compiler.
Compilation failed with an error
Compilation stopped early via a callback (contains the Output value)
Methods
impl < Ret > Outcome < Ret > {
pub fn to_result ( self ) -> Result <(), anyhow :: Error >;
pub fn to_result_with (
self ,
f : impl FnOnce ( Ret ) -> Result <(), anyhow :: Error >,
) -> Result <(), anyhow :: Error >;
}
to_result : Convert to Result, treating early returns as errors.
to_result_with : Convert to Result, applying a custom handler for early returns.
run Function
pub fn run < C : Callbacks >( cb : & mut C , cfg : Config ) -> Outcome < C :: Output >
Executes the compilation pipeline with callbacks.
The callbacks implementation
Compilation configuration
The result of compilation
Example: Noop Callbacks
The simplest implementation does nothing:
use std :: ops :: ControlFlow ;
use mcc_driver :: { Callbacks , Config , Outcome };
use mcc :: { Ast , Text , diagnostics :: Diagnostics , codegen :: asm, lowering :: tacky};
struct Noop ;
impl Callbacks for Noop {
type Output = ();
fn after_parse <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_source_file : mcc :: SourceFile ,
_ast : Ast <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_lower <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_tacky : tacky :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_codegen <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_asm : asm :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
fn after_render_assembly (
& mut self ,
_db : & dyn mcc :: Db ,
_asm : Text ,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
ControlFlow :: Continue (())
}
}
Example: Stop After Parse
Return the AST and stop the pipeline:
use std :: ops :: ControlFlow ;
use mcc_driver :: { Callbacks , Config , Outcome , run};
use mcc :: { Ast , SourceFile , Text , Database , default_target};
struct StopAfterParse {
result : Option < Ast <' static >>,
}
impl Callbacks for StopAfterParse {
type Output = ();
fn after_parse <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_source_file : SourceFile ,
ast : Ast <' db >,
diags : Vec < & mcc :: diagnostics :: Diagnostics >,
) -> ControlFlow < Self :: Output > {
if ! diags . is_empty () {
eprintln! ( "Parse errors:" );
for diag in diags {
eprintln! ( " {}" , diag . message);
}
}
// Stop the pipeline after parsing
ControlFlow :: Break (())
}
}
let db = Database :: default ();
let file = SourceFile :: new ( & db , "test.c" . into (), "int main() {}" . into ());
let config = Config {
db : db . clone (),
target : default_target (),
cc : "cc" . into (),
output : None ,
input : file ,
};
let mut cb = StopAfterParse { result : None };
let outcome = run ( & mut cb , config );
match outcome {
Outcome :: EarlyReturn (()) => println! ( "Stopped after parse" ),
Outcome :: Ok => println! ( "Unexpectedly completed" ),
Outcome :: Err ( e ) => eprintln! ( "Error: {}" , e ),
}
Example: Collect All Diagnostics
use std :: ops :: ControlFlow ;
use mcc_driver :: Callbacks ;
use mcc :: diagnostics :: Diagnostics ;
struct DiagCollector {
all_diags : Vec < Diagnostics >,
}
impl Callbacks for DiagCollector {
type Output = Vec < Diagnostics >;
fn after_parse <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_source_file : mcc :: SourceFile ,
_ast : mcc :: Ast <' db >,
diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
self . all_diags . extend ( diags . into_iter () . cloned ());
ControlFlow :: Continue (())
}
fn after_lower <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_tacky : mcc :: lowering :: tacky :: Program <' db >,
diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
self . all_diags . extend ( diags . into_iter () . cloned ());
ControlFlow :: Continue (())
}
fn after_codegen <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
_asm : mcc :: codegen :: asm :: Program <' db >,
diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
self . all_diags . extend ( diags . into_iter () . cloned ());
ControlFlow :: Continue (())
}
fn after_render_assembly (
& mut self ,
_db : & dyn mcc :: Db ,
_asm : mcc :: Text ,
diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
self . all_diags . extend ( diags . into_iter () . cloned ());
ControlFlow :: Continue (())
}
}
Example: Write Assembly to File
use std :: {fs, ops :: ControlFlow , path :: PathBuf };
use mcc_driver :: Callbacks ;
use mcc :: Text ;
struct AsmWriter {
output_path : PathBuf ,
}
impl Callbacks for AsmWriter {
type Output = ();
fn after_render_assembly (
& mut self ,
_db : & dyn mcc :: Db ,
asm : Text ,
_diags : Vec < & mcc :: diagnostics :: Diagnostics >,
) -> ControlFlow < Self :: Output > {
fs :: write ( & self . output_path, asm . as_str ()) . unwrap ();
println! ( "Wrote assembly to {}" , self . output_path . display ());
// Stop after writing assembly
ControlFlow :: Break (())
}
}
Best Practices
Handle diagnostics at each stage
Check for errors in diagnostics and handle them appropriately: fn after_parse <' db >(
& mut self ,
db : & ' db dyn mcc :: Db ,
file : SourceFile ,
ast : Ast <' db >,
diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
if diags . iter () . any ( | d | d . severity == Severity :: Error ) {
// Stop on error
return ControlFlow :: Break ( ExitCode :: FAILURE );
}
ControlFlow :: Continue (())
}
Use EarlyReturn for custom workflows
If you only need certain stages (e.g., parse + typecheck for a linter), use ControlFlow::Break to skip later stages: fn after_lower <' db >(
& mut self ,
_db : & ' db dyn mcc :: Db ,
tacky : tacky :: Program <' db >,
_diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
// Analyze TAC, then stop
self . analyze ( tacky );
ControlFlow :: Break ( self . result ())
}
Implement only needed callbacks
The trait provides default implementations for all methods. Override only what you need: impl Callbacks for MyTool {
type Output = ();
// Only implement after_codegen
fn after_codegen <' db >(
& mut self ,
db : & ' db dyn mcc :: Db ,
asm : asm :: Program <' db >,
diags : Vec < & Diagnostics >,
) -> ControlFlow < Self :: Output > {
// Custom logic
}
}
API Overview Learn about the core compilation API
Diagnostics Handle errors collected in callbacks
Database Understand the compilation database
CLI See how the CLI uses callbacks internally