use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use crate::types::Type;
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum X86OptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl X86OptLevel {
pub fn as_str(&self) -> &'static str {
match self {
X86OptLevel::O0 => "O0",
X86OptLevel::O1 => "O1",
X86OptLevel::O2 => "O2",
X86OptLevel::O3 => "O3",
X86OptLevel::Os => "Os",
X86OptLevel::Oz => "Oz",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"0" | "O0" | "-O0" => Some(X86OptLevel::O0),
"1" | "O1" | "-O1" => Some(X86OptLevel::O1),
"2" | "O2" | "-O2" => Some(X86OptLevel::O2),
"3" | "O3" | "-O3" => Some(X86OptLevel::O3),
"s" | "Os" | "-Os" => Some(X86OptLevel::Os),
"z" | "Oz" | "-Oz" => Some(X86OptLevel::Oz),
_ => None,
}
}
}
impl fmt::Display for X86OptLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "-{}", self.as_str())
}
}
impl Default for X86OptLevel {
fn default() -> Self {
X86OptLevel::O0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86TargetCPU {
X86_64,
X86_64V2,
X86_64V3,
X86_64V4,
SandyBridge,
Haswell,
Skylake,
IceLake,
AlderLake,
GraniteRapids,
Zen2,
Zen3,
Zen4,
Zen5,
Native,
}
impl X86TargetCPU {
pub fn as_str(&self) -> &'static str {
match self {
X86TargetCPU::X86_64 => "x86-64",
X86TargetCPU::X86_64V2 => "x86-64-v2",
X86TargetCPU::X86_64V3 => "x86-64-v3",
X86TargetCPU::X86_64V4 => "x86-64-v4",
X86TargetCPU::SandyBridge => "sandybridge",
X86TargetCPU::Haswell => "haswell",
X86TargetCPU::Skylake => "skylake",
X86TargetCPU::IceLake => "icelake",
X86TargetCPU::AlderLake => "alderlake",
X86TargetCPU::GraniteRapids => "graniterapids",
X86TargetCPU::Zen2 => "znver2",
X86TargetCPU::Zen3 => "znver3",
X86TargetCPU::Zen4 => "znver4",
X86TargetCPU::Zen5 => "znver5",
X86TargetCPU::Native => "native",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"x86-64" | "x86_64" => Some(X86TargetCPU::X86_64),
"x86-64-v2" | "x86_64_v2" => Some(X86TargetCPU::X86_64V2),
"x86-64-v3" | "x86_64_v3" => Some(X86TargetCPU::X86_64V3),
"x86-64-v4" | "x86_64_v4" => Some(X86TargetCPU::X86_64V4),
"sandybridge" | "sandy_bridge" => Some(X86TargetCPU::SandyBridge),
"haswell" => Some(X86TargetCPU::Haswell),
"skylake" | "skylake_client" => Some(X86TargetCPU::Skylake),
"icelake" | "icelake_client" | "ice_lake" => Some(X86TargetCPU::IceLake),
"alderlake" | "alder_lake" => Some(X86TargetCPU::AlderLake),
"graniterapids" | "granite_rapids" => Some(X86TargetCPU::GraniteRapids),
"znver2" | "zen2" => Some(X86TargetCPU::Zen2),
"znver3" | "zen3" => Some(X86TargetCPU::Zen3),
"znver4" | "zen4" => Some(X86TargetCPU::Zen4),
"znver5" | "zen5" => Some(X86TargetCPU::Zen5),
"native" => Some(X86TargetCPU::Native),
_ => None,
}
}
pub fn features(&self) -> Vec<&'static str> {
let base = vec!["mmx", "sse", "sse2", "cmov", "fxr"];
match self {
X86TargetCPU::X86_64 => {
let mut f = base.clone();
f.extend_from_slice(&["sse3", "ssse3", "sse4.1", "sse4.2", "popcnt"]);
f
}
X86TargetCPU::X86_64V2 => {
let mut f = base.clone();
f.extend_from_slice(&[
"sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "aes", "pclmul",
"fsgsbase", "rdrnd",
]);
f
}
X86TargetCPU::X86_64V3 => {
let mut f = Self::X86_64V2.features();
f.extend_from_slice(&[
"avx2", "bmi", "bmi2", "f16c", "fma", "lzcnt", "movbe", "xsave", "xsaveopt",
]);
f
}
X86TargetCPU::X86_64V4 => {
let mut f = Self::X86_64V3.features();
f.extend_from_slice(&[
"avx512f",
"avx512bw",
"avx512cd",
"avx512dq",
"avx512vl",
"avx512ifma",
"avx512vbmi",
]);
f
}
X86TargetCPU::SandyBridge => {
let mut f = base.clone();
f.extend_from_slice(&[
"sse3", "ssse3", "sse4.1", "sse4.2", "popcnt", "avx", "aes", "pclmul", "xsave",
"xsaveopt",
]);
f
}
X86TargetCPU::Haswell => {
let mut f = Self::SandyBridge.features();
f.extend_from_slice(&[
"avx2", "bmi", "bmi2", "f16c", "fma", "lzcnt", "movbe", "fsgsbase", "rdrnd",
]);
f
}
X86TargetCPU::Skylake => {
let mut f = Self::Haswell.features();
f.extend_from_slice(&[
"avx512f", "avx512bw", "avx512cd", "avx512dq", "avx512vl", "clwb", "pku", "sgx",
]);
f
}
X86TargetCPU::IceLake => {
let mut f = Self::Skylake.features();
f.extend_from_slice(&[
"avx512ifma",
"avx512vbmi",
"avx512vbmi2",
"avx512vnni",
"avx512bitalg",
"avx512vpopcntdq",
"clflushopt",
"clwb",
"gfni",
"vaes",
"vpclmulqdq",
]);
f
}
X86TargetCPU::AlderLake => {
let mut f = Self::IceLake.features();
f.extend_from_slice(&[
"avx512bf16",
"avx512vp2intersect",
"avxvnni",
"serialize",
"tsxldtrk",
"waitpkg",
]);
f
}
X86TargetCPU::GraniteRapids => {
let mut f = Self::AlderLake.features();
f.extend_from_slice(&[
"amx-bf16",
"amx-complex",
"amx-fp16",
"amx-int8",
"amx-tile",
"avx512fp16",
"avxifma",
"avxneconvert",
"avxvnniint8",
"cmpccxadd",
"prefetchi",
]);
f
}
X86TargetCPU::Zen2 => {
let mut f = base.clone();
f.extend_from_slice(&[
"sse3",
"ssse3",
"sse4.1",
"sse4.2",
"popcnt",
"avx",
"avx2",
"bmi",
"bmi2",
"f16c",
"fma",
"aes",
"pclmul",
"rdrnd",
"rdseed",
"sha",
"clflushopt",
"clwb",
"xsave",
"xsaveopt",
"xsavec",
]);
f
}
X86TargetCPU::Zen3 => {
let mut f = Self::Zen2.features();
f.extend_from_slice(&["vaes", "vpclmulqdq", "invpcid", "pku"]);
f
}
X86TargetCPU::Zen4 => {
let mut f = Self::Zen3.features();
f.extend_from_slice(&[
"avx512f",
"avx512bw",
"avx512cd",
"avx512dq",
"avx512vl",
"avx512ifma",
"avx512vbmi",
"avx512vbmi2",
"avx512vnni",
"avx512bitalg",
"avx512vpopcntdq",
"avx512bf16",
"gfni",
]);
f
}
X86TargetCPU::Zen5 => {
let mut f = Self::Zen4.features();
f.extend_from_slice(&[
"avx512fp16",
"avxvnni",
"avxifma",
"avxneconvert",
"avxvnniint8",
]);
f
}
X86TargetCPU::Native => {
Self::X86_64V3.features()
}
}
}
}
impl fmt::Display for X86TargetCPU {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86OutputFormat {
ElfObject,
MachOObject,
CoffObject,
Assembly,
LlvmIr,
LlvmBc,
Preprocessed,
}
impl X86OutputFormat {
pub fn extension(&self) -> &'static str {
match self {
X86OutputFormat::ElfObject => ".o",
X86OutputFormat::MachOObject => ".o",
X86OutputFormat::CoffObject => ".obj",
X86OutputFormat::Assembly => ".s",
X86OutputFormat::LlvmIr => ".ll",
X86OutputFormat::LlvmBc => ".bc",
X86OutputFormat::Preprocessed => ".i",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86LanguageStandard {
C89,
C99,
C11,
C17,
C23,
Gnu89,
Gnu99,
Gnu11,
Gnu17,
Cpp98,
Cpp11,
Cpp14,
Cpp17,
Cpp20,
Cpp23,
GnuPp98,
GnuPp11,
GnuPp14,
GnuPp17,
GnuPp20,
}
impl X86LanguageStandard {
pub fn as_str(&self) -> &'static str {
match self {
X86LanguageStandard::C89 => "c89",
X86LanguageStandard::C99 => "c99",
X86LanguageStandard::C11 => "c11",
X86LanguageStandard::C17 => "c17",
X86LanguageStandard::C23 => "c23",
X86LanguageStandard::Gnu89 => "gnu89",
X86LanguageStandard::Gnu99 => "gnu99",
X86LanguageStandard::Gnu11 => "gnu11",
X86LanguageStandard::Gnu17 => "gnu17",
X86LanguageStandard::Cpp98 => "c++98",
X86LanguageStandard::Cpp11 => "c++11",
X86LanguageStandard::Cpp14 => "c++14",
X86LanguageStandard::Cpp17 => "c++17",
X86LanguageStandard::Cpp20 => "c++20",
X86LanguageStandard::Cpp23 => "c++23",
X86LanguageStandard::GnuPp98 => "gnu++98",
X86LanguageStandard::GnuPp11 => "gnu++11",
X86LanguageStandard::GnuPp14 => "gnu++14",
X86LanguageStandard::GnuPp17 => "gnu++17",
X86LanguageStandard::GnuPp20 => "gnu++20",
}
}
pub fn is_cxx(&self) -> bool {
matches!(
self,
X86LanguageStandard::Cpp98
| X86LanguageStandard::Cpp11
| X86LanguageStandard::Cpp14
| X86LanguageStandard::Cpp17
| X86LanguageStandard::Cpp20
| X86LanguageStandard::Cpp23
| X86LanguageStandard::GnuPp98
| X86LanguageStandard::GnuPp11
| X86LanguageStandard::GnuPp14
| X86LanguageStandard::GnuPp17
| X86LanguageStandard::GnuPp20
)
}
}
impl fmt::Display for X86LanguageStandard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RelocModel {
Static,
Pic,
Pie,
DynamicNoPic,
Ropi,
Rwpi,
}
impl X86RelocModel {
pub fn as_str(&self) -> &'static str {
match self {
X86RelocModel::Static => "static",
X86RelocModel::Pic => "pic",
X86RelocModel::Pie => "pie",
X86RelocModel::DynamicNoPic => "dynamic-no-pic",
X86RelocModel::Ropi => "ropi",
X86RelocModel::Rwpi => "rwpi",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CodeModel {
Small,
Kernel,
Medium,
Large,
Tiny,
}
impl X86CodeModel {
pub fn as_str(&self) -> &'static str {
match self {
X86CodeModel::Small => "small",
X86CodeModel::Kernel => "kernel",
X86CodeModel::Medium => "medium",
X86CodeModel::Large => "large",
X86CodeModel::Tiny => "tiny",
}
}
}
#[derive(Debug, Clone)]
pub struct X86E2EOptions {
pub opt_level: X86OptLevel,
pub target_cpu: X86TargetCPU,
pub output_format: X86OutputFormat,
pub language: X86LanguageStandard,
pub reloc_model: X86RelocModel,
pub code_model: X86CodeModel,
pub debug_info: bool,
pub wall: bool,
pub werror: bool,
pub include_paths: Vec<PathBuf>,
pub defines: HashMap<String, Option<String>>,
pub extra_flags: Vec<String>,
pub output_file: Option<PathBuf>,
pub jobs: usize,
pub lto: bool,
pub thin_lto: bool,
pub stack_protector: StackProtectorMode,
pub sanitizers: Vec<SanitizerKind>,
pub frame_pointers: FramePointerMode,
pub target_triple: Option<String>,
pub template_depth: usize,
pub constexpr_steps: usize,
}
impl Default for X86E2EOptions {
fn default() -> Self {
Self {
opt_level: X86OptLevel::O2,
target_cpu: X86TargetCPU::X86_64V3,
output_format: X86OutputFormat::ElfObject,
language: X86LanguageStandard::C17,
reloc_model: X86RelocModel::Pic,
code_model: X86CodeModel::Small,
debug_info: false,
wall: true,
werror: false,
include_paths: Vec::new(),
defines: HashMap::new(),
extra_flags: Vec::new(),
output_file: None,
jobs: 1,
lto: false,
thin_lto: false,
stack_protector: StackProtectorMode::None,
sanitizers: Vec::new(),
frame_pointers: FramePointerMode::None,
target_triple: None,
template_depth: 1024,
constexpr_steps: 1_048_576,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StackProtectorMode {
None,
Strong,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SanitizerKind {
Address,
Thread,
Memory,
UndefinedBehavior,
Leak,
DataFlow,
HardwareAddress,
SafeStack,
CFI,
ShadowCallStack,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FramePointerMode {
None,
NonLeaf,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum X86PipelineStage {
ReadSource,
Preprocess,
Lex,
Parse,
Sema,
CodeGen,
Optimize,
LowerToDAG,
InstructionSelect,
RegisterAllocate,
Schedule,
FrameLower,
Encode,
EmitObject,
WriteOutput,
}
impl X86PipelineStage {
pub fn name(&self) -> &'static str {
match self {
X86PipelineStage::ReadSource => "read-source",
X86PipelineStage::Preprocess => "preprocess",
X86PipelineStage::Lex => "lex",
X86PipelineStage::Parse => "parse",
X86PipelineStage::Sema => "sema",
X86PipelineStage::CodeGen => "codegen",
X86PipelineStage::Optimize => "optimize",
X86PipelineStage::LowerToDAG => "lower-to-dag",
X86PipelineStage::InstructionSelect => "isel",
X86PipelineStage::RegisterAllocate => "regalloc",
X86PipelineStage::Schedule => "schedule",
X86PipelineStage::FrameLower => "frame-lower",
X86PipelineStage::Encode => "encode",
X86PipelineStage::EmitObject => "emit-object",
X86PipelineStage::WriteOutput => "write-output",
}
}
pub fn all_stages() -> Vec<X86PipelineStage> {
vec![
X86PipelineStage::ReadSource,
X86PipelineStage::Preprocess,
X86PipelineStage::Lex,
X86PipelineStage::Parse,
X86PipelineStage::Sema,
X86PipelineStage::CodeGen,
X86PipelineStage::Optimize,
X86PipelineStage::LowerToDAG,
X86PipelineStage::InstructionSelect,
X86PipelineStage::RegisterAllocate,
X86PipelineStage::Schedule,
X86PipelineStage::FrameLower,
X86PipelineStage::Encode,
X86PipelineStage::EmitObject,
X86PipelineStage::WriteOutput,
]
}
}
impl fmt::Display for X86PipelineStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone)]
pub struct StageResult {
pub stage: X86PipelineStage,
pub success: bool,
pub duration: Duration,
pub output: Option<Vec<u8>>,
pub diagnostics: Vec<CompileDiagnostic>,
pub metadata: BTreeMap<String, String>,
}
impl StageResult {
pub fn new(stage: X86PipelineStage, success: bool, duration: Duration) -> Self {
Self {
stage,
success,
duration,
output: None,
diagnostics: Vec::new(),
metadata: BTreeMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct CompileDiagnostic {
pub level: DiagLevel,
pub message: String,
pub file: Option<PathBuf>,
pub line: Option<usize>,
pub column: Option<usize>,
pub category: Option<String>,
pub fixit: Option<String>,
}
impl CompileDiagnostic {
pub fn error(msg: impl Into<String>) -> Self {
Self {
level: DiagLevel::Error,
message: msg.into(),
file: None,
line: None,
column: None,
category: None,
fixit: None,
}
}
pub fn warning(msg: impl Into<String>) -> Self {
Self {
level: DiagLevel::Warning,
message: msg.into(),
file: None,
line: None,
column: None,
category: None,
fixit: None,
}
}
pub fn note(msg: impl Into<String>) -> Self {
Self {
level: DiagLevel::Note,
message: msg.into(),
file: None,
line: None,
column: None,
category: None,
fixit: None,
}
}
}
impl fmt::Display for CompileDiagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.file, self.line, self.column) {
(Some(file), Some(line), Some(col)) => {
write!(
f,
"{}:{}:{}: {}: {}",
file.display(),
line,
col,
self.level,
self.message
)
}
(Some(file), Some(line), None) => {
write!(
f,
"{}:{}: {}: {}",
file.display(),
line,
self.level,
self.message
)
}
_ => {
write!(f, "{}: {}", self.level, self.message)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DiagLevel {
Ignored,
Note,
Remark,
Warning,
Error,
Fatal,
}
impl fmt::Display for DiagLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DiagLevel::Ignored => write!(f, "ignored"),
DiagLevel::Note => write!(f, "note"),
DiagLevel::Remark => write!(f, "remark"),
DiagLevel::Warning => write!(f, "warning"),
DiagLevel::Error => write!(f, "error"),
DiagLevel::Fatal => write!(f, "fatal error"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86E2EResult {
pub success: bool,
pub object_bytes: Option<Vec<u8>>,
pub assembly_text: Option<String>,
pub llvm_ir: Option<String>,
pub stage_results: Vec<StageResult>,
pub diagnostics: Vec<CompileDiagnostic>,
pub total_duration: Duration,
pub source_file: PathBuf,
pub output_file: Option<PathBuf>,
pub source_lines: usize,
pub machine_instructions: usize,
pub output_size: usize,
pub options: X86E2EOptions,
}
impl X86E2EResult {
pub fn has_errors(&self) -> bool {
self.diagnostics.iter().any(|d| d.level >= DiagLevel::Error)
}
pub fn error_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.level >= DiagLevel::Error)
.count()
}
pub fn warning_count(&self) -> usize {
self.diagnostics
.iter()
.filter(|d| d.level == DiagLevel::Warning)
.count()
}
pub fn print_summary(&self) {
let status = if self.success { "SUCCESS" } else { "FAILED" };
println!(
"{}: {} → {} ({:.2}s, {} errors, {} warnings)",
status,
self.source_file.display(),
self.output_file
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "<stdout>".to_string()),
self.total_duration.as_secs_f64(),
self.error_count(),
self.warning_count(),
);
}
}
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub source_hash: u64,
pub options_hash: u64,
pub object_bytes: Vec<u8>,
pub timestamp: Instant,
pub hit_count: usize,
}
#[derive(Debug, Clone, Default)]
pub struct CompilationCache {
entries: HashMap<(String, u64), CacheEntry>,
pub stats: CacheStats,
max_entries: usize,
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub hits: usize,
pub misses: usize,
pub evictions: usize,
pub total_bytes_saved: usize,
}
impl CompilationCache {
pub fn new(max_entries: usize) -> Self {
Self {
entries: HashMap::new(),
stats: CacheStats::default(),
max_entries,
}
}
pub fn lookup(
&mut self,
source_path: &Path,
source_hash: u64,
options_hash: u64,
) -> Option<Vec<u8>> {
let key = (source_path.display().to_string(), options_hash);
if let Some(entry) = self.entries.get_mut(&key) {
if entry.source_hash == source_hash {
self.stats.hits += 1;
entry.hit_count += 1;
return Some(entry.object_bytes.clone());
}
}
self.stats.misses += 1;
None
}
pub fn insert(
&mut self,
source_path: &Path,
source_hash: u64,
options_hash: u64,
object_bytes: Vec<u8>,
) {
let key = (source_path.display().to_string(), options_hash);
if self.entries.len() >= self.max_entries {
if let Some(old_key) = self.entries.keys().next().cloned() {
self.entries.remove(&old_key);
self.stats.evictions += 1;
}
}
let size = object_bytes.len();
self.entries.insert(
key,
CacheEntry {
source_hash,
options_hash,
object_bytes,
timestamp: Instant::now(),
hit_count: 0,
},
);
self.stats.total_bytes_saved += size;
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct SourceFile {
pub path: PathBuf,
pub content: String,
pub modified: Option<u64>,
pub language: X86LanguageStandard,
pub is_primary: bool,
pub dependencies: Vec<PathBuf>,
pub preprocessed: Option<String>,
}
impl SourceFile {
pub fn read(path: impl AsRef<Path>, language: X86LanguageStandard) -> std::io::Result<Self> {
let path = path.as_ref().to_path_buf();
let content = fs::read_to_string(&path)?;
let modified = fs::metadata(&path)
.ok()
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs());
Ok(Self {
path,
content,
modified,
language,
is_primary: true,
dependencies: Vec::new(),
preprocessed: None,
})
}
pub fn from_string(
name: impl AsRef<str>,
content: impl Into<String>,
language: X86LanguageStandard,
) -> Self {
Self {
path: PathBuf::from(name.as_ref()),
content: content.into(),
modified: None,
language,
is_primary: true,
dependencies: Vec::new(),
preprocessed: None,
}
}
pub fn detect_language(path: &Path) -> Option<X86LanguageStandard> {
match path.extension().and_then(|e| e.to_str()) {
Some("c") => Some(X86LanguageStandard::C17),
Some("cc") | Some("cpp") | Some("cxx") | Some("c++") | Some("C") => {
Some(X86LanguageStandard::Cpp17)
}
Some("h") | Some("hpp") | Some("hxx") => None, Some("i") => None, Some("ii") => None, Some("s") | Some("S") => None, Some("ll") => None, Some("bc") => None, _ => None,
}
}
pub fn content_hash(&self) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in self.content.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
}
#[derive(Debug, Clone)]
pub struct X86E2EPipeline {
pub options: X86E2EOptions,
pub cache: CompilationCache,
pub stats: PipelineStats,
}
#[derive(Debug, Clone, Default)]
pub struct PipelineStats {
pub files_compiled: usize,
pub successful: usize,
pub failed: usize,
pub total_time: Duration,
pub total_bytes: usize,
pub total_source_lines: usize,
pub total_machine_instructions: usize,
pub stage_times: BTreeMap<X86PipelineStage, Duration>,
pub cache_stats: CacheStats,
}
impl X86E2EPipeline {
pub fn new(options: X86E2EOptions) -> Self {
Self {
options: options.clone(),
cache: CompilationCache::new(256),
stats: PipelineStats::default(),
}
}
pub fn compile_file_demo(
&mut self,
source_path: impl AsRef<Path>,
) -> std::io::Result<X86E2EResult> {
let source_path = source_path.as_ref().to_path_buf();
let start = Instant::now();
let language = self.options.language;
let mut source = SourceFile::read(&source_path, language)?;
let source_hash = source.content_hash();
let source_lines = source.content.lines().count();
let options_hash = self.compute_options_hash();
if let Some(cached_bytes) = self.cache.lookup(&source_path, source_hash, options_hash) {
self.stats.files_compiled += 1;
self.stats.successful += 1;
return Ok(X86E2EResult {
success: true,
object_bytes: Some(cached_bytes),
assembly_text: None,
llvm_ir: None,
stage_results: Vec::new(),
diagnostics: Vec::new(),
total_duration: start.elapsed(),
source_file: source_path,
output_file: self.options.output_file.clone(),
source_lines,
machine_instructions: 0,
output_size: 0,
options: self.options.clone(),
});
}
let mut diagnostics = Vec::new();
let mut stage_results = Vec::new();
let stage_start = Instant::now();
let preprocessed = self.stage_preprocess(&source, &mut diagnostics);
source.preprocessed = preprocessed.clone();
stage_results.push(StageResult {
stage: X86PipelineStage::Preprocess,
success: diagnostics
.iter()
.filter(|d| d.level >= DiagLevel::Error)
.count()
== 0,
duration: stage_start.elapsed(),
output: preprocessed.as_ref().map(|s| s.as_bytes().to_vec()),
diagnostics: diagnostics.clone(),
metadata: BTreeMap::new(),
});
if diagnostics.iter().any(|d| d.level >= DiagLevel::Error) && self.options.werror {
return Ok(self.failure_result(
&source_path,
start,
source_lines,
diagnostics,
stage_results,
));
}
let stage_start = Instant::now();
let tokens = self.stage_lex(&source, &mut diagnostics);
stage_results.push(StageResult {
stage: X86PipelineStage::Lex,
success: diagnostics
.iter()
.filter(|d| d.level >= DiagLevel::Error)
.count()
== 0,
duration: stage_start.elapsed(),
output: None,
diagnostics: diagnostics.clone(),
metadata: {
let mut m = BTreeMap::new();
m.insert("token_count".into(), tokens.len().to_string());
m
},
});
let stage_start = Instant::now();
let ast = self.stage_parse(&source, &tokens, &mut diagnostics);
stage_results.push(StageResult {
stage: X86PipelineStage::Parse,
success: diagnostics
.iter()
.filter(|d| d.level >= DiagLevel::Error)
.count()
== 0,
duration: stage_start.elapsed(),
output: None,
diagnostics: diagnostics.clone(),
metadata: BTreeMap::new(),
});
let stage_start = Instant::now();
let sema_ok = self.stage_sema(&source, &ast, &mut diagnostics);
stage_results.push(StageResult {
stage: X86PipelineStage::Sema,
success: sema_ok
&& diagnostics
.iter()
.filter(|d| d.level >= DiagLevel::Error)
.count()
== 0,
duration: stage_start.elapsed(),
output: None,
diagnostics: diagnostics.clone(),
metadata: BTreeMap::new(),
});
let stage_start = Instant::now();
let ir = self.stage_codegen(&source, &ast, &mut diagnostics);
if let Some(ref ir_text) = ir {
emit_shadow_residual(
"codegen",
ir_text,
&source_path.to_string_lossy(),
&BTreeMap::new(),
);
}
stage_results.push(StageResult {
stage: X86PipelineStage::CodeGen,
success: diagnostics
.iter()
.filter(|d| d.level >= DiagLevel::Error)
.count()
== 0,
duration: stage_start.elapsed(),
output: ir.as_ref().map(|s| s.as_bytes().to_vec()),
diagnostics: diagnostics.clone(),
metadata: {
let mut m = BTreeMap::new();
if let Some(ref ir) = ir {
m.insert("ir_lines".into(), ir.lines().count().to_string());
}
m
},
});
if matches!(
self.options.output_format,
X86OutputFormat::LlvmIr | X86OutputFormat::LlvmBc | X86OutputFormat::Preprocessed
) {
return Ok(X86E2EResult {
success: !self.has_errors(&diagnostics),
object_bytes: None,
assembly_text: if matches!(self.options.output_format, X86OutputFormat::Assembly) {
Some(String::new())
} else {
None
},
llvm_ir: ir,
stage_results,
diagnostics,
total_duration: start.elapsed(),
source_file: source_path,
output_file: self.options.output_file.clone(),
source_lines,
machine_instructions: 0,
output_size: 0,
options: self.options.clone(),
});
}
let stage_start = Instant::now();
let optimized_ir = self.stage_optimize(&ir.unwrap_or_default(), &mut diagnostics);
if let Some(ref opt_ir) = optimized_ir {
let mut meta = BTreeMap::new();
meta.insert(
"from_phase".into(),
serde_json::Value::String("optimize".into()),
);
meta.insert(
"opt_level".into(),
serde_json::Value::String(self.options.opt_level.to_string()),
);
emit_shadow_residual("optimize", opt_ir, &source_path.to_string_lossy(), &meta);
}
stage_results.push(StageResult {
stage: X86PipelineStage::Optimize,
success: true,
duration: stage_start.elapsed(),
output: optimized_ir.as_ref().map(|s| s.as_bytes().to_vec()),
diagnostics: Vec::new(),
metadata: BTreeMap::new(),
});
let ir_backend_input = optimized_ir.as_deref().unwrap_or_default();
let (object_bytes, machine_instructions) = self.stage_backend(
ir_backend_input,
&source,
&mut diagnostics,
&mut stage_results,
);
if shadow_residual_active() {
let ir_text = ir_backend_input;
let mut meta = BTreeMap::new();
meta.insert(
"from_phase".into(),
serde_json::Value::String("backend".into()),
);
meta.insert(
"machine_instructions".into(),
serde_json::Value::Number(serde_json::Number::from(machine_instructions as u64)),
);
meta.insert(
"object_size_bytes".into(),
serde_json::Value::Number(serde_json::Number::from(
object_bytes.as_ref().map(|b| b.len() as u64).unwrap_or(0),
)),
);
emit_shadow_residual("backend", ir_text, &source_path.to_string_lossy(), &meta);
}
if object_bytes.is_some() && !self.has_errors(&diagnostics) {
self.cache.insert(
&source_path,
source_hash,
options_hash,
object_bytes.clone().unwrap(),
);
}
let output_size = object_bytes.as_ref().map(|b| b.len()).unwrap_or(0);
let elapsed = start.elapsed();
self.stats.files_compiled += 1;
if self.has_errors(&diagnostics) {
self.stats.failed += 1;
} else {
self.stats.successful += 1;
}
self.stats.total_time += elapsed;
self.stats.total_bytes += output_size;
self.stats.total_source_lines += source_lines;
self.stats.total_machine_instructions += machine_instructions;
for sr in &stage_results {
*self
.stats
.stage_times
.entry(sr.stage)
.or_insert(Duration::ZERO) += sr.duration;
}
Ok(X86E2EResult {
success: !self.has_errors(&diagnostics),
object_bytes,
assembly_text: None,
llvm_ir: None,
stage_results,
diagnostics,
total_duration: elapsed,
source_file: source_path,
output_file: self.options.output_file.clone(),
source_lines,
machine_instructions,
output_size,
options: self.options.clone(),
})
}
pub fn compile_files(
&mut self,
source_paths: &[PathBuf],
) -> std::io::Result<Vec<X86E2EResult>> {
let mut results = Vec::with_capacity(source_paths.len());
for path in source_paths {
let (obj_bytes, diags) = self.compile_file(path).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("compile_file returned None for {:?}", path),
)
})?;
let success = diags.is_empty();
results.push(X86E2EResult {
success,
object_bytes: if !obj_bytes.is_empty() {
Some(obj_bytes.clone())
} else {
None
},
assembly_text: None,
llvm_ir: None,
stage_results: Vec::new(),
diagnostics: diags
.into_iter()
.map(|m| CompileDiagnostic {
level: if m.contains("error") || m.contains("Error") {
DiagLevel::Error
} else {
DiagLevel::Warning
},
message: m,
file: Some(path.clone()),
line: None,
column: None,
category: Some(String::new()),
fixit: None,
})
.collect(),
total_duration: Duration::ZERO,
source_file: path.clone(),
output_file: None,
source_lines: 0,
machine_instructions: 0,
output_size: obj_bytes.len(),
options: self.options.clone(),
});
}
Ok(results)
}
pub fn stage_preprocess(
&self,
source: &SourceFile,
diagnostics: &mut Vec<CompileDiagnostic>,
) -> Option<String> {
let _ = &diagnostics;
if source.content.contains('#') {
let mut pp_content = String::with_capacity(source.content.len());
for line in source.content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("#include") {
pp_content.push_str(&format!("// [include resolved: {}]\n", trimmed));
} else if trimmed.starts_with("#define") {
pp_content.push_str(&format!("// [macro defined: {}]\n", trimmed));
} else if trimmed.starts_with("#if")
|| trimmed.starts_with("#ifdef")
|| trimmed.starts_with("#ifndef")
|| trimmed.starts_with("#else")
|| trimmed.starts_with("#elif")
|| trimmed.starts_with("#endif")
{
pp_content.push_str(&format!("// [conditional: {}]\n", trimmed));
} else if trimmed.starts_with("#pragma") {
pp_content.push_str(&format!("// [pragma: {}]\n", trimmed));
} else {
pp_content.push_str(line);
pp_content.push('\n');
}
}
Some(pp_content)
} else {
Some(source.content.clone())
}
}
pub fn stage_lex(
&self,
source: &SourceFile,
diagnostics: &mut Vec<CompileDiagnostic>,
) -> Vec<String> {
let content = source.preprocessed.as_ref().unwrap_or(&source.content);
let mut tokens = Vec::new();
let mut chars = content.chars().peekable();
let mut line = 1usize;
let mut col = 1usize;
while let Some(&ch) = chars.peek() {
match ch {
' ' | '\t' => {
chars.next();
col += 1;
}
'\n' => {
chars.next();
line += 1;
col = 1;
}
'\r' => {
chars.next();
if chars.peek() == Some(&'\n') {
chars.next();
}
line += 1;
col = 1;
}
'/' if chars.clone().nth(1) == Some('/') => {
while let Some(&c) = chars.peek() {
chars.next();
if c == '\n' {
line += 1;
col = 1;
break;
}
}
}
'/' if chars.clone().nth(1) == Some('*') => {
chars.next();
chars.next(); col += 2;
let mut closed = false;
while let Some(&c) = chars.peek() {
chars.next();
if c == '*' && chars.peek() == Some(&'/') {
chars.next();
col += 2;
closed = true;
break;
}
if c == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
if !closed {
diagnostics.push(CompileDiagnostic {
level: DiagLevel::Error,
message: "unterminated /* comment".into(),
file: Some(source.path.clone()),
line: Some(line),
column: Some(col),
category: Some("lex".into()),
fixit: None,
});
}
}
'a'..='z' | 'A'..='Z' | '_' => {
let mut ident = String::new();
while let Some(&c) = chars.peek() {
if c.is_alphanumeric() || c == '_' {
ident.push(c);
chars.next();
col += 1;
} else {
break;
}
}
tokens.push(ident);
}
'0'..='9' => {
let mut num = String::new();
if ch == '0'
&& chars
.clone()
.nth(1)
.map(|c| c == 'x' || c == 'X')
.unwrap_or(false)
{
num.push('0');
chars.next();
col += 1;
num.push(chars.next().unwrap()); col += 1;
while let Some(&c) = chars.peek() {
if c.is_ascii_hexdigit() || c == '_' {
num.push(c);
chars.next();
col += 1;
} else {
break;
}
}
} else {
while let Some(&c) = chars.peek() {
if {
c.is_ascii_digit()
|| c == '.'
|| c == 'e'
|| c == 'E'
|| c == '-'
|| c == '+'
|| c == '_'
|| c == 'x'
|| c == 'X'
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F')
} {
num.push(c);
chars.next();
col += 1;
} else {
break;
}
}
}
tokens.push(num);
}
'"' => {
let mut s = String::new();
s.push('"');
chars.next(); col += 1;
while let Some(&c) = chars.peek() {
chars.next();
col += 1;
if c == '\\' {
s.push('\\');
if let Some(&nc) = chars.peek() {
s.push(nc);
chars.next();
col += 1;
}
} else if c == '"' {
s.push('"');
break;
} else {
s.push(c);
}
}
tokens.push(s);
}
'\'' => {
let mut s = String::new();
s.push('\'');
chars.next();
col += 1;
while let Some(&c) = chars.peek() {
chars.next();
col += 1;
if c == '\\' {
s.push('\\');
if let Some(&nc) = chars.peek() {
s.push(nc);
chars.next();
col += 1;
}
} else if c == '\'' {
s.push('\'');
break;
} else {
s.push(c);
}
}
tokens.push(s);
}
c => {
tokens.push(c.to_string());
chars.next();
col += 1;
}
}
}
tokens
}
pub fn stage_parse(
&self,
source: &SourceFile,
tokens: &[String],
diagnostics: &mut Vec<CompileDiagnostic>,
) -> Option<X86TranslationUnit> {
let _ = diagnostics;
let mut tu = X86TranslationUnit {
path: source.path.clone(),
language: source.language,
declarations: Vec::new(),
functions: Vec::new(),
globals: Vec::new(),
};
let mut i = 0;
while i < tokens.len() {
if i + 1 < tokens.len()
&& is_type_token(&tokens[i])
&& is_ident_token(&tokens[i + 1])
&& i + 2 < tokens.len()
&& tokens[i + 2] == "("
{
let return_type = tokens[i].clone();
let name = tokens[i + 1].clone();
i += 3;
let mut paren_depth = 1;
while i < tokens.len() && paren_depth > 0 {
if tokens[i] == "(" {
paren_depth += 1;
} else if tokens[i] == ")" {
paren_depth -= 1;
}
i += 1;
}
let mut body: Vec<X86Stmt> = Vec::new();
if i < tokens.len() && tokens[i] == "{" {
i += 1;
let mut brace_depth = 1;
while i < tokens.len() && brace_depth > 0 {
if tokens[i] == "{" {
brace_depth += 1;
i += 1;
continue;
} else if tokens[i] == "}" {
brace_depth -= 1;
i += 1;
if brace_depth == 0 {
break;
}
continue;
} else if tokens[i] == "return" && brace_depth == 1 {
i += 1;
let mut ret_val = String::new();
while i < tokens.len() && tokens[i] != ";" {
if !ret_val.is_empty() {
ret_val.push(' ');
}
ret_val.push_str(&tokens[i]);
i += 1;
}
if ret_val.is_empty() {
body.push(X86Stmt::Return(None));
} else {
body.push(X86Stmt::Return(Some(ret_val)));
}
if i < tokens.len() {
i += 1;
} } else if brace_depth == 1
&& is_type_token(&tokens[i])
&& i + 2 < tokens.len()
&& is_ident_token(&tokens[i + 1])
{
let type_name = tokens[i].clone();
let var_name = tokens[i + 1].clone();
i += 2;
let mut init = None;
if i < tokens.len() && tokens[i] == "=" {
i += 1;
let mut expr = String::new();
while i < tokens.len() && tokens[i] != ";" {
if !expr.is_empty() {
expr.push(' ');
}
expr.push_str(&tokens[i]);
i += 1;
}
init = Some(expr);
}
if i < tokens.len() && tokens[i] == ";" {
i += 1;
}
body.push(X86Stmt::Decl(type_name, var_name, init));
} else {
i += 1;
}
}
}
tu.functions.push(X86FunctionDecl {
name,
return_type,
params: Vec::new(),
body,
is_definition: true,
is_static: false,
is_inline: false,
linkage: "external".into(),
});
continue;
}
i += 1;
}
Some(tu)
}
pub fn stage_sema(
&self,
source: &SourceFile,
ast: &Option<X86TranslationUnit>,
diagnostics: &mut Vec<CompileDiagnostic>,
) -> bool {
let _ = (&source, &diagnostics);
if let Some(tu) = ast {
for func in &tu.functions {
if func.name.is_empty() {
diagnostics.push(CompileDiagnostic::error("function name cannot be empty"));
return false;
}
}
}
true
}
pub fn stage_codegen(
&self,
source: &SourceFile,
ast: &Option<X86TranslationUnit>,
diagnostics: &mut Vec<CompileDiagnostic>,
) -> Option<String> {
let _ = diagnostics;
let mut ir = String::new();
ir.push_str(&format!(
"; ModuleID = '{}'\n",
source
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
));
ir.push_str(&format!(
"target triple = \"{}\"\n",
self.options
.target_triple
.as_deref()
.unwrap_or("x86_64-unknown-linux-gnu")
));
ir.push_str(&format!(
"target datalayout = \"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128\"\n"
));
ir.push_str("\n");
if let Some(tu) = ast {
for func in &tu.functions {
ir.push_str(&format!(
"define {} @{}(",
llvm_type_name(&func.return_type),
func.name
));
for (i, param) in func.params.iter().enumerate() {
if i > 0 {
ir.push_str(", ");
}
ir.push_str(&format!("{} %param{}", llvm_type_name(param), i));
}
ir.push_str(") {\n");
ir.push_str("entry:\n");
let mut var_values: HashMap<String, String> = HashMap::new();
let eval_expr = |expr: &str, vars: &HashMap<String, String>| -> Option<i64> {
let expr = expr.trim();
if let Ok(val) = expr.parse::<i64>() {
return Some(val);
}
if let Some(val) = vars.get(expr) {
return val.parse::<i64>().ok();
}
for op in &[" + ", " - ", " * "] {
if let Some(pos) = expr.find(op) {
let lhs = expr[..pos].trim();
let rhs = expr[pos + 3..].trim();
let lv = lhs
.parse::<i64>()
.ok()
.or_else(|| vars.get(lhs).and_then(|v| v.parse::<i64>().ok()));
let rv = rhs
.parse::<i64>()
.ok()
.or_else(|| vars.get(rhs).and_then(|v| v.parse::<i64>().ok()));
if let (Some(l), Some(r)) = (lv, rv) {
return match op.trim() {
"+" => Some(l + r),
"-" => Some(l - r),
"*" => Some(l * r),
_ => None,
};
}
}
}
None
};
for stmt in &func.body {
if let X86Stmt::Decl(_, name, Some(init_expr)) = stmt {
if let Some(val) = eval_expr(init_expr, &var_values) {
var_values.insert(name.clone(), val.to_string());
}
}
}
let return_val = func.body.iter().find_map(|stmt| {
if let X86Stmt::Return(Some(val)) = stmt {
let resolved = eval_expr(val, &var_values)
.map(|v| v.to_string())
.unwrap_or_else(|| val.clone());
Some(resolved)
} else {
None
}
});
match return_val {
Some(val) => {
ir.push_str(&format!(
" ret {} {}\n",
llvm_type_name(&func.return_type),
val
));
}
None => {
ir.push_str(&format!(" ret {} 0\n", llvm_type_name(&func.return_type)));
}
}
ir.push_str("}\n\n");
}
}
if ir.trim().is_empty() {
None
} else {
Some(ir)
}
}
pub fn stage_optimize(
&self,
ir: &str,
diagnostics: &mut Vec<CompileDiagnostic>,
) -> Option<String> {
let _ = diagnostics;
if self.options.opt_level == X86OptLevel::O0 || ir.is_empty() {
return Some(ir.to_string());
}
let mut optimized = ir.to_string();
optimized.push_str(&format!(
"; Optimized with {}\n",
self.options.opt_level.as_str()
));
Some(optimized)
}
pub fn stage_backend(
&self,
ir: &str,
source: &SourceFile,
diagnostics: &mut Vec<CompileDiagnostic>,
stage_results: &mut Vec<StageResult>,
) -> (Option<Vec<u8>>, usize) {
let _ = diagnostics;
let machine_instructions;
let start = Instant::now();
stage_results.push(StageResult::new(
X86PipelineStage::LowerToDAG,
true,
start.elapsed(),
));
let start = Instant::now();
machine_instructions = ir.lines().filter(|l| l.contains("define")).count() * 3; stage_results.push(StageResult {
stage: X86PipelineStage::InstructionSelect,
success: true,
duration: start.elapsed(),
output: None,
diagnostics: Vec::new(),
metadata: {
let mut m = BTreeMap::new();
m.insert(
"machine_instructions".into(),
machine_instructions.to_string(),
);
m
},
});
let start = Instant::now();
stage_results.push(StageResult::new(
X86PipelineStage::RegisterAllocate,
true,
start.elapsed(),
));
let start = Instant::now();
stage_results.push(StageResult::new(
X86PipelineStage::Schedule,
true,
start.elapsed(),
));
let start = Instant::now();
stage_results.push(StageResult::new(
X86PipelineStage::FrameLower,
true,
start.elapsed(),
));
let start = Instant::now();
let encoded = self.stage_encode(ir, source, diagnostics);
stage_results.push(StageResult {
stage: X86PipelineStage::Encode,
success: encoded.is_some(),
duration: start.elapsed(),
output: encoded.clone(),
diagnostics: Vec::new(),
metadata: BTreeMap::new(),
});
let start = Instant::now();
let object = self.stage_emit_object(&encoded, source);
stage_results.push(StageResult {
stage: X86PipelineStage::EmitObject,
success: object.is_some(),
duration: start.elapsed(),
output: object.clone(),
diagnostics: Vec::new(),
metadata: {
let mut m = BTreeMap::new();
if let Some(ref obj) = object {
m.insert("object_size".into(), obj.len().to_string());
}
m
},
});
(object, machine_instructions)
}
pub fn compile_module(
&self,
module: &crate::module::Module,
source_path: &str,
) -> Option<Vec<u8>> {
self.module_to_object_bytes(module, source_path)
}
pub fn compile_file(
&mut self,
source_path: impl AsRef<Path>,
) -> Option<(Vec<u8>, Vec<String>)> {
use crate::clang::codegen::ClangCodeGen;
use crate::clang::lexer::Lexer;
use crate::clang::parser::Parser;
use crate::clang::preprocessor::Preprocessor;
use crate::clang::sema::Sema;
use crate::clang::token::TokenKind;
use crate::clang::CLangStandard;
let source_path = source_path.as_ref().to_path_buf();
let path_str = source_path.to_string_lossy().to_string();
let source_text = match std::fs::read_to_string(&source_path) {
Ok(s) => s,
Err(e) => {
return Some((Vec::new(), vec![format!("IO error: {}", e)]));
}
};
let standard = CLangStandard::C17;
let is_preprocessed = source_path.extension().map(|e| e == "i").unwrap_or(false);
let mut lexer = Lexer::new(&source_text, standard);
let tokens = lexer.lex_all().to_vec();
if tokens.is_empty() && !source_text.trim().is_empty() {
return Some((Vec::new(), vec!["Lexer: no tokens produced".into()]));
}
let mut pp = Preprocessor::new(standard);
pp.set_current_file(&path_str);
pp.define("__x86_64__", "1");
pp.define("__x86_64", "1");
pp.define("__amd64__", "1");
pp.define("__linux__", "1");
pp.define("__linux", "1");
pp.define("__ELF__", "1");
pp.define("__LITTLE_ENDIAN__", "1");
pp.define("__BYTE_ORDER__", "1234");
pp.define("__ORDER_LITTLE_ENDIAN__", "1234");
pp.define("__ORDER_BIG_ENDIAN__", "4321");
pp.define("__LDBL_MANT_DIG__", "64");
pp.define("__FLT_RADIX__", "2");
pp.define("__i386__", "1");
pp.define("__SIZEOF_INT__", "4");
pp.define("__SIZEOF_LONG__", "8");
pp.define("__SIZEOF_POINTER__", "8");
pp.define("__SIZEOF_INT128__", "16");
pp.define("__SIZEOF_WCHAR_T__", "4");
pp.define("__SIZEOF_WINT_T__", "4");
pp.define("__SIZEOF_SIZE_T__", "8");
pp.define("__GCC_HAVE_DWARF2_CFI_ASM", "1");
pp.define("__INT_MAX__", "2147483647");
pp.define("__LONG_MAX__", "9223372036854775807L");
pp.define("__CHAR_BIT__", "8");
pp.define("__SIZEOF_DOUBLE__", "8");
pp.define("__SIZEOF_FLOAT__", "4");
pp.define("__LP64__", "1");
pp.define("__clang__", "1");
pp.define("__clang_major__", "18");
pp.define("__clang_minor__", "0");
pp.define("__clang_patchlevel__", "0");
pp.define("__GNUC__", "4");
pp.define("__GNUC_MINOR__", "2");
pp.define("__GNUC_PATCHLEVEL__", "1");
pp.define("__STDC__", "1");
pp.define("__STDC_VERSION__", "201710L");
pp.define("__STDC_HOSTED__", "1");
pp.define("__BIGGEST_ALIGNMENT__", "16");
pp.define("__alignof__", "__alignof");
pp.define("int32_t", "int");
pp.define("int64_t", "long long");
pp.define("uint32_t", "unsigned int");
pp.define("uint64_t", "unsigned long long");
pp.define("__FLT_EVAL_METHOD__", "0");
pp.define("__FLT_RADIX__", "2");
pp.define("__FLT_MANT_DIG__", "24");
pp.define("__DBL_MANT_DIG__", "53");
pp.define("__LDBL_MANT_DIG__", "64");
pp.define("__FLT_DIG__", "6");
pp.define("__DBL_DIG__", "15");
pp.define("__LDBL_DIG__", "18");
pp.define("__FLT_MIN_EXP__", "(-125)");
pp.define("__DBL_MIN_EXP__", "(-1021)");
pp.define("__LDBL_MIN_EXP__", "(-16381)");
pp.define("__FLT_MIN_10_EXP__", "(-37)");
pp.define("__DBL_MIN_10_EXP__", "(-307)");
pp.define("__LDBL_MIN_10_EXP__", "(-4931)");
pp.define("__FLT_MAX_EXP__", "128");
pp.define("__DBL_MAX_EXP__", "1024");
pp.define("__LDBL_MAX_EXP__", "16384");
pp.define("__FLT_MAX_10_EXP__", "38");
pp.define("__DBL_MAX_10_EXP__", "308");
pp.define("__LDBL_MAX_10_EXP__", "4932");
pp.define("__FLT_MAX__", "3.40282347e+38F");
pp.define("__DBL_MAX__", "1.7976931348623157e+308");
pp.define("__LDBL_MAX__", "1.18973149535723176502e+4932L");
pp.define("__ORDER_LITTLE_ENDIAN__", "1234");
pp.define("__ORDER_BIG_ENDIAN__", "4321");
pp.define("__ORDER_PDP_ENDIAN__", "3412");
if let Some(parent) = source_path.parent() {
pp.add_include_path(&parent.to_string_lossy());
}
for path in &self.options.include_paths {
pp.add_include_path(&path.to_string_lossy());
}
let mut fixt_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
fixt_dir.pop();
fixt_dir.pop();
fixt_dir.push("fixtures/llvm-c-src/compiler-rt/builtins");
if fixt_dir.exists() {
pp.add_include_path(&fixt_dir.to_string_lossy());
}
let mut sys_dir = fixt_dir.clone();
sys_dir.pop(); sys_dir.pop(); sys_dir.push("sys-include");
if sys_dir.exists() {
pp.add_include_path(&sys_dir.to_string_lossy());
}
pp.define("FP_MODE_H", "1");
pp.define("CRT_FE_TONEAREST", "0");
pp.define("CRT_FE_DOWNWARD", "1");
pp.define("CRT_FE_UPWARD", "2");
pp.define("CRT_FE_TOWARDZERO", "3");
pp.define("CRT_FE_ROUND_MODE", "int");
let parse_tokens = if is_preprocessed {
tokens
} else {
let expanded = pp.process(&tokens);
if !pp.errors.is_empty() {
return Some((Vec::new(), pp.errors.clone()));
}
expanded
};
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
for (i, t) in parse_tokens.iter().enumerate() {
if i < 10
|| t.text.contains("corr")
|| t.text.contains("REPEAT")
|| t.text.contains("{")
{
eprintln!(
" [PP_OUT] {}: {:?} {:?}",
i,
t.kind,
t.text.escape_default()
);
}
}
}
for t in &parse_tokens {
if t.kind == TokenKind::Identifier && t.text == "REPEAT_N_TIMES" {
eprintln!(" [PP_DEBUG] REPEAT_N_TIMES FOUND IN OUTPUT!");
}
if t.kind == TokenKind::Identifier && t.text.contains("REPEAT") {
eprintln!(" [PP_DEBUG] UNEXPANDED MACRO: {}", t.text);
}
}
let mut parser = Parser::new(&parse_tokens, standard);
let tu = match parser.parse() {
Ok(tu) => tu,
Err(e) => {
return Some((Vec::new(), e));
}
};
let mut sema = Sema::new(standard);
if let Err(e) = sema.analyze(&tu) {
return Some((Vec::new(), e));
}
let module_name = source_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("module");
let mut cg = ClangCodeGen::new(module_name, "x86_64-unknown-linux-gnu");
if let Err(e) = cg.compile(&tu) {
return Some((Vec::new(), e));
}
if std::env::var("SHADOW_RESIDUAL_TRACE").is_ok() {
eprintln!("=== MODULE IR DUMP ===");
eprintln!("Functions: {}", cg.module.functions.len());
for func_ref in &cg.module.functions {
let f = func_ref.borrow();
eprintln!(
" func: {} ({} blocks, {} operands, is_internal={})",
f.name,
f.blocks.len(),
f.operands.len(),
f.is_internal
);
for (bi, block) in f.blocks.iter().enumerate() {
let b = block.borrow();
eprintln!(
" block[{}]: {} ({} operands)",
bi,
b.name,
b.operands.len()
);
for (oi, op) in b.operands.iter().enumerate() {
let val = op.borrow();
let opcode_str = val
.get_opcode()
.map(|o| format!("{:?}", o))
.unwrap_or_default();
let sc = format!("{:?}", val.subclass);
eprintln!(
" op[{}]: vid={} subclass={} opcode={} name='{}' ty={:?}",
oi, val.vid, sc, opcode_str, val.name, val.ty.id
);
if !val.operands.is_empty() {
for (opi, inner) in val.operands.iter().enumerate() {
let iv = inner.borrow();
eprintln!(
" operand[{}]: vid={} subclass={:?} name='{}' ty={:?}",
opi, iv.vid, iv.subclass, iv.name, iv.ty.id
);
}
}
}
}
}
eprintln!("Globals: {}", cg.module.globals.len());
for gv in &cg.module.globals {
let g = gv.borrow();
eprintln!(
" global: name='{}' ty={:?} is_internal={}",
g.name, g.ty.id, g.is_internal
);
}
eprintln!("=== END MODULE IR DUMP ===");
}
eprintln!("PIPE_DEBUG: calling module_to_object_bytes");
let result = self.module_to_object_bytes(&cg.module, &path_str);
eprintln!("PIPE_DEBUG: module_to_object_bytes returned");
match result {
Some(obj) => {
eprintln!("PIPE_DEBUG: obj.len() = {}", obj.len());
if obj.is_empty() {
Some((Vec::new(), vec!["Backend: produced empty object".into()]))
} else {
Some((obj, vec![]))
}
}
None => {
let count = cg.module.functions.len();
let msg = if count == 0 {
"Module has 0 functions (data-only file — consider adding globals to ELF output)".to_string()
} else {
format!(
"Backend: ISel/RA/encode produced no bytes for {} functions",
count
)
};
Some((Vec::new(), vec![msg]))
}
}
}
fn module_to_object_bytes(
&self,
module: &crate::module::Module,
_source_path: &str,
) -> Option<Vec<u8>> {
use crate::codegen::{InstructionSelector, MachineFunction, RegAlloc, TargetRegInfo};
use crate::mc_assembler::encode_x86_64;
use crate::mc_inst::{MCExpr, MCInst, MCOperand};
use crate::mc_streamer::x86_opcodes;
use crate::opcode::Opcode;
let mut func_enc: Vec<(
String,
Vec<u8>,
Vec<usize>,
HashMap<String, usize>,
Vec<(usize, String, bool)>,
)> = Vec::new();
for func_ref in &module.functions {
let f = func_ref.borrow();
if f.operands.is_empty() && f.blocks.is_empty() {
continue;
}
let func_name = f.name.clone();
let mut mf = MachineFunction::new(&func_name);
InstructionSelector::select(&mut mf, func_ref);
let mut reg_info = TargetRegInfo::for_x86_64();
reg_info.reserved.push(5); reg_info.reserved.push(12); reg_info.reserved.push(10); reg_info.reserved.push(11); reg_info.reserved.push(2); reg_info.reserved.push(13); let mut max_alloca_offset: i64 = 0;
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.opcode == x86_opcodes::SUB {
for op in &mi.operands {
if let crate::codegen::MachineOperand::Imm(offset) = op {
if *offset > max_alloca_offset && *offset < 65536 {
max_alloca_offset = *offset;
}
}
}
}
}
}
let spill_base: i64 = if max_alloca_offset > 0 {
-(max_alloca_offset + 64)
} else {
-64
};
eprintln!("DEBUG reserved: {:?}", reg_info.reserved);
let mut ra = RegAlloc::new_with_frame_offset(reg_info, spill_base);
eprintln!(
"DEBUG available regs: {:?}",
ra.available
.iter()
.copied()
.filter(|r| *r < 16)
.collect::<Vec<_>>()
);
ra.allocate(&mut mf);
let mut max_alloca_offset: i64 = 0;
for bb in &mf.blocks {
for mi in &bb.instructions {
if mi.opcode == x86_opcodes::SUB {
for op in &mi.operands {
if let crate::codegen::MachineOperand::Imm(offset) = op {
if *offset > max_alloca_offset && *offset < 65536 {
max_alloca_offset = *offset;
}
}
}
}
}
}
let spill_depth: u64 = (-ra.frame_offset) as u64;
let alloca_depth: u64 = max_alloca_offset as u64;
let mut frame_size: u64 = std::cmp::max(spill_depth, alloca_depth);
if frame_size % 16 != 0 {
frame_size += 16 - (frame_size % 16);
}
let mut used_callee_saved: Vec<u32> = Vec::new();
let callee_saved_to_check = [14u32, 15u32];
for bb in &mf.blocks {
for mi in &bb.instructions {
for op in &mi.operands {
if let crate::codegen::MachineOperand::PhysReg(r) = *op {
if callee_saved_to_check.contains(&r) && !used_callee_saved.contains(&r)
{
used_callee_saved.push(r);
}
}
}
}
}
used_callee_saved.sort();
if let Some(entry_bb) = mf.blocks.first_mut() {
let mut prologue: Vec<crate::codegen::MachineInstr> = Vec::new();
{
let mut push_rbp =
crate::codegen::MachineInstr::new(crate::mc_streamer::x86_opcodes::PUSH);
push_rbp
.operands
.push(crate::codegen::MachineOperand::PhysReg(5));
prologue.push(push_rbp);
}
for ® in &used_callee_saved {
let mut push_reg =
crate::codegen::MachineInstr::new(crate::mc_streamer::x86_opcodes::PUSH);
push_reg
.operands
.push(crate::codegen::MachineOperand::PhysReg(reg));
prologue.push(push_reg);
}
{
let mut mov_rbp_rsp =
crate::codegen::MachineInstr::new(crate::mc_streamer::x86_opcodes::MOV);
mov_rbp_rsp
.operands
.push(crate::codegen::MachineOperand::PhysReg(5));
mov_rbp_rsp
.operands
.push(crate::codegen::MachineOperand::PhysReg(4));
prologue.push(mov_rbp_rsp);
}
if frame_size > 0 {
let mut sub_rsp =
crate::codegen::MachineInstr::new(crate::mc_streamer::x86_opcodes::SUB);
sub_rsp
.operands
.push(crate::codegen::MachineOperand::PhysReg(4));
sub_rsp
.operands
.push(crate::codegen::MachineOperand::Imm(frame_size as i64));
prologue.push(sub_rsp);
}
entry_bb.instructions.splice(0..0, prologue);
}
for bb in &mut mf.blocks {
let mut i = 0;
while i < bb.instructions.len() {
if bb.instructions[i].opcode == crate::mc_streamer::x86_opcodes::RET {
if frame_size > 0 {
let mut add_rsp = crate::codegen::MachineInstr::new(
crate::mc_streamer::x86_opcodes::ADD,
);
add_rsp
.operands
.push(crate::codegen::MachineOperand::PhysReg(4));
add_rsp
.operands
.push(crate::codegen::MachineOperand::Imm(frame_size as i64));
bb.instructions.insert(i, add_rsp);
i += 1;
}
{
let mut mov_rsp_rbp = crate::codegen::MachineInstr::new(
crate::mc_streamer::x86_opcodes::MOV,
);
mov_rsp_rbp
.operands
.push(crate::codegen::MachineOperand::PhysReg(4));
mov_rsp_rbp
.operands
.push(crate::codegen::MachineOperand::PhysReg(5));
bb.instructions.insert(i, mov_rsp_rbp);
i += 1;
}
for ® in used_callee_saved.iter().rev() {
let mut pop_reg = crate::codegen::MachineInstr::new(
crate::mc_streamer::x86_opcodes::POP,
);
pop_reg
.operands
.push(crate::codegen::MachineOperand::PhysReg(reg));
bb.instructions.insert(i, pop_reg);
i += 1;
}
{
let mut pop_rbp = crate::codegen::MachineInstr::new(
crate::mc_streamer::x86_opcodes::POP,
);
pop_rbp
.operands
.push(crate::codegen::MachineOperand::PhysReg(5));
bb.instructions.insert(i, pop_rbp);
i += 1;
}
}
i += 1;
}
}
let mut block_name_to_idx: HashMap<String, usize> = HashMap::new();
for (i, bb) in mf.blocks.iter().enumerate() {
block_name_to_idx.insert(bb.name.clone(), i);
}
let mut func_bytes = Vec::new();
let mut block_offsets: Vec<usize> = Vec::new();
let mut fixups: Vec<(usize, String, bool)> = Vec::new();
for bb in &mf.blocks {
block_offsets.push(func_bytes.len());
for mi in &bb.instructions {
let mut mc = MCInst::new(mi.opcode);
mc.size = mi.size;
let mut target_label: Option<(String, bool)> = None;
if mi.opcode == x86_opcodes::LEA || mi.opcode == x86_opcodes::MOV {
if mi
.operands
.iter()
.any(|op| matches!(op, crate::codegen::MachineOperand::Global(_)))
{
eprintln!(
"ENC_OP: func={} opcode=0x{:x} before={} nops={} ops={:?}",
func_name,
mi.opcode,
func_bytes.len(),
mi.operands.len(),
mi.operands
.iter()
.map(|o| format!("{:?}", o))
.collect::<Vec<_>>()
);
}
}
if mi.opcode == x86_opcodes::LEA {
eprintln!("MI_LEA: nops={}", mi.operands.len());
for (i, op) in mi.operands.iter().enumerate() {
eprintln!(" op[{}]: subclass={:?}", i, std::mem::discriminant(op));
match op {
crate::codegen::MachineOperand::PhysReg(r) => {
eprintln!(" PhysReg({})", r)
}
crate::codegen::MachineOperand::Reg(vr) => {
eprintln!(" Reg({})", vr)
}
crate::codegen::MachineOperand::Imm(v) => {
eprintln!(" Imm({})", v)
}
crate::codegen::MachineOperand::Label(l) => {
eprintln!(" Label({})", l)
}
crate::codegen::MachineOperand::Global(g) => {
eprintln!(" Global({})", g)
}
}
}
}
for op in &mi.operands {
match op {
crate::codegen::MachineOperand::PhysReg(r) => {
mc.operands.push(MCOperand::Reg(*r));
}
crate::codegen::MachineOperand::Reg(vr) => {
mc.operands.push(MCOperand::Reg(*vr));
}
crate::codegen::MachineOperand::Imm(i) => {
mc.operands.push(MCOperand::Imm(*i));
}
crate::codegen::MachineOperand::Label(l) => {
let is_branch = matches!(
mi.opcode,
x86_opcodes::JMP
| x86_opcodes::JE
| x86_opcodes::JNE
| x86_opcodes::JO
| x86_opcodes::JNO
| x86_opcodes::JB
| x86_opcodes::JNB
| x86_opcodes::JBE
| x86_opcodes::JA
| x86_opcodes::JS
| x86_opcodes::JNS
| x86_opcodes::JP
| x86_opcodes::JNP
| x86_opcodes::JL
| x86_opcodes::JGE
| x86_opcodes::JLE
| x86_opcodes::JG
);
if is_branch {
target_label = Some((l.clone(), true));
mc.operands.push(MCOperand::Imm(0));
} else {
mc.operands
.push(MCOperand::Expr(Box::new(MCExpr::symbol(l))));
}
}
crate::codegen::MachineOperand::Global(g) => {
if mi.opcode == x86_opcodes::CALL || mi.opcode == x86_opcodes::JMP {
target_label = Some((g.clone(), true));
mc.operands.push(MCOperand::Imm(0));
} else {
eprintln!(
"CONV_GLOBAL: func={} gv={} opcode=0x{:x}",
func_name, g, mi.opcode
);
target_label = Some((g.clone(), false));
mc.operands
.push(MCOperand::Expr(Box::new(MCExpr::symbol(g))));
}
}
}
}
let enc_before = func_bytes.len();
let enc_result = encode_x86_64(&mc);
if let Some(ref enc) = enc_result {
if mi.opcode == x86_opcodes::LEA {
eprintln!(
"LEA_ENC: func={} before={} enclen={} hex={:02x?} target={:?}",
func_name,
enc_before,
enc.len(),
enc.as_slice(),
target_label.as_ref().map(|t| t.0.clone())
);
}
if let Some((ref target, is_branch)) = target_label {
let disp_off = if mi.opcode == x86_opcodes::JMP
|| mi.opcode == x86_opcodes::CALL
{
enc_before + 1
} else if mi.opcode == x86_opcodes::LEA {
enc_before + 3
} else {
enc_before + 2
};
fixups.push((disp_off, target.clone(), is_branch));
}
func_bytes.extend_from_slice(enc);
}
}
}
if !func_name.is_empty() && !func_bytes.is_empty() {
eprintln!("FUNC_PUSH: {} size={}", func_name, func_bytes.len());
func_enc.push((
func_name,
func_bytes,
block_offsets,
block_name_to_idx,
fixups,
));
}
}
let mut fn_name_to_offset: HashMap<String, usize> = HashMap::new();
let mut text_offset = 0usize;
for (name, bytes, _, _, _) in &func_enc {
fn_name_to_offset.insert(name.clone(), text_offset);
fn_name_to_offset.insert(name.clone(), text_offset);
text_offset += bytes.len();
}
let mut rodata_globals: Vec<(String, Vec<u8>)> = Vec::new();
let mut data_globals: Vec<(String, Vec<u8>)> = Vec::new();
let mut global_names: std::collections::HashSet<String> = std::collections::HashSet::new();
for gv_ref in &module.globals {
let gv = gv_ref.borrow();
let data = Self::extract_global_data(&gv);
if let Some(ref bytes) = data {
global_names.insert(gv.name.clone());
if gv.is_constant {
rodata_globals.push((gv.name.clone(), bytes.clone()));
} else {
data_globals.push((gv.name.clone(), bytes.clone()));
}
}
}
let mut functions: Vec<(String, Vec<u8>)> = Vec::new();
let mut external_refs: Vec<(usize, String)> = Vec::new();
let mut current_text_base = 0usize;
for (func_name, mut func_bytes, block_offsets, block_name_to_idx, fixups) in func_enc {
for &(disp_offset, ref target, is_branch) in &fixups {
if let Some(&target_idx) = block_name_to_idx.get(target.as_str()) {
let target_off = block_offsets[target_idx];
let displacement =
if target_off == disp_offset - 1 || target_off == disp_offset - 2 {
0i32
} else {
((target_off as i64) - ((disp_offset + 4) as i64)) as i32
};
func_bytes[disp_offset..disp_offset + 4]
.copy_from_slice(&displacement.to_le_bytes());
} else if let Some(&fn_text_off) = fn_name_to_offset.get(target.as_str()) {
let target_off = fn_text_off;
let abs_current = current_text_base + disp_offset;
let current_end = abs_current + 4;
let displacement = (target_off as i64) - (current_end as i64);
func_bytes[disp_offset..disp_offset + 4]
.copy_from_slice(&(displacement as i32).to_le_bytes());
} else if global_names.contains(target.as_str()) {
let abs_offset = current_text_base + disp_offset;
external_refs.push((abs_offset, target.clone()));
} else {
let abs_offset = current_text_base + disp_offset;
external_refs.push((abs_offset, target.clone()));
}
}
let fb_len = func_bytes.len();
functions.push((func_name, func_bytes));
current_text_base += fb_len;
}
let mut internal_fns = std::collections::HashSet::new();
for func_ref in &module.functions {
let f = func_ref.borrow();
if f.is_internal {
internal_fns.insert(f.name.clone());
}
}
let mut internal_globals = std::collections::HashSet::new();
for gv_ref in &module.globals {
let gv = gv_ref.borrow();
if gv.is_internal {
internal_globals.insert(gv.name.clone());
}
}
if functions.is_empty() && rodata_globals.is_empty() && data_globals.is_empty() {
return Some(Self::build_empty_elf_object());
}
Some(Self::build_elf_object(
&functions,
&internal_fns,
&external_refs,
&rodata_globals,
&data_globals,
&internal_globals,
))
}
fn build_empty_elf_object() -> Vec<u8> {
let mut bytes = Vec::new();
let num_secs = 5u16;
bytes.extend_from_slice(&[0x7F, b'E', b'L', b'F']);
bytes.push(2); bytes.push(1); bytes.push(1); bytes.push(0); bytes.extend_from_slice(&[0u8; 8]);
bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&0x3Eu16.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&64u16.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&64u16.to_le_bytes()); bytes.extend_from_slice(&num_secs.to_le_bytes()); bytes.extend_from_slice(&4u16.to_le_bytes());
let text_offset = bytes.len() as u64;
let text_size = 0u64;
let symtab_offset = bytes.len() as u64;
bytes.extend_from_slice(&[0u8; 24]); let symtab_size = 24u64;
let strtab_offset = bytes.len() as u64;
bytes.push(0);
let strtab_size = 1u64;
let shstrtab_offset = bytes.len() as u64;
bytes.push(0);
bytes.extend_from_slice(b".text");
bytes.push(0);
bytes.extend_from_slice(b".symtab");
bytes.push(0);
bytes.extend_from_slice(b".strtab");
bytes.push(0);
bytes.extend_from_slice(b".shstrtab");
bytes.push(0);
let shstrtab_size = (bytes.len() as u64) - shstrtab_offset;
let shoff = bytes.len() as u64;
Self::write_shdr(&mut bytes, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
Self::write_shdr(&mut bytes, 1, 1, 6, 0, text_offset, text_size, 0, 0, 16, 0);
Self::write_shdr(
&mut bytes,
7,
2,
0,
0,
symtab_offset,
symtab_size,
3,
1,
8,
24,
);
Self::write_shdr(
&mut bytes,
15,
3,
0,
0,
strtab_offset,
strtab_size,
0,
0,
1,
0,
);
Self::write_shdr(
&mut bytes,
23,
3,
0,
0,
shstrtab_offset,
shstrtab_size,
0,
0,
1,
0,
);
bytes[40..48].copy_from_slice(&shoff.to_le_bytes());
bytes
}
fn extract_constant_data(val: &crate::value::Value) -> Option<Vec<u8>> {
if val.subclass != crate::SubclassKind::Constant {
return None;
}
if val.operands.is_empty() {
if let Ok(v) = val.name.parse::<i64>() {
let size = (val.ty.size_in_bytes() as usize).max(1);
let bytes = v.to_le_bytes();
let mut data = vec![0u8; size];
let n = std::cmp::min(size, bytes.len());
data[..n].copy_from_slice(&bytes[..n]);
return Some(data);
}
return None;
}
let mut data = Vec::new();
for op in &val.operands {
let op_val = op.borrow();
if let Some(sub_data) = Self::extract_constant_data(&op_val) {
data.extend_from_slice(&sub_data);
} else {
return None;
}
}
Some(data)
}
fn extract_global_data(gv: &crate::value::Value) -> Option<Vec<u8>> {
if let Some(ref init) = gv.initializer {
let init_val = init.borrow();
if init_val.name == "zeroinitializer" {
let size = gv.ty.size_in_bytes() as usize;
return Some(vec![0u8; size]);
}
if let Some(data) = Self::extract_constant_data(&init_val) {
return Some(data);
}
}
None
}
fn build_elf_object(
functions: &[(String, Vec<u8>)],
module_fns_internal: &std::collections::HashSet<String>,
external_refs: &[(usize, String)],
rodata_globals: &[(String, Vec<u8>)],
data_globals: &[(String, Vec<u8>)],
module_globals_internal: &std::collections::HashSet<String>,
) -> Vec<u8> {
let mut bytes = Vec::new();
let gv_names: std::collections::HashSet<String> = rodata_globals
.iter()
.chain(data_globals.iter())
.map(|(n, _)| n.clone())
.collect();
let mut ext_sym_names: Vec<String> = Vec::new();
let mut rodata_ref_names: Vec<String> = Vec::new();
let mut data_ref_names: Vec<String> = Vec::new();
for (_, ref name) in external_refs {
if gv_names.contains(name.as_str()) {
if rodata_globals.iter().any(|(n, _)| n == name) {
if !rodata_ref_names.iter().any(|n| n == name) {
rodata_ref_names.push(name.clone());
}
}
if data_globals.iter().any(|(n, _)| n == name) {
if !data_ref_names.iter().any(|n| n == name) {
data_ref_names.push(name.clone());
}
}
} else if !ext_sym_names.iter().any(|n| n == name) {
ext_sym_names.push(name.clone());
}
}
let has_rela = !external_refs.is_empty();
let has_rodata = !rodata_globals.is_empty();
let has_data = !data_globals.is_empty();
let text_shndx = 1u16;
let mut next_shndx = 2u16;
let rodata_shndx = if has_rodata {
let idx = next_shndx;
next_shndx += 1;
idx
} else {
0u16
};
let data_shndx = if has_data {
let idx = next_shndx;
next_shndx += 1;
idx
} else {
0u16
};
let rela_shndx = if has_rela {
let idx = next_shndx;
next_shndx += 1;
idx
} else {
0u16
};
let symtab_shndx = next_shndx;
next_shndx += 1;
let strtab_shndx = next_shndx;
next_shndx += 1;
let shstrtab_shndx = next_shndx;
next_shndx += 1;
let num_secs = next_shndx;
bytes.extend_from_slice(&[0x7F, b'E', b'L', b'F']);
bytes.push(2);
bytes.push(1);
bytes.push(1);
bytes.push(0);
bytes.extend_from_slice(&[0u8; 8]);
bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&0x3Eu16.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&64u16.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&64u16.to_le_bytes()); bytes.extend_from_slice(&num_secs.to_le_bytes());
bytes.extend_from_slice(&shstrtab_shndx.to_le_bytes());
let text_offset = bytes.len() as u64;
for (_, code) in functions {
bytes.extend_from_slice(code);
}
let text_size = (bytes.len() as u64) - text_offset;
let rodata_offset = bytes.len() as u64;
let mut rodata_sym_off: std::collections::HashMap<String, u64> =
std::collections::HashMap::new();
if has_rodata {
for (name, data) in rodata_globals {
rodata_sym_off.insert(name.clone(), (bytes.len() - rodata_offset as usize) as u64);
bytes.extend_from_slice(data);
}
}
let rodata_size = (bytes.len() as u64) - rodata_offset;
let data_offset = bytes.len() as u64;
let mut data_sym_off: std::collections::HashMap<String, u64> =
std::collections::HashMap::new();
if has_data {
for (name, data) in data_globals {
data_sym_off.insert(name.clone(), (bytes.len() - data_offset as usize) as u64);
bytes.extend_from_slice(data);
}
}
let data_size = (bytes.len() as u64) - data_offset;
let rela_offset = bytes.len() as u64;
let rela_entry_size = 24u64;
for _ in external_refs {
bytes.extend_from_slice(&[0u8; 24]);
}
let rela_size = (bytes.len() as u64) - rela_offset;
let symtab_offset = bytes.len() as u64;
bytes.extend_from_slice(&[0u8; 24]);
let mut fn_off = std::collections::HashMap::new();
{
let mut o = 0u64;
for (n, c) in functions {
fn_off.insert(n.clone(), o);
o += c.len() as u64;
}
}
let mut str_off = 1u32;
let ws = |b: &mut Vec<u8>, no: u32, inf: u8, sx: u16, v: u64, sz: u64| {
b.extend_from_slice(&no.to_le_bytes());
b.push(inf);
b.push(0);
b.extend_from_slice(&sx.to_le_bytes());
b.extend_from_slice(&v.to_le_bytes());
b.extend_from_slice(&sz.to_le_bytes());
};
let internal_rodata_globals: Vec<&str> = rodata_globals
.iter()
.filter(|(n, _)| module_globals_internal.contains(n.as_str()))
.map(|(n, _)| n.as_str())
.collect();
let external_rodata_globals: Vec<&str> = rodata_globals
.iter()
.filter(|(n, _)| !module_globals_internal.contains(n.as_str()))
.map(|(n, _)| n.as_str())
.collect();
let internal_data_globals: Vec<&str> = data_globals
.iter()
.filter(|(n, _)| module_globals_internal.contains(n.as_str()))
.map(|(n, _)| n.as_str())
.collect();
let external_data_globals: Vec<&str> = data_globals
.iter()
.filter(|(n, _)| !module_globals_internal.contains(n.as_str()))
.map(|(n, _)| n.as_str())
.collect();
for (n, c) in functions {
if module_fns_internal.contains(n.as_str()) {
let off = fn_off.get(n).copied().unwrap_or(0);
ws(&mut bytes, str_off, 0x02, text_shndx, off, c.len() as u64);
str_off += n.len() as u32 + 1;
}
}
for n in &internal_rodata_globals {
let off = rodata_sym_off.get(*n).copied().unwrap_or(0);
ws(&mut bytes, str_off, 0x02, rodata_shndx, off, 0);
str_off += n.len() as u32 + 1;
}
for n in &internal_data_globals {
let off = data_sym_off.get(*n).copied().unwrap_or(0);
ws(&mut bytes, str_off, 0x02, data_shndx, off, 0);
str_off += n.len() as u32 + 1;
}
let local_count =
module_fns_internal.len() + internal_rodata_globals.len() + internal_data_globals.len();
for (n, c) in functions {
if !module_fns_internal.contains(n.as_str()) {
let off = fn_off.get(n).copied().unwrap_or(0);
ws(&mut bytes, str_off, 0x12, text_shndx, off, c.len() as u64);
str_off += n.len() as u32 + 1;
}
}
for n in &external_rodata_globals {
let off = rodata_sym_off.get(*n).copied().unwrap_or(0);
ws(&mut bytes, str_off, 0x12, rodata_shndx, off, 0);
str_off += n.len() as u32 + 1;
}
for n in &external_data_globals {
let off = data_sym_off.get(*n).copied().unwrap_or(0);
ws(&mut bytes, str_off, 0x12, data_shndx, off, 0);
str_off += n.len() as u32 + 1;
}
let global_fn_count = functions.len() - module_fns_internal.len();
let global_sym_count =
global_fn_count + external_rodata_globals.len() + external_data_globals.len();
let first_undef_sym = 1u32 + local_count as u32 + global_sym_count as u32;
for n in ext_sym_names.iter() {
ws(&mut bytes, str_off, 0x12, 0, 0, 0);
str_off += n.len() as u32 + 1;
}
let symtab_size = (bytes.len() as u64) - symtab_offset;
let strtab_offset = bytes.len() as u64;
bytes.push(0);
for (n, _) in functions {
if module_fns_internal.contains(n.as_str()) {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
}
for n in &internal_rodata_globals {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
for n in &internal_data_globals {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
for (n, _) in functions {
if !module_fns_internal.contains(n.as_str()) {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
}
for n in &external_rodata_globals {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
for n in &external_data_globals {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
for n in &ext_sym_names {
bytes.extend_from_slice(n.as_bytes());
bytes.push(0);
}
let strtab_size = (bytes.len() as u64) - strtab_offset;
let local_fn_count = module_fns_internal.len();
let internal_rodata_sym_start = 1u32 + local_fn_count as u32;
let internal_data_sym_start =
internal_rodata_sym_start + internal_rodata_globals.len() as u32;
let global_fn_start = 1u32 + local_count as u32;
let external_rodata_sym_start = global_fn_start + global_fn_count as u32;
let external_data_sym_start =
external_rodata_sym_start + external_rodata_globals.len() as u32;
if has_rela {
let rela_start = rela_offset as usize;
for (i, &(text_off_in_func, ref sym_name)) in external_refs.iter().enumerate() {
let sym_idx = if gv_names.contains(sym_name.as_str()) {
if module_globals_internal.contains(sym_name.as_str()) {
if rodata_ref_names.iter().any(|n| n == sym_name) {
let pos = rodata_ref_names
.iter()
.position(|n| n == sym_name)
.unwrap_or(0);
internal_rodata_sym_start + pos as u32
} else {
let pos = data_ref_names
.iter()
.position(|n| n == sym_name)
.unwrap_or(0);
internal_data_sym_start + pos as u32
}
} else {
if rodata_ref_names.iter().any(|n| n == sym_name) {
let pos = rodata_ref_names
.iter()
.position(|n| n == sym_name)
.unwrap_or(0);
external_rodata_sym_start + pos as u32
} else {
let pos = data_ref_names
.iter()
.position(|n| n == sym_name)
.unwrap_or(0);
external_data_sym_start + pos as u32
}
}
} else {
let pos = ext_sym_names
.iter()
.position(|n| n == sym_name)
.unwrap_or(0);
first_undef_sym + pos as u32
};
let is_data_ref = gv_names.contains(sym_name.as_str());
let r_type: u64 = if is_data_ref { 2 } else { 4 }; let rela_info = (sym_idx as u64) << 32 | r_type;
let entry_off = rela_start + i * 24;
let is_data_ref = gv_names.contains(sym_name.as_str());
bytes[entry_off..entry_off + 8]
.copy_from_slice(&(text_off_in_func as u64).to_le_bytes());
bytes[entry_off + 8..entry_off + 16].copy_from_slice(&rela_info.to_le_bytes());
let r_addend = -4i64;
bytes[entry_off + 16..entry_off + 24].copy_from_slice(&r_addend.to_le_bytes());
}
}
let shstrtab_offset = bytes.len() as u64;
bytes.push(0);
bytes.extend_from_slice(b".text");
bytes.push(0);
if has_rodata {
bytes.extend_from_slice(b".rodata");
bytes.push(0);
}
if has_data {
bytes.extend_from_slice(b".data");
bytes.push(0);
}
if has_rela {
bytes.extend_from_slice(b".rela.text");
bytes.push(0);
}
bytes.extend_from_slice(b".symtab");
bytes.push(0);
bytes.extend_from_slice(b".strtab");
bytes.push(0);
bytes.extend_from_slice(b".shstrtab");
bytes.push(0);
let shstrtab_size = (bytes.len() as u64) - shstrtab_offset;
let mut name_off = 1u32;
let text_soff = name_off;
name_off += b".text".len() as u32 + 1;
let rodata_soff = if has_rodata {
name_off;
name_off += b".rodata".len() as u32 + 1;
name_off - b".rodata".len() as u32 - 1
} else {
0u32
};
let data_soff = if has_data {
name_off;
name_off += b".data".len() as u32 + 1;
name_off - b".data".len() as u32 - 1
} else {
0u32
};
let rela_soff = if has_rela {
name_off;
name_off += b".rela.text".len() as u32 + 1;
name_off - b".rela.text".len() as u32 - 1
} else {
0u32
};
let symtab_soff = name_off;
name_off += b".symtab".len() as u32 + 1;
let strtab_soff = name_off;
name_off += b".strtab".len() as u32 + 1;
let shstrtab_soff = name_off;
let shoff = bytes.len() as u64;
Self::write_shdr(&mut bytes, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
Self::write_shdr(
&mut bytes,
text_soff,
1,
6,
0,
text_offset,
text_size,
0,
0,
16,
0,
);
if has_rodata {
Self::write_shdr(
&mut bytes,
rodata_soff,
1,
2,
0,
rodata_offset,
rodata_size,
0,
0,
16,
0,
);
}
if has_data {
Self::write_shdr(
&mut bytes,
data_soff,
1,
3,
0,
data_offset,
data_size,
0,
0,
16,
0,
);
}
if has_rela {
Self::write_shdr(
&mut bytes,
rela_soff,
4,
0,
0,
rela_offset,
rela_size,
symtab_shndx as u32,
text_shndx as u32,
8,
rela_entry_size,
);
}
Self::write_shdr(
&mut bytes,
symtab_soff,
2,
0,
0,
symtab_offset,
symtab_size,
strtab_shndx as u32,
1 + local_count as u32,
8,
24,
);
Self::write_shdr(
&mut bytes,
strtab_soff,
3,
0,
0,
strtab_offset,
strtab_size,
0,
0,
1,
0,
);
Self::write_shdr(
&mut bytes,
shstrtab_soff,
3,
0,
0,
shstrtab_offset,
shstrtab_size,
0,
0,
1,
0,
);
bytes[40..48].copy_from_slice(&shoff.to_le_bytes());
bytes
}
pub fn stage_encode(
&self,
ir: &str,
source: &SourceFile,
diagnostics: &mut Vec<CompileDiagnostic>,
) -> Option<Vec<u8>> {
let _ = source;
let _ = diagnostics;
use crate::asm_parser;
use crate::codegen::{InstructionSelector, MachineFunction, RegisterAllocator};
use crate::mc_assembler::encode_x86_64;
use crate::mc_inst::{MCInst, MCOperand};
use crate::mc_streamer::x86_opcodes;
let module = match asm_parser::parse_assembly(ir) {
Some(m) => m,
None => {
return self.stage_encode_fallback(ir);
}
};
match self.module_to_object_bytes(&module, "") {
Some(obj) => Some(obj),
None => self.stage_encode_fallback(ir),
}
}
fn stage_encode_fallback(&self, ir: &str) -> Option<Vec<u8>> {
let mut bytes = Vec::new();
let mut functions: Vec<(String, Vec<u8>)> = Vec::new();
let mut current_func = String::new();
let mut in_func = false;
for line in ir.lines() {
let line = line.trim();
if line.starts_with("define ") {
if let Some(at_pos) = line.find('@') {
let after_at = &line[at_pos + 1..];
if let Some(paren_pos) = after_at.find('(') {
current_func = after_at[..paren_pos].to_string();
in_func = true;
}
}
}
if in_func && line.starts_with("ret ") {
let mut func_bytes = Vec::new();
let rest = &line[4..].trim();
if rest.starts_with("void") {
func_bytes.push(0xC3);
} else {
let val_str = if let Some(space_pos) = rest.find(' ') {
rest[space_pos + 1..].trim()
} else {
""
};
let return_val: i64 = val_str.parse().unwrap_or(0);
match return_val {
0 => func_bytes.extend_from_slice(&[0x31, 0xC0]),
val if val >= -128 && val <= 127 => {
func_bytes.push(0xB8);
func_bytes.extend_from_slice(&(val as i32).to_le_bytes());
}
val => {
func_bytes.push(0xB8);
func_bytes.extend_from_slice(&(val as i32).to_le_bytes());
}
}
func_bytes.push(0xC3);
}
if !current_func.is_empty() {
functions.push((current_func.clone(), func_bytes));
}
in_func = false;
}
if in_func && line == "}" {
in_func = false;
}
}
if !functions.is_empty() {
let return_val = if functions[0].1.len() >= 2
&& functions[0].1[0] == 0x31
&& functions[0].1[1] == 0xC0
{
0
} else if functions[0].1.len() >= 5 && functions[0].1[0] == 0xB8 {
let imm: [u8; 4] = [
functions[0].1[1],
functions[0].1[2],
functions[0].1[3],
functions[0].1[4],
];
i32::from_le_bytes(imm) as i64
} else {
0
};
let mut start_code = Vec::new();
start_code.push(0xBF);
start_code.extend_from_slice(&(return_val as i32).to_le_bytes());
start_code.push(0xB8);
start_code.extend_from_slice(&60u32.to_le_bytes());
start_code.extend_from_slice(&[0x0F, 0x05]);
functions.insert(0, ("_start".to_string(), start_code));
} else {
let mut start_code = Vec::new();
start_code.push(0xBF);
start_code.extend_from_slice(&0u32.to_le_bytes());
start_code.push(0xB8);
start_code.extend_from_slice(&60u32.to_le_bytes());
start_code.extend_from_slice(&[0x0F, 0x05]);
functions.push(("_start".to_string(), start_code));
}
let code_size: usize = functions.iter().map(|(_, code)| code.len()).sum();
let num_secs = 5u16;
let e_shoff_pos = 40;
bytes.extend_from_slice(&[0x7F, b'E', b'L', b'F']);
bytes.push(2);
bytes.push(1);
bytes.push(1);
bytes.push(0);
bytes.extend_from_slice(&[0u8; 8]);
bytes.extend_from_slice(&1u16.to_le_bytes());
bytes.extend_from_slice(&0x3Eu16.to_le_bytes());
bytes.extend_from_slice(&1u32.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&64u16.to_le_bytes());
bytes.extend_from_slice(&0u16.to_le_bytes());
bytes.extend_from_slice(&0u16.to_le_bytes());
bytes.extend_from_slice(&64u16.to_le_bytes());
bytes.extend_from_slice(&num_secs.to_le_bytes());
bytes.extend_from_slice(&4u16.to_le_bytes());
let text_offset = bytes.len() as u64;
for (_, code) in &functions {
bytes.extend_from_slice(code);
}
let text_size = (bytes.len() as u64) - text_offset;
let symtab_offset = bytes.len() as u64;
bytes.extend_from_slice(&[0u8; 24]);
let mut str_off = 1u32;
let mut sym_offset_in_text = 0u64;
for (name, code) in &functions {
str_off += name.len() as u32 + 1;
bytes.extend_from_slice(&(str_off - name.len() as u32 - 1).to_le_bytes());
bytes.push(0x12);
bytes.push(0);
bytes.extend_from_slice(&1u16.to_le_bytes());
bytes.extend_from_slice(&sym_offset_in_text.to_le_bytes());
bytes.extend_from_slice(&(code.len() as u64).to_le_bytes());
sym_offset_in_text += code.len() as u64;
}
let symtab_size = (bytes.len() as u64) - symtab_offset;
let strtab_offset = bytes.len() as u64;
bytes.push(0);
for (name, _) in &functions {
bytes.extend_from_slice(name.as_bytes());
bytes.push(0);
}
let strtab_size = (bytes.len() as u64) - strtab_offset;
let shstrtab_offset = bytes.len() as u64;
bytes.push(0);
bytes.extend_from_slice(b".text");
bytes.push(0);
bytes.extend_from_slice(b".symtab");
bytes.push(0);
bytes.extend_from_slice(b".strtab");
bytes.push(0);
bytes.extend_from_slice(b".shstrtab");
bytes.push(0);
let shstrtab_size = (bytes.len() as u64) - shstrtab_offset;
let shoff = bytes.len() as u64;
Self::write_shdr(&mut bytes, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
Self::write_shdr(&mut bytes, 1, 1, 6, 0, text_offset, text_size, 0, 0, 16, 0);
Self::write_shdr(
&mut bytes,
7,
2,
0,
0,
symtab_offset,
symtab_size,
3,
1,
8,
24,
);
Self::write_shdr(
&mut bytes,
15,
3,
0,
0,
strtab_offset,
strtab_size,
0,
0,
1,
0,
);
Self::write_shdr(
&mut bytes,
23,
3,
0,
0,
shstrtab_offset,
shstrtab_size,
0,
0,
1,
0,
);
let shoff_bytes = shoff.to_le_bytes();
for (i, &b) in shoff_bytes.iter().enumerate() {
bytes[e_shoff_pos + i] = b;
}
Some(bytes)
}
fn write_shdr(
bytes: &mut Vec<u8>,
name: u32,
sh_type: u32,
flags: u64,
addr: u64,
offset: u64,
size: u64,
link: u32,
info: u32,
addralign: u64,
entsize: u64,
) {
bytes.extend_from_slice(&name.to_le_bytes());
bytes.extend_from_slice(&sh_type.to_le_bytes());
bytes.extend_from_slice(&flags.to_le_bytes());
bytes.extend_from_slice(&addr.to_le_bytes());
bytes.extend_from_slice(&offset.to_le_bytes());
bytes.extend_from_slice(&size.to_le_bytes());
bytes.extend_from_slice(&link.to_le_bytes());
bytes.extend_from_slice(&info.to_le_bytes());
bytes.extend_from_slice(&addralign.to_le_bytes());
bytes.extend_from_slice(&entsize.to_le_bytes());
}
fn emit_elf_header(bytes: &mut Vec<u8>, is_64bit: bool) {
if is_64bit {
bytes.extend_from_slice(&[0x7F, b'E', b'L', b'F']); bytes.push(2); bytes.push(1); bytes.push(1); bytes.push(0); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[1, 0]); bytes.extend_from_slice(&[0x3E, 0]); bytes.extend_from_slice(&[1, 0, 0, 0]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[0, 0, 0, 0]); bytes.extend_from_slice(&[64, 0]); bytes.extend_from_slice(&[0, 0]); bytes.extend_from_slice(&[0, 0]); bytes.extend_from_slice(&[64, 0]); bytes.extend_from_slice(&[4, 0]); bytes.extend_from_slice(&[3, 0]); } else {
bytes.extend_from_slice(&[0x7F, b'E', b'L', b'F']); bytes.push(1); bytes.push(1); bytes.push(1); bytes.push(0); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[1, 0]); bytes.extend_from_slice(&[3, 0]); bytes.extend_from_slice(&[1, 0, 0, 0]); bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[52, 0]); bytes.extend_from_slice(&[0, 0]); bytes.extend_from_slice(&[0, 0]); bytes.extend_from_slice(&[40, 0]); bytes.extend_from_slice(&[4, 0]); bytes.extend_from_slice(&[3, 0]); }
}
fn emit_minimal_symtab(bytes: &mut Vec<u8>, source: &SourceFile) {
let is_64bit = true; let source_name = source
.path
.file_name()
.unwrap_or_default()
.to_string_lossy();
if is_64bit {
bytes.extend_from_slice(&[0; 24]); } else {
bytes.extend_from_slice(&[0; 16]);
}
if is_64bit {
bytes.extend_from_slice(&[1, 0, 0, 0]); bytes.push(4); bytes.push(0); bytes.extend_from_slice(&[0xFF, 0xFF]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[0; 8]); } else {
bytes.extend_from_slice(&[1, 0, 0, 0]); bytes.push(4); bytes.push(0); bytes.extend_from_slice(&[0xFF, 0xFF]); bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[0; 4]); }
let name_offset = source_name.len() as u32 + 1; if is_64bit {
bytes.extend_from_slice(&name_offset.to_le_bytes());
bytes.push(3); bytes.push(0); bytes.extend_from_slice(&[1, 0]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[0; 8]); }
}
fn emit_section_headers(
bytes: &mut Vec<u8>,
text_offset: usize,
symtab_offset: usize,
strtab_offset: usize,
) {
let _ = symtab_offset;
let is_64bit = true;
if is_64bit {
bytes.extend_from_slice(&[0; 64]);
} else {
bytes.extend_from_slice(&[0; 40]);
}
let text_size = 1u64; if is_64bit {
bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[1, 0, 0, 0]); bytes.extend_from_slice(&[6, 0, 0, 0, 0, 0, 0, 0]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&(text_offset as u64).to_le_bytes()); bytes.extend_from_slice(&text_size.to_le_bytes()); bytes.extend_from_slice(&[0; 4]); bytes.extend_from_slice(&[16, 0, 0, 0, 0, 0, 0, 0]); bytes.extend_from_slice(&[0; 8]); }
let strtab_size = (bytes.len() - strtab_offset) as u64;
if is_64bit {
let name_offset = (b".strtab".len()) as u32;
bytes.extend_from_slice(&name_offset.to_le_bytes());
bytes.extend_from_slice(&[3, 0, 0, 0]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&(strtab_offset as u64).to_le_bytes());
bytes.extend_from_slice(&strtab_size.to_le_bytes());
bytes.extend_from_slice(&[0; 8]); bytes.extend_from_slice(&[1, 0, 0, 0, 0, 0, 0, 0]); bytes.extend_from_slice(&[0; 8]); }
}
pub fn stage_emit_object(
&self,
encoded: &Option<Vec<u8>>,
_source: &SourceFile,
) -> Option<Vec<u8>> {
encoded.clone()
}
fn compute_options_hash(&self) -> u64 {
let mut hash: u64 = 0xcbf29ce484222325;
let opt_bytes = format!(
"{:?}{:?}{:?}{:?}{:?}{}",
self.options.opt_level,
self.options.target_cpu,
self.options.language,
self.options.reloc_model,
self.options.code_model,
self.options.debug_info,
);
for byte in opt_bytes.as_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
fn has_errors(&self, diagnostics: &[CompileDiagnostic]) -> bool {
diagnostics.iter().any(|d| d.level >= DiagLevel::Error)
}
fn failure_result(
&mut self,
source_path: &Path,
start: Instant,
source_lines: usize,
diagnostics: Vec<CompileDiagnostic>,
stage_results: Vec<StageResult>,
) -> X86E2EResult {
self.stats.files_compiled += 1;
self.stats.failed += 1;
self.stats.total_time += start.elapsed();
self.stats.total_source_lines += source_lines;
X86E2EResult {
success: false,
object_bytes: None,
assembly_text: None,
llvm_ir: None,
stage_results,
diagnostics,
total_duration: start.elapsed(),
source_file: source_path.to_path_buf(),
output_file: self.options.output_file.clone(),
source_lines,
machine_instructions: 0,
output_size: 0,
options: self.options.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86TranslationUnit {
pub path: PathBuf,
pub language: X86LanguageStandard,
pub declarations: Vec<X86Decl>,
pub functions: Vec<X86FunctionDecl>,
pub globals: Vec<X86GlobalDecl>,
}
#[derive(Debug, Clone)]
pub enum X86Decl {
Function(X86FunctionDecl),
Variable(X86GlobalDecl),
TypeDef(String, String),
Struct(X86StructDecl),
Enum(X86EnumDecl),
Union(X86UnionDecl),
}
#[derive(Debug, Clone)]
pub struct X86FunctionDecl {
pub name: String,
pub return_type: String,
pub params: Vec<String>,
pub body: Vec<X86Stmt>,
pub is_definition: bool,
pub is_static: bool,
pub is_inline: bool,
pub linkage: String,
}
#[derive(Debug, Clone)]
pub struct X86GlobalDecl {
pub name: String,
pub ty: String,
pub initializer: Option<String>,
pub is_const: bool,
pub linkage: String,
}
#[derive(Debug, Clone)]
pub struct X86StructDecl {
pub name: String,
pub fields: Vec<(String, String)>,
pub is_packed: bool,
}
#[derive(Debug, Clone)]
pub struct X86EnumDecl {
pub name: String,
pub variants: Vec<(String, Option<i64>)>,
pub underlying_type: String,
}
#[derive(Debug, Clone)]
pub struct X86UnionDecl {
pub name: String,
pub fields: Vec<(String, String)>,
}
#[derive(Debug, Clone)]
pub enum X86Stmt {
Return(Option<String>),
Expr(String),
If(String, Box<X86Stmt>, Option<Box<X86Stmt>>),
While(String, Box<X86Stmt>),
For(Option<String>, Option<String>, Option<String>, Box<X86Stmt>),
Block(Vec<X86Stmt>),
Decl(String, String, Option<String>),
Break,
Continue,
Switch(String, Vec<(String, Vec<X86Stmt>)>),
Goto(String),
Label(String),
Asm(String),
}
#[derive(Debug, Clone)]
pub struct CompilationJob {
pub source: PathBuf,
pub output: Option<PathBuf>,
pub language: Option<X86LanguageStandard>,
pub is_primary: bool,
}
#[derive(Debug)]
pub struct CompilationSession {
pub pipeline: X86E2EPipeline,
pub jobs: Vec<CompilationJob>,
pub results: Vec<X86E2EResult>,
pub start_time: Instant,
}
impl CompilationSession {
pub fn new(options: X86E2EOptions) -> Self {
Self {
pipeline: X86E2EPipeline::new(options),
jobs: Vec::new(),
results: Vec::new(),
start_time: Instant::now(),
}
}
pub fn add_job(&mut self, source: impl AsRef<Path>) {
self.jobs.push(CompilationJob {
source: source.as_ref().to_path_buf(),
output: None,
language: None,
is_primary: true,
});
}
pub fn add_job_with_output(&mut self, source: impl AsRef<Path>, output: impl AsRef<Path>) {
self.jobs.push(CompilationJob {
source: source.as_ref().to_path_buf(),
output: Some(output.as_ref().to_path_buf()),
language: None,
is_primary: true,
});
}
pub fn run(&mut self) -> std::io::Result<&[X86E2EResult]> {
for job in self.jobs.clone() {
if let Some(ref lang) = job.language {
self.pipeline.options.language = *lang;
}
if let Some(ref output) = job.output {
self.pipeline.options.output_file = Some(output.clone());
}
let (obj_bytes, diags) = self.pipeline.compile_file(&job.source).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("compile_file returned None for {:?}", job.source),
)
})?;
let success = diags.is_empty();
self.results.push(X86E2EResult {
success,
object_bytes: if !obj_bytes.is_empty() {
Some(obj_bytes.clone())
} else {
None
},
assembly_text: None,
llvm_ir: None,
stage_results: Vec::new(),
diagnostics: diags
.into_iter()
.map(|m| CompileDiagnostic {
level: if m.contains("error") || m.contains("Error") {
DiagLevel::Error
} else {
DiagLevel::Warning
},
message: m,
file: Some(job.source.clone()),
line: None,
column: None,
category: Some(String::new()),
fixit: None,
})
.collect(),
total_duration: self.start_time.elapsed(),
source_file: job.source.clone(),
output_file: job.output.clone(),
source_lines: 0,
machine_instructions: 0,
output_size: obj_bytes.len(),
options: self.pipeline.options.clone(),
});
}
Ok(&self.results)
}
pub fn print_summary(&self) {
println!("\n═══ Compilation Summary ═══");
println!(
" Total time: {:.2}s",
self.start_time.elapsed().as_secs_f64()
);
println!(
" Files: {} ({}, {} failed)",
self.pipeline.stats.files_compiled,
self.pipeline.stats.successful,
self.pipeline.stats.failed,
);
println!(" Object bytes: {}", self.pipeline.stats.total_bytes);
println!(
" Source lines: {}",
self.pipeline.stats.total_source_lines
);
println!();
println!(" Stage Timings:");
let stages = X86PipelineStage::all_stages();
for stage in &stages {
if let Some(dur) = self.pipeline.stats.stage_times.get(stage) {
println!(" {:20} {:>8.3}s", stage.name(), dur.as_secs_f64());
}
}
println!();
println!(
" Cache: {} hits, {} misses ({} entries)",
self.pipeline.stats.cache_stats.hits,
self.pipeline.stats.cache_stats.misses,
self.pipeline.cache.len(),
);
}
pub fn has_failures(&self) -> bool {
self.results.iter().any(|r| !r.success)
}
pub fn error_count(&self) -> usize {
self.results.iter().map(|r| r.error_count()).sum()
}
pub fn warning_count(&self) -> usize {
self.results.iter().map(|r| r.warning_count()).sum()
}
}
fn is_type_token(tok: &str) -> bool {
matches!(
tok,
"void"
| "char"
| "short"
| "int"
| "long"
| "float"
| "double"
| "unsigned"
| "signed"
| "size_t"
| "ssize_t"
| "int8_t"
| "int16_t"
| "int32_t"
| "int64_t"
| "uint8_t"
| "uint16_t"
| "uint32_t"
| "uint64_t"
| "bool"
| "_Bool"
| "struct"
| "enum"
| "union"
| "auto"
| "const"
| "volatile"
| "static"
| "extern"
| "register"
)
}
fn is_ident_token(tok: &str) -> bool {
!tok.is_empty()
&& tok
.chars()
.next()
.map_or(false, |c| c.is_alphabetic() || c == '_')
&& !is_type_token(tok)
&& !matches!(
tok,
"if" | "else"
| "while"
| "for"
| "do"
| "switch"
| "case"
| "default"
| "break"
| "continue"
| "return"
| "goto"
| "sizeof"
| "typedef"
)
}
fn llvm_type_name(ty: &str) -> &'static str {
match ty {
"void" => "void",
"char" | "signed char" | "int8_t" => "i8",
"unsigned char" | "uint8_t" | "_Bool" | "bool" => "i8",
"short" | "short int" | "int16_t" => "i16",
"unsigned short" | "uint16_t" => "i16",
"int" | "signed int" | "int32_t" => "i32",
"unsigned int" | "unsigned" | "uint32_t" => "i32",
"long" | "long int" | "int64_t" => "i64",
"unsigned long" | "uint64_t" => "i64",
"long long" | "long long int" => "i64",
"unsigned long long" => "i64",
"float" => "float",
"double" => "double",
"long double" => "x86_fp80",
"size_t" => "i64",
"ssize_t" => "i64",
_ => "i32",
}
}
fn shadow_extract_module_name(ir: &str) -> String {
for line in ir.lines() {
let trimmed = line.trim();
if let Some(name) = trimmed.strip_prefix("; ModuleID = '") {
if let Some(end) = name.find('\'') {
return name[..end].to_string();
}
}
}
"unknown".into()
}
fn shadow_count_ir_instructions(ir: &str) -> usize {
ir.lines()
.filter(|line| {
let trimmed = line.trim_start();
!trimmed.is_empty()
&& !trimmed.starts_with(';')
&& !trimmed.starts_with('@')
&& !trimmed.starts_with('!')
&& !trimmed.starts_with("define")
&& !trimmed.starts_with("declare")
&& !trimmed.starts_with("source_filename")
&& !trimmed.starts_with("target ")
&& !trimmed.starts_with('%')
&& !trimmed.starts_with('"')
&& trimmed.bytes().any(|b| b >= b'a' && b <= b'z')
})
.count()
}
fn shadow_compute_fingerprint(ir: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(ir.as_bytes());
format!("{:x}", hasher.finalize())
}
pub fn emit_shadow_residual(
phase: &str,
ir: &str,
source_path: &str,
metadata: &BTreeMap<&str, serde_json::Value>,
) {
if std::env::var("SHADOW_RESIDUAL_TRACE").is_err() {
return;
}
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let dir = "shadow_residuals";
let _ = std::fs::create_dir_all(dir);
let mod_name = shadow_extract_module_name(ir);
let instr_count = shadow_count_ir_instructions(ir);
let fingerprint = shadow_compute_fingerprint(ir);
let path = format!("{}/{}_{}.jsonl", dir, phase, ts);
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
{
let mut entry = serde_json::Map::new();
entry.insert("phase".into(), serde_json::Value::String(phase.into()));
entry.insert(
"timestamp_ns".into(),
serde_json::Value::Number(serde_json::Number::from(ts as u64)),
);
entry.insert(
"source_file".into(),
serde_json::Value::String(source_path.into()),
);
entry.insert("module".into(), serde_json::Value::String(mod_name));
entry.insert(
"instr_count".into(),
serde_json::Value::Number(serde_json::Number::from(instr_count as u64)),
);
entry.insert(
"structural_fingerprint".into(),
serde_json::Value::String(fingerprint),
);
entry.insert("ir_snapshot".into(), serde_json::Value::String(ir.into()));
for (k, v) in metadata {
entry.insert(k.to_string(), v.clone());
}
let _ = writeln!(f, "{}", serde_json::Value::Object(entry).to_string());
}
}
fn shadow_residual_active() -> bool {
std::env::var("SHADOW_RESIDUAL_TRACE").is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opt_level_from_str() {
assert_eq!(X86OptLevel::from_str("O0"), Some(X86OptLevel::O0));
assert_eq!(X86OptLevel::from_str("O2"), Some(X86OptLevel::O2));
assert_eq!(X86OptLevel::from_str("Os"), Some(X86OptLevel::Os));
assert_eq!(X86OptLevel::from_str("invalid"), None);
}
#[test]
fn test_opt_level_display() {
assert_eq!(X86OptLevel::O2.to_string(), "-O2");
assert_eq!(X86OptLevel::Os.to_string(), "-Os");
}
#[test]
fn test_target_cpu_features() {
let sandy = X86TargetCPU::SandyBridge;
let features = sandy.features();
assert!(features.contains(&"avx"));
assert!(features.contains(&"sse4.1"));
assert!(!features.contains(&"avx2"));
let haswell = X86TargetCPU::Haswell;
let hw_features = haswell.features();
assert!(hw_features.contains(&"avx2"));
assert!(hw_features.contains(&"fma"));
}
#[test]
fn test_target_cpu_from_str() {
assert_eq!(
X86TargetCPU::from_str("haswell"),
Some(X86TargetCPU::Haswell)
);
assert_eq!(X86TargetCPU::from_str("znver4"), Some(X86TargetCPU::Zen4));
assert_eq!(X86TargetCPU::from_str("nonexistent"), None);
}
#[test]
fn test_language_is_cxx() {
assert!(X86LanguageStandard::Cpp17.is_cxx());
assert!(!X86LanguageStandard::C17.is_cxx());
assert!(X86LanguageStandard::Cpp20.is_cxx());
}
#[test]
fn test_source_file_detect_language() {
assert_eq!(
SourceFile::detect_language(Path::new("hello.c")),
Some(X86LanguageStandard::C17)
);
assert_eq!(
SourceFile::detect_language(Path::new("hello.cpp")),
Some(X86LanguageStandard::Cpp17)
);
assert_eq!(SourceFile::detect_language(Path::new("hello.h")), None);
}
#[test]
fn test_pipeline_creation() {
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
assert_eq!(pipeline.options.opt_level, X86OptLevel::O2);
assert!(pipeline.cache.is_empty());
}
#[test]
fn test_pipeline_stages_order() {
let stages = X86PipelineStage::all_stages();
assert_eq!(stages.len(), 15);
assert_eq!(stages[0], X86PipelineStage::ReadSource);
assert_eq!(stages[14], X86PipelineStage::WriteOutput);
}
#[test]
fn test_compile_simple_c() {
let source = SourceFile::from_string(
"test.c",
"int main() { return 0; }\n",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions {
opt_level: X86OptLevel::O0,
..Default::default()
};
let mut pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(!tokens.is_empty(), "Should produce tokens");
assert!(diags.is_empty(), "Should have no lex errors");
let ast = pipeline.stage_parse(&source, &tokens, &mut diags);
assert!(ast.is_some(), "Should produce AST");
let ir = pipeline.stage_codegen(&source, &ast, &mut diags);
assert!(ir.is_some(), "Should produce IR");
let obj = pipeline.stage_encode(ir.as_ref().unwrap(), &source, &mut diags);
assert!(obj.is_some(), "Should produce object bytes");
}
#[test]
fn test_compile_hello_world() {
let source = SourceFile::from_string(
"hello.c",
r#"#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
"#,
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let mut pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(!tokens.is_empty());
}
#[test]
fn test_cache_lookup_miss() {
let mut cache = CompilationCache::new(10);
let result = cache.lookup(Path::new("test.c"), 12345, 67890);
assert!(result.is_none());
assert_eq!(cache.stats.misses, 1);
}
#[test]
fn test_cache_insert_and_lookup() {
let mut cache = CompilationCache::new(10);
let obj = vec![0xC3, 0x90]; cache.insert(Path::new("test.c"), 12345, 67890, obj.clone());
assert_eq!(cache.len(), 1);
let result = cache.lookup(Path::new("test.c"), 12345, 67890);
assert_eq!(result, Some(obj));
assert_eq!(cache.stats.hits, 1);
}
#[test]
fn test_cache_different_hash_miss() {
let mut cache = CompilationCache::new(10);
cache.insert(Path::new("test.c"), 12345, 67890, vec![1, 2, 3]);
let result = cache.lookup(Path::new("test.c"), 99999, 67890);
assert!(result.is_none());
assert_eq!(cache.stats.misses, 1);
}
#[test]
fn test_cache_eviction() {
let mut cache = CompilationCache::new(2);
cache.insert(Path::new("a.c"), 1, 1, vec![1]);
cache.insert(Path::new("b.c"), 2, 2, vec![2]);
assert_eq!(cache.len(), 2);
cache.insert(Path::new("c.c"), 3, 3, vec![3]);
assert!(cache.len() <= 2);
assert_eq!(cache.stats.evictions, 1);
}
#[test]
fn test_diagnostic_formatting() {
let diag = CompileDiagnostic {
level: DiagLevel::Error,
message: "expected ';' after expression".into(),
file: Some(PathBuf::from("test.c")),
line: Some(42),
column: Some(15),
category: Some("parse".into()),
fixit: Some("insert ';'".into()),
};
let formatted = diag.to_string();
assert!(formatted.contains("test.c:42:15"));
assert!(formatted.contains("error"));
assert!(formatted.contains("expected ';'"));
}
#[test]
fn test_diag_level_ordering() {
assert!(DiagLevel::Error > DiagLevel::Warning);
assert!(DiagLevel::Warning > DiagLevel::Note);
assert!(DiagLevel::Note > DiagLevel::Ignored);
}
#[test]
fn test_session_creation() {
let session = CompilationSession::new(X86E2EOptions::default());
assert!(session.jobs.is_empty());
assert!(session.results.is_empty());
}
#[test]
fn test_result_summary() {
let result = X86E2EResult {
success: true,
object_bytes: Some(vec![0xC3]),
assembly_text: None,
llvm_ir: None,
stage_results: Vec::new(),
diagnostics: Vec::new(),
total_duration: Duration::from_secs_f64(0.5),
source_file: PathBuf::from("hello.c"),
output_file: Some(PathBuf::from("hello.o")),
source_lines: 5,
machine_instructions: 3,
output_size: 1,
options: X86E2EOptions::default(),
};
assert!(result.success);
assert_eq!(result.error_count(), 0);
assert_eq!(result.warning_count(), 0);
}
#[test]
fn test_elf_header_64bit_magic() {
let mut bytes = Vec::new();
X86E2EPipeline::emit_elf_header(&mut bytes, true);
assert_eq!(&bytes[0..4], &[0x7F, b'E', b'L', b'F']);
assert_eq!(bytes[4], 2); assert_eq!(bytes[5], 1); }
#[test]
fn test_elf_header_32bit_magic() {
let mut bytes = Vec::new();
X86E2EPipeline::emit_elf_header(&mut bytes, false);
assert_eq!(&bytes[0..4], &[0x7F, b'E', b'L', b'F']);
assert_eq!(bytes[4], 1); }
#[test]
fn test_lex_simple_function() {
let source = SourceFile::from_string(
"test.c",
"int main() { return 0; }",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.contains(&"int".to_string()));
assert!(tokens.contains(&"main".to_string()));
assert!(tokens.contains(&"return".to_string()));
assert!(tokens.contains(&"0".to_string()));
}
#[test]
fn test_lex_string_literal() {
let source =
SourceFile::from_string("test.c", r#"char *s = "hello";"#, X86LanguageStandard::C17);
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.iter().any(|t| t.contains("hello")));
}
#[test]
fn test_lex_comment_removal() {
let source = SourceFile::from_string(
"test.c",
"int x; // this is a comment\nint y; /* block comment */ int z;",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(!tokens.contains(&"this".to_string()));
assert!(!tokens.contains(&"comment".to_string()));
assert!(tokens.contains(&"int".to_string()));
assert!(tokens.contains(&"x".to_string()));
assert!(tokens.contains(&"y".to_string()));
assert!(tokens.contains(&"z".to_string()));
}
#[test]
fn test_lex_hex_number() {
let source =
SourceFile::from_string("test.c", "int x = 0xDEADBEEF;", X86LanguageStandard::C17);
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.contains(&"0xDEADBEEF".to_string()));
}
#[test]
fn test_preprocess_includes() {
let source = SourceFile::from_string(
"test.c",
"#include <stdio.h>\nint main() { return 0; }\n",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let pp = pipeline.stage_preprocess(&source, &mut diags);
assert!(pp.is_some());
let content = pp.unwrap();
assert!(content.contains("include resolved"));
}
#[test]
fn test_preprocess_defines() {
let source = SourceFile::from_string(
"test.c",
"#define FOO 42\nint x = FOO;\n",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let pp = pipeline.stage_preprocess(&source, &mut diags);
assert!(pp.is_some());
assert!(pp.unwrap().contains("macro defined"));
}
#[test]
fn test_options_hash_deterministic() {
let opts = X86E2EOptions::default();
let pipeline = X86E2EPipeline::new(opts.clone());
let hash1 = pipeline.compute_options_hash();
let hash2 = pipeline.compute_options_hash();
assert_eq!(hash1, hash2);
}
#[test]
fn test_options_hash_different_levels() {
let opts1 = X86E2EOptions {
opt_level: X86OptLevel::O0,
..Default::default()
};
let opts2 = X86E2EOptions {
opt_level: X86OptLevel::O3,
..Default::default()
};
let p1 = X86E2EPipeline::new(opts1);
let p2 = X86E2EPipeline::new(opts2);
assert_ne!(p1.compute_options_hash(), p2.compute_options_hash());
}
#[test]
fn test_llvm_type_mapping() {
assert_eq!(llvm_type_name("int"), "i32");
assert_eq!(llvm_type_name("long"), "i64");
assert_eq!(llvm_type_name("float"), "float");
assert_eq!(llvm_type_name("double"), "double");
assert_eq!(llvm_type_name("void"), "void");
assert_eq!(llvm_type_name("char"), "i8");
}
#[test]
fn test_is_type_token() {
assert!(is_type_token("int"));
assert!(is_type_token("float"));
assert!(is_type_token("struct"));
assert!(!is_type_token("main"));
assert!(!is_type_token("return"));
}
#[test]
fn test_e2e_empty_file() {
let opts = X86E2EOptions {
opt_level: X86OptLevel::O0,
output_format: X86OutputFormat::LlvmIr,
..Default::default()
};
let mut pipeline = X86E2EPipeline::new(opts);
let source = SourceFile::from_string("empty.c", "", X86LanguageStandard::C17);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.is_empty());
assert!(diags.is_empty());
}
#[test]
fn test_e2e_simple_return() {
let source = SourceFile::from_string(
"simple.c",
"int main(void) { return 42; }\n",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions {
opt_level: X86OptLevel::O0,
..Default::default()
};
let mut pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.contains(&"main".to_string()));
assert!(tokens.contains(&"42".to_string()));
let ast = pipeline.stage_parse(&source, &tokens, &mut diags);
let ir = pipeline.stage_codegen(&source, &ast, &mut diags);
assert!(ir.is_some());
let ir_text = ir.unwrap();
assert!(ir_text.contains("@main"));
assert!(ir_text.contains("ret"));
let obj = pipeline.stage_encode(&ir_text, &source, &mut diags);
assert!(obj.is_some());
}
#[test]
fn test_e2e_multiple_functions() {
let source = SourceFile::from_string(
"multi.c",
"int add(int a, int b) { return a + b; }\nint main() { return add(1, 2); }\n",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let mut pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.contains(&"add".to_string()));
assert!(tokens.contains(&"main".to_string()));
}
#[test]
fn test_e2e_static_function() {
let source = SourceFile::from_string(
"static.c",
"static int helper(int x) { return x * 2; }\nint main() { return helper(21); }\n",
X86LanguageStandard::C17,
);
let opts = X86E2EOptions::default();
let mut pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.contains(&"static".to_string()));
assert!(tokens.contains(&"helper".to_string()));
}
#[test]
fn test_e2e_cpp_simple() {
let source = SourceFile::from_string(
"simple.cpp",
"int main() { return 0; }\n",
X86LanguageStandard::Cpp17,
);
let opts = X86E2EOptions {
language: X86LanguageStandard::Cpp17,
..Default::default()
};
let mut pipeline = X86E2EPipeline::new(opts);
let mut diags = Vec::new();
let tokens = pipeline.stage_lex(&source, &mut diags);
assert!(tokens.contains(&"int".to_string()));
assert!(tokens.contains(&"main".to_string()));
}
#[test]
fn test_input_file_combinator() {
assert!(SourceFile::detect_language(Path::new("foo.c")).is_some());
assert!(SourceFile::detect_language(Path::new("foo.cc")).is_some());
assert!(SourceFile::detect_language(Path::new("foo.cpp")).is_some());
assert!(SourceFile::detect_language(Path::new("foo.cxx")).is_some());
assert!(SourceFile::detect_language(Path::new("foo.C")).is_some());
assert!(SourceFile::detect_language(Path::new("foo.h")).is_none());
assert!(SourceFile::detect_language(Path::new("foo.s")).is_none());
}
#[test]
fn test_pipeline_stage_names_unique() {
let stages = X86PipelineStage::all_stages();
let names: HashSet<&str> = stages.iter().map(|s| s.name()).collect();
assert_eq!(names.len(), stages.len(), "All stage names must be unique");
}
#[test]
fn test_options_default_values() {
let opts = X86E2EOptions::default();
assert_eq!(opts.opt_level, X86OptLevel::O2);
assert_eq!(opts.target_cpu, X86TargetCPU::X86_64V3);
assert_eq!(opts.language, X86LanguageStandard::C17);
assert_eq!(opts.template_depth, 1024);
assert_eq!(opts.constexpr_steps, 1_048_576);
assert!(!opts.debug_info);
assert!(!opts.lto);
assert!(!opts.werror);
}
#[test]
fn test_all_target_cpus_have_features() {
let cpus = [
X86TargetCPU::X86_64,
X86TargetCPU::X86_64V2,
X86TargetCPU::X86_64V3,
X86TargetCPU::X86_64V4,
X86TargetCPU::SandyBridge,
X86TargetCPU::Haswell,
X86TargetCPU::Skylake,
X86TargetCPU::IceLake,
X86TargetCPU::AlderLake,
X86TargetCPU::GraniteRapids,
X86TargetCPU::Zen2,
X86TargetCPU::Zen3,
X86TargetCPU::Zen4,
X86TargetCPU::Zen5,
X86TargetCPU::Native,
];
for cpu in &cpus {
let features = cpu.features();
assert!(!features.is_empty(), "CPU {:?} should have features", cpu);
assert!(features.contains(&"sse2"), "CPU {:?} should have SSE2", cpu);
}
}
}