Feat: add -S flag to emit LLVM IR
`-S` stops the pipeline after IR emission and writes the `.ll` file directly to the output path (default `<stem>.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 → <stem>.o -S → emit → <stem>.ll Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 <file>",
|
||||
"-o".bold(),
|
||||
@@ -68,6 +72,8 @@ pub struct Opts {
|
||||
pub files: Vec<String>,
|
||||
/// `-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 <file>`: write final output to this path.
|
||||
pub output: Option<String>,
|
||||
}
|
||||
@@ -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<String> = 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 }
|
||||
}
|
||||
|
||||
@@ -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) | `<stem>` executable | emit → opt → llc → cc |
|
||||
/// | `-c` | `<stem>.o` | emit → opt → llc |
|
||||
/// | `-S` | `<stem>.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<Parsed>,
|
||||
@@ -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"));
|
||||
|
||||
// ── 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])?;
|
||||
|
||||
Reference in New Issue
Block a user