From f836f279de7e11bcf21002ac239429ee74280bf6 Mon Sep 17 00:00:00 2001 From: Jooris Hadeler Date: Wed, 11 Mar 2026 20:28:24 +0100 Subject: [PATCH] Feat: add -S flag to emit LLVM IR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `-S` stops the pipeline after IR emission and writes the `.ll` file directly to the output path (default `.ll`). It implies `-c` (no main required). Combined with `-o`, the IR goes to the specified path. Pipeline summary: (none) → emit → opt → llc → cc → executable -c → emit → opt → llc → .o -S → emit → .ll Co-Authored-By: Claude Sonnet 4.6 --- fluxc/src/cli.rs | 14 +++++---- fluxc/src/codegen/mod.rs | 62 +++++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/fluxc/src/cli.rs b/fluxc/src/cli.rs index da9f8cf..15d127e 100644 --- a/fluxc/src/cli.rs +++ b/fluxc/src/cli.rs @@ -27,6 +27,10 @@ pub fn print_help() { " {} Compile to object file (no `main` required, no linking)", "-c".bold(), ); + println!( + " {} Emit LLVM IR and stop (implies `-c`)", + "-S".bold(), + ); println!( " {} {} Write output to ", "-o".bold(), @@ -68,6 +72,8 @@ pub struct Opts { pub files: Vec, /// `-c`: compile to object file without requiring a `main` entry point. pub no_main: bool, + /// `-S`: emit LLVM IR text and stop (implies `-c`). + pub emit_ir: bool, /// `-o `: write final output to this path. pub output: Option, } @@ -75,6 +81,7 @@ pub struct Opts { pub fn parse_args() -> Opts { let mut files = Vec::new(); let mut no_main = false; + let mut emit_ir = false; let mut output: Option = None; let mut args = std::env::args().skip(1).peekable(); @@ -89,6 +96,7 @@ pub fn parse_args() -> Opts { process::exit(0); } "-c" => no_main = true, + "-S" => { emit_ir = true; no_main = true; } "-o" => match args.next() { Some(path) => output = Some(path), None => fatal("option `-o` requires an argument"), @@ -104,9 +112,5 @@ pub fn parse_args() -> Opts { fatal("no input files — at least one source file is required"); } - Opts { - files, - no_main, - output, - } + Opts { files, no_main, emit_ir, output } } diff --git a/fluxc/src/codegen/mod.rs b/fluxc/src/codegen/mod.rs index 74576f8..e1a615d 100644 --- a/fluxc/src/codegen/mod.rs +++ b/fluxc/src/codegen/mod.rs @@ -10,15 +10,19 @@ use crate::cli::Opts; // ── Entry point ──────────────────────────────────────────────────────────────── -/// Compile a parsed + type-checked program to a native binary (or object file -/// when `opts.no_main` is set). +/// Compile a parsed + type-checked program. /// -/// Pipeline: -/// 1. Emit LLVM IR text → temp `.ll` file -/// 2. `opt -O2` → optimised `.ll` file (`FLUXC_OPT` overrides `opt`) -/// 3. `llc -filetype=obj` → `.o` file (`FLUXC_LLC` overrides `llc`) -/// 4. `cc` link → executable (`FLUXC_CC` overrides `cc`) -/// (step 4 is skipped in `-c` mode) +/// Mode is controlled by `opts`: +/// +/// | flags | output | pipeline | +/// |----------|---------------------|-----------------------| +/// | (none) | `` executable | emit → opt → llc → cc | +/// | `-c` | `.o` | emit → opt → llc | +/// | `-S` | `.ll` | emit only | +/// +/// Tool overrides via environment variables: +/// `FLUXC_OPT` (default `opt`), `FLUXC_LLC` (default `llc`), +/// `FLUXC_CC` (default `cc`). pub fn compile( input_path: &str, program: &ast::Program, @@ -32,51 +36,51 @@ pub fn compile( .unwrap_or_else(|| "out".to_string()); let final_output = opts.output.clone().unwrap_or_else(|| { - if opts.no_main { + if opts.emit_ir { + format!("{stem}.ll") + } else if opts.no_main { format!("{stem}.o") } else { stem.clone() } }); - // ── Temp paths ──────────────────────────────────────────────────────────── + // ── Step 1: emit LLVM IR ────────────────────────────────────────────────── + let ir = emit::emit_program(program, &result.sigma, &result.phi); + + // `-S`: write IR directly to final output and stop. + if opts.emit_ir { + return fs::write(&final_output, &ir) + .map_err(|e| format!("cannot write {final_output}: {e}")); + } + + // ── Temp paths (only used when compiling beyond IR) ─────────────────────── let tmp = env::temp_dir(); let raw_ll = tmp.join(format!("fluxc_{stem}.ll")); let opt_ll = tmp.join(format!("fluxc_{stem}.opt.ll")); - let obj = tmp.join(format!("fluxc_{stem}.o")); + let obj = tmp.join(format!("fluxc_{stem}.o")); - // ── Step 1: emit LLVM IR ────────────────────────────────────────────────── - let ir = emit::emit_program(program, &result.sigma, &result.phi); - fs::write(&raw_ll, &ir).map_err(|e| format!("cannot write IR to {}: {e}", raw_ll.display()))?; + fs::write(&raw_ll, &ir) + .map_err(|e| format!("cannot write IR to {}: {e}", raw_ll.display()))?; // ── Step 2: opt ─────────────────────────────────────────────────────────── let opt_bin = tool_path("FLUXC_OPT", "opt"); run( &opt_bin, - &[ - "-O2", - raw_ll.to_str().unwrap(), - "-S", - "-o", - opt_ll.to_str().unwrap(), - ], + &["-O2", raw_ll.to_str().unwrap(), "-S", "-o", opt_ll.to_str().unwrap()], )?; // ── Step 3: llc ─────────────────────────────────────────────────────────── let llc_bin = tool_path("FLUXC_LLC", "llc"); run( &llc_bin, - &[ - opt_ll.to_str().unwrap(), - "-filetype=obj", - "-o", - obj.to_str().unwrap(), - ], + &[opt_ll.to_str().unwrap(), "-filetype=obj", "-o", obj.to_str().unwrap()], )?; - // ── Step 4: link (or copy object as final output) ───────────────────────── + // ── Step 4: link (skipped in `-c` mode) ─────────────────────────────────── if opts.no_main { - fs::copy(&obj, &final_output).map_err(|e| format!("cannot write {final_output}: {e}"))?; + fs::copy(&obj, &final_output) + .map_err(|e| format!("cannot write {final_output}: {e}"))?; } else { let cc_bin = tool_path("FLUXC_CC", "cc"); run(&cc_bin, &[obj.to_str().unwrap(), "-o", &final_output])?;