use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use crate::Module;
use super::clang_pipeline::{
compile_to_ir, compile_to_object, CompilationDriver, CompilerInstance, CompilerInvocation,
CompilerOptions, FrontendAction, FrontendActionKind, InputLanguage, OptLevel,
};
use super::driver::ClangDriver;
use super::{CLangStandard, ClangOptions};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DriverMode {
Clang,
ClangXX,
ClangCL,
CC1,
CC1Plus,
ClangCPP,
Unknown,
}
impl DriverMode {
pub fn from_argv0(name: &str) -> Self {
let basename = std::path::Path::new(name)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or(name);
match basename {
"clang++" | "clang++-" | "clang-c++" => Self::ClangXX,
"clang-cl" => Self::ClangCL,
"cc1" => Self::CC1,
"cc1plus" => Self::CC1Plus,
"clang-cpp" | "clang-cpp-" => Self::ClangCPP,
"clang" | "clang-" => Self::Clang,
_ => {
if basename.starts_with("clang++") {
Self::ClangXX
} else if basename.starts_with("clang-cpp") {
Self::ClangCPP
} else if basename.starts_with("clang-cl") {
Self::ClangCL
} else if basename.starts_with("cc1plus") {
Self::CC1Plus
} else if basename.starts_with("cc1") {
Self::CC1
} else if basename.starts_with("clang") {
Self::Clang
} else {
Self::Unknown
}
}
}
}
pub fn default_language(&self) -> InputLanguage {
match self {
Self::Clang | Self::CC1 | Self::ClangCPP => InputLanguage::C,
Self::ClangXX | Self::CC1Plus => InputLanguage::CXX,
Self::ClangCL => InputLanguage::C,
Self::Unknown => InputLanguage::C,
}
}
pub fn is_cxx(&self) -> bool {
matches!(self, Self::ClangXX | Self::CC1Plus)
}
pub fn display_name(&self) -> &'static str {
match self {
Self::Clang => "clang",
Self::ClangXX => "clang++",
Self::ClangCL => "clang-cl",
Self::CC1 => "cc1",
Self::CC1Plus => "cc1plus",
Self::ClangCPP => "clang-cpp",
Self::Unknown => "clang",
}
}
}
pub fn expand_response_files(args: &[String]) -> Vec<String> {
let mut result = Vec::new();
for arg in args {
if arg.starts_with('@') && arg.len() > 1 {
let filename = &arg[1..];
match fs::read_to_string(filename) {
Ok(contents) => {
for line in contents.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with('#') {
result.push(trimmed.to_string());
}
}
}
Err(e) => {
eprintln!(
"clang: error: unable to read response file '{}': {}",
filename, e
);
}
}
} else {
result.push(arg.clone());
}
}
result
}
pub fn print_version(driver_mode: DriverMode) {
let name = driver_mode.display_name();
println!("{} version 18.0.0 (based on LLVM-native)", name);
println!("Target: x86_64-unknown-linux-gnu");
println!("Thread model: posix");
}
pub fn print_help(driver_mode: DriverMode) {
let name = driver_mode.display_name();
println!("OVERVIEW: {} C/C++ compiler", name);
println!();
println!("USAGE: {} [options] file...", name);
println!();
println!("OPTIONS:");
println!();
println!(" -E Preprocess only; do not compile, assemble, or link.");
println!(" -S Compile only; do not assemble or link.");
println!(" -c Compile and assemble, but do not link.");
println!(" -fsyntax-only Run the preprocessor, parser and semantic analysis.");
println!(" -emit-llvm Emit LLVM IR (text) to the output file.");
println!(" -emit-llvm-bc Emit LLVM bitcode to the output file.");
println!(" -dump-ast Dump the AST to stdout.");
println!(" -dump-tokens Dump tokens to stdout.");
println!();
println!(" -o <file> Write output to <file>.");
println!();
println!(" -std=<standard> Language standard to compile for.");
println!(" c89, c99, c11, c17, c23,");
println!(" gnu89, gnu99, gnu11, gnu17");
println!(" -ansi Equivalent to -std=c89.");
println!(" -x <language> Treat subsequent input files as type <language>.");
println!(" c, c++, objective-c, assembler");
println!();
println!(" -target <triple> Set the target triple.");
println!(" -march=<cpu> Set the target CPU.");
println!();
println!(" -O0 No optimization.");
println!(" -O1 Moderate optimization.");
println!(" -O2 Aggressive optimization.");
println!(" -O3 Maximum optimization.");
println!(" -Os Optimize for size.");
println!(" -Oz Aggressively optimize for size.");
println!(" -g Generate debug information.");
println!();
println!(" -I<dir> Add directory to include search path.");
println!(" -D<macro>[=<value>] Define a preprocessor macro.");
println!(" -U<macro> Undefine a preprocessor macro.");
println!(" -nostdinc Do not search standard system include dirs.");
println!(" -include <file> Include file before parsing.");
println!();
println!(" -w Suppress all warnings.");
println!(" -Wall Enable most common warnings.");
println!(" -Wextra Enable extra warnings.");
println!(" -Wpedantic Issue warnings for non-standard code.");
println!(" -Werror Treat warnings as errors.");
println!(" -Wno-<warning> Disable specific warning.");
println!(" -W<warning> Enable specific warning.");
println!();
println!(" -fPIC Generate position-independent code.");
println!(" -fPIE Generate position-independent executable.");
println!(" -fstack-protector Enable stack protection.");
println!(" -fsanitize=<type> Enable sanitizer (address, undefined, etc.).");
println!();
println!(" -v Verbose mode.");
println!(" --version Print version information.");
println!(" --help Print this help message.");
println!(" -print-resource-dir Print the resource directory path.");
println!(" -print-search-dirs Print the search paths.");
println!(" -dumpmachine Print the target triple.");
}
pub fn print_resource_dir() {
println!("/usr/lib/clang/18");
}
pub fn print_search_dirs() {
println!("programs: =/usr/bin");
println!("libraries: =/usr/lib/clang/18:/usr/lib");
println!("includes: =/usr/lib/clang/18/include:/usr/include");
}
pub fn print_target_triple(triple: &str) {
println!("{}", triple);
}
pub fn clang_main(args: &[String]) -> i32 {
let driver_mode = if args.is_empty() {
DriverMode::Clang
} else {
DriverMode::from_argv0(&args[0])
};
let expanded = expand_response_files(args);
for arg in &expanded {
match arg.as_str() {
"--version" | "-version" => {
print_version(driver_mode);
return 0;
}
"--help" | "-help" => {
print_help(driver_mode);
return 0;
}
"-print-resource-dir" => {
print_resource_dir();
return 0;
}
"-print-search-dirs" => {
print_search_dirs();
return 0;
}
_ => {}
}
}
let invocation = CompilerInvocation::from_args(&expanded);
if invocation.opts.print_version {
print_version(driver_mode);
return 0;
}
if invocation.opts.print_help {
print_help(driver_mode);
return 0;
}
if invocation.opts.print_resource_dir {
print_resource_dir();
return 0;
}
if invocation.opts.print_search_dirs {
print_search_dirs();
return 0;
}
if invocation.opts.print_target_triple {
print_target_triple(&invocation.opts.target_triple);
return 0;
}
if invocation.opts.input_files.is_empty() {
eprintln!("clang: error: no input files");
return 1;
}
let mut compiler = CompilerInstance::new(invocation);
for input_file in &compiler.opts.input_files.clone() {
if let Err(e) = compiler.source_manager.load_file(&input_file) {
eprintln!("clang: error: {}", e);
return 1;
}
}
let action_kind = compiler.opts.action;
let mut action = FrontendAction::for_kind(action_kind);
if let Err(errs) = action.begin_source_file(&mut compiler) {
for err in &errs {
eprintln!("clang: error: {}", err);
}
return 1;
}
if let Err(errs) = action.execute() {
for err in &errs {
eprintln!("clang: error: {}", err);
}
return 1;
}
if compiler.options().preprocess_only {
if let Some(output) = action.get_output() {
print!("{}", output);
}
}
if let Some(output) = action.get_output() {
let output_path = compiler.opts.output_file.as_ref();
if let Some(path) = output_path {
if let Err(e) = fs::write(path, &output) {
eprintln!(
"clang: error: cannot write output to '{}': {}",
path.display(),
e
);
return 1;
}
} else {
print!("{}", output);
}
}
let _ = action.end_source_file();
0
}
pub fn run_clang<I, S>(args: I) -> i32
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let args: Vec<String> = args.into_iter().map(|s| s.as_ref().to_string()).collect();
let code = clang_main(&args);
code
}
pub fn cc1_main(args: &[String]) -> i32 {
clang_main(args)
}
pub fn cc1plus_main(args: &[String]) -> i32 {
clang_main(args)
}
pub fn clang_driver_main(args: &[String]) -> i32 {
let mut expanded = expand_response_files(args);
if expanded.len() > 1 {
expanded[0] = "clang".to_string();
}
clang_main(&expanded)
}
pub fn clangxx_driver_main(args: &[String]) -> i32 {
let mut expanded = expand_response_files(args);
if expanded.len() > 1 {
expanded[0] = "clang++".to_string();
}
clang_main(&expanded)
}
pub fn clang_cl_driver_main(args: &[String]) -> i32 {
let mut expanded = expand_response_files(args);
if expanded.len() > 1 {
expanded[0] = "clang-cl".to_string();
}
clang_main(&expanded)
}
pub fn compile_legacy(source: &str, opts: ClangOptions) -> Result<Vec<u8>, Vec<String>> {
let mut driver = ClangDriver::new(opts);
match driver.compile_string(source) {
Ok(_module) => {
Ok(Vec::new())
}
Err(errs) => Err(errs),
}
}
pub fn compile_to_assembly_legacy(source: &str, opts: ClangOptions) -> Result<String, Vec<String>> {
let mut driver = ClangDriver::new(opts);
driver.compile_to_assembly(source)
}
pub fn compile_to_llvm_ir(source: &str, triple: &str) -> Result<String, Vec<String>> {
compile_to_ir(source, triple)
}
pub fn compile_to_object_bytes(source: &str, triple: &str) -> Result<Vec<u8>, Vec<String>> {
compile_to_object(source, triple)
}
pub fn quick_compile(source: &str, triple: &str) -> Result<(), Vec<String>> {
let opts = CompilerOptions {
target_triple: triple.to_string(),
action: FrontendActionKind::EmitObj,
syntax_only: true,
..CompilerOptions::default()
};
let inv = CompilerInvocation {
opts,
args: vec!["clang".to_string()],
program_name: "clang".to_string(),
};
let mut compiler = CompilerInstance::new(inv);
let _fid = compiler
.source_manager
.load_virtual_file("<input>", source.to_string())
.map_err(|e| vec![e])?;
let tokens = super::lexer::tokenize(source, compiler.opts.standard);
let mut parser = super::parser::Parser::new(&tokens, compiler.opts.standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = super::sema::Sema::new(compiler.opts.standard);
sema.analyze(&tu).map_err(|e| {
e.iter()
.map(|err| format!("sema: {}", err))
.collect::<Vec<_>>()
})?;
if sema.has_errors() {
return Err(sema.errors.clone());
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct DriverJob {
pub source_file: PathBuf,
pub output_file: PathBuf,
pub options: CompilerOptions,
pub is_header_unit: bool,
pub preprocess_only: bool,
pub save_temps: bool,
pub temp_prefix: Option<String>,
}
impl DriverJob {
pub fn new(source: PathBuf, output: PathBuf, opts: CompilerOptions) -> Self {
Self {
source_file: source,
output_file: output,
options: opts,
is_header_unit: false,
preprocess_only: false,
save_temps: false,
temp_prefix: None,
}
}
pub fn preprocess(source: PathBuf, output: PathBuf, opts: CompilerOptions) -> Self {
Self {
source_file: source,
output_file: output,
options: opts,
is_header_unit: false,
preprocess_only: true,
save_temps: false,
temp_prefix: None,
}
}
pub fn with_save_temps(mut self, prefix: &str) -> Self {
self.save_temps = true;
self.temp_prefix = Some(prefix.to_string());
self
}
}
#[derive(Debug)]
pub struct DriverJobList {
pub jobs: Vec<DriverJob>,
pub link: bool,
pub link_output: Option<PathBuf>,
pub linker_args: Vec<String>,
pub shared: bool,
pub stats: bool,
pub time_compilation: bool,
}
impl DriverJobList {
pub fn new() -> Self {
Self {
jobs: Vec::new(),
link: false,
link_output: None,
linker_args: Vec::new(),
shared: false,
stats: false,
time_compilation: false,
}
}
pub fn add_job(&mut self, job: DriverJob) {
self.jobs.push(job);
}
pub fn set_link_output(&mut self, output: PathBuf) {
self.link_output = Some(output);
self.link = true;
}
pub fn set_shared(&mut self, shared: bool) {
self.shared = shared;
}
pub fn build_jobs(opts: &CompilerOptions) -> Self {
let mut job_list = Self::new();
job_list.stats = opts.stats;
job_list.time_compilation = opts.verbose;
if opts.input_files.is_empty() {
return job_list;
}
let is_single_file = opts.input_files.len() == 1;
let output = opts.output_file.clone();
for input in &opts.input_files {
let obj_output = if is_single_file {
output.clone().unwrap_or_else(|| {
let mut p = input.clone();
p.set_extension("o");
p
})
} else {
let mut p = input.clone();
p.set_extension("o");
p
};
let job = DriverJob::new(input.clone(), obj_output, opts.clone());
let job = if opts.asm_output.is_some()
|| opts.emit_llvm
|| opts.emit_bc
|| opts.preprocess_only
{
let stem = input
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("output");
job.with_save_temps(stem)
} else {
job
};
job_list.add_job(job);
}
if opts.obj_output.is_none()
&& opts.asm_output.is_none()
&& !opts.emit_llvm
&& !opts.emit_bc
&& !opts.preprocess_only
&& !opts.syntax_only
{
let link_out = output.unwrap_or_else(|| PathBuf::from("a.out"));
job_list.set_link_output(link_out);
}
job_list
}
pub fn execute_all(&self) -> Result<Vec<Vec<u8>>, Vec<String>> {
let mut results: Vec<Vec<u8>> = Vec::new();
let mut all_errors: Vec<String> = Vec::new();
for job in &self.jobs {
match execute_driver_job(job) {
Ok(object_bytes) => {
results.push(object_bytes);
}
Err(errors) => {
all_errors.extend(errors);
}
}
}
if all_errors.is_empty() {
Ok(results)
} else {
Err(all_errors)
}
}
pub fn is_empty(&self) -> bool {
self.jobs.is_empty()
}
pub fn len(&self) -> usize {
self.jobs.len()
}
}
impl Default for DriverJobList {
fn default() -> Self {
Self::new()
}
}
fn execute_driver_job(job: &DriverJob) -> Result<Vec<u8>, Vec<String>> {
let source = std::fs::read_to_string(&job.source_file)
.map_err(|e| vec![format!("cannot read {}: {}", job.source_file.display(), e)])?;
if job.preprocess_only || (job.save_temps && job.options.preprocess_only) {
let pp_output = run_preprocess_job(&source, &job.options)?;
if let Some(ref prefix) = job.temp_prefix {
let pp_file = format!("{}.i", prefix);
std::fs::write(&pp_file, &pp_output)
.map_err(|e| vec![format!("cannot write {}: {}", pp_file, e)])?;
}
if job.preprocess_only {
return Ok(Vec::new());
}
}
let tokens = super::lexer::tokenize(&source, job.options.standard);
let mut parser = super::parser::Parser::new(&tokens, job.options.standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = super::sema::Sema::new(job.options.standard);
sema.analyze(&tu).map_err(|e| {
e.iter()
.map(|err| format!("sema error: {}", err))
.collect::<Vec<_>>()
})?;
if sema.has_errors() {
return Err(sema.errors.clone());
}
let triple = &job.options.target_triple;
let mut cg = super::codegen::ClangCodeGen::new(
job.source_file
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("input"),
triple,
);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen error: {}", err))
.collect::<Vec<_>>()
})?;
if job.options.asm_output.is_some() {
let asm = emit_assembly_text(&cg.module, &job.options);
if let Some(ref prefix) = job.temp_prefix {
let asm_file = format!("{}.s", prefix);
std::fs::write(&asm_file, &asm)
.map_err(|e| vec![format!("cannot write {}: {}", asm_file, e)])?;
}
std::fs::write(&job.output_file, &asm)
.map_err(|e| vec![format!("cannot write {}: {}", job.output_file.display(), e)])?;
}
if job.options.emit_llvm {
let ir = emit_llvm_ir_text(&cg.module, triple);
if let Some(ref prefix) = job.temp_prefix {
let ir_file = format!("{}.ll", prefix);
std::fs::write(&ir_file, &ir)
.map_err(|e| vec![format!("cannot write {}: {}", ir_file, e)])?;
}
std::fs::write(&job.output_file, &ir)
.map_err(|e| vec![format!("cannot write {}: {}", job.output_file.display(), e)])?;
}
if job.options.emit_bc {
let bc = emit_bitcode_bytes(&cg.module);
if let Some(ref prefix) = job.temp_prefix {
let bc_file = format!("{}.bc", prefix);
std::fs::write(&bc_file, &bc)
.map_err(|e| vec![format!("cannot write {}: {}", bc_file, e)])?;
}
std::fs::write(&job.output_file, &bc)
.map_err(|e| vec![format!("cannot write {}: {}", job.output_file.display(), e)])?;
}
let object_bytes = emit_object_bytes(&cg.module, &job.options);
if job.options.asm_output.is_none() && !job.options.emit_llvm && !job.options.emit_bc {
std::fs::write(&job.output_file, &object_bytes)
.map_err(|e| vec![format!("cannot write {}: {}", job.output_file.display(), e)])?;
}
Ok(object_bytes)
}
fn run_preprocess_job(source: &str, _opts: &CompilerOptions) -> Result<String, Vec<String>> {
let mut output = String::new();
let mut in_block_comment = false;
let chars: Vec<char> = source.chars().collect();
let mut i = 0;
while i < chars.len() {
if in_block_comment {
if chars[i] == '*' && i + 1 < chars.len() && chars[i + 1] == '/' {
in_block_comment = false;
i += 2;
continue;
}
i += 1;
continue;
}
if chars[i] == '/' && i + 1 < chars.len() && chars[i + 1] == '*' {
in_block_comment = true;
i += 2;
continue;
}
if chars[i] == '/' && i + 1 < chars.len() && chars[i + 1] == '/' {
while i < chars.len() && chars[i] != '\n' {
i += 1;
}
if i < chars.len() {
output.push('\n');
i += 1;
}
continue;
}
output.push(chars[i]);
i += 1;
}
Ok(output)
}
fn emit_assembly_text(module: &Module, _opts: &CompilerOptions) -> String {
let mut asm = String::new();
asm.push_str("\t.text\n");
asm.push_str(&format!("\t.file\t\"{}\"\n", module.name));
asm.push_str("\n");
for func in &module.functions {
let func = func.borrow();
asm.push_str(&format!("\t.globl\t{}\n", func.name));
asm.push_str(&format!("\t.type\t{},@function\n", func.name));
asm.push_str(&format!("{}:\n", func.name));
for bb in &func.blocks {
let bb = bb.borrow();
if !bb.name.is_empty() {
asm.push_str(&format!(".L{}:\n", bb.name));
}
for inst in &bb.instructions {
let inst = inst.borrow();
asm.push_str(&format!("\t; {:?}\n", inst.opcode));
}
}
asm.push_str(&format!("\t.size\t{}, .-{}\n\n", func.name, func.name));
}
asm
}
fn emit_llvm_ir_text(module: &Module, triple: &str) -> String {
let mut ir = String::new();
ir.push_str(&format!("; ModuleID = '{}'\n", module.name));
ir.push_str(&format!("target triple = \"{}\"\n", triple));
ir.push_str(&format!("target datalayout = \"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128\"\n"));
for func in &module.functions {
let func = func.borrow();
ir.push_str(&format!(
"define {} @{} (",
func.return_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| "void".to_string()),
func.name
));
for (i, param) in func.params.iter().enumerate() {
if i > 0 {
ir.push_str(", ");
}
ir.push_str(&format!("{} %{}", param, i));
}
ir.push_str(") {\n");
for bb in &func.blocks {
let bb = bb.borrow();
ir.push_str(&format!(
"{}:\n",
if bb.name.is_empty() {
"entry"
} else {
&bb.name
}
));
for inst in &bb.instructions {
let inst = inst.borrow();
if let Some(ref r) = inst.result {
ir.push_str(&format!(" %{} = {:?} ", r, inst.opcode));
} else {
ir.push_str(&format!(" {:?}", inst.opcode));
}
ir.push_str("\n");
}
}
ir.push_str("}\n\n");
}
ir
}
fn emit_bitcode_bytes(_module: &Module) -> Vec<u8> {
let mut bc: Vec<u8> = Vec::new();
bc.extend_from_slice(&[0xDE, 0xC0, 0x17, 0x0B]);
bc.extend_from_slice(&[0; 64]);
bc
}
fn emit_object_bytes(_module: &Module, opts: &CompilerOptions) -> Vec<u8> {
let mut obj: Vec<u8> = Vec::new();
if opts.target_triple.contains("x86_64") || opts.target_triple.contains("aarch64") {
obj.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
obj.push(2);
obj.push(1);
obj.push(1);
obj.push(0);
obj.push(0);
obj.extend_from_slice(&[0; 7]);
obj.extend_from_slice(&[1, 0]); if opts.target_triple.contains("x86_64") {
obj.extend_from_slice(&[0x3e, 0]); } else {
obj.extend_from_slice(&[0xB7, 0]); }
obj.extend_from_slice(&[1, 0, 0, 0]);
obj.extend_from_slice(&[0; 8 + 8 + 4]);
obj.extend_from_slice(&[64, 0]);
obj.extend_from_slice(&[56, 0]);
obj.extend_from_slice(&[0, 0]);
obj.extend_from_slice(&[64, 0]);
obj.extend_from_slice(&[0, 0]);
obj.extend_from_slice(&[0, 0]);
} else {
obj.extend_from_slice(&[0; 64]);
}
obj
}
#[derive(Debug)]
pub struct CompilationResult {
pub exit_code: i32,
pub error_count: usize,
pub warning_count: usize,
pub output_files: Vec<PathBuf>,
pub diagnostic_summary: String,
pub time_breakdown: Option<CompileTimeBreakdown>,
pub statistics: Option<CompilationStatistics>,
}
#[derive(Debug, Clone)]
pub struct CompileTimeBreakdown {
pub lex_time_ms: u64,
pub preprocess_time_ms: u64,
pub parse_time_ms: u64,
pub sema_time_ms: u64,
pub codegen_time_ms: u64,
pub opt_time_ms: u64,
pub emit_time_ms: u64,
pub total_time_ms: u64,
}
impl CompileTimeBreakdown {
pub fn new() -> Self {
Self {
lex_time_ms: 0,
preprocess_time_ms: 0,
parse_time_ms: 0,
sema_time_ms: 0,
codegen_time_ms: 0,
opt_time_ms: 0,
emit_time_ms: 0,
total_time_ms: 0,
}
}
pub fn format(&self) -> String {
let mut out = String::new();
out.push_str("=== Compile Time Breakdown ===\n");
if self.lex_time_ms > 0 {
out.push_str(&format!(" Lexing: {:>6} ms\n", self.lex_time_ms));
}
if self.preprocess_time_ms > 0 {
out.push_str(&format!(
" Preprocessing: {:>6} ms\n",
self.preprocess_time_ms
));
}
out.push_str(&format!(" Parsing: {:>6} ms\n", self.parse_time_ms));
out.push_str(&format!(" Sema: {:>6} ms\n", self.sema_time_ms));
out.push_str(&format!(
" Codegen: {:>6} ms\n",
self.codegen_time_ms
));
if self.opt_time_ms > 0 {
out.push_str(&format!(" Optimization: {:>6} ms\n", self.opt_time_ms));
}
out.push_str(&format!(" Emission: {:>6} ms\n", self.emit_time_ms));
out.push_str(&format!(" Total: {:>6} ms\n", self.total_time_ms));
out
}
}
impl Default for CompileTimeBreakdown {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CompilationStatistics {
pub num_source_files: usize,
pub num_lines: usize,
pub num_tokens: usize,
pub num_ast_nodes: usize,
pub num_ir_instructions: usize,
pub num_machine_instructions: usize,
pub num_object_bytes: usize,
pub peak_memory_bytes: usize,
pub num_includes: usize,
pub num_macro_expansions: usize,
}
impl CompilationStatistics {
pub fn new() -> Self {
Self {
num_source_files: 0,
num_lines: 0,
num_tokens: 0,
num_ast_nodes: 0,
num_ir_instructions: 0,
num_machine_instructions: 0,
num_object_bytes: 0,
peak_memory_bytes: 0,
num_includes: 0,
num_macro_expansions: 0,
}
}
pub fn format(&self) -> String {
let mut out = String::new();
out.push_str("=== Compilation Statistics ===\n");
out.push_str(&format!(
" Source files: {:>6}\n",
self.num_source_files
));
out.push_str(&format!(" Lines of code: {:>6}\n", self.num_lines));
out.push_str(&format!(" Tokens: {:>6}\n", self.num_tokens));
out.push_str(&format!(
" AST nodes: {:>6}\n",
self.num_ast_nodes
));
out.push_str(&format!(
" IR instructions: {:>6}\n",
self.num_ir_instructions
));
out.push_str(&format!(
" Machine instrs: {:>6}\n",
self.num_machine_instructions
));
out.push_str(&format!(
" Object bytes: {:>6}\n",
self.num_object_bytes
));
out.push_str(&format!(
" Includes: {:>6}\n",
self.num_includes
));
out.push_str(&format!(
" Macro expansions: {:>6}\n",
self.num_macro_expansions
));
out.push_str(&format!(
" Peak memory (KB): {:>6}\n",
self.peak_memory_bytes / 1024
));
out
}
}
impl Default for CompilationStatistics {
fn default() -> Self {
Self::new()
}
}
pub fn execute_compilation(inv: &CompilerInvocation) -> CompilationResult {
let mut error_count: usize = 0;
let mut warning_count: usize = 0;
let mut output_files: Vec<PathBuf> = Vec::new();
let job_list = DriverJobList::build_jobs(&inv.opts);
if job_list.is_empty() && inv.opts.input_files.is_empty() {
return CompilationResult {
exit_code: 1,
error_count: 1,
warning_count: 0,
output_files: Vec::new(),
diagnostic_summary: "clang: error: no input files\n".to_string(),
time_breakdown: None,
statistics: None,
};
}
match job_list.execute_all() {
Ok(objects) => {
for job in &job_list.jobs {
output_files.push(job.output_file.clone());
}
if job_list.link {
if let Some(ref link_output) = job_list.link_output {
match invoke_linker(&objects, link_output, &inv.opts, job_list.shared) {
Ok(()) => {
output_files.push(link_output.clone());
}
Err(errs) => {
error_count += errs.len();
return CompilationResult {
exit_code: 1,
error_count,
warning_count,
output_files,
diagnostic_summary: format!("linker errors:\n{}", errs.join("\n")),
time_breakdown: None,
statistics: None,
};
}
}
}
}
}
Err(errors) => {
error_count = errors.len();
return CompilationResult {
exit_code: 1,
error_count,
warning_count,
output_files,
diagnostic_summary: format!("compilation errors:\n{}", errors.join("\n")),
time_breakdown: None,
statistics: None,
};
}
}
let mut summary = String::new();
if error_count > 0 {
summary.push_str(&format!("{} error(s) generated.\n", error_count));
}
if warning_count > 0 {
summary.push_str(&format!("{} warning(s) generated.\n", warning_count));
}
let time_breakdown = if inv.opts.verbose {
let mut tb = CompileTimeBreakdown::new();
tb.total_time_ms = 1; Some(tb)
} else {
None
};
let statistics = if inv.opts.stats {
let mut stats = CompilationStatistics::new();
stats.num_source_files = inv.opts.input_files.len();
Some(stats)
} else {
None
};
CompilationResult {
exit_code: if error_count > 0 { 1 } else { 0 },
error_count,
warning_count,
output_files,
diagnostic_summary: summary,
time_breakdown,
statistics,
}
}
fn invoke_linker(
object_bytes_list: &[Vec<u8>],
output: &PathBuf,
opts: &CompilerOptions,
shared: bool,
) -> Result<(), Vec<String>> {
if object_bytes_list.is_empty() {
return Err(vec!["no object files to link".to_string()]);
}
let tmp_dir = std::env::temp_dir().join(format!("clang-link-{}", std::process::id()));
if let Err(e) = std::fs::create_dir_all(&tmp_dir) {
return Err(vec![format!("cannot create temp dir: {}", e)]);
}
let mut obj_files: Vec<PathBuf> = Vec::new();
for (i, obj) in object_bytes_list.iter().enumerate() {
let obj_path = tmp_dir.join(format!("obj_{}.o", i));
if let Err(e) = std::fs::write(&obj_path, obj) {
return Err(vec![format!("cannot write temp object: {}", e)]);
}
obj_files.push(obj_path);
}
let mut linked: Vec<u8> = Vec::new();
if opts.target_triple.contains("x86_64") {
linked.extend_from_slice(&[0x7f, b'E', b'L', b'F']);
linked.push(2);
linked.push(1);
linked.push(1);
linked.push(0);
linked.push(0);
linked.extend_from_slice(&[0; 7]);
linked.extend_from_slice(&[2, 0]); linked.extend_from_slice(&[0x3e, 0]); linked.extend_from_slice(&[1, 0, 0, 0]);
linked.extend_from_slice(&[0; 24]);
linked.extend_from_slice(&[64, 0]);
linked.extend_from_slice(&[56, 0]);
linked.extend_from_slice(&[1, 0]); linked.extend_from_slice(&[64, 0]);
linked.extend_from_slice(&[0, 0]);
linked.extend_from_slice(&[0, 0]);
linked.extend_from_slice(&[1, 0, 0, 0]);
linked.extend_from_slice(&[5, 0, 0, 0]); linked.extend_from_slice(&[0; 8]);
linked.extend_from_slice(&[0; 8]);
linked.extend_from_slice(&[0; 8]);
linked.extend_from_slice(&[0; 8]);
linked.extend_from_slice(&[0; 8]);
linked.extend_from_slice(&[0x00, 0x10, 0, 0, 0, 0, 0, 0]);
}
for obj in object_bytes_list {
linked.extend_from_slice(obj);
}
std::fs::write(output, &linked)
.map_err(|e| vec![format!("cannot write {}: {}", output.display(), e)])?;
let _ = std::fs::remove_dir_all(&tmp_dir);
Ok(())
}
pub fn print_diagnostic_summary(error_count: usize, warning_count: usize) -> String {
let mut summary = String::new();
if error_count > 0 || warning_count > 0 {
if error_count > 0 {
summary.push_str(&format!(
"{} error{}",
error_count,
if error_count == 1 { "" } else { "s" }
));
if warning_count > 0 {
summary.push_str(", ");
}
}
if warning_count > 0 {
summary.push_str(&format!(
"{} warning{}",
warning_count,
if warning_count == 1 { "" } else { "s" }
));
}
summary.push_str(" generated.\n");
}
summary
}
pub fn run_compiler(args: &[String]) -> i32 {
let inv = CompilerInvocation::from_args_native(args.iter().map(|s| s.as_str()));
let result = execute_compilation(&inv);
if !result.diagnostic_summary.is_empty() {
eprint!("{}", result.diagnostic_summary);
}
if let Some(ref tb) = result.time_breakdown {
eprint!("{}", tb.format());
}
if let Some(ref stats) = result.statistics {
eprint!("{}", stats.format());
}
result.exit_code
}
impl CompilerInvocation {
pub fn from_args_native<'a, I>(args: I) -> Self
where
I: IntoIterator<Item = &'a str>,
{
let args_vec: Vec<String> = args.into_iter().map(|s| s.to_string()).collect();
Self::from_args(args_vec.iter().map(|s| s.as_str()))
}
}
#[derive(Debug)]
pub struct CompilationJobRunner {
pub jobs: DriverJobList,
pub parallel_jobs: usize,
pub keep_going: bool,
pub verbose: bool,
pub results: Vec<JobResult>,
}
#[derive(Debug, Clone)]
pub struct JobResult {
pub source_file: PathBuf,
pub output_file: PathBuf,
pub succeeded: bool,
pub errors: Vec<String>,
pub duration_ms: u64,
pub warning_count: usize,
}
impl JobResult {
pub fn success(source: PathBuf, output: PathBuf) -> Self {
Self {
source_file: source,
output_file: output,
succeeded: true,
errors: Vec::new(),
duration_ms: 0,
warning_count: 0,
}
}
pub fn failure(source: PathBuf, output: PathBuf, errors: Vec<String>) -> Self {
Self {
source_file: source,
output_file: output,
succeeded: false,
errors,
duration_ms: 0,
warning_count: 0,
}
}
}
impl CompilationJobRunner {
pub fn new(jobs: DriverJobList) -> Self {
Self {
jobs,
parallel_jobs: 0,
keep_going: false,
verbose: false,
results: Vec::new(),
}
}
pub fn with_parallel(mut self, n: usize) -> Self {
self.parallel_jobs = n;
self
}
pub fn with_keep_going(mut self) -> Self {
self.keep_going = true;
self
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
pub fn run(&mut self) -> Result<(), Vec<String>> {
self.results.clear();
for job in &self.jobs.jobs {
if self.verbose {
eprintln!("Compiling {}...", job.source_file.display());
}
let start = std::time::Instant::now();
match execute_driver_job(job) {
Ok(_bytes) => {
let duration = start.elapsed().as_millis() as u64;
self.results.push(JobResult {
source_file: job.source_file.clone(),
output_file: job.output_file.clone(),
succeeded: true,
errors: Vec::new(),
duration_ms: duration,
warning_count: 0,
});
if self.verbose {
eprintln!(" -> {} ({:?})", job.output_file.display(), start.elapsed());
}
}
Err(errors) => {
let duration = start.elapsed().as_millis() as u64;
self.results.push(JobResult {
source_file: job.source_file.clone(),
output_file: job.output_file.clone(),
succeeded: false,
errors: errors.clone(),
duration_ms: duration,
warning_count: 0,
});
if !self.keep_going {
return Err(errors);
}
}
}
}
let all_errors: Vec<String> = self
.results
.iter()
.filter(|r| !r.succeeded)
.flat_map(|r| r.errors.clone())
.collect();
if all_errors.is_empty() {
Ok(())
} else {
Err(all_errors)
}
}
pub fn summary(&self) -> String {
let succeeded = self.results.iter().filter(|r| r.succeeded).count();
let failed = self.results.iter().filter(|r| !r.succeeded).count();
let total_time: u64 = self.results.iter().map(|r| r.duration_ms).sum();
format!(
"{} job(s) succeeded, {} failed, {} total time: {} ms",
succeeded,
failed,
self.results.len(),
total_time
)
}
}
#[derive(Debug, Clone)]
pub struct FileCache {
entries: std::collections::HashMap<PathBuf, CacheEntry>,
enabled: bool,
}
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub source_hash: u64,
pub flags_hash: u64,
pub object_bytes: Vec<u8>,
pub timestamp: u64,
pub valid: bool,
}
impl CacheEntry {
pub fn new(source_hash: u64, flags_hash: u64, object_bytes: Vec<u8>) -> Self {
Self {
source_hash,
flags_hash,
object_bytes,
timestamp: 0,
valid: true,
}
}
pub fn invalidate(&mut self) {
self.valid = false;
}
}
impl FileCache {
pub fn new() -> Self {
Self {
entries: std::collections::HashMap::new(),
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn lookup(&self, source: &Path, source_hash: u64, flags_hash: u64) -> Option<&CacheEntry> {
if !self.enabled {
return None;
}
self.entries.get(source).and_then(|entry| {
if entry.valid && entry.source_hash == source_hash && entry.flags_hash == flags_hash {
Some(entry)
} else {
None
}
})
}
pub fn insert(&mut self, source: &Path, entry: CacheEntry) {
if self.enabled {
self.entries.insert(source.to_path_buf(), entry);
}
}
pub fn invalidate(&mut self, source: &Path) {
if let Some(entry) = self.entries.get_mut(source) {
entry.invalidate();
}
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn compute_flags_hash(flags: &[String]) -> u64 {
let mut hash: u64 = 5381;
for flag in flags {
for b in flag.bytes() {
hash = hash.wrapping_mul(33).wrapping_add(b as u64);
}
}
hash
}
}
impl Default for FileCache {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct CompilationSession {
pub invocation: CompilerInvocation,
pub driver_mode: DriverMode,
pub jobs: DriverJobList,
pub started: bool,
pub completed: bool,
pub errors: super::clang_pipeline::ErrorTracker,
pub cache: Option<FileCache>,
}
impl CompilationSession {
pub fn new(args: &[String]) -> Self {
let mode = DriverMode::from_argv0(&args.first().cloned().unwrap_or_default());
let expanded = expand_response_files(args);
let inv = CompilerInvocation::from_args_native(expanded.iter().map(|s| s.as_str()));
let jobs = DriverJobList::build_jobs(&inv.opts);
Self {
invocation: inv,
driver_mode: mode,
jobs,
started: false,
completed: false,
errors: super::clang_pipeline::ErrorTracker::new(),
cache: None,
}
}
pub fn with_cache(mut self, cache: FileCache) -> Self {
self.cache = Some(cache);
self
}
pub fn start(&mut self) {
self.started = true;
}
pub fn run(&mut self) -> CompilationResult {
self.started = true;
let result = execute_compilation(&self.invocation);
self.completed = true;
result
}
pub fn mode(&self) -> DriverMode {
self.driver_mode
}
pub fn is_cxx(&self) -> bool {
self.driver_mode.is_cxx()
}
pub fn num_source_files(&self) -> usize {
self.invocation.opts.input_files.len()
}
pub fn is_started(&self) -> bool {
self.started
}
pub fn is_completed(&self) -> bool {
self.completed
}
pub fn report_error(&mut self, msg: &str) {
self.errors.error(msg);
}
pub fn report_warning(&mut self, msg: &str) {
self.errors.warning(msg);
}
}
#[derive(Debug)]
pub struct BuildOrchestrator {
pub sessions: Vec<CompilationSession>,
pub keep_going: bool,
pub stats: BuildStats,
pub show_progress: bool,
}
#[derive(Debug, Clone, Default)]
pub struct BuildStats {
pub total_sessions: usize,
pub successful: usize,
pub failed: usize,
pub total_files: usize,
pub total_errors: usize,
pub total_warnings: usize,
pub total_time_ms: u64,
}
impl BuildOrchestrator {
pub fn new() -> Self {
Self {
sessions: Vec::new(),
keep_going: false,
stats: BuildStats::default(),
show_progress: false,
}
}
pub fn add_session(&mut self, session: CompilationSession) {
self.sessions.push(session);
}
pub fn build_all(&mut self) -> Result<(), Vec<String>> {
let mut all_errors: Vec<String> = Vec::new();
self.stats.total_sessions = self.sessions.len();
for session in &mut self.sessions {
self.stats.total_files += session.num_source_files();
if self.show_progress {
eprintln!(
"Building {} file(s) with mode {:?}...",
session.num_source_files(),
session.driver_mode
);
}
let result = session.run();
self.stats.total_errors += result.error_count;
self.stats.total_warnings += result.warning_count;
if result.exit_code != 0 {
self.stats.failed += 1;
all_errors.push(result.diagnostic_summary);
if !self.keep_going {
return Err(all_errors);
}
} else {
self.stats.successful += 1;
}
}
if all_errors.is_empty() {
Ok(())
} else {
Err(all_errors)
}
}
pub fn summary(&self) -> String {
format!(
"Build complete: {} succeeded, {} failed out of {} session(s) ({} files, {} errors, {} warnings)",
self.stats.successful,
self.stats.failed,
self.stats.total_sessions,
self.stats.total_files,
self.stats.total_errors,
self.stats.total_warnings
)
}
}
impl Default for BuildOrchestrator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_driver_mode_from_argv0_clang() {
assert_eq!(DriverMode::from_argv0("clang"), DriverMode::Clang);
assert_eq!(DriverMode::from_argv0("/usr/bin/clang"), DriverMode::Clang);
assert_eq!(
DriverMode::from_argv0("/usr/bin/clang-18"),
DriverMode::Clang
);
}
#[test]
fn test_driver_mode_from_argv0_clangxx() {
assert_eq!(DriverMode::from_argv0("clang++"), DriverMode::ClangXX);
assert_eq!(
DriverMode::from_argv0("/usr/bin/clang++"),
DriverMode::ClangXX
);
assert_eq!(DriverMode::from_argv0("clang++-18"), DriverMode::ClangXX);
}
#[test]
fn test_driver_mode_from_argv0_clang_cl() {
assert_eq!(DriverMode::from_argv0("clang-cl"), DriverMode::ClangCL);
}
#[test]
fn test_driver_mode_from_argv0_cc1() {
assert_eq!(DriverMode::from_argv0("cc1"), DriverMode::CC1);
}
#[test]
fn test_driver_mode_from_argv0_cc1plus() {
assert_eq!(DriverMode::from_argv0("cc1plus"), DriverMode::CC1Plus);
}
#[test]
fn test_driver_mode_default_language() {
assert_eq!(DriverMode::CC1.default_language(), InputLanguage::C);
assert_eq!(DriverMode::CC1Plus.default_language(), InputLanguage::CXX);
}
#[test]
fn test_expand_response_files_no_at() {
let args = vec!["clang".to_string(), "-c".to_string(), "file.c".to_string()];
let expanded = expand_response_files(&args);
assert_eq!(expanded.len(), 3);
assert_eq!(expanded[1], "-c");
}
#[test]
fn test_expand_response_files_with_at() {
let args = vec![
"clang".to_string(),
"@nonexistent.rsp".to_string(),
"file.c".to_string(),
];
let expanded = expand_response_files(&args);
assert_eq!(expanded.last(), Some(&"file.c".to_string()));
}
#[test]
fn test_print_version() {
print_version(DriverMode::Clang);
}
#[test]
fn test_print_help() {
print_help(DriverMode::Clang);
}
#[test]
fn test_clang_main_version() {
let args = vec!["clang".to_string(), "--version".to_string()];
let code = clang_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clang_main_help() {
let args = vec!["clang".to_string(), "--help".to_string()];
let code = clang_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clang_main_no_input() {
let args = vec!["clang".to_string()];
let code = clang_main(&args);
assert_eq!(code, 1); }
#[test]
fn test_clang_main_print_resource_dir() {
let args = vec!["clang".to_string(), "-print-resource-dir".to_string()];
let code = clang_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clang_main_print_search_dirs() {
let args = vec!["clang".to_string(), "-print-search-dirs".to_string()];
let code = clang_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clang_main_print_target_triple() {
let args = vec!["clang".to_string(), "-dumpmachine".to_string()];
let code = clang_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_run_clang() {
let code = run_clang(["clang", "--version"]);
assert_eq!(code, 0);
}
#[test]
fn test_quick_compile_minimal() {
let result = quick_compile("int x;", "x86_64-unknown-linux-gnu");
assert!(result.is_ok());
}
#[test]
fn test_quick_compile_function() {
let result = quick_compile("int f(void) { return 42; }", "x86_64-unknown-linux-gnu");
assert!(result.is_ok());
}
#[test]
fn test_quick_compile_syntax_error() {
let result = quick_compile("int f( {", "x86_64-unknown-linux-gnu");
assert!(result.is_err());
}
#[test]
fn test_compile_legacy() {
let opts = ClangOptions {
standard: CLangStandard::C11,
optimize: false,
..ClangOptions::default()
};
let result = compile_legacy("int x;", opts);
assert!(result.is_ok());
}
#[test]
fn test_cc1_main() {
let args = vec!["cc1".to_string(), "--version".to_string()];
let code = cc1_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_cc1plus_main() {
let args = vec!["cc1plus".to_string(), "--version".to_string()];
let code = cc1plus_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clang_driver_main() {
let args = vec!["clang".to_string(), "--version".to_string()];
let code = clang_driver_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clangxx_driver_main() {
let args = vec!["clang++".to_string(), "--version".to_string()];
let code = clangxx_driver_main(&args);
assert_eq!(code, 0);
}
#[test]
fn test_clang_cl_driver_main() {
let args = vec!["clang-cl".to_string(), "--version".to_string()];
let code = clang_cl_driver_main(&args);
assert_eq!(code, 0);
}
}