Compare commits
2 Commits
9aa6b9694b
...
f836f279de
| Author | SHA1 | Date | |
|---|---|---|---|
| f836f279de | |||
| c2fc83b74b |
@@ -42,14 +42,25 @@ pub enum Ty {
|
|||||||
Char,
|
Char,
|
||||||
Unit,
|
Unit,
|
||||||
// Pointer types
|
// Pointer types
|
||||||
Ptr { mutable: bool, pointee: Box<Ty> },
|
Ptr {
|
||||||
OpaquePtr { mutable: bool },
|
mutable: bool,
|
||||||
|
pointee: Box<Ty>,
|
||||||
|
},
|
||||||
|
OpaquePtr {
|
||||||
|
mutable: bool,
|
||||||
|
},
|
||||||
// Array type
|
// Array type
|
||||||
Array { elem: Box<Ty>, size: u64 },
|
Array {
|
||||||
|
elem: Box<Ty>,
|
||||||
|
size: u64,
|
||||||
|
},
|
||||||
// User-defined struct
|
// User-defined struct
|
||||||
Struct(String),
|
Struct(String),
|
||||||
// Internal function signature (not user-facing)
|
// 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.
|
/// Unresolved integer type from a literal or an unannotated let-binding.
|
||||||
/// Compatible with every concrete integer type; defaults to `i32` in
|
/// Compatible with every concrete integer type; defaults to `i32` in
|
||||||
/// error messages when no concrete type can be inferred.
|
/// error messages when no concrete type can be inferred.
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ use crate::diagnostics::{Diagnostic, Label};
|
|||||||
use crate::token::Span;
|
use crate::token::Span;
|
||||||
use env::{FieldEntry, FuncTable, ParamEntry, StructTable};
|
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 ────────────────────────────────────────────────────────────────────
|
// ── Checker ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
pub struct Checker {
|
pub struct Checker {
|
||||||
@@ -166,7 +177,7 @@ fn value_struct_name(ty: &Ty) -> Option<&str> {
|
|||||||
|
|
||||||
// ── Entry point ────────────────────────────────────────────────────────────────
|
// ── 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();
|
let mut checker = Checker::new();
|
||||||
|
|
||||||
// ── Pass 1: collect struct names + function signatures ────────────────────
|
// ── 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
|
parser.errors
|
||||||
);
|
);
|
||||||
checker::check(&program, false)
|
checker::check(&program, false)
|
||||||
|
.errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|d| d.message)
|
.map(|d| d.message)
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -24,9 +24,18 @@ pub fn print_help() {
|
|||||||
"--version".bold(),
|
"--version".bold(),
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
" {} Compile without requiring a `main` entry point",
|
" {} Compile to object file (no `main` required, no linking)",
|
||||||
"-c".bold(),
|
"-c".bold(),
|
||||||
);
|
);
|
||||||
|
println!(
|
||||||
|
" {} Emit LLVM IR and stop (implies `-c`)",
|
||||||
|
"-S".bold(),
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
" {} {} Write output to <file>",
|
||||||
|
"-o".bold(),
|
||||||
|
"<file>".bold(),
|
||||||
|
);
|
||||||
println!();
|
println!();
|
||||||
println!("{}", "ARGS:".bold().yellow());
|
println!("{}", "ARGS:".bold().yellow());
|
||||||
println!(
|
println!(
|
||||||
@@ -61,15 +70,22 @@ pub fn io_error(path: &str, err: std::io::Error) -> ! {
|
|||||||
|
|
||||||
pub struct Opts {
|
pub struct Opts {
|
||||||
pub files: Vec<String>,
|
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,
|
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 {
|
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 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() {
|
match arg.as_str() {
|
||||||
"-h" | "--help" => {
|
"-h" | "--help" => {
|
||||||
print_help();
|
print_help();
|
||||||
@@ -80,6 +96,11 @@ 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() {
|
||||||
|
Some(path) => output = Some(path),
|
||||||
|
None => fatal("option `-o` requires an argument"),
|
||||||
|
},
|
||||||
flag if flag.starts_with('-') => {
|
flag if flag.starts_with('-') => {
|
||||||
fatal(&format!("unknown option `{flag}`"));
|
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");
|
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 ast;
|
||||||
pub mod checker;
|
pub mod checker;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
|
pub mod codegen;
|
||||||
pub mod diagnostics;
|
pub mod diagnostics;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
@@ -14,6 +15,10 @@ fn main() {
|
|||||||
let opts = cli::parse_args();
|
let opts = cli::parse_args();
|
||||||
let mut had_errors = false;
|
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 {
|
for path in &opts.files {
|
||||||
let content = fs::read_to_string(path).unwrap_or_else(|e| cli::io_error(path, e));
|
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() {
|
if parser.errors.is_empty() {
|
||||||
let sema_errors = checker::check(&program, opts.no_main);
|
let result = checker::check(&program, opts.no_main);
|
||||||
for diag in &sema_errors {
|
for diag in &result.errors {
|
||||||
eprint!("{}", diag.render(&content, path));
|
eprint!("{}", diag.render(&content, path));
|
||||||
had_errors = true;
|
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