Compare commits
2 Commits
9aa6b9694b
...
f836f279de
| Author | SHA1 | Date | |
|---|---|---|---|
| f836f279de | |||
| c2fc83b74b |
@@ -42,14 +42,25 @@ pub enum Ty {
|
||||
Char,
|
||||
Unit,
|
||||
// Pointer types
|
||||
Ptr { mutable: bool, pointee: Box<Ty> },
|
||||
OpaquePtr { mutable: bool },
|
||||
Ptr {
|
||||
mutable: bool,
|
||||
pointee: Box<Ty>,
|
||||
},
|
||||
OpaquePtr {
|
||||
mutable: bool,
|
||||
},
|
||||
// Array type
|
||||
Array { elem: Box<Ty>, size: u64 },
|
||||
Array {
|
||||
elem: Box<Ty>,
|
||||
size: u64,
|
||||
},
|
||||
// User-defined struct
|
||||
Struct(String),
|
||||
// Internal function signature (not user-facing)
|
||||
FnSig { params: Vec<Ty>, ret: Box<Ty> },
|
||||
FnSig {
|
||||
params: Vec<Ty>,
|
||||
ret: Box<Ty>,
|
||||
},
|
||||
/// Unresolved integer type from a literal or an unannotated let-binding.
|
||||
/// Compatible with every concrete integer type; defaults to `i32` in
|
||||
/// error messages when no concrete type can be inferred.
|
||||
|
||||
@@ -10,6 +10,17 @@ use crate::diagnostics::{Diagnostic, Label};
|
||||
use crate::token::Span;
|
||||
use env::{FieldEntry, FuncTable, ParamEntry, StructTable};
|
||||
|
||||
// ── Check result ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// The result of running the semantic checker. Carries both the diagnostics and
|
||||
/// the resolved symbol tables so that downstream passes (e.g. codegen) can
|
||||
/// reuse them without re-running the checker.
|
||||
pub struct CheckResult {
|
||||
pub errors: Vec<Diagnostic>,
|
||||
pub sigma: StructTable,
|
||||
pub phi: FuncTable,
|
||||
}
|
||||
|
||||
// ── Checker ────────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct Checker {
|
||||
@@ -166,7 +177,7 @@ fn value_struct_name(ty: &Ty) -> Option<&str> {
|
||||
|
||||
// ── Entry point ────────────────────────────────────────────────────────────────
|
||||
|
||||
pub fn check(program: &ast::Program<Parsed>, no_main: bool) -> Vec<Diagnostic> {
|
||||
pub fn check(program: &ast::Program<Parsed>, no_main: bool) -> CheckResult {
|
||||
let mut checker = Checker::new();
|
||||
|
||||
// ── Pass 1: collect struct names + function signatures ────────────────────
|
||||
@@ -288,5 +299,9 @@ pub fn check(program: &ast::Program<Parsed>, no_main: bool) -> Vec<Diagnostic> {
|
||||
}
|
||||
}
|
||||
|
||||
checker.errors
|
||||
CheckResult {
|
||||
errors: checker.errors,
|
||||
sigma: checker.sigma,
|
||||
phi: checker.phi,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ mod tests {
|
||||
parser.errors
|
||||
);
|
||||
checker::check(&program, false)
|
||||
.errors
|
||||
.into_iter()
|
||||
.map(|d| d.message)
|
||||
.collect()
|
||||
|
||||
@@ -24,9 +24,18 @@ pub fn print_help() {
|
||||
"--version".bold(),
|
||||
);
|
||||
println!(
|
||||
" {} Compile without requiring a `main` entry point",
|
||||
" {} 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(),
|
||||
"<file>".bold(),
|
||||
);
|
||||
println!();
|
||||
println!("{}", "ARGS:".bold().yellow());
|
||||
println!(
|
||||
@@ -61,15 +70,22 @@ pub fn io_error(path: &str, err: std::io::Error) -> ! {
|
||||
|
||||
pub struct Opts {
|
||||
pub files: Vec<String>,
|
||||
/// `-c`: compile without requiring a `main` entry point.
|
||||
/// `-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>,
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
for arg in std::env::args().skip(1) {
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"-h" | "--help" => {
|
||||
print_help();
|
||||
@@ -80,6 +96,11 @@ 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"),
|
||||
},
|
||||
flag if flag.starts_with('-') => {
|
||||
fatal(&format!("unknown option `{flag}`"));
|
||||
}
|
||||
@@ -91,5 +112,5 @@ pub fn parse_args() -> Opts {
|
||||
fatal("no input files — at least one source file is required");
|
||||
}
|
||||
|
||||
Opts { files, no_main }
|
||||
Opts { files, no_main, emit_ir, output }
|
||||
}
|
||||
|
||||
1418
fluxc/src/codegen/emit.rs
Normal file
1418
fluxc/src/codegen/emit.rs
Normal file
File diff suppressed because it is too large
Load Diff
120
fluxc/src/codegen/mod.rs
Normal file
120
fluxc/src/codegen/mod.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
pub mod emit;
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{env, fs};
|
||||
|
||||
use crate::ast::{self, Parsed};
|
||||
use crate::checker::CheckResult;
|
||||
use crate::cli::Opts;
|
||||
|
||||
// ── Entry point ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Compile a parsed + type-checked program.
|
||||
///
|
||||
/// 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>,
|
||||
result: CheckResult,
|
||||
opts: &Opts,
|
||||
) -> Result<(), String> {
|
||||
// ── Derive output path ────────────────────────────────────────────────────
|
||||
let stem = Path::new(input_path)
|
||||
.file_stem()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "out".to_string());
|
||||
|
||||
let final_output = opts.output.clone().unwrap_or_else(|| {
|
||||
if opts.emit_ir {
|
||||
format!("{stem}.ll")
|
||||
} else if opts.no_main {
|
||||
format!("{stem}.o")
|
||||
} else {
|
||||
stem.clone()
|
||||
}
|
||||
});
|
||||
|
||||
// ── 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"));
|
||||
|
||||
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()],
|
||||
)?;
|
||||
|
||||
// ── 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()],
|
||||
)?;
|
||||
|
||||
// ── 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}"))?;
|
||||
} else {
|
||||
let cc_bin = tool_path("FLUXC_CC", "cc");
|
||||
run(&cc_bin, &[obj.to_str().unwrap(), "-o", &final_output])?;
|
||||
}
|
||||
|
||||
// ── Clean up temp files ───────────────────────────────────────────────────
|
||||
let _ = fs::remove_file(&raw_ll);
|
||||
let _ = fs::remove_file(&opt_ll);
|
||||
let _ = fs::remove_file(&obj);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn tool_path(env_var: &str, default: &str) -> String {
|
||||
env::var(env_var).unwrap_or_else(|_| default.to_string())
|
||||
}
|
||||
|
||||
fn run(bin: &str, args: &[&str]) -> Result<(), String> {
|
||||
let output = Command::new(bin)
|
||||
.args(args)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.map_err(|e| format!("failed to run `{bin}`: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!(
|
||||
"`{bin}` exited with {}\n{}",
|
||||
output.status,
|
||||
stderr.trim_end()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use crate::parser::Parser;
|
||||
pub mod ast;
|
||||
pub mod checker;
|
||||
pub mod cli;
|
||||
pub mod codegen;
|
||||
pub mod diagnostics;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
@@ -14,6 +15,10 @@ fn main() {
|
||||
let opts = cli::parse_args();
|
||||
let mut had_errors = false;
|
||||
|
||||
// Collect (path, source, program, check_result) for all input files.
|
||||
// We gate codegen on all files being error-free.
|
||||
let mut compiled = Vec::new();
|
||||
|
||||
for path in &opts.files {
|
||||
let content = fs::read_to_string(path).unwrap_or_else(|e| cli::io_error(path, e));
|
||||
|
||||
@@ -26,11 +31,24 @@ fn main() {
|
||||
}
|
||||
|
||||
if parser.errors.is_empty() {
|
||||
let sema_errors = checker::check(&program, opts.no_main);
|
||||
for diag in &sema_errors {
|
||||
let result = checker::check(&program, opts.no_main);
|
||||
for diag in &result.errors {
|
||||
eprint!("{}", diag.render(&content, path));
|
||||
had_errors = true;
|
||||
}
|
||||
compiled.push((path.clone(), program, result));
|
||||
}
|
||||
}
|
||||
|
||||
if had_errors {
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// All files are clean — run codegen.
|
||||
for (path, program, result) in compiled {
|
||||
if let Err(e) = codegen::compile(&path, &program, result, &opts) {
|
||||
eprintln!("{}: {e}", "error".to_string());
|
||||
had_errors = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user