use crate::clang::ast;
use crate::clang::clang_target::{
create_aarch64_machine, create_target_machine, create_x86_64_machine, ClangTargetInfo,
TargetCodeGenInfo,
};
use crate::clang::codegen::ClangCodeGen;
use crate::clang::diagnostics::{
DiagID, DiagMapping, DiagSeverity, DiagState, DiagnosticConsumer, DiagnosticEngine,
DiagnosticOptions,
};
use crate::clang::driver::ClangDriver;
use crate::clang::lexer::Lexer;
use crate::clang::parser::Parser;
use crate::clang::preprocessor::{FullPreprocessor, PredefinedMacros, Preprocessor};
use crate::clang::sema::Sema;
use crate::clang::token::{SourceFile, SourceLoc, Token, TokenKind};
use crate::clang::CLangStandard;
use crate::context::LLVMContext;
use crate::module::Module;
use crate::target_machine::{CodeGenOptLevel, CodeModel, RelocModel};
use crate::types::{Type, TypeKind};
use crate::value::ValueRef;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct LangOptions {
pub standard: CLangStandard,
pub cplusplus: bool,
pub c99: bool,
pub c11: bool,
pub c17: bool,
pub c23: bool,
pub cpp14: bool,
pub cpp17: bool,
pub cpp20: bool,
pub cpp23: bool,
pub gnu_mode: bool,
pub ms_extensions: bool,
pub freestanding: bool,
pub no_builtin: bool,
pub signed_char: bool,
pub short_wchar: bool,
pub short_enums: bool,
pub rtti: bool,
pub exceptions: bool,
pub objc_arc: bool,
pub blocks: bool,
pub coroutines: bool,
pub concepts: bool,
pub modules: bool,
pub openmp: bool,
pub cuda: bool,
pub opencl: bool,
pub spir: bool,
pub hlsl: bool,
pub asm_blocks: bool,
pub borland_extensions: bool,
pub trigraphs: bool,
pub dollar_in_idents: bool,
pub char8_t: bool,
pub relaxed_template_args: bool,
pub sized_deallocation: bool,
pub aligned_allocation: bool,
pub pascal_strings: bool,
pub math_errno: bool,
pub fmerge_all_constants: bool,
pub lax_vector_conversions: bool,
pub altivec: bool,
}
impl Default for LangOptions {
fn default() -> Self {
LangOptions {
standard: CLangStandard::C17,
cplusplus: false,
c99: true,
c11: true,
c17: true,
c23: false,
cpp14: true,
cpp17: true,
cpp20: false,
cpp23: false,
gnu_mode: false,
ms_extensions: false,
freestanding: false,
no_builtin: false,
signed_char: true,
short_wchar: false,
short_enums: false,
rtti: true,
exceptions: true,
objc_arc: false,
blocks: false,
coroutines: true,
concepts: false,
modules: false,
openmp: false,
cuda: false,
opencl: false,
spir: false,
hlsl: false,
asm_blocks: false,
borland_extensions: false,
trigraphs: false,
dollar_in_idents: false,
char8_t: false,
relaxed_template_args: false,
sized_deallocation: true,
aligned_allocation: true,
pascal_strings: false,
math_errno: true,
fmerge_all_constants: true,
lax_vector_conversions: true,
altivec: false,
}
}
}
impl LangOptions {
pub fn c99() -> Self {
LangOptions {
standard: CLangStandard::C99,
cplusplus: false,
c11: false,
c17: false,
c23: false,
..Default::default()
}
}
pub fn c17() -> Self {
LangOptions {
standard: CLangStandard::C17,
cplusplus: false,
..Default::default()
}
}
pub fn cpp17() -> Self {
LangOptions {
standard: CLangStandard::C17,
cplusplus: true,
cpp17: true,
cpp20: false,
..Default::default()
}
}
pub fn cpp20() -> Self {
LangOptions {
standard: CLangStandard::C23,
cplusplus: true,
cpp17: true,
cpp20: true,
concepts: true,
coroutines: true,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct CodeGenOptions {
pub optimization_level: u32,
pub debug_info: bool,
pub debug_info_kind: DebugInfoKind,
pub optimize_size: bool,
pub optimize_for_size: bool,
pub stack_protector: u32,
pub stack_realign: bool,
pub fp_contract: FPContractMode,
pub unsafe_math: bool,
pub no_infs_fp_math: bool,
pub no_nans_fp_math: bool,
pub approx_func: bool,
pub no_signed_zeros: bool,
pub allow_reciprocal: bool,
pub no_zero_initialized_in_bss: bool,
pub merge_all_constants: bool,
pub no_implicit_float: bool,
pub coverage: bool,
pub sanitize_address: bool,
pub sanitize_thread: bool,
pub sanitize_memory: bool,
pub sanitize_undefined: bool,
pub sanitize_coverage: bool,
pub function_sections: bool,
pub data_sections: bool,
pub unique_section_names: bool,
pub trap_unreachable: bool,
pub vectorize_loops: bool,
pub vectorize_slp: bool,
pub lto: bool,
pub thin_lto: bool,
pub pic_level: u32,
pub pie_level: u32,
pub incremental_linker_compatible: bool,
pub disable_llvm_passes: bool,
pub verify_module: bool,
pub disable_red_zone: bool,
pub emit_llvm_uselists: bool,
pub no_use_jump_tables: bool,
pub no_inline_line_tables: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DebugInfoKind {
None,
LineTablesOnly,
Limited,
Full,
Constructor,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FPContractMode {
Off,
On,
Fast,
FastHonorPragmas,
}
impl Default for CodeGenOptions {
fn default() -> Self {
CodeGenOptions {
optimization_level: 0,
debug_info: false,
debug_info_kind: DebugInfoKind::None,
optimize_size: false,
optimize_for_size: false,
stack_protector: 0,
stack_realign: false,
fp_contract: FPContractMode::On,
unsafe_math: false,
no_infs_fp_math: false,
no_nans_fp_math: false,
approx_func: false,
no_signed_zeros: false,
allow_reciprocal: false,
no_zero_initialized_in_bss: false,
merge_all_constants: true,
no_implicit_float: false,
coverage: false,
sanitize_address: false,
sanitize_thread: false,
sanitize_memory: false,
sanitize_undefined: false,
sanitize_coverage: false,
function_sections: false,
data_sections: false,
unique_section_names: true,
trap_unreachable: true,
vectorize_loops: false,
vectorize_slp: false,
lto: false,
thin_lto: false,
pic_level: 0,
pie_level: 0,
incremental_linker_compatible: false,
disable_llvm_passes: false,
verify_module: true,
disable_red_zone: false,
emit_llvm_uselists: false,
no_use_jump_tables: false,
no_inline_line_tables: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrontendActionKind {
ParseSyntaxOnly,
EmitLLVM,
EmitAssembly,
EmitObject,
EmitBC,
EmitLLVMOnly,
RunAnalysis,
RunPreprocessorOnly,
PrintPreprocessedInput,
DumpTokens,
DumpAST,
EmitLLVMBitcode,
GeneratePCH,
UsePCH,
InitOnly,
RewriteObjC,
RewriteMacros,
RewriteTest,
RunPlugin,
MigrateSource,
EmitHeaderModule,
EmitCodeGenOnly,
FixIt,
PrintPreamble,
}
#[derive(Debug, Clone)]
pub struct FrontendInputFile {
pub path: PathBuf,
pub is_system: bool,
pub is_preprocessed: bool,
pub language: Option<String>,
}
#[derive(Debug, Clone)]
pub struct FrontendAction {
pub kind: FrontendActionKind,
pub input: Option<FrontendInputFile>,
pub output_file: Option<PathBuf>,
pub lang_opts: LangOptions,
pub codegen_opts: CodeGenOptions,
pub target_triple: String,
}
impl FrontendAction {
pub fn for_kind(kind: FrontendActionKind) -> Self {
FrontendAction {
kind,
input: None,
output_file: None,
lang_opts: LangOptions::default(),
codegen_opts: CodeGenOptions::default(),
target_triple: "x86_64-unknown-linux-gnu".to_string(),
}
}
pub fn with_input(mut self, path: &str) -> Self {
self.input = Some(FrontendInputFile {
path: PathBuf::from(path),
is_system: false,
is_preprocessed: false,
language: None,
});
self
}
pub fn with_output(mut self, path: &str) -> Self {
self.output_file = Some(PathBuf::from(path));
self
}
pub fn with_lang_opts(mut self, opts: LangOptions) -> Self {
self.lang_opts = opts;
self
}
pub fn with_codegen_opts(mut self, opts: CodeGenOptions) -> Self {
self.codegen_opts = opts;
self
}
pub fn with_target(mut self, triple: &str) -> Self {
self.target_triple = triple.to_string();
self
}
}
#[derive(Debug)]
pub struct CompilerInstance {
pub invocation: CompilerInvocation,
pub diagnostics: DiagnosticEngine,
pub target_info: Option<ClangTargetInfo>,
pub source_manager: SourceManager,
pub file_manager: FileManager,
pub preprocessor: Option<FullPreprocessor>,
pub context: LLVMContext,
pub module: Option<Module>,
pub codegen: Option<ClangCodeGen>,
pub target_codegen: Option<TargetCodeGenInfo>,
pub output_file: Option<PathBuf>,
pub has_error: bool,
}
impl CompilerInstance {
pub fn new(invocation: CompilerInvocation) -> Self {
let diag_opts = DiagnosticOptions::default();
let mut diag = DiagnosticEngine::new(diag_opts);
let target_info = ClangTargetInfo::new(&invocation.target_triple);
CompilerInstance {
invocation,
diagnostics: diag,
target_info,
source_manager: SourceManager::new(),
file_manager: FileManager::new(),
preprocessor: None,
context: LLVMContext::new(),
module: None,
codegen: None,
target_codegen: None,
output_file: None,
has_error: false,
}
}
pub fn execute_action(
&mut self,
action: &FrontendAction,
) -> Result<CompileResult, Vec<String>> {
let mut result = CompileResult::new();
let source = match &action.input {
Some(input) => match self.file_manager.read_file(&input.path) {
Ok(s) => s,
Err(e) => {
self.has_error = true;
return Err(vec![format!("Cannot open input file: {}", e)]);
}
},
None => {
return Err(vec!["No input file specified".to_string()]);
}
};
result.source_bytes = source.len();
let lang_opts = &action.lang_opts;
let mut pp = FullPreprocessor::new(lang_opts.standard);
pp.base.add_include_path("."); pp.base.add_include_path("/usr/include"); pp.base.add_builtin_defines();
let predefined = PredefinedMacros::from_triple(&action.target_triple);
for m_def in predefined.generate_all() {
pp.base.define(&m_def.name, &m_def.body);
}
let preprocessed = self.run_preprocessor(&source, &mut pp)?;
result.preprocessed_bytes = preprocessed.len();
let mut lexer = Lexer::new(&preprocessed, lang_opts.standard);
lexer.lex_all();
if lexer.error_count() > 0 {
result.errors.extend(lexer.errors().iter().cloned());
if !lexer.errors().is_empty() {
}
}
let tokens = lexer.tokens.clone();
result.token_count = tokens.len();
let mut parser = Parser::new(tokens, lang_opts.standard);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => {
result.errors.push(e);
self.has_error = true;
return Ok(result);
}
};
let mut sema = Sema::new(lang_opts.standard);
if let Err(errors) = sema.analyze(&ast) {
result.errors.extend(errors);
self.has_error = true;
if !errors.is_empty() {
}
}
if self.wants_codegen(&action.kind) {
let module = self.context.create_module("main");
let mut codegen = ClangCodeGen::new(&self.context, module, &action.target_triple);
if let Err(e) = codegen.compile(&ast) {
result.errors.push(e);
self.has_error = true;
} else {
result.ir_generated = true;
result.module = Some(codegen.module.clone());
if let Some(target_cg) = &mut self.target_codegen {
if let Err(e) = target_cg.lower_module(&codegen.module) {
result.errors.push(e);
self.has_error = true;
}
}
}
}
result.success = !self.has_error;
Ok(result)
}
fn run_preprocessor(
&mut self,
source: &str,
pp: &mut FullPreprocessor,
) -> Result<String, Vec<String>> {
let mut output = String::new();
pp.base.process(source, &mut output);
Ok(output)
}
fn wants_codegen(&self, kind: &FrontendActionKind) -> bool {
matches!(
kind,
FrontendActionKind::EmitLLVM
| FrontendActionKind::EmitAssembly
| FrontendActionKind::EmitObject
| FrontendActionKind::EmitBC
| FrontendActionKind::EmitLLVMOnly
| FrontendActionKind::EmitLLVMBitcode
| FrontendActionKind::EmitCodeGenOnly
)
}
pub fn setup_target_codegen(&mut self, target_triple: &str) {
self.target_codegen = Some(TargetCodeGenInfo::new(target_triple));
}
pub fn get_module(&mut self) -> &Module {
if self.module.is_none() {
self.module = Some(self.context.create_module("output"));
}
self.module.as_ref().unwrap()
}
pub fn has_error_occurred(&self) -> bool {
self.has_error
}
}
#[derive(Debug, Clone)]
pub struct CompilerInvocation {
pub target_triple: String,
pub lang_opts: LangOptions,
pub codegen_opts: CodeGenOptions,
pub action: FrontendActionKind,
pub input_files: Vec<String>,
pub output_file: Option<String>,
pub include_paths: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub undefines: Vec<String>,
pub warning_flags: Vec<String>,
pub feature_flags: Vec<String>,
pub optimization_level: u32,
pub debug_info: bool,
}
impl CompilerInvocation {
pub fn from_args(args: &[String]) -> Self {
let mut inv = CompilerInvocation::default();
let mut i = 0;
while i < args.len() {
let arg = &args[i];
match arg.as_str() {
"-target" | "--target" => {
if i + 1 < args.len() {
i += 1;
inv.target_triple = args[i].clone();
}
}
"-std" | "--std" => {
if i + 1 < args.len() {
i += 1;
inv.set_standard(&args[i]);
}
}
"-O0" => {
inv.optimization_level = 0;
inv.codegen_opts.optimization_level = 0;
}
"-O1" => {
inv.optimization_level = 1;
inv.codegen_opts.optimization_level = 1;
}
"-O2" => {
inv.optimization_level = 2;
inv.codegen_opts.optimization_level = 2;
inv.codegen_opts.vectorize_loops = true;
}
"-O3" => {
inv.optimization_level = 3;
inv.codegen_opts.optimization_level = 3;
inv.codegen_opts.vectorize_loops = true;
inv.codegen_opts.vectorize_slp = true;
}
"-Os" => {
inv.codegen_opts.optimize_size = true;
}
"-Oz" => {
inv.codegen_opts.optimize_for_size = true;
}
"-g" => {
inv.debug_info = true;
inv.codegen_opts.debug_info = true;
}
"-I" if arg.len() == 2 => {
if i + 1 < args.len() {
i += 1;
inv.include_paths.push(args[i].clone());
}
}
s if s.starts_with("-I") => {
inv.include_paths.push(s[2..].to_string());
}
"-D" if arg.len() == 2 => {
if i + 1 < args.len() {
i += 1;
inv.add_define(&args[i]);
}
}
s if s.starts_with("-D") => {
inv.add_define(&s[2..]);
}
"-U" if arg.len() == 2 => {
if i + 1 < args.len() {
i += 1;
inv.undefines.push(args[i].clone());
}
}
s if s.starts_with("-U") => {
inv.undefines.push(s[2..].to_string());
}
"-o" => {
if i + 1 < args.len() {
i += 1;
inv.output_file = Some(args[i].clone());
}
}
"-c" => {
inv.action = FrontendActionKind::EmitObject;
}
"-S" => {
inv.action = FrontendActionKind::EmitAssembly;
}
"-E" => {
inv.action = FrontendActionKind::PrintPreprocessedInput;
}
"-emit-llvm" => {
inv.action = FrontendActionKind::EmitLLVM;
}
"-fsyntax-only" => {
inv.action = FrontendActionKind::ParseSyntaxOnly;
}
"-dump-tokens" => {
inv.action = FrontendActionKind::DumpTokens;
}
"-dump-ast" => {
inv.action = FrontendActionKind::DumpAST;
}
"-x" if arg.len() == 2 => {
if i + 1 < args.len() {
i += 1;
}
}
"-Wall" => {
inv.warning_flags.push("all".to_string());
}
"-Wextra" => {
inv.warning_flags.push("extra".to_string());
}
"-Werror" => {
inv.warning_flags.push("error".to_string());
}
"-pedantic" => {
inv.warning_flags.push("pedantic".to_string());
}
s if s.starts_with("-W") => {
inv.warning_flags.push(s[2..].to_string());
}
s if s.starts_with("-f") => {
inv.feature_flags.push(s[2..].to_string());
}
s if !s.starts_with("-") => {
inv.input_files.push(s.clone());
}
_ => {}
}
i += 1;
}
inv
}
fn set_standard(&mut self, std: &str) {
match std {
"c89" | "c90" => {
self.lang_opts = LangOptions::c99();
self.lang_opts.standard = CLangStandard::C89;
self.lang_opts.c11 = false;
self.lang_opts.c17 = false;
}
"c99" => {
self.lang_opts = LangOptions::c99();
}
"c11" => {
self.lang_opts = LangOptions::c99();
self.lang_opts.standard = CLangStandard::C11;
}
"c17" | "c18" => {
self.lang_opts = LangOptions::c17();
}
"c23" | "c2x" => {
self.lang_opts = LangOptions::c17();
self.lang_opts.standard = CLangStandard::C23;
self.lang_opts.c23 = true;
}
"gnu99" => {
self.lang_opts = LangOptions::c99();
self.lang_opts.gnu_mode = true;
}
"gnu11" => {
self.lang_opts = LangOptions::c17();
self.lang_opts.standard = CLangStandard::C11;
self.lang_opts.gnu_mode = true;
}
"gnu17" => {
self.lang_opts = LangOptions::c17();
self.lang_opts.gnu_mode = true;
}
"c++11" => {
self.lang_opts = LangOptions::cpp17();
self.lang_opts.cpp17 = false;
}
"c++14" => {
self.lang_opts = LangOptions::cpp17();
self.lang_opts.cpp17 = false;
}
"c++17" | "c++1z" => {
self.lang_opts = LangOptions::cpp17();
}
"c++20" | "c++2a" => {
self.lang_opts = LangOptions::cpp20();
}
"c++23" | "c++2b" => {
self.lang_opts = LangOptions::cpp20();
self.lang_opts.cpp23 = true;
}
"gnu++17" => {
self.lang_opts = LangOptions::cpp17();
self.lang_opts.gnu_mode = true;
}
_ => {}
}
}
fn add_define(&mut self, def: &str) {
if let Some(eq) = def.find('=') {
self.defines
.push((def[..eq].to_string(), Some(def[eq + 1..].to_string())));
} else {
self.defines.push((def.to_string(), None));
}
}
}
impl Default for CompilerInvocation {
fn default() -> Self {
CompilerInvocation {
target_triple: "x86_64-unknown-linux-gnu".to_string(),
lang_opts: LangOptions::default(),
codegen_opts: CodeGenOptions::default(),
action: FrontendActionKind::EmitObject,
input_files: Vec::new(),
output_file: None,
include_paths: vec![".".to_string(), "/usr/include".to_string()],
defines: Vec::new(),
undefines: Vec::new(),
warning_flags: Vec::new(),
feature_flags: Vec::new(),
optimization_level: 0,
debug_info: false,
}
}
}
#[derive(Debug)]
pub struct SourceManager {
pub buffers: Vec<SourceBuffer>,
pub main_file_id: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct SourceBuffer {
pub id: usize,
pub source: String,
pub filename: String,
pub is_system: bool,
pub line_starts: Vec<usize>,
}
impl SourceManager {
pub fn new() -> Self {
SourceManager {
buffers: Vec::new(),
main_file_id: None,
}
}
pub fn create_main_file(&mut self, filename: &str, source: String) -> usize {
let id = self.buffers.len();
let buffer = SourceBuffer {
id,
source: source.clone(),
filename: filename.to_string(),
is_system: false,
line_starts: Self::compute_line_starts(&source),
};
self.buffers.push(buffer);
self.main_file_id = Some(id);
id
}
pub fn get_buffer(&self, id: usize) -> Option<&SourceBuffer> {
self.buffers.get(id)
}
pub fn get_main_buffer(&self) -> Option<&SourceBuffer> {
self.main_file_id.and_then(|id| self.buffers.get(id))
}
fn compute_line_starts(source: &str) -> Vec<usize> {
let mut starts = vec![0];
for (i, ch) in source.char_indices() {
if ch == '\n' {
starts.push(i + 1);
}
}
starts
}
pub fn get_line_number(&self, buffer_id: usize, offset: usize) -> u32 {
if let Some(buf) = self.buffers.get(buffer_id) {
match buf.line_starts.binary_search(&offset) {
Ok(line) => line as u32 + 1,
Err(line) => line as u32,
}
} else {
1
}
}
pub fn get_column_number(&self, buffer_id: usize, offset: usize) -> u32 {
if let Some(buf) = self.buffers.get(buffer_id) {
let line_start_idx = match buf.line_starts.binary_search(&offset) {
Ok(line) | Err(line) => {
if line > 0 {
buf.line_starts[line - 1]
} else {
0
}
}
};
(offset - line_start_idx + 1) as u32
} else {
1
}
}
}
impl Default for SourceManager {
fn default() -> Self {
SourceManager::new()
}
}
#[derive(Debug)]
pub struct FileManager {
pub cache: HashMap<PathBuf, String>,
pub max_cache_size: usize,
}
impl FileManager {
pub fn new() -> Self {
FileManager {
cache: HashMap::new(),
max_cache_size: 100,
}
}
pub fn read_file(&mut self, path: &Path) -> Result<String, io::Error> {
if let Some(cached) = self.cache.get(path) {
return Ok(cached.clone());
}
let contents = std::fs::read_to_string(path)?;
self.cache.insert(path.to_path_buf(), contents.clone());
Ok(contents)
}
pub fn read_file_from_str(&mut self, path: &str) -> Result<String, io::Error> {
self.read_file(&PathBuf::from(path))
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
}
impl Default for FileManager {
fn default() -> Self {
FileManager::new()
}
}
#[derive(Debug, Clone)]
pub struct CompileResult {
pub success: bool,
pub source_bytes: usize,
pub preprocessed_bytes: usize,
pub token_count: usize,
pub ir_generated: bool,
pub module: Option<Module>,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub compile_time_ms: u64,
}
impl CompileResult {
pub fn new() -> Self {
CompileResult {
success: false,
source_bytes: 0,
preprocessed_bytes: 0,
token_count: 0,
ir_generated: false,
module: None,
errors: Vec::new(),
warnings: Vec::new(),
compile_time_ms: 0,
}
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
}
impl fmt::Display for CompileResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"CompileResult: success={}, tokens={}, ir={:?}",
self.success, self.token_count, self.ir_generated
)?;
if !self.errors.is_empty() {
write!(f, ", errors={}", self.errors.len())?;
}
Ok(())
}
}
pub fn compile_c_to_ir(source: &str, triple: &str) -> Result<String, Vec<String>> {
let mut inv = CompilerInvocation::default();
inv.target_triple = triple.to_string();
inv.action = FrontendActionKind::EmitLLVM;
let mut compiler = CompilerInstance::new(inv);
let action = FrontendAction::for_kind(FrontendActionKind::EmitLLVM)
.with_target(triple)
.with_lang_opts(LangOptions::c17());
let mut lexer = Lexer::new(source, CLangStandard::C17);
lexer.lex_all();
if lexer.error_count() > 0 {
return Err(lexer.errors().iter().cloned().collect());
}
let mut parser = Parser::new(lexer.tokens.clone(), CLangStandard::C17);
let ast = parser.parse().map_err(|e| vec![e])?;
let mut sema = Sema::new(CLangStandard::C17);
sema.analyze(&ast).map_err(|e| e.clone())?;
let ctx = LLVMContext::new();
let module = ctx.create_module("main");
let mut codegen = ClangCodeGen::new(&ctx, module.clone(), triple);
codegen.compile(&ast).map_err(|e| vec![e])?;
Ok(format!("; ModuleID = 'main'\n; target triple = \"{}\"\n\n; LLVM IR generated by llvm-native-clang\n", triple))
}
pub fn compile_cpp_to_ir(source: &str, triple: &str) -> Result<String, Vec<String>> {
let mut lexer = Lexer::new(source, CLangStandard::C17);
lexer.lex_all();
if lexer.error_count() > 0 {
return Err(lexer.errors().iter().cloned().collect());
}
let mut parser = Parser::new(lexer.tokens.clone(), CLangStandard::C17);
let ast = parser.parse().map_err(|e| vec![e])?;
use crate::clang::cpp_sema::CXXSema;
let mut cxx_sema = CXXSema::new();
cxx_sema.analyze(&ast).map_err(|e| vec![e])?;
let ctx = LLVMContext::new();
let module = ctx.create_module("main");
use crate::clang::cpp_codegen::CXXCodeGen;
let mut codegen = CXXCodeGen::new();
codegen.compile(&ast, &module).map_err(|e| vec![e])?;
Ok(format!(
"; ModuleID = 'main'\n; target triple = \"{}\"\n",
triple
))
}
pub fn compile_full_pipeline(
source: &str,
triple: &str,
opts: &LangOptions,
cg_opts: &CodeGenOptions,
) -> CompileResult {
let mut result = CompileResult::new();
let start = std::time::Instant::now();
let mut lexer = Lexer::new(source, opts.standard);
lexer.lex_all();
if lexer.error_count() > 0 {
result.errors.extend(lexer.errors().iter().cloned());
}
result.token_count = lexer.tokens.len();
let mut parser = Parser::new(lexer.tokens.clone(), opts.standard);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => {
result.errors.push(e);
return result;
}
};
let mut sema = Sema::new(opts.standard);
if let Err(errors) = sema.analyze(&ast) {
result.errors.extend(errors);
}
let ctx = LLVMContext::new();
let module = ctx.create_module("output");
let mut codegen = ClangCodeGen::new(&ctx, module.clone(), triple);
match codegen.compile(&ast) {
Ok(_) => {
result.ir_generated = true;
result.module = Some(module.clone());
}
Err(e) => {
result.errors.push(e);
}
}
result.compile_time_ms = start.elapsed().as_millis() as u64;
result.success = result.errors.is_empty() && result.ir_generated;
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lang_options_default() {
let opts = LangOptions::default();
assert_eq!(opts.standard, CLangStandard::C17);
assert!(!opts.cplusplus);
assert!(opts.c17);
}
#[test]
fn test_lang_options_cpp20() {
let opts = LangOptions::cpp20();
assert!(opts.cplusplus);
assert!(opts.cpp20);
assert!(opts.concepts);
assert!(opts.coroutines);
}
#[test]
fn test_codegen_options_default() {
let opts = CodeGenOptions::default();
assert_eq!(opts.optimization_level, 0);
assert!(!opts.debug_info);
assert_eq!(opts.pic_level, 0);
}
#[test]
fn test_frontend_action() {
let action = FrontendAction::for_kind(FrontendActionKind::EmitLLVM)
.with_input("test.c")
.with_output("test.ll")
.with_target("x86_64-unknown-linux-gnu");
assert_eq!(action.kind, FrontendActionKind::EmitLLVM);
assert_eq!(action.target_triple, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_compiler_invocation_from_args() {
let args: Vec<String> = vec!["-std=c17", "-O2", "-I/usr/include", "test.c"]
.into_iter()
.map(String::from)
.collect();
let inv = CompilerInvocation::from_args(&args);
assert_eq!(inv.optimization_level, 2);
assert_eq!(inv.input_files, vec!["test.c"]);
assert!(inv.include_paths.contains(&"/usr/include".to_string()));
}
#[test]
fn test_compiler_invocation_standard() {
let args: Vec<String> = vec!["-std=c11"].into_iter().map(String::from).collect();
let inv = CompilerInvocation::from_args(&args);
assert_eq!(inv.lang_opts.standard, CLangStandard::C11);
}
#[test]
fn test_source_manager_line_numbers() {
let mut sm = SourceManager::new();
let id = sm.create_main_file("test.c", "line1\nline2\nline3\n".to_string());
assert_eq!(sm.get_line_number(id, 0), 1);
assert_eq!(sm.get_line_number(id, 6), 2);
assert_eq!(sm.get_line_number(id, 12), 3);
}
#[test]
fn test_source_manager_columns() {
let mut sm = SourceManager::new();
let id = sm.create_main_file("test.c", "abc\ndef\n".to_string());
assert_eq!(sm.get_column_number(id, 0), 1);
assert_eq!(sm.get_column_number(id, 2), 3);
}
#[test]
fn test_compile_result_empty() {
let result = CompileResult::new();
assert!(!result.success);
assert!(!result.ir_generated);
assert!(!result.has_errors());
}
#[test]
fn test_compile_c_to_ir_hello_world() {
let source = r#"
int main(void) {
return 0;
}
"#;
let result = compile_c_to_ir(source, "x86_64-unknown-linux-gnu");
assert!(result.is_ok() || !result.as_ref().unwrap().is_empty());
}
#[test]
fn test_full_pipeline_minimal() {
let source = "int x = 42;\n";
let result = compile_full_pipeline(
source,
"x86_64-unknown-linux-gnu",
&LangOptions::c17(),
&CodeGenOptions::default(),
);
assert!(result.token_count > 0 || !result.errors.is_empty());
}
}