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)",
|
" {} Compile to object file (no `main` required, no linking)",
|
||||||
"-c".bold(),
|
"-c".bold(),
|
||||||
);
|
);
|
||||||
|
println!(
|
||||||
|
" {} Emit LLVM IR and stop (implies `-c`)",
|
||||||
|
"-S".bold(),
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
" {} {} Write output to <file>",
|
" {} {} Write output to <file>",
|
||||||
"-o".bold(),
|
"-o".bold(),
|
||||||
@@ -68,6 +72,8 @@ pub struct Opts {
|
|||||||
pub files: Vec<String>,
|
pub files: Vec<String>,
|
||||||
/// `-c`: compile to object file without requiring a `main` entry point.
|
/// `-c`: compile to object file without requiring a `main` entry point.
|
||||||
pub no_main: bool,
|
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.
|
/// `-o <file>`: write final output to this path.
|
||||||
pub output: Option<String>,
|
pub output: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -75,6 +81,7 @@ pub struct Opts {
|
|||||||
pub fn parse_args() -> Opts {
|
pub fn parse_args() -> Opts {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
let mut no_main = false;
|
let mut no_main = false;
|
||||||
|
let mut emit_ir = false;
|
||||||
let mut output: Option<String> = None;
|
let mut output: Option<String> = None;
|
||||||
let mut args = std::env::args().skip(1).peekable();
|
let mut args = std::env::args().skip(1).peekable();
|
||||||
|
|
||||||
@@ -89,6 +96,7 @@ pub fn parse_args() -> Opts {
|
|||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
"-c" => no_main = true,
|
"-c" => no_main = true,
|
||||||
|
"-S" => { emit_ir = true; no_main = true; }
|
||||||
"-o" => match args.next() {
|
"-o" => match args.next() {
|
||||||
Some(path) => output = Some(path),
|
Some(path) => output = Some(path),
|
||||||
None => fatal("option `-o` requires an argument"),
|
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");
|
fatal("no input files — at least one source file is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
Opts {
|
Opts { files, no_main, emit_ir, output }
|
||||||
files,
|
|
||||||
no_main,
|
|
||||||
output,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,19 @@ use crate::cli::Opts;
|
|||||||
|
|
||||||
// ── Entry point ────────────────────────────────────────────────────────────────
|
// ── Entry point ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Compile a parsed + type-checked program to a native binary (or object file
|
/// Compile a parsed + type-checked program.
|
||||||
/// when `opts.no_main` is set).
|
|
||||||
///
|
///
|
||||||
/// Pipeline:
|
/// Mode is controlled by `opts`:
|
||||||
/// 1. Emit LLVM IR text → temp `.ll` file
|
///
|
||||||
/// 2. `opt -O2` → optimised `.ll` file (`FLUXC_OPT` overrides `opt`)
|
/// | flags | output | pipeline |
|
||||||
/// 3. `llc -filetype=obj` → `.o` file (`FLUXC_LLC` overrides `llc`)
|
/// |----------|---------------------|-----------------------|
|
||||||
/// 4. `cc` link → executable (`FLUXC_CC` overrides `cc`)
|
/// | (none) | `<stem>` executable | emit → opt → llc → cc |
|
||||||
/// (step 4 is skipped in `-c` mode)
|
/// | `-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(
|
pub fn compile(
|
||||||
input_path: &str,
|
input_path: &str,
|
||||||
program: &ast::Program<Parsed>,
|
program: &ast::Program<Parsed>,
|
||||||
@@ -32,51 +36,51 @@ pub fn compile(
|
|||||||
.unwrap_or_else(|| "out".to_string());
|
.unwrap_or_else(|| "out".to_string());
|
||||||
|
|
||||||
let final_output = opts.output.clone().unwrap_or_else(|| {
|
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")
|
format!("{stem}.o")
|
||||||
} else {
|
} else {
|
||||||
stem.clone()
|
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 tmp = env::temp_dir();
|
||||||
let raw_ll = tmp.join(format!("fluxc_{stem}.ll"));
|
let raw_ll = tmp.join(format!("fluxc_{stem}.ll"));
|
||||||
let opt_ll = tmp.join(format!("fluxc_{stem}.opt.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 ──────────────────────────────────────────────────
|
fs::write(&raw_ll, &ir)
|
||||||
let ir = emit::emit_program(program, &result.sigma, &result.phi);
|
.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 ───────────────────────────────────────────────────────────
|
// ── Step 2: opt ───────────────────────────────────────────────────────────
|
||||||
let opt_bin = tool_path("FLUXC_OPT", "opt");
|
let opt_bin = tool_path("FLUXC_OPT", "opt");
|
||||||
run(
|
run(
|
||||||
&opt_bin,
|
&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 ───────────────────────────────────────────────────────────
|
// ── Step 3: llc ───────────────────────────────────────────────────────────
|
||||||
let llc_bin = tool_path("FLUXC_LLC", "llc");
|
let llc_bin = tool_path("FLUXC_LLC", "llc");
|
||||||
run(
|
run(
|
||||||
&llc_bin,
|
&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 {
|
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 {
|
} else {
|
||||||
let cc_bin = tool_path("FLUXC_CC", "cc");
|
let cc_bin = tool_path("FLUXC_CC", "cc");
|
||||||
run(&cc_bin, &[obj.to_str().unwrap(), "-o", &final_output])?;
|
run(&cc_bin, &[obj.to_str().unwrap(), "-o", &final_output])?;
|
||||||
|
|||||||
Reference in New Issue
Block a user