use std::collections::HashMap;
use std::fmt;
use crate::Function;
use crate::Opcode;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use crate::module::Module;
use super::ast::*;
use super::codegen::ClangCodeGen;
use super::diagnostics::{
ClangSourceManager, DiagID, DiagSeverity, DiagnosticBuilder, DiagnosticConsumer,
DiagnosticEngine, DiagnosticOptions, SourceRange, StructuredDiagnostic,
};
use super::lexer;
use super::parser;
use super::preprocessor::Preprocessor;
use super::sema::Sema;
use super::{CLangStandard, ClangOptions};
#[derive(Debug, Clone)]
pub struct CompilerOptions {
pub standard: CLangStandard,
pub lang: InputLanguage,
pub gnu_extensions: bool,
pub ms_extensions: bool,
pub trigraphs: bool,
pub digraphs: bool,
pub output_file: Option<PathBuf>,
pub action: FrontendActionKind,
pub output_format: OutputFormat,
pub target_triple: String,
pub target_cpu: Option<String>,
pub target_features: Vec<String>,
pub opt_level: OptLevel,
pub debug_info: bool,
pub debug_info_kind: DebugInfoKind,
pub warnings: bool,
pub wall: bool,
pub wextra: bool,
pub pedantic: bool,
pub werror: bool,
pub wno: Vec<String>,
pub w: Vec<String>,
pub weverything: bool,
pub includes: Vec<PathBuf>,
pub system_includes: Vec<PathBuf>,
pub defines: Vec<(String, Option<String>)>,
pub undefines: Vec<String>,
pub nostdinc: bool,
pub nostdincpp: bool,
pub pic: bool,
pub pie: bool,
pub stack_protector: i32,
pub coverage: bool,
pub profile: bool,
pub sanitize: Vec<String>,
pub tls_model: String,
pub emit_llvm: bool,
pub emit_bc: bool,
pub asm_output: Option<PathBuf>,
pub obj_output: Option<PathBuf>,
pub module_output: Option<PathBuf>,
pub preprocess_only: bool,
pub syntax_only: bool,
pub linker_args: Vec<String>,
pub static_linking: bool,
pub shared: bool,
pub input_files: Vec<PathBuf>,
pub verbose: bool,
pub dry_run: bool,
pub print_resource_dir: bool,
pub print_search_dirs: bool,
pub print_target_triple: bool,
pub print_version: bool,
pub print_help: bool,
pub working_dir: Option<PathBuf>,
pub stats: bool,
}
impl Default for CompilerOptions {
fn default() -> Self {
Self {
standard: CLangStandard::C17,
lang: InputLanguage::C,
gnu_extensions: false,
ms_extensions: false,
trigraphs: false,
digraphs: true,
output_file: None,
action: FrontendActionKind::EmitObj,
output_format: OutputFormat::Object,
target_triple: "x86_64-unknown-linux-gnu".into(),
target_cpu: None,
target_features: Vec::new(),
opt_level: OptLevel::O0,
debug_info: false,
debug_info_kind: DebugInfoKind::None,
warnings: true,
wall: false,
wextra: false,
pedantic: false,
werror: false,
wno: Vec::new(),
w: Vec::new(),
weverything: false,
includes: Vec::new(),
system_includes: Vec::new(),
defines: Vec::new(),
undefines: Vec::new(),
nostdinc: false,
nostdincpp: false,
pic: false,
pie: false,
stack_protector: 0,
coverage: false,
profile: false,
sanitize: Vec::new(),
tls_model: "global-dynamic".into(),
emit_llvm: false,
emit_bc: false,
asm_output: None,
obj_output: None,
module_output: None,
preprocess_only: false,
syntax_only: false,
linker_args: Vec::new(),
static_linking: false,
shared: false,
input_files: Vec::new(),
verbose: false,
dry_run: false,
print_resource_dir: false,
print_search_dirs: false,
print_target_triple: false,
print_version: false,
print_help: false,
working_dir: None,
stats: false,
}
}
}
impl CompilerOptions {
pub fn from_clang_options(opts: &ClangOptions) -> Self {
Self {
standard: opts.standard,
output_file: opts.output_file.as_ref().map(PathBuf::from),
target_triple: opts.target_triple.clone(),
opt_level: if opts.optimize {
OptLevel::O2
} else {
OptLevel::O0
},
debug_info: opts.debug_info,
warnings: opts.warnings,
wall: opts.wall,
pedantic: opts.pedantic,
werror: opts.werror,
includes: opts.includes.iter().map(PathBuf::from).collect(),
defines: opts.defines.clone(),
action: FrontendActionKind::EmitObj,
..Default::default()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputLanguage {
C,
CXX,
ObjC,
ObjCXX,
OpenCL,
CUDA,
Asm,
LLVM_IR,
}
impl InputLanguage {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"c" => Some(Self::C),
"c++" | "cxx" => Some(Self::CXX),
"objective-c" | "objc" => Some(Self::ObjC),
"objective-c++" | "objc++" => Some(Self::ObjCXX),
"opencl" | "cl" => Some(Self::OpenCL),
"cuda" => Some(Self::CUDA),
"assembler" | "assembler-with-cpp" => Some(Self::Asm),
"ir" | "llvm-ir" => Some(Self::LLVM_IR),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::C => "c",
Self::CXX => "c++",
Self::ObjC => "objective-c",
Self::ObjCXX => "objective-c++",
Self::OpenCL => "opencl",
Self::CUDA => "cuda",
Self::Asm => "assembler",
Self::LLVM_IR => "ir",
}
}
}
impl fmt::Display for InputLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum OptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl OptLevel {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"0" => Some(Self::O0),
"1" => Some(Self::O1),
"2" => Some(Self::O2),
"3" => Some(Self::O3),
"s" => Some(Self::Os),
"z" => Some(Self::Oz),
_ => None,
}
}
pub fn to_arg(&self) -> &'static str {
match self {
Self::O0 => "-O0",
Self::O1 => "-O1",
Self::O2 => "-O2",
Self::O3 => "-O3",
Self::Os => "-Os",
Self::Oz => "-Oz",
}
}
pub fn is_optimizing(&self) -> bool {
!matches!(self, Self::O0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DebugInfoKind {
None,
LineTablesOnly,
Limited,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
PreprocessedSource,
Assembly,
Object,
LLVM_IR,
LLVM_BC,
Executable,
SharedLib,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrontendActionKind {
PreprocessOnly,
SyntaxOnly,
EmitLLVM,
EmitBC,
EmitAssembly,
EmitObj,
EmitExecutable,
GeneratePCH,
UsePCH,
GenerateModule,
ASTDump,
DumpTokens,
DumpRawTokens,
}
impl FrontendActionKind {
pub fn from_flag(flag: &str) -> Option<Self> {
match flag {
"-E" => Some(Self::PreprocessOnly),
"-fsyntax-only" => Some(Self::SyntaxOnly),
"-emit-llvm" => Some(Self::EmitLLVM),
"-emit-llvm-bc" => Some(Self::EmitBC),
"-S" => Some(Self::EmitAssembly),
"-c" => Some(Self::EmitObj),
"-dump-ast" => Some(Self::ASTDump),
"-dump-tokens" => Some(Self::DumpTokens),
"-dump-raw-tokens" => Some(Self::DumpRawTokens),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct FileEntry {
pub id: FileID,
pub path: PathBuf,
pub name: String,
pub buffer: Option<String>,
pub size: u64,
pub mod_time: Option<SystemTime>,
pub is_virtual: bool,
pub is_valid: bool,
pub is_system: bool,
pub is_main_file: bool,
}
impl FileEntry {
pub fn new(id: FileID, path: PathBuf, name: String) -> Self {
let mut entry = Self {
id,
path,
name,
buffer: None,
size: 0,
mod_time: None,
is_virtual: false,
is_valid: true,
is_system: false,
is_main_file: false,
};
entry.stat();
entry
}
pub fn virtual_file(id: FileID, name: String, content: String) -> Self {
let size = content.len() as u64;
Self {
id,
path: PathBuf::from(&name),
name,
buffer: Some(content),
size,
mod_time: None,
is_virtual: true,
is_valid: true,
is_system: false,
is_main_file: false,
}
}
fn stat(&mut self) {
if self.is_virtual {
return;
}
if let Ok(meta) = fs::metadata(&self.path) {
self.size = meta.len();
self.mod_time = meta.modified().ok();
} else {
self.is_valid = false;
}
}
pub fn get_buffer(&mut self) -> Result<&str, String> {
if self.buffer.is_none() {
if self.is_virtual {
return Err(format!("virtual file '{}' has no buffer", self.name));
}
let content = fs::read_to_string(&self.path)
.map_err(|e| format!("cannot read file '{}': {}", self.path.display(), e))?;
self.size = content.len() as u64;
self.buffer = Some(content);
}
Ok(self.buffer.as_ref().unwrap())
}
pub fn is_stale(&self) -> bool {
if self.is_virtual {
return false;
}
match fs::metadata(&self.path) {
Ok(meta) => self.mod_time != meta.modified().ok(),
Err(_) => true,
}
}
pub fn invalidate(&mut self) {
self.buffer = None;
}
}
#[derive(Debug)]
pub struct FileManager {
cache: HashMap<PathBuf, FileEntry>,
files_by_id: HashMap<FileID, PathBuf>,
next_file_id: u64,
include_paths: Vec<PathBuf>,
system_include_paths: Vec<PathBuf>,
working_dir: PathBuf,
overlay: HashMap<String, String>,
system_header_paths: Vec<PathBuf>,
}
impl FileManager {
pub fn new() -> Self {
let working_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
Self {
cache: HashMap::new(),
files_by_id: HashMap::new(),
next_file_id: 1,
include_paths: Vec::new(),
system_include_paths: Vec::new(),
working_dir,
overlay: HashMap::new(),
system_header_paths: Vec::new(),
}
}
pub fn set_working_dir(&mut self, dir: PathBuf) {
self.working_dir = dir;
}
pub fn add_include_path(&mut self, path: PathBuf) {
self.include_paths.push(path);
}
pub fn add_system_include_path(&mut self, path: PathBuf) {
self.system_include_paths.push(path);
}
pub fn set_include_paths(&mut self, paths: Vec<PathBuf>) {
self.include_paths = paths;
}
pub fn set_system_include_paths(&mut self, paths: Vec<PathBuf>) {
self.system_include_paths = paths;
}
pub fn mark_system_header(&mut self, path: PathBuf) {
self.system_header_paths.push(path);
}
pub fn add_overlay(&mut self, filename: String, contents: String) {
self.overlay.insert(filename, contents);
}
pub fn get_file(&mut self, path: &Path) -> Result<FileID, String> {
let overlay_contents = self.overlay.get(path.to_str().unwrap_or("")).cloned();
if let Some(contents) = overlay_contents {
let fid = self.next_file_id();
let entry = FileEntry::virtual_file(fid, path.to_string_lossy().to_string(), contents);
self.cache.insert(path.to_path_buf(), entry);
self.files_by_id.insert(fid, path.to_path_buf());
return Ok(fid);
}
let abs = if path.is_absolute() {
path.to_path_buf()
} else {
self.working_dir.join(path)
};
let canonical = fs::canonicalize(&abs).unwrap_or(abs);
if let Some(entry) = self.cache.get(&canonical) {
if !entry.is_stale() {
return Ok(entry.id);
}
self.cache.get_mut(&canonical).unwrap().invalidate();
}
let fid = self.next_file_id();
let name = canonical.to_string_lossy().to_string();
let mut entry = FileEntry::new(fid, canonical.clone(), name);
entry.is_system = self.is_system_header(&canonical);
let _ = entry.get_buffer(); self.cache.insert(canonical.clone(), entry);
self.files_by_id.insert(fid, canonical);
Ok(fid)
}
pub fn get_virtual_file(&mut self, name: &str, content: String) -> Result<FileID, String> {
let fid = self.next_file_id();
let entry = FileEntry::virtual_file(fid, name.to_string(), content);
let path = PathBuf::from(name);
self.cache.insert(path.clone(), entry);
self.files_by_id.insert(fid, path);
Ok(fid)
}
pub fn get_file_by_id(&self, fid: FileID) -> Option<&FileEntry> {
self.files_by_id.get(&fid).and_then(|p| self.cache.get(p))
}
pub fn get_file_by_id_mut(&mut self, fid: FileID) -> Option<&mut FileEntry> {
if let Some(path) = self.files_by_id.get(&fid).cloned() {
self.cache.get_mut(&path)
} else {
None
}
}
pub fn get_buffer(&mut self, fid: FileID) -> Result<String, String> {
let path = self
.files_by_id
.get(&fid)
.cloned()
.ok_or(format!("unknown file ID {}", fid.0))?;
let entry = self.cache.get_mut(&path).unwrap();
let buf = entry.get_buffer()?;
Ok(buf.to_string())
}
pub fn find_include(&mut self, filename: &str, relative_to: Option<&Path>) -> Option<FileID> {
if let Some(base) = relative_to {
if let Some(parent) = base.parent() {
let candidate = parent.join(filename);
if let Ok(fid) = self.get_file(&candidate) {
return Some(fid);
}
}
}
let candidate = self.working_dir.join(filename);
if let Ok(fid) = self.get_file(&candidate) {
return Some(fid);
}
let user_dirs: Vec<PathBuf> = self.include_paths.iter().cloned().collect();
for dir in &user_dirs {
let candidate = dir.join(filename);
if let Ok(fid) = self.get_file(&candidate) {
return Some(fid);
}
}
let system_dirs: Vec<PathBuf> = self.system_include_paths.iter().cloned().collect();
for dir in &system_dirs {
let candidate = dir.join(filename);
if let Ok(fid) = self.get_file(&candidate) {
return Some(fid);
}
}
None
}
fn is_system_header(&self, path: &Path) -> bool {
self.system_header_paths
.iter()
.any(|sp| path.starts_with(sp))
}
fn next_file_id(&mut self) -> FileID {
let id = FileID(self.next_file_id);
self.next_file_id += 1;
id
}
pub fn num_files(&self) -> usize {
self.cache.len()
}
pub fn clear(&mut self) {
self.cache.clear();
self.files_by_id.clear();
self.next_file_id = 1;
}
}
impl Default for FileManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FileID(pub u64);
impl fmt::Display for FileID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "fid:{}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct SourceLineEntry {
pub line: u32,
pub offset: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SourceLocation {
pub file_id: u64,
pub offset: u32,
pub line: u32,
pub column: u32,
}
impl SourceLocation {
pub fn invalid() -> Self {
Self {
file_id: 0,
offset: 0,
line: 0,
column: 0,
}
}
pub fn is_valid(&self) -> bool {
self.file_id != 0
}
pub fn is_in_main_file(&self) -> bool {
self.file_id == 1
}
pub fn to_display(&self, sm: &SourceManager) -> String {
if !self.is_valid() {
return "<invalid loc>".to_string();
}
let name = sm
.get_file_by_id(FileID(self.file_id))
.map(|e| e.name.clone())
.unwrap_or_else(|| format!("<fid:{}>", self.file_id));
format!("{}:{}:{}", name, self.line, self.column)
}
}
impl fmt::Display for SourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_valid() {
write!(f, "<{}:{}:{}>", self.file_id, self.line, self.column)
} else {
write!(f, "<invalid>")
}
}
}
impl Default for SourceLocation {
fn default() -> Self {
Self::invalid()
}
}
#[derive(Debug)]
pub struct SourceManager {
file_manager: FileManager,
main_file_id: Option<FileID>,
line_tables: HashMap<FileID, Vec<SourceLineEntry>>,
buffers: HashMap<FileID, String>,
line_tables_built: HashMap<FileID, bool>,
}
impl SourceManager {
pub fn new(file_manager: FileManager) -> Self {
Self {
file_manager,
main_file_id: None,
line_tables: HashMap::new(),
buffers: HashMap::new(),
line_tables_built: HashMap::new(),
}
}
pub fn set_main_file(&mut self, fid: FileID) {
self.main_file_id = Some(fid);
}
pub fn main_file_id(&self) -> Option<FileID> {
self.main_file_id
}
pub fn load_file(&mut self, path: &Path) -> Result<FileID, String> {
let fid = self.file_manager.get_file(path)?;
if self.main_file_id.is_none() {
self.main_file_id = Some(fid);
}
let entry = self.file_manager.get_file_by_id(fid).unwrap();
let is_main = self.main_file_id == Some(fid);
let marker = if is_main {
self.file_manager
.get_file_by_id_mut(fid)
.unwrap()
.is_main_file = true;
};
let _ = marker;
self.ensure_buffer(fid)?;
self.build_line_table(fid)?;
Ok(fid)
}
pub fn load_virtual_file(&mut self, name: &str, content: String) -> Result<FileID, String> {
let fid = self.file_manager.get_virtual_file(name, content)?;
if self.main_file_id.is_none() {
self.main_file_id = Some(fid);
}
self.ensure_buffer(fid)?;
self.build_line_table(fid)?;
Ok(fid)
}
pub fn file_manager(&self) -> &FileManager {
&self.file_manager
}
pub fn file_manager_mut(&mut self) -> &mut FileManager {
&mut self.file_manager
}
fn ensure_buffer(&mut self, fid: FileID) -> Result<(), String> {
if !self.buffers.contains_key(&fid) {
let buf = self.file_manager.get_buffer(fid)?;
self.buffers.insert(fid, buf);
}
Ok(())
}
fn build_line_table(&mut self, fid: FileID) -> Result<(), String> {
if self.line_tables_built.get(&fid) == Some(&true) {
return Ok(());
}
let buffer = self
.buffers
.get(&fid)
.ok_or(format!("no buffer for file {}", fid))?
.clone();
let mut lines = Vec::new();
lines.push(SourceLineEntry { line: 1, offset: 0 });
let mut offset = 0u32;
for ch in buffer.chars() {
if ch == '\n' {
lines.push(SourceLineEntry {
line: lines.len() as u32 + 1,
offset: offset + 1,
});
}
offset += ch.len_utf8() as u32;
}
self.line_tables.insert(fid, lines);
self.line_tables_built.insert(fid, true);
Ok(())
}
pub fn get_location(&self, fid: FileID, offset: u32) -> SourceLocation {
let line_table = match self.line_tables.get(&fid) {
Some(lt) => lt,
None => {
return SourceLocation {
file_id: fid.0,
offset,
line: 0,
column: 0,
};
}
};
let idx = match line_table.binary_search_by(|entry| entry.offset.cmp(&offset)) {
Ok(i) => i,
Err(i) => i.saturating_sub(1),
};
let entry = &line_table[idx];
let line = entry.line;
let column = (offset - entry.offset) + 1;
SourceLocation {
file_id: fid.0,
offset,
line,
column,
}
}
pub fn get_source_text(&self, start: SourceLocation, end: SourceLocation) -> String {
if start.file_id != end.file_id || start.file_id == 0 {
return String::new();
}
let fid = FileID(start.file_id);
if let Some(buffer) = self.buffers.get(&fid) {
let s = start.offset as usize;
let e = end.offset as usize;
if s <= e && e <= buffer.len() {
return buffer[s..e].to_string();
}
}
String::new()
}
pub fn get_line(&self, loc: SourceLocation) -> Option<String> {
if loc.file_id == 0 {
return None;
}
let fid = FileID(loc.file_id);
let buffer = self.buffers.get(&fid)?;
let line_table = self.line_tables.get(&fid)?;
let idx = match line_table.binary_search_by(|entry| entry.offset.cmp(&loc.offset)) {
Ok(i) => i,
Err(i) => i.saturating_sub(1),
};
let line_start = line_table[idx].offset as usize;
let line_end = if idx + 1 < line_table.len() {
line_table[idx + 1].offset as usize
} else {
buffer.len()
};
let mut line = buffer[line_start..line_end].to_string();
if line.ends_with('\n') {
line.pop();
}
if line.ends_with('\r') {
line.pop();
}
Some(line)
}
pub fn get_file_by_id(&self, fid: FileID) -> Option<&FileEntry> {
self.file_manager.get_file_by_id(fid)
}
pub fn get_buffer(&self, fid: FileID) -> Option<&str> {
self.buffers.get(&fid).map(|s| s.as_str())
}
pub fn num_files(&self) -> usize {
self.file_manager.num_files()
}
}
pub trait ASTConsumer: fmt::Debug {
fn handle_translation_unit_begin(&mut self, _tu: &TranslationUnit) {}
fn handle_top_level_decl(&mut self, decl: &Decl);
fn handle_translation_unit_end(&mut self, _tu: &TranslationUnit) {}
fn finish(&mut self) {}
fn print_stats(&self) {}
}
#[derive(Debug)]
pub struct SyntaxOnlyConsumer;
impl ASTConsumer for SyntaxOnlyConsumer {
fn handle_top_level_decl(&mut self, _decl: &Decl) {}
}
#[derive(Debug)]
pub struct ASTDumpConsumer;
impl ASTConsumer for ASTDumpConsumer {
fn handle_top_level_decl(&mut self, decl: &Decl) {
println!("{}", decl);
}
}
pub struct CodeGenConsumer<'a> {
pub codegen: ClangCodeGen<'a>,
}
impl<'a> fmt::Debug for CodeGenConsumer<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CodeGenConsumer")
.field("codegen", &"<ClangCodeGen>")
.finish()
}
}
impl<'a> CodeGenConsumer<'a> {
pub fn new(codegen: ClangCodeGen<'a>) -> Self {
Self { codegen }
}
}
impl ASTConsumer for CodeGenConsumer<'_> {
fn handle_top_level_decl(&mut self, _decl: &Decl) {
}
fn handle_translation_unit_begin(&mut self, _tu: &TranslationUnit) {}
fn handle_translation_unit_end(&mut self, tu: &TranslationUnit) {
let _ = self.codegen.compile(tu);
}
}
pub struct MultiplexConsumer {
pub consumers: Vec<Box<dyn ASTConsumer>>,
}
impl fmt::Debug for MultiplexConsumer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MultiplexConsumer")
.field("num_consumers", &self.consumers.len())
.finish()
}
}
impl MultiplexConsumer {
pub fn new(consumers: Vec<Box<dyn ASTConsumer>>) -> Self {
Self { consumers }
}
pub fn add(&mut self, consumer: Box<dyn ASTConsumer>) {
self.consumers.push(consumer);
}
}
impl ASTConsumer for MultiplexConsumer {
fn handle_translation_unit_begin(&mut self, tu: &TranslationUnit) {
for c in &mut self.consumers {
c.handle_translation_unit_begin(tu);
}
}
fn handle_top_level_decl(&mut self, decl: &Decl) {
for c in &mut self.consumers {
c.handle_top_level_decl(decl);
}
}
fn handle_translation_unit_end(&mut self, tu: &TranslationUnit) {
for c in &mut self.consumers {
c.handle_translation_unit_end(tu);
}
}
fn finish(&mut self) {
for c in &mut self.consumers {
c.finish();
}
}
fn print_stats(&self) {
for c in &self.consumers {
c.print_stats();
}
}
}
#[derive(Debug)]
pub enum FrontendAction {
PreprocessOnly(PreprocessOnlyAction),
SyntaxOnly(SyntaxOnlyAction),
EmitLLVM(EmitLLVMAction),
EmitBC(EmitBCAction),
EmitAssembly(EmitAssemblyAction),
EmitObj(EmitObjAction),
ASTDump(ASTDumpAction),
DumpTokens(DumpTokensAction),
}
impl FrontendAction {
pub fn for_kind(kind: FrontendActionKind) -> Self {
match kind {
FrontendActionKind::PreprocessOnly => {
Self::PreprocessOnly(PreprocessOnlyAction { output: None })
}
FrontendActionKind::SyntaxOnly => Self::SyntaxOnly(SyntaxOnlyAction),
FrontendActionKind::EmitLLVM => Self::EmitLLVM(EmitLLVMAction { output: None }),
FrontendActionKind::EmitBC => Self::EmitBC(EmitBCAction),
FrontendActionKind::EmitAssembly => {
Self::EmitAssembly(EmitAssemblyAction { output: None })
}
FrontendActionKind::EmitObj => Self::EmitObj(EmitObjAction::new()),
FrontendActionKind::ASTDump => Self::ASTDump(ASTDumpAction),
FrontendActionKind::DumpTokens => Self::DumpTokens(DumpTokensAction { output: None }),
_ => Self::EmitObj(EmitObjAction::new()),
}
}
pub fn begin_source_file(
&mut self,
compiler: &mut CompilerInstance,
) -> Result<(), Vec<String>> {
match self {
Self::PreprocessOnly(action) => action.begin_source_file(compiler),
Self::SyntaxOnly(action) => action.begin_source_file(compiler),
Self::EmitLLVM(action) => action.begin_source_file(compiler),
Self::EmitBC(action) => action.begin_source_file(compiler),
Self::EmitAssembly(action) => action.begin_source_file(compiler),
Self::EmitObj(action) => action.begin_source_file(compiler),
Self::ASTDump(action) => action.begin_source_file(compiler),
Self::DumpTokens(action) => action.begin_source_file(compiler),
}
}
pub fn execute(&mut self) -> Result<(), Vec<String>> {
match self {
Self::PreprocessOnly(action) => action.execute(),
Self::SyntaxOnly(action) => action.execute(),
Self::EmitLLVM(action) => action.execute(),
Self::EmitBC(action) => action.execute(),
Self::EmitAssembly(action) => action.execute(),
Self::EmitObj(action) => action.execute(),
Self::ASTDump(action) => action.execute(),
Self::DumpTokens(action) => action.execute(),
}
}
pub fn end_source_file(&mut self) -> Result<(), Vec<String>> {
match self {
Self::PreprocessOnly(action) => action.end_source_file(),
Self::SyntaxOnly(action) => action.end_source_file(),
Self::EmitLLVM(action) => action.end_source_file(),
Self::EmitBC(action) => action.end_source_file(),
Self::EmitAssembly(action) => action.end_source_file(),
Self::EmitObj(action) => action.end_source_file(),
Self::ASTDump(action) => action.end_source_file(),
Self::DumpTokens(action) => action.end_source_file(),
}
}
pub fn get_output(&self) -> Option<String> {
match self {
Self::PreprocessOnly(action) => action.get_output(),
Self::EmitLLVM(action) => action.get_output(),
Self::EmitAssembly(action) => action.get_output(),
Self::DumpTokens(action) => action.get_output(),
_ => None,
}
}
pub fn get_object_bytes(&self) -> Option<Vec<u8>> {
match self {
Self::EmitObj(action) => action.get_object_bytes(),
_ => None,
}
}
}
trait ActionImpl {
fn begin_source_file(&mut self, compiler: &mut CompilerInstance) -> Result<(), Vec<String>>;
fn execute(&mut self) -> Result<(), Vec<String>>;
fn end_source_file(&mut self) -> Result<(), Vec<String>>;
fn get_output(&self) -> Option<String> {
None
}
}
#[derive(Debug)]
pub struct PreprocessOnlyAction {
output: Option<String>,
}
impl ActionImpl for PreprocessOnlyAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
self.output = Some(String::new());
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn get_output(&self) -> Option<String> {
self.output.clone()
}
}
#[derive(Debug)]
pub struct SyntaxOnlyAction;
impl ActionImpl for SyntaxOnlyAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
}
#[derive(Debug)]
pub struct EmitLLVMAction {
output: Option<String>,
}
impl ActionImpl for EmitLLVMAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
self.output = Some(String::new());
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn get_output(&self) -> Option<String> {
self.output.clone()
}
}
#[derive(Debug)]
pub struct EmitBCAction;
impl ActionImpl for EmitBCAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
}
#[derive(Debug)]
pub struct EmitAssemblyAction {
output: Option<String>,
}
impl ActionImpl for EmitAssemblyAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
self.output = Some(String::new());
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn get_output(&self) -> Option<String> {
self.output.clone()
}
}
#[derive(Debug)]
pub struct EmitObjAction {
module: Option<Module>,
}
impl EmitObjAction {
pub fn new() -> Self {
Self { module: None }
}
pub fn set_module(&mut self, module: Module) {
self.module = Some(module);
}
pub fn get_object_bytes(&self) -> Option<Vec<u8>> {
Some(Vec::new())
}
}
impl ActionImpl for EmitObjAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
}
#[derive(Debug)]
pub struct ASTDumpAction;
impl ActionImpl for ASTDumpAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
}
#[derive(Debug)]
pub struct DumpTokensAction {
output: Option<String>,
}
impl ActionImpl for DumpTokensAction {
fn begin_source_file(&mut self, _compiler: &mut CompilerInstance) -> Result<(), Vec<String>> {
self.output = Some(String::new());
Ok(())
}
fn execute(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn end_source_file(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn get_output(&self) -> Option<String> {
self.output.clone()
}
}
#[derive(Debug)]
pub struct CompilerInstance {
pub opts: CompilerOptions,
pub diagnostics: DiagnosticEngine,
pub source_manager: SourceManager,
source_file_begun: bool,
has_ast: bool,
translation_unit: Option<TranslationUnit>,
output_buffer: Option<String>,
}
impl CompilerInstance {
pub fn new(invocation: CompilerInvocation) -> Self {
let mut diag_engine = DiagnosticEngine::new(ClangSourceManager::new());
diag_engine.set_warnings_as_errors(invocation.opts.werror);
if !invocation.opts.warnings {
diag_engine.set_ignore_warnings(true);
}
let mut fm = FileManager::new();
fm.set_include_paths(invocation.opts.includes.clone());
fm.set_system_include_paths(invocation.opts.system_includes.clone());
Self {
opts: invocation.opts,
diagnostics: diag_engine,
source_manager: SourceManager::new(fm),
source_file_begun: false,
has_ast: false,
translation_unit: None,
output_buffer: None,
}
}
pub fn create_default() -> Self {
Self::new(CompilerInvocation::default())
}
pub fn options(&self) -> &CompilerOptions {
&self.opts
}
pub fn options_mut(&mut self) -> &mut CompilerOptions {
&mut self.opts
}
pub fn target_triple(&self) -> &str {
&self.opts.target_triple
}
pub fn begin_source_file(&mut self, action: &mut FrontendAction) -> Result<(), Vec<String>> {
if self.source_file_begun {
return Err(vec!["source file already begun".to_string()]);
}
self.source_file_begun = true;
action.begin_source_file(self)?;
if self.opts.preprocess_only {
self.output_buffer = Some(self.run_preprocessor_only()?);
} else {
self.translation_unit = Some(self.run_parse_and_sema()?);
self.has_ast = true;
}
Ok(())
}
pub fn end_source_file(&mut self) -> Result<(), Vec<String>> {
if !self.source_file_begun {
return Ok(());
}
self.source_file_begun = false;
Ok(())
}
pub fn run_pipeline(
&mut self,
virtual_name: &str,
source: &str,
) -> Result<Module, Vec<String>> {
let _fid = self
.source_manager
.load_virtual_file(virtual_name, source.to_string())
.map_err(|e| vec![e])?;
let tokens = lexer::tokenize(source, self.opts.standard);
let _pp = Preprocessor::new(self.opts.standard);
if self.opts.preprocess_only {
return Err(vec!["preprocessing only; no module produced".to_string()]);
}
let mut p = parser::Parser::new(&tokens, self.opts.standard);
let tu = p.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(self.opts.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());
}
if self.opts.syntax_only {
return Err(vec!["syntax-only; no module produced".to_string()]);
}
let mut cg = ClangCodeGen::new("input", &self.opts.target_triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen error: {}", err))
.collect::<Vec<_>>()
})?;
let module = cg.module;
Ok(module)
}
fn run_preprocessor_only(&mut self) -> Result<String, Vec<String>> {
let fid = self
.source_manager
.main_file_id()
.ok_or_else(|| vec!["no main file for preprocessing".to_string()])?;
let source = self
.source_manager
.get_buffer(fid)
.map(|s| s.to_string())
.ok_or_else(|| vec!["no buffer for main file".to_string()])?;
let _pp = Preprocessor::new(self.opts.standard);
Ok(source)
}
fn run_parse_and_sema(&mut self) -> Result<TranslationUnit, Vec<String>> {
let fid = self
.source_manager
.main_file_id()
.ok_or_else(|| vec!["no main file for parsing".to_string()])?;
let source = self
.source_manager
.get_buffer(fid)
.map(|s| s.to_string())
.ok_or_else(|| vec!["no buffer for main file".to_string()])?;
let tokens = lexer::tokenize(&source, self.opts.standard);
let mut parser = parser::Parser::new(&tokens, self.opts.standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(self.opts.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());
}
Ok(tu)
}
pub fn execute_codegen(&mut self) -> Result<(), Vec<String>> {
let standard = self.opts.standard;
let triple = self.opts.target_triple.clone();
let fid = self
.source_manager
.main_file_id()
.ok_or_else(|| vec!["no main file for codegen".to_string()])?;
let source = self
.source_manager
.get_buffer(fid)
.ok_or_else(|| vec!["no buffer for main file".to_string()])?;
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|e| format!("parse error: {}", e))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(standard);
sema.analyze(&tu).map_err(|e| {
e.iter()
.map(|e| format!("sema error: {}", e))
.collect::<Vec<_>>()
})?;
if sema.has_errors() {
return Err(sema.errors.clone());
}
let mut cg = ClangCodeGen::new("input", &triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|e| format!("codegen error: {}", e))
.collect::<Vec<_>>()
})?;
Ok(())
}
pub fn get_translation_unit(&self) -> Option<&TranslationUnit> {
self.translation_unit.as_ref()
}
pub fn report(&mut self, id: DiagID, _severity: DiagSeverity, _msg: &str) {
let mut builder = self.diagnostics.report(id);
builder.emit();
}
}
#[derive(Debug, Clone)]
pub struct CompilerInvocation {
pub opts: CompilerOptions,
pub args: Vec<String>,
pub program_name: String,
}
impl CompilerInvocation {
pub fn from_args<I, S>(args: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let raw: Vec<String> = args.into_iter().map(|s| s.as_ref().to_string()).collect();
let program_name = raw.first().cloned().unwrap_or_else(|| "clang".to_string());
let mut opts = CompilerOptions::default();
let mut i = 1;
let mut expanded = vec![program_name.clone()];
while i < raw.len() {
let arg = &raw[i];
if arg.starts_with('@') {
let filename = &arg[1..];
if let Ok(contents) = fs::read_to_string(filename) {
for line in contents.lines() {
let line = line.trim();
if !line.is_empty() && !line.starts_with('#') {
expanded.push(line.to_string());
}
}
}
} else {
expanded.push(arg.clone());
}
i += 1;
}
i = 1;
while i < expanded.len() {
let arg = &expanded[i];
match arg.as_str() {
"-E" => {
opts.action = FrontendActionKind::PreprocessOnly;
opts.preprocess_only = true;
}
"-fsyntax-only" => {
opts.action = FrontendActionKind::SyntaxOnly;
opts.syntax_only = true;
}
"-S" => opts.action = FrontendActionKind::EmitAssembly,
"-c" => opts.action = FrontendActionKind::EmitObj,
"-emit-llvm" => {
opts.action = FrontendActionKind::EmitLLVM;
opts.emit_llvm = true;
}
"-emit-llvm-bc" => {
opts.action = FrontendActionKind::EmitBC;
opts.emit_bc = true;
}
"-dump-ast" => opts.action = FrontendActionKind::ASTDump,
"-dump-tokens" => opts.action = FrontendActionKind::DumpTokens,
"-o" => {
i += 1;
if i < expanded.len() {
opts.output_file = Some(PathBuf::from(&expanded[i]));
}
}
flag if flag.starts_with("-std=") => {
let std = &flag[5..];
if let Some(cstd) = CLangStandard::from_str(std) {
opts.standard = cstd;
opts.gnu_extensions = cstd.is_gnu();
}
}
"-ansi" => opts.standard = CLangStandard::C89,
"-target" => {
i += 1;
if i < expanded.len() {
opts.target_triple = expanded[i].clone();
}
}
flag if flag.starts_with("-target=") => {
opts.target_triple = flag[8..].to_string();
}
"-march" => {
i += 1;
if i < expanded.len() {
opts.target_cpu = Some(expanded[i].clone());
}
}
flag if flag.starts_with("-march=") => {
opts.target_cpu = Some(flag[7..].to_string());
}
"-O0" => opts.opt_level = OptLevel::O0,
"-O1" => opts.opt_level = OptLevel::O1,
"-O2" => opts.opt_level = OptLevel::O2,
"-O3" => opts.opt_level = OptLevel::O3,
"-Os" => opts.opt_level = OptLevel::Os,
"-Oz" => opts.opt_level = OptLevel::Oz,
"-g" => opts.debug_info = true,
flag if flag.starts_with("-I") => {
let path = if flag.len() > 2 {
PathBuf::from(&flag[2..])
} else {
i += 1;
if i < expanded.len() {
PathBuf::from(&expanded[i])
} else {
PathBuf::new()
}
};
if !path.as_os_str().is_empty() {
opts.includes.push(path);
}
}
flag if flag.starts_with("-D") => {
let def = &flag[2..];
if let Some(eq) = def.find('=') {
let name = def[..eq].to_string();
let val = def[eq + 1..].to_string();
opts.defines.push((name, Some(val)));
} else {
opts.defines.push((def.to_string(), Some("1".to_string())));
}
}
flag if flag.starts_with("-U") => {
let undef = &flag[2..];
opts.undefines.push(undef.to_string());
}
"-w" => opts.warnings = false,
"-Wall" => opts.wall = true,
"-Wextra" => opts.wextra = true,
"-Wpedantic" | "-pedantic" => opts.pedantic = true,
"-Werror" => opts.werror = true,
flag if flag.starts_with("-Wno-") => {
opts.wno.push(flag[5..].to_string());
}
flag if flag.starts_with("-W")
&& !flag.starts_with("-Wall")
&& !flag.starts_with("-Wextra")
&& !flag.starts_with("-Werror")
&& !flag.starts_with("-Wno-")
&& !flag.starts_with("-Wpedantic") =>
{
opts.w.push(flag[2..].to_string());
}
"-nostdinc" => opts.nostdinc = true,
"-nostdinc++" => opts.nostdincpp = true,
"-fPIC" | "-fpic" => opts.pic = true,
"-fPIE" | "-fpie" => opts.pie = true,
"-v" | "--verbose" => opts.verbose = true,
"--version" => opts.print_version = true,
"--help" | "-help" => opts.print_help = true,
"-print-resource-dir" => opts.print_resource_dir = true,
"-print-search-dirs" => opts.print_search_dirs = true,
"-dumpmachine" => opts.print_target_triple = true,
"-x" => {
i += 1;
if i < expanded.len() {
if let Some(lang) = InputLanguage::from_str(&expanded[i]) {
opts.lang = lang;
}
}
}
"-include" => {
i += 1;
if i < expanded.len() {
let _ = expanded[i].clone();
}
}
flag if flag.starts_with("-fstack-protector") => {
opts.stack_protector = 1;
}
flag if flag.starts_with("-fstack-protector-strong") => {
opts.stack_protector = 2;
}
flag if flag.starts_with("-fstack-protector-all") => {
opts.stack_protector = 3;
}
flag if flag.starts_with("-fsanitize=") => {
let s = &flag[11..];
for part in s.split(',') {
opts.sanitize.push(part.to_string());
}
}
flag if flag.starts_with("-ftls-model=") => {
opts.tls_model = flag[12..].to_string();
}
flag if flag.starts_with("-f") => {
}
flag if flag.starts_with("-m") => {
}
arg if !arg.starts_with('-') => {
opts.input_files.push(PathBuf::from(arg));
}
_ => {
}
}
i += 1;
}
CompilerInvocation {
opts,
args: raw,
program_name,
}
}
pub fn default() -> Self {
Self {
opts: CompilerOptions::default(),
args: vec!["clang".to_string()],
program_name: "clang".to_string(),
}
}
pub fn output_file(&self) -> PathBuf {
if let Some(ref out) = self.opts.output_file {
return out.clone();
}
if let Some(input) = self.opts.input_files.first() {
let stem = input
.file_stem()
.unwrap_or_else(|| std::ffi::OsStr::new("a"));
let ext = match self.opts.action {
FrontendActionKind::PreprocessOnly => "i",
FrontendActionKind::EmitLLVM => "ll",
FrontendActionKind::EmitBC => "bc",
FrontendActionKind::EmitAssembly => "s",
FrontendActionKind::EmitObj => "o",
_ => "out",
};
let mut out = PathBuf::from(stem);
out.set_extension(ext);
return out;
}
PathBuf::from("a.out")
}
}
impl Default for CompilerInvocation {
fn default() -> Self {
Self {
opts: CompilerOptions::default(),
args: vec![],
program_name: "clang".to_string(),
}
}
}
#[derive(Debug)]
pub struct CompilationDriver {
pub compiler: CompilerInstance,
pub action: FrontendAction,
phases: CompilationPhases,
}
#[derive(Debug, Default)]
struct CompilationPhases {
preprocess_done: bool,
parse_done: bool,
sema_done: bool,
codegen_done: bool,
emit_done: bool,
}
impl CompilationDriver {
pub fn new(compiler: CompilerInstance, action: FrontendAction) -> Self {
Self {
compiler,
action,
phases: CompilationPhases::default(),
}
}
pub fn from_args<I, S>(args: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let invocation = CompilerInvocation::from_args(args);
let action = FrontendAction::for_kind(invocation.opts.action);
let compiler = CompilerInstance::new(invocation);
Self::new(compiler, action)
}
pub fn run(&mut self) -> Result<(), Vec<String>> {
self.preprocess()?;
self.parse()?;
self.sema()?;
self.codegen()?;
self.emit()?;
Ok(())
}
pub fn preprocess(&mut self) -> Result<(), Vec<String>> {
if self.phases.preprocess_done {
return Ok(());
}
if self.compiler.opts.preprocess_only {
let fid = self
.compiler
.source_manager
.main_file_id()
.ok_or_else(|| vec!["no main file for preprocessing".to_string()])?;
let source = self
.compiler
.source_manager
.get_buffer(fid)
.ok_or_else(|| vec!["no buffer for main file".to_string()])?;
let _pp = Preprocessor::new(self.compiler.opts.standard);
}
self.phases.preprocess_done = true;
Ok(())
}
pub fn parse(&mut self) -> Result<(), Vec<String>> {
if self.phases.parse_done {
return Ok(());
}
if self.compiler.opts.preprocess_only {
return Ok(());
}
let fid = self
.compiler
.source_manager
.main_file_id()
.ok_or_else(|| vec!["no main file for parsing".to_string()])?;
let source = self
.compiler
.source_manager
.get_buffer(fid)
.ok_or_else(|| vec!["no buffer for main file".to_string()])?;
let tokens = lexer::tokenize(source, self.compiler.opts.standard);
let mut parser = parser::Parser::new(&tokens, self.compiler.opts.standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse: {}", err))
.collect::<Vec<_>>()
})?;
self.compiler.translation_unit = Some(tu);
self.phases.parse_done = true;
Ok(())
}
pub fn sema(&mut self) -> Result<(), Vec<String>> {
if self.phases.sema_done {
return Ok(());
}
if self.compiler.opts.preprocess_only {
return Ok(());
}
let tu = self
.compiler
.translation_unit
.as_ref()
.ok_or_else(|| vec!["no AST for semantic analysis".to_string()])?;
let mut sema = Sema::new(self.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());
}
if self.compiler.opts.syntax_only {
return Ok(());
}
self.phases.sema_done = true;
Ok(())
}
pub fn codegen(&mut self) -> Result<(), Vec<String>> {
if self.phases.codegen_done {
return Ok(());
}
if self.compiler.opts.preprocess_only || self.compiler.opts.syntax_only {
return Ok(());
}
let tu = self
.compiler
.translation_unit
.as_ref()
.ok_or_else(|| vec!["no AST for codegen".to_string()])?;
let mut cg = ClangCodeGen::new("input", &self.compiler.opts.target_triple);
cg.compile(tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen: {}", err))
.collect::<Vec<_>>()
})?;
self.phases.codegen_done = true;
Ok(())
}
pub fn emit(&mut self) -> Result<(), Vec<String>> {
if self.phases.emit_done {
return Ok(());
}
self.phases.emit_done = true;
match &mut self.action {
FrontendAction::PreprocessOnly(a) => {
}
FrontendAction::SyntaxOnly(_) => {}
FrontendAction::EmitLLVM(a) => {
}
FrontendAction::EmitBC(_) => {
}
FrontendAction::EmitAssembly(_) => {
}
FrontendAction::EmitObj(_) => {
}
_ => {}
}
Ok(())
}
pub fn is_preprocess_done(&self) -> bool {
self.phases.preprocess_done
}
pub fn is_parse_done(&self) -> bool {
self.phases.parse_done
}
pub fn is_sema_done(&self) -> bool {
self.phases.sema_done
}
pub fn is_codegen_done(&self) -> bool {
self.phases.codegen_done
}
pub fn is_emit_done(&self) -> bool {
self.phases.emit_done
}
}
impl CompilerInstance {
pub fn create_diagnostics(&mut self) -> DiagnosticEngine {
let mut diag = DiagnosticEngine::new(ClangSourceManager::new());
if self.opts.werror {
diag.set_warnings_as_errors(true);
}
if !self.opts.warnings {
diag.set_ignore_warnings(true);
}
diag.set_error_limit(20);
self.diagnostics = diag;
DiagnosticEngine::new(ClangSourceManager::new())
}
pub fn create_preprocessor(&mut self) -> Preprocessor {
let mut pp = Preprocessor::new(self.opts.standard);
pp.define("__STDC__", "1");
match self.opts.standard {
CLangStandard::C89 => {
pp.define("__STDC_VERSION__", "199409L");
}
CLangStandard::C99 | CLangStandard::Gnu99 => {
pp.define("__STDC_VERSION__", "199901L");
}
CLangStandard::C11 | CLangStandard::Gnu11 => {
pp.define("__STDC_VERSION__", "201112L");
}
CLangStandard::C17 | CLangStandard::Gnu17 => {
pp.define("__STDC_VERSION__", "201710L");
}
CLangStandard::C23 => {
pp.define("__STDC_VERSION__", "202311L");
}
_ => {}
}
if self.opts.target_triple.contains("x86_64") {
pp.define("__x86_64__", "1");
pp.define("__amd64__", "1");
}
if self.opts.target_triple.contains("aarch64") {
pp.define("__aarch64__", "1");
}
if self.opts.target_triple.contains("linux") {
pp.define("__linux__", "1");
pp.define("__gnu_linux__", "1");
}
if self.opts.target_triple.contains("darwin") || self.opts.target_triple.contains("macos") {
pp.define("__APPLE__", "1");
}
if self.opts.target_triple.contains("windows") || self.opts.target_triple.contains("mingw")
{
pp.define("_WIN32", "1");
}
if self.opts.gnu_extensions {
pp.define("__GNUC__", "4");
pp.define("__GNUC_MINOR__", "2");
pp.define("__GNUC_PATCHLEVEL__", "1");
}
for (name, value) in &self.opts.defines {
let body: String = value.as_deref().unwrap_or("1").to_string();
pp.define(name, &body);
}
for path in &self.opts.includes {
if let Some(s) = path.to_str() {
pp.add_include_path(s);
}
}
for path in &self.opts.system_includes {
if let Some(s) = path.to_str() {
pp.add_include_path(s);
}
}
if self.opts.nostdinc {
}
pp
}
pub fn create_ast_context(&mut self) -> ASTContext {
let lang_opts = LangOptions {
standard: self.opts.standard,
gnu_extensions: self.opts.gnu_extensions,
ms_extensions: self.opts.ms_extensions,
trigraphs: self.opts.trigraphs,
digraphs: self.opts.digraphs,
cxx_mode: false,
};
let target_info = TargetInfo {
triple: self.opts.target_triple.clone(),
pointer_width: 64,
size_t_width: 64,
wchar_width: 32,
char_is_signed: true,
int_width: 32,
long_width: if self.opts.target_triple.contains("windows") {
32
} else {
64
},
long_long_width: 64,
short_width: 16,
float_width: 32,
double_width: 64,
long_double_width: if self.opts.target_triple.contains("x86_64") {
80
} else {
128
},
alignof_pointer: 8,
max_alignment: 16,
null_pointer_value: 0u64,
user_label_prefix: String::new(),
};
ASTContext::new(lang_opts, target_info)
}
pub fn create_sema(&mut self) -> Sema {
Sema::new(self.opts.standard)
}
pub fn create_codegen(&self) -> ClangCodeGen<'_> {
ClangCodeGen::new("input", &self.opts.target_triple)
}
pub fn create_codegen_named(&self, module_name: &str) -> ClangCodeGen<'_> {
ClangCodeGen::new(module_name, &self.opts.target_triple)
}
pub fn execute_action(&mut self, action: &mut FrontendAction) -> Result<(), Vec<String>> {
self.begin_source_file(action)?;
action.execute()?;
self.end_source_file()?;
Ok(())
}
pub fn execute_action_with_recovery(
&mut self,
action: &mut FrontendAction,
) -> (bool, Vec<String>) {
let mut errors: Vec<String> = Vec::new();
match self.begin_source_file(action) {
Ok(()) => {}
Err(e) => {
errors.extend(e);
return (false, errors);
}
}
match action.execute() {
Ok(()) => {}
Err(e) => {
errors.extend(e);
}
}
if let Err(e) = self.end_source_file() {
errors.extend(e);
}
(errors.is_empty(), errors)
}
}
#[derive(Debug, Clone)]
pub struct LangOptions {
pub standard: CLangStandard,
pub gnu_extensions: bool,
pub ms_extensions: bool,
pub trigraphs: bool,
pub digraphs: bool,
pub cxx_mode: bool,
}
impl Default for LangOptions {
fn default() -> Self {
Self {
standard: CLangStandard::C17,
gnu_extensions: false,
ms_extensions: false,
trigraphs: false,
digraphs: true,
cxx_mode: false,
}
}
}
#[derive(Debug, Clone)]
pub struct TargetInfo {
pub triple: String,
pub pointer_width: u32,
pub size_t_width: u32,
pub wchar_width: u32,
pub char_is_signed: bool,
pub int_width: u32,
pub long_width: u32,
pub long_long_width: u32,
pub short_width: u32,
pub float_width: u32,
pub double_width: u32,
pub long_double_width: u32,
pub alignof_pointer: u32,
pub max_alignment: u32,
pub null_pointer_value: u64,
pub user_label_prefix: String,
}
impl TargetInfo {
pub fn default_x86_64_linux() -> Self {
Self {
triple: "x86_64-unknown-linux-gnu".to_string(),
pointer_width: 64,
size_t_width: 64,
wchar_width: 32,
char_is_signed: true,
int_width: 32,
long_width: 64,
long_long_width: 64,
short_width: 16,
float_width: 32,
double_width: 64,
long_double_width: 80,
alignof_pointer: 8,
max_alignment: 16,
null_pointer_value: 0,
user_label_prefix: String::new(),
}
}
pub fn default_aarch64_linux() -> Self {
Self {
triple: "aarch64-unknown-linux-gnu".to_string(),
pointer_width: 64,
size_t_width: 64,
wchar_width: 32,
char_is_signed: false,
int_width: 32,
long_width: 64,
long_long_width: 64,
short_width: 16,
float_width: 32,
double_width: 64,
long_double_width: 128,
alignof_pointer: 8,
max_alignment: 16,
null_pointer_value: 0,
user_label_prefix: String::new(),
}
}
pub fn get_type_width(&self, ty: &str) -> Option<u32> {
match ty {
"char" => Some(8),
"short" => Some(self.short_width),
"int" => Some(self.int_width),
"long" => Some(self.long_width),
"long long" => Some(self.long_long_width),
"float" => Some(self.float_width),
"double" => Some(self.double_width),
"long double" => Some(self.long_double_width),
"pointer" | "size_t" => Some(self.pointer_width),
"wchar_t" => Some(self.wchar_width),
_ => None,
}
}
pub fn get_type_align(&self, ty: &str) -> Option<u32> {
let width = self.get_type_width(ty)?;
Some((width + 7) / 8)
}
pub fn pointer_width_bytes(&self) -> u32 {
self.pointer_width / 8
}
pub fn is_little_endian(&self) -> bool {
true
}
}
impl Default for TargetInfo {
fn default() -> Self {
Self::default_x86_64_linux()
}
}
#[derive(Debug, Clone)]
pub struct ASTContext {
pub lang_opts: LangOptions,
pub target_info: TargetInfo,
pub types: Vec<QualType>,
pub type_names: Vec<String>,
pub builtin_types_initialized: bool,
}
impl ASTContext {
pub fn new(lang_opts: LangOptions, target_info: TargetInfo) -> Self {
let mut ctx = Self {
lang_opts,
target_info,
types: Vec::new(),
type_names: Vec::new(),
builtin_types_initialized: false,
};
ctx.initialize_builtin_types();
ctx
}
fn initialize_builtin_types(&mut self) {
if self.builtin_types_initialized {
return;
}
self.builtin_types_initialized = true;
self.types.push(QualType::void());
self.type_names.push("void".to_string());
self.types.push(QualType::char_type());
self.type_names.push("char".to_string());
self.types.push(QualType::schar());
self.type_names.push("signed char".to_string());
self.types.push(QualType::uchar());
self.type_names.push("unsigned char".to_string());
self.types.push(QualType::short_type());
self.type_names.push("short".to_string());
self.types.push(QualType::ushort());
self.type_names.push("unsigned short".to_string());
self.types.push(QualType::int());
self.type_names.push("int".to_string());
self.types.push(QualType::uint());
self.type_names.push("unsigned int".to_string());
self.types.push(QualType::long_type());
self.type_names.push("long".to_string());
self.types.push(QualType::ulong());
self.type_names.push("unsigned long".to_string());
self.types.push(QualType::long_long());
self.type_names.push("long long".to_string());
self.types.push(QualType::ulong_long());
self.type_names.push("unsigned long long".to_string());
self.types.push(QualType::float_type());
self.type_names.push("float".to_string());
self.types.push(QualType::double_type());
self.type_names.push("double".to_string());
self.types.push(QualType::long_double());
self.type_names.push("long double".to_string());
}
pub fn lookup_type(&self, name: &str) -> Option<&QualType> {
self.type_names
.iter()
.position(|n| n == name)
.map(|i| &self.types[i])
}
pub fn get_type_size(&self, ty: &QualType) -> u32 {
if ty.is_void() {
return 0;
}
if ty.is_pointer() {
return self.target_info.pointer_width;
}
if ty.is_integer() {
if ty.is_char() {
return 8;
}
if ty.is_short() {
return self.target_info.short_width;
}
if ty.is_int() {
return self.target_info.int_width;
}
if ty.is_long() {
return self.target_info.long_width;
}
if ty.is_long_long() {
return self.target_info.long_long_width;
}
return 32;
}
if ty.is_floating() {
if ty.is_float() {
return self.target_info.float_width;
}
if ty.is_double() {
return self.target_info.double_width;
}
return self.target_info.long_double_width;
}
32
}
pub fn get_type_align(&self, ty: &QualType) -> u32 {
let size = self.get_type_size(ty);
if size >= 128 {
128
} else if size >= 64 {
64
} else if size >= 32 {
32
} else if size >= 16 {
16
} else {
8
}
}
}
impl Default for ASTContext {
fn default() -> Self {
Self::new(LangOptions::default(), TargetInfo::default())
}
}
pub fn parse_ast(
consumer: &mut dyn ASTConsumer,
source: &str,
standard: CLangStandard,
) -> Result<TranslationUnit, Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
consumer.handle_translation_unit_begin(&tu);
for decl in &tu.decls {
consumer.handle_top_level_decl(decl);
}
consumer.handle_translation_unit_end(&tu);
Ok(tu)
}
pub fn parse_and_sema_ast(
consumer: &mut dyn ASTConsumer,
source: &str,
standard: CLangStandard,
) -> Result<TranslationUnit, Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(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());
}
consumer.handle_translation_unit_begin(&tu);
for decl in &tu.decls {
consumer.handle_top_level_decl(decl);
}
consumer.handle_translation_unit_end(&tu);
Ok(tu)
}
#[derive(Debug)]
pub struct CodeGenAction {
pub module: Option<Module>,
pub output_kind: OutputFormat,
pub triple: String,
pub output_file: Option<PathBuf>,
pub verify_module: bool,
pub diagnostics: Vec<String>,
pub succeeded: bool,
}
impl CodeGenAction {
pub fn new(output_kind: OutputFormat, triple: &str) -> Self {
Self {
module: None,
output_kind,
triple: triple.to_string(),
output_file: None,
verify_module: true,
diagnostics: Vec::new(),
succeeded: false,
}
}
pub fn set_output_file(&mut self, path: PathBuf) {
self.output_file = Some(path);
}
pub fn execute_action(
&mut self,
source: &str,
standard: CLangStandard,
) -> Result<(), Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
let errs: Vec<String> = e
.into_iter()
.map(|err| format!("parse error: {}", err))
.collect();
self.diagnostics.extend(errs.clone());
errs
})?;
let mut sema = Sema::new(standard);
sema.analyze(&tu).map_err(|e| {
let errs: Vec<String> = e.iter().map(|err| format!("sema error: {}", err)).collect();
self.diagnostics.extend(errs.clone());
errs
})?;
if sema.has_errors() {
self.diagnostics.extend(sema.errors.clone());
return Err(sema.errors.clone());
}
let mut cg = ClangCodeGen::new("input", &self.triple);
cg.compile(&tu).map_err(|e| {
let errs: Vec<String> = e
.iter()
.map(|err| format!("codegen error: {}", err))
.collect();
self.diagnostics.extend(errs.clone());
errs
})?;
if self.verify_module {
}
self.module = Some(cg.module);
self.succeeded = true;
match self.output_kind {
OutputFormat::LLVM_IR => {
if let Some(ref module) = self.module {
let _ir = format!("; ModuleID = '{}'\n", module.name);
}
}
OutputFormat::LLVM_BC => {
}
OutputFormat::Assembly => {
}
OutputFormat::Object => {
}
OutputFormat::Executable | OutputFormat::SharedLib => {
}
_ => {}
}
Ok(())
}
pub fn get_module(&self) -> Option<&Module> {
self.module.as_ref()
}
pub fn is_successful(&self) -> bool {
self.succeeded
}
pub fn get_diagnostics(&self) -> &[String] {
&self.diagnostics
}
}
#[derive(Debug)]
pub struct EmitObjActionFull {
pub triple: String,
pub output_file: PathBuf,
pub opt_level: OptLevel,
pub debug_info: bool,
pub object_bytes: Option<Vec<u8>>,
pub completed: bool,
}
impl EmitObjActionFull {
pub fn new(triple: &str, output_file: PathBuf, opt_level: OptLevel) -> Self {
Self {
triple: triple.to_string(),
output_file,
opt_level,
debug_info: false,
object_bytes: None,
completed: false,
}
}
pub fn set_debug_info(&mut self, debug: bool) {
self.debug_info = debug;
}
pub fn execute(
&mut self,
source: &str,
standard: CLangStandard,
) -> Result<Vec<u8>, Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(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 mut cg = ClangCodeGen::new("input", &self.triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen error: {}", err))
.collect::<Vec<_>>()
})?;
let obj = self.emit_object(&cg.module)?;
self.object_bytes = Some(obj.clone());
self.completed = true;
if !self.output_file.as_os_str().is_empty() {
if let Some(ref bytes) = self.object_bytes {
std::fs::write(&self.output_file, bytes)
.map_err(|e| vec![format!("failed to write object file: {}", e)])?;
}
}
Ok(obj)
}
fn emit_object(&self, _module: &Module) -> Result<Vec<u8>, Vec<String>> {
let mut obj = Vec::new();
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(&[2, 0]); obj.extend_from_slice(&[0x3e, 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]);
Ok(obj)
}
pub fn get_object_bytes(&self) -> Option<&[u8]> {
self.object_bytes.as_deref()
}
}
#[derive(Debug)]
pub struct EmitAssemblyActionFull {
pub triple: String,
pub output_file: PathBuf,
pub opt_level: OptLevel,
pub assembly_text: Option<String>,
pub completed: bool,
}
impl EmitAssemblyActionFull {
pub fn new(triple: &str, output_file: PathBuf, opt_level: OptLevel) -> Self {
Self {
triple: triple.to_string(),
output_file,
opt_level,
assembly_text: None,
completed: false,
}
}
pub fn execute(
&mut self,
source: &str,
standard: CLangStandard,
) -> Result<String, Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(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 mut cg = ClangCodeGen::new("input", &self.triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen error: {}", err))
.collect::<Vec<_>>()
})?;
let asm = self.emit_assembly(&cg.module)?;
self.assembly_text = Some(asm.clone());
self.completed = true;
if !self.output_file.as_os_str().is_empty() {
if let Some(ref text) = self.assembly_text {
std::fs::write(&self.output_file, text)
.map_err(|e| vec![format!("failed to write assembly file: {}", e)])?;
}
}
Ok(asm)
}
fn emit_assembly(&self, module: &Module) -> Result<String, Vec<String>> {
let mut asm = String::new();
asm.push_str(&format!("\t.text\n"));
asm.push_str(&format!("\t.file\t\"{}\"\n", module.name));
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", func.name, func.name));
}
if module.functions.is_empty() {
asm.push_str("\t; empty module\n");
}
Ok(asm)
}
pub fn get_assembly(&self) -> Option<&str> {
self.assembly_text.as_deref()
}
}
#[derive(Debug)]
pub struct EmitLLVMActionFull {
pub triple: String,
pub output_file: PathBuf,
pub opt_level: OptLevel,
pub ir_text: Option<String>,
pub completed: bool,
}
impl EmitLLVMActionFull {
pub fn new(triple: &str, output_file: PathBuf) -> Self {
Self {
triple: triple.to_string(),
output_file,
opt_level: OptLevel::O0,
ir_text: None,
completed: false,
}
}
pub fn execute(
&mut self,
source: &str,
standard: CLangStandard,
) -> Result<String, Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(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 mut cg = ClangCodeGen::new("input", &self.triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen error: {}", err))
.collect::<Vec<_>>()
})?;
let ir = self.emit_ir(&cg.module);
self.ir_text = Some(ir.clone());
self.completed = true;
if !self.output_file.as_os_str().is_empty() {
if let Some(ref text) = self.ir_text {
std::fs::write(&self.output_file, text)
.map_err(|e| vec![format!("failed to write IR file: {}", e)])?;
}
}
Ok(ir)
}
fn emit_ir(&self, module: &Module) -> String {
let mut ir = String::new();
ir.push_str(&format!("; ModuleID = '{}'\n", module.name));
ir.push_str(&format!("source_filename = \"{}\"\n", module.name));
ir.push_str(&format!("target triple = \"{}\"\n", self.triple));
ir.push_str("target datalayout = \"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128\"\n");
ir.push_str("\n");
for gv in &module.globals {
let gv = gv.borrow();
ir.push_str(&format!("@{} = ", gv.name));
if gv.is_constant {
ir.push_str("constant ");
} else {
ir.push_str("global ");
}
if let Some(ref init) = gv.initializer {
ir.push_str(&format!("{}", init.borrow().name));
} else {
ir.push_str("zeroinitializer");
}
ir.push_str("\n");
}
if !module.globals.is_empty() {
ir.push_str("\n");
}
for decl in &module.declarations {
let decl = decl.borrow();
ir.push_str(&format!(
"declare {} @{}(",
decl.return_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| "void".to_string()),
decl.name
));
for (i, param) in decl.params.iter().enumerate() {
if i > 0 {
ir.push_str(", ");
}
ir.push_str(&format!("{}", param));
}
if decl.is_vararg {
if !decl.params.is_empty() {
ir.push_str(", ");
}
ir.push_str("...");
}
ir.push_str(")\n");
}
if !module.declarations.is_empty() {
ir.push_str("\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();
if !bb.name.is_empty() {
ir.push_str(&format!("{}:\n", bb.name));
} else {
ir.push_str("entry:\n");
}
for inst in &bb.instructions {
let inst = inst.borrow();
if let Some(ref result) = inst.result {
ir.push_str(&format!(" %{} = {:?}", result, inst.opcode));
} else {
ir.push_str(&format!(" {:?}", inst.opcode));
}
ir.push_str("\n");
}
}
ir.push_str("}\n\n");
}
ir.push_str(&format!("!llvm.module.flags = !{{!0}}\n"));
ir.push_str(&format!(
"!0 = !{{i32 2, !\"Debug Info Version\", i32 3}}\n"
));
ir
}
pub fn get_ir(&self) -> Option<&str> {
self.ir_text.as_deref()
}
}
#[derive(Debug)]
pub struct EmitBCActionFull {
pub triple: String,
pub output_file: PathBuf,
pub bitcode_bytes: Option<Vec<u8>>,
pub completed: bool,
}
impl EmitBCActionFull {
pub fn new(triple: &str, output_file: PathBuf) -> Self {
Self {
triple: triple.to_string(),
output_file,
bitcode_bytes: None,
completed: false,
}
}
pub fn execute(
&mut self,
source: &str,
standard: CLangStandard,
) -> Result<Vec<u8>, Vec<String>> {
let tokens = lexer::tokenize(source, standard);
let mut parser = parser::Parser::new(&tokens, standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(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 mut cg = ClangCodeGen::new("input", &self.triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen error: {}", err))
.collect::<Vec<_>>()
})?;
let bc = self.emit_bitcode(&cg.module);
self.bitcode_bytes = Some(bc.clone());
self.completed = true;
if !self.output_file.as_os_str().is_empty() {
if let Some(ref bytes) = self.bitcode_bytes {
std::fs::write(&self.output_file, bytes)
.map_err(|e| vec![format!("failed to write bitcode file: {}", e)])?;
}
}
Ok(bc)
}
fn emit_bitcode(&self, _module: &Module) -> Vec<u8> {
let mut bc = Vec::new();
bc.extend_from_slice(&[0xDE, 0xC0, 0x17, 0x0B]);
bc.extend_from_slice(&[0; 64]);
bc
}
pub fn get_bitcode(&self) -> Option<&[u8]> {
self.bitcode_bytes.as_deref()
}
}
impl FrontendAction {
pub fn begin_source_file_full(
&mut self,
source: &str,
compiler: &mut CompilerInstance,
) -> Result<(), Vec<String>> {
let _fid = compiler
.source_manager
.load_virtual_file("<input>", source.to_string())
.map_err(|e| vec![format!("failed to load source: {}", e)])?;
let _pp = compiler.create_preprocessor();
let _ast_ctx = compiler.create_ast_context();
Ok(())
}
pub fn execute_full(
&mut self,
source: &str,
compiler: &mut CompilerInstance,
) -> Result<(), Vec<String>> {
match self {
FrontendAction::PreprocessOnly(action) => {
action.execute();
Ok(())
}
FrontendAction::SyntaxOnly(_) => {
let tokens = lexer::tokenize(source, compiler.opts.standard);
let mut parser = parser::Parser::new(&tokens, compiler.opts.standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
let mut sema = Sema::new(compiler.opts.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());
}
Ok(())
}
FrontendAction::EmitLLVM(action) => {
action.execute();
Ok(())
}
FrontendAction::EmitBC(action) => {
action.execute();
Ok(())
}
FrontendAction::EmitAssembly(action) => {
action.execute();
Ok(())
}
FrontendAction::EmitObj(action) => {
action.execute();
Ok(())
}
FrontendAction::ASTDump(_) => {
let tokens = lexer::tokenize(source, compiler.opts.standard);
let mut parser = parser::Parser::new(&tokens, compiler.opts.standard);
let tu = parser.parse().map_err(|e| {
e.into_iter()
.map(|err| format!("parse error: {}", err))
.collect::<Vec<_>>()
})?;
for decl in &tu.decls {
eprintln!("{}", decl);
}
Ok(())
}
FrontendAction::DumpTokens(_) => {
let tokens = lexer::tokenize(source, compiler.opts.standard);
for tok in &tokens {
eprintln!("{} {:?}", tok.location.line, tok.kind);
}
Ok(())
}
}
}
pub fn end_source_file_full(
&mut self,
compiler: &mut CompilerInstance,
) -> Result<(), Vec<String>> {
compiler.source_file_begun = false;
self.end_source_file()?;
Ok(())
}
}
impl MultiplexConsumer {
pub fn create(consumers: Vec<Box<dyn ASTConsumer>>) -> Self {
Self { consumers }
}
pub fn handle_translation_unit(&mut self, tu: &TranslationUnit) {
for consumer in &mut self.consumers {
consumer.handle_translation_unit_begin(tu);
for decl in &tu.decls {
consumer.handle_top_level_decl(decl);
}
consumer.handle_translation_unit_end(tu);
}
}
pub fn finish_all(&mut self) {
for consumer in &mut self.consumers {
consumer.finish();
}
}
pub fn print_all_stats(&self) {
for consumer in &self.consumers {
consumer.print_stats();
}
}
pub fn num_consumers(&self) -> usize {
self.consumers.len()
}
pub fn is_empty(&self) -> bool {
self.consumers.is_empty()
}
}
#[derive(Debug)]
pub struct VerifyDiagnosticConsumer {
pub expected: Vec<ExpectedDiagnostic>,
pub received: Vec<ReceivedDiagnostic>,
pub strict: bool,
pub optional: Vec<ExpectedDiagnostic>,
}
#[derive(Debug, Clone)]
pub struct ExpectedDiagnostic {
pub severity: DiagSeverity,
pub file: Option<String>,
pub line: u32,
pub column: u32,
pub message_contains: Option<String>,
pub message_exact: Option<String>,
pub seen: bool,
}
impl ExpectedDiagnostic {
pub fn error_on_line(line: u32, msg: &str) -> Self {
Self {
severity: DiagSeverity::Error,
file: None,
line,
column: 0,
message_contains: Some(msg.to_string()),
message_exact: None,
seen: false,
}
}
pub fn warning_on_line(line: u32, msg: &str) -> Self {
Self {
severity: DiagSeverity::Warning,
file: None,
line,
column: 0,
message_contains: Some(msg.to_string()),
message_exact: None,
seen: false,
}
}
pub fn error_exact(line: u32, msg: &str) -> Self {
Self {
severity: DiagSeverity::Error,
file: None,
line,
column: 0,
message_contains: None,
message_exact: Some(msg.to_string()),
seen: false,
}
}
pub fn matches(&self, received: &ReceivedDiagnostic) -> bool {
if self.severity != received.severity {
return false;
}
if let Some(ref file) = self.file {
if received.file.as_deref() != Some(file.as_str()) {
return false;
}
}
if self.line != 0 && self.line != received.line {
return false;
}
if self.column != 0 && self.column != received.column {
return false;
}
if let Some(ref contains) = self.message_contains {
if !received.message.contains(contains.as_str()) {
return false;
}
}
if let Some(ref exact) = self.message_exact {
if received.message != *exact {
return false;
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct ReceivedDiagnostic {
pub severity: DiagSeverity,
pub file: Option<String>,
pub line: u32,
pub column: u32,
pub message: String,
pub diag_id: DiagID,
pub fixits: Vec<String>,
}
impl VerifyDiagnosticConsumer {
pub fn new(expected: Vec<ExpectedDiagnostic>) -> Self {
Self {
expected,
received: Vec::new(),
strict: true,
optional: Vec::new(),
}
}
pub fn lenient(expected: Vec<ExpectedDiagnostic>) -> Self {
Self {
expected,
received: Vec::new(),
strict: false,
optional: Vec::new(),
}
}
pub fn add_optional(&mut self, diag: ExpectedDiagnostic) {
self.optional.push(diag);
}
pub fn record(
&mut self,
severity: DiagSeverity,
file: Option<&str>,
line: u32,
column: u32,
message: &str,
diag_id: DiagID,
) {
let received = ReceivedDiagnostic {
severity,
file: file.map(|s| s.to_string()),
line,
column,
message: message.to_string(),
diag_id,
fixits: Vec::new(),
};
self.received.push(received);
}
pub fn verify(&self) -> Vec<ExpectedDiagnostic> {
let mut missing = Vec::new();
for expected in &self.expected {
let found = self
.received
.iter()
.any(|received| expected.matches(received));
if !found {
missing.push(expected.clone());
}
}
missing
}
pub fn verify_strict(&self) -> (Vec<ExpectedDiagnostic>, Vec<ReceivedDiagnostic>) {
let missing = self.verify();
let mut unexpected = Vec::new();
for received in &self.received {
let is_expected = self
.expected
.iter()
.any(|expected| expected.matches(received));
let is_optional = self.optional.iter().any(|opt| opt.matches(received));
if !is_expected && !is_optional {
unexpected.push(received.clone());
}
}
(missing, unexpected)
}
pub fn all_seen(&self) -> bool {
self.verify().is_empty()
}
pub fn count_received(&self) -> usize {
self.received.len()
}
pub fn count_expected(&self) -> usize {
self.expected.len()
}
pub fn clear(&mut self) {
self.received.clear();
}
pub fn format_results(&self) -> String {
let mut out = String::new();
let (missing, unexpected) = self.verify_strict();
if missing.is_empty() && unexpected.is_empty() {
out.push_str("All expected diagnostics received.\n");
} else {
if !missing.is_empty() {
out.push_str(&format!(
"Missing {} expected diagnostics:\n",
missing.len()
));
for m in &missing {
out.push_str(&format!(
" - {:?} at line {}, contains: {:?}\n",
m.severity, m.line, m.message_contains
));
}
}
if self.strict && !unexpected.is_empty() {
out.push_str(&format!("Unexpected {} diagnostics:\n", unexpected.len()));
for u in &unexpected {
out.push_str(&format!(
" - {:?} at {}:{}: {}\n",
u.severity,
u.file.as_deref().unwrap_or("<unknown>"),
u.line,
u.message
));
}
}
}
out
}
}
#[derive(Debug, Clone)]
pub enum CompilationPhase {
Lexing,
Preprocessing,
Parsing,
SemanticAnalysis,
CodeGeneration,
Optimization,
Emission,
Linking,
}
impl CompilationPhase {
pub fn name(&self) -> &'static str {
match self {
CompilationPhase::Lexing => "lexing",
CompilationPhase::Preprocessing => "preprocessing",
CompilationPhase::Parsing => "parsing",
CompilationPhase::SemanticAnalysis => "semantic analysis",
CompilationPhase::CodeGeneration => "code generation",
CompilationPhase::Optimization => "optimization",
CompilationPhase::Emission => "emission",
CompilationPhase::Linking => "linking",
}
}
pub fn required_for_syntax_only(&self) -> bool {
matches!(
self,
CompilationPhase::Lexing
| CompilationPhase::Preprocessing
| CompilationPhase::Parsing
| CompilationPhase::SemanticAnalysis
)
}
pub fn can_skip(&self) -> bool {
matches!(
self,
CompilationPhase::CodeGeneration
| CompilationPhase::Optimization
| CompilationPhase::Emission
| CompilationPhase::Linking
)
}
}
#[derive(Debug, Clone)]
pub struct ErrorTracker {
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub notes: Vec<String>,
pub error_limit: usize,
pub warning_limit: usize,
pub limit_reached: bool,
}
impl ErrorTracker {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
notes: Vec::new(),
error_limit: 20,
warning_limit: 100,
limit_reached: false,
}
}
pub fn error(&mut self, msg: &str) {
if self.limit_reached {
return;
}
self.errors.push(msg.to_string());
if self.errors.len() >= self.error_limit {
self.limit_reached = true;
}
}
pub fn warning(&mut self, msg: &str) {
self.warnings.push(msg.to_string());
}
pub fn note(&mut self, msg: &str) {
self.notes.push(msg.to_string());
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn error_count(&self) -> usize {
self.errors.len()
}
pub fn warning_count(&self) -> usize {
self.warnings.len()
}
pub fn clear(&mut self) {
self.errors.clear();
self.warnings.clear();
self.notes.clear();
self.limit_reached = false;
}
pub fn merge(&mut self, other: &ErrorTracker) {
for e in &other.errors {
self.error(e);
}
for w in &other.warnings {
self.warning(w);
}
for n in &other.notes {
self.note(n);
}
}
}
impl Default for ErrorTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PhaseTimer {
start_time: Option<std::time::Instant>,
pub phase_times: Vec<(CompilationPhase, std::time::Duration)>,
}
impl PhaseTimer {
pub fn new() -> Self {
Self {
start_time: None,
phase_times: Vec::new(),
}
}
pub fn start_phase(&mut self, _phase: CompilationPhase) {
self.start_time = Some(std::time::Instant::now());
}
pub fn end_phase(&mut self, phase: CompilationPhase) {
if let Some(start) = self.start_time {
let elapsed = start.elapsed();
self.phase_times.push((phase, elapsed));
}
self.start_time = None;
}
pub fn total_time(&self) -> std::time::Duration {
self.phase_times.iter().map(|(_, d)| *d).sum()
}
pub fn format(&self) -> String {
let mut out = String::new();
out.push_str("=== Phase Timings ===\n");
for (phase, duration) in &self.phase_times {
out.push_str(&format!(
" {:20}: {:>8.2} ms\n",
phase.name(),
duration.as_secs_f64() * 1000.0
));
}
out.push_str(&format!(
" {:20}: {:>8.2} ms\n",
"TOTAL",
self.total_time().as_secs_f64() * 1000.0
));
out
}
}
impl Default for PhaseTimer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CompilationUnitInfo {
pub source_file: PathBuf,
pub standard: CLangStandard,
pub is_cxx: bool,
pub target_triple: String,
pub opt_level: OptLevel,
pub module_name: String,
pub source_hash: Option<u64>,
}
impl CompilationUnitInfo {
pub fn new(source: &Path, standard: CLangStandard, triple: &str) -> Self {
let module_name = source
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("input")
.to_string();
Self {
source_file: source.to_path_buf(),
standard,
is_cxx: false,
target_triple: triple.to_string(),
opt_level: OptLevel::O0,
module_name,
source_hash: None,
}
}
pub fn with_opt_level(mut self, level: OptLevel) -> Self {
self.opt_level = level;
self
}
pub fn with_cxx(mut self, cxx: bool) -> Self {
self.is_cxx = cxx;
self
}
pub fn compute_hash(&mut self, source: &str) {
let mut hash: u64 = 5381;
for b in source.bytes() {
hash = hash.wrapping_mul(33).wrapping_add(b as u64);
}
self.source_hash = Some(hash);
}
}
#[derive(Debug)]
pub struct PipelineResult {
pub success: bool,
pub output_format: OutputFormat,
pub text_output: Option<String>,
pub binary_output: Option<Vec<u8>>,
pub module: Option<Module>,
pub error_tracker: ErrorTracker,
pub phase_timer: PhaseTimer,
pub output_file: Option<PathBuf>,
}
impl PipelineResult {
pub fn success(format: OutputFormat) -> Self {
Self {
success: true,
output_format: format,
text_output: None,
binary_output: None,
module: None,
error_tracker: ErrorTracker::new(),
phase_timer: PhaseTimer::new(),
output_file: None,
}
}
pub fn failure(format: OutputFormat, errors: Vec<String>) -> Self {
let mut tracker = ErrorTracker::new();
for e in &errors {
tracker.error(e);
}
Self {
success: false,
output_format: format,
text_output: None,
binary_output: None,
module: None,
error_tracker: tracker,
phase_timer: PhaseTimer::new(),
output_file: None,
}
}
pub fn with_text(mut self, text: String) -> Self {
self.text_output = Some(text);
self
}
pub fn with_binary(mut self, bytes: Vec<u8>) -> Self {
self.binary_output = Some(bytes);
self
}
pub fn with_module(mut self, module: Module) -> Self {
self.module = Some(module);
self
}
pub fn with_output_file(mut self, path: PathBuf) -> Self {
self.output_file = Some(path);
self
}
pub fn has_errors(&self) -> bool {
self.error_tracker.has_errors()
}
pub fn error_count(&self) -> usize {
self.error_tracker.error_count()
}
}
#[derive(Debug, Clone)]
pub struct TargetMachineStub {
pub triple: String,
pub cpu: String,
pub features: String,
pub opt_level: OptLevel,
pub code_model: CodeModel,
pub reloc_model: RelocModel,
pub debug_info: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeModel {
Default,
Small,
Kernel,
Medium,
Large,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocModel {
Default,
Static,
PIC,
DynamicNoPic,
ROPI,
RWPI,
ROPI_RWPI,
}
impl TargetMachineStub {
pub fn new(triple: &str) -> Self {
Self {
triple: triple.to_string(),
cpu: "generic".to_string(),
features: String::new(),
opt_level: OptLevel::O0,
code_model: CodeModel::Default,
reloc_model: RelocModel::Default,
debug_info: false,
}
}
pub fn with_cpu(mut self, cpu: &str) -> Self {
self.cpu = cpu.to_string();
self
}
pub fn with_features(mut self, features: &str) -> Self {
self.features = features.to_string();
self
}
pub fn with_opt_level(mut self, level: OptLevel) -> Self {
self.opt_level = level;
self
}
pub fn with_code_model(mut self, model: CodeModel) -> Self {
self.code_model = model;
self
}
pub fn with_reloc_model(mut self, model: RelocModel) -> Self {
self.reloc_model = model;
self
}
pub fn with_debug_info(mut self, debug: bool) -> Self {
self.debug_info = debug;
self
}
pub fn is_pic(&self) -> bool {
matches!(
self.reloc_model,
RelocModel::PIC | RelocModel::ROPI | RelocModel::ROPI_RWPI
)
}
pub fn has_feature(&self, feature: &str) -> bool {
self.features.contains(feature)
}
pub fn emit_object(&self, _module: &Module) -> Result<Vec<u8>, String> {
let mut obj = Vec::new();
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 self.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]);
Ok(obj)
}
pub fn emit_assembly(&self, _module: &Module) -> Result<String, String> {
let mut asm = String::new();
asm.push_str(&format!("\t.arch {}\n", self.triple));
asm.push_str(&format!("\t.cpu {}\n", self.cpu));
asm.push_str("\t.text\n");
Ok(asm)
}
}
impl Default for TargetMachineStub {
fn default() -> Self {
Self::new("x86_64-unknown-linux-gnu")
}
}
#[derive(Debug, Clone)]
pub struct ModuleVerifier {
pub abort_on_error: bool,
pub verbose: bool,
pub errors: Vec<String>,
}
impl ModuleVerifier {
pub fn new() -> Self {
Self {
abort_on_error: false,
verbose: false,
errors: Vec::new(),
}
}
pub fn verify(&mut self, module: &Module) -> bool {
self.errors.clear();
for func in &module.functions {
let func = func.borrow();
if func.blocks.is_empty() {
self.errors
.push(format!("function '{}' has no basic blocks", func.name));
if self.abort_on_error {
return false;
}
}
for bb in &func.blocks {
let bb = bb.borrow();
if bb.instructions.is_empty() && !bb.name.is_empty() {
if self.verbose {
}
}
if let Some(last) = bb.instructions.last() {
let last = last.borrow();
if !self.is_valid_terminator(&last.opcode) {
self.errors.push(format!(
"block '{}' in function '{}' does not end with a terminator instruction",
bb.name, func.name
));
if self.abort_on_error {
return false;
}
}
}
}
}
for gv in &module.globals {
let gv = gv.borrow();
if gv.initializer.is_none() && gv.is_constant {
self.errors.push(format!(
"constant global '{}' must have an initializer",
gv.name
));
if self.abort_on_error {
return false;
}
}
}
self.errors.is_empty()
}
fn is_valid_terminator(&self, opcode: &Option<Opcode>) -> bool {
if let Some(op) = opcode {
matches!(
op,
Opcode::Ret
| Opcode::Br
| Opcode::Switch
| Opcode::IndirectBr
| Opcode::Invoke
| Opcode::Resume
| Opcode::Unreachable
| Opcode::CatchRet
| Opcode::CatchSwitch
)
} else {
false
}
}
pub fn verify_function(&mut self, func: &Function) -> bool {
self.errors.clear();
if func.basic_blocks.is_empty() {
self.errors.push(format!(
"function '{}' has no body",
func.value.borrow().name
));
return false;
}
if let Some(entry) = func.basic_blocks.first() {
if entry.borrow().name != "entry"
&& !func.basic_blocks.iter().any(|b| b.borrow().name == "entry")
{
if self.verbose {
}
}
}
true
}
pub fn get_errors(&self) -> &[String] {
&self.errors
}
pub fn passed(&self) -> bool {
self.errors.is_empty()
}
}
impl Default for ModuleVerifier {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OptimizerConfig {
pub opt_level: OptLevel,
pub inline: bool,
pub inline_threshold: u32,
pub loop_unroll: bool,
pub vectorize: bool,
pub slp_vectorize: bool,
pub gvn: bool,
pub dce: bool,
pub const_prop: bool,
pub tail_call_elim: bool,
pub merge_functions: bool,
pub custom_passes: Vec<String>,
}
impl OptimizerConfig {
pub fn o0() -> Self {
Self {
opt_level: OptLevel::O0,
inline: false,
inline_threshold: 0,
loop_unroll: false,
vectorize: false,
slp_vectorize: false,
gvn: false,
dce: false,
const_prop: false,
tail_call_elim: false,
merge_functions: false,
custom_passes: Vec::new(),
}
}
pub fn o1() -> Self {
Self {
opt_level: OptLevel::O1,
inline: true,
inline_threshold: 225,
loop_unroll: false,
vectorize: false,
slp_vectorize: false,
gvn: true,
dce: true,
const_prop: true,
tail_call_elim: true,
merge_functions: false,
custom_passes: Vec::new(),
}
}
pub fn o2() -> Self {
Self {
opt_level: OptLevel::O2,
inline: true,
inline_threshold: 275,
loop_unroll: true,
vectorize: true,
slp_vectorize: true,
gvn: true,
dce: true,
const_prop: true,
tail_call_elim: true,
merge_functions: true,
custom_passes: Vec::new(),
}
}
pub fn o3() -> Self {
Self {
opt_level: OptLevel::O3,
inline: true,
inline_threshold: 350,
loop_unroll: true,
vectorize: true,
slp_vectorize: true,
gvn: true,
dce: true,
const_prop: true,
tail_call_elim: true,
merge_functions: true,
custom_passes: Vec::new(),
}
}
pub fn os() -> Self {
Self {
opt_level: OptLevel::Os,
inline: true,
inline_threshold: 75,
loop_unroll: false,
vectorize: false,
slp_vectorize: false,
gvn: false,
dce: true,
const_prop: true,
tail_call_elim: true,
merge_functions: true,
custom_passes: Vec::new(),
}
}
pub fn from_opt_level(level: OptLevel) -> Self {
match level {
OptLevel::O0 => Self::o0(),
OptLevel::O1 => Self::o1(),
OptLevel::O2 => Self::o2(),
OptLevel::O3 => Self::o3(),
OptLevel::Os => Self::os(),
OptLevel::Oz => {
let mut s = Self::os();
s.opt_level = OptLevel::Oz;
s.inline_threshold = 25;
s
}
}
}
pub fn add_pass(&mut self, pass_name: &str) {
self.custom_passes.push(pass_name.to_string());
}
pub fn is_optimizing(&self) -> bool {
self.opt_level.is_optimizing()
}
pub fn description(&self) -> String {
let mut desc = format!("Optimization level: {:?}", self.opt_level);
if self.inline {
desc.push_str(&format!(", inline(threshold={})", self.inline_threshold));
}
if self.loop_unroll {
desc.push_str(", loop-unroll");
}
if self.vectorize {
desc.push_str(", vectorize");
}
if self.slp_vectorize {
desc.push_str(", slp-vectorize");
}
desc
}
}
impl Default for OptimizerConfig {
fn default() -> Self {
Self::o0()
}
}
#[derive(Debug)]
pub struct CodeEmitter {
pub target_machine: TargetMachineStub,
pub optimizer_config: OptimizerConfig,
pub verify_module: bool,
pub include_debug_info: bool,
pub debug_info_kind: DebugInfoKind,
}
impl CodeEmitter {
pub fn new(target: TargetMachineStub, opt: OptimizerConfig) -> Self {
Self {
target_machine: target,
optimizer_config: opt,
verify_module: true,
include_debug_info: false,
debug_info_kind: DebugInfoKind::None,
}
}
pub fn emit_object(&self, module: &Module) -> Result<Vec<u8>, String> {
if self.verify_module {
let mut verifier = ModuleVerifier::new();
if !verifier.verify(module) {
return Err(format!("module verification failed: {:?}", verifier.errors));
}
}
self.target_machine.emit_object(module)
}
pub fn emit_assembly(&self, module: &Module) -> Result<String, String> {
self.target_machine.emit_assembly(module)
}
pub fn should_emit_debug_info(&self) -> bool {
self.include_debug_info && self.debug_info_kind != DebugInfoKind::None
}
}
impl Default for CodeEmitter {
fn default() -> Self {
Self::new(TargetMachineStub::default(), OptimizerConfig::default())
}
}
pub type CompilationPipelineFn = fn(source: &str, triple: &str) -> Result<Vec<u8>, Vec<String>>;
pub type IRPipelineFn = fn(source: &str, triple: &str) -> Result<String, Vec<String>>;
#[derive(Debug, Clone)]
pub struct PipelineRegistry {
pub default_pipeline: String,
pub pipelines: std::collections::HashMap<String, CompilationPipelineFn>,
}
impl PipelineRegistry {
pub fn new() -> Self {
let mut registry = Self {
default_pipeline: "standard".to_string(),
pipelines: std::collections::HashMap::new(),
};
registry.pipelines.insert(
"standard".to_string(),
compile_to_object as CompilationPipelineFn,
);
registry
}
pub fn register(&mut self, name: &str, pipeline: CompilationPipelineFn) {
self.pipelines.insert(name.to_string(), pipeline);
}
pub fn run(&self, source: &str, triple: &str) -> Result<Vec<u8>, Vec<String>> {
let pipeline = self
.pipelines
.get(&self.default_pipeline)
.or_else(|| self.pipelines.values().next())
.ok_or_else(|| vec!["no pipeline registered".to_string()])?;
pipeline(source, triple)
}
}
impl Default for PipelineRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn compile_to_object(source: &str, triple: &str) -> Result<Vec<u8>, Vec<String>> {
let opts = CompilerOptions {
target_triple: triple.to_string(),
action: FrontendActionKind::EmitObj,
..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 = lexer::tokenize(source, compiler.opts.standard);
let mut parser = 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 = 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());
}
let mut cg = ClangCodeGen::new("input", triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen: {}", err))
.collect::<Vec<_>>()
})?;
Ok(Vec::new())
}
pub fn compile_to_ir(source: &str, triple: &str) -> Result<String, Vec<String>> {
let opts = CompilerOptions {
target_triple: triple.to_string(),
emit_llvm: 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 = lexer::tokenize(source, compiler.opts.standard);
let mut parser = 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 = Sema::new(compiler.opts.standard);
sema.analyze(&tu).map_err(|e| {
e.iter()
.map(|err| format!("sema: {}", err))
.collect::<Vec<_>>()
})?;
let mut cg = ClangCodeGen::new("input", triple);
cg.compile(&tu).map_err(|e| {
e.iter()
.map(|err| format!("codegen: {}", err))
.collect::<Vec<_>>()
})?;
let ir = String::new();
let _ = cg.module;
Ok(ir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compiler_options_default() {
let opts = CompilerOptions::default();
assert_eq!(opts.standard, CLangStandard::C17);
assert_eq!(opts.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(opts.opt_level, OptLevel::O0);
}
#[test]
fn test_compiler_options_from_clang_options() {
let co = ClangOptions {
standard: CLangStandard::C11,
optimize: true,
debug_info: true,
warnings: true,
pedantic: true,
wall: true,
werror: true,
verbose: false,
includes: vec!["/usr/include".to_string()],
defines: vec![("FOO".to_string(), Some("bar".to_string()))],
output_file: Some("out.o".to_string()),
target_triple: "aarch64-unknown-linux-gnu".to_string(),
};
let opts = CompilerOptions::from_clang_options(&co);
assert_eq!(opts.standard, CLangStandard::C11);
assert_eq!(opts.opt_level, OptLevel::O2);
assert!(opts.debug_info);
assert!(opts.wall);
assert!(opts.pedantic);
assert!(opts.werror);
assert_eq!(opts.includes.len(), 1);
assert!(opts.output_file.is_some());
}
#[test]
fn test_file_manager_virtual_file() {
let mut fm = FileManager::new();
let fid = fm.get_virtual_file("test.c", "int x;".to_string()).unwrap();
assert!(fid.0 > 0);
let entry = fm.get_file_by_id(fid).unwrap();
assert_eq!(entry.name, "test.c");
assert!(entry.is_virtual);
}
#[test]
fn test_file_manager_overlay() {
let mut fm = FileManager::new();
fm.add_overlay("builtin.h".to_string(), "#define NULL 0".to_string());
let fid = fm.get_file(Path::new("builtin.h")).unwrap();
let entry = fm.get_file_by_id(fid).unwrap();
assert!(entry.is_virtual);
}
#[test]
fn test_file_manager_include_search() {
let mut fm = FileManager::new();
fm.add_include_path(PathBuf::from("/include"));
fm.add_overlay(
"/include/header.h".to_string(),
"int answer = 42;".to_string(),
);
let result = fm.find_include("header.h", None);
assert!(result.is_some());
}
#[test]
fn test_source_manager_virtual_file() {
let fm = FileManager::new();
let mut sm = SourceManager::new(fm);
let fid = sm
.load_virtual_file("test.c", "int x;\nint y;\nint z;\n".to_string())
.unwrap();
let loc1 = sm.get_location(fid, 0);
assert_eq!(loc1.line, 1);
assert_eq!(loc1.column, 1);
let loc2 = sm.get_location(fid, 7); assert_eq!(loc2.line, 2);
}
#[test]
fn test_source_manager_get_line() {
let fm = FileManager::new();
let mut sm = SourceManager::new(fm);
let fid = sm
.load_virtual_file("test.c", "line one\nline two\n".to_string())
.unwrap();
let loc1 = sm.get_location(fid, 0);
let line1 = sm.get_line(loc1).unwrap();
assert_eq!(line1, "line one");
let loc2 = sm.get_location(fid, 9); let line2 = sm.get_line(loc2).unwrap();
assert_eq!(line2, "line two");
}
#[test]
fn test_source_manager_invalid_location() {
let fm = FileManager::new();
let sm = SourceManager::new(fm);
let loc = SourceLocation::invalid();
assert!(!loc.is_valid());
assert_eq!(loc.to_display(&sm), "<invalid loc>");
}
#[test]
fn test_compiler_invocation_default() {
let inv = CompilerInvocation::default();
assert_eq!(inv.opts.standard, CLangStandard::C17);
}
#[test]
fn test_compiler_invocation_parse_std() {
let inv = CompilerInvocation::from_args(["clang", "-std=c11", "-c", "file.c"]);
assert_eq!(inv.opts.standard, CLangStandard::C11);
assert!(inv.opts.input_files.len() >= 1);
}
#[test]
fn test_compiler_invocation_parse_optimization() {
let inv = CompilerInvocation::from_args(["clang", "-O2", "-g", "file.c"]);
assert_eq!(inv.opts.opt_level, OptLevel::O2);
assert!(inv.opts.debug_info);
}
#[test]
fn test_compiler_invocation_parse_emit_llvm() {
let inv = CompilerInvocation::from_args(["clang", "-emit-llvm", "file.c"]);
assert!(inv.opts.emit_llvm);
}
#[test]
fn test_compiler_invocation_parse_includes() {
let inv = CompilerInvocation::from_args([
"clang",
"-I/usr/include",
"-I",
"/usr/local/include",
"file.c",
]);
assert!(inv.opts.includes.len() >= 1);
}
#[test]
fn test_compiler_invocation_parse_defines() {
let inv = CompilerInvocation::from_args(["clang", "-DFOO", "-DBAR=baz", "file.c"]);
assert!(inv.opts.defines.iter().any(|(n, _)| n == "FOO"));
assert!(inv.opts.defines.iter().any(|(n, _)| n == "BAR"));
}
#[test]
fn test_compiler_instance_create_default() {
let ci = CompilerInstance::create_default();
assert_eq!(ci.target_triple(), "x86_64-unknown-linux-gnu");
}
#[test]
fn test_full_pipeline_minimal() {
let source = "int main(void) { return 0; }";
let result = compile_to_object(source, "x86_64-unknown-linux-gnu");
assert!(result.is_ok());
}
#[test]
fn test_full_pipeline_with_function() {
let source = "int add(int a, int b) { return a + b; }";
let result = compile_to_ir(source, "x86_64-unknown-linux-gnu");
assert!(result.is_ok());
}
#[test]
fn test_compiler_invocation_output_file() {
let inv = CompilerInvocation::from_args(["clang", "-c", "hello.c", "-o", "hello.o"]);
let out = inv.output_file();
assert_eq!(out, PathBuf::from("hello.o"));
}
#[test]
fn test_compiler_invocation_output_file_stem() {
let inv = CompilerInvocation::from_args(["clang", "-S", "hello.c"]);
let out = inv.output_file();
assert_eq!(out, PathBuf::from("hello.s"));
}
#[test]
fn test_frontend_action_for_kind_preprocess() {
let action = FrontendAction::for_kind(FrontendActionKind::PreprocessOnly);
match action {
FrontendAction::PreprocessOnly(_) => {}
_ => panic!("expected PreprocessOnly"),
}
}
#[test]
fn test_frontend_action_for_kind_syntax_only() {
let action = FrontendAction::for_kind(FrontendActionKind::SyntaxOnly);
match action {
FrontendAction::SyntaxOnly(_) => {}
_ => panic!("expected SyntaxOnly"),
}
}
#[test]
fn test_frontend_action_for_kind_emit_obj() {
let action = FrontendAction::for_kind(FrontendActionKind::EmitObj);
match action {
FrontendAction::EmitObj(_) => {}
_ => panic!("expected EmitObj"),
}
}
#[test]
fn test_compilation_driver_from_args() {
let mut driver = CompilationDriver::from_args(["clang", "-std=c11", "-fsyntax-only", "-c"]);
assert_eq!(driver.compiler.opts.standard, CLangStandard::C11);
}
#[test]
fn test_opt_level_from_str() {
assert_eq!(OptLevel::from_str("0"), Some(OptLevel::O0));
assert_eq!(OptLevel::from_str("2"), Some(OptLevel::O2));
assert_eq!(OptLevel::from_str("s"), Some(OptLevel::Os));
assert_eq!(OptLevel::from_str("invalid"), None);
}
#[test]
fn test_opt_level_is_optimizing() {
assert!(!OptLevel::O0.is_optimizing());
assert!(OptLevel::O2.is_optimizing());
assert!(OptLevel::Os.is_optimizing());
}
#[test]
fn test_input_language_from_str() {
assert_eq!(InputLanguage::from_str("c"), Some(InputLanguage::C));
assert_eq!(InputLanguage::from_str("c++"), Some(InputLanguage::CXX));
assert!(InputLanguage::from_str("fortran").is_none());
}
#[test]
fn test_multiplex_consumer_basic() {
let mut mc = MultiplexConsumer::new(vec![
Box::new(SyntaxOnlyConsumer),
Box::new(ASTDumpConsumer),
]);
mc.consumers.clear(); }
#[test]
fn test_syntax_only_consumer() {
let mut c = SyntaxOnlyConsumer;
c.handle_top_level_decl(&Decl::VarDecl {
name: "x".to_string(),
ty: QualType::int(),
init: None,
});
}
}