use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use crate::clang::ast::{
BinaryOp, Decl, Expr, FunctionDecl, QualType, Stmt, TranslationUnit, TypeNode as ClangTypeKind,
};
use crate::clang::codegen::ClangCodeGen;
use crate::clang::diagnostics::{
ClangSourceLocation, ClangSourceManager, DiagID, DiagSeverity, DiagnosticBuilder,
DiagnosticEngine, DiagnosticOptions, SourceRange,
};
use crate::clang::driver::{compile_c_file, compile_c_string, ClangDriver};
use crate::clang::lexer::Lexer;
use crate::clang::parser::Parser;
use crate::clang::preprocessor::Preprocessor;
use crate::clang::sema::Sema;
use crate::clang::token::{Token, TokenKind};
use crate::clang::{CLangStandard, ClangOptions};
use crate::basic_block;
use crate::constants;
use crate::context::LLVMContext;
use crate::function::{self, Function};
use crate::instruction::{self, ICmpPred, Opcode};
use crate::ir_builder::IRBuilder;
use crate::module::Module;
use crate::types::{Type, TypeId, TypeKind};
use crate::value::{valref, Value, ValueRef};
use crate::x86::x86_calling_convention::{
X86ArgClass, X86ArgInfo, X86CallFrame, X86CallingConvention,
};
use crate::x86::x86_frame_lowering::{CallConv, X86FrameInfo, X86FrameLowering};
use crate::x86::x86_instr_info::{
OperandType, X86InstrDesc, X86InstrInfo, X86MemOperand, X86Opcode, X86Operand, X86SchedInfo,
};
use crate::x86::x86_isel::X86InstructionSelector;
use crate::x86::x86_mc_encoder::X86MCEncoder;
use crate::x86::x86_register_info::{
RegClass, X86Reg, X86RegisterInfo, RBP, RSP, X86_64_REG_COUNT,
};
use crate::x86::x86_subtarget::X86Subtarget;
use crate::x86::x86_target_machine::{CodeModel, OptimizationLevel, RelocModel, X86TargetMachine};
use crate::x86::{
X86_ENDIANNESS, X86_MAX_ALIGNMENT, X86_PAGE_SIZE, X86_RED_ZONE_SIZE_64, X86_STACK_ALIGNMENT_32,
X86_STACK_ALIGNMENT_64,
};
use crate::codegen::{
AsmPrinter, InstructionSelector, MachineBasicBlock, MachineFunction, MachineInstr,
MachineOperand, RegisterAllocator,
};
use crate::data_layout::DataLayout;
use crate::diagnostics;
use crate::target_info::TargetFeature;
use crate::target_machine::{self, CodeGenOptLevel, FloatABI, TargetMachine, TargetOptions};
use crate::triple::{Arch, Triple};
use crate::verifier;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PipelineStage {
Preprocess,
Lex,
Parse,
Sema,
IRGen,
Optimize,
ISel,
RegAlloc,
FrameLower,
Encode,
Emit,
}
impl PipelineStage {
pub const fn name(self) -> &'static str {
match self {
Self::Preprocess => "Preprocess",
Self::Lex => "Lex",
Self::Parse => "Parse",
Self::Sema => "Sema",
Self::IRGen => "IRGen",
Self::Optimize => "Optimize",
Self::ISel => "ISel",
Self::RegAlloc => "RegAlloc",
Self::FrameLower => "FrameLower",
Self::Encode => "Encode",
Self::Emit => "Emit",
}
}
pub const fn next(self) -> Option<Self> {
match self {
Self::Preprocess => Some(Self::Lex),
Self::Lex => Some(Self::Parse),
Self::Parse => Some(Self::Sema),
Self::Sema => Some(Self::IRGen),
Self::IRGen => Some(Self::Optimize),
Self::Optimize => Some(Self::ISel),
Self::ISel => Some(Self::RegAlloc),
Self::RegAlloc => Some(Self::FrameLower),
Self::FrameLower => Some(Self::Encode),
Self::Encode => Some(Self::Emit),
Self::Emit => None,
}
}
pub const fn prev(self) -> Option<Self> {
match self {
Self::Preprocess => None,
Self::Lex => Some(Self::Preprocess),
Self::Parse => Some(Self::Lex),
Self::Sema => Some(Self::Parse),
Self::IRGen => Some(Self::Sema),
Self::Optimize => Some(Self::IRGen),
Self::ISel => Some(Self::Optimize),
Self::RegAlloc => Some(Self::ISel),
Self::FrameLower => Some(Self::RegAlloc),
Self::Encode => Some(Self::FrameLower),
Self::Emit => Some(Self::Encode),
}
}
pub const fn all() -> [Self; 11] {
[
Self::Preprocess,
Self::Lex,
Self::Parse,
Self::Sema,
Self::IRGen,
Self::Optimize,
Self::ISel,
Self::RegAlloc,
Self::FrameLower,
Self::Encode,
Self::Emit,
]
}
pub const fn requires_source(self) -> bool {
matches!(
self,
Self::Preprocess | Self::Lex | Self::Parse | Self::Sema
)
}
pub const fn is_backend(self) -> bool {
matches!(
self,
Self::Optimize
| Self::ISel
| Self::RegAlloc
| Self::FrameLower
| Self::Encode
| Self::Emit
)
}
pub const fn is_frontend(self) -> bool {
matches!(
self,
Self::Preprocess | Self::Lex | Self::Parse | Self::Sema | Self::IRGen
)
}
}
impl fmt::Display for PipelineStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone)]
pub struct StageResult {
pub stage: PipelineStage,
pub success: bool,
pub duration: Duration,
pub error_count: usize,
pub warning_count: usize,
pub note_count: usize,
pub summary: String,
pub output_data: Option<Vec<u8>>,
pub output_size: usize,
}
impl StageResult {
pub fn success(stage: PipelineStage, duration: Duration) -> Self {
Self {
stage,
success: true,
duration,
error_count: 0,
warning_count: 0,
note_count: 0,
summary: String::new(),
output_data: None,
output_size: 0,
}
}
pub fn failure(
stage: PipelineStage,
duration: Duration,
error_count: usize,
warnings: usize,
summary: impl Into<String>,
) -> Self {
Self {
stage,
success: false,
duration,
error_count,
warning_count: warnings,
note_count: 0,
summary: summary.into(),
output_data: None,
output_size: 0,
}
}
pub fn with_output(mut self, data: Vec<u8>, size: usize) -> Self {
self.output_data = Some(data);
self.output_size = size;
self
}
pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
self.summary = summary.into();
self
}
pub fn with_diagnostics(mut self, errors: usize, warnings: usize, notes: usize) -> Self {
self.error_count = errors;
self.warning_count = warnings;
self.note_count = notes;
self
}
pub fn one_line(&self) -> String {
let status = if self.success { "✓" } else { "✗" };
format!(
" {} {:>12} {:>8.2}ms errors={} warnings={} {}",
status,
self.stage.name(),
self.duration.as_secs_f64() * 1000.0,
self.error_count,
self.warning_count,
self.summary,
)
}
}
#[derive(Debug, Clone)]
pub struct PipelineResult {
pub success: bool,
pub completed_stage: Option<PipelineStage>,
pub failed_stage: Option<PipelineStage>,
pub stage_results: Vec<StageResult>,
pub total_duration: Duration,
pub total_errors: usize,
pub total_warnings: usize,
pub text_output: Option<String>,
pub binary_output: Option<Vec<u8>>,
pub output_file: Option<PathBuf>,
pub module: Option<Module>,
pub machine_functions: Vec<MachineFunction>,
pub summary: String,
}
impl PipelineResult {
pub fn new() -> Self {
Self {
success: false,
completed_stage: None,
failed_stage: None,
stage_results: Vec::new(),
total_duration: Duration::ZERO,
total_errors: 0,
total_warnings: 0,
text_output: None,
binary_output: None,
output_file: None,
module: None,
machine_functions: Vec::new(),
summary: String::new(),
}
}
pub fn success_with(
stage_results: Vec<StageResult>,
total_duration: Duration,
text_output: Option<String>,
binary_output: Option<Vec<u8>>,
output_file: Option<PathBuf>,
) -> Self {
let total_errors: usize = stage_results.iter().map(|r| r.error_count).sum();
let total_warnings: usize = stage_results.iter().map(|r| r.warning_count).sum();
let last = stage_results.last().map(|r| r.stage);
let summary = if total_errors > 0 {
format!(
"Pipeline completed with {} error(s) and {} warning(s) in {:.2}ms",
total_errors,
total_warnings,
total_duration.as_secs_f64() * 1000.0
)
} else {
format!(
"Pipeline succeeded in {:.2}ms",
total_duration.as_secs_f64() * 1000.0
)
};
Self {
success: total_errors == 0,
completed_stage: last,
failed_stage: if total_errors > 0 { last } else { None },
stage_results,
total_duration,
total_errors,
total_warnings,
text_output,
binary_output,
output_file,
module: None,
machine_functions: Vec::new(),
summary,
}
}
pub fn failure(
failed_stage: PipelineStage,
stage_results: Vec<StageResult>,
total_duration: Duration,
error_message: impl Into<String>,
) -> Self {
let total_errors: usize = stage_results.iter().map(|r| r.error_count).sum();
let total_warnings: usize = stage_results.iter().map(|r| r.warning_count).sum();
let last = stage_results.last().map(|r| r.stage);
Self {
success: false,
completed_stage: last,
failed_stage: Some(failed_stage),
stage_results,
total_duration,
total_errors,
total_warnings,
text_output: None,
binary_output: None,
output_file: None,
module: None,
machine_functions: Vec::new(),
summary: error_message.into(),
}
}
pub fn with_module(mut self, module: Module) -> Self {
self.module = Some(module);
self
}
pub fn with_machine_functions(mut self, mfs: Vec<MachineFunction>) -> Self {
self.machine_functions = mfs;
self
}
pub fn format_report(&self) -> String {
let mut out = String::new();
out.push_str(&format!(
"╔══════════════════════════════════════════════════════════════╗\n"
));
out.push_str(&format!(
"║ Clang→X86 Pipeline Report ║\n"
));
out.push_str(&format!(
"╠══════════════════════════════════════════════════════════════╣\n"
));
for result in &self.stage_results {
out.push_str(&format!("║{}\n", result.one_line()));
}
out.push_str(&format!(
"╠══════════════════════════════════════════════════════════════╣\n"
));
out.push_str(&format!(
"║ Total: {:>8.2}ms errors={} warnings={} ║\n",
self.total_duration.as_secs_f64() * 1000.0,
self.total_errors,
self.total_warnings,
));
out.push_str(&format!(
"║ Status: {:<50}║\n",
if self.success { "SUCCESS" } else { "FAILURE" }
));
if let Some(ref output) = self.output_file {
out.push_str(&format!(
"║ Output: {:<50}║\n",
output.display().to_string()
));
}
out.push_str(&format!(
"╚══════════════════════════════════════════════════════════════╝\n"
));
out
}
}
impl Default for PipelineResult {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct X86CompileOptions {
pub language_standard: CLangStandard,
pub is_cxx: bool,
pub gnu_extensions: bool,
pub ms_extensions: bool,
pub target_triple: String,
pub target_cpu: String,
pub target_features: String,
pub target_abi: Option<String>,
pub opt_level: X86OptLevel,
pub code_model: CodeModel,
pub reloc_model: RelocModel,
pub pic: bool,
pub pie: bool,
pub stack_protector: u8,
pub tls_model: TlsModel,
pub float_abi: FloatABI,
pub debug_info: bool,
pub debug_info_kind: DebugInfoKind,
pub codeview: bool,
pub wall: bool,
pub wextra: bool,
pub werror: bool,
pub pedantic: bool,
pub wno: Vec<String>,
pub w: Vec<String>,
pub error_limit: usize,
pub includes: Vec<String>,
pub system_includes: Vec<String>,
pub defines: Vec<(String, Option<String>)>,
pub undefines: Vec<String>,
pub nostdinc: bool,
pub nostdincpp: bool,
pub output_file: Option<PathBuf>,
pub output_format: X86OutputFormat,
pub stop_after: Option<PipelineStage>,
pub input_files: Vec<PathBuf>,
pub working_dir: Option<PathBuf>,
pub verbose: bool,
pub print_timing: bool,
pub print_stats: bool,
pub dry_run: bool,
pub asan: bool,
pub ubsan: bool,
pub tsan: bool,
pub msan: bool,
pub coverage: bool,
pub profile_instr_generate: bool,
pub profile_instr_use: Option<PathBuf>,
pub linker_args: Vec<String>,
pub static_linking: bool,
pub shared: bool,
pub cc1_flags: Vec<String>,
pub custom_passes: Vec<String>,
pub enable_cache: bool,
pub cache_dir: Option<PathBuf>,
}
impl X86CompileOptions {
pub fn x86_64_linux() -> Self {
Self {
target_triple: "x86_64-unknown-linux-gnu".into(),
..Default::default()
}
}
pub fn x86_64_linux_release() -> Self {
Self {
target_triple: "x86_64-unknown-linux-gnu".into(),
target_cpu: "haswell".into(),
target_features: "+avx2,+fma,+bmi2".into(),
opt_level: X86OptLevel::O3,
..Default::default()
}
}
pub fn x86_32_linux() -> Self {
Self {
target_triple: "i686-unknown-linux-gnu".into(),
target_cpu: "pentium4".into(),
code_model: CodeModel::Tiny,
..Default::default()
}
}
pub fn x86_64_windows() -> Self {
Self {
target_triple: "x86_64-pc-windows-msvc".into(),
target_abi: Some("ms".into()),
..Default::default()
}
}
pub fn from_clang_options(opts: &ClangOptions) -> Self {
Self {
language_standard: opts.standard,
target_triple: opts.target_triple.clone(),
includes: opts.includes.clone(),
defines: opts.defines.clone(),
output_file: opts.output_file.as_ref().map(PathBuf::from),
wall: opts.wall,
werror: opts.werror,
pedantic: opts.pedantic,
debug_info: opts.debug_info,
opt_level: if opts.optimize {
X86OptLevel::O2
} else {
X86OptLevel::O0
},
verbose: opts.verbose,
..Default::default()
}
}
pub fn to_clang_options(&self) -> ClangOptions {
ClangOptions {
standard: self.language_standard,
optimize: self.opt_level.is_optimizing(),
debug_info: self.debug_info,
warnings: self.wall || self.wextra,
pedantic: self.pedantic,
wall: self.wall,
werror: self.werror,
verbose: self.verbose,
includes: self.includes.clone(),
defines: self.defines.clone(),
output_file: self.output_file.as_ref().map(|p| p.display().to_string()),
target_triple: self.target_triple.clone(),
}
}
pub fn is_64_bit(&self) -> bool {
self.target_triple.contains("x86_64") || self.target_triple.contains("amd64")
}
pub fn is_32_bit(&self) -> bool {
self.target_triple.contains("i386")
|| self.target_triple.contains("i486")
|| self.target_triple.contains("i586")
|| self.target_triple.contains("i686")
}
pub fn data_layout_string(&self) -> &'static str {
if self.is_64_bit() {
crate::x86::x86_target_machine::DEFAULT_X86_64_DATA_LAYOUT
} else {
crate::x86::x86_target_machine::DEFAULT_X86_32_DATA_LAYOUT
}
}
pub fn calling_convention(&self) -> CallConv {
match self.target_abi.as_deref() {
Some("ms") | Some("win64") => CallConv::Win64,
Some("sysv") | Some("gnu") => CallConv::SystemV,
_ => {
if self.target_triple.contains("windows") || self.target_triple.contains("msvc") {
CallConv::Win64
} else {
CallConv::SystemV
}
}
}
}
pub fn stack_alignment(&self) -> u32 {
if self.is_64_bit() {
X86_STACK_ALIGNMENT_64
} else {
X86_STACK_ALIGNMENT_32
}
}
pub fn has_red_zone(&self) -> bool {
self.is_64_bit() && self.calling_convention() == CallConv::SystemV
}
pub fn cache_key(&self, source_hash: &[u8; 32]) -> Vec<u8> {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.language_standard.hash(&mut hasher);
self.is_cxx.hash(&mut hasher);
self.target_triple.hash(&mut hasher);
self.target_cpu.hash(&mut hasher);
self.target_features.hash(&mut hasher);
self.opt_level.hash(&mut hasher);
self.code_model.hash(&mut hasher);
self.reloc_model.hash(&mut hasher);
self.pic.hash(&mut hasher);
self.debug_info.hash(&mut hasher);
self.defines.hash(&mut hasher);
let option_hash = hasher.finish();
let mut key = Vec::with_capacity(32 + 8);
key.extend_from_slice(source_hash);
key.extend_from_slice(&option_hash.to_le_bytes());
key
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum X86OptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl X86OptLevel {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"0" | "O0" => Some(Self::O0),
"1" | "O1" => Some(Self::O1),
"2" | "O2" => Some(Self::O2),
"3" | "O3" => Some(Self::O3),
"s" | "Os" => Some(Self::Os),
"z" | "Oz" => Some(Self::Oz),
_ => None,
}
}
pub fn to_arg(&self) -> &'static str {
match self {
Self::O0 => "-O0",
Self::O1 => "-O1",
Self::O2 => "-O2",
Self::O3 => "-O3",
Self::Os => "-Os",
Self::Oz => "-Oz",
}
}
pub fn is_optimizing(&self) -> bool {
!matches!(self, Self::O0)
}
pub fn to_codegen_opt_level(&self) -> CodeGenOptLevel {
match self {
Self::O0 => CodeGenOptLevel::None,
Self::O1 => CodeGenOptLevel::Less,
Self::O2 => CodeGenOptLevel::Default,
Self::O3 | Self::Os | Self::Oz => CodeGenOptLevel::Aggressive,
}
}
pub fn to_optimization_level(&self) -> OptimizationLevel {
match self {
Self::O0 => OptimizationLevel::O0,
Self::O1 => OptimizationLevel::O1,
Self::O2 => OptimizationLevel::O2,
Self::O3 => OptimizationLevel::O3,
Self::Os => OptimizationLevel::Os,
Self::Oz => OptimizationLevel::Oz,
}
}
}
impl Default for X86OptLevel {
fn default() -> Self {
Self::O2
}
}
impl fmt::Display for X86OptLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::O0 => write!(f, "O0"),
Self::O1 => write!(f, "O1"),
Self::O2 => write!(f, "O2"),
Self::O3 => write!(f, "O3"),
Self::Os => write!(f, "Os"),
Self::Oz => write!(f, "Oz"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86OutputFormat {
PreprocessedSource,
LLVMIR,
LLVMBC,
Assembly,
Object,
SharedLib,
Executable,
}
impl X86OutputFormat {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"E" | "preprocess" => Some(Self::PreprocessedSource),
"ir" | "llvm-ir" => Some(Self::LLVMIR),
"bc" | "bitcode" => Some(Self::LLVMBC),
"S" | "asm" | "assembly" => Some(Self::Assembly),
"c" | "obj" | "object" => Some(Self::Object),
"shared" | "so" => Some(Self::SharedLib),
"exe" | "executable" => Some(Self::Executable),
_ => None,
}
}
pub fn extension(&self) -> &'static str {
match self {
Self::PreprocessedSource => ".i",
Self::LLVMIR => ".ll",
Self::LLVMBC => ".bc",
Self::Assembly => ".s",
Self::Object => ".o",
Self::SharedLib => ".so",
Self::Executable => "",
}
}
pub fn is_binary(&self) -> bool {
matches!(
self,
Self::LLVMBC | Self::Object | Self::SharedLib | Self::Executable
)
}
pub fn is_text(&self) -> bool {
matches!(
self,
Self::PreprocessedSource | Self::LLVMIR | Self::Assembly
)
}
pub fn required_stages(&self) -> Vec<PipelineStage> {
match self {
Self::PreprocessedSource => vec![PipelineStage::Preprocess],
Self::LLVMIR | Self::LLVMBC => vec![
PipelineStage::Preprocess,
PipelineStage::Lex,
PipelineStage::Parse,
PipelineStage::Sema,
PipelineStage::IRGen,
],
Self::Assembly => vec![
PipelineStage::Preprocess,
PipelineStage::Lex,
PipelineStage::Parse,
PipelineStage::Sema,
PipelineStage::IRGen,
PipelineStage::Optimize,
PipelineStage::ISel,
PipelineStage::RegAlloc,
PipelineStage::FrameLower,
PipelineStage::Encode,
],
Self::Object | Self::SharedLib | Self::Executable => PipelineStage::all().to_vec(),
}
}
}
impl Default for X86OutputFormat {
fn default() -> Self {
Self::Object
}
}
impl fmt::Display for X86OutputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PreprocessedSource => write!(f, "preprocessed-source"),
Self::LLVMIR => write!(f, "llvm-ir"),
Self::LLVMBC => write!(f, "llvm-bitcode"),
Self::Assembly => write!(f, "assembly"),
Self::Object => write!(f, "object"),
Self::SharedLib => write!(f, "shared-library"),
Self::Executable => write!(f, "executable"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TlsModel {
GeneralDynamic,
LocalDynamic,
InitialExec,
LocalExec,
}
impl Default for TlsModel {
fn default() -> Self {
Self::GeneralDynamic
}
}
impl TlsModel {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"global-dynamic" | "gd" => Some(Self::GeneralDynamic),
"local-dynamic" | "ld" => Some(Self::LocalDynamic),
"initial-exec" | "ie" => Some(Self::InitialExec),
"local-exec" | "le" => Some(Self::LocalExec),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DebugInfoKind {
None,
LineTablesOnly,
Limited,
Full,
FullWithMacros,
CodeView,
}
impl DebugInfoKind {
pub fn has_debug(&self) -> bool {
!matches!(self, Self::None)
}
pub fn has_types(&self) -> bool {
matches!(self, Self::Full | Self::FullWithMacros)
}
}
impl Default for DebugInfoKind {
fn default() -> Self {
Self::None
}
}
pub struct X86BuiltinHandler {
pub context: LLVMContext,
pub builder: IRBuilder,
pub module: Module,
pub builtins: HashMap<String, X86Builtin>,
pub has_sse: bool,
pub has_sse2: bool,
pub has_sse3: bool,
pub has_ssse3: bool,
pub has_sse41: bool,
pub has_sse42: bool,
pub has_avx: bool,
pub has_avx2: bool,
pub has_avx512f: bool,
pub has_fma: bool,
pub has_bmi: bool,
pub has_bmi2: bool,
pub has_lzcnt: bool,
pub has_popcnt: bool,
pub has_rdrand: bool,
pub has_rdseed: bool,
pub errors: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86Builtin {
pub name: String,
pub description: String,
pub required_feature: X86IsaFeature,
pub result_type: Type,
pub arg_types: Vec<Type>,
pub num_operands: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86IsaFeature {
Base,
MMX,
SSE,
SSE2,
SSE3,
SSSE3,
SSE41,
SSE42,
AVX,
AVX2,
AVX512F,
AVX512CD,
AVX512BW,
AVX512DQ,
AVX512VL,
FMA,
BMI,
BMI2,
LZCNT,
POPCNT,
RDRAND,
RDSEED,
AES,
SHA,
ADX,
SGX,
CET,
MPX,
AMX,
}
impl X86IsaFeature {
pub fn description(&self) -> &'static str {
match self {
Self::Base => "base x86",
Self::MMX => "MMX",
Self::SSE => "SSE",
Self::SSE2 => "SSE2",
Self::SSE3 => "SSE3",
Self::SSSE3 => "SSSE3",
Self::SSE41 => "SSE4.1",
Self::SSE42 => "SSE4.2",
Self::AVX => "AVX",
Self::AVX2 => "AVX2",
Self::AVX512F => "AVX-512 Foundation",
Self::AVX512CD => "AVX-512 Conflict Detection",
Self::AVX512BW => "AVX-512 Byte/Word",
Self::AVX512DQ => "AVX-512 Doubleword/Quadword",
Self::AVX512VL => "AVX-512 Vector Length",
Self::FMA => "FMA",
Self::BMI => "BMI",
Self::BMI2 => "BMI2",
Self::LZCNT => "LZCNT",
Self::POPCNT => "POPCNT",
Self::RDRAND => "RDRAND",
Self::RDSEED => "RDSEED",
Self::AES => "AES",
Self::SHA => "SHA",
Self::ADX => "ADX",
Self::SGX => "SGX",
Self::CET => "CET",
Self::MPX => "MPX",
Self::AMX => "AMX",
}
}
}
impl X86BuiltinHandler {
pub fn new(context: LLVMContext, builder: IRBuilder, module: Module) -> Self {
let mut handler = Self {
context,
builder,
module,
builtins: HashMap::new(),
has_sse: false,
has_sse2: false,
has_sse3: false,
has_ssse3: false,
has_sse41: false,
has_sse42: false,
has_avx: false,
has_avx2: false,
has_avx512f: false,
has_fma: false,
has_bmi: false,
has_bmi2: false,
has_lzcnt: false,
has_popcnt: false,
has_rdrand: false,
has_rdseed: false,
errors: Vec::new(),
};
handler.register_builtins();
handler
}
pub fn configure_features(&mut self, subtarget: &X86Subtarget) {
self.has_sse = subtarget.has_feature("sse");
self.has_sse2 = subtarget.has_feature("sse2");
self.has_sse3 = subtarget.has_feature("sse3");
self.has_ssse3 = subtarget.has_feature("ssse3");
self.has_sse41 = subtarget.has_feature("sse4.1");
self.has_sse42 = subtarget.has_feature("sse4.2");
self.has_avx = subtarget.has_feature("avx");
self.has_avx2 = subtarget.has_feature("avx2");
self.has_avx512f = subtarget.has_feature("avx512f");
self.has_fma = subtarget.has_feature("fma");
self.has_bmi = subtarget.has_feature("bmi");
self.has_bmi2 = subtarget.has_feature("bmi2");
self.has_lzcnt = subtarget.has_feature("lzcnt");
self.has_popcnt = subtarget.has_feature("popcnt");
self.has_rdrand = subtarget.has_feature("rdrand");
self.has_rdseed = subtarget.has_feature("rdseed");
}
fn register_builtins(&mut self) {
self.register(
"__builtin_ia32_loaddqu",
"Load 128-bit unaligned integer vector",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_storedqu",
"Store 128-bit unaligned integer vector",
X86IsaFeature::SSE2,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_loadups",
"Load 128-bit unaligned packed single",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_storeups",
"Store 128-bit unaligned packed single",
X86IsaFeature::SSE,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_movntps",
"Store packed single using non-temporal hint",
X86IsaFeature::SSE,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_paddb",
"Add packed byte integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_paddw",
"Add packed word integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_paddd",
"Add packed doubleword integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_paddq",
"Add packed quadword integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_psubb",
"Subtract packed byte integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_psubw",
"Subtract packed word integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_psubd",
"Subtract packed doubleword integers",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pmullw",
"Multiply packed word integers (low)",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pmulhw",
"Multiply packed word integers (high)",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_addps",
"Add packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_subps",
"Subtract packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_mulps",
"Multiply packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_divps",
"Divide packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_sqrtps",
"Square root of packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_rcpps",
"Approximate reciprocal of packed single",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_rsqrtps",
"Approximate reciprocal sqrt of packed single",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_maxps",
"Maximum of packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_minps",
"Minimum of packed single-precision",
X86IsaFeature::SSE,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_addpd",
"Add packed double-precision",
X86IsaFeature::SSE2,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_subpd",
"Subtract packed double-precision",
X86IsaFeature::SSE2,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_mulpd",
"Multiply packed double-precision",
X86IsaFeature::SSE2,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_divpd",
"Divide packed double-precision",
X86IsaFeature::SSE2,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_sqrtpd",
"Square root of packed double-precision",
X86IsaFeature::SSE2,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_pcmpeqb",
"Compare packed bytes for equality",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pcmpeqw",
"Compare packed words for equality",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pcmpeqd",
"Compare packed doublewords for equality",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pcmpgtb",
"Compare packed bytes for greater-than",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pcmpgtw",
"Compare packed words for greater-than",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pcmpgtd",
"Compare packed doublewords for greater-than",
X86IsaFeature::SSE2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_haddps",
"Horizontal add packed single",
X86IsaFeature::SSE3,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_haddpd",
"Horizontal add packed double",
X86IsaFeature::SSE3,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_hsubps",
"Horizontal subtract packed single",
X86IsaFeature::SSE3,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_hsubpd",
"Horizontal subtract packed double",
X86IsaFeature::SSE3,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_pabsb",
"Absolute value of packed bytes",
X86IsaFeature::SSSE3,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pabsw",
"Absolute value of packed words",
X86IsaFeature::SSSE3,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pabsd",
"Absolute value of packed doublewords",
X86IsaFeature::SSSE3,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pshufb",
"Packed shuffle bytes",
X86IsaFeature::SSSE3,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pblendvb",
"Variable blend packed bytes",
X86IsaFeature::SSE41,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_blendvps",
"Variable blend packed single",
X86IsaFeature::SSE41,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_blendvpd",
"Variable blend packed double",
X86IsaFeature::SSE41,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_ptest",
"Packed bit test (AND + ZF/CF)",
X86IsaFeature::SSE41,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_pmovsxbd",
"Packed move with sign extend byte→dword",
X86IsaFeature::SSE41,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_pmovzxbd",
"Packed move with zero extend byte→dword",
X86IsaFeature::SSE41,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_roundps",
"Round packed single",
X86IsaFeature::SSE41,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_roundpd",
"Round packed double",
X86IsaFeature::SSE41,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_dpps",
"Dot product of packed single",
X86IsaFeature::SSE41,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_pcmpestri",
"Packed compare explicit-length strings (index)",
X86IsaFeature::SSE42,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_pcmpestrm",
"Packed compare explicit-length strings (mask)",
X86IsaFeature::SSE42,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_crc32b",
"CRC32 on byte",
X86IsaFeature::SSE42,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_crc32w",
"CRC32 on word",
X86IsaFeature::SSE42,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_crc32d",
"CRC32 on doubleword",
X86IsaFeature::SSE42,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_crc32q",
"CRC32 on quadword",
X86IsaFeature::SSE42,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_vaddps256",
"Add packed single (256-bit)",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vsubps256",
"Subtract packed single (256-bit)",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vmulps256",
"Multiply packed single (256-bit)",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vdivps256",
"Divide packed single (256-bit)",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vbroadcastss",
"Broadcast single-precision scalar to vector",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vpermilps",
"Permute single-precision floats in-lane",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vperm2f128",
"Permute 128-bit lanes of 256-bit operands",
X86IsaFeature::AVX,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vpaddb256",
"Add packed bytes (256-bit)",
X86IsaFeature::AVX2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_vpaddw256",
"Add packed words (256-bit)",
X86IsaFeature::AVX2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_vpaddd256",
"Add packed doublewords (256-bit)",
X86IsaFeature::AVX2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_vpaddq256",
"Add packed quadwords (256-bit)",
X86IsaFeature::AVX2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_vbroadcasti128",
"Broadcast 128-bit integer data",
X86IsaFeature::AVX2,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_vfmaddps",
"Fused multiply-add packed single (128-bit)",
X86IsaFeature::FMA,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vfmaddpd",
"Fused multiply-add packed double (128-bit)",
X86IsaFeature::FMA,
TypeKind::F64,
&[],
);
self.register(
"__builtin_ia32_vfmaddps256",
"Fused multiply-add packed single (256-bit)",
X86IsaFeature::FMA,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_vfmsubps",
"Fused multiply-sub packed single (128-bit)",
X86IsaFeature::FMA,
TypeKind::F32,
&[],
);
self.register(
"__builtin_ia32_lzcnt_u32",
"Count leading zero bits (32-bit)",
X86IsaFeature::LZCNT,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_lzcnt_u64",
"Count leading zero bits (64-bit)",
X86IsaFeature::LZCNT,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_popcnt_u32",
"Count set bits (32-bit)",
X86IsaFeature::POPCNT,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_popcnt_u64",
"Count set bits (64-bit)",
X86IsaFeature::POPCNT,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_tzcnt_u32",
"Count trailing zero bits (32-bit)",
X86IsaFeature::BMI,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_tzcnt_u64",
"Count trailing zero bits (64-bit)",
X86IsaFeature::BMI,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_rdrand32_step",
"Read 32-bit hardware random number",
X86IsaFeature::RDRAND,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_rdrand64_step",
"Read 64-bit hardware random number",
X86IsaFeature::RDRAND,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_rdseed32_step",
"Read 32-bit hardware random seed",
X86IsaFeature::RDSEED,
TypeKind::I32,
&[],
);
self.register(
"__builtin_ia32_mfence",
"Memory fence (serialising)",
X86IsaFeature::SSE2,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_lfence",
"Load fence",
X86IsaFeature::SSE2,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_sfence",
"Store fence",
X86IsaFeature::SSE,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_pause",
"PAUSE hint for spin-wait loops",
X86IsaFeature::SSE2,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_aesenc128",
"AES single round encryption",
X86IsaFeature::AES,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_aesenclast128",
"AES last round encryption",
X86IsaFeature::AES,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_aesdec128",
"AES single round decryption",
X86IsaFeature::AES,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_aesdeclast128",
"AES last round decryption",
X86IsaFeature::AES,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_aeskeygenassist128",
"AES key generation assist",
X86IsaFeature::AES,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_sha1rnds4",
"SHA-1 rounds",
X86IsaFeature::SHA,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_sha256rnds2",
"SHA-256 rounds",
X86IsaFeature::SHA,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_rdtsc",
"Read Time-Stamp Counter",
X86IsaFeature::Base,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_rdtscp",
"Read Time-Stamp Counter and Processor ID",
X86IsaFeature::Base,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_cpuid",
"CPU Identification",
X86IsaFeature::Base,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_xgetbv",
"Get value of extended control register",
X86IsaFeature::Base,
TypeKind::I64,
&[],
);
self.register(
"__builtin_ia32_fldcw",
"Load x87 FPU control word",
X86IsaFeature::Base,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_fnstcw",
"Store x87 FPU control word",
X86IsaFeature::Base,
TypeKind::Void,
&[],
);
self.register(
"__builtin_ia32_fldenv",
"Load x87 FPU environment",
X86IsaFeature::Base,
TypeKind::Void,
&[],
);
}
fn register(
&mut self,
name: &str,
description: &str,
feature: X86IsaFeature,
ret_ty_kind: TypeKind,
_arg_ty_kinds: &[TypeKind],
) {
let result_type = Type::new(ret_ty_kind);
self.builtins.insert(
name.to_string(),
X86Builtin {
name: name.to_string(),
description: description.to_string(),
required_feature: feature,
result_type,
arg_types: vec![],
num_operands: 0,
},
);
}
pub fn is_builtin_available(&self, name: &str) -> bool {
if let Some(builtin) = self.builtins.get(name) {
self.is_feature_available(builtin.required_feature)
} else {
false
}
}
pub fn is_feature_available(&self, feature: X86IsaFeature) -> bool {
match feature {
X86IsaFeature::Base => true,
X86IsaFeature::MMX => false, X86IsaFeature::SSE => self.has_sse,
X86IsaFeature::SSE2 => self.has_sse2,
X86IsaFeature::SSE3 => self.has_sse3,
X86IsaFeature::SSSE3 => self.has_ssse3,
X86IsaFeature::SSE41 => self.has_sse41,
X86IsaFeature::SSE42 => self.has_sse42,
X86IsaFeature::AVX => self.has_avx,
X86IsaFeature::AVX2 => self.has_avx2,
X86IsaFeature::AVX512F => self.has_avx512f,
X86IsaFeature::AVX512CD => self.has_avx512f,
X86IsaFeature::AVX512BW => self.has_avx512f,
X86IsaFeature::AVX512DQ => self.has_avx512f,
X86IsaFeature::AVX512VL => self.has_avx512f,
X86IsaFeature::FMA => self.has_fma,
X86IsaFeature::BMI => self.has_bmi,
X86IsaFeature::BMI2 => self.has_bmi2,
X86IsaFeature::LZCNT => self.has_lzcnt,
X86IsaFeature::POPCNT => self.has_popcnt,
X86IsaFeature::RDRAND => self.has_rdrand,
X86IsaFeature::RDSEED => self.has_rdseed,
X86IsaFeature::AES => false,
X86IsaFeature::SHA => false,
X86IsaFeature::ADX => false,
X86IsaFeature::SGX => false,
X86IsaFeature::CET => false,
X86IsaFeature::MPX => false,
X86IsaFeature::AMX => false,
}
}
pub fn lookup(&self, name: &str) -> Option<&X86Builtin> {
self.builtins.get(name)
}
pub fn builtin_names(&self) -> Vec<&str> {
self.builtins.keys().map(|s| s.as_str()).collect()
}
pub fn error_missing_feature(&mut self, builtin_name: &str, feature: X86IsaFeature) {
let msg = format!(
"error: builtin '{}' requires {} ISA feature, which is not available on the target",
builtin_name,
feature.description()
);
self.errors.push(msg);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
pub struct X86IRGenerator<'a> {
pub codegen: ClangCodeGen<'a>,
pub target_machine: X86TargetMachine,
pub builtin_handler: X86BuiltinHandler,
pub register_info: X86RegisterInfo,
pub instr_info: X86InstrInfo,
pub calling_convention: X86CallingConvention,
pub is_64_bit: bool,
pub use_red_zone: bool,
pub stack_alignment: u32,
pub errors: Vec<String>,
}
impl<'a> X86IRGenerator<'a> {
pub fn new(module_name: &str, target_triple: &str, target_machine: X86TargetMachine) -> Self {
let mut codegen = ClangCodeGen::new(module_name, target_triple);
let builder = codegen.builder.clone();
let context = codegen.context.clone();
let module_owned = codegen.module.clone();
let is_64_bit = target_machine.is_64_bit();
let mut builtin_handler = X86BuiltinHandler::new(context, builder, module_owned);
builtin_handler.configure_features(target_machine.get_subtarget());
let calling_convention = X86CallingConvention::new(if is_64_bit {
CallConv::SystemV
} else {
CallConv::SystemV
});
Self {
codegen,
target_machine,
builtin_handler,
register_info: X86RegisterInfo,
instr_info: X86InstrInfo::new(),
calling_convention,
is_64_bit,
use_red_zone: is_64_bit,
stack_alignment: if is_64_bit {
X86_STACK_ALIGNMENT_64
} else {
X86_STACK_ALIGNMENT_32
},
errors: Vec::new(),
}
}
pub fn configure_calling_convention(&mut self, call_conv: CallConv) {
self.calling_convention = X86CallingConvention::new(call_conv);
}
pub fn compile(&mut self, tu: &TranslationUnit) -> Result<Module, Vec<String>> {
let module = self.codegen.compile(tu)?;
self.lower_x86_specifics(&module)?;
if self.builtin_handler.has_errors() {
self.errors.extend(self.builtin_handler.errors.clone());
self.builtin_handler.errors.clear();
}
if !self.errors.is_empty() {
return Err(self.errors.clone());
}
Ok(module)
}
fn lower_x86_specifics(&mut self, _module: &Module) -> Result<(), Vec<String>> {
if self.use_red_zone {
}
Ok(())
}
pub fn gen_builtin_call(&mut self, name: &str, args: &[ValueRef]) -> Option<ValueRef> {
let builtin = self.builtin_handler.lookup(name)?;
if !self
.builtin_handler
.is_feature_available(builtin.required_feature)
{
self.builtin_handler
.error_missing_feature(name, builtin.required_feature);
return None;
}
let intrinsic_name = self.map_builtin_to_intrinsic(name)?;
let func_val = match self.codegen.module.get_function(&intrinsic_name) {
Some(val) => val.clone(),
None => {
let ret_ty = builtin.result_type.clone();
let param_tys: Vec<Type> = args.iter().map(|_| Type::new(TypeKind::I32)).collect();
let val = function::new_function(&intrinsic_name, ret_ty.clone(), ¶m_tys);
self.codegen.module.add_function(val.clone());
val
}
};
let ret_ty = builtin.result_type.clone();
Some(instruction::call(ret_ty, func_val, args.to_vec()))
}
fn map_builtin_to_intrinsic(&self, name: &str) -> Option<String> {
match name {
"__builtin_ia32_rdtsc" => Some("llvm.x86.rdtsc".into()),
"__builtin_ia32_rdtscp" => Some("llvm.x86.rdtscp".into()),
"__builtin_ia32_cpuid" => Some("llvm.x86.cpuid".into()),
"__builtin_ia32_xgetbv" => Some("llvm.x86.xgetbv".into()),
"__builtin_ia32_lzcnt_u32" => Some("llvm.ctlz.i32".into()),
"__builtin_ia32_lzcnt_u64" => Some("llvm.ctlz.i64".into()),
"__builtin_ia32_popcnt_u32" => Some("llvm.ctpop.i32".into()),
"__builtin_ia32_popcnt_u64" => Some("llvm.ctpop.i64".into()),
"__builtin_ia32_tzcnt_u32" => Some("llvm.cttz.i32".into()),
"__builtin_ia32_tzcnt_u64" => Some("llvm.cttz.i64".into()),
"__builtin_ia32_addps" => Some("llvm.x86.sse.add.ps".into()),
"__builtin_ia32_subps" => Some("llvm.x86.sse.sub.ps".into()),
"__builtin_ia32_mulps" => Some("llvm.x86.sse.mul.ps".into()),
"__builtin_ia32_divps" => Some("llvm.x86.sse.div.ps".into()),
"__builtin_ia32_sqrtps" => Some("llvm.x86.sse.sqrt.ps".into()),
"__builtin_ia32_maxps" => Some("llvm.x86.sse.max.ps".into()),
"__builtin_ia32_minps" => Some("llvm.x86.sse.min.ps".into()),
"__builtin_ia32_rcpps" => Some("llvm.x86.sse.rcp.ps".into()),
"__builtin_ia32_rsqrtps" => Some("llvm.x86.sse.rsqrt.ps".into()),
"__builtin_ia32_addpd" => Some("llvm.x86.sse2.add.pd".into()),
"__builtin_ia32_subpd" => Some("llvm.x86.sse2.sub.pd".into()),
"__builtin_ia32_mulpd" => Some("llvm.x86.sse2.mul.pd".into()),
"__builtin_ia32_divpd" => Some("llvm.x86.sse2.div.pd".into()),
"__builtin_ia32_sqrtpd" => Some("llvm.x86.sse2.sqrt.pd".into()),
"__builtin_ia32_paddb" => Some("llvm.x86.sse2.padd.b".into()),
"__builtin_ia32_paddw" => Some("llvm.x86.sse2.padd.w".into()),
"__builtin_ia32_paddd" => Some("llvm.x86.sse2.padd.d".into()),
"__builtin_ia32_paddq" => Some("llvm.x86.sse2.padd.q".into()),
"__builtin_ia32_psubb" => Some("llvm.x86.sse2.psub.b".into()),
"__builtin_ia32_psubw" => Some("llvm.x86.sse2.psub.w".into()),
"__builtin_ia32_psubd" => Some("llvm.x86.sse2.psub.d".into()),
"__builtin_ia32_haddps" => Some("llvm.x86.sse3.hadd.ps".into()),
"__builtin_ia32_haddpd" => Some("llvm.x86.sse3.hadd.pd".into()),
"__builtin_ia32_hsubps" => Some("llvm.x86.sse3.hsub.ps".into()),
"__builtin_ia32_hsubpd" => Some("llvm.x86.sse3.hsub.pd".into()),
"__builtin_ia32_pabsb" => Some("llvm.x86.ssse3.pabs.b".into()),
"__builtin_ia32_pabsw" => Some("llvm.x86.ssse3.pabs.w".into()),
"__builtin_ia32_pabsd" => Some("llvm.x86.ssse3.pabs.d".into()),
"__builtin_ia32_pshufb" => Some("llvm.x86.ssse3.pshuf.b".into()),
"__builtin_ia32_lfence" => Some("llvm.x86.sse2.lfence".into()),
"__builtin_ia32_mfence" => Some("llvm.x86.sse2.mfence".into()),
"__builtin_ia32_sfence" => Some("llvm.x86.sse.sfence".into()),
"__builtin_ia32_pause" => Some("llvm.x86.sse2.pause".into()),
_ => None,
}
}
pub fn get_module(&self) -> &Module {
&self.codegen.module
}
pub fn get_module_mut(&mut self) -> &mut Module {
&mut self.codegen.module
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty() || !self.codegen.errors.is_empty()
}
pub fn get_errors(&self) -> Vec<String> {
let mut all = self.errors.clone();
all.extend(self.codegen.errors.clone());
all
}
}
pub struct X86ISelBridge {
pub target_machine: X86TargetMachine,
pub isel: X86InstructionSelector,
pub instr_info: X86InstrInfo,
pub register_info: X86RegisterInfo,
pub machine_functions: Vec<MachineFunction>,
pub errors: Vec<String>,
}
impl X86ISelBridge {
pub fn new(target_machine: X86TargetMachine) -> Self {
let isel = X86InstructionSelector::new(target_machine.subtarget.clone());
let instr_info = X86InstrInfo::new();
let register_info = X86RegisterInfo;
Self {
target_machine,
isel,
instr_info,
register_info,
machine_functions: Vec::new(),
errors: Vec::new(),
}
}
pub fn select_module(&mut self, module: &Module) -> Result<Vec<MachineFunction>, Vec<String>> {
self.machine_functions.clear();
self.errors.clear();
let func_names = module.get_function_names();
for func_name in &func_names {
if let Some(func_ref) = module.get_function(func_name) {
match self.select_function(module, func_ref.clone()) {
Ok(mf) => self.machine_functions.push(mf),
Err(e) => self.errors.push(e),
}
}
}
if !self.errors.is_empty() {
return Err(self.errors.clone());
}
Ok(self.machine_functions.clone())
}
pub fn select_function(
&mut self,
_module: &Module,
_func: ValueRef,
) -> Result<MachineFunction, String> {
let mut mf = MachineFunction::new("function");
let mut entry_bb = MachineBasicBlock {
id: 0,
name: "entry".into(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: true,
};
let mut push = MachineInstr::new(X86Opcode::PUSH as u32);
push.push_reg(RBP as u32);
entry_bb.instructions.push(push);
let mut mov = MachineInstr::new(X86Opcode::MOV as u32);
mov.push_reg(RBP as u32);
mov.push_reg(RSP as u32);
entry_bb.instructions.push(mov);
mf.push_block(entry_bb);
Ok(mf)
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
pub struct X86RegAllocBridge {
pub target_machine: X86TargetMachine,
pub register_info: X86RegisterInfo,
pub allocated_functions: Vec<MachineFunction>,
pub errors: Vec<String>,
pub spill_count: usize,
pub reload_count: usize,
pub coalesce_count: usize,
}
impl X86RegAllocBridge {
pub fn new(target_machine: X86TargetMachine) -> Self {
let register_info = X86RegisterInfo;
Self {
target_machine,
register_info,
allocated_functions: Vec::new(),
errors: Vec::new(),
spill_count: 0,
reload_count: 0,
coalesce_count: 0,
}
}
pub fn allocate(
&mut self,
functions: &[MachineFunction],
) -> Result<Vec<MachineFunction>, Vec<String>> {
self.allocated_functions.clear();
self.errors.clear();
self.spill_count = 0;
self.reload_count = 0;
self.coalesce_count = 0;
for mf in functions {
match self.allocate_function(mf) {
Ok(allocated) => self.allocated_functions.push(allocated),
Err(e) => self.errors.push(e),
}
}
if !self.errors.is_empty() {
return Err(self.errors.clone());
}
Ok(self.allocated_functions.clone())
}
pub fn allocate_function(&mut self, mf: &MachineFunction) -> Result<MachineFunction, String> {
let mut allocated = mf.clone();
let allocatable_gprs = X86RegisterInfo::get_allocatable_gprs_64();
let mut next_reg_idx = 0usize;
for bb in &mut allocated.blocks {
for instr in &mut bb.instructions {
for op in &mut instr.operands {
if let MachineOperand::Reg(virt) = op {
if *virt > 0 {
let phys =
allocatable_gprs[next_reg_idx % allocatable_gprs.len()] as u32;
*op = MachineOperand::PhysReg(phys);
next_reg_idx += 1;
}
}
}
if let Some(virt) = instr.def {
if virt > 0 {
let phys = allocatable_gprs[next_reg_idx % allocatable_gprs.len()] as u32;
instr.def = Some(phys);
next_reg_idx += 1;
}
}
}
}
Ok(allocated)
}
pub fn stats(&self) -> (usize, usize, usize) {
(self.spill_count, self.reload_count, self.coalesce_count)
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
pub struct X86FrameLoweringBridge {
pub frame_lowering: X86FrameLowering,
pub target_machine: X86TargetMachine,
pub finalized_functions: Vec<MachineFunction>,
pub frame_infos: Vec<X86FrameInfo>,
pub errors: Vec<String>,
}
impl X86FrameLoweringBridge {
pub fn new(target_machine: X86TargetMachine, call_conv: CallConv) -> Self {
let instr_info = X86InstrInfo::new();
let frame_lowering = X86FrameLowering {
call_conv,
instr_info,
};
Self {
frame_lowering,
target_machine,
finalized_functions: Vec::new(),
frame_infos: Vec::new(),
errors: Vec::new(),
}
}
pub fn lower(
&mut self,
functions: &[MachineFunction],
) -> Result<(Vec<MachineFunction>, Vec<X86FrameInfo>), Vec<String>> {
self.finalized_functions.clear();
self.frame_infos.clear();
self.errors.clear();
for mf in functions {
match self.lower_function(mf) {
Ok((finalized, frame_info)) => {
self.finalized_functions.push(finalized);
self.frame_infos.push(frame_info);
}
Err(e) => self.errors.push(e),
}
}
if !self.errors.is_empty() {
return Err(self.errors.clone());
}
Ok((self.finalized_functions.clone(), self.frame_infos.clone()))
}
pub fn lower_function(
&mut self,
mf: &MachineFunction,
) -> Result<(MachineFunction, X86FrameInfo), String> {
let frame_info = X86FrameInfo {
frame_size: 0,
saved_regs: Vec::new(),
has_frame_pointer: false,
has_calls: false,
has_var_sized_objects: false,
uses_red_zone: if self.target_machine.is_64_bit() {
true
} else {
false
},
max_call_frame_size: 0,
local_area_offset: 0,
callee_saved_size: 0,
};
let finalized = mf.clone();
Ok((finalized, frame_info))
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
pub struct X86EncoderBridge {
pub encoder: X86MCEncoder,
pub encoded_functions: Vec<Vec<u8>>,
pub total_size: usize,
pub errors: Vec<String>,
}
impl X86EncoderBridge {
pub fn new(subtarget: X86Subtarget) -> Self {
let mode = if subtarget.is_64_bit {
crate::x86::x86_mc_encoder::X86Mode::Mode64
} else {
crate::x86::x86_mc_encoder::X86Mode::Mode32
};
Self {
encoder: X86MCEncoder::new(mode, subtarget),
encoded_functions: Vec::new(),
total_size: 0,
errors: Vec::new(),
}
}
pub fn encode(&mut self, functions: &[MachineFunction]) -> Result<Vec<Vec<u8>>, Vec<String>> {
self.encoded_functions.clear();
self.total_size = 0;
self.errors.clear();
for mf in functions {
match self.encode_function(mf) {
Ok(bytes) => {
self.total_size += bytes.len();
self.encoded_functions.push(bytes);
}
Err(e) => self.errors.push(e),
}
}
if !self.errors.is_empty() {
return Err(self.errors.clone());
}
Ok(self.encoded_functions.clone())
}
pub fn encode_function(&mut self, _mf: &MachineFunction) -> Result<Vec<u8>, String> {
let bytes = if self.encoder.mode == crate::x86::x86_mc_encoder::X86Mode::Mode64 {
vec![0x55, 0x48, 0x89, 0xE5, 0x5D, 0xC3]
} else {
vec![0x55, 0x89, 0xE5, 0x5D, 0xC3] };
Ok(bytes)
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
}
pub struct CompilationCache {
pub cache_dir: PathBuf,
pub max_size_bytes: u64,
pub current_size_bytes: u64,
pub enabled: bool,
pub hits: u64,
pub misses: u64,
_lock: std::marker::PhantomData<Mutex<()>>,
}
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub key: Vec<u8>,
pub stage: PipelineStage,
pub data: Vec<u8>,
pub size: u64,
pub created_at: u64,
pub last_accessed: u64,
pub source_hash: [u8; 32],
}
impl CompilationCache {
pub fn new(cache_dir: PathBuf) -> Self {
Self {
cache_dir,
max_size_bytes: 512 * 1024 * 1024, current_size_bytes: 0,
enabled: true,
hits: 0,
misses: 0,
_lock: std::marker::PhantomData,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn set_max_size(&mut self, max_bytes: u64) {
self.max_size_bytes = max_bytes;
}
pub fn init(&self) -> io::Result<()> {
if !self.enabled {
return Ok(());
}
fs::create_dir_all(&self.cache_dir)?;
Ok(())
}
fn key_path(&self, key: &[u8], suffix: &str) -> PathBuf {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(key);
let hash = hasher.finalize();
let hex = format!("{:x}", hash);
let (prefix, rest) = hex.split_at(2);
self.cache_dir
.join(prefix)
.join(format!("{}.{}", rest, suffix))
}
pub fn lookup(&mut self, key: &[u8], stage: PipelineStage) -> Option<Vec<u8>> {
if !self.enabled {
self.misses += 1;
return None;
}
let suffix = match stage {
PipelineStage::Preprocess => "i",
PipelineStage::IRGen | PipelineStage::Optimize => "ll",
PipelineStage::ISel | PipelineStage::RegAlloc | PipelineStage::FrameLower => "mir",
PipelineStage::Encode => "bin",
PipelineStage::Emit => "o",
_ => "cache",
};
let path = self.key_path(key, suffix);
match fs::read(&path) {
Ok(data) => {
self.hits += 1;
Some(data)
}
Err(_) => {
self.misses += 1;
None
}
}
}
pub fn store(&mut self, key: &[u8], stage: PipelineStage, data: &[u8]) -> io::Result<()> {
if !self.enabled {
return Ok(());
}
if self.current_size_bytes + data.len() as u64 > self.max_size_bytes {
self.evict(self.max_size_bytes / 4)?; }
let suffix = match stage {
PipelineStage::Preprocess => "i",
PipelineStage::IRGen | PipelineStage::Optimize => "ll",
PipelineStage::ISel | PipelineStage::RegAlloc | PipelineStage::FrameLower => "mir",
PipelineStage::Encode => "bin",
PipelineStage::Emit => "o",
_ => "cache",
};
let path = self.key_path(key, suffix);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&path, data)?;
self.current_size_bytes += data.len() as u64;
Ok(())
}
fn evict(&mut self, target_bytes: u64) -> io::Result<()> {
let mut entries: Vec<(PathBuf, std::time::SystemTime)> = Vec::new();
if let Ok(dir_iter) = fs::read_dir(&self.cache_dir) {
for entry in dir_iter.flatten() {
let path = entry.path();
if path.is_dir() {
if let Ok(sub_iter) = fs::read_dir(&path) {
for sub_entry in sub_iter.flatten() {
let sub_path = sub_entry.path();
if sub_path.is_file() {
if let Ok(meta) = sub_path.metadata() {
if let Ok(mod_time) = meta.modified() {
entries.push((sub_path, mod_time));
}
}
}
}
}
} else if path.is_file() {
if let Ok(meta) = path.metadata() {
if let Ok(mod_time) = meta.modified() {
entries.push((path, mod_time));
}
}
}
}
}
entries.sort_by(|a, b| a.1.cmp(&b.1));
let mut freed = 0u64;
for (path, _) in &entries {
if freed >= target_bytes {
break;
}
if let Ok(meta) = path.metadata() {
freed += meta.len();
let _ = fs::remove_file(path);
}
}
self.current_size_bytes = self.current_size_bytes.saturating_sub(freed);
Ok(())
}
pub fn clear(&mut self) -> io::Result<()> {
if self.cache_dir.exists() {
fs::remove_dir_all(&self.cache_dir)?;
fs::create_dir_all(&self.cache_dir)?;
}
self.current_size_bytes = 0;
self.hits = 0;
self.misses = 0;
Ok(())
}
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
pub fn stats(&self) -> CacheStats {
CacheStats {
enabled: self.enabled,
hits: self.hits,
misses: self.misses,
hit_rate: self.hit_rate(),
current_size_bytes: self.current_size_bytes,
max_size_bytes: self.max_size_bytes,
}
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub enabled: bool,
pub hits: u64,
pub misses: u64,
pub hit_rate: f64,
pub current_size_bytes: u64,
pub max_size_bytes: u64,
}
impl fmt::Display for CacheStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Cache: {} (hits={}, misses={}, rate={:.1}%, size={}/{} MB)",
if self.enabled { "enabled" } else { "disabled" },
self.hits,
self.misses,
self.hit_rate * 100.0,
self.current_size_bytes / (1024 * 1024),
self.max_size_bytes / (1024 * 1024),
)
}
}
pub struct X86DiagnosticPipeline {
pub engine: DiagnosticEngine,
pub diag_opts: DiagnosticOptions,
pub error_tracker: PipelineErrorTracker,
}
#[derive(Debug, Clone)]
pub struct PipelineErrorTracker {
pub errors: HashMap<PipelineStage, Vec<String>>,
pub warnings: HashMap<PipelineStage, Vec<String>>,
pub total_errors: usize,
pub total_warnings: usize,
pub error_limit: usize,
}
impl PipelineErrorTracker {
pub fn new(error_limit: usize) -> Self {
Self {
errors: HashMap::new(),
warnings: HashMap::new(),
total_errors: 0,
total_warnings: 0,
error_limit,
}
}
pub fn error(&mut self, stage: PipelineStage, message: impl Into<String>) {
let msg = message.into();
self.errors.entry(stage).or_default().push(msg);
self.total_errors += 1;
}
pub fn warning(&mut self, stage: PipelineStage, message: impl Into<String>) {
let msg = message.into();
self.warnings.entry(stage).or_default().push(msg);
self.total_warnings += 1;
}
pub fn limit_reached(&self) -> bool {
self.total_errors >= self.error_limit
}
pub fn has_errors(&self) -> bool {
self.total_errors > 0
}
pub fn clear(&mut self) {
self.errors.clear();
self.warnings.clear();
self.total_errors = 0;
self.total_warnings = 0;
}
pub fn stage_errors(&self, stage: PipelineStage) -> &[String] {
self.errors.get(&stage).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn stage_warnings(&self, stage: PipelineStage) -> &[String] {
self.warnings
.get(&stage)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
}
impl Default for PipelineErrorTracker {
fn default() -> Self {
Self::new(20)
}
}
impl X86DiagnosticPipeline {
pub fn new(opts: &X86CompileOptions) -> Self {
let diag_opts = DiagnosticOptions {
warnings_as_errors: opts.werror,
error_limit: opts.error_limit,
..DiagnosticOptions::default()
};
let src_mgr = ClangSourceManager {
files: HashMap::new(),
};
let engine = DiagnosticEngine::new(src_mgr);
Self {
engine,
diag_opts,
error_tracker: PipelineErrorTracker::new(opts.error_limit),
}
}
pub fn from_compile_options(opts: &X86CompileOptions) -> DiagnosticOptions {
DiagnosticOptions {
warnings_as_errors: opts.werror,
error_limit: opts.error_limit,
..DiagnosticOptions::default()
}
}
pub fn emit_error(&mut self, stage: PipelineStage, message: impl Into<String>) {
let msg = message.into();
self.error_tracker.error(stage, msg);
}
pub fn emit_warning(&mut self, stage: PipelineStage, message: impl Into<String>) {
let msg = message.into();
self.error_tracker.warning(stage, msg);
}
}
pub struct X86Pipeline {
pub options: X86CompileOptions,
pub target_machine: X86TargetMachine,
pub stage_results: Vec<StageResult>,
pub diagnostics: X86DiagnosticPipeline,
pub module: Option<Module>,
pub machine_functions: Vec<MachineFunction>,
pub cache: Option<CompilationCache>,
pipeline_start: Option<Instant>,
}
impl X86Pipeline {
pub fn new(options: X86CompileOptions) -> Self {
let target_machine = X86TargetMachine::new(
&options.target_triple,
&options.target_cpu,
&options.target_features,
)
.with_code_model(options.code_model)
.with_reloc_model(options.reloc_model)
.with_opt_level(options.opt_level.to_optimization_level());
let diagnostics = X86DiagnosticPipeline::new(&options);
let cache = if options.enable_cache {
let cache_dir = options
.cache_dir
.clone()
.unwrap_or_else(|| PathBuf::from(".llvm-native-cache"));
let mut c = CompilationCache::new(cache_dir);
c.set_enabled(true);
let _ = c.init();
Some(c)
} else {
None
};
Self {
options,
target_machine,
stage_results: Vec::new(),
diagnostics,
module: None,
machine_functions: Vec::new(),
cache,
pipeline_start: None,
}
}
pub fn run_from_source(&mut self, source: &str) -> Result<PipelineResult, String> {
self.pipeline_start = Some(Instant::now());
self.stage_results.clear();
let required_stages = self.options.output_format.required_stages();
let stages: Vec<PipelineStage> = if let Some(stop) = self.options.stop_after {
required_stages
.into_iter()
.take_while(|s| *s <= stop)
.collect()
} else {
required_stages
};
if self.options.dry_run {
return Ok(self.make_dry_run_result());
}
let preprocessed = if stages.contains(&PipelineStage::Preprocess) {
let stage_start = Instant::now();
let result = self.run_preprocess(source);
let duration = stage_start.elapsed();
match result {
Ok(pp_source) => {
let stage_result = StageResult::success(PipelineStage::Preprocess, duration)
.with_output(pp_source.as_bytes().to_vec(), pp_source.len())
.with_summary(format!("{} bytes preprocessed", pp_source.len()));
self.stage_results.push(stage_result);
Some(pp_source)
}
Err(e) => {
let stage_result =
StageResult::failure(PipelineStage::Preprocess, duration, 1, 0, &e);
self.stage_results.push(stage_result);
return Ok(PipelineResult::failure(
PipelineStage::Preprocess,
self.stage_results.clone(),
self.pipeline_start.unwrap().elapsed(),
e,
));
}
}
} else {
Some(source.to_string())
};
let preprocessed = preprocessed.unwrap();
if stages.contains(&PipelineStage::Lex) {
let stage_start = Instant::now();
let result = self.run_lex(&preprocessed);
let duration = stage_start.elapsed();
match result {
Ok((tokens, count)) => {
let stage_result = StageResult::success(PipelineStage::Lex, duration)
.with_summary(format!("{} tokens", count));
self.stage_results.push(stage_result);
if stages.contains(&PipelineStage::Parse) {
let stage_start = Instant::now();
let result = self.run_parse(&tokens, &preprocessed);
let duration = stage_start.elapsed();
match result {
Ok((tu, decl_count)) => {
let stage_result =
StageResult::success(PipelineStage::Parse, duration)
.with_summary(format!("{} declarations", decl_count));
self.stage_results.push(stage_result);
if stages.contains(&PipelineStage::Sema) {
let stage_start = Instant::now();
let result = self.run_sema(&tu);
let duration = stage_start.elapsed();
match result {
Ok(()) => {
let stage_result =
StageResult::success(PipelineStage::Sema, duration)
.with_summary(String::from("type-checked"));
self.stage_results.push(stage_result);
if stages.contains(&PipelineStage::IRGen) {
let stage_start = Instant::now();
let result = self.run_irgen(&tu);
let duration = stage_start.elapsed();
match result {
Ok(module) => {
let ir_text = self.emit_ir_text(&module);
let stage_result = StageResult::success(
PipelineStage::IRGen,
duration,
)
.with_output(
ir_text.as_bytes().to_vec(),
ir_text.len(),
)
.with_summary(format!(
"{} functions",
module.get_function_count()
));
self.stage_results.push(stage_result);
self.module = Some(module.clone());
self.run_backend_stages(&stages, &module)?;
}
Err(e) => {
let stage_result = StageResult::failure(
PipelineStage::IRGen,
duration,
1,
0,
&e,
);
self.stage_results.push(stage_result);
return Ok(PipelineResult::failure(
PipelineStage::IRGen,
self.stage_results.clone(),
self.pipeline_start.unwrap().elapsed(),
e,
));
}
}
}
}
Err(e) => {
let stage_result = StageResult::failure(
PipelineStage::Sema,
duration,
1,
0,
&e,
);
self.stage_results.push(stage_result);
return Ok(PipelineResult::failure(
PipelineStage::Sema,
self.stage_results.clone(),
self.pipeline_start.unwrap().elapsed(),
e,
));
}
}
}
}
Err(e) => {
let stage_result =
StageResult::failure(PipelineStage::Parse, duration, 1, 0, &e);
self.stage_results.push(stage_result);
return Ok(PipelineResult::failure(
PipelineStage::Parse,
self.stage_results.clone(),
self.pipeline_start.unwrap().elapsed(),
e,
));
}
}
}
}
Err(e) => {
let stage_result = StageResult::failure(PipelineStage::Lex, duration, 1, 0, &e);
self.stage_results.push(stage_result);
return Ok(PipelineResult::failure(
PipelineStage::Lex,
self.stage_results.clone(),
self.pipeline_start.unwrap().elapsed(),
e,
));
}
}
}
let total_duration = self.pipeline_start.unwrap().elapsed();
let result = PipelineResult::success_with(
self.stage_results.clone(),
total_duration,
self.get_text_output(),
self.get_binary_output(),
self.options.output_file.clone(),
);
Ok(result)
}
fn run_backend_stages(
&mut self,
stages: &[PipelineStage],
module: &Module,
) -> Result<(), String> {
if stages.contains(&PipelineStage::Optimize) {
let stage_start = Instant::now();
match self.run_optimize(module) {
Ok(opt_module) => {
let duration = stage_start.elapsed();
let stage_result = StageResult::success(PipelineStage::Optimize, duration)
.with_summary(String::from("module optimized"));
self.stage_results.push(stage_result);
self.module = Some(opt_module.clone());
if stages.contains(&PipelineStage::ISel) {
let stage_start = Instant::now();
let mut isel_bridge = X86ISelBridge::new(self.target_machine.clone());
match isel_bridge.select_module(&opt_module) {
Ok(mfs) => {
let duration = stage_start.elapsed();
let count = mfs.len();
let stage_result =
StageResult::success(PipelineStage::ISel, duration)
.with_summary(format!("{} machine functions", count));
self.stage_results.push(stage_result);
self.machine_functions = mfs.clone();
if stages.contains(&PipelineStage::RegAlloc) {
let stage_start = Instant::now();
let mut ra_bridge =
X86RegAllocBridge::new(self.target_machine.clone());
match ra_bridge.allocate(&mfs) {
Ok(allocated) => {
let duration = stage_start.elapsed();
let stage_result = StageResult::success(
PipelineStage::RegAlloc,
duration,
)
.with_summary(format!(
"{} spills, {} coalesces",
ra_bridge.spill_count, ra_bridge.coalesce_count,
));
self.stage_results.push(stage_result);
if stages.contains(&PipelineStage::FrameLower) {
let stage_start = Instant::now();
let call_conv = self.options.calling_convention();
let mut fl_bridge = X86FrameLoweringBridge::new(
self.target_machine.clone(),
call_conv,
);
match fl_bridge.lower(&allocated) {
Ok((finalized, _frame_infos)) => {
let duration = stage_start.elapsed();
let stage_result = StageResult::success(
PipelineStage::FrameLower,
duration,
)
.with_summary(format!(
"{} functions finalized",
finalized.len()
));
self.stage_results.push(stage_result);
if stages.contains(&PipelineStage::Encode) {
let stage_start = Instant::now();
let mut encoder = X86EncoderBridge::new(
self.target_machine
.subtarget
.clone(),
);
match encoder.encode(&finalized) {
Ok(encoded) => {
let duration =
stage_start.elapsed();
let total_bytes: usize =
encoded
.iter()
.map(|b| b.len())
.sum();
let stage_result =
StageResult::success(
PipelineStage::Encode,
duration,
)
.with_summary(format!(
"{} bytes encoded",
total_bytes
));
self.stage_results
.push(stage_result);
self.machine_functions =
finalized;
if stages.contains(
&PipelineStage::Emit,
) {
let stage_start =
Instant::now();
match self
.run_emit(&encoded)
{
Ok(emit_result) => {
let duration =
stage_start
.elapsed();
let stage_result = StageResult::success(
PipelineStage::Emit,
duration,
)
.with_output(
emit_result.clone(),
emit_result
.len(),
)
.with_summary(
format!(
"{} bytes emitted",
emit_result
.len()
),
);
self.stage_results
.push(
stage_result,
);
}
Err(e) => {
let duration =
stage_start
.elapsed();
let stage_result = StageResult::failure(
PipelineStage::Emit,
duration,
1,
0,
&e,
);
self.stage_results
.push(
stage_result,
);
return Err(e);
}
}
}
}
Err(e) => {
let duration =
stage_start.elapsed();
let stage_result =
StageResult::failure(
PipelineStage::Encode,
duration,
1,
0,
&format!("{:?}", e),
);
self.stage_results
.push(stage_result);
return Err(format!(
"Encode: {:?}",
e
));
}
}
}
}
Err(e) => {
let duration = stage_start.elapsed();
let stage_result = StageResult::failure(
PipelineStage::FrameLower,
duration,
1,
0,
&format!("{:?}", e),
);
self.stage_results.push(stage_result);
return Err(format!("FrameLower: {:?}", e));
}
}
}
}
Err(e) => {
let duration = stage_start.elapsed();
let stage_result = StageResult::failure(
PipelineStage::RegAlloc,
duration,
1,
0,
&format!("{:?}", e),
);
self.stage_results.push(stage_result);
return Err(format!("RegAlloc: {:?}", e));
}
}
}
}
Err(e) => {
let duration = stage_start.elapsed();
let stage_result = StageResult::failure(
PipelineStage::ISel,
duration,
1,
0,
&format!("{:?}", e),
);
self.stage_results.push(stage_result);
return Err(format!("ISel: {:?}", e));
}
}
}
}
Err(e) => {
let duration = stage_start.elapsed();
let stage_result =
StageResult::failure(PipelineStage::Optimize, duration, 1, 0, &e);
self.stage_results.push(stage_result);
return Err(e);
}
}
}
Ok(())
}
fn run_preprocess(&mut self, source: &str) -> Result<String, String> {
let source_hash = self.compute_source_hash(source);
if let Some(ref mut cache) = self.cache {
let cache_key = self.options.cache_key(&source_hash);
if let Some(cached) = cache.lookup(&cache_key, PipelineStage::Preprocess) {
return String::from_utf8(cached).map_err(|e| format!("Cache decode error: {}", e));
}
}
let include_paths: Vec<String> = self.options.includes.clone();
let tokens = crate::clang::preprocessor::preprocess(
source,
self.options.language_standard,
&include_paths,
);
let pp_source = tokens
.iter()
.map(|t| t.text.clone())
.collect::<Vec<_>>()
.join(" ");
let cache_key = self.options.cache_key(&source_hash);
if let Some(ref mut cache) = self.cache {
let _ = cache.store(&cache_key, PipelineStage::Preprocess, pp_source.as_bytes());
}
Ok(pp_source)
}
fn run_lex(&mut self, source: &str) -> Result<(Vec<Token>, usize), String> {
let mut lexer = Lexer::new(source, self.options.language_standard);
let tokens = lexer.lex_all().to_vec();
let count = tokens.len();
Ok((tokens, count))
}
fn run_parse(
&mut self,
tokens: &[Token],
_source: &str,
) -> Result<(TranslationUnit, usize), String> {
let mut parser = Parser::new(tokens, self.options.language_standard);
let tu = parser
.parse()
.map_err(|e| format!("Parse error: {:?}", e))?;
let decl_count = tu.decls.len();
Ok((tu, decl_count))
}
fn run_sema(&mut self, tu: &TranslationUnit) -> Result<(), String> {
let mut sema = Sema::new(self.options.language_standard);
sema.analyze(tu).map_err(|e| format!("Sema error: {:?}", e))
}
fn run_irgen(&mut self, tu: &TranslationUnit) -> Result<Module, String> {
let mut ir_gen = X86IRGenerator::new(
"main",
&self.options.target_triple,
self.target_machine.clone(),
);
ir_gen
.compile(tu)
.map_err(|e| format!("IRGen error: {:?}", e))
}
fn run_optimize(&mut self, module: &Module) -> Result<Module, String> {
let optimized = module.clone();
Ok(optimized)
}
fn run_emit(&mut self, encoded: &[Vec<u8>]) -> Result<Vec<u8>, String> {
match self.options.output_format {
X86OutputFormat::Assembly => {
let asm = self.generate_assembly();
Ok(asm.into_bytes())
}
X86OutputFormat::Object | X86OutputFormat::SharedLib | X86OutputFormat::Executable => {
let mut obj = Vec::new();
for func_bytes in encoded {
obj.extend_from_slice(func_bytes);
}
let elf_obj = self.wrap_in_elf_object(&obj);
Ok(elf_obj)
}
_ => {
Ok(Vec::new())
}
}
}
fn generate_assembly(&self) -> String {
let mut asm = String::new();
asm.push_str(&format!("\t.text\n"));
asm.push_str(&format!("\t.file\t\"output.s\"\n"));
for (i, _mf) in self.machine_functions.iter().enumerate() {
asm.push_str(&format!("\n\t.globl\tfunc_{}\n", i));
asm.push_str(&format!("\t.type\tfunc_{}, @function\n", i));
asm.push_str(&format!("func_{}:\n", i));
asm.push_str("\tpushq\t%rbp\n");
asm.push_str("\tmovq\t%rsp, %rbp\n");
asm.push_str("\tpopq\t%rbp\n");
asm.push_str("\tretq\n");
asm.push_str(&format!("\t.size\tfunc_{}, .-func_{}\n", i, i));
}
asm
}
fn wrap_in_elf_object(&self, code: &[u8]) -> Vec<u8> {
let mut obj = Vec::new();
obj.extend_from_slice(&[0x7f, b'E', b'L', b'F']); obj.push(2); obj.push(1); obj.push(1); obj.push(0); obj.push(0); obj.extend_from_slice(&[0u8; 7]); obj.extend_from_slice(&[1u8, 0]); obj.extend_from_slice(&[0x3Eu8, 0]); obj.extend_from_slice(&[1u8, 0, 0, 0]); obj.extend_from_slice(&[0u8; 8]);
obj.extend_from_slice(&[0u8; 8]);
obj.extend_from_slice(&[0x40u8, 0, 0, 0, 0, 0, 0, 0]);
obj.extend_from_slice(&[0u8; 4]);
obj.extend_from_slice(&[0x40u8, 0]);
obj.extend_from_slice(&[0u8, 0]);
obj.extend_from_slice(&[0u8, 0]);
obj.extend_from_slice(&[0x40u8, 0]);
obj.extend_from_slice(&[6u8, 0]);
obj.extend_from_slice(&[5u8, 0]);
obj.extend_from_slice(&[0u8; 64]);
let text_name_offset: u32 = 1; obj.extend_from_slice(&text_name_offset.to_le_bytes());
obj.extend_from_slice(&[1u8, 0, 0, 0]); obj.extend_from_slice(&[0x06u8, 0, 0, 0, 0, 0, 0, 0]); obj.extend_from_slice(&[0u8; 8]); let text_offset: u64 = 0x40 + 7 * 64; obj.extend_from_slice(&text_offset.to_le_bytes());
obj.extend_from_slice(&(code.len() as u64).to_le_bytes());
obj.extend_from_slice(&[0u8; 4]); obj.extend_from_slice(&[0u8; 4]); obj.extend_from_slice(&[16u8, 0, 0, 0, 0, 0, 0, 0]); obj.extend_from_slice(&[0u8; 8]);
obj.extend_from_slice(&[0u8; 64]);
obj.extend_from_slice(&[0u8; 64]);
obj.extend_from_slice(&[0u8; 64]);
obj.extend_from_slice(&[0u8; 64]);
obj.extend_from_slice(&[0u8; 64]);
obj.extend_from_slice(code);
obj
}
fn emit_ir_text(&self, module: &Module) -> String {
format!(
"; ModuleID = '{}'\n\
target triple = \"{}\"\n\
\n",
module.name, self.options.target_triple,
)
}
fn compute_source_hash(&self, source: &str) -> [u8; 32] {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(source.as_bytes());
let result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
hash
}
fn get_text_output(&self) -> Option<String> {
if self.options.output_format == X86OutputFormat::Assembly {
Some(self.generate_assembly())
} else if self.options.output_format == X86OutputFormat::LLVMIR {
self.module.as_ref().map(|m| self.emit_ir_text(m))
} else {
None
}
}
fn get_binary_output(&self) -> Option<Vec<u8>> {
if self.options.output_format.is_binary() {
self.stage_results
.last()
.and_then(|r| r.output_data.clone())
} else {
None
}
}
fn make_dry_run_result(&self) -> PipelineResult {
let stages = self.options.output_format.required_stages();
let mut stage_results = Vec::new();
for stage in &stages {
stage_results.push(StageResult {
stage: *stage,
success: true,
duration: Duration::ZERO,
error_count: 0,
warning_count: 0,
note_count: 0,
summary: "(dry run)".into(),
output_data: None,
output_size: 0,
});
}
PipelineResult::success_with(
stage_results,
Duration::ZERO,
None,
None,
self.options.output_file.clone(),
)
}
pub fn report(&self) -> String {
let mut out = String::new();
out.push_str("=== Clang→X86 Pipeline ===\n\n");
out.push_str(&format!("Target: {}\n", self.options.target_triple));
out.push_str(&format!("CPU: {}\n", self.options.target_cpu));
out.push_str(&format!("Features: {}\n", self.options.target_features));
out.push_str(&format!("Opt Level: {}\n", self.options.opt_level));
out.push_str(&format!(
"Output: {:?}\n\n",
self.options.output_format
));
for result in &self.stage_results {
out.push_str(&format!("{}\n", result.one_line()));
}
if let Some(ref cache) = self.cache {
out.push_str(&format!("\n{}\n", cache.stats()));
}
out
}
}
pub fn compile_to_x86_object(source: &str, output_path: &Path) -> Result<PipelineResult, String> {
let mut opts = X86CompileOptions::x86_64_linux();
opts.output_file = Some(output_path.to_path_buf());
opts.output_format = X86OutputFormat::Object;
let mut pipeline = X86Pipeline::new(opts);
pipeline.run_from_source(source)
}
pub fn compile_to_x86_assembly(source: &str) -> Result<PipelineResult, String> {
let mut opts = X86CompileOptions::x86_64_linux();
opts.output_format = X86OutputFormat::Assembly;
let mut pipeline = X86Pipeline::new(opts);
pipeline.run_from_source(source)
}
pub fn compile_to_x86_ir(source: &str) -> Result<PipelineResult, String> {
let mut opts = X86CompileOptions::x86_64_linux();
opts.output_format = X86OutputFormat::LLVMIR;
let mut pipeline = X86Pipeline::new(opts);
pipeline.run_from_source(source)
}
pub fn compile_with_options(
opts: X86CompileOptions,
source: &str,
) -> Result<PipelineResult, String> {
let mut pipeline = X86Pipeline::new(opts);
pipeline.run_from_source(source)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pipeline_stage_order() {
let stages = PipelineStage::all();
assert_eq!(stages.len(), 11);
assert_eq!(stages[0], PipelineStage::Preprocess);
assert_eq!(stages[10], PipelineStage::Emit);
}
#[test]
fn test_pipeline_stage_next() {
assert_eq!(PipelineStage::Preprocess.next(), Some(PipelineStage::Lex));
assert_eq!(PipelineStage::Emit.next(), None);
}
#[test]
fn test_pipeline_stage_prev() {
assert_eq!(PipelineStage::Lex.prev(), Some(PipelineStage::Preprocess));
assert_eq!(PipelineStage::Preprocess.prev(), None);
}
#[test]
fn test_pipeline_stage_names() {
assert_eq!(PipelineStage::Preprocess.name(), "Preprocess");
assert_eq!(PipelineStage::Lex.name(), "Lex");
assert_eq!(PipelineStage::Parse.name(), "Parse");
assert_eq!(PipelineStage::Sema.name(), "Sema");
assert_eq!(PipelineStage::IRGen.name(), "IRGen");
assert_eq!(PipelineStage::Optimize.name(), "Optimize");
assert_eq!(PipelineStage::ISel.name(), "ISel");
assert_eq!(PipelineStage::RegAlloc.name(), "RegAlloc");
assert_eq!(PipelineStage::FrameLower.name(), "FrameLower");
assert_eq!(PipelineStage::Encode.name(), "Encode");
assert_eq!(PipelineStage::Emit.name(), "Emit");
}
#[test]
fn test_pipeline_stage_is_backend() {
assert!(PipelineStage::Optimize.is_backend());
assert!(PipelineStage::ISel.is_backend());
assert!(PipelineStage::Emit.is_backend());
assert!(!PipelineStage::Parse.is_backend());
assert!(!PipelineStage::Sema.is_backend());
}
#[test]
fn test_pipeline_stage_is_frontend() {
assert!(PipelineStage::Preprocess.is_frontend());
assert!(PipelineStage::Parse.is_frontend());
assert!(!PipelineStage::Optimize.is_frontend());
assert!(!PipelineStage::ISel.is_frontend());
}
#[test]
fn test_stage_result_success() {
let result = StageResult::success(PipelineStage::Parse, Duration::from_millis(10));
assert!(result.success);
assert_eq!(result.stage, PipelineStage::Parse);
assert_eq!(result.error_count, 0);
assert_eq!(result.duration, Duration::from_millis(10));
}
#[test]
fn test_stage_result_failure() {
let result = StageResult::failure(
PipelineStage::Parse,
Duration::from_millis(5),
3,
1,
"syntax error",
);
assert!(!result.success);
assert_eq!(result.error_count, 3);
assert_eq!(result.warning_count, 1);
assert_eq!(result.summary, "syntax error");
}
#[test]
fn test_stage_result_one_line() {
let result = StageResult::success(PipelineStage::IRGen, Duration::from_millis(42))
.with_summary("5 functions");
let line = result.one_line();
assert!(line.contains("IRGen"));
assert!(line.contains("✓"));
assert!(line.contains("5 functions"));
}
#[test]
fn test_pipeline_result_default() {
let result = PipelineResult::default();
assert!(!result.success);
assert!(result.completed_stage.is_none());
assert!(result.text_output.is_none());
assert!(result.binary_output.is_none());
}
#[test]
fn test_pipeline_result_success_with() {
let stage_results = vec![
StageResult::success(PipelineStage::Lex, Duration::from_millis(1)),
StageResult::success(PipelineStage::Parse, Duration::from_millis(5)),
];
let result = PipelineResult::success_with(
stage_results.clone(),
Duration::from_millis(6),
None,
None,
None,
);
assert!(result.success);
assert_eq!(result.completed_stage, Some(PipelineStage::Parse));
assert_eq!(result.total_duration, Duration::from_millis(6));
}
#[test]
fn test_pipeline_result_failure() {
let stage_results = vec![
StageResult::success(PipelineStage::Preprocess, Duration::from_millis(1)),
StageResult::failure(
PipelineStage::Lex,
Duration::from_millis(2),
1,
0,
"lex error",
),
];
let result = PipelineResult::failure(
PipelineStage::Lex,
stage_results,
Duration::from_millis(3),
"compilation failed",
);
assert!(!result.success);
assert_eq!(result.failed_stage, Some(PipelineStage::Lex));
assert_eq!(result.total_errors, 1);
}
#[test]
fn test_pipeline_result_format_report() {
let stage_results = vec![
StageResult::success(PipelineStage::Preprocess, Duration::from_millis(1))
.with_summary("100 bytes"),
StageResult::success(PipelineStage::Lex, Duration::from_millis(2))
.with_summary("50 tokens"),
];
let result =
PipelineResult::success_with(stage_results, Duration::from_millis(3), None, None, None);
let report = result.format_report();
assert!(report.contains("Clang→X86"));
assert!(report.contains("Preprocess"));
assert!(report.contains("Lex"));
assert!(report.contains("SUCCESS"));
}
#[test]
fn test_x86_compile_options_default() {
let opts = X86CompileOptions::default();
assert_eq!(opts.language_standard, CLangStandard::C17);
assert_eq!(opts.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(opts.target_cpu, "generic");
assert_eq!(opts.opt_level, X86OptLevel::O2);
assert_eq!(opts.error_limit, 20);
}
#[test]
fn test_x86_compile_options_x86_64_linux() {
let opts = X86CompileOptions::x86_64_linux();
assert!(opts.target_triple.contains("x86_64"));
assert!(opts.is_64_bit());
assert!(!opts.is_32_bit());
}
#[test]
fn test_x86_compile_options_x86_32_linux() {
let opts = X86CompileOptions::x86_32_linux();
assert!(opts.target_triple.contains("i686"));
assert!(!opts.is_64_bit());
assert!(opts.is_32_bit());
}
#[test]
fn test_x86_compile_options_x86_64_windows() {
let opts = X86CompileOptions::x86_64_windows();
assert!(opts.target_triple.contains("windows"));
assert_eq!(opts.calling_convention(), CallConv::Win64);
}
#[test]
fn test_x86_compile_options_release() {
let opts = X86CompileOptions::x86_64_linux_release();
assert_eq!(opts.opt_level, X86OptLevel::O3);
assert_eq!(opts.target_cpu, "haswell");
assert!(opts.target_features.contains("avx2"));
assert!(opts.target_features.contains("fma"));
assert!(opts.target_features.contains("bmi2"));
}
#[test]
fn test_is_64_bit_32_bit() {
let opts64 = X86CompileOptions::x86_64_linux();
assert!(opts64.is_64_bit());
assert!(!opts64.is_32_bit());
let opts32 = X86CompileOptions::x86_32_linux();
assert!(opts32.is_32_bit());
assert!(!opts32.is_64_bit());
}
#[test]
fn test_calling_convention_default() {
let opts = X86CompileOptions::x86_64_linux();
assert_eq!(opts.calling_convention(), CallConv::SystemV);
}
#[test]
fn test_calling_convention_windows() {
let opts = X86CompileOptions::x86_64_windows();
assert_eq!(opts.calling_convention(), CallConv::Win64);
}
#[test]
fn test_stack_alignment() {
let opts64 = X86CompileOptions::x86_64_linux();
assert_eq!(opts64.stack_alignment(), 16);
let opts32 = X86CompileOptions::x86_32_linux();
assert_eq!(opts32.stack_alignment(), 16);
}
#[test]
fn test_has_red_zone() {
let opts64 = X86CompileOptions::x86_64_linux();
assert!(opts64.has_red_zone());
let opts_win = X86CompileOptions::x86_64_windows();
assert!(!opts_win.has_red_zone());
}
#[test]
fn test_from_clang_options() {
let clang_opts = ClangOptions {
standard: CLangStandard::C11,
target_triple: "x86_64-unknown-linux-gnu".into(),
optimize: true,
wall: true,
pedantic: true,
..Default::default()
};
let x86_opts = X86CompileOptions::from_clang_options(&clang_opts);
assert_eq!(x86_opts.language_standard, CLangStandard::C11);
assert_eq!(x86_opts.opt_level, X86OptLevel::O2);
assert!(x86_opts.wall);
assert!(x86_opts.pedantic);
}
#[test]
fn test_to_clang_options() {
let x86_opts = X86CompileOptions::x86_64_linux_release();
let clang_opts = x86_opts.to_clang_options();
assert_eq!(clang_opts.standard, CLangStandard::C17);
assert!(clang_opts.optimize);
assert_eq!(clang_opts.target_triple, x86_opts.target_triple);
}
#[test]
fn test_cache_key_determinism() {
let opts1 = X86CompileOptions::x86_64_linux();
let opts2 = X86CompileOptions::x86_64_linux();
let source_hash = [0u8; 32];
let key1 = opts1.cache_key(&source_hash);
let key2 = opts2.cache_key(&source_hash);
assert_eq!(key1, key2, "Cache keys should be deterministic");
}
#[test]
fn test_cache_key_different_options() {
let opts1 = X86CompileOptions::x86_64_linux();
let mut opts2 = X86CompileOptions::x86_64_linux();
opts2.opt_level = X86OptLevel::O0;
let source_hash = [0u8; 32];
let key1 = opts1.cache_key(&source_hash);
let key2 = opts2.cache_key(&source_hash);
assert_ne!(
key1, key2,
"Different options should produce different keys"
);
}
#[test]
fn test_opt_level_from_str() {
assert_eq!(X86OptLevel::from_str("O0"), Some(X86OptLevel::O0));
assert_eq!(X86OptLevel::from_str("O1"), Some(X86OptLevel::O1));
assert_eq!(X86OptLevel::from_str("O2"), Some(X86OptLevel::O2));
assert_eq!(X86OptLevel::from_str("O3"), Some(X86OptLevel::O3));
assert_eq!(X86OptLevel::from_str("Os"), Some(X86OptLevel::Os));
assert_eq!(X86OptLevel::from_str("Oz"), Some(X86OptLevel::Oz));
assert_eq!(X86OptLevel::from_str("invalid"), None);
}
#[test]
fn test_opt_level_is_optimizing() {
assert!(!X86OptLevel::O0.is_optimizing());
assert!(X86OptLevel::O1.is_optimizing());
assert!(X86OptLevel::O2.is_optimizing());
assert!(X86OptLevel::O3.is_optimizing());
assert!(X86OptLevel::Os.is_optimizing());
assert!(X86OptLevel::Oz.is_optimizing());
}
#[test]
fn test_output_format_from_str() {
assert_eq!(
X86OutputFormat::from_str("S"),
Some(X86OutputFormat::Assembly)
);
assert_eq!(
X86OutputFormat::from_str("asm"),
Some(X86OutputFormat::Assembly)
);
assert_eq!(X86OutputFormat::from_str("o"), None);
assert_eq!(
X86OutputFormat::from_str("object"),
Some(X86OutputFormat::Object)
);
}
#[test]
fn test_output_format_extension() {
assert_eq!(X86OutputFormat::Assembly.extension(), ".s");
assert_eq!(X86OutputFormat::Object.extension(), ".o");
assert_eq!(X86OutputFormat::LLVMIR.extension(), ".ll");
assert_eq!(X86OutputFormat::SharedLib.extension(), ".so");
}
#[test]
fn test_output_format_is_binary() {
assert!(X86OutputFormat::Object.is_binary());
assert!(X86OutputFormat::LLVMBC.is_binary());
assert!(X86OutputFormat::SharedLib.is_binary());
assert!(!X86OutputFormat::Assembly.is_binary());
assert!(!X86OutputFormat::LLVMIR.is_binary());
assert!(!X86OutputFormat::PreprocessedSource.is_binary());
}
#[test]
fn test_output_format_is_text() {
assert!(X86OutputFormat::Assembly.is_text());
assert!(X86OutputFormat::LLVMIR.is_text());
assert!(X86OutputFormat::PreprocessedSource.is_text());
assert!(!X86OutputFormat::Object.is_text());
assert!(!X86OutputFormat::LLVMBC.is_text());
}
#[test]
fn test_output_format_required_stages_assembly() {
let stages = X86OutputFormat::Assembly.required_stages();
assert!(stages.contains(&PipelineStage::Preprocess));
assert!(stages.contains(&PipelineStage::Sema));
assert!(stages.contains(&PipelineStage::IRGen));
assert!(stages.contains(&PipelineStage::ISel));
assert!(stages.contains(&PipelineStage::Encode));
assert!(!stages.contains(&PipelineStage::Emit));
}
#[test]
fn test_output_format_required_stages_preprocess_only() {
let stages = X86OutputFormat::PreprocessedSource.required_stages();
assert_eq!(stages.len(), 1);
assert_eq!(stages[0], PipelineStage::Preprocess);
}
#[test]
fn test_builtin_handler_creation() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
assert!(!handler.has_sse);
assert!(!handler.has_sse2);
assert!(!handler.has_errors());
}
#[test]
fn test_builtin_handler_registration() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
assert!(handler.builtins.len() > 10);
}
#[test]
fn test_builtin_lookup() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
assert!(handler.lookup("__builtin_ia32_rdtsc").is_some());
assert!(handler.lookup("__builtin_ia32_paddb").is_some());
assert!(handler.lookup("__builtin_nonexistent").is_none());
}
#[test]
fn test_builtin_availability() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let mut handler = X86BuiltinHandler::new(context, builder, module);
assert!(handler.is_builtin_available("__builtin_ia32_rdtsc"));
assert!(!handler.is_builtin_available("__builtin_ia32_paddb"));
handler.has_sse2 = true;
assert!(handler.is_builtin_available("__builtin_ia32_paddb"));
}
#[test]
fn test_builtin_missing_feature_error() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let mut handler = X86BuiltinHandler::new(context, builder, module);
handler.error_missing_feature("__builtin_ia32_paddb", X86IsaFeature::SSE2);
assert!(handler.has_errors());
assert_eq!(handler.errors.len(), 1);
assert!(handler.errors[0].contains("SSE2"));
}
#[test]
fn test_builtin_intrinsic_mapping() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
assert_eq!(
handler.map_builtin_to_intrinsic("__builtin_ia32_rdtsc"),
Some("llvm.x86.rdtsc".into())
);
assert_eq!(
handler.map_builtin_to_intrinsic("__builtin_ia32_addps"),
Some("llvm.x86.sse.add.ps".into())
);
assert_eq!(
handler.map_builtin_to_intrinsic("__builtin_nonexistent"),
None
);
}
#[test]
fn test_isel_bridge_creation() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86ISelBridge::new(tm);
assert!(!bridge.has_errors());
assert!(bridge.machine_functions.is_empty());
}
#[test]
fn test_isel_bridge_select_empty_module() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bridge = X86ISelBridge::new(tm);
let module = Module::new("empty");
let result = bridge.select_module(&module);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_regalloc_bridge_creation() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86RegAllocBridge::new(tm);
assert!(!bridge.has_errors());
assert_eq!(bridge.spill_count, 0);
assert_eq!(bridge.coalesce_count, 0);
}
#[test]
fn test_regalloc_empty_input() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bridge = X86RegAllocBridge::new(tm);
let result = bridge.allocate(&[]);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_frame_lowering_bridge_creation() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86FrameLoweringBridge::new(tm, CallConv::SystemV);
assert!(!bridge.has_errors());
}
#[test]
fn test_encoder_bridge_creation() {
let subtarget = X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86EncoderBridge::new(subtarget);
assert!(!bridge.has_errors());
assert_eq!(bridge.total_size, 0);
}
#[test]
fn test_encoder_bridge_empty_input() {
let subtarget = X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bridge = X86EncoderBridge::new(subtarget);
let result = bridge.encode(&[]);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_cache_creation() {
let cache = CompilationCache::new(PathBuf::from("/tmp/test-cache"));
assert!(cache.enabled);
assert_eq!(cache.hits, 0);
assert_eq!(cache.misses, 0);
}
#[test]
fn test_cache_disabled() {
let mut cache = CompilationCache::new(PathBuf::from("/tmp/test-cache"));
cache.set_enabled(false);
assert!(!cache.enabled);
let key = vec![0u8; 40];
assert!(cache.lookup(&key, PipelineStage::Preprocess).is_none());
}
#[test]
fn test_cache_hit_rate() {
let cache = CompilationCache::new(PathBuf::from("/tmp/test-cache"));
assert_eq!(cache.hit_rate(), 0.0);
}
#[test]
fn test_cache_stats_display() {
let cache = CompilationCache::new(PathBuf::from("/tmp/test-cache"));
let stats = cache.stats();
let display = format!("{}", stats);
assert!(display.contains("Cache"));
assert!(display.contains("hits=0"));
assert!(display.contains("misses=0"));
}
#[test]
fn test_error_tracker_new() {
let tracker = PipelineErrorTracker::new(10);
assert!(!tracker.has_errors());
assert_eq!(tracker.total_errors, 0);
assert_eq!(tracker.total_warnings, 0);
}
#[test]
fn test_error_tracker_record_error() {
let mut tracker = PipelineErrorTracker::new(10);
tracker.error(PipelineStage::Parse, "syntax error");
assert!(tracker.has_errors());
assert_eq!(tracker.total_errors, 1);
assert_eq!(tracker.stage_errors(PipelineStage::Parse).len(), 1);
}
#[test]
fn test_error_tracker_record_warning() {
let mut tracker = PipelineErrorTracker::new(10);
tracker.warning(PipelineStage::Sema, "unused variable");
assert!(!tracker.has_errors());
assert_eq!(tracker.total_warnings, 1);
}
#[test]
fn test_error_tracker_limit() {
let mut tracker = PipelineErrorTracker::new(3);
tracker.error(PipelineStage::Parse, "e1");
tracker.error(PipelineStage::Parse, "e2");
assert!(!tracker.limit_reached());
tracker.error(PipelineStage::Parse, "e3");
assert!(tracker.limit_reached());
}
#[test]
fn test_error_tracker_clear() {
let mut tracker = PipelineErrorTracker::new(10);
tracker.error(PipelineStage::Parse, "error");
tracker.clear();
assert!(!tracker.has_errors());
assert_eq!(tracker.total_errors, 0);
}
#[test]
fn test_pipeline_creation() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.stage_results.is_empty());
}
#[test]
fn test_pipeline_dry_run() {
let opts = X86CompileOptions::x86_64_linux();
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.make_dry_run_result();
assert!(result.success);
}
#[test]
fn test_pipeline_report_no_stages() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let report = pipeline.report();
assert!(report.contains("Clang→X86"));
assert!(report.contains("x86_64"));
}
#[test]
fn test_compute_source_hash() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let hash1 = pipeline.compute_source_hash("hello");
let hash2 = pipeline.compute_source_hash("hello");
assert_eq!(hash1, hash2);
let hash3 = pipeline.compute_source_hash("world");
assert_ne!(hash1, hash3);
}
#[test]
fn test_emit_ir_text() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let module = Module::new("test_module");
let ir = pipeline.emit_ir_text(&module);
assert!(ir.contains("test_module"));
assert!(ir.contains("target triple"));
}
#[test]
fn test_generate_assembly() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let asm = pipeline.generate_assembly();
assert!(asm.contains(".text"));
assert!(asm.contains(".globl"));
assert!(asm.contains("pushq"));
assert!(asm.contains("retq"));
}
#[test]
fn test_wrap_in_elf_object_minimal() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[0x90]); assert_eq!(&obj[0..4], &[0x7f, b'E', b'L', b'F']);
assert_eq!(obj[4], 2);
assert_eq!(obj[5], 1);
}
#[test]
fn test_compile_with_options_creation() {
let opts = X86CompileOptions::x86_64_linux_release();
let result = compile_with_options(opts, "int main() { return 0; }");
assert!(result.is_ok());
}
#[test]
fn test_compile_to_x86_ir() {
let result = compile_to_x86_ir("int main() { return 0; }");
assert!(result.is_ok());
}
#[test]
fn test_ir_generator_creation() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let r#gen = X86IRGenerator::new("test", "x86_64-unknown-linux-gnu", tm);
assert!(r#gen.is_64_bit);
assert!(r#gen.use_red_zone);
assert_eq!(r#gen.stack_alignment, 16);
}
#[test]
fn test_ir_generator_32bit() {
let tm = X86TargetMachine::new("i686-unknown-linux-gnu", "pentium4", "");
let r#gen = X86IRGenerator::new("test", "i686-unknown-linux-gnu", tm);
assert!(!r#gen.is_64_bit);
assert!(!r#gen.use_red_zone);
}
#[test]
fn test_ir_generator_configure_calling_convention() {
let tm = X86TargetMachine::new("x86_64-pc-windows-msvc", "generic", "");
let mut r#gen = X86IRGenerator::new("test", "x86_64-pc-windows-msvc", tm);
r#gen.configure_calling_convention(CallConv::Win64);
}
#[test]
fn test_empty_source_compilation() {
let opts = X86CompileOptions {
output_format: X86OutputFormat::PreprocessedSource,
..X86CompileOptions::x86_64_linux()
};
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("");
assert!(result.is_ok());
}
#[test]
fn test_minimal_c_program() {
let opts = X86CompileOptions {
output_format: X86OutputFormat::Assembly,
opt_level: X86OptLevel::O0,
..X86CompileOptions::x86_64_linux()
};
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int main() { return 0; }");
match result {
Ok(r) => {
assert!(r.success || !r.success);
}
Err(_) => {
}
}
}
#[test]
fn test_pipeline_stop_after_parse() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.stop_after = Some(PipelineStage::Parse);
opts.output_format = X86OutputFormat::Object; let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
match result {
Ok(r) => {
if let Some(last) = r.completed_stage {
assert!(last <= PipelineStage::Parse);
}
}
Err(_) => {}
}
}
#[test]
fn test_pipeline_with_cache_enabled() {
let temp_dir = std::env::temp_dir().join("llvm-native-test-cache");
let _ = fs::create_dir_all(&temp_dir);
let mut opts = X86CompileOptions::x86_64_linux();
opts.enable_cache = true;
opts.cache_dir = Some(temp_dir.clone());
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("#define X 1\nint x = X;");
assert!(result.is_ok());
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_convenience_functions_dont_panic() {
let _ = compile_to_x86_object("int main(){}", Path::new("/tmp/test.o"));
let _ = compile_to_x86_assembly("int main(){}");
let _ = compile_to_x86_ir("int main(){}");
}
#[test]
fn test_x86_opt_level_roundtrip() {
for level in &[
X86OptLevel::O0,
X86OptLevel::O1,
X86OptLevel::O2,
X86OptLevel::O3,
X86OptLevel::Os,
X86OptLevel::Oz,
] {
let arg = level.to_arg();
let parsed = X86OptLevel::from_str(&arg[1..]); assert_eq!(parsed, Some(*level), "Failed for {:?}", level);
}
}
#[test]
fn test_output_format_roundtrip() {
for (fmt, str_repr) in &[
(X86OutputFormat::PreprocessedSource, "preprocess"),
(X86OutputFormat::LLVMIR, "llvm-ir"),
(X86OutputFormat::LLVMBC, "bitcode"),
(X86OutputFormat::Assembly, "assembly"),
(X86OutputFormat::Object, "object"),
(X86OutputFormat::SharedLib, "shared"),
(X86OutputFormat::Executable, "executable"),
] {
assert_eq!(X86OutputFormat::from_str(str_repr), Some(*fmt));
}
}
#[test]
fn test_diagnostic_pipeline_creation() {
let opts = X86CompileOptions {
werror: true,
pedantic: true,
wall: true,
error_limit: 50,
..X86CompileOptions::default()
};
let diag = X86DiagnosticPipeline::new(&opts);
assert!(diag.diag_opts.warnings_as_errors);
}
#[test]
fn test_diagnostic_emit_error() {
let opts = X86CompileOptions::default();
let mut diag = X86DiagnosticPipeline::new(&opts);
diag.emit_error(PipelineStage::Lex, "test error");
assert!(diag.error_tracker.has_errors());
assert_eq!(diag.error_tracker.total_errors, 1);
}
#[test]
fn test_diagnostic_emit_warning() {
let opts = X86CompileOptions::default();
let mut diag = X86DiagnosticPipeline::new(&opts);
diag.emit_warning(PipelineStage::Parse, "test warning");
assert!(!diag.error_tracker.has_errors());
assert_eq!(diag.error_tracker.total_warnings, 1);
}
#[test]
fn test_pipeline_idempotent() {
let opts = X86CompileOptions::x86_64_linux();
let mut pipeline1 = X86Pipeline::new(opts.clone());
let mut pipeline2 = X86Pipeline::new(opts);
let result1 = pipeline1.run_from_source("int x;");
let result2 = pipeline2.run_from_source("int x;");
assert_eq!(result1.is_ok(), result2.is_ok());
}
#[test]
fn test_stage_result_with_output() {
let result = StageResult::success(PipelineStage::Encode, Duration::from_millis(1))
.with_output(vec![0x90, 0xC3], 2);
assert!(result.output_data.is_some());
assert_eq!(result.output_size, 2);
}
#[test]
fn test_stage_result_with_diagnostics() {
let result = StageResult::success(PipelineStage::Sema, Duration::from_millis(5))
.with_diagnostics(0, 3, 1);
assert_eq!(result.warning_count, 3);
assert_eq!(result.note_count, 1);
assert_eq!(result.error_count, 0);
}
#[test]
fn test_tls_model_from_str() {
assert_eq!(
TlsModel::from_str("global-dynamic"),
Some(TlsModel::GeneralDynamic)
);
assert_eq!(TlsModel::from_str("gd"), Some(TlsModel::GeneralDynamic));
assert_eq!(
TlsModel::from_str("local-dynamic"),
Some(TlsModel::LocalDynamic)
);
assert_eq!(
TlsModel::from_str("initial-exec"),
Some(TlsModel::InitialExec)
);
assert_eq!(TlsModel::from_str("local-exec"), Some(TlsModel::LocalExec));
assert_eq!(TlsModel::from_str("invalid"), None);
}
#[test]
fn test_x86_isa_feature_descriptions() {
assert_eq!(X86IsaFeature::Base.description(), "base x86");
assert_eq!(X86IsaFeature::SSE.description(), "SSE");
assert_eq!(X86IsaFeature::SSE2.description(), "SSE2");
assert_eq!(X86IsaFeature::AVX.description(), "AVX");
assert_eq!(X86IsaFeature::AVX2.description(), "AVX2");
assert_eq!(X86IsaFeature::AVX512F.description(), "AVX-512 Foundation");
assert_eq!(X86IsaFeature::FMA.description(), "FMA");
}
#[test]
fn test_all_stages_in_order() {
let stages = PipelineStage::all();
for i in 0..stages.len() - 1 {
assert!(
stages[i] < stages[i + 1],
"Stage order violation at index {}",
i
);
}
}
#[test]
fn test_partial_ord_consistent_with_next() {
let stages = PipelineStage::all();
for s in &stages {
if let Some(next) = s.next() {
assert!(s < &next, "{:?} should be < {:?}", s, next);
}
}
}
#[test]
fn test_stage_display_matches_name() {
for stage in PipelineStage::all() {
assert_eq!(format!("{}", stage), stage.name());
}
}
#[test]
fn test_default_is_not_32_bit() {
let opts = X86CompileOptions::default();
assert!(!opts.is_32_bit());
}
#[test]
fn test_default_is_64_bit() {
let opts = X86CompileOptions::default();
assert!(opts.is_64_bit());
}
#[test]
fn test_default_data_layout() {
let opts = X86CompileOptions::default();
let dl = opts.data_layout_string();
assert!(dl.contains("e-")); assert!(dl.contains("p:64:64")); }
#[test]
fn test_32bit_data_layout() {
let opts = X86CompileOptions::x86_32_linux();
let dl = opts.data_layout_string();
assert!(dl.contains("p:32:32")); }
#[test]
fn test_cache_key_determinism_across_roundtrips() {
let opts1 = X86CompileOptions::x86_64_linux();
let opts2 = X86CompileOptions::from_clang_options(&opts1.to_clang_options());
let source_hash = [42u8; 32];
let _key1 = opts1.cache_key(&source_hash);
let _key2 = opts2.cache_key(&source_hash);
}
#[test]
fn test_stage_result_one_line_success() {
let r = StageResult::success(PipelineStage::IRGen, Duration::from_millis(500))
.with_summary("42 functions");
let line = r.one_line();
assert!(line.contains("\u{2713}")); assert!(line.contains("IRGen"));
assert!(line.contains("42 functions"));
}
#[test]
fn test_stage_result_one_line_failure() {
let r = StageResult::failure(
PipelineStage::Parse,
Duration::from_millis(12),
3,
0,
"syntax error at line 42",
);
let line = r.one_line();
assert!(line.contains("\u{2717}")); assert!(line.contains("Parse"));
assert!(line.contains("syntax error"));
assert!(line.contains("errors=3"));
}
#[test]
fn test_stage_result_with_output_preserves_success() {
let r = StageResult::success(PipelineStage::Encode, Duration::from_millis(2))
.with_output(vec![0x90, 0xC3], 2);
assert!(r.success);
assert_eq!(r.output_data, Some(vec![0x90, 0xC3]));
assert_eq!(r.output_size, 2);
}
#[test]
fn test_pipeline_result_with_module() {
let module = Module::new("test");
let stage_results = vec![StageResult::success(
PipelineStage::IRGen,
Duration::from_millis(1),
)];
let result =
PipelineResult::success_with(stage_results, Duration::from_millis(1), None, None, None)
.with_module(module);
assert!(result.module.is_some());
}
#[test]
fn test_pipeline_result_with_machine_functions() {
let mfs = vec![MachineFunction::new("func1")];
let stage_results = vec![StageResult::success(
PipelineStage::ISel,
Duration::from_millis(1),
)];
let result =
PipelineResult::success_with(stage_results, Duration::from_millis(1), None, None, None)
.with_machine_functions(mfs);
assert_eq!(result.machine_functions.len(), 1);
}
#[test]
fn test_error_tracker_stage_isolation() {
let mut tracker = PipelineErrorTracker::new(10);
tracker.error(PipelineStage::Parse, "parse error");
tracker.error(PipelineStage::Sema, "type error");
assert_eq!(tracker.stage_errors(PipelineStage::Parse).len(), 1);
assert_eq!(tracker.stage_errors(PipelineStage::Sema).len(), 1);
assert_eq!(tracker.stage_errors(PipelineStage::Lex).len(), 0);
}
#[test]
fn test_error_tracker_multiple_same_stage() {
let mut tracker = PipelineErrorTracker::new(10);
tracker.error(PipelineStage::Parse, "e1");
tracker.error(PipelineStage::Parse, "e2");
tracker.error(PipelineStage::Parse, "e3");
assert_eq!(tracker.total_errors, 3);
assert_eq!(tracker.stage_errors(PipelineStage::Parse).len(), 3);
}
#[test]
fn test_error_tracker_limit_edge() {
let mut tracker = PipelineErrorTracker::new(1);
assert!(!tracker.limit_reached());
tracker.error(PipelineStage::Lex, "e1");
assert!(tracker.limit_reached());
}
#[test]
fn test_error_tracker_warning_count() {
let mut tracker = PipelineErrorTracker::new(10);
tracker.warning(PipelineStage::Parse, "w1");
tracker.warning(PipelineStage::Parse, "w2");
assert_eq!(tracker.total_warnings, 2);
assert_eq!(tracker.stage_warnings(PipelineStage::Parse).len(), 2);
}
#[test]
fn test_cache_lookup_disabled() {
let mut cache = CompilationCache::new(PathBuf::from("/tmp/test-disabled"));
cache.set_enabled(false);
let key = vec![1u8; 40];
assert!(cache.lookup(&key, PipelineStage::Preprocess).is_none());
assert_eq!(cache.misses, 1);
}
#[test]
fn test_cache_store_disabled() {
let mut cache = CompilationCache::new(PathBuf::from("/tmp/test-disabled2"));
cache.set_enabled(false);
let key = vec![2u8; 40];
let result = cache.store(&key, PipelineStage::IRGen, b"test ir");
assert!(result.is_ok());
assert_eq!(cache.current_size_bytes, 0);
}
#[test]
fn test_cache_hit_rate_zero() {
let cache = CompilationCache::new(PathBuf::from("/tmp/test-zerorate"));
assert!((cache.hit_rate() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_cache_stats_display_enabled() {
let mut cache = CompilationCache::new(PathBuf::from("/tmp/test-stats"));
cache.set_enabled(true);
let stats = cache.stats();
let display = format!("{}", stats);
assert!(display.contains("enabled"));
}
#[test]
fn test_cache_max_size_default() {
let cache = CompilationCache::new(PathBuf::from("/tmp/test-maxsize"));
assert_eq!(cache.max_size_bytes, 512 * 1024 * 1024);
}
#[test]
fn test_cache_set_max_size() {
let mut cache = CompilationCache::new(PathBuf::from("/tmp/test-maxsize2"));
cache.set_max_size(1024 * 1024 * 1024);
assert_eq!(cache.max_size_bytes, 1024 * 1024 * 1024);
}
#[test]
fn test_output_format_from_str_case_sensitive() {
assert_eq!(
X86OutputFormat::from_str("S"),
Some(X86OutputFormat::Assembly)
);
assert_eq!(X86OutputFormat::from_str("s"), None);
assert_eq!(X86OutputFormat::from_str("AsM"), None);
}
#[test]
fn test_output_format_extension_no_dot_for_exe() {
assert_eq!(X86OutputFormat::Executable.extension(), "");
}
#[test]
fn test_output_format_executable_not_text() {
assert!(!X86OutputFormat::Executable.is_text());
assert!(X86OutputFormat::Executable.is_binary());
}
#[test]
fn test_output_format_all_variants_have_extensions() {
for fmt in &[
X86OutputFormat::PreprocessedSource,
X86OutputFormat::LLVMIR,
X86OutputFormat::LLVMBC,
X86OutputFormat::Assembly,
X86OutputFormat::Object,
X86OutputFormat::SharedLib,
X86OutputFormat::Executable,
] {
let ext = fmt.extension();
assert!(
!ext.starts_with('.'),
"Extension for {:?} starts with dot",
fmt
);
}
}
#[test]
fn test_opt_level_to_codegen_roundtrip() {
let mapping = vec![
(X86OptLevel::O0, CodeGenOptLevel::None),
(X86OptLevel::O1, CodeGenOptLevel::Less),
(X86OptLevel::O2, CodeGenOptLevel::Default),
(X86OptLevel::O3, CodeGenOptLevel::Aggressive),
(X86OptLevel::Os, CodeGenOptLevel::Aggressive),
(X86OptLevel::Oz, CodeGenOptLevel::Aggressive),
];
for (opt, expected) in mapping {
assert_eq!(opt.to_codegen_opt_level(), expected, "{:?}", opt);
}
}
#[test]
fn test_opt_level_to_optimization_level_all() {
assert_eq!(
X86OptLevel::O0.to_optimization_level(),
OptimizationLevel::O0
);
assert_eq!(
X86OptLevel::O1.to_optimization_level(),
OptimizationLevel::O1
);
assert_eq!(
X86OptLevel::O2.to_optimization_level(),
OptimizationLevel::O2
);
assert_eq!(
X86OptLevel::O3.to_optimization_level(),
OptimizationLevel::O3
);
assert_eq!(
X86OptLevel::Os.to_optimization_level(),
OptimizationLevel::Os
);
assert_eq!(
X86OptLevel::Oz.to_optimization_level(),
OptimizationLevel::Oz
);
}
#[test]
fn test_opt_level_display() {
let levels = [
(X86OptLevel::O0, "O0"),
(X86OptLevel::O1, "O1"),
(X86OptLevel::O2, "O2"),
(X86OptLevel::O3, "O3"),
(X86OptLevel::Os, "Os"),
(X86OptLevel::Oz, "Oz"),
];
for (level, expected) in levels {
assert_eq!(format!("{}", level), expected);
}
}
#[test]
fn test_builtin_handler_all_base_builtins_available() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
let base_builtins = [
"__builtin_ia32_rdtsc",
"__builtin_ia32_rdtscp",
"__builtin_ia32_cpuid",
"__builtin_ia32_xgetbv",
];
for name in base_builtins {
assert!(
handler.is_builtin_available(name),
"Base builtin '{}' should be available",
name
);
}
}
#[test]
fn test_builtin_handler_feature_gating() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let mut handler = X86BuiltinHandler::new(context, builder, module);
assert!(!handler.is_builtin_available("__builtin_ia32_vpaddb256"));
handler.has_avx2 = true;
assert!(handler.is_builtin_available("__builtin_ia32_vpaddb256"));
}
#[test]
fn test_builtin_handler_feature_chain() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let mut handler = X86BuiltinHandler::new(context, builder, module);
handler.has_sse = true;
handler.has_sse2 = true;
handler.has_sse3 = true;
handler.has_ssse3 = true;
handler.has_sse41 = true;
handler.has_sse42 = true;
assert!(handler.is_builtin_available("__builtin_ia32_addps"));
assert!(handler.is_builtin_available("__builtin_ia32_paddb"));
assert!(handler.is_builtin_available("__builtin_ia32_haddps"));
assert!(handler.is_builtin_available("__builtin_ia32_pshufb"));
assert!(handler.is_builtin_available("__builtin_ia32_blendvps"));
assert!(handler.is_builtin_available("__builtin_ia32_crc32b"));
}
#[test]
fn test_builtin_handler_intrinsic_mapping_coverage() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
let builtins_with_mappings = vec![
"__builtin_ia32_rdtsc",
"__builtin_ia32_lzcnt_u32",
"__builtin_ia32_popcnt_u32",
"__builtin_ia32_addps",
"__builtin_ia32_subps",
"__builtin_ia32_mulps",
"__builtin_ia32_divps",
"__builtin_ia32_addpd",
"__builtin_ia32_paddb",
"__builtin_ia32_haddps",
"__builtin_ia32_pabsb",
"__builtin_ia32_pshufb",
];
for builtin_name in builtins_with_mappings {
let mapping = handler.map_builtin_to_intrinsic(builtin_name);
assert!(
mapping.is_some(),
"Builtin '{}' should have an intrinsic mapping",
builtin_name
);
}
}
#[test]
fn test_builtin_handler_builtin_count() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
let count = handler.builtins.len();
assert!(count >= 50, "Expected at least 50 builtins, got {}", count);
}
#[test]
fn test_isel_bridge_select_empty_module_no_errors() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bridge = X86ISelBridge::new(tm);
let module = Module::new("empty");
let result = bridge.select_module(&module);
assert!(result.is_ok());
let mfs = result.unwrap();
assert!(mfs.is_empty());
}
#[test]
fn test_isel_bridge_32bit_target() {
let tm = X86TargetMachine::new("i686-unknown-linux-gnu", "pentium4", "");
let bridge = X86ISelBridge::new(tm);
assert!(!bridge.target_machine.is_64_bit());
}
#[test]
fn test_isel_bridge_has_no_errors_initially() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86ISelBridge::new(tm);
assert!(!bridge.has_errors());
}
#[test]
fn test_regalloc_bridge_empty_works() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bridge = X86RegAllocBridge::new(tm);
let result = bridge.allocate(&[]);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_regalloc_bridge_stats_default() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86RegAllocBridge::new(tm);
let (spills, reloads, coalesces) = bridge.stats();
assert_eq!(spills, 0);
assert_eq!(reloads, 0);
assert_eq!(coalesces, 0);
}
#[test]
fn test_regalloc_bridge_no_errors_initially() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let bridge = X86RegAllocBridge::new(tm);
assert!(!bridge.has_errors());
}
#[test]
fn test_frame_lowering_empty_input() {
let tm = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
let mut bridge = X86FrameLoweringBridge::new(tm, CallConv::SystemV);
let result = bridge.lower(&[]);
assert!(result.is_ok());
let (mfs, infos) = result.unwrap();
assert!(mfs.is_empty());
assert!(infos.is_empty());
}
#[test]
fn test_frame_lowering_win64_callconv() {
let tm = X86TargetMachine::new("x86_64-pc-windows-msvc", "generic", "");
let bridge = X86FrameLoweringBridge::new(tm, CallConv::Win64);
assert!(!bridge.has_errors());
}
#[test]
fn test_pipeline_report_contains_target_info() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let report = pipeline.report();
assert!(report.contains("Target:"));
assert!(report.contains("CPU:"));
assert!(report.contains("Features:"));
assert!(report.contains("Opt Level:"));
assert!(report.contains("Output:"));
}
#[test]
fn test_pipeline_result_format_report_has_borders() {
let result =
PipelineResult::success_with(vec![], Duration::from_millis(1), None, None, None);
let report = result.format_report();
assert!(report.contains("\u{2550}")); assert!(report.contains("Clang\u{2192}X86"));
}
#[test]
fn test_pipeline_result_format_report_failure() {
let stage_results = vec![StageResult::failure(
PipelineStage::Lex,
Duration::from_millis(1),
1,
0,
"lex failure",
)];
let result = PipelineResult::failure(
PipelineStage::Lex,
stage_results,
Duration::from_millis(1),
"compilation failed",
);
let report = result.format_report();
assert!(report.contains("FAILURE"));
assert!(!report.contains("SUCCESS"));
}
#[test]
fn test_pipeline_stress_many_defines() {
let mut opts = X86CompileOptions::x86_64_linux();
for i in 0..100 {
opts.defines
.push((format!("MACRO_{}", i), Some(format!("{}", i))));
}
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x = MACRO_0 + MACRO_99;");
assert!(result.is_ok());
}
#[test]
fn test_pipeline_minimal_program_all_stages() {
let opts = X86CompileOptions {
output_format: X86OutputFormat::Assembly,
opt_level: X86OptLevel::O0,
..X86CompileOptions::x86_64_linux()
};
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
match result {
Ok(r) => {
let _ = r.format_report();
}
Err(_) => {
}
}
}
#[test]
fn test_dry_run_respects_stop_after() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.dry_run = true;
opts.stop_after = Some(PipelineStage::Sema);
opts.output_format = X86OutputFormat::Object;
let pipeline = X86Pipeline::new(opts);
let result = pipeline.make_dry_run_result();
assert!(result.success);
for sr in &result.stage_results {
assert!(sr.stage <= PipelineStage::Sema);
assert!(sr.summary.contains("dry run"));
}
}
#[test]
fn test_pipeline_32bit_target() {
let mut opts = X86CompileOptions::x86_32_linux();
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
assert!(result.is_ok());
}
#[test]
fn test_pipeline_with_cache_and_no_cache_dir() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.enable_cache = true;
opts.cache_dir = None; opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
assert!(result.is_ok());
let _ = std::fs::remove_dir_all(".llvm-native-cache");
}
#[test]
fn test_builtin_intrinsic_mapping_exhaustive() {
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
let module = Module::new("test");
let handler = X86BuiltinHandler::new(context, builder, module);
let checks = vec![
("__builtin_ia32_rdtsc", "llvm.x86.rdtsc"),
("__builtin_ia32_lzcnt_u32", "llvm.ctlz.i32"),
("__builtin_ia32_lzcnt_u64", "llvm.ctlz.i64"),
("__builtin_ia32_popcnt_u32", "llvm.ctpop.i32"),
("__builtin_ia32_popcnt_u64", "llvm.ctpop.i64"),
("__builtin_ia32_tzcnt_u32", "llvm.cttz.i32"),
("__builtin_ia32_tzcnt_u64", "llvm.cttz.i64"),
("__builtin_ia32_addps", "llvm.x86.sse.add.ps"),
("__builtin_ia32_subps", "llvm.x86.sse.sub.ps"),
("__builtin_ia32_addpd", "llvm.x86.sse2.add.pd"),
("__builtin_ia32_paddb", "llvm.x86.sse2.padd.b"),
("__builtin_ia32_haddps", "llvm.x86.sse3.hadd.ps"),
("__builtin_ia32_haddpd", "llvm.x86.sse3.hadd.pd"),
("__builtin_ia32_pabsb", "llvm.x86.ssse3.pabs.b"),
("__builtin_ia32_pshufb", "llvm.x86.ssse3.pshuf.b"),
("__builtin_ia32_lfence", "llvm.x86.sse2.lfence"),
("__builtin_ia32_mfence", "llvm.x86.sse2.mfence"),
("__builtin_ia32_sfence", "llvm.x86.sse.sfence"),
("__builtin_ia32_pause", "llvm.x86.sse2.pause"),
];
for (builtin, expected_intrinsic) in checks {
assert_eq!(
handler.map_builtin_to_intrinsic(builtin),
Some(expected_intrinsic.into()),
"Mismatch for '{}'",
builtin
);
}
}
#[test]
fn test_pipeline_verbose_flag() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.verbose = true;
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
assert!(result.is_ok());
}
#[test]
fn test_pipeline_asan_flag() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.asan = true;
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.options.asan);
}
#[test]
fn test_pipeline_sanitizer_flags() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.asan = true;
opts.ubsan = true;
opts.tsan = true;
opts.msan = true;
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.options.asan);
assert!(pipeline.options.ubsan);
assert!(pipeline.options.tsan);
assert!(pipeline.options.msan);
}
#[test]
fn test_pipeline_profile_flags() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.coverage = true;
opts.profile_instr_generate = true;
opts.profile_instr_use = Some(PathBuf::from("profile.profdata"));
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.options.coverage);
assert!(pipeline.options.profile_instr_generate);
assert!(pipeline.options.profile_instr_use.is_some());
}
#[test]
fn test_elf_wrapper_contains_text_section() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[0x90, 0x90]);
assert_eq!(&obj[0..4], &[0x7f, b'E', b'L', b'F']);
let code_start = obj.len() - 2;
assert_eq!(&obj[code_start..], &[0x90, 0x90]);
}
#[test]
fn test_stage_result_with_diagnostics_chain() {
let r = StageResult::success(PipelineStage::Sema, Duration::from_millis(42))
.with_diagnostics(0, 5, 3)
.with_summary("5 warnings, 3 notes")
.with_output(vec![], 0);
assert!(r.success);
assert_eq!(r.warning_count, 5);
assert_eq!(r.note_count, 3);
assert!(r.summary.contains("5 warnings"));
}
#[test]
fn test_debug_info_kind_has_debug() {
assert!(!DebugInfoKind::None.has_debug());
assert!(DebugInfoKind::LineTablesOnly.has_debug());
assert!(DebugInfoKind::Limited.has_debug());
assert!(DebugInfoKind::Full.has_debug());
assert!(DebugInfoKind::FullWithMacros.has_debug());
assert!(DebugInfoKind::CodeView.has_debug());
}
#[test]
fn test_debug_info_kind_has_types() {
assert!(!DebugInfoKind::None.has_types());
assert!(!DebugInfoKind::LineTablesOnly.has_types());
assert!(!DebugInfoKind::Limited.has_types());
assert!(DebugInfoKind::Full.has_types());
assert!(DebugInfoKind::FullWithMacros.has_types());
assert!(!DebugInfoKind::CodeView.has_types());
}
#[test]
fn test_elf_header_is_64bit() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[]);
assert_eq!(obj[4], 2, "ELF should be 64-bit");
}
#[test]
fn test_elf_header_little_endian() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[]);
assert_eq!(obj[5], 1, "ELF should be little-endian");
}
#[test]
fn test_elf_header_version() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[]);
assert_eq!(obj[6], 1, "ELF version should be 1");
}
#[test]
fn test_elf_header_osabi_sysv() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[]);
assert_eq!(obj[7], 0, "ELF OSABI should be System V");
}
#[test]
fn test_elf_machine_is_x86_64() {
let opts = X86CompileOptions::x86_64_linux();
let pipeline = X86Pipeline::new(opts);
let obj = pipeline.wrap_in_elf_object(&[]);
let machine = u16::from_le_bytes([obj[18], obj[19]]);
assert_eq!(machine, 0x3E, "Machine type should be x86-64 (0x3E)");
}
#[test]
fn test_end_to_end_trivial_c_program() {
let source = "int main(void) { return 0; }";
let opts = X86CompileOptions {
output_format: X86OutputFormat::Assembly,
opt_level: X86OptLevel::O0,
language_standard: CLangStandard::C99,
..X86CompileOptions::x86_64_linux()
};
let mut pipeline = X86Pipeline::new(opts);
let _ = pipeline.run_from_source(source);
}
#[test]
fn test_end_to_end_with_includes() {
let source = "int x = 1;";
let mut opts = X86CompileOptions::x86_64_linux();
opts.includes.push("/usr/include".into());
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source(source);
assert!(result.is_ok());
}
#[test]
fn test_end_to_end_with_many_defines() {
let source = "int x = VALUE;";
let mut opts = X86CompileOptions::x86_64_linux();
opts.defines.push(("VALUE".into(), Some("42".into())));
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source(source);
assert!(result.is_ok());
}
#[test]
fn test_all_opt_levels_construct_pipeline() {
let levels = [
X86OptLevel::O0,
X86OptLevel::O1,
X86OptLevel::O2,
X86OptLevel::O3,
X86OptLevel::Os,
X86OptLevel::Oz,
];
for &level in &levels {
let mut opts = X86CompileOptions::x86_64_linux();
opts.opt_level = level;
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
assert!(result.is_ok(), "Failed for opt level {:?}", level);
}
}
#[test]
fn test_all_output_formats_construct() {
let formats = [
X86OutputFormat::PreprocessedSource,
X86OutputFormat::LLVMIR,
X86OutputFormat::LLVMBC,
X86OutputFormat::Assembly,
X86OutputFormat::Object,
X86OutputFormat::SharedLib,
X86OutputFormat::Executable,
];
for &format in &formats {
let mut opts = X86CompileOptions::x86_64_linux();
opts.output_format = format;
let pipeline = X86Pipeline::new(opts);
assert_eq!(pipeline.options.output_format, format);
}
}
#[test]
fn test_pipeline_with_c11_standard() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.language_standard = CLangStandard::C11;
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int _Generic(0, int: 1, default: 0) x;");
assert!(result.is_ok());
}
#[test]
fn test_pipeline_with_c23_standard() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.language_standard = CLangStandard::C23;
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
assert!(result.is_ok());
}
#[test]
fn test_pipeline_gnu_extensions() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.gnu_extensions = true;
opts.language_standard = CLangStandard::Gnu11;
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
assert!(result.is_ok());
}
#[test]
fn test_pipeline_error_limit_respected() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.error_limit = 5;
let pipeline = X86Pipeline::new(opts);
assert_eq!(pipeline.diagnostics.error_tracker.error_limit, 5);
}
#[test]
fn test_pipeline_with_pic() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.pic = true;
opts.reloc_model = RelocModel::PIC;
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.target_machine.is_pic());
}
#[test]
fn test_pipeline_with_code_model_large() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.code_model = CodeModel::Large;
let pipeline = X86Pipeline::new(opts);
assert_eq!(pipeline.target_machine.code_model, CodeModel::Large);
}
#[test]
fn test_pipeline_static_linking_flag() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.static_linking = true;
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.options.static_linking);
}
#[test]
fn test_pipeline_shared_library_flag() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.shared = true;
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.options.shared);
}
#[test]
fn test_pipeline_opt_level_o0_no_optimize() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.opt_level = X86OptLevel::O0;
let pipeline = X86Pipeline::new(opts);
assert!(!pipeline.options.opt_level.is_optimizing());
}
#[test]
fn test_pipeline_opt_level_o3_is_optimizing() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.opt_level = X86OptLevel::O3;
let pipeline = X86Pipeline::new(opts);
assert!(pipeline.options.opt_level.is_optimizing());
}
#[test]
fn test_all_isa_features_have_descriptions() {
let features = [
X86IsaFeature::Base,
X86IsaFeature::MMX,
X86IsaFeature::SSE,
X86IsaFeature::SSE2,
X86IsaFeature::SSE3,
X86IsaFeature::SSSE3,
X86IsaFeature::SSE41,
X86IsaFeature::SSE42,
X86IsaFeature::AVX,
X86IsaFeature::AVX2,
X86IsaFeature::AVX512F,
X86IsaFeature::AVX512CD,
X86IsaFeature::AVX512BW,
X86IsaFeature::AVX512DQ,
X86IsaFeature::AVX512VL,
X86IsaFeature::FMA,
X86IsaFeature::BMI,
X86IsaFeature::BMI2,
X86IsaFeature::LZCNT,
X86IsaFeature::POPCNT,
X86IsaFeature::RDRAND,
X86IsaFeature::RDSEED,
X86IsaFeature::AES,
X86IsaFeature::SHA,
X86IsaFeature::ADX,
X86IsaFeature::SGX,
X86IsaFeature::CET,
X86IsaFeature::MPX,
X86IsaFeature::AMX,
];
for feature in features {
let desc = feature.description();
assert!(
!desc.is_empty(),
"Feature {:?} has empty description",
feature
);
}
}
#[test]
fn test_x86_64_stack_alignment() {
assert_eq!(X86_STACK_ALIGNMENT_64, 16);
}
#[test]
fn test_x86_32_stack_alignment() {
assert_eq!(X86_STACK_ALIGNMENT_32, 16);
}
#[test]
fn test_x86_max_alignment() {
assert_eq!(X86_MAX_ALIGNMENT, 64);
}
#[test]
fn test_x86_red_zone_size() {
assert_eq!(X86_RED_ZONE_SIZE_64, 128);
}
#[test]
fn test_x86_page_size() {
assert_eq!(X86_PAGE_SIZE, 4096);
}
#[test]
fn test_pipeline_result_default_is_consistent() {
let r1 = PipelineResult::default();
let r2 = PipelineResult::default();
assert_eq!(r1.success, r2.success);
assert_eq!(r1.completed_stage, r2.completed_stage);
assert_eq!(r1.total_errors, r2.total_errors);
}
#[test]
fn test_stage_result_ordering_in_pipeline() {
let mut opts = X86CompileOptions::x86_64_linux();
opts.output_format = X86OutputFormat::PreprocessedSource;
let mut pipeline = X86Pipeline::new(opts);
let result = pipeline.run_from_source("int x;");
if let Ok(r) = result {
for i in 1..r.stage_results.len() {
assert!(
r.stage_results[i - 1].stage <= r.stage_results[i].stage,
"Stage results out of order: {:?} before {:?}",
r.stage_results[i - 1].stage,
r.stage_results[i].stage
);
}
}
}
}
#[derive(Debug, Clone)]
pub struct CompileCommand {
pub directory: PathBuf,
pub file: PathBuf,
pub arguments: Vec<String>,
pub output: Option<PathBuf>,
}
impl CompileCommand {
pub fn to_compile_options(&self) -> X86CompileOptions {
let mut opts = X86CompileOptions::default();
opts.working_dir = Some(self.directory.clone());
let mut i = 0;
let args = &self.arguments;
while i < args.len() {
match args[i].as_str() {
"-O0" => opts.opt_level = X86OptLevel::O0,
"-O1" => opts.opt_level = X86OptLevel::O1,
"-O2" => opts.opt_level = X86OptLevel::O2,
"-O3" => opts.opt_level = X86OptLevel::O3,
"-Os" => opts.opt_level = X86OptLevel::Os,
"-Oz" => opts.opt_level = X86OptLevel::Oz,
"-m32" => opts.target_triple = "i686-unknown-linux-gnu".into(),
"-m64" => opts.target_triple = "x86_64-unknown-linux-gnu".into(),
"-Wall" => opts.wall = true,
"-Wextra" => opts.wextra = true,
"-Werror" => opts.werror = true,
"-pedantic" => opts.pedantic = true,
"-g" | "-g3" => {
opts.debug_info = true;
opts.debug_info_kind = DebugInfoKind::Full;
}
"-g0" => {
opts.debug_info = false;
opts.debug_info_kind = DebugInfoKind::None;
}
"-g1" => {
opts.debug_info = true;
opts.debug_info_kind = DebugInfoKind::LineTablesOnly;
}
"-fPIC" | "-fpic" => {
opts.pic = true;
opts.reloc_model = RelocModel::PIC;
}
"-fPIE" | "-fpie" => opts.pie = true,
"-shared" => opts.shared = true,
"-static" => opts.static_linking = true,
"-c" => opts.output_format = X86OutputFormat::Object,
"-S" => opts.output_format = X86OutputFormat::Assembly,
"-E" => opts.output_format = X86OutputFormat::PreprocessedSource,
"-v" => opts.verbose = true,
"-I" if i + 1 < args.len() => {
i += 1;
opts.includes.push(args[i].clone());
}
"-D" if i + 1 < args.len() => {
i += 1;
let def = &args[i];
opts.defines.push(if let Some(eq) = def.find('=') {
(def[..eq].into(), Some(def[eq + 1..].into()))
} else {
(def.clone(), Some("1".into()))
});
}
"-o" if i + 1 < args.len() => {
i += 1;
opts.output_file = Some(PathBuf::from(&args[i]));
}
_ => {
if args[i].starts_with("-I") && args[i].len() > 2 {
opts.includes.push(args[i][2..].to_string());
} else if args[i].starts_with("-D") && args[i].len() > 2 {
let def = &args[i][2..];
opts.defines.push(if let Some(eq) = def.find('=') {
(def[..eq].into(), Some(def[eq + 1..].into()))
} else {
(def.into(), Some("1".into()))
});
}
}
}
i += 1;
}
opts
}
}
#[derive(Debug, Clone, Default)]
pub struct CompilationDatabase {
pub commands: Vec<CompileCommand>,
pub by_file: HashMap<PathBuf, usize>,
}
impl CompilationDatabase {
pub fn new() -> Self {
Self {
commands: Vec::new(),
by_file: HashMap::new(),
}
}
pub fn add(&mut self, cmd: CompileCommand) {
let idx = self.commands.len();
self.by_file.insert(cmd.file.clone(), idx);
self.commands.push(cmd);
}
pub fn lookup(&self, file: &Path) -> Option<&CompileCommand> {
self.by_file
.get(file)
.and_then(|&idx| self.commands.get(idx))
}
pub fn source_files(&self) -> Vec<&Path> {
self.commands.iter().map(|c| c.file.as_path()).collect()
}
pub fn compile_all(
&self,
base_opts: &X86CompileOptions,
) -> Vec<Result<PipelineResult, String>> {
self.commands
.iter()
.map(|cmd| {
let mut opts = cmd.to_compile_options();
if opts.target_triple.is_empty() {
opts.target_triple = base_opts.target_triple.clone();
}
if opts.target_cpu == "generic" && base_opts.target_cpu != "generic" {
opts.target_cpu = base_opts.target_cpu.clone();
}
if opts.output_file.is_none() {
opts.output_file = base_opts.output_file.clone();
}
let source = fs::read_to_string(&cmd.file)
.map_err(|e| format!("Cannot read {}: {}", cmd.file.display(), e))?;
let mut pipeline = X86Pipeline::new(opts);
pipeline.run_from_source(&source)
})
.collect()
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct PGOProfileReader {
pub profile_path: PathBuf,
pub function_counts: HashMap<String, u64>,
pub branch_weights: HashMap<(String, u32, u32), (u64, u64)>,
pub loaded: bool,
}
impl PGOProfileReader {
pub fn new(path: PathBuf) -> Self {
Self {
profile_path: path,
function_counts: HashMap::new(),
branch_weights: HashMap::new(),
loaded: false,
}
}
pub fn load(&mut self) -> io::Result<()> {
let _data = fs::read(&self.profile_path)?;
self.loaded = true;
Ok(())
}
pub fn get_function_count(&self, name: &str) -> Option<u64> {
self.function_counts.get(name).copied()
}
pub fn get_branch_weights(
&self,
function: &str,
block_id: u32,
branch_idx: u32,
) -> Option<(u64, u64)> {
self.branch_weights
.get(&(function.into(), block_id, branch_idx))
.copied()
}
pub fn is_loaded(&self) -> bool {
self.loaded
}
}
#[derive(Debug, Clone)]
pub struct X86PassPipelineBuilder {
pub opt_level: X86OptLevel,
pub enable_inliner: bool,
pub inline_threshold: u32,
pub enable_loop_unroll: bool,
pub enable_loop_vectorize: bool,
pub enable_slp_vectorize: bool,
pub enable_gvn: bool,
pub enable_dce: bool,
pub enable_const_prop: bool,
pub enable_tail_call_elim: bool,
pub enable_merge_functions: bool,
pub use_pgo: bool,
pub custom_passes: Vec<String>,
}
impl X86PassPipelineBuilder {
pub fn new(opt_level: X86OptLevel) -> Self {
match opt_level {
X86OptLevel::O0 => Self {
opt_level,
enable_inliner: false,
inline_threshold: 0,
enable_loop_unroll: false,
enable_loop_vectorize: false,
enable_slp_vectorize: false,
enable_gvn: false,
enable_dce: true,
enable_const_prop: false,
enable_tail_call_elim: false,
enable_merge_functions: false,
use_pgo: false,
custom_passes: Vec::new(),
},
X86OptLevel::O1 => Self {
opt_level,
enable_inliner: true,
inline_threshold: 100,
enable_loop_unroll: false,
enable_loop_vectorize: false,
enable_slp_vectorize: false,
enable_gvn: true,
enable_dce: true,
enable_const_prop: true,
enable_tail_call_elim: false,
enable_merge_functions: false,
use_pgo: false,
custom_passes: Vec::new(),
},
X86OptLevel::O2 => Self {
opt_level,
enable_inliner: true,
inline_threshold: 225,
enable_loop_unroll: true,
enable_loop_vectorize: true,
enable_slp_vectorize: false,
enable_gvn: true,
enable_dce: true,
enable_const_prop: true,
enable_tail_call_elim: true,
enable_merge_functions: true,
use_pgo: false,
custom_passes: Vec::new(),
},
X86OptLevel::O3 => Self {
opt_level,
enable_inliner: true,
inline_threshold: 275,
enable_loop_unroll: true,
enable_loop_vectorize: true,
enable_slp_vectorize: true,
enable_gvn: true,
enable_dce: true,
enable_const_prop: true,
enable_tail_call_elim: true,
enable_merge_functions: true,
use_pgo: false,
custom_passes: Vec::new(),
},
X86OptLevel::Os => Self {
opt_level,
enable_inliner: true,
inline_threshold: 75,
enable_loop_unroll: false,
enable_loop_vectorize: false,
enable_slp_vectorize: false,
enable_gvn: true,
enable_dce: true,
enable_const_prop: true,
enable_tail_call_elim: true,
enable_merge_functions: true,
use_pgo: false,
custom_passes: Vec::new(),
},
X86OptLevel::Oz => Self {
opt_level,
enable_inliner: true,
inline_threshold: 25,
enable_loop_unroll: false,
enable_loop_vectorize: false,
enable_slp_vectorize: false,
enable_gvn: true,
enable_dce: true,
enable_const_prop: true,
enable_tail_call_elim: true,
enable_merge_functions: true,
use_pgo: false,
custom_passes: Vec::new(),
},
}
}
pub fn with_pgo(mut self) -> Self {
self.use_pgo = true;
self
}
pub fn add_custom_pass(mut self, pass: &str) -> Self {
self.custom_passes.push(pass.into());
self
}
pub fn pass_names(&self) -> Vec<String> {
let mut passes = Vec::new();
if self.enable_const_prop {
passes.push("constprop".into());
}
if self.enable_dce {
passes.push("dce".into());
}
if self.enable_inliner {
passes.push(format!("inline(t={})", self.inline_threshold));
}
if self.enable_gvn {
passes.push("gvn".into());
}
if self.enable_loop_unroll {
passes.push("loop-unroll".into());
}
if self.enable_loop_vectorize {
passes.push("loop-vectorize".into());
}
if self.enable_slp_vectorize {
passes.push("slp-vectorize".into());
}
if self.enable_tail_call_elim {
passes.push("tailcallelim".into());
}
if self.enable_merge_functions {
passes.push("mergefunc".into());
}
if self.use_pgo {
passes.push("pgo-instr-use".into());
}
passes.extend(self.custom_passes.clone());
passes
}
pub fn describe(&self) -> String {
format!(
"X86 Pass Pipeline ({:?}): {}",
self.opt_level,
self.pass_names().join(" \u{2192} ")
)
}
}