use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;
use std::time::{Duration, SystemTime};
use crate::clang::*;
use crate::triple::{Arch, Environment, Triple, Vendor, OS};
use crate::x86::*;
#[derive(Debug, Clone)]
pub struct X86HostInfo {
pub vendor: String,
pub brand: String,
pub family: u32,
pub model: u32,
pub stepping: u32,
pub features: HashSet<String>,
pub architecture_level: X86ArchitectureLevel,
pub cache_l1_data_kb: u32,
pub cache_l1_inst_kb: u32,
pub cache_l2_kb: u32,
pub cache_l3_kb: u32,
pub cache_line_size: u32,
pub physical_cores: u32,
pub logical_processors: u32,
pub base_frequency_mhz: u32,
pub max_frequency_mhz: u32,
pub vector_widths: Vec<u32>,
pub sched_model: String,
pub is_hybrid: bool,
pub e_core_model: Option<String>,
pub uarch_codename: String,
pub has_avx512: bool,
pub has_amx: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86ArchitectureLevel {
V1,
V2,
V3,
V4,
}
impl X86ArchitectureLevel {
pub fn as_str(&self) -> &'static str {
match self {
Self::V1 => "x86-64",
Self::V2 => "x86-64-v2",
Self::V3 => "x86-64-v3",
Self::V4 => "x86-64-v4",
}
}
pub fn required_features(&self) -> &'static [&'static str] {
match self {
Self::V1 => &[
"cmov", "cx8", "fpu", "fxsr", "mmx", "syscall", "sse", "sse2",
],
Self::V2 => &["cx16", "lahf_lm", "popcnt", "sse4_1", "sse4_2", "ssse3"],
Self::V3 => &[
"avx", "avx2", "bmi1", "bmi2", "f16c", "fma", "abm", "movbe", "xsave",
],
Self::V4 => &["avx512f", "avx512bw", "avx512cd", "avx512dq", "avx512vl"],
}
}
pub fn from_features(features: &HashSet<String>) -> Self {
let has = |f: &str| features.contains(f);
if Self::V4.required_features().iter().all(|f| has(f)) {
Self::V4
} else if Self::V3.required_features().iter().all(|f| has(f)) {
Self::V3
} else if Self::V2.required_features().iter().all(|f| has(f)) {
Self::V2
} else {
Self::V1
}
}
}
impl fmt::Display for X86ArchitectureLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl X86HostInfo {
pub fn detect() -> Self {
let mut info = Self::default_host();
info.probe_cpuid();
info.probe_cache();
info.probe_topology();
info.determine_arch_level();
info.determine_sched_model();
info
}
fn default_host() -> Self {
Self {
vendor: String::new(),
brand: String::new(),
family: 0,
model: 0,
stepping: 0,
features: HashSet::new(),
architecture_level: X86ArchitectureLevel::V1,
cache_l1_data_kb: 32,
cache_l1_inst_kb: 32,
cache_l2_kb: 256,
cache_l3_kb: 0,
cache_line_size: 64,
physical_cores: 1,
logical_processors: 1,
base_frequency_mhz: 0,
max_frequency_mhz: 0,
vector_widths: Vec::new(),
sched_model: "generic".to_string(),
is_hybrid: false,
e_core_model: None,
uarch_codename: "generic".to_string(),
has_avx512: false,
has_amx: false,
}
}
fn probe_cpuid(&mut self) {
self.vendor = detect_vendor_string();
self.family = 6;
self.model = detect_cpu_model(&self.vendor);
self.stepping = 1;
self.features = detect_host_features(&self.vendor, self.model, self.family);
self.brand = detect_brand_string(&self.vendor, self.model);
if self.features.contains("avx512f") {
self.vector_widths = vec![64, 128, 256, 512];
} else if self.features.contains("avx") || self.features.contains("avx2") {
self.vector_widths = vec![64, 128, 256];
} else {
self.vector_widths = vec![64, 128];
}
self.has_avx512 = self.features.contains("avx512f");
self.has_amx = self.features.contains("amx-tile");
}
fn probe_cache(&mut self) {
#[cfg(target_os = "linux")]
{
self.cache_l1_data_kb = read_sysfs_cache("L1-DATA").unwrap_or(32);
self.cache_l1_inst_kb = read_sysfs_cache("L1-INST").unwrap_or(32);
self.cache_l2_kb = read_sysfs_cache("L2").unwrap_or(256);
self.cache_l3_kb = read_sysfs_cache("L3").unwrap_or(0);
self.cache_line_size = read_sysfs_cache_line().unwrap_or(64);
}
}
fn probe_topology(&mut self) {
#[cfg(target_os = "linux")]
{
if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
let mut phys_ids = HashSet::new();
let mut core_ids = HashSet::new();
for line in cpuinfo.lines() {
if line.starts_with("physical id") {
if let Some(v) = line.split(':').nth(1) {
phys_ids.insert(v.trim().to_string());
}
}
if line.starts_with("core id") {
if let Some(v) = line.split(':').nth(1) {
core_ids.insert(v.trim().to_string());
}
}
}
self.physical_cores = phys_ids.len().max(1) as u32;
self.logical_processors = core_ids.len().max(1) as u32;
}
}
if self.logical_processors == 0 {
self.logical_processors = num_cpus::get() as u32;
self.physical_cores = (self.logical_processors / 2).max(1);
}
}
fn determine_arch_level(&mut self) {
self.architecture_level = X86ArchitectureLevel::from_features(&self.features);
}
fn determine_sched_model(&mut self) {
self.sched_model = match (self.vendor.as_str(), self.model) {
("GenuineIntel", 0x8e..=0x9e) | ("GenuineIntel", 0xa5..=0xaf) => {
self.uarch_codename = "skylake".to_string();
"skylake".to_string()
}
("GenuineIntel", 0x55..=0x57) | ("GenuineIntel", 0x6a..=0x6c) => {
self.uarch_codename = "icelake".to_string();
"icelake".to_string()
}
("GenuineIntel", 0x97..=0x9f) | ("GenuineIntel", 0xb7..=0xbf) => {
self.uarch_codename = "alderlake".to_string();
self.is_hybrid = true;
self.e_core_model = Some("gracemont".to_string());
"alderlake".to_string()
}
("AuthenticAMD", 0x00..=0x2f) => {
self.uarch_codename = "zen2".to_string();
"znver2".to_string()
}
("AuthenticAMD", 0x30..=0x4f) => {
self.uarch_codename = "zen3".to_string();
"znver3".to_string()
}
("AuthenticAMD", 0x60..=0x7f) => {
self.uarch_codename = "zen4".to_string();
"znver4".to_string()
}
_ => {
self.uarch_codename = "generic".to_string();
"generic".to_string()
}
};
}
pub fn target_cpu(&self) -> &str {
&self.uarch_codename
}
pub fn target_features_string(&self) -> String {
let mut feats: Vec<&str> = self.features.iter().map(|s| s.as_str()).collect();
feats.sort();
feats.join(",")
}
pub fn has_feature(&self, feature: &str) -> bool {
self.features.contains(feature)
}
pub fn max_vector_width_bits(&self) -> u32 {
self.vector_widths.last().copied().unwrap_or(128)
}
pub fn supports_arch_level(&self, level: X86ArchitectureLevel) -> bool {
self.architecture_level >= level
}
}
fn detect_vendor_string() -> String {
#[cfg(target_os = "linux")]
{
if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
for line in cpuinfo.lines() {
if line.starts_with("vendor_id") {
if let Some(v) = line.split(':').nth(1) {
return v.trim().to_string();
}
}
}
}
}
"GenuineIntel".to_string()
}
fn detect_cpu_model(vendor: &str) -> u32 {
#[cfg(target_os = "linux")]
{
if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
for line in cpuinfo.lines() {
if line.starts_with("model") && !line.starts_with("model name") {
if let Some(v) = line.split(':').nth(1) {
if let Ok(m) = v.trim().parse::<u32>() {
return m;
}
}
}
}
}
}
match vendor {
"AuthenticAMD" => 0x30, _ => 0x8e, }
}
fn detect_host_features(vendor: &str, _model: u32, _family: u32) -> HashSet<String> {
let mut feats = HashSet::new();
let base = [
"cmov", "cx8", "fpu", "fxsr", "mmx", "syscall", "sse", "sse2", "sse3", "ssse3", "sse4.1",
"sse4.2", "popcnt", "lahf_lm", "cx16", "movbe", "aes", "pclmul", "rdrnd", "xsave",
"xsaveopt", "fsgsbase", "prfchw", "clflush", "f16c",
];
for f in &base {
feats.insert(f.to_string());
}
match vendor {
"GenuineIntel" => {
let intel_feats = [
"avx",
"avx2",
"bmi",
"bmi2",
"fma",
"lzcnt",
"adx",
"rdseed",
"clflushopt",
"clwb",
"pku",
"sgx",
"rtm",
"hle",
"mpx",
"xsaves",
];
for f in &intel_feats {
feats.insert(f.to_string());
}
}
"AuthenticAMD" => {
let amd_feats = [
"avx",
"avx2",
"bmi",
"bmi2",
"fma",
"lzcnt",
"adx",
"rdseed",
"clflushopt",
"clwb",
"mwaitx",
"clzero",
"wbnoinvd",
"xsaves",
];
for f in &amd_feats {
feats.insert(f.to_string());
}
}
_ => {
for f in &["avx", "avx2", "bmi", "bmi2", "fma", "lzcnt"] {
feats.insert(f.to_string());
}
}
}
feats
}
fn detect_brand_string(vendor: &str, model: u32) -> String {
#[cfg(target_os = "linux")]
{
if let Ok(cpuinfo) = fs::read_to_string("/proc/cpuinfo") {
for line in cpuinfo.lines() {
if line.starts_with("model name") {
if let Some(v) = line.split(':').nth(1) {
return v.trim().to_string();
}
}
}
}
}
match (vendor, model) {
("GenuineIntel", 0x8e) => "Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz".to_string(),
("AuthenticAMD", 0x30) => "AMD Ryzen 9 5950X 16-Core Processor".to_string(),
_ => "Unknown x86_64 CPU".to_string(),
}
}
#[cfg(target_os = "linux")]
fn read_sysfs_cache(cache_type: &str) -> Option<u32> {
let paths = [
format!("/sys/devices/system/cpu/cpu0/cache/index0/size"),
format!("/sys/devices/system/cpu/cpu0/cache/index1/size"),
format!("/sys/devices/system/cpu/cpu0/cache/index2/size"),
format!("/sys/devices/system/cpu/cpu0/cache/index3/size"),
];
for (i, path) in paths.iter().enumerate() {
if let Ok(type_path) = fs::read_to_string(format!(
"/sys/devices/system/cpu/cpu0/cache/index{}/type",
i
)) {
if type_path.trim() == cache_type {
if let Ok(size_str) = fs::read_to_string(path) {
let s = size_str.trim();
if s.ends_with('K') {
return s[..s.len() - 1].parse::<u32>().ok();
}
if s.ends_with('M') {
return s[..s.len() - 1].parse::<u32>().ok().map(|v| v * 1024);
}
}
}
}
}
None
}
#[cfg(target_os = "linux")]
fn read_sysfs_cache_line() -> Option<u32> {
fs::read_to_string("/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size")
.ok()
.and_then(|s| s.trim().parse::<u32>().ok())
}
impl Default for X86HostInfo {
fn default() -> Self {
Self::detect()
}
}
impl fmt::Display for X86HostInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} ({} cores, {} threads, {:?}, L1={}K L2={}K L3={}K)",
self.brand,
self.physical_cores,
self.logical_processors,
self.architecture_level,
self.cache_l1_data_kb,
self.cache_l2_kb,
self.cache_l3_kb,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct X86TargetTriple {
pub original: String,
pub arch: X86TripleArch,
pub vendor: X86TripleVendor,
pub os: X86TripleOS,
pub environment: X86TripleEnvironment,
pub normalized: String,
pub is_64bit: bool,
pub is_x32: bool,
pub data_layout: String,
pub object_format: X86ObjectFormat,
pub default_code_model: X86CodeModel,
pub default_pic: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TripleArch {
I386,
X86_64,
X32,
I486,
I586,
I686,
Pentium,
PentiumMMX,
PentiumPro,
Pentium2,
Pentium3,
Pentium4,
K6,
K6_2,
K6_3,
Athlon,
AthlonXP,
Athlon64,
Core2,
Nehalem,
SandyBridge,
IvyBridge,
Haswell,
Broadwell,
Skylake,
Icelake,
AlderLake,
}
impl X86TripleArch {
pub fn from_str(s: &str) -> Self {
match s {
"i386" | "i486" | "i586" | "i686" | "x86" => Self::I386,
"i486" => Self::I486,
"i586" => Self::I586,
"i686" => Self::I686,
"x86_64" | "amd64" | "x86-64" | "x64" => Self::X86_64,
"x32" => Self::X32,
_ if s.starts_with("x86_64") => Self::X86_64,
_ if s.starts_with("i386")
|| s.starts_with("i486")
|| s.starts_with("i586")
|| s.starts_with("i686") =>
{
Self::I386
}
_ => Self::X86_64, }
}
pub fn as_str(&self) -> &'static str {
match self {
Self::I386
| Self::I486
| Self::I586
| Self::I686
| Self::Pentium
| Self::PentiumMMX
| Self::PentiumPro
| Self::Pentium2
| Self::Pentium3
| Self::Pentium4
| Self::K6
| Self::K6_2
| Self::K6_3
| Self::Athlon
| Self::AthlonXP => "i386",
Self::Athlon64
| Self::Core2
| Self::Nehalem
| Self::SandyBridge
| Self::IvyBridge
| Self::Haswell
| Self::Broadwell
| Self::Skylake
| Self::Icelake
| Self::AlderLake
| Self::X86_64 => "x86_64",
Self::X32 => "x86_64", }
}
pub fn is_64bit(&self) -> bool {
matches!(
self,
Self::X86_64
| Self::X32
| Self::Athlon64
| Self::Core2
| Self::Nehalem
| Self::SandyBridge
| Self::IvyBridge
| Self::Haswell
| Self::Broadwell
| Self::Skylake
| Self::Icelake
| Self::AlderLake
)
}
pub fn pointer_width_bits(&self) -> u32 {
if matches!(self, Self::X32) {
32
} else if self.is_64bit() {
64
} else {
32
}
}
}
impl fmt::Display for X86TripleArch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TripleVendor {
Unknown,
PC,
Apple,
AMD,
Intel,
}
impl X86TripleVendor {
pub fn from_str(s: &str) -> Self {
match s {
"pc" => Self::PC,
"apple" => Self::Apple,
"amd" => Self::AMD,
"intel" => Self::Intel,
_ => Self::Unknown,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Unknown => "unknown",
Self::PC => "pc",
Self::Apple => "apple",
Self::AMD => "amd",
Self::Intel => "intel",
}
}
}
impl fmt::Display for X86TripleVendor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TripleOS {
Unknown,
Linux,
Darwin,
MacOSX,
Win32,
FreeBSD,
NetBSD,
OpenBSD,
DragonFly,
Solaris,
Haiku,
Android,
Emscripten,
Fuchsia,
WASI,
Hurd,
}
impl X86TripleOS {
pub fn from_str(s: &str) -> Self {
match s {
"linux" => Self::Linux,
"darwin" | "macos" | "macosx" | "ios" => Self::Darwin,
"macosx" => Self::MacOSX,
"win32" | "windows" | "mingw32" | "cygwin" => Self::Win32,
"freebsd" => Self::FreeBSD,
"netbsd" => Self::NetBSD,
"openbsd" => Self::OpenBSD,
"dragonfly" => Self::DragonFly,
"solaris" | "sunos" => Self::Solaris,
"haiku" => Self::Haiku,
"android" => Self::Android,
"emscripten" => Self::Emscripten,
"fuchsia" => Self::Fuchsia,
"wasi" => Self::WASI,
"hurd" => Self::Hurd,
_ => Self::Unknown,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Unknown => "unknown",
Self::Linux => "linux",
Self::Darwin | Self::MacOSX => "darwin",
Self::Win32 => "win32",
Self::FreeBSD => "freebsd",
Self::NetBSD => "netbsd",
Self::OpenBSD => "openbsd",
Self::DragonFly => "dragonfly",
Self::Solaris => "solaris",
Self::Haiku => "haiku",
Self::Android => "android",
Self::Emscripten => "emscripten",
Self::Fuchsia => "fuchsia",
Self::WASI => "wasi",
Self::Hurd => "hurd",
}
}
pub fn is_unix(&self) -> bool {
matches!(
self,
Self::Linux
| Self::Darwin
| Self::MacOSX
| Self::FreeBSD
| Self::NetBSD
| Self::OpenBSD
| Self::DragonFly
| Self::Solaris
| Self::Haiku
| Self::Android
| Self::Hurd
)
}
pub fn is_elf(&self) -> bool {
matches!(
self,
Self::Linux
| Self::FreeBSD
| Self::NetBSD
| Self::OpenBSD
| Self::DragonFly
| Self::Solaris
| Self::Haiku
| Self::Android
| Self::Hurd
| Self::Fuchsia
)
}
pub fn is_macho(&self) -> bool {
matches!(self, Self::Darwin | Self::MacOSX)
}
pub fn is_coff(&self) -> bool {
matches!(self, Self::Win32)
}
}
impl fmt::Display for X86TripleOS {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TripleEnvironment {
Unknown,
GNU,
GNUEABI,
GNUEABIHF,
GNUX32,
Android,
Androideabi,
MSVC,
Itanium,
Cygnus,
Musl,
Musleabi,
Musleabihf,
CoreCLR,
}
impl X86TripleEnvironment {
pub fn from_str(s: &str) -> Self {
match s {
"gnu" => Self::GNU,
"gnueabi" => Self::GNUEABI,
"gnueabihf" => Self::GNUEABIHF,
"gnux32" => Self::GNUX32,
"android" => Self::Android,
"androideabi" => Self::Androideabi,
"msvc" => Self::MSVC,
"itanium" => Self::Itanium,
"cygnus" => Self::Cygnus,
"musl" => Self::Musl,
"musleabi" => Self::Musleabi,
"musleabihf" => Self::Musleabihf,
"coreclr" => Self::CoreCLR,
_ => Self::Unknown,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Unknown => "",
Self::GNU => "gnu",
Self::GNUEABI => "gnueabi",
Self::GNUEABIHF => "gnueabihf",
Self::GNUX32 => "gnux32",
Self::Android => "android",
Self::Androideabi => "androideabi",
Self::MSVC => "msvc",
Self::Itanium => "itanium",
Self::Cygnus => "cygnus",
Self::Musl => "musl",
Self::Musleabi => "musleabi",
Self::Musleabihf => "musleabihf",
Self::CoreCLR => "coreclr",
}
}
pub fn is_msvc(&self) -> bool {
matches!(self, Self::MSVC)
}
pub fn is_gnu(&self) -> bool {
matches!(
self,
Self::GNU | Self::GNUEABI | Self::GNUEABIHF | Self::GNUX32
)
}
pub fn is_musl(&self) -> bool {
matches!(self, Self::Musl | Self::Musleabi | Self::Musleabihf)
}
pub fn is_android(&self) -> bool {
matches!(self, Self::Android | Self::Androideabi)
}
}
impl fmt::Display for X86TripleEnvironment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ObjectFormat {
ELF,
MachO,
COFF,
Wasm,
}
impl X86ObjectFormat {
pub fn as_str(&self) -> &'static str {
match self {
Self::ELF => "elf",
Self::MachO => "macho",
Self::COFF => "coff",
Self::Wasm => "wasm",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CodeModel {
Tiny,
Small,
Kernel,
Medium,
Large,
}
impl X86CodeModel {
pub fn as_str(&self) -> &'static str {
match self {
Self::Tiny => "tiny",
Self::Small => "small",
Self::Kernel => "kernel",
Self::Medium => "medium",
Self::Large => "large",
}
}
}
impl Default for X86CodeModel {
fn default() -> Self {
Self::Small
}
}
impl X86TargetTriple {
pub fn parse(triple_str: &str) -> Self {
let original = triple_str.to_string();
let parts: Vec<&str> = triple_str.split('-').collect();
let arch = if !parts.is_empty() {
X86TripleArch::from_str(parts[0])
} else {
X86TripleArch::X86_64
};
let vendor = if parts.len() > 1 {
X86TripleVendor::from_str(parts[1])
} else {
X86TripleVendor::Unknown
};
let os = if parts.len() > 2 {
X86TripleOS::from_str(parts[2])
} else {
X86TripleOS::Unknown
};
let environment = if parts.len() > 3 {
X86TripleEnvironment::from_str(parts[3])
} else {
X86TripleEnvironment::Unknown
};
let is_x32 = arch == X86TripleArch::X32 || environment == X86TripleEnvironment::GNUX32;
let is_64bit = arch.is_64bit() && !is_x32;
let object_format = if os.is_macho() {
X86ObjectFormat::MachO
} else if os.is_coff() {
X86ObjectFormat::COFF
} else {
X86ObjectFormat::ELF
};
let default_pic = matches!(
os,
X86TripleOS::Darwin | X86TripleOS::MacOSX | X86TripleOS::Android
);
let default_code_model = if is_64bit {
X86CodeModel::Small
} else {
X86CodeModel::Small
};
let data_layout = if is_64bit {
"e-m:e-p:64:64-i64:64-i128:128-n32:64-S128".to_string()
} else if is_x32 {
"e-m:e-p:32:32-i64:64-i128:128-n32:64-S128".to_string()
} else {
"e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128".to_string()
};
let normalized = Self::build_normalized(arch, vendor, os, environment);
Self {
original,
arch,
vendor,
os,
environment,
normalized,
is_64bit,
is_x32,
data_layout,
object_format,
default_code_model,
default_pic,
}
}
fn build_normalized(
arch: X86TripleArch,
vendor: X86TripleVendor,
os: X86TripleOS,
env: X86TripleEnvironment,
) -> String {
let env_str = env.as_str();
if env_str.is_empty() {
format!("{}-{}-{}", arch.as_str(), vendor.as_str(), os.as_str())
} else {
format!(
"{}-{}-{}-{}",
arch.as_str(),
vendor.as_str(),
os.as_str(),
env_str
)
}
}
pub fn x86_64_linux_gnu() -> Self {
Self::parse("x86_64-unknown-linux-gnu")
}
pub fn i386_linux_gnu() -> Self {
Self::parse("i386-unknown-linux-gnu")
}
pub fn x86_64_windows_msvc() -> Self {
Self::parse("x86_64-pc-windows-msvc")
}
pub fn x86_64_apple_darwin() -> Self {
Self::parse("x86_64-apple-darwin")
}
pub fn x86_64_linux_musl() -> Self {
Self::parse("x86_64-unknown-linux-musl")
}
pub fn i686_linux_android() -> Self {
Self::parse("i686-linux-android")
}
pub fn x86_64_linux_android() -> Self {
Self::parse("x86_64-linux-android")
}
pub fn x86_64_linux_gnux32() -> Self {
Self::parse("x86_64-unknown-linux-gnux32")
}
pub fn default_linker(&self) -> X86LinkerFlavor {
if self.os.is_coff() {
if self.environment.is_msvc() {
X86LinkerFlavor::MSVCLink
} else {
X86LinkerFlavor::GnuLd
}
} else if self.os.is_macho() {
X86LinkerFlavor::DarwinLd
} else {
X86LinkerFlavor::GnuLd
}
}
pub fn default_crt_object(&self) -> Option<&'static str> {
match (self.os, self.environment) {
(X86TripleOS::Win32, X86TripleEnvironment::MSVC) if self.is_64bit => Some("msvcrt"),
(X86TripleOS::Win32, X86TripleEnvironment::MSVC) => Some("msvcrt"),
(X86TripleOS::Win32, X86TripleEnvironment::GNU) => Some("msvcrt"),
_ => None,
}
}
pub fn default_cxx_lib(&self) -> &'static str {
match (self.os, self.environment) {
(X86TripleOS::Win32, X86TripleEnvironment::MSVC) => "msvcprt",
(X86TripleOS::Android, _) => "c++_shared",
_ => "stdc++",
}
}
pub fn supports_tls(&self) -> bool {
!self.os.is_coff() || matches!(self.environment, X86TripleEnvironment::GNU)
}
pub fn default_seh(&self) -> bool {
self.os.is_coff() && matches!(self.environment, X86TripleEnvironment::MSVC) && self.is_64bit
}
pub fn default_dwarf_eh(&self) -> bool {
!self.os.is_coff() || matches!(self.environment, X86TripleEnvironment::GNU)
}
}
impl Default for X86TargetTriple {
fn default() -> Self {
Self::x86_64_linux_gnu()
}
}
impl fmt::Display for X86TargetTriple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.normalized)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LinkerFlavor {
GnuLd,
Lld,
DarwinLd,
MSVCLink,
LldLink,
}
impl X86LinkerFlavor {
pub fn as_str(&self) -> &'static str {
match self {
Self::GnuLd => "gnu-ld",
Self::Lld => "lld",
Self::DarwinLd => "darwin-ld",
Self::MSVCLink => "msvc-link",
Self::LldLink => "lld-link",
}
}
pub fn default_executable(&self) -> &'static str {
match self {
Self::GnuLd => "ld",
Self::Lld => "ld.lld",
Self::DarwinLd => "ld",
Self::MSVCLink => "link.exe",
Self::LldLink => "lld-link",
}
}
pub fn supports_lto(&self) -> bool {
matches!(
self,
Self::GnuLd | Self::Lld | Self::DarwinLd | Self::LldLink
)
}
pub fn supports_thin_lto(&self) -> bool {
matches!(self, Self::Lld | Self::LldLink)
}
}
#[derive(Debug, Clone)]
pub struct X86ClangDriver {
pub triple: X86TargetTriple,
pub host_info: X86HostInfo,
pub toolchain: X86ToolchainPath,
pub x86_args: X86DriverArgs,
pub linker_flavor: X86LinkerFlavor,
pub sanitizers: X86SanitizerSetup,
pub coverage: X86CoverageSetup,
pub header_search: X86HeaderSearch,
pub stdlib: X86StdLibSetup,
pub multilib: X86Multilib,
pub lto_config: X86LTOConfig,
pub reproducer: Option<X86BitcodeReproducer>,
pub clang_options: ClangOptions,
pub input_files: Vec<PathBuf>,
pub output_file: Option<PathBuf>,
pub include_paths: Vec<PathBuf>,
pub system_include_paths: Vec<PathBuf>,
pub library_paths: Vec<PathBuf>,
pub libraries: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub undefines: Vec<String>,
pub opt_level: X86DriverOptLevel,
pub debug_info: X86DriverDebugInfo,
pub pic_level: X86DriverPicLevel,
pub verbose: bool,
pub dry_run: bool,
pub linker_flags: Vec<String>,
pub assembler_flags: Vec<String>,
pub preprocessor_defines: Vec<String>,
pub output_type: X86DriverOutputType,
pub phases: Vec<X86CompilationPhase>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DriverOptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl X86DriverOptLevel {
pub fn as_str(&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)
}
pub fn lto_enabled_by_default(&self) -> bool {
false
}
}
impl Default for X86DriverOptLevel {
fn default() -> Self {
Self::O0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DriverDebugInfo {
None,
LineTablesOnly,
Limited,
Full,
FullWithMacros,
}
impl X86DriverDebugInfo {
pub fn has_debug(&self) -> bool {
!matches!(self, Self::None)
}
}
impl Default for X86DriverDebugInfo {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DriverPicLevel {
Default,
PIC,
PIE,
NoPIC,
}
impl Default for X86DriverPicLevel {
fn default() -> Self {
Self::Default
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DriverOutputType {
PreprocessedSource,
LLVMIR,
LLVMBitcode,
Assembly,
Object,
SharedLibrary,
Executable,
}
impl X86DriverOutputType {
pub fn extension(&self) -> &'static str {
match self {
Self::PreprocessedSource => ".i",
Self::LLVMIR => ".ll",
Self::LLVMBitcode => ".bc",
Self::Assembly => ".s",
Self::Object => ".o",
Self::SharedLibrary => ".so",
Self::Executable => "",
}
}
pub fn requires_linking(&self) -> bool {
matches!(self, Self::SharedLibrary | Self::Executable)
}
}
impl Default for X86DriverOutputType {
fn default() -> Self {
Self::Executable
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86CompilationPhase {
Preprocess,
Compile,
Backend,
Assemble,
Link,
}
impl X86CompilationPhase {
pub fn name(&self) -> &'static str {
match self {
Self::Preprocess => "preprocess",
Self::Compile => "compile",
Self::Backend => "backend",
Self::Assemble => "assemble",
Self::Link => "link",
}
}
pub fn all() -> &'static [Self] {
&[
Self::Preprocess,
Self::Compile,
Self::Backend,
Self::Assemble,
Self::Link,
]
}
}
impl X86ClangDriver {
pub fn new() -> Self {
let host_info = X86HostInfo::detect();
let triple = X86TargetTriple::x86_64_linux_gnu();
let toolchain = X86ToolchainPath::detect(&triple);
let x86_args = X86DriverArgs::default();
Self {
linker_flavor: triple.default_linker(),
sanitizers: X86SanitizerSetup::new(),
coverage: X86CoverageSetup::new(),
header_search: X86HeaderSearch::new(&triple, &toolchain),
stdlib: X86StdLibSetup::new(&triple, &toolchain),
multilib: X86Multilib::new(&triple),
lto_config: X86LTOConfig::default(),
reproducer: None,
clang_options: ClangOptions::default(),
triple,
host_info,
toolchain,
x86_args,
input_files: Vec::new(),
output_file: None,
include_paths: Vec::new(),
system_include_paths: Vec::new(),
library_paths: Vec::new(),
libraries: Vec::new(),
defines: Vec::new(),
undefines: Vec::new(),
opt_level: X86DriverOptLevel::default(),
debug_info: X86DriverDebugInfo::default(),
pic_level: X86DriverPicLevel::default(),
verbose: false,
dry_run: false,
linker_flags: Vec::new(),
assembler_flags: Vec::new(),
preprocessor_defines: Vec::new(),
output_type: X86DriverOutputType::default(),
phases: X86CompilationPhase::all().to_vec(),
}
}
pub fn x86_64_linux() -> Self {
let mut driver = Self::new();
driver.triple = X86TargetTriple::x86_64_linux_gnu();
driver.toolchain = X86ToolchainPath::detect(&driver.triple);
driver.linker_flavor = driver.triple.default_linker();
driver.header_search = X86HeaderSearch::new(&driver.triple, &driver.toolchain);
driver.stdlib = X86StdLibSetup::new(&driver.triple, &driver.toolchain);
driver
}
pub fn i386_linux() -> Self {
let mut driver = Self::new();
driver.triple = X86TargetTriple::i386_linux_gnu();
driver.toolchain = X86ToolchainPath::detect(&driver.triple);
driver.linker_flavor = driver.triple.default_linker();
driver.multilib = X86Multilib::new(&driver.triple);
driver.header_search = X86HeaderSearch::new(&driver.triple, &driver.toolchain);
driver.stdlib = X86StdLibSetup::new(&driver.triple, &driver.toolchain);
driver
}
pub fn x86_64_windows() -> Self {
let mut driver = Self::new();
driver.triple = X86TargetTriple::x86_64_windows_msvc();
driver.toolchain = X86ToolchainPath::detect(&driver.triple);
driver.linker_flavor = driver.triple.default_linker();
driver.header_search = X86HeaderSearch::new(&driver.triple, &driver.toolchain);
driver.stdlib = X86StdLibSetup::new(&driver.triple, &driver.toolchain);
driver
}
pub fn parse_args(&mut self, args: &[String]) {
self.x86_args = X86DriverArgs::parse(args);
if let Some(ref arch) = self.x86_args.march {
self.x86_args.target_cpu = Some(arch.clone());
}
if let Some(ref cpu) = self.x86_args.mcpu {
self.x86_args.target_cpu = Some(cpu.clone());
}
if let Some(ref triple) = self.x86_args.target {
self.triple = X86TargetTriple::parse(triple);
self.toolchain = X86ToolchainPath::detect(&self.triple);
self.linker_flavor = self.triple.default_linker();
self.header_search = X86HeaderSearch::new(&self.triple, &self.toolchain);
self.stdlib = X86StdLibSetup::new(&self.triple, &self.toolchain);
}
if self.x86_args.m32 {
self.triple = X86TargetTriple::i386_linux_gnu();
self.multilib.select_32bit();
self.toolchain = X86ToolchainPath::detect(&self.triple);
self.header_search = X86HeaderSearch::new(&self.triple, &self.toolchain);
self.stdlib = X86StdLibSetup::new(&self.triple, &self.toolchain);
} else if self.x86_args.mx32 {
self.triple = X86TargetTriple::x86_64_linux_gnux32();
self.multilib.select_x32();
self.toolchain = X86ToolchainPath::detect(&self.triple);
}
if let Some(opt) = &self.x86_args.opt_level {
self.opt_level = *opt;
}
if self.x86_args.fpic || self.x86_args.fPIC {
self.pic_level = X86DriverPicLevel::PIC;
} else if self.x86_args.fpie || self.x86_args.fPIE {
self.pic_level = X86DriverPicLevel::PIE;
} else if self.x86_args.fno_pic {
self.pic_level = X86DriverPicLevel::NoPIC;
}
self.sanitizers = X86SanitizerSetup::from_args(&self.x86_args);
self.coverage = X86CoverageSetup::from_args(&self.x86_args);
if self.x86_args.flto.is_some() || self.x86_args.flto_thin {
self.lto_config = X86LTOConfig::from_args(&self.x86_args);
}
self.verbose = self.x86_args.v;
self.dry_run = self.x86_args.dry_run;
self.include_paths
.extend(self.x86_args.include_paths.iter().map(PathBuf::from));
self.system_include_paths
.extend(self.x86_args.system_include_paths.iter().map(PathBuf::from));
self.library_paths
.extend(self.x86_args.library_paths.iter().map(PathBuf::from));
self.libraries.extend(self.x86_args.libraries.clone());
self.defines.extend(self.x86_args.defines.clone());
self.undefines.extend(self.x86_args.undefines.clone());
self.input_files = self
.x86_args
.input_files
.iter()
.map(PathBuf::from)
.collect();
self.output_file = self.x86_args.output_file.as_ref().map(PathBuf::from);
}
pub fn all_include_paths(&self) -> Vec<PathBuf> {
let mut paths = Vec::new();
paths.extend(self.include_paths.clone());
paths.extend(self.system_include_paths.clone());
paths.extend(self.header_search.system_include_paths());
paths.extend(self.header_search.cxx_include_paths());
paths.extend(self.header_search.builtin_include_paths());
paths
}
pub fn all_library_paths(&self) -> Vec<PathBuf> {
let mut paths = Vec::new();
paths.extend(self.library_paths.clone());
paths.extend(self.stdlib.library_paths());
paths.extend(self.multilib.library_paths());
paths
}
pub fn default_libraries(&self) -> Vec<String> {
let mut libs = Vec::new();
libs.extend(self.libraries.clone());
libs.extend(self.stdlib.default_link_libraries());
libs.extend(self.sanitizers.linker_libraries());
libs.extend(self.coverage.linker_libraries());
libs
}
pub fn build_compiler_command(&self, input: &Path, output: &Path) -> Vec<String> {
let mut cmd = vec!["clang".to_string()];
cmd.push(format!("-target={}", self.triple.normalized));
if let Some(ref cpu) = self.x86_args.target_cpu {
cmd.push(format!("-mcpu={}", cpu));
}
if !self.x86_args.target_features.is_empty() {
cmd.push(format!(
"-mattr={}",
self.x86_args.target_features.join(",")
));
}
cmd.push(self.opt_level.as_str().to_string());
match self.debug_info {
X86DriverDebugInfo::None => {}
X86DriverDebugInfo::LineTablesOnly => cmd.push("-gline-tables-only".to_string()),
X86DriverDebugInfo::Limited => cmd.push("-g1".to_string()),
X86DriverDebugInfo::Full => cmd.push("-g".to_string()),
X86DriverDebugInfo::FullWithMacros => cmd.push("-g3".to_string()),
}
match self.pic_level {
X86DriverPicLevel::PIC => cmd.push("-fPIC".to_string()),
X86DriverPicLevel::PIE => cmd.push("-fPIE".to_string()),
X86DriverPicLevel::NoPIC => cmd.push("-fno-PIC".to_string()),
X86DriverPicLevel::Default => {}
}
cmd.push(format!("-std={}", self.clang_options.standard.as_str()));
cmd.extend(self.x86_args.to_clang_flags());
for inc in &self.include_paths {
cmd.push(format!("-I{}", inc.display()));
}
for inc in &self.system_include_paths {
cmd.push(format!("-isystem{}", inc.display()));
}
for inc in self.header_search.system_include_paths() {
cmd.push(format!("-isystem{}", inc.display()));
}
for (name, val) in &self.defines {
if let Some(v) = val {
cmd.push(format!("-D{}={}", name, v));
} else {
cmd.push(format!("-D{}", name));
}
}
for u in &self.undefines {
cmd.push(format!("-U{}", u));
}
cmd.extend(self.sanitizers.compiler_flags());
cmd.extend(self.coverage.compiler_flags());
if self.lto_config.is_enabled() {
cmd.push("-flto".to_string());
if let Some(ref jobs) = self.lto_config.thin_lto_jobs {
cmd.push(format!("-flto-jobs={}", jobs));
}
}
cmd.push(input.to_string_lossy().to_string());
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
cmd
}
pub fn build_linker_invocation(&self) -> X86LinkerInvocation {
let mut inv = X86LinkerInvocation::new(self.linker_flavor, &self.triple);
for input in &self.input_files {
inv.add_input(input);
}
if let Some(ref out) = self.output_file {
inv.set_output(out);
}
for lib in &self.library_paths {
inv.add_library_path(lib);
}
for lib in &self.libraries {
inv.add_library(lib);
}
for lib in self.stdlib.default_link_libraries() {
inv.add_library(&lib);
}
for lib in self.sanitizers.linker_libraries() {
inv.add_library(&lib);
}
for lib in self.coverage.linker_libraries() {
inv.add_library(&lib);
}
for p in self.multilib.library_paths() {
inv.add_library_path(&p);
}
for flag in &self.linker_flags {
inv.add_flag(flag);
}
if self.lto_config.is_enabled() {
if let Some(ref plugin) = self.lto_config.linker_plugin {
inv.set_lto_plugin(plugin);
}
}
inv
}
pub fn build_assembler_invocation(
&self,
input: &Path,
output: &Path,
) -> X86AssemblerInvocation {
let mut inv = X86AssemblerInvocation::new(&self.triple);
inv.set_input(input);
inv.set_output(output);
for flag in &self.assembler_flags {
inv.add_flag(flag);
}
inv
}
pub fn compile(&self) -> Result<Vec<PathBuf>, String> {
if self.input_files.is_empty() {
return Err("No input files specified".to_string());
}
let mut intermediate_files = Vec::new();
let mut current_inputs: Vec<PathBuf> = self.input_files.clone();
for phase in &self.phases {
match phase {
X86CompilationPhase::Preprocess => {
let mut outputs = Vec::new();
for input in ¤t_inputs {
let output = input.with_extension("i");
let cmd = self.build_preprocess_command(input, &output);
if self.verbose || self.dry_run {
eprintln!("[preprocess] {}", cmd.join(" "));
}
if !self.dry_run {
Self::run_command(&cmd)?;
}
outputs.push(output);
}
intermediate_files.extend(outputs.clone());
current_inputs = outputs;
}
X86CompilationPhase::Compile => {
let mut outputs = Vec::new();
for input in ¤t_inputs {
let output = input.with_extension("bc");
let cmd = self.build_compiler_command(input, &output);
if self.verbose || self.dry_run {
eprintln!("[compile] {}", cmd.join(" "));
}
if !self.dry_run {
Self::run_command(&cmd)?;
}
outputs.push(output);
}
intermediate_files.extend(outputs.clone());
current_inputs = outputs;
}
X86CompilationPhase::Backend => {
let mut outputs = Vec::new();
for input in ¤t_inputs {
let output = input.with_extension("o");
let cmd = self.build_backend_command(input, &output);
if self.verbose || self.dry_run {
eprintln!("[backend] {}", cmd.join(" "));
}
if !self.dry_run {
Self::run_command(&cmd)?;
}
outputs.push(output);
}
intermediate_files.extend(outputs.clone());
current_inputs = outputs;
}
X86CompilationPhase::Assemble => {
let mut outputs = Vec::new();
for input in ¤t_inputs {
let output = input.with_extension("o");
let inv = self.build_assembler_invocation(input, &output);
let cmd = inv.to_command();
if self.verbose || self.dry_run {
eprintln!("[assemble] {}", cmd.join(" "));
}
if !self.dry_run {
Self::run_command(&cmd)?;
}
if !intermediate_files.contains(&output) {
outputs.push(output);
}
}
current_inputs = outputs;
}
X86CompilationPhase::Link => {
let inv = self.build_linker_invocation();
let cmd = inv.to_command();
if self.verbose || self.dry_run {
eprintln!("[link] {}", cmd.join(" "));
}
if !self.dry_run {
Self::run_command(&cmd)?;
}
}
}
}
Ok(intermediate_files)
}
fn build_preprocess_command(&self, input: &Path, output: &Path) -> Vec<String> {
let mut cmd = vec!["clang".to_string(), "-E".to_string()];
cmd.push(format!("-target={}", self.triple.normalized));
cmd.push(format!("-std={}", self.clang_options.standard.as_str()));
for inc in &self.include_paths {
cmd.push(format!("-I{}", inc.display()));
}
for (name, val) in &self.defines {
if let Some(v) = val {
cmd.push(format!("-D{}={}", name, v));
} else {
cmd.push(format!("-D{}", name));
}
}
cmd.push(input.to_string_lossy().to_string());
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
cmd
}
fn build_backend_command(&self, input: &Path, output: &Path) -> Vec<String> {
let mut cmd = vec!["llc".to_string()];
cmd.push(format!("-mtriple={}", self.triple.normalized));
if let Some(ref cpu) = self.x86_args.target_cpu {
cmd.push(format!("-mcpu={}", cpu));
}
cmd.push(self.opt_level.as_str().to_string());
cmd.push("-filetype=obj".to_string());
cmd.push(input.to_string_lossy().to_string());
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
cmd
}
fn run_command(cmd: &[String]) -> Result<(), String> {
if cmd.is_empty() {
return Err("Empty command".to_string());
}
let status = ProcessCommand::new(&cmd[0])
.args(&cmd[1..])
.status()
.map_err(|e| format!("Failed to execute {}: {}", cmd[0], e))?;
if !status.success() {
return Err(format!(
"Command failed with exit code: {:?}",
status.code()
));
}
Ok(())
}
pub fn set_output_type(&mut self, ty: X86DriverOutputType) {
self.output_type = ty;
self.phases = match ty {
X86DriverOutputType::PreprocessedSource => {
vec![X86CompilationPhase::Preprocess]
}
X86DriverOutputType::LLVMIR | X86DriverOutputType::LLVMBitcode => {
vec![
X86CompilationPhase::Preprocess,
X86CompilationPhase::Compile,
]
}
X86DriverOutputType::Assembly => {
vec![
X86CompilationPhase::Preprocess,
X86CompilationPhase::Compile,
X86CompilationPhase::Backend,
]
}
X86DriverOutputType::Object => {
vec![
X86CompilationPhase::Preprocess,
X86CompilationPhase::Compile,
X86CompilationPhase::Backend,
X86CompilationPhase::Assemble,
]
}
X86DriverOutputType::SharedLibrary | X86DriverOutputType::Executable => {
X86CompilationPhase::all().to_vec()
}
};
}
pub fn enable_reproducer(&mut self, output_dir: &Path) {
self.reproducer = Some(X86BitcodeReproducer::new(output_dir));
}
pub fn capture_for_reproducer(&self) {
if let Some(ref reproducer) = self.reproducer {
let _ = reproducer.capture_state(self);
}
}
pub fn describe(&self) -> String {
let mut desc = String::new();
desc.push_str(&format!("Target: {}\n", self.triple.normalized));
desc.push_str(&format!("Host: {}\n", self.host_info));
desc.push_str(&format!("Toolchain: {}\n", self.toolchain));
desc.push_str(&format!("Linker: {:?}\n", self.linker_flavor));
desc.push_str(&format!("Optimization: {:?}\n", self.opt_level));
desc.push_str(&format!("Output: {:?}\n", self.output_type));
desc.push_str(&format!("Multilib: {:?}\n", self.multilib.active_variant));
if !self.include_paths.is_empty() {
desc.push_str(&format!("Include paths: {:?}\n", self.include_paths));
}
if !self.library_paths.is_empty() {
desc.push_str(&format!("Library paths: {:?}\n", self.library_paths));
}
desc
}
}
impl Default for X86ClangDriver {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ToolchainPath {
pub triple: X86TargetTriple,
pub install_dir: Option<PathBuf>,
pub sysroot: Option<PathBuf>,
pub resource_dir: Option<PathBuf>,
pub c_include_dirs: Vec<PathBuf>,
pub cxx_include_dirs: Vec<PathBuf>,
pub lib_dirs: Vec<PathBuf>,
pub lib32_dirs: Vec<PathBuf>,
pub libx32_dirs: Vec<PathBuf>,
pub crt_objects: HashMap<String, PathBuf>,
pub compiler_path: Option<PathBuf>,
pub assembler_path: Option<PathBuf>,
pub linker_path: Option<PathBuf>,
pub auto_detected: bool,
}
impl X86ToolchainPath {
pub fn detect(triple: &X86TargetTriple) -> Self {
let mut tc = Self {
triple: triple.clone(),
install_dir: None,
sysroot: None,
resource_dir: None,
c_include_dirs: Vec::new(),
cxx_include_dirs: Vec::new(),
lib_dirs: Vec::new(),
lib32_dirs: Vec::new(),
libx32_dirs: Vec::new(),
crt_objects: HashMap::new(),
compiler_path: None,
assembler_path: None,
linker_path: None,
auto_detected: true,
};
tc.detect_sysroot();
tc.detect_gcc_install();
tc.detect_resource_dir();
tc.detect_include_paths();
tc.detect_library_paths();
tc.detect_crt_objects();
tc.detect_tool_paths();
tc
}
fn detect_sysroot(&mut self) {
let candidates = ["/", "/usr", "/usr/local", "/opt/llvm"];
for candidate in &candidates {
let path = Path::new(candidate);
if path.exists() {
self.sysroot = Some(path.to_path_buf());
break;
}
}
}
fn detect_gcc_install(&mut self) {
let candidates = ["/usr/lib/gcc", "/usr/lib/gcc-cross", "/usr/local/lib/gcc"];
for base in &candidates {
let base_path = Path::new(base);
if let Ok(entries) = fs::read_dir(base_path) {
for entry in entries.flatten() {
let target_dir = entry.path();
if let Ok(versions) = fs::read_dir(&target_dir) {
for ver in versions.flatten() {
let install = ver.path();
if install.join("include").exists() {
self.install_dir = Some(install);
return;
}
}
}
}
}
}
}
fn detect_resource_dir(&mut self) {
let candidates = [
"/usr/lib/clang",
"/usr/local/lib/clang",
"/opt/llvm/lib/clang",
];
for base in &candidates {
let base_path = Path::new(base);
if let Ok(entries) = fs::read_dir(base_path) {
for entry in entries.flatten() {
let ver_path = entry.path();
if ver_path.join("include").exists() {
self.resource_dir = Some(ver_path);
return;
}
}
}
}
if self.resource_dir.is_none() {
self.resource_dir = Some(PathBuf::from("/usr/lib/clang/16/include"));
}
}
fn detect_include_paths(&mut self) {
let triple_str = self.triple.normalized.clone();
let c_candidates = vec![
PathBuf::from("/usr/include"),
PathBuf::from("/usr/local/include"),
format!("/usr/{}/include", triple_str).into(),
];
for path in c_candidates {
if path.exists() {
self.c_include_dirs.push(path);
}
}
let cxx_candidates = vec![
PathBuf::from("/usr/include/c++/v1"),
format!("/usr/include/c++/12").into(),
format!("/usr/include/c++/12/{}/", triple_str).into(),
PathBuf::from("/usr/local/include/c++/v1"),
];
for path in cxx_candidates {
if path.exists() {
self.cxx_include_dirs.push(path);
}
}
if let Some(ref install_dir) = self.install_dir {
let gcc_include = install_dir.join("include");
if gcc_include.exists() {
self.c_include_dirs.push(gcc_include);
}
let gcc_include_fixed = install_dir.join("include-fixed");
if gcc_include_fixed.exists() {
self.c_include_dirs.push(gcc_include_fixed);
}
}
if let Some(ref resource_dir) = self.resource_dir {
let builtin_include = resource_dir.join("include");
if builtin_include.exists() {
self.c_include_dirs.push(builtin_include);
}
}
}
fn detect_library_paths(&mut self) {
let triple_str = self.triple.normalized.clone();
let is_64bit = self.triple.is_64bit;
let is_x32 = self.triple.is_x32;
let lib_candidates = if is_64bit {
vec![
PathBuf::from("/usr/lib64"),
PathBuf::from("/usr/lib"),
PathBuf::from("/usr/lib/x86_64-linux-gnu"),
format!("/usr/{}/lib64", triple_str).into(),
format!("/usr/{}/lib", triple_str).into(),
]
} else if is_x32 {
vec![
PathBuf::from("/usr/libx32"),
PathBuf::from("/usr/lib/x86_64-linux-gnux32"),
]
} else {
vec![
PathBuf::from("/usr/lib32"),
PathBuf::from("/usr/lib"),
PathBuf::from("/usr/lib/i386-linux-gnu"),
format!("/usr/{}/lib32", triple_str).into(),
format!("/usr/{}/lib", triple_str).into(),
]
};
for path in lib_candidates {
if path.exists() {
self.lib_dirs.push(path);
}
}
let lib32_candidates = [
PathBuf::from("/usr/lib32"),
PathBuf::from("/usr/lib/i386-linux-gnu"),
];
for path in &lib32_candidates {
if path.exists() {
self.lib32_dirs.push(path.clone());
}
}
let libx32_candidates = [
PathBuf::from("/usr/libx32"),
PathBuf::from("/usr/lib/x86_64-linux-gnux32"),
];
for path in &libx32_candidates {
if path.exists() {
self.libx32_dirs.push(path.clone());
}
}
if let Some(ref install_dir) = self.install_dir {
let gcc_lib = install_dir.clone();
if gcc_lib.exists() {
self.lib_dirs.push(gcc_lib);
}
}
}
fn detect_crt_objects(&mut self) {
let crt_names = [
"crt1.o",
"crti.o",
"crtbegin.o",
"crtend.o",
"crtn.o",
"Scrt1.o",
"gcrt1.o",
"rcrt1.o",
];
for name in &crt_names {
for lib_dir in &self.lib_dirs {
let candidate = lib_dir.join(name);
if candidate.exists() {
self.crt_objects.insert(name.to_string(), candidate);
break;
}
}
}
}
fn detect_tool_paths(&mut self) {
self.compiler_path = find_executable("clang")
.or_else(|| find_executable("clang-16"))
.or_else(|| find_executable("clang-15"));
self.assembler_path = find_executable("as").or_else(|| find_executable("llvm-mc"));
self.linker_path = find_executable("ld")
.or_else(|| find_executable("ld.lld"))
.or_else(|| find_executable("ld.gold"));
}
pub fn c_include_dirs(&self) -> &[PathBuf] {
&self.c_include_dirs
}
pub fn cxx_include_dirs(&self) -> &[PathBuf] {
&self.cxx_include_dirs
}
pub fn all_include_dirs(&self) -> Vec<PathBuf> {
let mut dirs = self.c_include_dirs.clone();
dirs.extend(self.cxx_include_dirs.clone());
dirs
}
pub fn active_lib_dirs(&self, variant: X86MultilibVariant) -> &[PathBuf] {
match variant {
X86MultilibVariant::Default64 => &self.lib_dirs,
X86MultilibVariant::M32 => &self.lib32_dirs,
X86MultilibVariant::MX32 => &self.libx32_dirs,
}
}
pub fn crt_object(&self, name: &str) -> Option<&PathBuf> {
self.crt_objects.get(name)
}
pub fn resource_dir(&self) -> Option<&PathBuf> {
self.resource_dir.as_ref()
}
}
impl Default for X86ToolchainPath {
fn default() -> Self {
Self::detect(&X86TargetTriple::default())
}
}
impl fmt::Display for X86ToolchainPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Toolchain[triple={}, compiler={:?}, linker={:?}]",
self.triple.normalized, self.compiler_path, self.linker_path,
)
}
}
fn find_executable(name: &str) -> Option<PathBuf> {
if let Ok(paths) = std::env::var("PATH") {
for dir in paths.split(':') {
let candidate = Path::new(dir).join(name);
if candidate.exists() && candidate.is_file() {
return Some(candidate);
}
}
}
None
}
#[derive(Debug, Clone)]
pub struct X86LinkerInvocation {
pub flavor: X86LinkerFlavor,
pub triple: X86TargetTriple,
pub inputs: Vec<PathBuf>,
pub output: Option<PathBuf>,
pub library_paths: Vec<PathBuf>,
pub libraries: Vec<String>,
pub flags: Vec<String>,
pub entry: Option<String>,
pub dynamic_linker: Option<String>,
pub script: Option<PathBuf>,
pub lto_plugin: Option<PathBuf>,
pub shared: bool,
pub strip: bool,
pub allow_unresolved: bool,
pub export_all: bool,
pub export_dynamic: bool,
pub pie: bool,
pub soname: Option<String>,
pub gcc_install: Option<PathBuf>,
pub emulation: Option<String>,
pub subsystem: Option<String>,
pub win_entry: Option<String>,
}
impl X86LinkerInvocation {
pub fn new(flavor: X86LinkerFlavor, triple: &X86TargetTriple) -> Self {
let emulation = match flavor {
X86LinkerFlavor::GnuLd | X86LinkerFlavor::Lld => {
if triple.is_64bit {
Some("elf_x86_64".to_string())
} else if triple.is_x32 {
Some("elf32_x86_64".to_string())
} else {
Some("elf_i386".to_string())
}
}
_ => None,
};
Self {
flavor,
triple: triple.clone(),
inputs: Vec::new(),
output: None,
library_paths: Vec::new(),
libraries: Vec::new(),
flags: Vec::new(),
entry: None,
dynamic_linker: None,
script: None,
lto_plugin: None,
shared: false,
strip: false,
allow_unresolved: false,
export_all: false,
export_dynamic: false,
pie: false,
soname: None,
gcc_install: None,
emulation,
subsystem: None,
win_entry: None,
}
}
pub fn add_input(&mut self, input: &Path) {
self.inputs.push(input.to_path_buf());
}
pub fn add_library_path(&mut self, path: &Path) {
self.library_paths.push(path.to_path_buf());
}
pub fn add_library(&mut self, lib: &str) {
self.libraries.push(lib.to_string());
}
pub fn add_flag(&mut self, flag: &str) {
self.flags.push(flag.to_string());
}
pub fn set_output(&mut self, output: &Path) {
self.output = Some(output.to_path_buf());
}
pub fn set_entry(&mut self, entry: &str) {
self.entry = Some(entry.to_string());
}
pub fn set_lto_plugin(&mut self, plugin: &Path) {
self.lto_plugin = Some(plugin.to_path_buf());
}
pub fn set_dynamic_linker(&mut self, linker: &str) {
self.dynamic_linker = Some(linker.to_string());
}
pub fn to_command(&self) -> Vec<String> {
match self.flavor {
X86LinkerFlavor::GnuLd => self.build_gnu_ld_command(),
X86LinkerFlavor::Lld => self.build_lld_command(),
X86LinkerFlavor::DarwinLd => self.build_darwin_ld_command(),
X86LinkerFlavor::MSVCLink => self.build_msvc_link_command(),
X86LinkerFlavor::LldLink => self.build_lld_link_command(),
}
}
fn build_gnu_ld_command(&self) -> Vec<String> {
let mut cmd = vec!["ld".to_string()];
if let Some(ref emulation) = self.emulation {
cmd.push("-m".to_string());
cmd.push(emulation.clone());
}
if let Some(ref plugin) = self.lto_plugin {
cmd.push(format!("-plugin={}", plugin.display()));
cmd.push("-plugin-opt=mcpu=x86-64".to_string());
}
if let Some(ref linker) = self.dynamic_linker {
cmd.push(format!("-dynamic-linker={}", linker));
} else if !self.shared {
let default_ld = if self.triple.is_64bit {
"/lib64/ld-linux-x86-64.so.2"
} else if self.triple.is_x32 {
"/libx32/ld-linux-x32.so.2"
} else {
"/lib/ld-linux.so.2"
};
cmd.push(format!("-dynamic-linker={}", default_ld));
}
if let Some(ref entry) = self.entry {
cmd.push("-e".to_string());
cmd.push(entry.clone());
} else if !self.shared {
cmd.push("-e".to_string());
cmd.push("_start".to_string());
}
if let Some(ref output) = self.output {
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
}
if self.shared {
cmd.push("-shared".to_string());
if let Some(ref soname) = self.soname {
cmd.push(format!("-soname={}", soname));
}
}
if self.pie {
cmd.push("-pie".to_string());
}
for lp in &self.library_paths {
cmd.push(format!("-L{}", lp.display()));
}
for lib in &self.libraries {
cmd.push(format!("-l{}", lib));
}
if self.strip {
cmd.push("-s".to_string());
}
if self.allow_unresolved {
cmd.push("--allow-shlib-undefined".to_string());
}
if self.export_dynamic {
cmd.push("--export-dynamic".to_string());
}
cmd.push("--build-id".to_string());
cmd.push("--eh-frame-hdr".to_string());
cmd.push("--hash-style=gnu".to_string());
cmd.push("--gc-sections".to_string());
cmd.extend(self.flags.clone());
for input in &self.inputs {
cmd.push(input.to_string_lossy().to_string());
}
cmd
}
fn build_lld_command(&self) -> Vec<String> {
let mut cmd = vec!["ld.lld".to_string()];
if let Some(ref emulation) = self.emulation {
cmd.push("-m".to_string());
cmd.push(emulation.clone());
}
if self.lto_plugin.is_some() {
cmd.push("--lto=thin".to_string());
}
if let Some(ref entry) = self.entry {
cmd.push("--entry".to_string());
cmd.push(entry.clone());
} else if !self.shared {
cmd.push("--entry=_start".to_string());
}
if let Some(ref output) = self.output {
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
}
if self.shared {
cmd.push("-shared".to_string());
}
for lp in &self.library_paths {
cmd.push(format!("-L{}", lp.display()));
}
for lib in &self.libraries {
cmd.push(format!("-l{}", lib));
}
if self.strip {
cmd.push("--strip-all".to_string());
}
if self.export_dynamic {
cmd.push("--export-dynamic".to_string());
}
cmd.push("--build-id".to_string());
cmd.push("--eh-frame-hdr".to_string());
cmd.push("--hash-style=gnu".to_string());
cmd.push("--gc-sections".to_string());
cmd.extend(self.flags.clone());
for input in &self.inputs {
cmd.push(input.to_string_lossy().to_string());
}
cmd
}
fn build_darwin_ld_command(&self) -> Vec<String> {
let mut cmd = vec!["ld".to_string()];
if let Some(ref entry) = self.entry {
cmd.push("-e".to_string());
cmd.push(entry.clone());
}
if let Some(ref output) = self.output {
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
}
if self.shared {
cmd.push("-dylib".to_string());
if let Some(ref soname) = self.soname {
cmd.push(format!("-install_name={}", soname));
}
}
for lp in &self.library_paths {
cmd.push(format!("-L{}", lp.display()));
}
for lib in &self.libraries {
cmd.push(format!("-l{}", lib));
}
cmd.extend(self.flags.clone());
cmd.push("-macosx_version_min".to_string());
cmd.push("10.15".to_string());
cmd.push("-platform_version".to_string());
cmd.push("macos".to_string());
cmd.push("10.15".to_string());
cmd.push("10.15".to_string());
for input in &self.inputs {
cmd.push(input.to_string_lossy().to_string());
}
cmd
}
fn build_msvc_link_command(&self) -> Vec<String> {
let mut cmd = vec!["link.exe".to_string()];
if let Some(ref output) = self.output {
cmd.push(format!("/OUT:{}", output.display()));
}
if self.shared {
cmd.push("/DLL".to_string());
}
if let Some(ref entry) = self.win_entry {
cmd.push(format!("/ENTRY:{}", entry));
}
if let Some(ref subsystem) = self.subsystem {
cmd.push(format!("/SUBSYSTEM:{}", subsystem));
} else {
cmd.push("/SUBSYSTEM:CONSOLE".to_string());
}
for lp in &self.library_paths {
cmd.push(format!("/LIBPATH:{}", lp.display()));
}
for lib in &self.libraries {
cmd.push(format!("{}.lib", lib));
}
if self.strip {
cmd.push("/DEBUG:NONE".to_string());
}
cmd.extend(self.flags.clone());
for input in &self.inputs {
cmd.push(input.to_string_lossy().to_string());
}
cmd
}
fn build_lld_link_command(&self) -> Vec<String> {
let mut cmd = vec!["lld-link".to_string()];
if let Some(ref output) = self.output {
cmd.push(format!("/out:{}", output.display()));
}
if self.shared {
cmd.push("/dll".to_string());
}
if let Some(ref entry) = self.win_entry {
cmd.push(format!("/entry:{}", entry));
}
for lp in &self.library_paths {
cmd.push(format!("/libpath:{}", lp.display()));
}
for lib in &self.libraries {
cmd.push(format!("{}.lib", lib));
}
cmd.extend(self.flags.clone());
for input in &self.inputs {
cmd.push(input.to_string_lossy().to_string());
}
cmd
}
pub fn to_response_file(&self) -> String {
let mut resp = String::new();
for input in &self.inputs {
resp.push_str(&format!("{}\n", input.display()));
}
for lp in &self.library_paths {
resp.push_str(&format!("/LIBPATH:{}\n", lp.display()));
}
for lib in &self.libraries {
resp.push_str(&format!("{}.lib\n", lib));
}
if let Some(ref output) = self.output {
resp.push_str(&format!("/OUT:{}\n", output.display()));
}
resp
}
}
#[derive(Debug, Clone)]
pub struct X86AssemblerInvocation {
pub triple: X86TargetTriple,
pub input: Option<PathBuf>,
pub output: Option<PathBuf>,
pub integrated_as: bool,
pub flags: Vec<String>,
pub gen_debug: bool,
pub arch_flag: Option<String>,
pub use_avx: bool,
pub pic: bool,
}
impl X86AssemblerInvocation {
pub fn new(triple: &X86TargetTriple) -> Self {
Self {
triple: triple.clone(),
input: None,
output: None,
integrated_as: true,
flags: Vec::new(),
gen_debug: false,
arch_flag: None,
use_avx: false,
pic: false,
}
}
pub fn set_input(&mut self, input: &Path) {
self.input = Some(input.to_path_buf());
}
pub fn set_output(&mut self, output: &Path) {
self.output = Some(output.to_path_buf());
}
pub fn add_flag(&mut self, flag: &str) {
self.flags.push(flag.to_string());
}
pub fn to_command(&self) -> Vec<String> {
if self.integrated_as {
self.build_llvm_mc_command()
} else {
self.build_gnu_as_command()
}
}
fn build_llvm_mc_command(&self) -> Vec<String> {
let mut cmd = vec!["llvm-mc".to_string()];
cmd.push(format!("-triple={}", self.triple.normalized));
if self.triple.is_64bit {
cmd.push("-arch=x86-64".to_string());
} else {
cmd.push("-arch=x86".to_string());
}
cmd.push("-filetype=obj".to_string());
if self.pic {
cmd.push("-position-independent".to_string());
}
if self.gen_debug {
cmd.push("-g".to_string());
}
if self.use_avx {
cmd.push("-mattr=+avx".to_string());
}
cmd.extend(self.flags.clone());
if let Some(ref input) = self.input {
cmd.push(input.to_string_lossy().to_string());
}
if let Some(ref output) = self.output {
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
}
cmd
}
fn build_gnu_as_command(&self) -> Vec<String> {
let mut cmd = vec!["as".to_string()];
if self.triple.is_64bit {
cmd.push("--64".to_string());
} else if self.triple.is_x32 {
cmd.push("--x32".to_string());
} else {
cmd.push("--32".to_string());
}
if self.gen_debug {
cmd.push("--gdwarf-5".to_string());
}
if self.use_avx {
cmd.push("-mavx=1".to_string());
}
cmd.extend(self.flags.clone());
if let Some(ref output) = self.output {
cmd.push("-o".to_string());
cmd.push(output.to_string_lossy().to_string());
}
if let Some(ref input) = self.input {
cmd.push(input.to_string_lossy().to_string());
}
cmd
}
}
#[derive(Debug, Clone)]
pub struct X86SanitizerSetup {
pub address: bool,
pub memory: bool,
pub thread: bool,
pub undefined: bool,
pub leak: bool,
pub data_flow: bool,
pub safe_stack: bool,
pub shadow_call_stack: bool,
pub hwaddress: bool,
pub ubsan_recover: bool,
pub ubsan_trap: bool,
pub minimal_runtime: bool,
pub blacklist_file: Option<PathBuf>,
pub extra_flags: Vec<String>,
}
impl X86SanitizerSetup {
pub fn new() -> Self {
Self {
address: false,
memory: false,
thread: false,
undefined: false,
leak: false,
data_flow: false,
safe_stack: false,
shadow_call_stack: false,
hwaddress: false,
ubsan_recover: false,
ubsan_trap: false,
minimal_runtime: false,
blacklist_file: None,
extra_flags: Vec::new(),
}
}
pub fn from_args(args: &X86DriverArgs) -> Self {
let mut setup = Self::new();
if let Some(ref sanitizers) = args.fsanitize {
for san in sanitizers.split(',') {
match san.trim() {
"address" => setup.address = true,
"memory" => setup.memory = true,
"thread" => setup.thread = true,
"undefined" | "undefined-behavior" => setup.undefined = true,
"leak" => setup.leak = true,
"dataflow" => setup.data_flow = true,
"safe-stack" => setup.safe_stack = true,
"shadow-call-stack" => setup.shadow_call_stack = true,
"hwaddress" => setup.hwaddress = true,
_ => {}
}
}
}
if let Some(ref recover) = args.fsanitize_recover {
if recover == "all" || recover.contains("undefined") {
setup.ubsan_recover = true;
}
}
if args.fsanitize_trap.is_some() {
setup.ubsan_trap = true;
}
if args.fsanitize_minimal_runtime {
setup.minimal_runtime = true;
}
setup
}
pub fn has_any(&self) -> bool {
self.address
|| self.memory
|| self.thread
|| self.undefined
|| self.leak
|| self.data_flow
|| self.safe_stack
|| self.shadow_call_stack
|| self.hwaddress
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if !self.has_any() {
return flags;
}
let mut san_list = Vec::new();
if self.address {
san_list.push("address");
}
if self.memory {
san_list.push("memory");
}
if self.thread {
san_list.push("thread");
}
if self.undefined {
san_list.push("undefined");
}
if self.leak {
san_list.push("leak");
}
if self.data_flow {
san_list.push("dataflow");
}
if self.safe_stack {
san_list.push("safe-stack");
}
if self.shadow_call_stack {
san_list.push("shadow-call-stack");
}
if self.hwaddress {
san_list.push("hwaddress");
}
if !san_list.is_empty() {
flags.push(format!("-fsanitize={}", san_list.join(",")));
}
if self.ubsan_recover {
flags.push("-fsanitize-recover=undefined".to_string());
}
if self.ubsan_trap {
flags.push("-fsanitize-trap=undefined".to_string());
}
if self.minimal_runtime {
flags.push("-fsanitize-minimal-runtime".to_string());
}
if let Some(ref bl) = self.blacklist_file {
flags.push(format!("-fsanitize-blacklist={}", bl.display()));
}
flags
}
pub fn linker_libraries(&self) -> Vec<String> {
let mut libs = Vec::new();
if self.address {
libs.push("asan".to_string());
libs.push("asan-preinit".to_string());
}
if self.memory {
libs.push("msan".to_string());
}
if self.thread {
libs.push("tsan".to_string());
}
if self.undefined {
libs.push("ubsan_standalone".to_string());
}
if self.leak {
libs.push("lsan".to_string());
}
if self.data_flow {
libs.push("dfsan".to_string());
}
if self.hwaddress {
libs.push("hwasan".to_string());
}
libs
}
pub fn asan_options(&self) -> Vec<String> {
let mut opts = Vec::new();
if self.address {
opts.push("detect_leaks=1".to_string());
opts.push("check_initialization_order=true".to_string());
opts.push("detect_stack_use_after_return=true".to_string());
opts.push("strict_string_checks=true".to_string());
}
opts
}
pub fn ubsan_options(&self) -> Vec<String> {
let mut opts = Vec::new();
if self.undefined {
opts.push("print_stacktrace=1".to_string());
if self.ubsan_recover {
opts.push("halt_on_error=0".to_string());
}
}
opts
}
pub fn check_compatibility(&self) -> Result<(), String> {
if self.address && self.memory {
return Err("AddressSanitizer and MemorySanitizer are mutually exclusive".to_string());
}
if self.address && self.thread {
return Err("AddressSanitizer and ThreadSanitizer are mutually exclusive".to_string());
}
if self.memory && self.thread {
return Err("MemorySanitizer and ThreadSanitizer are mutually exclusive".to_string());
}
Ok(())
}
}
impl Default for X86SanitizerSetup {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CoverageSetup {
pub coverage_mapping: bool,
pub profile_instr_generate: bool,
pub profile_instr_use: Option<PathBuf>,
pub profile_sample_use: Option<PathBuf>,
pub cs_profile_generate: Option<PathBuf>,
pub cs_profile_use: Option<PathBuf>,
pub coverage_dir: Option<PathBuf>,
pub coverage_notes_file: Option<PathBuf>,
pub coverage_data_file: Option<PathBuf>,
pub function_sections: bool,
pub debug_info_for_profiling: bool,
}
impl X86CoverageSetup {
pub fn new() -> Self {
Self {
coverage_mapping: false,
profile_instr_generate: false,
profile_instr_use: None,
profile_sample_use: None,
cs_profile_generate: None,
cs_profile_use: None,
coverage_dir: None,
coverage_notes_file: None,
coverage_data_file: None,
function_sections: false,
debug_info_for_profiling: false,
}
}
pub fn from_args(args: &X86DriverArgs) -> Self {
let mut setup = Self::new();
if args.fprofile_instr_generate.is_some() {
setup.profile_instr_generate = true;
if let Some(ref dir) = args.fprofile_instr_generate {
if !dir.is_empty() {
setup.coverage_dir = Some(PathBuf::from(dir));
}
}
}
if let Some(ref profile) = args.fprofile_instr_use {
if !profile.is_empty() {
setup.profile_instr_use = Some(PathBuf::from(profile));
}
}
if args.fcoverage_mapping {
setup.coverage_mapping = true;
setup.debug_info_for_profiling = true;
}
if let Some(ref cs_profile) = args.fcs_profile_generate {
setup.cs_profile_generate = Some(PathBuf::from(cs_profile));
}
if args.ffunction_sections {
setup.function_sections = true;
}
setup
}
pub fn has_any(&self) -> bool {
self.coverage_mapping
|| self.profile_instr_generate
|| self.profile_instr_use.is_some()
|| self.profile_sample_use.is_some()
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.coverage_mapping {
flags.push("-fcoverage-mapping".to_string());
}
if self.profile_instr_generate {
if let Some(ref dir) = self.coverage_dir {
flags.push(format!("-fprofile-instr-generate={}", dir.display()));
} else {
flags.push("-fprofile-instr-generate".to_string());
}
}
if let Some(ref profile) = self.profile_instr_use {
flags.push(format!("-fprofile-instr-use={}", profile.display()));
}
if let Some(ref sample) = self.profile_sample_use {
flags.push(format!("-fprofile-sample-use={}", sample.display()));
}
if let Some(ref cs) = self.cs_profile_generate {
flags.push(format!("-fcs-profile-generate={}", cs.display()));
}
if let Some(ref cs) = self.cs_profile_use {
flags.push(format!("-fcs-profile-use={}", cs.display()));
}
if self.function_sections {
flags.push("-ffunction-sections".to_string());
flags.push("-fdata-sections".to_string());
}
if self.debug_info_for_profiling {
flags.push("-fdebug-info-for-profiling".to_string());
}
flags
}
pub fn linker_libraries(&self) -> Vec<String> {
let mut libs = Vec::new();
if self.profile_instr_generate || self.profile_instr_use.is_some() {
libs.push("clang_rt.profile-x86_64".to_string());
}
if self.coverage_mapping {
libs.push("clang_rt.profile-x86_64".to_string());
}
libs
}
pub fn set_default_paths(&mut self) {
if self.coverage_dir.is_none() {
self.coverage_dir = Some(PathBuf::from("."));
}
if self.profile_instr_generate && self.coverage_data_file.is_none() {
self.coverage_data_file = Some(PathBuf::from("default.profraw"));
}
}
}
impl Default for X86CoverageSetup {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86HeaderSearch {
pub triple: X86TargetTriple,
pub c_system_includes: Vec<PathBuf>,
pub cxx_system_includes: Vec<PathBuf>,
pub builtin_includes: Vec<PathBuf>,
pub framework_includes: Vec<PathBuf>,
pub idirafter_paths: Vec<PathBuf>,
pub nostdinc: bool,
pub nostdincxx: bool,
pub standard_includes: Vec<PathBuf>,
}
impl X86HeaderSearch {
pub fn new(triple: &X86TargetTriple, toolchain: &X86ToolchainPath) -> Self {
let mut hs = Self {
triple: triple.clone(),
c_system_includes: Vec::new(),
cxx_system_includes: Vec::new(),
builtin_includes: Vec::new(),
framework_includes: Vec::new(),
idirafter_paths: Vec::new(),
nostdinc: false,
nostdincxx: false,
standard_includes: Vec::new(),
};
hs.populate_from_toolchain(toolchain);
hs
}
fn populate_from_toolchain(&mut self, toolchain: &X86ToolchainPath) {
self.c_system_includes = toolchain.c_include_dirs.clone();
self.cxx_system_includes = toolchain.cxx_include_dirs.clone();
if let Some(ref resource_dir) = toolchain.resource_dir {
let builtin_include = resource_dir.join("include");
if builtin_include.exists() {
self.builtin_includes.push(builtin_include);
}
let share_include = resource_dir.join("share");
if share_include.exists() {
self.builtin_includes.push(share_include);
}
}
let std_includes = [
"/usr/include/c++/v1",
"/usr/include/c++/12",
"/usr/local/include/c++/v1",
];
for path in &std_includes {
let p = PathBuf::from(path);
if p.exists() && !self.cxx_system_includes.contains(&p) {
self.standard_includes.push(p);
}
}
}
pub fn system_include_paths(&self) -> Vec<PathBuf> {
if self.nostdinc {
return Vec::new();
}
let mut paths = Vec::new();
paths.extend(self.builtin_includes.clone());
paths.extend(self.c_system_includes.clone());
paths.extend(self.idirafter_paths.clone());
paths
}
pub fn cxx_include_paths(&self) -> Vec<PathBuf> {
if self.nostdinc || self.nostdincxx {
return Vec::new();
}
let mut paths = Vec::new();
paths.extend(self.standard_includes.clone());
paths.extend(self.cxx_system_includes.clone());
paths
}
pub fn builtin_include_paths(&self) -> Vec<PathBuf> {
self.builtin_includes.clone()
}
pub fn add_framework_path(&mut self, path: &Path) {
self.framework_includes.push(path.to_path_buf());
}
pub fn all_include_paths(&self) -> Vec<PathBuf> {
let mut paths = Vec::new();
paths.extend(self.builtin_include_paths());
paths.extend(self.standard_includes.clone());
paths.extend(self.c_system_includes.clone());
paths.extend(self.cxx_system_includes.clone());
paths.extend(self.framework_includes.clone());
paths.extend(self.idirafter_paths.clone());
paths
}
}
#[derive(Debug, Clone)]
pub struct X86StdLibSetup {
pub triple: X86TargetTriple,
pub c_lib: X86CLibKind,
pub cxx_lib: X86CXXLibKind,
pub std_lib_paths: Vec<PathBuf>,
pub crt_files: Vec<PathBuf>,
pub link_libraries: Vec<String>,
pub use_compiler_rt: bool,
pub use_libunwind: bool,
pub nodefaultlibs: bool,
pub nostdlib: bool,
pub nostartfiles: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CLibKind {
Glibc,
Musl,
Msvcrt,
Bionic,
LibSystem,
Newlib,
DietLibc,
UClibc,
Unknown,
}
impl X86CLibKind {
pub fn default_crt_object(&self) -> &'static str {
match self {
Self::Glibc | Self::Musl | Self::Newlib | Self::DietLibc | Self::UClibc => "crt1.o",
Self::Msvcrt => "crt0.obj",
Self::Bionic => "crtbegin_dynamic.o",
Self::LibSystem => "crt1.o",
Self::Unknown => "crt1.o",
}
}
pub fn default_c_lib(&self) -> &'static str {
match self {
Self::Glibc | Self::Musl | Self::Newlib | Self::DietLibc | Self::UClibc => "c",
Self::Msvcrt => "msvcrt",
Self::Bionic => "c",
Self::LibSystem => "System",
Self::Unknown => "c",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CXXLibKind {
LibStdCXX,
LibCXX,
Msvcprt,
AndroidCXX,
None,
}
impl X86CXXLibKind {
pub fn default_cxx_lib(&self) -> Option<&'static str> {
match self {
Self::LibStdCXX => Some("stdc++"),
Self::LibCXX => Some("c++"),
Self::Msvcprt => Some("msvcprt"),
Self::AndroidCXX => Some("c++_shared"),
Self::None => None,
}
}
}
impl X86StdLibSetup {
pub fn new(triple: &X86TargetTriple, toolchain: &X86ToolchainPath) -> Self {
let c_lib = Self::detect_c_lib(triple);
let cxx_lib = Self::detect_cxx_lib(triple);
let mut setup = Self {
triple: triple.clone(),
c_lib,
cxx_lib,
std_lib_paths: Vec::new(),
crt_files: Vec::new(),
link_libraries: Vec::new(),
use_compiler_rt: false,
use_libunwind: false,
nodefaultlibs: false,
nostdlib: false,
nostartfiles: false,
};
setup.detect_lib_paths(toolchain);
setup.detect_crt_files(toolchain);
setup.setup_default_libraries();
setup
}
fn detect_c_lib(triple: &X86TargetTriple) -> X86CLibKind {
match (triple.os, triple.environment) {
(X86TripleOS::Win32, X86TripleEnvironment::MSVC) => X86CLibKind::Msvcrt,
(X86TripleOS::Android, _) => X86CLibKind::Bionic,
(X86TripleOS::Darwin, _) | (X86TripleOS::MacOSX, _) => X86CLibKind::LibSystem,
(_, X86TripleEnvironment::Musl)
| (_, X86TripleEnvironment::Musleabi)
| (_, X86TripleEnvironment::Musleabihf) => X86CLibKind::Musl,
_ => X86CLibKind::Glibc,
}
}
fn detect_cxx_lib(triple: &X86TargetTriple) -> X86CXXLibKind {
match (triple.os, triple.environment) {
(X86TripleOS::Win32, X86TripleEnvironment::MSVC) => X86CXXLibKind::Msvcprt,
(X86TripleOS::Android, _) => X86CXXLibKind::AndroidCXX,
_ => X86CXXLibKind::LibStdCXX,
}
}
fn detect_lib_paths(&mut self, toolchain: &X86ToolchainPath) {
self.std_lib_paths = toolchain.lib_dirs.clone();
let extra_paths = ["/usr/lib", "/usr/lib64", "/usr/local/lib", "/lib", "/lib64"];
for path in &extra_paths {
let p = PathBuf::from(path);
if p.exists() && !self.std_lib_paths.contains(&p) {
self.std_lib_paths.push(p);
}
}
}
fn detect_crt_files(&mut self, toolchain: &X86ToolchainPath) {
let crt_names = ["crt1.o", "crti.o", "crtbegin.o", "crtend.o", "crtn.o"];
for name in &crt_names {
if let Some(path) = toolchain.crt_object(name).cloned() {
self.crt_files.push(path);
} else {
for lib_dir in &self.std_lib_paths {
let candidate = lib_dir.join(name);
if candidate.exists() {
self.crt_files.push(candidate);
break;
}
}
}
}
}
fn setup_default_libraries(&mut self) {
if self.nostdlib || self.nodefaultlibs {
return;
}
match self.c_lib {
X86CLibKind::Glibc => {
self.link_libraries.extend([
"c".to_string(),
"m".to_string(),
"resolv".to_string(),
"pthread".to_string(),
"dl".to_string(),
"rt".to_string(),
]);
}
X86CLibKind::Musl => {
self.link_libraries.extend([
"c".to_string(),
"m".to_string(),
"pthread".to_string(),
"dl".to_string(),
]);
}
X86CLibKind::Msvcrt => {
self.link_libraries.extend([
"msvcrt".to_string(),
"kernel32".to_string(),
"user32".to_string(),
"advapi32".to_string(),
"shell32".to_string(),
"ole32".to_string(),
]);
}
X86CLibKind::Bionic => {
self.link_libraries
.extend(["c".to_string(), "m".to_string(), "dl".to_string()]);
}
X86CLibKind::LibSystem => {
self.link_libraries.extend([
"System".to_string(),
"c".to_string(),
"m".to_string(),
]);
}
_ => {
self.link_libraries
.extend(["c".to_string(), "m".to_string()]);
}
}
if let Some(cxx_lib) = self.cxx_lib.default_cxx_lib() {
self.link_libraries.push(cxx_lib.to_string());
}
if self.use_compiler_rt {
self.link_libraries
.push("clang_rt.builtins-x86_64".to_string());
} else {
self.link_libraries.push("gcc".to_string());
self.link_libraries.push("gcc_s".to_string());
}
if self.use_libunwind {
self.link_libraries.push("unwind".to_string());
}
}
pub fn library_paths(&self) -> Vec<PathBuf> {
self.std_lib_paths.clone()
}
pub fn default_link_libraries(&self) -> Vec<String> {
self.link_libraries.clone()
}
pub fn crt_startup_files(&self) -> &[PathBuf] {
&self.crt_files
}
}
#[derive(Debug, Clone)]
pub struct X86Multilib {
pub triple: X86TargetTriple,
pub active_variant: X86MultilibVariant,
pub multilib_enabled: bool,
pub include_paths_32: Vec<PathBuf>,
pub include_paths_64: Vec<PathBuf>,
pub include_paths_x32: Vec<PathBuf>,
pub lib_paths_32: Vec<PathBuf>,
pub lib_paths_64: Vec<PathBuf>,
pub lib_paths_x32: Vec<PathBuf>,
pub gcc_multilib_dir: Option<PathBuf>,
pub gcc_multilib_32_dir: Option<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MultilibVariant {
Default64,
M32,
MX32,
}
impl X86MultilibVariant {
pub fn as_str(&self) -> &'static str {
match self {
Self::Default64 => "64",
Self::M32 => "32",
Self::MX32 => "x32",
}
}
pub fn is_64bit(&self) -> bool {
matches!(self, Self::Default64)
}
pub fn is_32bit(&self) -> bool {
matches!(self, Self::M32)
}
pub fn pointer_width_bits(&self) -> u32 {
match self {
Self::Default64 => 64,
Self::M32 => 32,
Self::MX32 => 32,
}
}
pub fn gcc_multilib_suffix(&self) -> &'static str {
match self {
Self::Default64 => "64",
Self::M32 => "32",
Self::MX32 => "x32",
}
}
}
impl fmt::Display for X86MultilibVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl X86Multilib {
pub fn new(triple: &X86TargetTriple) -> Self {
let active = if triple.is_x32 {
X86MultilibVariant::MX32
} else if triple.is_64bit {
X86MultilibVariant::Default64
} else {
X86MultilibVariant::M32
};
let mut m = Self {
triple: triple.clone(),
active_variant: active,
multilib_enabled: true,
include_paths_32: Vec::new(),
include_paths_64: Vec::new(),
include_paths_x32: Vec::new(),
lib_paths_32: Vec::new(),
lib_paths_64: Vec::new(),
lib_paths_x32: Vec::new(),
gcc_multilib_dir: None,
gcc_multilib_32_dir: None,
};
m.detect_multilib_paths();
m
}
fn detect_multilib_paths(&mut self) {
self.lib_paths_32 = vec![
PathBuf::from("/usr/lib32"),
PathBuf::from("/usr/lib/i386-linux-gnu"),
PathBuf::from("/lib32"),
];
self.include_paths_32 = vec![
PathBuf::from("/usr/include/i386-linux-gnu"),
PathBuf::from("/usr/include32"),
];
self.lib_paths_64 = vec![
PathBuf::from("/usr/lib64"),
PathBuf::from("/usr/lib/x86_64-linux-gnu"),
PathBuf::from("/lib64"),
];
self.include_paths_64 = vec![
PathBuf::from("/usr/include/x86_64-linux-gnu"),
PathBuf::from("/usr/include64"),
];
self.lib_paths_x32 = vec![
PathBuf::from("/usr/libx32"),
PathBuf::from("/usr/lib/x86_64-linux-gnux32"),
PathBuf::from("/libx32"),
];
self.include_paths_x32 = vec![PathBuf::from("/usr/include/x86_64-linux-gnux32")];
let gcc_base = PathBuf::from("/usr/lib/gcc/x86_64-linux-gnu");
if gcc_base.exists() {
if let Ok(entries) = fs::read_dir(&gcc_base) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
let dir32 = path.join("32");
if dir32.exists() {
self.gcc_multilib_32_dir = Some(dir32);
}
self.gcc_multilib_dir = Some(path);
break;
}
}
}
}
}
pub fn select_32bit(&mut self) {
self.active_variant = X86MultilibVariant::M32;
}
pub fn select_x32(&mut self) {
self.active_variant = X86MultilibVariant::MX32;
}
pub fn select_64bit(&mut self) {
self.active_variant = X86MultilibVariant::Default64;
}
pub fn library_paths(&self) -> Vec<PathBuf> {
match self.active_variant {
X86MultilibVariant::Default64 => self.lib_paths_64.clone(),
X86MultilibVariant::M32 => self.lib_paths_32.clone(),
X86MultilibVariant::MX32 => self.lib_paths_x32.clone(),
}
}
pub fn include_paths(&self) -> Vec<PathBuf> {
match self.active_variant {
X86MultilibVariant::Default64 => self.include_paths_64.clone(),
X86MultilibVariant::M32 => self.include_paths_32.clone(),
X86MultilibVariant::MX32 => self.include_paths_x32.clone(),
}
}
pub fn gcc_lib_dir(&self) -> Option<PathBuf> {
match self.active_variant {
X86MultilibVariant::Default64 => self.gcc_multilib_dir.clone(),
X86MultilibVariant::M32 => self.gcc_multilib_32_dir.clone(),
X86MultilibVariant::MX32 => self.gcc_multilib_dir.clone(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86DriverArgs {
pub target: Option<String>,
pub target_cpu: Option<String>,
pub target_abi: Option<String>,
pub target_features: Vec<String>,
pub opt_level: Option<X86DriverOptLevel>,
pub m32: bool,
pub m64: bool,
pub mx32: bool,
pub march: Option<String>,
pub mcpu: Option<String>,
pub mtune: Option<String>,
pub mfpmath: Option<String>,
pub mregparm: Option<u32>,
pub mstackrealign: bool,
pub mstack_alignment: Option<u32>,
pub mred_zone: Option<bool>,
pub mcmodel: Option<String>,
pub fPIC: bool,
pub fpic: bool,
pub fPIE: bool,
pub fpie: bool,
pub fno_pic: bool,
pub fsanitize: Option<String>,
pub fsanitize_recover: Option<String>,
pub fsanitize_trap: Option<String>,
pub fsanitize_minimal_runtime: bool,
pub fprofile_instr_generate: Option<String>,
pub fprofile_instr_use: Option<String>,
pub fcoverage_mapping: bool,
pub fcs_profile_generate: Option<String>,
pub ffunction_sections: bool,
pub flto: Option<String>,
pub flto_thin: bool,
pub flto_jobs: Option<u32>,
pub fuse_ld: Option<String>,
pub mno_features: Vec<String>,
pub m_features: Vec<String>,
pub include_prefixes: Vec<String>,
pub include_paths: Vec<String>,
pub system_include_paths: Vec<String>,
pub library_paths: Vec<String>,
pub libraries: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub undefines: Vec<String>,
pub output_file: Option<String>,
pub input_files: Vec<String>,
pub v: bool,
pub verbose: bool,
pub dry_run: bool,
pub wl_flags: Vec<String>,
pub wa_flags: Vec<String>,
pub wp_flags: Vec<String>,
pub xlinker_flags: Vec<String>,
pub xassembler_flags: Vec<String>,
pub preprocess_only: bool,
pub assembly_only: bool,
pub compile_only: bool,
pub shared: bool,
pub static_linking: bool,
pub rdynamic: bool,
pub nodefaultlibs: bool,
pub nostdlib: bool,
pub nostartfiles: bool,
pub nostdinc: bool,
pub nostdincxx: bool,
pub stdlib: Option<String>,
}
impl X86DriverArgs {
pub fn parse(args: &[String]) -> Self {
let mut parsed = Self::default();
let mut i = 0;
while i < args.len() {
let arg = &args[i];
match arg.as_str() {
"-target" => {
if i + 1 < args.len() {
i += 1;
parsed.target = Some(args[i].clone());
}
}
"-m32" => parsed.m32 = true,
"-m64" => parsed.m64 = true,
"-mx32" => parsed.mx32 = true,
"-march" | "--march" => {
if i + 1 < args.len() {
i += 1;
parsed.march = Some(args[i].clone());
}
}
"-mcpu" | "--mcpu" => {
if i + 1 < args.len() {
i += 1;
parsed.mcpu = Some(args[i].clone());
}
}
"-mtune" | "--mtune" => {
if i + 1 < args.len() {
i += 1;
parsed.mtune = Some(args[i].clone());
}
}
"-mfpmath" => {
if i + 1 < args.len() {
i += 1;
parsed.mfpmath = Some(args[i].clone());
}
}
"-mregparm" => {
if i + 1 < args.len() {
i += 1;
parsed.mregparm = args[i].parse::<u32>().ok();
}
}
"-mstackrealign" => parsed.mstackrealign = true,
"-mstack-alignment" => {
if i + 1 < args.len() {
i += 1;
parsed.mstack_alignment = args[i].parse::<u32>().ok();
}
}
"-mred-zone" => parsed.mred_zone = Some(true),
"-mno-red-zone" => parsed.mred_zone = Some(false),
"-mcmodel" => {
if i + 1 < args.len() {
i += 1;
parsed.mcmodel = Some(args[i].clone());
}
}
"-fPIC" => parsed.fPIC = true,
"-fpic" => parsed.fpic = true,
"-fPIE" => parsed.fPIE = true,
"-fpie" => parsed.fpie = true,
"-fno-pic" | "-fno-PIC" | "-fno-pie" | "-fno-PIE" => {
parsed.fno_pic = true;
}
"-fsanitize" => {
if i + 1 < args.len() {
i += 1;
parsed.fsanitize = Some(args[i].clone());
}
}
a if a.starts_with("-fsanitize=") => {
parsed.fsanitize = Some(a[11..].to_string());
}
"-fsanitize-recover" => {
if i + 1 < args.len() {
i += 1;
parsed.fsanitize_recover = Some(args[i].clone());
}
}
a if a.starts_with("-fsanitize-recover=") => {
parsed.fsanitize_recover = Some(a[19..].to_string());
}
"-fsanitize-trap" => {
if i + 1 < args.len() {
i += 1;
parsed.fsanitize_trap = Some(args[i].clone());
}
}
a if a.starts_with("-fsanitize-trap=") => {
parsed.fsanitize_trap = Some(a[16..].to_string());
}
"-fsanitize-minimal-runtime" => parsed.fsanitize_minimal_runtime = true,
"-fprofile-instr-generate" => {
if i + 1 < args.len() && !args[i + 1].starts_with('-') {
i += 1;
parsed.fprofile_instr_generate = Some(args[i].clone());
} else {
parsed.fprofile_instr_generate = Some(String::new());
}
}
a if a.starts_with("-fprofile-instr-generate=") => {
parsed.fprofile_instr_generate = Some(a[25..].to_string());
}
"-fprofile-instr-use" => {
if i + 1 < args.len() {
i += 1;
parsed.fprofile_instr_use = Some(args[i].clone());
}
}
a if a.starts_with("-fprofile-instr-use=") => {
parsed.fprofile_instr_use = Some(a[21..].to_string());
}
"-fcoverage-mapping" => parsed.fcoverage_mapping = true,
"-fcs-profile-generate" => {
if i + 1 < args.len() {
i += 1;
parsed.fcs_profile_generate = Some(args[i].clone());
}
}
a if a.starts_with("-fcs-profile-generate=") => {
parsed.fcs_profile_generate = Some(a[22..].to_string());
}
"-ffunction-sections" => parsed.ffunction_sections = true,
"-flto" => {
if i + 1 < args.len() && !args[i + 1].starts_with('-') {
i += 1;
parsed.flto = Some(args[i].clone());
} else {
parsed.flto = Some("full".to_string());
}
}
a if a.starts_with("-flto=") => {
parsed.flto = Some(a[6..].to_string());
}
"-flto-thin" => {
parsed.flto_thin = true;
parsed.flto = Some("thin".to_string());
}
"-flto-jobs" => {
if i + 1 < args.len() {
i += 1;
parsed.flto_jobs = args[i].parse::<u32>().ok();
}
}
a if a.starts_with("-flto-jobs=") => {
parsed.flto_jobs = a[11..].parse::<u32>().ok();
}
"-fuse-ld" => {
if i + 1 < args.len() {
i += 1;
parsed.fuse_ld = Some(args[i].clone());
}
}
a if a.starts_with("-fuse-ld=") => {
parsed.fuse_ld = Some(a[9..].to_string());
}
"-I" => {
if i + 1 < args.len() {
i += 1;
parsed.include_paths.push(args[i].clone());
}
}
a if a.starts_with("-I") && a.len() > 2 => {
parsed.include_paths.push(a[2..].to_string());
}
"-isystem" => {
if i + 1 < args.len() {
i += 1;
parsed.system_include_paths.push(args[i].clone());
}
}
a if a.starts_with("-isystem") && a.len() > 8 => {
parsed.system_include_paths.push(a[8..].to_string());
}
"-L" => {
if i + 1 < args.len() {
i += 1;
parsed.library_paths.push(args[i].clone());
}
}
a if a.starts_with("-L") && a.len() > 2 => {
parsed.library_paths.push(a[2..].to_string());
}
"-l" => {
if i + 1 < args.len() {
i += 1;
parsed.libraries.push(args[i].clone());
}
}
a if a.starts_with("-l") && a.len() > 2 => {
parsed.libraries.push(a[2..].to_string());
}
"-D" => {
if i + 1 < args.len() {
i += 1;
let def = &args[i];
if let Some(eq) = def.find('=') {
let name = def[..eq].to_string();
let val = def[eq + 1..].to_string();
parsed.defines.push((name, Some(val)));
} else {
parsed.defines.push((def.clone(), Some("1".to_string())));
}
}
}
a if a.starts_with("-D") && a.len() > 2 => {
let def = &a[2..];
if let Some(eq) = def.find('=') {
let name = def[..eq].to_string();
let val = def[eq + 1..].to_string();
parsed.defines.push((name, Some(val)));
} else {
parsed
.defines
.push((def.to_string(), Some("1".to_string())));
}
}
"-U" => {
if i + 1 < args.len() {
i += 1;
parsed.undefines.push(args[i].clone());
}
}
a if a.starts_with("-U") && a.len() > 2 => {
parsed.undefines.push(a[2..].to_string());
}
"-o" => {
if i + 1 < args.len() {
i += 1;
parsed.output_file = Some(args[i].clone());
}
}
"-include" => {
if i + 1 < args.len() {
i += 1;
parsed.include_prefixes.push(args[i].clone());
}
}
a if a.starts_with("-include") && a.len() > 8 => {
parsed.include_prefixes.push(a[8..].to_string());
}
"-v" => parsed.v = true,
"--verbose" => parsed.verbose = true,
"-###" => parsed.dry_run = true,
"-E" => parsed.preprocess_only = true,
"-S" => parsed.assembly_only = true,
"-c" => parsed.compile_only = true,
"-shared" => parsed.shared = true,
"-static" => parsed.static_linking = true,
"-rdynamic" => parsed.rdynamic = true,
"-nodefaultlibs" => parsed.nodefaultlibs = true,
"-nostdlib" => parsed.nostdlib = true,
"-nostartfiles" => parsed.nostartfiles = true,
"-nostdinc" => parsed.nostdinc = true,
"-nostdinc++" | "-nostdincxx" => parsed.nostdincxx = true,
"-stdlib" => {
if i + 1 < args.len() {
i += 1;
parsed.stdlib = Some(args[i].clone());
}
}
a if a.starts_with("-stdlib=") => {
parsed.stdlib = Some(a[9..].to_string());
}
"-Wl," => {
}
a if a.starts_with("-Wl,") => {
for flag in a[4..].split(',') {
if !flag.is_empty() {
parsed.wl_flags.push(flag.to_string());
}
}
}
a if a.starts_with("-Wa,") => {
for flag in a[4..].split(',') {
if !flag.is_empty() {
parsed.wa_flags.push(flag.to_string());
}
}
}
a if a.starts_with("-Wp,") => {
for flag in a[4..].split(',') {
if !flag.is_empty() {
parsed.wp_flags.push(flag.to_string());
}
}
}
"-Xlinker" => {
if i + 1 < args.len() {
i += 1;
parsed.xlinker_flags.push(args[i].clone());
}
}
"-Xassembler" => {
if i + 1 < args.len() {
i += 1;
parsed.xassembler_flags.push(args[i].clone());
}
}
a if a.starts_with("-O") => {
let level = &a[2..];
parsed.opt_level = match level {
"0" => Some(X86DriverOptLevel::O0),
"1" => Some(X86DriverOptLevel::O1),
"2" => Some(X86DriverOptLevel::O2),
"3" => Some(X86DriverOptLevel::O3),
"s" => Some(X86DriverOptLevel::Os),
"z" => Some(X86DriverOptLevel::Oz),
"" => Some(X86DriverOptLevel::O1),
_ => None,
};
}
a if a.starts_with("-mno-") => {
parsed.mno_features.push(a[5..].to_string());
}
a if a.starts_with("-m")
&& !a.starts_with("-m32")
&& !a.starts_with("-m64")
&& !a.starts_with("-mx32")
&& !a.starts_with("-march")
&& !a.starts_with("-mcpu")
&& !a.starts_with("-mtune")
&& !a.starts_with("-mfpmath")
&& !a.starts_with("-mregparm")
&& !a.starts_with("-mstack")
&& !a.starts_with("-mred-zone")
&& !a.starts_with("-mcmodel") =>
{
parsed.m_features.push(a[2..].to_string());
}
a if !a.starts_with('-') => {
parsed.input_files.push(a.to_string());
}
_ => {}
}
i += 1;
}
if parsed.m32 && parsed.target.is_none() {
parsed.target = Some("i386-unknown-linux-gnu".to_string());
}
if parsed.mx32 && parsed.target.is_none() {
parsed.target = Some("x86_64-unknown-linux-gnux32".to_string());
}
parsed
}
pub fn to_clang_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.m32 {
flags.push("-m32".to_string());
}
if self.m64 {
flags.push("-m64".to_string());
}
if self.mx32 {
flags.push("-mx32".to_string());
}
if let Some(ref march) = self.march {
flags.push(format!("-march={}", march));
}
if let Some(ref mcpu) = self.mcpu {
flags.push(format!("-mcpu={}", mcpu));
}
if let Some(ref mtune) = self.mtune {
flags.push(format!("-mtune={}", mtune));
}
if let Some(ref fpmath) = self.mfpmath {
flags.push(format!("-mfpmath={}", fpmath));
}
if let Some(regparm) = self.mregparm {
flags.push(format!("-mregparm={}", regparm));
}
if self.mstackrealign {
flags.push("-mstackrealign".to_string());
}
if let Some(align) = self.mstack_alignment {
flags.push(format!("-mstack-alignment={}", align));
}
if let Some(red_zone) = self.mred_zone {
if red_zone {
flags.push("-mred-zone".to_string());
} else {
flags.push("-mno-red-zone".to_string());
}
}
if let Some(ref cmodel) = self.mcmodel {
flags.push(format!("-mcmodel={}", cmodel));
}
for feat in &self.m_features {
flags.push(format!("-m{}", feat));
}
for feat in &self.mno_features {
flags.push(format!("-mno-{}", feat));
}
if self.fPIC {
flags.push("-fPIC".to_string());
}
if self.fpic {
flags.push("-fpic".to_string());
}
if self.fPIE {
flags.push("-fPIE".to_string());
}
if self.fpie {
flags.push("-fpie".to_string());
}
if self.fno_pic {
flags.push("-fno-pic".to_string());
}
if let Some(ref san) = self.fsanitize {
flags.push(format!("-fsanitize={}", san));
}
if self.fsanitize_minimal_runtime {
flags.push("-fsanitize-minimal-runtime".to_string());
}
if let Some(ref r#gen) = self.fprofile_instr_generate {
if r#gen.is_empty() {
flags.push("-fprofile-instr-generate".to_string());
} else {
flags.push(format!("-fprofile-instr-generate={}", r#gen));
}
}
if let Some(ref profile_path) = self.fprofile_instr_use {
flags.push(format!("-fprofile-instr-use={}", profile_path));
}
if self.fcoverage_mapping {
flags.push("-fcoverage-mapping".to_string());
}
if self.ffunction_sections {
flags.push("-ffunction-sections".to_string());
}
if let Some(ref lto) = self.flto {
flags.push(format!("-flto={}", lto));
}
if let Some(ref ld) = self.fuse_ld {
flags.push(format!("-fuse-ld={}", ld));
}
if let Some(ref abi) = self.target_abi {
flags.push(format!("-mabi={}", abi));
}
flags
}
}
#[derive(Debug, Clone)]
pub struct X86LTOConfig {
pub enabled: bool,
pub thin: bool,
pub thin_lto_jobs: Option<String>,
pub cache_dir: Option<PathBuf>,
pub cache_pruning_interval: u64,
pub cache_max_size_bytes: Option<u64>,
pub cache_max_size_percent: Option<u32>,
pub linker_plugin: Option<PathBuf>,
pub lto_opt_level: Option<u32>,
pub lto_debug_info: bool,
pub new_pass_manager: bool,
pub lto_visibility: X86LTOVisibility,
pub generate_resolution: bool,
pub resolution_file: Option<PathBuf>,
pub sample_profile: Option<PathBuf>,
pub csirpgo: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LTOVisibility {
Default,
Hidden,
Protected,
}
impl X86LTOConfig {
pub fn new() -> Self {
Self {
enabled: false,
thin: false,
thin_lto_jobs: None,
cache_dir: None,
cache_pruning_interval: 3600,
cache_max_size_bytes: None,
cache_max_size_percent: Some(75),
linker_plugin: None,
lto_opt_level: None,
lto_debug_info: false,
new_pass_manager: true,
lto_visibility: X86LTOVisibility::Default,
generate_resolution: false,
resolution_file: None,
sample_profile: None,
csirpgo: false,
}
}
pub fn from_args(args: &X86DriverArgs) -> Self {
let mut config = Self::new();
if let Some(ref lto) = args.flto {
config.enabled = true;
config.thin = lto == "thin";
if args.flto_thin {
config.thin = true;
}
}
if let Some(ref ld) = args.fuse_ld {
if ld == "lld" || ld == "gold" {
config.detect_plugin();
}
}
if let Some(jobs) = args.flto_jobs {
config.thin_lto_jobs = Some(jobs.to_string());
}
config
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn is_thin(&self) -> bool {
self.thin
}
pub fn detect_plugin(&mut self) {
let candidates = [
"/usr/lib/LLVMgold.so",
"/usr/lib64/LLVMgold.so",
"/usr/local/lib/LLVMgold.so",
"/opt/llvm/lib/LLVMgold.so",
"/usr/lib/bfd-plugins/LLVMgold.so",
];
for candidate in &candidates {
let path = Path::new(candidate);
if path.exists() {
self.linker_plugin = Some(path.to_path_buf());
return;
}
}
}
pub fn linker_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if !self.enabled {
return flags;
}
if self.thin {
flags.push("-flto=thin".to_string());
if let Some(ref jobs) = self.thin_lto_jobs {
flags.push(format!("-plugin-opt=jobs={}", jobs));
}
} else {
flags.push("-flto".to_string());
}
if let Some(ref plugin) = self.linker_plugin {
flags.push(format!("-plugin={}", plugin.display()));
}
if let Some(ref cache) = self.cache_dir {
flags.push(format!("-plugin-opt=cache-dir={}", cache.display()));
flags.push(format!(
"-plugin-opt=cache-pruning-interval={}",
self.cache_pruning_interval
));
}
if self.new_pass_manager {
flags.push("-plugin-opt=new-pass-manager".to_string());
}
if self.lto_debug_info {
flags.push("-plugin-opt=debug-info".to_string());
}
flags
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if !self.enabled {
return flags;
}
if self.thin {
flags.push("-flto=thin".to_string());
} else {
flags.push("-flto".to_string());
}
if let Some(opt) = self.lto_opt_level {
flags.push(format!("-flto-O{}", opt));
}
flags
}
}
impl Default for X86LTOConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86BitcodeReproducer {
pub output_dir: PathBuf,
pub capture_preprocessed: bool,
pub capture_bitcode: bool,
pub capture_assembly: bool,
pub generate_replay_script: bool,
pub captured_state: Option<X86ReproducerState>,
pub capture_time: Option<SystemTime>,
}
#[derive(Debug, Clone)]
pub struct X86ReproducerState {
pub triple: String,
pub compiler_command: Vec<String>,
pub linker_command: Vec<String>,
pub include_paths: Vec<PathBuf>,
pub input_files: Vec<PathBuf>,
pub preprocessed_file: Option<PathBuf>,
pub bitcode_file: Option<PathBuf>,
pub assembly_file: Option<PathBuf>,
pub environment: HashMap<String, String>,
pub working_dir: PathBuf,
pub arguments: Vec<String>,
}
impl X86BitcodeReproducer {
pub fn new(output_dir: &Path) -> Self {
Self {
output_dir: output_dir.to_path_buf(),
capture_preprocessed: true,
capture_bitcode: true,
capture_assembly: false,
generate_replay_script: true,
captured_state: None,
capture_time: None,
}
}
pub fn capture_state(&self, driver: &X86ClangDriver) -> Result<(), String> {
fs::create_dir_all(&self.output_dir)
.map_err(|e| format!("Failed to create output dir: {}", e))?;
let mut state = X86ReproducerState {
triple: driver.triple.normalized.clone(),
compiler_command: Vec::new(),
linker_command: Vec::new(),
include_paths: driver.all_include_paths(),
input_files: driver.input_files.clone(),
preprocessed_file: None,
bitcode_file: None,
assembly_file: None,
environment: HashMap::new(),
working_dir: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
arguments: Vec::new(),
};
for (key, value) in std::env::vars() {
state.environment.insert(key, value);
}
if self.generate_replay_script {
let script_path = self.output_dir.join("replay.sh");
let script = self.generate_replay_script(&state);
fs::write(&script_path, script)
.map_err(|e| format!("Failed to write replay script: {}", e))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&script_path)
.map_err(|e| format!("Failed to get metadata: {}", e))?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&script_path, perms)
.map_err(|e| format!("Failed to make script executable: {}", e))?;
}
}
for input in &state.input_files {
if input.exists() {
let dest = self
.output_dir
.join(input.file_name().unwrap_or_else(|| input.as_os_str()));
fs::copy(input, &dest).map_err(|e| format!("Failed to copy input file: {}", e))?;
}
}
Ok(())
}
fn generate_replay_script(&self, state: &X86ReproducerState) -> String {
let mut script = String::new();
script.push_str("#!/bin/sh\n");
script.push_str("# Clang X86 Bitcode Reproducer — Replay Script\n");
script.push_str("# Generated automatically for debugging\n\n");
script.push_str("set -e\n\n");
script.push_str("# Environment\n");
for (key, value) in &state.environment {
if key != "PATH" && key != "HOME" && key != "USER" {
script.push_str(&format!("export {}='{}'\n", key, value));
}
}
script.push('\n');
script.push_str(&format!("cd '{}'\n\n", state.working_dir.display()));
script.push_str("# Replay compilation\n");
script.push_str(&state.compiler_command.join(" "));
script.push('\n');
script
}
pub fn set_state(&mut self, state: X86ReproducerState) {
self.capture_time = Some(SystemTime::now());
self.captured_state = Some(state);
}
pub fn replay_script_path(&self) -> PathBuf {
self.output_dir.join("replay.sh")
}
pub fn has_capture(&self) -> bool {
self.captured_state.is_some()
}
}
pub fn create_x86_64_linux_driver() -> X86ClangDriver {
X86ClangDriver::x86_64_linux()
}
pub fn create_i386_linux_driver() -> X86ClangDriver {
X86ClangDriver::i386_linux()
}
pub fn create_x86_64_windows_driver() -> X86ClangDriver {
X86ClangDriver::x86_64_windows()
}
pub fn parse_x86_driver_args(args: &[String]) -> X86DriverArgs {
X86DriverArgs::parse(args)
}
pub fn detect_host_x86() -> X86HostInfo {
X86HostInfo::detect()
}
pub fn parse_x86_target_triple(s: &str) -> X86TargetTriple {
X86TargetTriple::parse(s)
}
#[derive(Debug, Clone)]
pub struct X86DriverJob {
pub phase: X86CompilationPhase,
pub input: PathBuf,
pub output: PathBuf,
pub arguments: Vec<String>,
pub use_response_file: bool,
pub working_dir: Option<PathBuf>,
pub env_vars: HashMap<String, String>,
pub estimated_memory_mb: u64,
pub timeout_secs: u64,
}
impl X86DriverJob {
pub fn new(phase: X86CompilationPhase, input: &Path, output: &Path) -> Self {
Self {
phase,
input: input.to_path_buf(),
output: output.to_path_buf(),
arguments: Vec::new(),
use_response_file: false,
working_dir: None,
env_vars: HashMap::new(),
estimated_memory_mb: 0,
timeout_secs: 0,
}
}
pub fn add_arg(&mut self, arg: &str) {
self.arguments.push(arg.to_string());
}
pub fn add_args(&mut self, args: &[String]) {
self.arguments.extend(args.iter().cloned());
}
pub fn to_shell_command(&self) -> String {
let mut cmd = String::new();
for (key, value) in &self.env_vars {
cmd.push_str(&format!("{}='{}' ", key, value));
}
if let Some(ref dir) = self.working_dir {
cmd.push_str(&format!("cd '{}' && ", dir.display()));
}
cmd.push_str(&self.arguments.join(" "));
cmd
}
}
#[derive(Debug, Clone)]
pub struct X86DriverJobList {
pub jobs: Vec<X86DriverJob>,
pub parallel: bool,
pub max_parallel_jobs: usize,
}
impl X86DriverJobList {
pub fn new() -> Self {
Self {
jobs: Vec::new(),
parallel: true,
max_parallel_jobs: num_cpus::get(),
}
}
pub fn add_job(&mut self, job: X86DriverJob) {
self.jobs.push(job);
}
pub fn is_empty(&self) -> bool {
self.jobs.is_empty()
}
pub fn len(&self) -> usize {
self.jobs.len()
}
pub fn to_shell_script(&self) -> String {
let mut script = String::from("#!/bin/sh\nset -e\n\n");
for (i, job) in self.jobs.iter().enumerate() {
script.push_str(&format!("# Job {}: {:?}\n", i + 1, job.phase));
script.push_str(&job.to_shell_command());
script.push_str("\n\n");
}
script
}
}
impl Default for X86DriverJobList {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86PredefinedMacros {
pub macros: Vec<(String, String)>,
}
impl X86PredefinedMacros {
pub fn for_triple(triple: &X86TargetTriple) -> Self {
let mut macros = Vec::new();
if triple.is_64bit {
macros.push(("__x86_64__".to_string(), "1".to_string()));
macros.push(("__amd64__".to_string(), "1".to_string()));
} else {
macros.push(("__i386__".to_string(), "1".to_string()));
}
match triple.os {
X86TripleOS::Linux => {
macros.push(("__linux__".to_string(), "1".to_string()));
macros.push(("__gnu_linux__".to_string(), "1".to_string()));
}
X86TripleOS::Darwin | X86TripleOS::MacOSX => {
macros.push(("__APPLE__".to_string(), "1".to_string()));
macros.push(("__MACH__".to_string(), "1".to_string()));
}
X86TripleOS::Win32 => {
macros.push(("_WIN32".to_string(), "1".to_string()));
if triple.is_64bit {
macros.push(("_WIN64".to_string(), "1".to_string()));
}
}
_ => {}
}
macros.push((
"__SIZEOF_POINTER__".to_string(),
triple.arch.pointer_width_bits().to_string(),
));
macros.push((
"__BYTE_ORDER__".to_string(),
"__ORDER_LITTLE_ENDIAN__".to_string(),
));
macros.push(("__ORDER_LITTLE_ENDIAN__".to_string(), "1234".to_string()));
Self { macros }
}
pub fn to_clang_flags(&self) -> Vec<String> {
self.macros
.iter()
.map(|(name, value)| format!("-D{}={}", name, value))
.collect()
}
}
pub trait X86DiagnosticReport {
fn report(&self) -> String;
fn report_short(&self) -> String;
}
impl X86DiagnosticReport for X86ClangDriver {
fn report(&self) -> String {
self.describe()
}
fn report_short(&self) -> String {
format!(
"Driver[triple={}, opt={:?}]",
self.triple.normalized, self.opt_level
)
}
}
impl X86DiagnosticReport for X86TargetTriple {
fn report(&self) -> String {
format!(
"Triple[norm={}, 64bit={}, x32={}]",
self.normalized, self.is_64bit, self.is_x32
)
}
fn report_short(&self) -> String {
self.normalized.clone()
}
}
impl X86DiagnosticReport for X86SanitizerSetup {
fn report(&self) -> String {
let mut parts = Vec::new();
if self.address {
parts.push("addr");
}
if self.memory {
parts.push("mem");
}
if self.thread {
parts.push("thread");
}
if self.undefined {
parts.push("undef");
}
if parts.is_empty() {
"Sanitizers[none]".to_string()
} else {
format!("Sanitizers[{}]", parts.join(","))
}
}
fn report_short(&self) -> String {
if self.has_any() {
"San[on]".to_string()
} else {
"San[off]".to_string()
}
}
}
impl X86DiagnosticReport for X86LTOConfig {
fn report(&self) -> String {
if !self.enabled {
"LTO[off]".to_string()
} else if self.thin {
"LTO[thin]".to_string()
} else {
"LTO[full]".to_string()
}
}
fn report_short(&self) -> String {
if self.enabled {
"LTO[on]".to_string()
} else {
"LTO[off]".to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_host_info_detect() {
let info = X86HostInfo::detect();
assert!(!info.vendor.is_empty());
assert!(!info.brand.is_empty());
assert!(
!info.features.is_empty(),
"Should have at least base features"
);
assert!(info.logical_processors > 0);
assert!(info.physical_cores > 0);
assert!(!info.vector_widths.is_empty());
}
#[test]
fn test_host_info_has_sse2() {
let info = X86HostInfo::detect();
assert!(
info.has_feature("sse2"),
"All x86_64 CPUs must support SSE2"
);
}
#[test]
fn test_host_info_architecture_level() {
let info = X86HostInfo::detect();
assert!(info.supports_arch_level(X86ArchitectureLevel::V1));
}
#[test]
fn test_arch_level_ordering() {
assert!(X86ArchitectureLevel::V4 > X86ArchitectureLevel::V3);
assert!(X86ArchitectureLevel::V3 > X86ArchitectureLevel::V2);
assert!(X86ArchitectureLevel::V2 > X86ArchitectureLevel::V1);
}
#[test]
fn test_arch_level_required_features_v1() {
let feats = X86ArchitectureLevel::V1.required_features();
assert!(feats.contains(&"sse"));
assert!(feats.contains(&"sse2"));
}
#[test]
fn test_arch_level_required_features_v2() {
let feats = X86ArchitectureLevel::V2.required_features();
assert!(feats.contains(&"popcnt"));
assert!(feats.contains(&"sse4_2"));
}
#[test]
fn test_arch_level_required_features_v3() {
let feats = X86ArchitectureLevel::V3.required_features();
assert!(feats.contains(&"avx2"));
assert!(feats.contains(&"bmi2"));
}
#[test]
fn test_arch_level_required_features_v4() {
let feats = X86ArchitectureLevel::V4.required_features();
assert!(feats.contains(&"avx512f"));
assert!(feats.contains(&"avx512bw"));
}
#[test]
fn test_host_info_target_cpu() {
let info = X86HostInfo::detect();
let cpu = info.target_cpu();
assert!(!cpu.is_empty());
}
#[test]
fn test_host_info_target_features_string() {
let info = X86HostInfo::detect();
let feats = info.target_features_string();
assert!(feats.contains("sse"));
assert!(feats.contains("sse2"));
}
#[test]
fn test_host_info_display() {
let info = X86HostInfo::detect();
let display = format!("{}", info);
assert!(display.contains("cores"));
assert!(display.contains("threads"));
}
#[test]
fn test_host_info_max_vector_width() {
let info = X86HostInfo::detect();
assert!(info.max_vector_width_bits() >= 128);
}
#[test]
fn test_arch_level_as_str() {
assert_eq!(X86ArchitectureLevel::V1.as_str(), "x86-64");
assert_eq!(X86ArchitectureLevel::V2.as_str(), "x86-64-v2");
assert_eq!(X86ArchitectureLevel::V3.as_str(), "x86-64-v3");
assert_eq!(X86ArchitectureLevel::V4.as_str(), "x86-64-v4");
}
#[test]
fn test_triple_parse_x86_64_linux_gnu() {
let t = X86TargetTriple::parse("x86_64-unknown-linux-gnu");
assert_eq!(t.arch, X86TripleArch::X86_64);
assert_eq!(t.os, X86TripleOS::Linux);
assert_eq!(t.environment, X86TripleEnvironment::GNU);
assert!(t.is_64bit);
assert!(!t.is_x32);
}
#[test]
fn test_triple_parse_i386_linux_gnu() {
let t = X86TargetTriple::parse("i386-unknown-linux-gnu");
assert_eq!(t.arch, X86TripleArch::I386);
assert!(!t.is_64bit);
}
#[test]
fn test_triple_parse_x86_64_windows_msvc() {
let t = X86TargetTriple::parse("x86_64-pc-windows-msvc");
assert_eq!(t.arch, X86TripleArch::X86_64);
assert_eq!(t.os, X86TripleOS::Win32);
assert_eq!(t.environment, X86TripleEnvironment::MSVC);
assert_eq!(t.object_format, X86ObjectFormat::COFF);
}
#[test]
fn test_triple_parse_x86_64_linux_gnux32() {
let t = X86TargetTriple::parse("x86_64-unknown-linux-gnux32");
assert!(t.is_x32);
assert!(!t.is_64bit);
}
#[test]
fn test_triple_normalized() {
let t = X86TargetTriple::parse("x86_64-unknown-linux-gnu");
assert_eq!(t.normalized, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_triple_data_layout_64() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert!(t.data_layout.contains("p:64:64"));
}
#[test]
fn test_triple_data_layout_32() {
let t = X86TargetTriple::i386_linux_gnu();
assert!(t.data_layout.contains("p:32:32"));
}
#[test]
fn test_triple_default_linker_gnu() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert_eq!(t.default_linker(), X86LinkerFlavor::GnuLd);
}
#[test]
fn test_triple_default_linker_msvc() {
let t = X86TargetTriple::x86_64_windows_msvc();
assert_eq!(t.default_linker(), X86LinkerFlavor::MSVCLink);
}
#[test]
fn test_triple_default_linker_darwin() {
let t = X86TargetTriple::x86_64_apple_darwin();
assert_eq!(t.default_linker(), X86LinkerFlavor::DarwinLd);
}
#[test]
fn test_triple_default_cxx_lib_linux() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert_eq!(t.default_cxx_lib(), "stdc++");
}
#[test]
fn test_triple_default_cxx_lib_windows() {
let t = X86TargetTriple::x86_64_windows_msvc();
assert_eq!(t.default_cxx_lib(), "msvcprt");
}
#[test]
fn test_triple_supports_tls_linux() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert!(t.supports_tls());
}
#[test]
fn test_triple_default_pic_linux() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert!(!t.default_pic);
}
#[test]
fn test_triple_default_pic_darwin() {
let t = X86TargetTriple::x86_64_apple_darwin();
assert!(t.default_pic);
}
#[test]
fn test_triple_object_format() {
assert_eq!(
X86TargetTriple::x86_64_linux_gnu().object_format,
X86ObjectFormat::ELF
);
assert_eq!(
X86TargetTriple::x86_64_windows_msvc().object_format,
X86ObjectFormat::COFF
);
assert_eq!(
X86TargetTriple::x86_64_apple_darwin().object_format,
X86ObjectFormat::MachO
);
}
#[test]
fn test_triple_display() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert_eq!(format!("{}", t), "x86_64-unknown-linux-gnu");
}
#[test]
fn test_driver_new() {
let driver = X86ClangDriver::new();
assert_eq!(driver.triple.normalized, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_driver_x86_64_linux() {
let driver = X86ClangDriver::x86_64_linux();
assert!(driver.triple.is_64bit);
}
#[test]
fn test_driver_i386_linux() {
let driver = X86ClangDriver::i386_linux();
assert!(!driver.triple.is_64bit);
}
#[test]
fn test_driver_x86_64_windows() {
let driver = X86ClangDriver::x86_64_windows();
assert_eq!(driver.triple.os, X86TripleOS::Win32);
assert_eq!(driver.linker_flavor, X86LinkerFlavor::MSVCLink);
}
#[test]
fn test_driver_parse_args_basic() {
let mut driver = X86ClangDriver::new();
let args = vec![
"-c".to_string(),
"-O2".to_string(),
"-o".to_string(),
"output.o".to_string(),
"input.c".to_string(),
];
driver.parse_args(&args);
assert_eq!(driver.opt_level, X86DriverOptLevel::O2);
assert_eq!(driver.output_file, Some(PathBuf::from("output.o")));
assert_eq!(driver.input_files, vec![PathBuf::from("input.c")]);
}
#[test]
fn test_driver_parse_args_m32() {
let mut driver = X86ClangDriver::new();
let args = vec!["-m32".to_string(), "input.c".to_string()];
driver.parse_args(&args);
assert!(!driver.triple.is_64bit);
}
#[test]
fn test_driver_parse_args_sanitizer() {
let mut driver = X86ClangDriver::new();
let args = vec![
"-fsanitize=address,undefined".to_string(),
"input.c".to_string(),
];
driver.parse_args(&args);
assert!(driver.sanitizers.address);
assert!(driver.sanitizers.undefined);
}
#[test]
fn test_driver_all_include_paths() {
let driver = X86ClangDriver::new();
let paths = driver.all_include_paths();
assert!(!paths.is_empty(), "Should have default include paths");
}
#[test]
fn test_driver_all_library_paths() {
let driver = X86ClangDriver::new();
let paths = driver.all_library_paths();
}
#[test]
fn test_driver_default_libraries() {
let driver = X86ClangDriver::x86_64_linux();
let libs = driver.default_libraries();
assert!(libs.contains(&"c".to_string()));
}
#[test]
fn test_driver_build_compiler_command() {
let driver = X86ClangDriver::x86_64_linux();
let cmd = driver.build_compiler_command(Path::new("test.c"), Path::new("test.o"));
assert!(cmd.contains(&"-target=x86_64-unknown-linux-gnu".to_string()));
assert!(cmd.contains(&"-O0".to_string()));
}
#[test]
fn test_driver_set_output_type() {
let mut driver = X86ClangDriver::new();
driver.set_output_type(X86DriverOutputType::Assembly);
assert_eq!(driver.phases.len(), 3);
}
#[test]
fn test_driver_set_output_type_executable() {
let mut driver = X86ClangDriver::new();
driver.set_output_type(X86DriverOutputType::Executable);
assert_eq!(driver.phases.len(), 5);
}
#[test]
fn test_driver_describe() {
let driver = X86ClangDriver::new();
let desc = driver.describe();
assert!(desc.contains("Target:"));
assert!(desc.contains("Host:"));
}
#[test]
fn test_toolchain_detect() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
assert!(tc.auto_detected);
}
#[test]
fn test_toolchain_default() {
let tc = X86ToolchainPath::default();
assert_eq!(tc.triple.normalized, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_toolchain_display() {
let tc = X86ToolchainPath::default();
let display = format!("{}", tc);
assert!(display.contains("Toolchain"));
}
#[test]
fn test_linker_invocation_gnu_ld() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
let cmd = inv.to_command();
assert!(!cmd.is_empty());
assert_eq!(cmd[0], "ld");
}
#[test]
fn test_linker_invocation_lld() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::Lld, &triple);
let cmd = inv.to_command();
assert_eq!(cmd[0], "ld.lld");
}
#[test]
fn test_linker_invocation_msvc() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::MSVCLink, &triple);
let cmd = inv.to_command();
assert_eq!(cmd[0], "link.exe");
}
#[test]
fn test_linker_invocation_darwin() {
let triple = X86TargetTriple::x86_64_apple_darwin();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::DarwinLd, &triple);
let cmd = inv.to_command();
assert_eq!(cmd[0], "ld");
}
#[test]
fn test_linker_invocation_lld_link() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::LldLink, &triple);
let cmd = inv.to_command();
assert_eq!(cmd[0], "lld-link");
}
#[test]
fn test_linker_invocation_shared() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.shared = true;
let cmd = inv.to_command();
assert!(cmd.contains(&"-shared".to_string()));
}
#[test]
fn test_linker_invocation_output() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.set_output(Path::new("a.out"));
let cmd = inv.to_command();
assert!(cmd.contains(&"a.out".to_string()));
}
#[test]
fn test_linker_invocation_library_paths() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.add_library_path(Path::new("/custom/lib"));
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("/custom/lib")));
}
#[test]
fn test_linker_invocation_libraries() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.add_library("pthread");
let cmd = inv.to_command();
assert!(cmd.contains(&"-lpthread".to_string()));
}
#[test]
fn test_linker_invocation_pie() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.pie = true;
let cmd = inv.to_command();
assert!(cmd.contains(&"-pie".to_string()));
}
#[test]
fn test_linker_flavor_str() {
assert_eq!(X86LinkerFlavor::GnuLd.as_str(), "gnu-ld");
assert_eq!(X86LinkerFlavor::Lld.as_str(), "lld");
assert_eq!(X86LinkerFlavor::MSVCLink.as_str(), "msvc-link");
assert_eq!(X86LinkerFlavor::LldLink.as_str(), "lld-link");
}
#[test]
fn test_linker_flavor_supports_lto() {
assert!(X86LinkerFlavor::GnuLd.supports_lto());
assert!(X86LinkerFlavor::Lld.supports_lto());
assert!(X86LinkerFlavor::LldLink.supports_lto());
}
#[test]
fn test_linker_flavor_supports_thin_lto() {
assert!(X86LinkerFlavor::Lld.supports_thin_lto());
assert!(X86LinkerFlavor::LldLink.supports_thin_lto());
assert!(!X86LinkerFlavor::GnuLd.supports_thin_lto());
}
#[test]
fn test_assembler_invocation_llvm_mc() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let inv = X86AssemblerInvocation::new(&triple);
let cmd = inv.to_command();
assert_eq!(cmd[0], "llvm-mc");
}
#[test]
fn test_assembler_invocation_gnu_as() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.integrated_as = false;
let cmd = inv.to_command();
assert_eq!(cmd[0], "as");
}
#[test]
fn test_assembler_invocation_input_output() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.set_input(Path::new("test.s"));
inv.set_output(Path::new("test.o"));
let cmd = inv.to_command();
assert!(cmd.contains(&"test.s".to_string()));
assert!(cmd.contains(&"test.o".to_string()));
}
#[test]
fn test_sanitizer_setup_empty() {
let setup = X86SanitizerSetup::new();
assert!(!setup.has_any());
assert!(setup.compiler_flags().is_empty());
assert!(setup.linker_libraries().is_empty());
}
#[test]
fn test_sanitizer_setup_address() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
assert!(setup.has_any());
let flags = setup.compiler_flags();
assert!(flags.iter().any(|f| f.contains("address")));
}
#[test]
fn test_sanitizer_setup_address_and_undefined() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
setup.undefined = true;
assert!(setup.has_any());
let flags = setup.compiler_flags();
let joined = flags.join(" ");
assert!(joined.contains("address"));
assert!(joined.contains("undefined"));
}
#[test]
fn test_sanitizer_setup_compatibility_address_memory() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
setup.memory = true;
assert!(setup.check_compatibility().is_err());
}
#[test]
fn test_sanitizer_setup_compatibility_address_thread() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
setup.thread = true;
assert!(setup.check_compatibility().is_err());
}
#[test]
fn test_sanitizer_setup_compatibility_ok() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
setup.undefined = true;
assert!(setup.check_compatibility().is_ok());
}
#[test]
fn test_sanitizer_setup_linker_libraries() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
let libs = setup.linker_libraries();
assert!(libs.contains(&"asan".to_string()));
}
#[test]
fn test_sanitizer_setup_ubsan_linker_libraries() {
let mut setup = X86SanitizerSetup::new();
setup.undefined = true;
let libs = setup.linker_libraries();
assert!(libs.contains(&"ubsan_standalone".to_string()));
}
#[test]
fn test_sanitizer_setup_asan_options() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
let opts = setup.asan_options();
assert!(!opts.is_empty());
}
#[test]
fn test_sanitizer_setup_ubsan_options() {
let mut setup = X86SanitizerSetup::new();
setup.undefined = true;
let opts = setup.ubsan_options();
assert!(opts.iter().any(|o| o.contains("print_stacktrace")));
}
#[test]
fn test_sanitizer_setup_from_args() {
let mut args = X86DriverArgs::default();
args.fsanitize = Some("address,undefined".to_string());
let setup = X86SanitizerSetup::from_args(&args);
assert!(setup.address);
assert!(setup.undefined);
}
#[test]
fn test_sanitizer_setup_ubsan_recover() {
let mut args = X86DriverArgs::default();
args.fsanitize = Some("undefined".to_string());
args.fsanitize_recover = Some("undefined".to_string());
let setup = X86SanitizerSetup::from_args(&args);
assert!(setup.ubsan_recover);
}
#[test]
fn test_sanitizer_setup_ubsan_trap() {
let mut args = X86DriverArgs::default();
args.fsanitize_trap = Some("undefined".to_string());
let setup = X86SanitizerSetup::from_args(&args);
assert!(setup.ubsan_trap);
}
#[test]
fn test_coverage_setup_empty() {
let setup = X86CoverageSetup::new();
assert!(!setup.has_any());
assert!(setup.compiler_flags().is_empty());
}
#[test]
fn test_coverage_setup_profile_generate() {
let mut args = X86DriverArgs::default();
args.fprofile_instr_generate = Some(String::new());
let setup = X86CoverageSetup::from_args(&args);
assert!(setup.profile_instr_generate);
assert!(setup.has_any());
}
#[test]
fn test_coverage_setup_profile_use() {
let mut args = X86DriverArgs::default();
args.fprofile_instr_use = Some("profile.profdata".to_string());
let setup = X86CoverageSetup::from_args(&args);
assert!(setup.profile_instr_use.is_some());
}
#[test]
fn test_coverage_setup_coverage_mapping() {
let mut args = X86DriverArgs::default();
args.fcoverage_mapping = true;
let setup = X86CoverageSetup::from_args(&args);
assert!(setup.coverage_mapping);
assert!(setup.debug_info_for_profiling);
}
#[test]
fn test_coverage_setup_compiler_flags() {
let mut setup = X86CoverageSetup::new();
setup.profile_instr_generate = true;
let flags = setup.compiler_flags();
assert!(flags.iter().any(|f| f.contains("fprofile-instr-generate")));
}
#[test]
fn test_coverage_setup_linker_libraries() {
let mut setup = X86CoverageSetup::new();
setup.profile_instr_generate = true;
let libs = setup.linker_libraries();
assert!(!libs.is_empty());
}
#[test]
fn test_header_search_new() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let hs = X86HeaderSearch::new(&triple, &tc);
assert!(!hs.builtin_includes.is_empty() || hs.c_system_includes.is_empty());
}
#[test]
fn test_header_search_nostdinc() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let mut hs = X86HeaderSearch::new(&triple, &tc);
hs.nostdinc = true;
assert!(hs.system_include_paths().is_empty());
}
#[test]
fn test_header_search_nostdincxx() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let mut hs = X86HeaderSearch::new(&triple, &tc);
hs.nostdincxx = true;
assert!(hs.cxx_include_paths().is_empty());
}
#[test]
fn test_stdlib_setup_new_linux() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.c_lib, X86CLibKind::Glibc);
}
#[test]
fn test_stdlib_setup_new_windows() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.c_lib, X86CLibKind::Msvcrt);
}
#[test]
fn test_stdlib_setup_new_musl() {
let triple = X86TargetTriple::x86_64_linux_musl();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.c_lib, X86CLibKind::Musl);
}
#[test]
fn test_stdlib_setup_library_paths() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert!(!setup.library_paths().is_empty());
}
#[test]
fn test_stdlib_setup_default_libraries_glibc() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
let libs = setup.default_link_libraries();
assert!(libs.contains(&"c".to_string()));
}
#[test]
fn test_stdlib_setup_default_libraries_msvcrt() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
let libs = setup.default_link_libraries();
assert!(libs
.iter()
.any(|l| l.contains("msvcrt") || l.contains("kernel32")));
}
#[test]
fn test_clib_kind_default_crt() {
assert_eq!(X86CLibKind::Glibc.default_crt_object(), "crt1.o");
assert_eq!(X86CLibKind::Msvcrt.default_crt_object(), "crt0.obj");
}
#[test]
fn test_clib_kind_default_c_lib() {
assert_eq!(X86CLibKind::Glibc.default_c_lib(), "c");
assert_eq!(X86CLibKind::Msvcrt.default_c_lib(), "msvcrt");
}
#[test]
fn test_cxx_lib_kind_default_cxx_lib() {
assert_eq!(X86CXXLibKind::LibStdCXX.default_cxx_lib(), Some("stdc++"));
assert_eq!(X86CXXLibKind::LibCXX.default_cxx_lib(), Some("c++"));
assert_eq!(X86CXXLibKind::Msvcprt.default_cxx_lib(), Some("msvcprt"));
assert_eq!(X86CXXLibKind::None.default_cxx_lib(), None);
}
#[test]
fn test_multilib_new_64() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let m = X86Multilib::new(&triple);
assert_eq!(m.active_variant, X86MultilibVariant::Default64);
}
#[test]
fn test_multilib_new_32() {
let triple = X86TargetTriple::i386_linux_gnu();
let m = X86Multilib::new(&triple);
assert_eq!(m.active_variant, X86MultilibVariant::M32);
}
#[test]
fn test_multilib_new_x32() {
let triple = X86TargetTriple::x86_64_linux_gnux32();
let m = X86Multilib::new(&triple);
assert_eq!(m.active_variant, X86MultilibVariant::MX32);
}
#[test]
fn test_multilib_select_32bit() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut m = X86Multilib::new(&triple);
m.select_32bit();
assert_eq!(m.active_variant, X86MultilibVariant::M32);
}
#[test]
fn test_multilib_select_x32() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut m = X86Multilib::new(&triple);
m.select_x32();
assert_eq!(m.active_variant, X86MultilibVariant::MX32);
}
#[test]
fn test_multilib_library_paths() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let m = X86Multilib::new(&triple);
let paths = m.library_paths();
assert!(!paths.is_empty());
}
#[test]
fn test_multilib_variant_display() {
assert_eq!(format!("{}", X86MultilibVariant::Default64), "64");
assert_eq!(format!("{}", X86MultilibVariant::M32), "32");
assert_eq!(format!("{}", X86MultilibVariant::MX32), "x32");
}
#[test]
fn test_multilib_variant_pointer_width() {
assert_eq!(X86MultilibVariant::Default64.pointer_width_bits(), 64);
assert_eq!(X86MultilibVariant::M32.pointer_width_bits(), 32);
assert_eq!(X86MultilibVariant::MX32.pointer_width_bits(), 32);
}
#[test]
fn test_driver_args_default() {
let args = X86DriverArgs::default();
assert!(!args.m32);
assert!(!args.m64);
assert!(args.opt_level.is_none());
assert!(args.input_files.is_empty());
}
#[test]
fn test_driver_args_parse_simple() {
let args =
X86DriverArgs::parse(&["-c".to_string(), "-O2".to_string(), "test.c".to_string()]);
assert_eq!(args.opt_level, Some(X86DriverOptLevel::O2));
assert!(args.compile_only);
assert_eq!(args.input_files, vec!["test.c"]);
}
#[test]
fn test_driver_args_parse_target() {
let args = X86DriverArgs::parse(&[
"-target".to_string(),
"i386-unknown-linux-gnu".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.target, Some("i386-unknown-linux-gnu".to_string()));
}
#[test]
fn test_driver_args_parse_m32() {
let args = X86DriverArgs::parse(&["-m32".to_string(), "test.c".to_string()]);
assert!(args.m32);
assert_eq!(args.target, Some("i386-unknown-linux-gnu".to_string()));
}
#[test]
fn test_driver_args_parse_march() {
let args = X86DriverArgs::parse(&[
"-march".to_string(),
"skylake".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.march, Some("skylake".to_string()));
}
#[test]
fn test_driver_args_parse_sanitize() {
let args = X86DriverArgs::parse(&[
"-fsanitize=address,undefined".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.fsanitize, Some("address,undefined".to_string()));
}
#[test]
fn test_driver_args_parse_flto() {
let args = X86DriverArgs::parse(&["-flto=thin".to_string(), "test.c".to_string()]);
assert_eq!(args.flto, Some("thin".to_string()));
}
#[test]
fn test_driver_args_parse_flto_thin_alias() {
let args = X86DriverArgs::parse(&["-flto-thin".to_string(), "test.c".to_string()]);
assert!(args.flto_thin);
assert_eq!(args.flto, Some("thin".to_string()));
}
#[test]
fn test_driver_args_parse_include_paths() {
let args = X86DriverArgs::parse(&[
"-I/usr/local/include".to_string(),
"-I".to_string(),
"/custom/include".to_string(),
"test.c".to_string(),
]);
assert!(args
.include_paths
.contains(&"/usr/local/include".to_string()));
assert!(args.include_paths.contains(&"/custom/include".to_string()));
}
#[test]
fn test_driver_args_parse_library_paths() {
let args = X86DriverArgs::parse(&[
"-L/usr/local/lib".to_string(),
"-L".to_string(),
"/custom/lib".to_string(),
"test.c".to_string(),
]);
assert!(args.library_paths.contains(&"/usr/local/lib".to_string()));
assert!(args.library_paths.contains(&"/custom/lib".to_string()));
}
#[test]
fn test_driver_args_parse_defines() {
let args = X86DriverArgs::parse(&[
"-DFOO=bar".to_string(),
"-D".to_string(),
"BAZ=qux".to_string(),
"-DNODEF".to_string(),
"test.c".to_string(),
]);
assert!(args
.defines
.contains(&("FOO".to_string(), Some("bar".to_string()))));
assert!(args
.defines
.contains(&("NODEF".to_string(), Some("1".to_string()))));
}
#[test]
fn test_driver_args_parse_opt_levels() {
for level in &["-O0", "-O1", "-O2", "-O3", "-Os", "-Oz"] {
let args = X86DriverArgs::parse(&[level.to_string(), "test.c".to_string()]);
assert!(args.opt_level.is_some(), "Failed for {}", level);
}
}
#[test]
fn test_driver_args_parse_pic_flags() {
let args = X86DriverArgs::parse(&["-fPIC".to_string(), "test.c".to_string()]);
assert!(args.fPIC);
}
#[test]
fn test_driver_args_parse_fuse_ld() {
let args = X86DriverArgs::parse(&["-fuse-ld=lld".to_string(), "test.c".to_string()]);
assert_eq!(args.fuse_ld, Some("lld".to_string()));
}
#[test]
fn test_driver_args_to_clang_flags() {
let mut args = X86DriverArgs::default();
args.m32 = true;
args.march = Some("skylake".to_string());
let flags = args.to_clang_flags();
assert!(flags.contains(&"-m32".to_string()));
assert!(flags.contains(&"-march=skylake".to_string()));
}
#[test]
fn test_lto_config_default_disabled() {
let config = X86LTOConfig::default();
assert!(!config.is_enabled());
assert!(!config.is_thin());
}
#[test]
fn test_lto_config_from_args_full() {
let mut args = X86DriverArgs::default();
args.flto = Some("full".to_string());
let config = X86LTOConfig::from_args(&args);
assert!(config.is_enabled());
assert!(!config.is_thin());
}
#[test]
fn test_lto_config_from_args_thin() {
let mut args = X86DriverArgs::default();
args.flto = Some("thin".to_string());
let config = X86LTOConfig::from_args(&args);
assert!(config.is_enabled());
assert!(config.is_thin());
}
#[test]
fn test_lto_config_linker_flags_disabled() {
let config = X86LTOConfig::default();
assert!(config.linker_flags().is_empty());
}
#[test]
fn test_lto_config_linker_flags_enabled() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.thin = true;
let flags = config.linker_flags();
assert!(!flags.is_empty());
}
#[test]
fn test_lto_config_compiler_flags() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.thin = true;
let flags = config.compiler_flags();
assert!(flags.iter().any(|f| f.contains("flto")));
}
#[test]
fn test_reproducer_new() {
let reproducer = X86BitcodeReproducer::new(Path::new("/tmp/test_repro"));
assert_eq!(reproducer.output_dir, PathBuf::from("/tmp/test_repro"));
assert!(!reproducer.has_capture());
}
#[test]
fn test_reproducer_replay_script_path() {
let reproducer = X86BitcodeReproducer::new(Path::new("/tmp/test_repro"));
assert_eq!(
reproducer.replay_script_path(),
PathBuf::from("/tmp/test_repro/replay.sh")
);
}
#[test]
fn test_reproducer_set_state() {
let mut reproducer = X86BitcodeReproducer::new(Path::new("/tmp/test_repro"));
let state = X86ReproducerState {
triple: "x86_64-unknown-linux-gnu".to_string(),
compiler_command: vec!["clang".to_string()],
linker_command: vec!["ld".to_string()],
include_paths: vec![],
input_files: vec![],
preprocessed_file: None,
bitcode_file: None,
assembly_file: None,
environment: HashMap::new(),
working_dir: PathBuf::from("."),
arguments: vec![],
};
reproducer.set_state(state);
assert!(reproducer.has_capture());
assert!(reproducer.capture_time.is_some());
}
#[test]
fn test_create_x86_64_linux_driver() {
let driver = create_x86_64_linux_driver();
assert!(driver.triple.is_64bit);
}
#[test]
fn test_create_i386_linux_driver() {
let driver = create_i386_linux_driver();
assert!(!driver.triple.is_64bit);
}
#[test]
fn test_create_x86_64_windows_driver() {
let driver = create_x86_64_windows_driver();
assert_eq!(driver.triple.os, X86TripleOS::Win32);
}
#[test]
fn test_parse_x86_driver_args_fn() {
let args = vec!["-c".to_string(), "test.c".to_string()];
let parsed = parse_x86_driver_args(&args);
assert!(parsed.compile_only);
}
#[test]
fn test_detect_host_x86_fn() {
let info = detect_host_x86();
assert!(!info.vendor.is_empty());
}
#[test]
fn test_parse_x86_target_triple_fn() {
let t = parse_x86_target_triple("x86_64-unknown-linux-gnu");
assert!(t.is_64bit);
}
#[test]
fn test_driver_full_pipeline_x86_64_linux() {
let driver = X86ClangDriver::x86_64_linux();
assert_eq!(driver.triple.arch, X86TripleArch::X86_64);
assert_eq!(driver.triple.os, X86TripleOS::Linux);
assert_eq!(driver.linker_flavor, X86LinkerFlavor::GnuLd);
assert_eq!(driver.stdlib.c_lib, X86CLibKind::Glibc);
}
#[test]
fn test_driver_full_pipeline_i386_linux() {
let driver = X86ClangDriver::i386_linux();
assert_eq!(driver.triple.arch, X86TripleArch::I386);
assert_eq!(driver.multilib.active_variant, X86MultilibVariant::M32);
}
#[test]
fn test_driver_parse_and_build_linker() {
let mut driver = X86ClangDriver::x86_64_linux();
let args = vec![
"-O2".to_string(),
"test.c".to_string(),
"-o".to_string(),
"a.out".to_string(),
];
driver.parse_args(&args);
let inv = driver.build_linker_invocation();
let cmd = inv.to_command();
assert!(!cmd.is_empty());
}
#[test]
fn test_driver_with_sanitizer_and_lto() {
let mut driver = X86ClangDriver::x86_64_linux();
let args = vec![
"-fsanitize=address".to_string(),
"-flto=thin".to_string(),
"test.c".to_string(),
];
driver.parse_args(&args);
assert!(driver.sanitizers.address);
assert!(driver.lto_config.is_enabled());
}
#[test]
fn test_driver_parse_full_command() {
let args = vec![
"-target".to_string(),
"x86_64-unknown-linux-gnu".to_string(),
"-march=skylake".to_string(),
"-O2".to_string(),
"-g".to_string(),
"-fPIC".to_string(),
"-I/usr/include".to_string(),
"-L/usr/lib64".to_string(),
"-lpthread".to_string(),
"-DDEBUG=1".to_string(),
"-o".to_string(),
"output".to_string(),
"input.c".to_string(),
];
let mut driver = X86ClangDriver::new();
driver.parse_args(&args);
assert_eq!(driver.opt_level, X86DriverOptLevel::O2);
assert_eq!(driver.x86_args.march, Some("skylake".to_string()));
assert_eq!(driver.pic_level, X86DriverPicLevel::PIC);
assert_eq!(driver.output_file, Some(PathBuf::from("output")));
assert!(driver.libraries.contains(&"pthread".to_string()));
}
#[test]
fn test_x86_os_enum() {
assert!(X86TripleOS::Linux.is_unix());
assert!(X86TripleOS::Linux.is_elf());
assert!(X86TripleOS::Darwin.is_macho());
assert!(X86TripleOS::Win32.is_coff());
assert!(!X86TripleOS::Unknown.is_unix());
}
#[test]
fn test_x86_env_enum() {
assert!(X86TripleEnvironment::GNU.is_gnu());
assert!(X86TripleEnvironment::MSVC.is_msvc());
assert!(X86TripleEnvironment::Musl.is_musl());
assert!(!X86TripleEnvironment::Unknown.is_gnu());
}
#[test]
fn test_driver_output_type_extension() {
assert_eq!(X86DriverOutputType::Assembly.extension(), ".s");
assert_eq!(X86DriverOutputType::Object.extension(), ".o");
assert_eq!(X86DriverOutputType::SharedLibrary.extension(), ".so");
assert_eq!(X86DriverOutputType::Executable.extension(), "");
}
#[test]
fn test_driver_output_type_requires_linking() {
assert!(X86DriverOutputType::Executable.requires_linking());
assert!(X86DriverOutputType::SharedLibrary.requires_linking());
assert!(!X86DriverOutputType::Object.requires_linking());
assert!(!X86DriverOutputType::Assembly.requires_linking());
}
#[test]
fn test_compilation_phase_all() {
let all = X86CompilationPhase::all();
assert_eq!(all.len(), 5);
assert_eq!(all[0], X86CompilationPhase::Preprocess);
assert_eq!(all[4], X86CompilationPhase::Link);
}
#[test]
fn test_opt_level_as_str() {
assert_eq!(X86DriverOptLevel::O0.as_str(), "-O0");
assert_eq!(X86DriverOptLevel::O3.as_str(), "-O3");
assert_eq!(X86DriverOptLevel::Os.as_str(), "-Os");
}
#[test]
fn test_opt_level_is_optimizing() {
assert!(X86DriverOptLevel::O3.is_optimizing());
assert!(!X86DriverOptLevel::O0.is_optimizing());
}
#[test]
fn test_lto_visibility() {
assert_eq!(X86LTOVisibility::Default as i32, 0);
assert_eq!(X86LTOVisibility::Hidden as i32, 1);
assert_eq!(X86LTOVisibility::Protected as i32, 2);
}
#[test]
fn test_linker_invocation_response_file() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::MSVCLink, &triple);
inv.add_input(Path::new("test.obj"));
inv.set_output(Path::new("test.exe"));
let resp = inv.to_response_file();
assert!(resp.contains("test.obj"));
assert!(resp.contains("test.exe"));
}
#[test]
fn test_coverage_setup_set_default_paths() {
let mut setup = X86CoverageSetup::new();
setup.profile_instr_generate = true;
setup.set_default_paths();
assert!(setup.coverage_dir.is_some());
assert!(setup.coverage_data_file.is_some());
}
#[test]
fn test_host_info_default() {
let info = X86HostInfo::default();
assert!(!info.vendor.is_empty());
assert!(info.logical_processors > 0);
}
#[test]
fn test_host_info_has_feature_sse() {
let info = X86HostInfo::detect();
assert!(info.has_feature("sse"));
assert!(info.has_feature("sse2"));
}
#[test]
fn test_host_info_has_feature_negative() {
let info = X86HostInfo::detect();
assert!(!info.has_feature("avx999"));
}
#[test]
fn test_host_info_max_vector_width_min() {
let info = X86HostInfo::detect();
assert!(info.max_vector_width_bits() >= 128);
}
#[test]
fn test_host_info_has_avx512_detection() {
let info = X86HostInfo::detect();
assert_eq!(info.has_avx512, info.features.contains("avx512f"));
}
#[test]
fn test_host_info_has_amx_detection() {
let info = X86HostInfo::detect();
assert_eq!(info.has_amx, info.features.contains("amx-tile"));
}
#[test]
fn test_arch_level_from_features_empty() {
let feats = HashSet::new();
assert_eq!(
X86ArchitectureLevel::from_features(&feats),
X86ArchitectureLevel::V1
);
}
#[test]
fn test_arch_level_from_features_v2_exact() {
let feats: HashSet<String> = ["cx16", "lahf_lm", "popcnt", "sse4_1", "sse4_2", "ssse3"]
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(
X86ArchitectureLevel::from_features(&feats),
X86ArchitectureLevel::V2
);
}
#[test]
fn test_arch_level_display() {
assert_eq!(format!("{}", X86ArchitectureLevel::V1), "x86-64");
assert_eq!(format!("{}", X86ArchitectureLevel::V4), "x86-64-v4");
}
#[test]
fn test_host_info_sched_model_detection() {
let info = X86HostInfo::detect();
assert!(!info.sched_model.is_empty());
assert!(!info.uarch_codename.is_empty());
}
#[test]
fn test_host_info_cache_defaults() {
let info = X86HostInfo::detect();
assert!(info.cache_l1_data_kb > 0);
assert!(info.cache_l2_kb > 0);
assert!(info.cache_line_size > 0);
}
#[test]
fn test_triple_parse_amd64() {
let t = X86TargetTriple::parse("amd64-unknown-linux-gnu");
assert_eq!(t.arch, X86TripleArch::X86_64);
}
#[test]
fn test_triple_parse_x64() {
let t = X86TargetTriple::parse("x64-pc-windows-msvc");
assert_eq!(t.arch, X86TripleArch::X86_64);
}
#[test]
fn test_triple_parse_empty() {
let t = X86TargetTriple::parse("");
assert_eq!(t.arch, X86TripleArch::X86_64);
}
#[test]
fn test_triple_parse_unknown_components() {
let t = X86TargetTriple::parse("x86_64-foo-bar-baz");
assert_eq!(t.arch, X86TripleArch::X86_64);
assert_eq!(t.vendor, X86TripleVendor::Unknown);
assert_eq!(t.os, X86TripleOS::Unknown);
}
#[test]
fn test_triple_arch_pointer_width_64() {
assert_eq!(X86TripleArch::X86_64.pointer_width_bits(), 64);
}
#[test]
fn test_triple_arch_pointer_width_32() {
assert_eq!(X86TripleArch::I386.pointer_width_bits(), 32);
}
#[test]
fn test_triple_arch_pointer_width_x32() {
assert_eq!(X86TripleArch::X32.pointer_width_bits(), 32);
}
#[test]
fn test_triple_arch_is_64bit_core2() {
assert!(X86TripleArch::Core2.is_64bit());
}
#[test]
fn test_triple_arch_is_64bit_athlon64() {
assert!(X86TripleArch::Athlon64.is_64bit());
}
#[test]
fn test_triple_arch_is_64bit_skylake() {
assert!(X86TripleArch::Skylake.is_64bit());
}
#[test]
fn test_triple_vendor_from_str() {
assert_eq!(X86TripleVendor::from_str("pc"), X86TripleVendor::PC);
assert_eq!(X86TripleVendor::from_str("apple"), X86TripleVendor::Apple);
assert_eq!(X86TripleVendor::from_str("amd"), X86TripleVendor::AMD);
assert_eq!(X86TripleVendor::from_str("intel"), X86TripleVendor::Intel);
assert_eq!(
X86TripleVendor::from_str("foobar"),
X86TripleVendor::Unknown
);
}
#[test]
fn test_triple_vendor_display() {
assert_eq!(format!("{}", X86TripleVendor::PC), "pc");
assert_eq!(format!("{}", X86TripleVendor::Apple), "apple");
}
#[test]
fn test_triple_os_is_unix() {
assert!(X86TripleOS::FreeBSD.is_unix());
assert!(X86TripleOS::NetBSD.is_unix());
assert!(X86TripleOS::OpenBSD.is_unix());
assert!(X86TripleOS::Solaris.is_unix());
assert!(!X86TripleOS::Win32.is_unix());
}
#[test]
fn test_triple_os_is_elf() {
assert!(X86TripleOS::FreeBSD.is_elf());
assert!(X86TripleOS::DragonFly.is_elf());
assert!(X86TripleOS::Haiku.is_elf());
assert!(X86TripleOS::Fuchsia.is_elf());
assert!(!X86TripleOS::Win32.is_elf());
}
#[test]
fn test_triple_os_display() {
assert_eq!(format!("{}", X86TripleOS::Linux), "linux");
assert_eq!(format!("{}", X86TripleOS::Darwin), "darwin");
assert_eq!(format!("{}", X86TripleOS::Win32), "win32");
}
#[test]
fn test_triple_env_from_str_all() {
assert_eq!(
X86TripleEnvironment::from_str("gnu"),
X86TripleEnvironment::GNU
);
assert_eq!(
X86TripleEnvironment::from_str("gnueabi"),
X86TripleEnvironment::GNUEABI
);
assert_eq!(
X86TripleEnvironment::from_str("gnueabihf"),
X86TripleEnvironment::GNUEABIHF
);
assert_eq!(
X86TripleEnvironment::from_str("gnux32"),
X86TripleEnvironment::GNUX32
);
assert_eq!(
X86TripleEnvironment::from_str("musl"),
X86TripleEnvironment::Musl
);
assert_eq!(
X86TripleEnvironment::from_str("musleabi"),
X86TripleEnvironment::Musleabi
);
assert_eq!(
X86TripleEnvironment::from_str("msvc"),
X86TripleEnvironment::MSVC
);
assert_eq!(
X86TripleEnvironment::from_str("android"),
X86TripleEnvironment::Android
);
assert_eq!(
X86TripleEnvironment::from_str("itanium"),
X86TripleEnvironment::Itanium
);
assert_eq!(
X86TripleEnvironment::from_str("cygnus"),
X86TripleEnvironment::Cygnus
);
assert_eq!(
X86TripleEnvironment::from_str("coreclr"),
X86TripleEnvironment::CoreCLR
);
assert_eq!(
X86TripleEnvironment::from_str("unknown-xyz"),
X86TripleEnvironment::Unknown
);
}
#[test]
fn test_triple_env_is_gnu() {
assert!(X86TripleEnvironment::GNU.is_gnu());
assert!(X86TripleEnvironment::GNUEABI.is_gnu());
assert!(X86TripleEnvironment::GNUEABIHF.is_gnu());
assert!(X86TripleEnvironment::GNUX32.is_gnu());
assert!(!X86TripleEnvironment::MSVC.is_gnu());
assert!(!X86TripleEnvironment::Musl.is_gnu());
}
#[test]
fn test_triple_env_is_musl() {
assert!(X86TripleEnvironment::Musl.is_musl());
assert!(X86TripleEnvironment::Musleabi.is_musl());
assert!(X86TripleEnvironment::Musleabihf.is_musl());
assert!(!X86TripleEnvironment::GNU.is_musl());
}
#[test]
fn test_triple_env_is_android() {
assert!(X86TripleEnvironment::Android.is_android());
assert!(X86TripleEnvironment::Androideabi.is_android());
assert!(!X86TripleEnvironment::GNU.is_android());
}
#[test]
fn test_triple_default_seh_linux() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert!(!t.default_seh());
}
#[test]
fn test_triple_default_seh_windows() {
let t = X86TargetTriple::x86_64_windows_msvc();
assert!(t.default_seh());
}
#[test]
fn test_triple_default_dwarf_eh_linux() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert!(t.default_dwarf_eh());
}
#[test]
fn test_triple_default_crt_object_windows() {
let t = X86TargetTriple::x86_64_windows_msvc();
assert_eq!(t.default_crt_object(), Some("msvcrt"));
}
#[test]
fn test_triple_default_crt_object_linux() {
let t = X86TargetTriple::x86_64_linux_gnu();
assert_eq!(t.default_crt_object(), None);
}
#[test]
fn test_triple_x86_64_linux_musl_factory() {
let t = X86TargetTriple::x86_64_linux_musl();
assert_eq!(t.environment, X86TripleEnvironment::Musl);
assert!(t.is_64bit);
}
#[test]
fn test_triple_i686_linux_android_factory() {
let t = X86TargetTriple::i686_linux_android();
assert_eq!(t.environment, X86TripleEnvironment::Android);
}
#[test]
fn test_triple_x86_64_linux_android_factory() {
let t = X86TargetTriple::x86_64_linux_android();
assert_eq!(t.environment, X86TripleEnvironment::Android);
assert!(t.is_64bit);
}
#[test]
fn test_triple_x86_64_linux_gnux32_factory() {
let t = X86TargetTriple::x86_64_linux_gnux32();
assert!(t.is_x32);
}
#[test]
fn test_object_format_as_str() {
assert_eq!(X86ObjectFormat::ELF.as_str(), "elf");
assert_eq!(X86ObjectFormat::MachO.as_str(), "macho");
assert_eq!(X86ObjectFormat::COFF.as_str(), "coff");
assert_eq!(X86ObjectFormat::Wasm.as_str(), "wasm");
}
#[test]
fn test_code_model_as_str() {
assert_eq!(X86CodeModel::Tiny.as_str(), "tiny");
assert_eq!(X86CodeModel::Small.as_str(), "small");
assert_eq!(X86CodeModel::Kernel.as_str(), "kernel");
assert_eq!(X86CodeModel::Medium.as_str(), "medium");
assert_eq!(X86CodeModel::Large.as_str(), "large");
}
#[test]
fn test_triple_default_code_model() {
let t = X86TargetTriple::default();
assert_eq!(t.default_code_model, X86CodeModel::Small);
}
#[test]
fn test_opt_level_default() {
let opt = X86DriverOptLevel::default();
assert_eq!(opt, X86DriverOptLevel::O0);
}
#[test]
fn test_opt_level_lto_default() {
assert!(!X86DriverOptLevel::O0.lto_enabled_by_default());
assert!(!X86DriverOptLevel::O2.lto_enabled_by_default());
}
#[test]
fn test_debug_info_default() {
let di = X86DriverDebugInfo::default();
assert!(!di.has_debug());
}
#[test]
fn test_debug_info_has_debug() {
assert!(X86DriverDebugInfo::Full.has_debug());
assert!(X86DriverDebugInfo::Limited.has_debug());
assert!(X86DriverDebugInfo::LineTablesOnly.has_debug());
assert!(!X86DriverDebugInfo::None.has_debug());
}
#[test]
fn test_pic_level_default() {
let pic = X86DriverPicLevel::default();
assert_eq!(pic, X86DriverPicLevel::Default);
}
#[test]
fn test_output_type_extension_all() {
assert_eq!(X86DriverOutputType::PreprocessedSource.extension(), ".i");
assert_eq!(X86DriverOutputType::LLVMIR.extension(), ".ll");
assert_eq!(X86DriverOutputType::LLVMBitcode.extension(), ".bc");
assert_eq!(X86DriverOutputType::Assembly.extension(), ".s");
assert_eq!(X86DriverOutputType::Object.extension(), ".o");
assert_eq!(X86DriverOutputType::SharedLibrary.extension(), ".so");
assert_eq!(X86DriverOutputType::Executable.extension(), "");
}
#[test]
fn test_output_type_requires_linking_all() {
assert!(X86DriverOutputType::Executable.requires_linking());
assert!(X86DriverOutputType::SharedLibrary.requires_linking());
assert!(!X86DriverOutputType::Object.requires_linking());
assert!(!X86DriverOutputType::Assembly.requires_linking());
assert!(!X86DriverOutputType::LLVMIR.requires_linking());
assert!(!X86DriverOutputType::LLVMBitcode.requires_linking());
assert!(!X86DriverOutputType::PreprocessedSource.requires_linking());
}
#[test]
fn test_compilation_phase_name() {
assert_eq!(X86CompilationPhase::Preprocess.name(), "preprocess");
assert_eq!(X86CompilationPhase::Compile.name(), "compile");
assert_eq!(X86CompilationPhase::Backend.name(), "backend");
assert_eq!(X86CompilationPhase::Assemble.name(), "assemble");
assert_eq!(X86CompilationPhase::Link.name(), "link");
}
#[test]
fn test_compilation_phase_ordering() {
let all = X86CompilationPhase::all();
for i in 1..all.len() {
assert!(all[i] > all[i - 1], "Phases must be ordered");
}
}
#[test]
fn test_driver_set_output_type_preprocessed() {
let mut driver = X86ClangDriver::new();
driver.set_output_type(X86DriverOutputType::PreprocessedSource);
assert_eq!(driver.phases.len(), 1);
assert_eq!(driver.phases[0], X86CompilationPhase::Preprocess);
}
#[test]
fn test_driver_set_output_type_llvm_ir() {
let mut driver = X86ClangDriver::new();
driver.set_output_type(X86DriverOutputType::LLVMIR);
assert_eq!(driver.phases.len(), 2);
}
#[test]
fn test_driver_set_output_type_object() {
let mut driver = X86ClangDriver::new();
driver.set_output_type(X86DriverOutputType::Object);
assert_eq!(driver.phases.len(), 4);
}
#[test]
fn test_driver_set_output_type_shared_library() {
let mut driver = X86ClangDriver::new();
driver.set_output_type(X86DriverOutputType::SharedLibrary);
assert_eq!(driver.phases.len(), 5);
}
#[test]
fn test_driver_enable_reproducer() {
let mut driver = X86ClangDriver::new();
driver.enable_reproducer(Path::new("/tmp/repro"));
assert!(driver.reproducer.is_some());
}
#[test]
fn test_driver_capture_for_reproducer_noop_when_none() {
let driver = X86ClangDriver::new();
driver.capture_for_reproducer();
}
#[test]
fn test_driver_compile_no_inputs() {
let driver = X86ClangDriver::new();
let result = driver.compile();
assert!(result.is_err());
}
#[test]
fn test_driver_build_preprocess_command() {
let driver = X86ClangDriver::x86_64_linux();
let cmd = driver.build_preprocess_command(Path::new("test.c"), Path::new("test.i"));
assert!(cmd.contains(&"-E".to_string()));
}
#[test]
fn test_driver_build_backend_command() {
let driver = X86ClangDriver::x86_64_linux();
let cmd = driver.build_backend_command(Path::new("test.bc"), Path::new("test.o"));
assert!(cmd.contains(&"-filetype=obj".to_string()));
}
#[test]
fn test_driver_default_new_is_linux_64() {
let driver = X86ClangDriver::new();
assert_eq!(driver.triple.normalized, "x86_64-unknown-linux-gnu");
assert!(driver.triple.is_64bit);
}
#[test]
fn test_driver_default_is_not_dry_run() {
let driver = X86ClangDriver::new();
assert!(!driver.dry_run);
}
#[test]
fn test_driver_build_assembler_invocation() {
let driver = X86ClangDriver::x86_64_linux();
let inv = driver.build_assembler_invocation(Path::new("test.s"), Path::new("test.o"));
let cmd = inv.to_command();
assert!(!cmd.is_empty());
}
#[test]
fn test_driver_args_parse_flto_jobs() {
let args = X86DriverArgs::parse(&["-flto-jobs=8".to_string(), "test.c".to_string()]);
assert_eq!(args.flto_jobs, Some(8));
}
#[test]
fn test_driver_args_parse_flto_jobs_separate() {
let args = X86DriverArgs::parse(&[
"-flto-jobs".to_string(),
"4".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.flto_jobs, Some(4));
}
#[test]
fn test_driver_args_parse_fprofile_instr_generate_eq() {
let args = X86DriverArgs::parse(&[
"-fprofile-instr-generate=/tmp/prof".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.fprofile_instr_generate, Some("/tmp/prof".to_string()));
}
#[test]
fn test_driver_args_parse_fprofile_instr_generate_bare() {
let args =
X86DriverArgs::parse(&["-fprofile-instr-generate".to_string(), "test.c".to_string()]);
assert_eq!(args.fprofile_instr_generate, Some(String::new()));
}
#[test]
fn test_driver_args_parse_fprofile_instr_use_eq() {
let args = X86DriverArgs::parse(&[
"-fprofile-instr-use=prof.profdata".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.fprofile_instr_use, Some("prof.profdata".to_string()));
}
#[test]
fn test_driver_args_parse_fcs_profile_generate_eq() {
let args = X86DriverArgs::parse(&[
"-fcs-profile-generate=/tmp/cs".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.fcs_profile_generate, Some("/tmp/cs".to_string()));
}
#[test]
fn test_driver_args_parse_sanitize_recover() {
let args = X86DriverArgs::parse(&[
"-fsanitize-recover=undefined".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.fsanitize_recover, Some("undefined".to_string()));
}
#[test]
fn test_driver_args_parse_sanitize_trap() {
let args = X86DriverArgs::parse(&[
"-fsanitize-trap=undefined".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.fsanitize_trap, Some("undefined".to_string()));
}
#[test]
fn test_driver_args_parse_stdlib() {
let args = X86DriverArgs::parse(&["-stdlib=libc++".to_string(), "test.c".to_string()]);
assert_eq!(args.stdlib, Some("libc++".to_string()));
}
#[test]
fn test_driver_args_parse_stdlib_separate() {
let args = X86DriverArgs::parse(&[
"-stdlib".to_string(),
"libstdc++".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.stdlib, Some("libstdc++".to_string()));
}
#[test]
fn test_driver_args_parse_wl_flags() {
let args = X86DriverArgs::parse(&[
"-Wl,-rpath,/usr/local/lib".to_string(),
"test.c".to_string(),
]);
assert!(args.wl_flags.contains(&"-rpath".to_string()));
assert!(args.wl_flags.contains(&"/usr/local/lib".to_string()));
}
#[test]
fn test_driver_args_parse_wa_flags() {
let args = X86DriverArgs::parse(&["-Wa,-mavx2".to_string(), "test.c".to_string()]);
assert!(args.wa_flags.contains(&"-mavx2".to_string()));
}
#[test]
fn test_driver_args_parse_xlinker() {
let args = X86DriverArgs::parse(&[
"-Xlinker".to_string(),
"--no-undefined".to_string(),
"test.c".to_string(),
]);
assert!(args.xlinker_flags.contains(&"--no-undefined".to_string()));
}
#[test]
fn test_driver_args_parse_xassembler() {
let args = X86DriverArgs::parse(&[
"-Xassembler".to_string(),
"--divide".to_string(),
"test.c".to_string(),
]);
assert!(args.xassembler_flags.contains(&"--divide".to_string()));
}
#[test]
fn test_driver_args_parse_rdynamic() {
let args = X86DriverArgs::parse(&["-rdynamic".to_string(), "test.c".to_string()]);
assert!(args.rdynamic);
}
#[test]
fn test_driver_args_parse_nodefaultlibs() {
let args = X86DriverArgs::parse(&["-nodefaultlibs".to_string(), "test.c".to_string()]);
assert!(args.nodefaultlibs);
}
#[test]
fn test_driver_args_parse_nostdlib() {
let args = X86DriverArgs::parse(&["-nostdlib".to_string(), "test.c".to_string()]);
assert!(args.nostdlib);
}
#[test]
fn test_driver_args_parse_nostartfiles() {
let args = X86DriverArgs::parse(&["-nostartfiles".to_string(), "test.c".to_string()]);
assert!(args.nostartfiles);
}
#[test]
fn test_driver_args_parse_nostdinc() {
let args = X86DriverArgs::parse(&["-nostdinc".to_string(), "test.c".to_string()]);
assert!(args.nostdinc);
}
#[test]
fn test_driver_args_parse_nostdincxx() {
let args = X86DriverArgs::parse(&["-nostdinc++".to_string(), "test.c".to_string()]);
assert!(args.nostdincxx);
}
#[test]
fn test_driver_args_parse_mfpmath() {
let args = X86DriverArgs::parse(&[
"-mfpmath".to_string(),
"sse".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.mfpmath, Some("sse".to_string()));
}
#[test]
fn test_driver_args_parse_mregparm() {
let args = X86DriverArgs::parse(&[
"-mregparm".to_string(),
"3".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.mregparm, Some(3));
}
#[test]
fn test_driver_args_parse_mstack_alignment() {
let args = X86DriverArgs::parse(&[
"-mstack-alignment".to_string(),
"16".to_string(),
"test.c".to_string(),
]);
assert_eq!(args.mstack_alignment, Some(16));
}
#[test]
fn test_driver_args_parse_mno_features() {
let args = X86DriverArgs::parse(&[
"-mno-sse".to_string(),
"-mno-avx".to_string(),
"test.c".to_string(),
]);
assert!(args.mno_features.contains(&"sse".to_string()));
assert!(args.mno_features.contains(&"avx".to_string()));
}
#[test]
fn test_driver_args_parse_m_features() {
let args = X86DriverArgs::parse(&[
"-mavx2".to_string(),
"-mbmi2".to_string(),
"test.c".to_string(),
]);
assert!(args.m_features.contains(&"avx2".to_string()));
assert!(args.m_features.contains(&"bmi2".to_string()));
}
#[test]
fn test_driver_args_parse_verbose() {
let args = X86DriverArgs::parse(&["-v".to_string(), "test.c".to_string()]);
assert!(args.v);
}
#[test]
fn test_driver_args_parse_dry_run() {
let args = X86DriverArgs::parse(&["-###".to_string(), "test.c".to_string()]);
assert!(args.dry_run);
}
#[test]
fn test_driver_args_parse_shared() {
let args = X86DriverArgs::parse(&["-shared".to_string(), "test.c".to_string()]);
assert!(args.shared);
}
#[test]
fn test_driver_args_parse_static() {
let args = X86DriverArgs::parse(&["-static".to_string(), "test.c".to_string()]);
assert!(args.static_linking);
}
#[test]
fn test_driver_args_parse_mx32_with_target() {
let args = X86DriverArgs::parse(&["-mx32".to_string(), "test.c".to_string()]);
assert!(args.mx32);
assert_eq!(args.target, Some("x86_64-unknown-linux-gnux32".to_string()));
}
#[test]
fn test_driver_args_to_clang_flags_full() {
let mut args = X86DriverArgs::default();
args.m32 = true;
args.march = Some("haswell".to_string());
args.mtune = Some("generic".to_string());
args.fpic = true;
args.fstackrealign = true;
args.mregparm = Some(3);
let flags = args.to_clang_flags();
assert!(flags.contains(&"-m32".to_string()));
assert!(flags.contains(&"-march=haswell".to_string()));
assert!(flags.contains(&"-mtune=generic".to_string()));
assert!(flags.contains(&"-fpic".to_string()));
assert!(flags.contains(&"-mregparm=3".to_string()));
}
#[test]
fn test_multilib_variant_gcc_suffix() {
assert_eq!(X86MultilibVariant::Default64.gcc_multilib_suffix(), "64");
assert_eq!(X86MultilibVariant::M32.gcc_multilib_suffix(), "32");
assert_eq!(X86MultilibVariant::MX32.gcc_multilib_suffix(), "x32");
}
#[test]
fn test_multilib_include_paths() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let m = X86Multilib::new(&triple);
let paths = m.include_paths();
assert!(!paths.is_empty());
}
#[test]
fn test_multilib_select_64bit() {
let triple = X86TargetTriple::i386_linux_gnu();
let mut m = X86Multilib::new(&triple);
m.select_64bit();
assert_eq!(m.active_variant, X86MultilibVariant::Default64);
}
#[test]
fn test_sanitizer_setup_thread() {
let mut setup = X86SanitizerSetup::new();
setup.thread = true;
assert!(setup.has_any());
let libs = setup.linker_libraries();
assert!(libs.contains(&"tsan".to_string()));
}
#[test]
fn test_sanitizer_setup_memory() {
let mut setup = X86SanitizerSetup::new();
setup.memory = true;
assert!(setup.has_any());
let libs = setup.linker_libraries();
assert!(libs.contains(&"msan".to_string()));
}
#[test]
fn test_sanitizer_setup_leak() {
let mut setup = X86SanitizerSetup::new();
setup.leak = true;
assert!(setup.has_any());
let libs = setup.linker_libraries();
assert!(libs.contains(&"lsan".to_string()));
}
#[test]
fn test_sanitizer_setup_dataflow() {
let mut setup = X86SanitizerSetup::new();
setup.data_flow = true;
assert!(setup.has_any());
let libs = setup.linker_libraries();
assert!(libs.contains(&"dfsan".to_string()));
}
#[test]
fn test_sanitizer_setup_hwaddress() {
let mut setup = X86SanitizerSetup::new();
setup.hwaddress = true;
assert!(setup.has_any());
let libs = setup.linker_libraries();
assert!(libs.contains(&"hwasan".to_string()));
}
#[test]
fn test_sanitizer_setup_safe_stack() {
let mut setup = X86SanitizerSetup::new();
setup.safe_stack = true;
assert!(setup.has_any());
}
#[test]
fn test_sanitizer_setup_shadow_call_stack() {
let mut setup = X86SanitizerSetup::new();
setup.shadow_call_stack = true;
assert!(setup.has_any());
}
#[test]
fn test_sanitizer_setup_minimal_runtime() {
let mut setup = X86SanitizerSetup::new();
setup.minimal_runtime = true;
let flags = setup.compiler_flags();
assert!(flags.is_empty());
}
#[test]
fn test_sanitizer_setup_blacklist() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
setup.blacklist_file = Some(PathBuf::from("/tmp/asan_blacklist.txt"));
let flags = setup.compiler_flags();
assert!(flags.iter().any(|f| f.contains("blacklist")));
}
#[test]
fn test_sanitizer_setup_asan_options_detect_leaks() {
let mut setup = X86SanitizerSetup::new();
setup.address = true;
let opts = setup.asan_options();
assert!(opts.iter().any(|o| o.contains("detect_leaks")));
}
#[test]
fn test_coverage_setup_function_sections() {
let mut args = X86DriverArgs::default();
args.ffunction_sections = true;
let setup = X86CoverageSetup::from_args(&args);
assert!(setup.function_sections);
let flags = setup.compiler_flags();
assert!(flags.iter().any(|f| f.contains("ffunction-sections")));
assert!(flags.iter().any(|f| f.contains("fdata-sections")));
}
#[test]
fn test_coverage_setup_cs_profile_generate() {
let mut args = X86DriverArgs::default();
args.fcs_profile_generate = Some("/tmp/cs".to_string());
let setup = X86CoverageSetup::from_args(&args);
assert!(setup.cs_profile_generate.is_some());
}
#[test]
fn test_coverage_setup_debug_info_for_profiling() {
let mut setup = X86CoverageSetup::new();
setup.coverage_mapping = true;
setup.debug_info_for_profiling = true;
let flags = setup.compiler_flags();
assert!(flags.iter().any(|f| f == "-fdebug-info-for-profiling"));
}
#[test]
fn test_coverage_setup_has_any_false() {
let setup = X86CoverageSetup::new();
assert!(!setup.has_any());
}
#[test]
fn test_coverage_setup_has_any_true_with_coverage() {
let mut setup = X86CoverageSetup::new();
setup.coverage_mapping = true;
assert!(setup.has_any());
}
#[test]
fn test_coverage_setup_has_any_true_with_profile() {
let mut setup = X86CoverageSetup::new();
setup.profile_instr_use = Some(PathBuf::from("test.profdata"));
assert!(setup.has_any());
}
#[test]
fn test_lto_config_detect_plugin() {
let mut config = X86LTOConfig::new();
config.detect_plugin();
}
#[test]
fn test_lto_config_compiler_flags_disabled() {
let config = X86LTOConfig::new();
assert!(config.compiler_flags().is_empty());
}
#[test]
fn test_lto_config_compiler_flags_full() {
let mut config = X86LTOConfig::new();
config.enabled = true;
let flags = config.compiler_flags();
assert!(flags.contains(&"-flto".to_string()));
}
#[test]
fn test_lto_config_compiler_flags_thin() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.thin = true;
let flags = config.compiler_flags();
assert!(flags.contains(&"-flto=thin".to_string()));
}
#[test]
fn test_lto_config_compiler_flags_with_opt_level() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.lto_opt_level = Some(2);
let flags = config.compiler_flags();
assert!(flags.iter().any(|f| f.contains("-flto-O2")));
}
#[test]
fn test_lto_config_linker_flags_new_pm() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.new_pass_manager = true;
let flags = config.linker_flags();
assert!(flags.iter().any(|f| f.contains("new-pass-manager")));
}
#[test]
fn test_lto_config_linker_flags_debug_info() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.lto_debug_info = true;
let flags = config.linker_flags();
assert!(flags.iter().any(|f| f.contains("debug-info")));
}
#[test]
fn test_lto_config_linker_flags_cache_dir() {
let mut config = X86LTOConfig::new();
config.enabled = true;
config.cache_dir = Some(PathBuf::from("/tmp/lto-cache"));
let flags = config.linker_flags();
assert!(flags.iter().any(|f| f.contains("cache-dir")));
}
#[test]
fn test_toolchain_c_include_dirs() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let dirs = tc.c_include_dirs();
}
#[test]
fn test_toolchain_cxx_include_dirs() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let dirs = tc.cxx_include_dirs();
}
#[test]
fn test_toolchain_all_include_dirs() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let dirs = tc.all_include_dirs();
}
#[test]
fn test_toolchain_active_lib_dirs_64() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let dirs = tc.active_lib_dirs(X86MultilibVariant::Default64);
}
#[test]
fn test_toolchain_active_lib_dirs_32() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let dirs = tc.active_lib_dirs(X86MultilibVariant::M32);
}
#[test]
fn test_toolchain_crt_object_nonexistent() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let obj = tc.crt_object("nonexistent_crt.o");
assert!(obj.is_none());
}
#[test]
fn test_toolchain_resource_dir() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
assert!(tc.resource_dir().is_some());
}
#[test]
fn test_header_search_all_include_paths() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let hs = X86HeaderSearch::new(&triple, &tc);
let paths = hs.all_include_paths();
}
#[test]
fn test_header_search_add_framework_path() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let mut hs = X86HeaderSearch::new(&triple, &tc);
hs.add_framework_path(Path::new("/System/Library/Frameworks"));
assert_eq!(hs.framework_includes.len(), 1);
}
#[test]
fn test_stdlib_setup_new_bionic() {
let triple = X86TargetTriple::x86_64_linux_android();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.c_lib, X86CLibKind::Bionic);
}
#[test]
fn test_stdlib_setup_new_darwin() {
let triple = X86TargetTriple::x86_64_apple_darwin();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.c_lib, X86CLibKind::LibSystem);
}
#[test]
fn test_stdlib_setup_cxx_lib_android() {
let triple = X86TargetTriple::x86_64_linux_android();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.cxx_lib, X86CXXLibKind::AndroidCXX);
}
#[test]
fn test_stdlib_setup_cxx_lib_msvc() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
assert_eq!(setup.cxx_lib, X86CXXLibKind::Msvcprt);
}
#[test]
fn test_stdlib_setup_nostdlib_disables_libraries() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let mut setup = X86StdLibSetup::new(&triple, &tc);
setup.nostdlib = true;
setup.setup_default_libraries();
}
#[test]
fn test_stdlib_setup_crt_files() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let tc = X86ToolchainPath::detect(&triple);
let setup = X86StdLibSetup::new(&triple, &tc);
let crt = setup.crt_startup_files();
}
#[test]
fn test_clib_kind_all_variants() {
for kind in &[
X86CLibKind::Glibc,
X86CLibKind::Musl,
X86CLibKind::Msvcrt,
X86CLibKind::Bionic,
X86CLibKind::LibSystem,
X86CLibKind::Newlib,
X86CLibKind::DietLibc,
X86CLibKind::UClibc,
X86CLibKind::Unknown,
] {
let crt = kind.default_crt_object();
assert!(!crt.is_empty());
let lib = kind.default_c_lib();
assert!(!lib.is_empty());
}
}
#[test]
fn test_linker_invocation_set_entry() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.set_entry("my_custom_start");
let cmd = inv.to_command();
assert!(cmd.contains(&"my_custom_start".to_string()));
}
#[test]
fn test_linker_invocation_set_dynamic_linker() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.set_dynamic_linker("/lib/custom-ld.so");
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("custom-ld.so")));
}
#[test]
fn test_linker_invocation_allow_unresolved() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.allow_unresolved = true;
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("shlib-undefined")));
}
#[test]
fn test_linker_invocation_export_dynamic() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.export_dynamic = true;
let cmd = inv.to_command();
assert!(cmd.contains(&"--export-dynamic".to_string()));
}
#[test]
fn test_linker_invocation_strip() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.strip = true;
let cmd = inv.to_command();
assert!(cmd.contains(&"-s".to_string()));
}
#[test]
fn test_linker_invocation_soname() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
inv.shared = true;
inv.soname = Some("libtest.so.1".to_string());
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("libtest.so.1")));
}
#[test]
fn test_linker_invocation_emulation_32() {
let triple = X86TargetTriple::i386_linux_gnu();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("elf_i386")));
}
#[test]
fn test_linker_invocation_emulation_x32() {
let triple = X86TargetTriple::x86_64_linux_gnux32();
let inv = X86LinkerInvocation::new(X86LinkerFlavor::GnuLd, &triple);
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("elf32_x86_64")));
}
#[test]
fn test_linker_invocation_msvc_subsystem() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::MSVCLink, &triple);
inv.subsystem = Some("WINDOWS".to_string());
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("WINDOWS")));
}
#[test]
fn test_linker_invocation_msvc_win_entry() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let mut inv = X86LinkerInvocation::new(X86LinkerFlavor::MSVCLink, &triple);
inv.win_entry = Some("mainCRTStartup".to_string());
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("mainCRTStartup")));
}
#[test]
fn test_assembler_invocation_avx_flag() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.use_avx = true;
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("avx")));
}
#[test]
fn test_assembler_invocation_debug_flag() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.gen_debug = true;
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a == "-g" || a.contains("gdwarf")));
}
#[test]
fn test_assembler_invocation_pic_flag() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.pic = true;
let cmd = inv.to_command();
assert!(cmd.iter().any(|a| a.contains("position-independent")));
}
#[test]
fn test_assembler_invocation_32bit() {
let triple = X86TargetTriple::i386_linux_gnu();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.integrated_as = false;
let cmd = inv.to_command();
assert!(cmd.contains(&"--32".to_string()));
}
#[test]
fn test_assembler_invocation_x32() {
let triple = X86TargetTriple::x86_64_linux_gnux32();
let mut inv = X86AssemblerInvocation::new(&triple);
inv.integrated_as = false;
let cmd = inv.to_command();
assert!(cmd.contains(&"--x32".to_string()));
}
#[test]
fn test_smoke_create_driver_and_inspect_state() {
let driver = X86ClangDriver::x86_64_linux();
assert!(driver.triple.is_64bit);
assert!(driver.multilib.multilib_enabled);
assert!(!driver.sanitizers.has_any());
assert!(!driver.coverage.has_any());
assert!(!driver.lto_config.is_enabled());
assert_eq!(driver.linker_flavor, X86LinkerFlavor::GnuLd);
assert_eq!(driver.opt_level, X86DriverOptLevel::O0);
}
#[test]
fn test_smoke_windows_driver() {
let driver = X86ClangDriver::x86_64_windows();
assert_eq!(driver.triple.os, X86TripleOS::Win32);
assert_eq!(driver.linker_flavor, X86LinkerFlavor::MSVCLink);
assert_eq!(driver.stdlib.c_lib, X86CLibKind::Msvcrt);
}
#[test]
fn test_smoke_i386_driver() {
let driver = X86ClangDriver::i386_linux();
assert!(!driver.triple.is_64bit);
assert_eq!(driver.multilib.active_variant, X86MultilibVariant::M32);
assert_eq!(driver.stdlib.c_lib, X86CLibKind::Glibc);
}
#[test]
fn test_smoke_parse_all_common_flags() {
let args = [
"-target",
"x86_64-unknown-linux-gnu",
"-march=skylake",
"-mcpu=native",
"-mtune=generic",
"-O2",
"-g",
"-fPIC",
"-Wall",
"-I/usr/include",
"-isystem/usr/local/include",
"-L/usr/lib64",
"-lpthread",
"-lm",
"-DDEBUG",
"-DNAME=value",
"-UNDEBUG",
"-fsanitize=address",
"-flto=thin",
"-fprofile-instr-generate",
"-fuse-ld=lld",
"-shared",
"-o",
"output.so",
"input.c",
]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
let parsed = X86DriverArgs::parse(&args);
assert_eq!(parsed.target, Some("x86_64-unknown-linux-gnu".to_string()));
assert_eq!(parsed.march, Some("skylake".to_string()));
assert_eq!(parsed.mcpu, Some("native".to_string()));
assert_eq!(parsed.opt_level, Some(X86DriverOptLevel::O2));
assert!(parsed.fPIC);
assert_eq!(parsed.fsanitize, Some("address".to_string()));
assert_eq!(parsed.flto, Some("thin".to_string()));
assert_eq!(parsed.fuse_ld, Some("lld".to_string()));
assert!(parsed.shared);
assert_eq!(parsed.output_file, Some("output.so".to_string()));
assert!(parsed.input_files.contains(&"input.c".to_string()));
}
#[test]
fn test_smoke_driver_complete_parse_and_flags_roundtrip() {
let mut driver = X86ClangDriver::x86_64_linux();
let args = vec![
"-O2".to_string(),
"-march=skylake".to_string(),
"-flto=thin".to_string(),
"-fsanitize=address".to_string(),
"-fprofile-instr-generate".to_string(),
"test.c".to_string(),
];
driver.parse_args(&args);
assert_eq!(driver.opt_level, X86DriverOptLevel::O2);
assert!(driver.sanitizers.address);
assert!(driver.lto_config.is_enabled());
assert!(driver.coverage.profile_instr_generate);
let inv = driver.build_linker_invocation();
assert!(!inv.to_command().is_empty());
let cmd = driver.build_compiler_command(Path::new("test.c"), Path::new("test.o"));
assert!(!cmd.is_empty());
assert!(cmd.contains(&"-target=x86_64-unknown-linux-gnu".to_string()));
}
#[test]
fn test_smoke_x86_arch_from_str_all() {
let test_cases = [
("i386", X86TripleArch::I386),
("i486", X86TripleArch::I486),
("i586", X86TripleArch::I586),
("i686", X86TripleArch::I686),
("x86_64", X86TripleArch::X86_64),
("amd64", X86TripleArch::X86_64),
("x86-64", X86TripleArch::X86_64),
("x64", X86TripleArch::X86_64),
("x32", X86TripleArch::X32),
];
for (input, expected) in &test_cases {
assert_eq!(
X86TripleArch::from_str(input),
*expected,
"Failed for input: {}",
input
);
}
}
#[test]
fn test_smoke_x86_os_from_str_all() {
let test_cases = [
("linux", X86TripleOS::Linux),
("darwin", X86TripleOS::Darwin),
("macosx", X86TripleOS::MacOSX),
("win32", X86TripleOS::Win32),
("windows", X86TripleOS::Win32),
("mingw32", X86TripleOS::Win32),
("freebsd", X86TripleOS::FreeBSD),
("netbsd", X86TripleOS::NetBSD),
("openbsd", X86TripleOS::OpenBSD),
("android", X86TripleOS::Android),
("emscripten", X86TripleOS::Emscripten),
("fuchsia", X86TripleOS::Fuchsia),
("wasi", X86TripleOS::WASI),
("hurd", X86TripleOS::Hurd),
];
for (input, expected) in &test_cases {
assert_eq!(
X86TripleOS::from_str(input),
*expected,
"Failed for input: {}",
input
);
}
}
#[test]
fn test_smoke_linker_flavor_default_executable_all() {
let flavors = [
(X86LinkerFlavor::GnuLd, "ld"),
(X86LinkerFlavor::Lld, "ld.lld"),
(X86LinkerFlavor::DarwinLd, "ld"),
(X86LinkerFlavor::MSVCLink, "link.exe"),
(X86LinkerFlavor::LldLink, "lld-link"),
];
for (flavor, expected) in &flavors {
assert_eq!(
flavor.default_executable(),
*expected,
"Failed for {:?}",
flavor
);
}
}
#[test]
fn test_driver_job_new() {
let job = X86DriverJob::new(
X86CompilationPhase::Compile,
Path::new("test.c"),
Path::new("test.o"),
);
assert_eq!(job.phase, X86CompilationPhase::Compile);
assert_eq!(job.input, PathBuf::from("test.c"));
}
#[test]
fn test_driver_job_add_arg() {
let mut job = X86DriverJob::new(
X86CompilationPhase::Compile,
Path::new("test.c"),
Path::new("test.o"),
);
job.add_arg("-O2");
assert!(job.arguments.contains(&"-O2".to_string()));
}
#[test]
fn test_driver_job_add_args() {
let mut job = X86DriverJob::new(
X86CompilationPhase::Compile,
Path::new("test.c"),
Path::new("test.o"),
);
job.add_args(&["-c".to_string(), "-O2".to_string()]);
assert_eq!(job.arguments.len(), 2);
}
#[test]
fn test_driver_job_to_shell_command() {
let mut job = X86DriverJob::new(
X86CompilationPhase::Compile,
Path::new("test.c"),
Path::new("test.o"),
);
job.add_arg("clang");
job.add_arg("test.c");
let cmd = job.to_shell_command();
assert!(cmd.contains("clang"));
assert!(cmd.contains("test.c"));
}
#[test]
fn test_driver_job_list_new() {
let list = X86DriverJobList::new();
assert!(list.is_empty());
assert_eq!(list.len(), 0);
}
#[test]
fn test_driver_job_list_add_job() {
let mut list = X86DriverJobList::new();
let job = X86DriverJob::new(
X86CompilationPhase::Compile,
Path::new("test.c"),
Path::new("test.o"),
);
list.add_job(job);
assert_eq!(list.len(), 1);
}
#[test]
fn test_driver_job_list_to_shell_script() {
let mut list = X86DriverJobList::new();
let job = X86DriverJob::new(
X86CompilationPhase::Compile,
Path::new("test.c"),
Path::new("test.o"),
);
list.add_job(job);
let script = list.to_shell_script();
assert!(script.contains("#!/bin/sh"));
}
#[test]
fn test_predefined_macros_x86_64_linux() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let macros = X86PredefinedMacros::for_triple(&triple);
let names: Vec<&str> = macros.macros.iter().map(|(n, _)| n.as_str()).collect();
assert!(names.contains(&"__x86_64__"));
assert!(names.contains(&"__linux__"));
assert!(names.contains(&"__gnu_linux__"));
}
#[test]
fn test_predefined_macros_i386_linux() {
let triple = X86TargetTriple::i386_linux_gnu();
let macros = X86PredefinedMacros::for_triple(&triple);
let names: Vec<&str> = macros.macros.iter().map(|(n, _)| n.as_str()).collect();
assert!(names.contains(&"__i386__"));
}
#[test]
fn test_predefined_macros_windows() {
let triple = X86TargetTriple::x86_64_windows_msvc();
let macros = X86PredefinedMacros::for_triple(&triple);
let names: Vec<&str> = macros.macros.iter().map(|(n, _)| n.as_str()).collect();
assert!(names.contains(&"_WIN32"));
assert!(names.contains(&"_WIN64"));
}
#[test]
fn test_predefined_macros_darwin() {
let triple = X86TargetTriple::x86_64_apple_darwin();
let macros = X86PredefinedMacros::for_triple(&triple);
let names: Vec<&str> = macros.macros.iter().map(|(n, _)| n.as_str()).collect();
assert!(names.contains(&"__APPLE__"));
assert!(names.contains(&"__MACH__"));
}
#[test]
fn test_predefined_macros_to_clang_flags() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let macros = X86PredefinedMacros::for_triple(&triple);
let flags = macros.to_clang_flags();
assert!(flags
.iter()
.any(|f| f.contains("__x86_64__") && f.starts_with("-D")));
}
#[test]
fn test_diagnostic_report_driver() {
let driver = X86ClangDriver::x86_64_linux();
let report = driver.report();
assert!(report.contains("Target:"));
let short = driver.report_short();
assert!(short.contains("Driver["));
}
#[test]
fn test_diagnostic_report_triple() {
let triple = X86TargetTriple::x86_64_linux_gnu();
let report = triple.report();
assert!(report.contains("x86_64"));
let short = triple.report_short();
assert_eq!(short, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_diagnostic_report_sanitizer_empty() {
let san = X86SanitizerSetup::new();
let report = san.report();
assert!(report.contains("none"));
let short = san.report_short();
assert_eq!(short, "San[off]");
}
#[test]
fn test_diagnostic_report_sanitizer_address() {
let mut san = X86SanitizerSetup::new();
san.address = true;
let report = san.report();
assert!(report.contains("addr"));
let short = san.report_short();
assert_eq!(short, "San[on]");
}
#[test]
fn test_diagnostic_report_lto_disabled() {
let lto = X86LTOConfig::default();
let report = lto.report();
assert_eq!(report, "LTO[off]");
assert_eq!(lto.report_short(), "LTO[off]");
}
#[test]
fn test_diagnostic_report_lto_thin() {
let mut lto = X86LTOConfig::new();
lto.enabled = true;
lto.thin = true;
assert_eq!(lto.report(), "LTO[thin]");
assert_eq!(lto.report_short(), "LTO[on]");
}
#[test]
fn test_diagnostic_report_lto_full() {
let mut lto = X86LTOConfig::new();
lto.enabled = true;
assert_eq!(lto.report(), "LTO[full]");
assert_eq!(lto.report_short(), "LTO[on]");
}
}