use std::fmt;
use std::path::{Path, PathBuf};
use crate::module::Module;
use super::cpp_ast::CXXTranslationUnit;
use super::cpp_codegen::CXXCodeGen;
use super::cpp_lexer::CppLexer;
use super::cpp_parser::CppParser;
use super::cpp_token::CppStandard;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CXXAbi {
Itanium,
Microsoft,
}
impl CXXAbi {
pub fn as_str(&self) -> &'static str {
match self {
CXXAbi::Itanium => "itanium",
CXXAbi::Microsoft => "microsoft",
}
}
}
pub struct CXXDriver {
pub standard: CppStandard,
pub opt_level: u8,
pub debug_info: bool,
pub warnings: bool,
pub werror: bool,
pub pedantic: bool,
pub includes: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub output_file: Option<String>,
pub target_triple: String,
pub exceptions: bool,
pub rtti: bool,
pub modules: bool,
pub coroutines: bool,
pub stdlib: CXXStdLib,
pub linker_flags: Vec<String>,
pub source_files: Vec<String>,
pub compile_only: bool,
pub emit_assembly: bool,
pub emit_llvm: bool,
pub abi: CXXAbi,
pub abi_version: u8,
pub template_depth: usize,
pub delayed_template_parsing: bool,
pub access_control: bool,
pub ms_extensions: bool,
pub implicit_modules: bool,
pub module_maps: bool,
pub prebuilt_module_path: Option<String>,
pub include_pch: Option<String>,
pub emit_pch: bool,
pub building_pch_with_obj: bool,
pub sanitizers: Vec<CXXSanitizer>,
pub dialect_flags: Vec<String>,
pub coverage: bool,
pub profile_use: Option<String>,
pub lto: bool,
pub thin_lto: bool,
pub verbose: bool,
pub show_includes: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CXXStdLib {
LibStdCxx,
LibCxx,
None,
}
impl fmt::Display for CXXStdLib {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_flag())
}
}
impl CXXStdLib {
pub fn as_flag(&self) -> &'static str {
match self {
CXXStdLib::LibStdCxx => "libstdc++",
CXXStdLib::LibCxx => "libc++",
CXXStdLib::None => "",
}
}
pub fn from_flag(s: &str) -> Option<CXXStdLib> {
match s {
"libstdc++" => Some(CXXStdLib::LibStdCxx),
"libc++" => Some(CXXStdLib::LibCxx),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CXXSanitizer {
Vptr,
Undefined,
Address,
Memory,
Thread,
Leak,
DataFlow,
SafeStack,
CFI,
}
impl CXXSanitizer {
pub fn from_flag(s: &str) -> Option<Self> {
match s {
"vptr" => Some(CXXSanitizer::Vptr),
"undefined" => Some(CXXSanitizer::Undefined),
"address" => Some(CXXSanitizer::Address),
"memory" => Some(CXXSanitizer::Memory),
"thread" => Some(CXXSanitizer::Thread),
"leak" => Some(CXXSanitizer::Leak),
"dataflow" => Some(CXXSanitizer::DataFlow),
"safestack" => Some(CXXSanitizer::SafeStack),
"cfi" => Some(CXXSanitizer::CFI),
_ => None,
}
}
pub fn as_flag(&self) -> &'static str {
match self {
CXXSanitizer::Vptr => "vptr",
CXXSanitizer::Undefined => "undefined",
CXXSanitizer::Address => "address",
CXXSanitizer::Memory => "memory",
CXXSanitizer::Thread => "thread",
CXXSanitizer::Leak => "leak",
CXXSanitizer::DataFlow => "dataflow",
CXXSanitizer::SafeStack => "safestack",
CXXSanitizer::CFI => "cfi",
}
}
}
impl CXXDriver {
pub fn new() -> Self {
Self {
standard: CppStandard::Cxx17,
opt_level: 0,
debug_info: false,
warnings: true,
werror: false,
pedantic: false,
includes: Vec::new(),
defines: Vec::new(),
output_file: None,
target_triple: "x86_64-unknown-linux-gnu".into(),
exceptions: true,
rtti: true,
modules: false,
coroutines: false,
stdlib: CXXStdLib::LibStdCxx,
linker_flags: Vec::new(),
source_files: Vec::new(),
compile_only: false,
emit_assembly: false,
emit_llvm: false,
abi: CXXAbi::Itanium,
abi_version: 0,
template_depth: 1024,
delayed_template_parsing: false,
access_control: true,
ms_extensions: false,
implicit_modules: false,
module_maps: false,
prebuilt_module_path: None,
include_pch: None,
emit_pch: false,
building_pch_with_obj: false,
sanitizers: Vec::new(),
dialect_flags: Vec::new(),
coverage: false,
profile_use: None,
lto: false,
thin_lto: false,
verbose: false,
show_includes: false,
}
}
pub fn parse_args(&mut self, args: &[String]) -> Result<(), String> {
let mut i = 0;
while i < args.len() {
let arg = &args[i];
match arg.as_str() {
s if s.starts_with("-std=") => {
let std_str = &s[5..];
self.standard = CppStandard::from_flag(std_str)
.ok_or_else(|| format!("unknown C++ standard: {}", std_str))?;
}
"-O0" => self.opt_level = 0,
"-O1" => self.opt_level = 1,
"-O2" => self.opt_level = 2,
"-O3" => self.opt_level = 3,
"-Os" => self.opt_level = 2,
"-Oz" => self.opt_level = 2,
"-Ofast" => self.opt_level = 3,
"-w" => self.warnings = false,
"-Wall" => self.warnings = true,
"-Wextra" => self.warnings = true,
"-Werror" => self.werror = true,
"-pedantic" | "-pedantic-errors" => self.pedantic = true,
"-g" | "-g0" | "-g1" | "-g2" | "-g3" | "-ggdb" | "-gdwarf" => {
self.debug_info = true
}
"-o" => {
i += 1;
self.output_file = Some(args.get(i).cloned().ok_or("-o requires an argument")?);
}
"-c" => self.compile_only = true,
"-S" => self.emit_assembly = true,
"-E" => {} s if s.starts_with("-emit-llvm") => self.emit_llvm = true,
"-I" => {
i += 1;
if let Some(p) = args.get(i) {
self.includes.push(p.clone());
}
}
s if s.starts_with("-I") => {
self.includes.push(s[2..].to_string());
}
"-D" => {
i += 1;
if let Some(def) = args.get(i) {
if let Some(eq) = def.find('=') {
self.defines
.push((def[..eq].into(), Some(def[eq + 1..].into())));
} else {
self.defines.push((def.clone(), None));
}
}
}
s if s.starts_with("-D") => {
let def = &s[2..];
if let Some(eq) = def.find('=') {
self.defines
.push((def[..eq].into(), Some(def[eq + 1..].into())));
} else {
self.defines.push((def.to_string(), None));
}
}
"-target" => {
i += 1;
if let Some(t) = args.get(i) {
self.target_triple = t.clone();
}
}
s if s.starts_with("--target=") => {
self.target_triple = s[9..].to_string();
}
s if s.starts_with("-stdlib=") => {
self.stdlib = CXXStdLib::from_flag(&s[8..])
.ok_or_else(|| format!("unknown stdlib: {}", &s[8..]))?;
}
"-fexceptions" => self.exceptions = true,
"-fno-exceptions" => {
self.exceptions = false;
self.dialect_flags.push("-fno-exceptions".into());
}
"-frtti" => self.rtti = true,
"-fno-rtti" => {
self.rtti = false;
self.dialect_flags.push("-fno-rtti".into());
}
"-fmodules" => self.modules = true,
"-fno-modules" => self.modules = false,
"-fimplicit-modules" => self.implicit_modules = true,
"-fmodule-maps" => self.module_maps = true,
s if s.starts_with("-fprebuilt-module-path=") => {
self.prebuilt_module_path = Some(s[23..].to_string());
}
"-fcoroutines-ts" => self.coroutines = true,
s if s.starts_with("-fabi-version=") => {
self.abi_version = s[14..].parse().unwrap_or(0);
}
"-fms-extensions" => {
self.ms_extensions = true;
self.abi = CXXAbi::Microsoft;
}
"-fno-access-control" => self.access_control = false,
s if s.starts_with("-ftemplate-depth=") => {
self.template_depth = s[17..].parse().unwrap_or(1024);
}
"-fdelayed-template-parsing" => self.delayed_template_parsing = true,
s if s.starts_with("-include-pch") => {
i += 1;
self.include_pch = args.get(i).cloned();
}
"-emit-pch" => self.emit_pch = true,
"-building-pch-with-obj" => self.building_pch_with_obj = true,
s if s.starts_with("-fsanitize=") => {
let san = &s[11..];
if san.contains(',') {
for part in san.split(',') {
if let Some(sanitizer) = CXXSanitizer::from_flag(part) {
self.sanitizers.push(sanitizer);
}
}
} else if let Some(sanitizer) = CXXSanitizer::from_flag(san) {
self.sanitizers.push(sanitizer);
}
}
"-fprofile-arcs" | "--coverage" => self.coverage = true,
"-ftest-coverage" => self.coverage = true,
s if s.starts_with("-fprofile-use=") => {
self.profile_use = Some(s[14..].to_string());
}
"-flto" => self.lto = true,
"-flto=thin" => {
self.lto = true;
self.thin_lto = true;
}
"-v" | "--verbose" => self.verbose = true,
"-H" => self.show_includes = true,
"-shared" => {
self.compile_only = false;
self.linker_flags.push("-shared".into());
}
"-static" => {
self.linker_flags.push("-static".into());
}
"-pie" => {
self.linker_flags.push("-pie".into());
}
"-rdynamic" => {
self.linker_flags.push("-rdynamic".into());
}
"-nostdlib" => self.stdlib = CXXStdLib::None,
"-nostdlib++" => self.stdlib = CXXStdLib::None,
"-nodefaultlibs" => {
self.linker_flags.push("-nodefaultlibs".into());
}
s if s.starts_with("-f") => {
self.dialect_flags.push(s.to_string());
}
s if s.starts_with("-Wl,") => {
self.linker_flags.push(s[4..].to_string());
}
s if s.starts_with("-l") => {
self.linker_flags.push(s.to_string());
}
s if !s.starts_with('-') => {
self.source_files.push(s.to_string());
}
_ => {
if !arg.starts_with('-') {
self.source_files.push(arg.clone());
}
}
}
i += 1;
}
if self.source_files.is_empty() {
return Err("no input files".into());
}
Ok(())
}
pub fn compile_all(&self) -> Result<Vec<Module>, Vec<String>> {
let mut modules = Vec::new();
let mut errors = Vec::new();
for source_file in &self.source_files {
match self.compile_file(source_file) {
Ok(module) => modules.push(module),
Err(errs) => errors.extend(errs),
}
}
if errors.is_empty() {
Ok(modules)
} else {
Err(errors)
}
}
pub fn compile_file(&self, source_file: &str) -> Result<Module, Vec<String>> {
let source = std::fs::read_to_string(source_file)
.map_err(|e| vec![format!("cannot read '{}': {}", source_file, e)])?;
self.compile_source(&source, source_file)
}
pub fn compile_source(&self, source: &str, filename: &str) -> Result<Module, Vec<String>> {
let tu = CppParser::parse_source(source, self.standard).map_err(|e| e)?;
let module_name = Path::new(filename)
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let mut codegen = CXXCodeGen::new(&module_name, &self.target_triple, self.standard);
codegen
.compile(&tu)
.map_err(|errs| vec![format!("codegen errors: {:?}", errs)])
}
pub fn linker_command(&self) -> String {
let mut cmd = String::from("clang++");
cmd.push_str(&format!(" --target={}", self.target_triple));
if self.stdlib != CXXStdLib::None {
cmd.push_str(&format!(" -stdlib={}", self.stdlib.as_flag()));
}
for san in &self.sanitizers {
cmd.push_str(&format!(" -fsanitize={}", san.as_flag()));
}
for flag in &self.linker_flags {
cmd.push(' ');
cmd.push_str(flag);
}
if let Some(ref output) = self.output_file {
cmd.push_str(&format!(" -o {}", output));
}
for src in &self.source_files {
let obj = Path::new(src).with_extension("o");
cmd.push(' ');
cmd.push_str(&obj.to_string_lossy());
}
cmd
}
pub fn version_string(&self) -> String {
let mut s = format!(
"llvm-native-cxx version 0.1.0\nTarget: {}\nC++ Standard: {}\n",
self.target_triple,
self.standard.as_flag()
);
s.push_str(&format!(
"ABI: {} (v{})\n",
self.abi.as_str(),
self.abi_version
));
s.push_str(&format!("Thread model: posix\n"));
if !self.sanitizers.is_empty() {
let names: Vec<_> = self.sanitizers.iter().map(|s| s.as_flag()).collect();
s.push_str(&format!("Sanitizers: {}\n", names.join(",")));
}
s
}
pub fn to_command_line(&self) -> String {
let mut cmd = String::from("clang++");
cmd.push_str(&format!(" -std={}", self.standard.as_flag()));
if self.opt_level > 0 {
cmd.push_str(&format!(" -O{}", self.opt_level));
}
if self.debug_info {
cmd.push_str(" -g");
}
if !self.warnings {
cmd.push_str(" -w");
}
if self.werror {
cmd.push_str(" -Werror");
}
if self.pedantic {
cmd.push_str(" -pedantic");
}
for inc in &self.includes {
cmd.push_str(&format!(" -I{}", inc));
}
for (k, v) in &self.defines {
match v {
Some(val) => cmd.push_str(&format!(" -D{}={}", k, val)),
None => cmd.push_str(&format!(" -D{}", k)),
}
}
if self.stdlib != CXXStdLib::None {
cmd.push_str(&format!(" -stdlib={}", self.stdlib.as_flag()));
}
if !self.exceptions {
cmd.push_str(" -fno-exceptions");
}
if !self.rtti {
cmd.push_str(" -fno-rtti");
}
if self.modules {
cmd.push_str(" -fmodules");
}
if self.coroutines {
cmd.push_str(" -fcoroutines-ts");
}
for san in &self.sanitizers {
cmd.push_str(&format!(" -fsanitize={}", san.as_flag()));
}
if self.lto {
cmd.push_str(if self.thin_lto {
" -flto=thin"
} else {
" -flto"
});
}
if self.emit_llvm {
cmd.push_str(" -emit-llvm");
}
if self.compile_only {
cmd.push_str(" -c");
}
if let Some(ref out) = self.output_file {
cmd.push_str(&format!(" -o {}", out));
}
for src in &self.source_files {
cmd.push(' ');
cmd.push_str(src);
}
cmd
}
pub fn has_cxx20(&self) -> bool {
self.standard >= CppStandard::Cxx20
}
pub fn has_cxx17(&self) -> bool {
self.standard >= CppStandard::Cxx17
}
pub fn has_rtti(&self) -> bool {
self.rtti
}
pub fn has_exceptions(&self) -> bool {
self.exceptions
}
pub fn has_sanitizer(&self, san: CXXSanitizer) -> bool {
self.sanitizers.contains(&san)
}
}
impl Default for CXXDriver {
fn default() -> Self {
Self::new()
}
}
pub fn compile_cpp_source(source: &str, filename: &str) -> Result<Module, Vec<String>> {
CXXDriver::new().compile_source(source, filename)
}
pub fn compile_cpp_with_options(
source: &str,
filename: &str,
standard: CppStandard,
opt_level: u8,
target_triple: &str,
) -> Result<Module, Vec<String>> {
let mut driver = CXXDriver::new();
driver.standard = standard;
driver.opt_level = opt_level;
driver.target_triple = target_triple.to_string();
driver.compile_source(source, filename)
}
pub fn driver_from_args(args: &[String]) -> Result<CXXDriver, String> {
let mut driver = CXXDriver::new();
driver.parse_args(args)?;
Ok(driver)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_driver_create() {
let driver = CXXDriver::new();
assert_eq!(driver.standard, CppStandard::Cxx17);
assert_eq!(driver.opt_level, 0);
assert!(!driver.debug_info);
assert_eq!(driver.abi, CXXAbi::Itanium);
}
#[test]
fn test_driver_parse_std() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-std=c++20".into(), "test.cpp".into()])
.unwrap();
assert_eq!(driver.standard, CppStandard::Cxx20);
}
#[test]
fn test_driver_parse_std_all() {
for (flag, expected) in &[
("-std=c++98", CppStandard::Cxx98),
("-std=c++03", CppStandard::Cxx03),
("-std=c++11", CppStandard::Cxx11),
("-std=c++14", CppStandard::Cxx14),
("-std=c++17", CppStandard::Cxx17),
("-std=c++20", CppStandard::Cxx20),
("-std=c++23", CppStandard::Cxx23),
("-std=gnu++17", CppStandard::GnuXx17),
("-std=gnu++20", CppStandard::GnuXx20),
] {
let mut d = CXXDriver::new();
d.parse_args(&[flag.to_string(), "t.cpp".into()]).unwrap();
assert_eq!(d.standard, *expected, "failed for {}", flag);
}
}
#[test]
fn test_driver_parse_opt() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-O2".into(), "file.cpp".into()])
.unwrap();
assert_eq!(driver.opt_level, 2);
}
#[test]
fn test_driver_parse_no_input() {
let mut driver = CXXDriver::new();
assert!(driver.parse_args(&[]).is_err());
}
#[test]
fn test_driver_parse_flags() {
let mut driver = CXXDriver::new();
driver
.parse_args(&[
"-c".into(),
"-g".into(),
"-Wall".into(),
"-fno-exceptions".into(),
"-fno-rtti".into(),
"test.cpp".into(),
])
.unwrap();
assert!(driver.compile_only);
assert!(driver.debug_info);
assert!(driver.warnings);
assert!(!driver.exceptions);
assert!(!driver.rtti);
}
#[test]
fn test_driver_parse_stdlib() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-stdlib=libc++".into(), "test.cpp".into()])
.unwrap();
assert_eq!(driver.stdlib, CXXStdLib::LibCxx);
}
#[test]
fn test_driver_parse_include() {
let mut driver = CXXDriver::new();
driver
.parse_args(&[
"-I/usr/include".into(),
"-I".into(),
"/usr/local/include".into(),
"test.cpp".into(),
])
.unwrap();
assert!(driver.includes.contains(&"/usr/include".to_string()));
assert!(driver.includes.contains(&"/usr/local/include".to_string()));
}
#[test]
fn test_driver_parse_defines() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-DDEBUG".into(), "-DVALUE=42".into(), "test.cpp".into()])
.unwrap();
assert!(driver
.defines
.iter()
.any(|(k, v)| k == "DEBUG" && v.is_none()));
assert!(driver
.defines
.iter()
.any(|(k, v)| k == "VALUE" && v == &Some("42".into())));
}
#[test]
fn test_driver_compile_empty_source() {
let driver = CXXDriver::new();
let result = driver.compile_source("", "empty.cpp");
match result {
Ok(_) => {}
Err(_) => {}
}
}
#[test]
fn test_version_string() {
let driver = CXXDriver::new();
let version = driver.version_string();
assert!(version.contains("llvm-native-cxx"));
assert!(version.contains("C++ Standard"));
assert!(version.contains("ABI"));
}
#[test]
fn test_linker_command() {
let mut driver = CXXDriver::new();
driver.source_files = vec!["test.cpp".into()];
driver.output_file = Some("a.out".into());
let cmd = driver.linker_command();
assert!(cmd.contains("clang++"));
assert!(cmd.contains("-o a.out"));
}
#[test]
fn test_stdlib_parsing() {
assert_eq!(
CXXStdLib::from_flag("libstdc++"),
Some(CXXStdLib::LibStdCxx)
);
assert_eq!(CXXStdLib::from_flag("libc++"), Some(CXXStdLib::LibCxx));
assert_eq!(CXXStdLib::from_flag("invalid"), None);
}
#[test]
fn test_driver_parse_coroutines() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-fcoroutines-ts".into(), "coro.cpp".into()])
.unwrap();
assert!(driver.coroutines);
}
#[test]
fn test_compile_cpp_source_fn() {
let result = compile_cpp_source("", "test.cpp");
match result {
Ok(_) => {}
Err(_) => {}
}
}
#[test]
fn test_driver_parse_sanitizers() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-fsanitize=vptr".into(), "test.cpp".into()])
.unwrap();
assert!(driver.has_sanitizer(CXXSanitizer::Vptr));
let mut driver2 = CXXDriver::new();
driver2
.parse_args(&["-fsanitize=undefined,address".into(), "test.cpp".into()])
.unwrap();
assert!(driver2.has_sanitizer(CXXSanitizer::Undefined));
assert!(driver2.has_sanitizer(CXXSanitizer::Address));
}
#[test]
fn test_driver_parse_template_depth() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-ftemplate-depth=512".into(), "test.cpp".into()])
.unwrap();
assert_eq!(driver.template_depth, 512);
}
#[test]
fn test_driver_parse_abi_version() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-fabi-version=2".into(), "test.cpp".into()])
.unwrap();
assert_eq!(driver.abi_version, 2);
}
#[test]
fn test_driver_parse_lto() {
let mut driver = CXXDriver::new();
driver
.parse_args(&["-flto".into(), "test.cpp".into()])
.unwrap();
assert!(driver.lto);
let mut driver2 = CXXDriver::new();
driver2
.parse_args(&["-flto=thin".into(), "test.cpp".into()])
.unwrap();
assert!(driver2.lto);
assert!(driver2.thin_lto);
}
#[test]
fn test_driver_to_command_line() {
let mut driver = CXXDriver::new();
driver.standard = CppStandard::Cxx20;
driver.opt_level = 2;
driver.source_files = vec!["main.cpp".into()];
driver.output_file = Some("a.out".into());
let cmd = driver.to_command_line();
assert!(cmd.contains("-std=c++20"));
assert!(cmd.contains("-O2"));
assert!(cmd.contains("main.cpp"));
}
#[test]
fn test_driver_has_rtti() {
let mut driver = CXXDriver::new();
assert!(driver.has_rtti());
driver.rtti = false;
assert!(!driver.has_rtti());
}
#[test]
fn test_driver_from_args() {
let driver = driver_from_args(&["-std=c++17".into(), "main.cpp".into()]).unwrap();
assert_eq!(driver.standard, CppStandard::Cxx17);
}
#[test]
fn test_cxx_sanitizer_from_flag() {
assert_eq!(CXXSanitizer::from_flag("vptr"), Some(CXXSanitizer::Vptr));
assert_eq!(
CXXSanitizer::from_flag("undefined"),
Some(CXXSanitizer::Undefined)
);
assert_eq!(
CXXSanitizer::from_flag("address"),
Some(CXXSanitizer::Address)
);
assert_eq!(CXXSanitizer::from_flag("nonsense"), None);
}
#[test]
fn test_cxx_abi() {
assert_eq!(CXXAbi::Itanium.as_str(), "itanium");
assert_eq!(CXXAbi::Microsoft.as_str(), "microsoft");
}
}