use crate::clang::*;
use crate::x86::*;
use std::collections::{HashMap, HashSet};
pub struct CXX86CodeGen {
pub base: CXXCodeGen,
pub subtarget: X86Subtarget,
pub target_machine: X86TargetMachine,
pub vtable_emitter: X86VTableEmitter,
pub rtti_emitter: X86RTTIEmitter,
pub eh_codegen: X86EHCodeGen,
pub guard_emission: X86GuardEmission,
pub typeinfo_emitter: X86TypeInfoEmitter,
pub ctor_dtor: X86CtorDtorLowering,
pub virtual_dispatch: X86VirtualDispatch,
pub member_pointer: X86MemberPointer,
pub lambda_lowering: X86LambdaLowering,
pub new_delete: X86NewDelete,
pub array_ctor_dtor: X86ArrayCtorDtor,
pub dynamic_cast: X86DynamicCast,
pub typeid_op: X86TypeID,
pub static_init: X86StaticInit,
pub thread_local_init: X86ThreadLocalInit,
pub abi_support: X86CXXABISupport,
pub name_mangling: X86NameMangling,
pub template_instantiation: X86TemplateInstantiation,
pub cxx_modules: X86CXXModules,
pub is_64bit: bool,
pub triple: String,
}
impl CXX86CodeGen {
pub fn new(
base: CXXCodeGen,
subtarget: X86Subtarget,
target_machine: X86TargetMachine,
) -> Self {
let is_64bit = subtarget.is_64_bit;
let triple = target_machine.get_target_triple().to_string();
let vtable_emitter = X86VTableEmitter::new(Mangler::new(), is_64bit, triple.clone());
let rtti_emitter = X86RTTIEmitter::new(Mangler::new(), is_64bit, triple.clone());
let eh_codegen = X86EHCodeGen::new(EHCodeGen::itanium(), is_64bit, triple.clone());
let guard_emission = X86GuardEmission::new(is_64bit, triple.clone());
let typeinfo_emitter = X86TypeInfoEmitter::new(Mangler::new(), is_64bit, triple.clone());
let ctor_dtor = X86CtorDtorLowering::new(is_64bit, triple.clone());
let virtual_dispatch = X86VirtualDispatch::new(is_64bit, triple.clone());
let member_pointer = X86MemberPointer::new(is_64bit, triple.clone());
let lambda_lowering = X86LambdaLowering::new(is_64bit, triple.clone());
let new_delete = X86NewDelete::new(is_64bit, triple.clone());
let array_ctor_dtor = X86ArrayCtorDtor::new(is_64bit, triple.clone());
let dynamic_cast = X86DynamicCast::new(is_64bit, triple.clone());
let typeid_op = X86TypeID::new(is_64bit, triple.clone());
let static_init = X86StaticInit::new(is_64bit, triple.clone());
let thread_local_init = X86ThreadLocalInit::new(is_64bit, triple.clone());
let abi_support = X86CXXABISupport::new(is_64bit, triple.clone(), subtarget.clone());
let name_mangling = X86NameMangling::new(Mangler::new(), is_64bit, triple.clone());
let template_instantiation = X86TemplateInstantiation::new(is_64bit, triple.clone());
let cxx_modules = X86CXXModules::new(is_64bit, triple.clone());
Self {
base,
subtarget,
target_machine,
vtable_emitter,
rtti_emitter,
eh_codegen,
guard_emission,
typeinfo_emitter,
ctor_dtor,
virtual_dispatch,
member_pointer,
lambda_lowering,
new_delete,
array_ctor_dtor,
dynamic_cast,
typeid_op,
static_init,
thread_local_init,
abi_support,
name_mangling,
template_instantiation,
cxx_modules,
is_64bit,
triple,
}
}
pub fn new_x86_64(module_name: &str, standard: CppStandard) -> Self {
let base = CXXCodeGen::new(module_name, "x86_64-unknown-linux-gnu", standard);
let subtarget = X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", "");
let target_machine = X86TargetMachine::new("x86_64-unknown-linux-gnu", "generic", "");
Self::new(base, subtarget, target_machine)
}
pub fn new_x86_32(module_name: &str, standard: CppStandard) -> Self {
let base = CXXCodeGen::new(module_name, "i386-unknown-linux-gnu", standard);
let subtarget = X86Subtarget::new("i386-unknown-linux-gnu", "generic", "");
let target_machine = X86TargetMachine::new("i386-unknown-linux-gnu", "generic", "");
Self::new(base, subtarget, target_machine)
}
pub fn generate(&mut self, tu: &CXXTranslationUnit) -> Result<String, Vec<String>> {
self.base.compile(tu).map(|module| module.dump())
}
pub fn emit_all_vtables(&mut self) -> String {
self.vtable_emitter.emit_all()
}
pub fn emit_all_rtti(&mut self) -> String {
self.rtti_emitter.emit_all()
}
pub fn emit_all_eh_tables(&mut self) -> String {
self.eh_codegen.emit_all()
}
pub fn emit_all_guards(&mut self) -> String {
self.guard_emission.emit_all()
}
pub fn emit_all_typeinfo(&mut self) -> String {
self.typeinfo_emitter.emit_all()
}
pub fn lower_constructor(
&mut self,
class_name: &str,
variant: X86CtorVariant,
) -> Result<String, String> {
self.ctor_dtor.lower_constructor(class_name, variant)
}
pub fn lower_destructor(
&mut self,
class_name: &str,
variant: X86DtorVariant,
) -> Result<String, String> {
self.ctor_dtor.lower_destructor(class_name, variant)
}
pub fn gen_virtual_dispatch(
&mut self,
class_name: &str,
method_name: &str,
vtable_index: u32,
) -> Result<String, String> {
self.virtual_dispatch
.gen_dispatch(class_name, method_name, vtable_index)
}
pub fn lower_member_pointer(
&mut self,
class_name: &str,
member_name: &str,
member_type: &str,
) -> Result<String, String> {
self.member_pointer
.lower(class_name, member_name, member_type)
}
pub fn lower_lambda(
&mut self,
captures: &[LambdaCapture],
body: &str,
) -> Result<String, String> {
self.lambda_lowering.lower(captures, body)
}
pub fn gen_new(&mut self, size: u64, align: u32, nothrow: bool) -> Result<String, String> {
self.new_delete.gen_new(size, align, nothrow)
}
pub fn gen_delete(&mut self, ptr_name: &str, size: u64) -> Result<String, String> {
self.new_delete.gen_delete(ptr_name, size)
}
pub fn lower_array_construction(
&mut self,
class_name: &str,
count: u64,
) -> Result<String, String> {
self.array_ctor_dtor.lower_construction(class_name, count)
}
pub fn lower_array_destruction(
&mut self,
class_name: &str,
count: u64,
) -> Result<String, String> {
self.array_ctor_dtor.lower_destruction(class_name, count)
}
pub fn gen_dynamic_cast(
&mut self,
expr_ptr: &str,
source_type: &str,
target_type: &str,
) -> Result<String, String> {
self.dynamic_cast
.gen_cast(expr_ptr, source_type, target_type)
}
pub fn gen_typeid(&mut self, expr: &str) -> Result<String, String> {
self.typeid_op.gen_typeid(expr)
}
pub fn gen_static_init_guard(&mut self, var_name: &str) -> Result<String, String> {
self.static_init.gen_guard(var_name)
}
pub fn gen_thread_local_init(&mut self, var_name: &str) -> Result<String, String> {
self.thread_local_init.gen_init(var_name)
}
pub fn get_abi_support(&self) -> &X86CXXABISupport {
&self.abi_support
}
pub fn mangle_x86_name(
&mut self,
name: &str,
types: &[String],
is_ctor: bool,
is_dtor: bool,
) -> String {
self.name_mangling.mangle(name, types, is_ctor, is_dtor)
}
pub fn instantiate_template(
&mut self,
template_name: &str,
args: &[String],
) -> Result<String, String> {
self.template_instantiation.instantiate(template_name, args)
}
pub fn process_module(&mut self, module_name: &str, source: &str) -> Result<String, String> {
self.cxx_modules.process(module_name, source)
}
pub fn generate_full(
&mut self,
tu: &CXXTranslationUnit,
) -> Result<X86CompilationResult, Vec<String>> {
let ir = self.generate(tu)?;
let vtables = self.vtable_emitter.emit_all();
let rtti = self.rtti_emitter.emit_all();
let eh = self.eh_codegen.emit_all();
let guards = self.guard_emission.emit_all();
let typeinfo = self.typeinfo_emitter.emit_all();
Ok(X86CompilationResult {
ir,
vtables,
rtti,
eh_tables: eh,
guard_variables: guards,
typeinfo,
triple: self.triple.clone(),
})
}
pub fn is_msvc_abi(&self) -> bool {
self.abi_support.is_msvc_abi()
}
pub fn is_itanium_abi(&self) -> bool {
self.abi_support.is_itanium_abi()
}
}
impl Default for CXX86CodeGen {
fn default() -> Self {
Self::new_x86_64("default_module", CppStandard::Cpp17)
}
}
#[derive(Debug, Clone)]
pub struct X86CompilationResult {
pub ir: String,
pub vtables: String,
pub rtti: String,
pub eh_tables: String,
pub guard_variables: String,
pub typeinfo: String,
pub triple: String,
}
impl X86CompilationResult {
pub fn to_single_module(&self) -> String {
let mut result = String::new();
result.push_str("; === Main IR ===\n");
result.push_str(&self.ir);
result.push_str("\n; === Vtables ===\n");
result.push_str(&self.vtables);
result.push_str("\n; === RTTI ===\n");
result.push_str(&self.rtti);
result.push_str("\n; === EH Tables ===\n");
result.push_str(&self.eh_tables);
result.push_str("\n; === Guard Variables ===\n");
result.push_str(&self.guard_variables);
result.push_str("\n; === Type Info ===\n");
result.push_str(&self.typeinfo);
result
}
pub fn ir_size(&self) -> usize {
self.ir.len()
+ self.vtables.len()
+ self.rtti.len()
+ self.eh_tables.len()
+ self.guard_variables.len()
+ self.typeinfo.len()
}
}
pub struct X86VTableEmitter {
pub mangler: Mangler,
pub entries: Vec<X86VtableRecord>,
pub thunks: Vec<X86ThunkRecord>,
pub is_64bit: bool,
pub triple: String,
pub layouts: std::collections::HashMap<String, VtableLayout>,
pub vtt_entries: Vec<X86VTTRecord>,
pub secondary_vtables: Vec<X86SecondaryVtable>,
}
#[derive(Debug, Clone)]
pub struct X86VtableRecord {
pub class_name: String,
pub mangled_sym: String,
pub num_entries: u32,
pub offset_to_top: i64,
pub is_primary: bool,
pub has_virtual_bases: bool,
pub entries: Vec<X86VtableEntry>,
pub construction_vtables: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86VtableEntry {
pub kind: X86VtableEntryKind,
pub mangled_name: String,
pub this_adjustment: i64,
pub vcall_offset: i64,
pub this_register: X86ThisRegister,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86VtableEntryKind {
OffsetToTop,
RttiPtr,
VFuncPtr,
VirtualBaseOffset,
CompleteDtor,
DeletingDtor,
BaseObjectDtor,
}
impl X86VtableEntryKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::OffsetToTop => "offset_to_top",
Self::RttiPtr => "rtti_ptr",
Self::VFuncPtr => "vfunc_ptr",
Self::VirtualBaseOffset => "vbase_offset",
Self::CompleteDtor => "complete_dtor",
Self::DeletingDtor => "deleting_dtor",
Self::BaseObjectDtor => "base_dtor",
}
}
}
#[derive(Debug, Clone)]
pub struct X86ThunkRecord {
pub mangled_thunk: String,
pub mangled_target: String,
pub this_adjustment: i64,
pub vcall_offset: i64,
}
#[derive(Debug, Clone)]
pub struct X86VTTRecord {
pub base_class: String,
pub vtable_sym: String,
pub offset: u32,
}
#[derive(Debug, Clone)]
pub struct X86SecondaryVtable {
pub primary_class: String,
pub base_class: String,
pub base_offset: i64,
pub layout: VtableLayout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ThisRegister {
Default,
Explicit(u8),
}
impl X86ThisRegister {
pub fn register_name(&self, is_64bit: bool) -> &'static str {
match self {
Self::Default => {
if is_64bit {
"%rdi"
} else {
"%ecx"
}
}
Self::Explicit(reg) => {
if is_64bit {
match reg {
0 => "%rdi",
1 => "%rsi",
2 => "%rdx",
3 => "%rcx",
4 => "%r8",
5 => "%r9",
_ => "%rdi",
}
} else {
"%ecx"
}
}
}
}
}
impl X86VTableEmitter {
pub fn new(mangler: Mangler, is_64bit: bool, triple: String) -> Self {
Self {
mangler,
entries: Vec::new(),
thunks: Vec::new(),
is_64bit,
triple,
layouts: std::collections::HashMap::new(),
vtt_entries: Vec::new(),
secondary_vtables: Vec::new(),
}
}
pub fn ptr_size(&self) -> u32 {
if self.is_64bit {
8
} else {
4
}
}
pub fn ptr_align(&self) -> u32 {
self.ptr_size()
}
pub fn add_vtable(&mut self, class_name: &str, layout: VtableLayout) {
let mangled_sym = self.mangler.mangle_vtable(class_name, None).into_string();
let record = X86VtableRecord {
class_name: class_name.to_string(),
mangled_sym,
num_entries: layout.num_entries as u32,
offset_to_top: layout.offset_to_top,
is_primary: layout.is_primary,
has_virtual_bases: layout.has_virtual_bases,
entries: layout
.entries
.iter()
.map(|e| {
let (kind, mangled_name, this_adjustment, vcall_offset) = match e {
VtableEntry::OffsetToTop(offset) => (
X86VtableEntryKind::OffsetToTop,
format!("{}", offset),
0i64,
0i64,
),
VtableEntry::RttiPtr(mangled) => {
(X86VtableEntryKind::RttiPtr, mangled.to_string(), 0, 0)
}
VtableEntry::VFuncPtr {
mangled_name,
this_adjustment: adj,
vcall_offset: vcall,
} => (
X86VtableEntryKind::VFuncPtr,
mangled_name.to_string(),
*adj,
vcall.unwrap_or(0),
),
VtableEntry::VirtualBaseOffset(offset) => (
X86VtableEntryKind::VirtualBaseOffset,
format!("{}", offset),
0,
0,
),
VtableEntry::CompleteDtor(mangled) => {
(X86VtableEntryKind::CompleteDtor, mangled.to_string(), 0, 0)
}
VtableEntry::DeletingDtor(mangled) => {
(X86VtableEntryKind::DeletingDtor, mangled.to_string(), 0, 0)
}
VtableEntry::BaseObjectDtor(mangled) => (
X86VtableEntryKind::BaseObjectDtor,
mangled.to_string(),
0,
0,
),
};
X86VtableEntry {
kind,
mangled_name,
this_adjustment,
vcall_offset,
this_register: X86ThisRegister::Default,
}
})
.collect(),
construction_vtables: layout.construction_vtables.keys().cloned().collect(),
};
self.layouts.insert(class_name.to_string(), layout);
self.entries.push(record);
}
pub fn add_secondary_vtable(
&mut self,
primary_class: &str,
base_class: &str,
base_offset: i64,
layout: VtableLayout,
) {
self.secondary_vtables.push(X86SecondaryVtable {
primary_class: primary_class.to_string(),
base_class: base_class.to_string(),
base_offset,
layout,
});
}
pub fn add_vtt_entry(&mut self, base_class: &str, vtable_sym: &str, offset: u32) {
self.vtt_entries.push(X86VTTRecord {
base_class: base_class.to_string(),
vtable_sym: vtable_sym.to_string(),
offset,
});
}
pub fn add_thunk(
&mut self,
mangled_thunk: &str,
mangled_target: &str,
this_adjustment: i64,
vcall_offset: i64,
) {
self.thunks.push(X86ThunkRecord {
mangled_thunk: mangled_thunk.to_string(),
mangled_target: mangled_target.to_string(),
this_adjustment,
vcall_offset,
});
}
pub fn emit_all(&self) -> String {
let mut ir = String::new();
ir.push_str("; === X86 Vtables ===\n");
for record in &self.entries {
ir.push_str(&self.emit_vtable_ir(record));
ir.push('\n');
}
for secondary in &self.secondary_vtables {
ir.push_str(&self.emit_secondary_vtable_ir(secondary));
ir.push('\n');
}
if !self.vtt_entries.is_empty() {
ir.push_str(&self.emit_vtt_ir());
ir.push('\n');
}
for thunk in &self.thunks {
ir.push_str(&self.emit_thunk_ir(thunk));
ir.push('\n');
}
ir
}
fn emit_vtable_ir(&self, record: &X86VtableRecord) -> String {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str(&format!(
"@{} = linkonce_odr dso_local global [{} x {}] [\n",
record.mangled_sym, record.num_entries, ptr_ty,
));
for entry in &record.entries {
match entry.kind {
X86VtableEntryKind::OffsetToTop => {
let offset = if self.is_64bit {
record.offset_to_top
} else {
record.offset_to_top as i32 as i64
};
ir.push_str(&format!(" {} {}, ; offset_to_top\n", ptr_ty, offset));
}
X86VtableEntryKind::RttiPtr => {
ir.push_str(&format!(
" {} ptrtoint ({{ ... }}* @{} to {}), ; rtti_ptr\n",
ptr_ty, entry.mangled_name, ptr_ty,
));
}
X86VtableEntryKind::VFuncPtr => {
ir.push_str(&format!(
" {} ptrtoint (void ()* @{} to {}), ; vfunc\n",
ptr_ty, entry.mangled_name, ptr_ty,
));
}
X86VtableEntryKind::VirtualBaseOffset => {
ir.push_str(&format!(" {} 0, ; vbase_offset placeholder\n", ptr_ty));
}
X86VtableEntryKind::CompleteDtor => {
ir.push_str(&format!(
" {} ptrtoint (void ()* @{} to {}), ; complete dtor\n",
ptr_ty, entry.mangled_name, ptr_ty,
));
}
X86VtableEntryKind::DeletingDtor => {
ir.push_str(&format!(
" {} ptrtoint (void ()* @{} to {}), ; deleting dtor\n",
ptr_ty, entry.mangled_name, ptr_ty,
));
}
X86VtableEntryKind::BaseObjectDtor => {
ir.push_str(&format!(
" {} ptrtoint (void ()* @{} to {}), ; base dtor\n",
ptr_ty, entry.mangled_name, ptr_ty,
));
}
}
}
ir.push_str("], align 8\n");
ir
}
fn emit_secondary_vtable_ir(&self, secondary: &X86SecondaryVtable) -> String {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
let secondary_sym = format!("_ZTS{}", secondary.base_class);
let primary_sym = format!("_ZTV{}", secondary.primary_class);
ir.push_str(&format!(
"@{} = linkonce_odr dso_local global [{} x {}] [\n",
secondary_sym, secondary.layout.num_entries, ptr_ty,
));
ir.push_str(&format!(
" {} {}, ; offset_to_top ({})\n",
ptr_ty, -secondary.base_offset, secondary.base_class,
));
ir.push_str(&format!(
" {} ptrtoint ({{ ... }}* @{} to {}), ; RTTI\n",
ptr_ty, primary_sym, ptr_ty,
));
ir.push_str("], align 8\n");
ir
}
fn emit_vtt_ir(&self) -> String {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str("; === X86 VTT ===\n");
for (i, entry) in self.vtt_entries.iter().enumerate() {
ir.push_str(&format!(
"@__VTT_{}_{} = linkonce_odr dso_local global [1 x {}] [\n",
entry.base_class, i, ptr_ty,
));
ir.push_str(&format!(
" {} ptrtoint ({{ ... }}* @{} to {}),\n",
ptr_ty, entry.vtable_sym, ptr_ty,
));
ir.push_str("], align 8\n");
}
ir
}
fn emit_thunk_ir(&self, thunk: &X86ThunkRecord) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"define linkonce_odr void @{}(i8* %this) {{\n",
thunk.mangled_thunk,
));
if thunk.this_adjustment != 0 {
if self.is_64bit {
ir.push_str(&format!(
" %adjusted = getelementptr inbounds i8, i8* %this, i64 {}\n",
thunk.this_adjustment,
));
} else {
ir.push_str(&format!(
" %adjusted = getelementptr inbounds i8, i8* %this, i32 {}\n",
thunk.this_adjustment,
));
}
} else {
ir.push_str(" %adjusted = %this\n");
}
if thunk.vcall_offset != 0 {
if self.is_64bit {
ir.push_str(&format!(
" %vcall_base = getelementptr inbounds i8, i8* %adjusted, i64 {}\n",
thunk.vcall_offset,
));
} else {
ir.push_str(&format!(
" %vcall_base = getelementptr inbounds i8, i8* %adjusted, i32 {}\n",
thunk.vcall_offset,
));
}
ir.push_str(" %final = %vcall_base\n");
} else {
ir.push_str(" %final = %adjusted\n");
}
ir.push_str(&format!(
" tail call void @{}(i8* %final)\n",
thunk.mangled_target,
));
ir.push_str(" ret void\n");
ir.push_str("}\n");
ir
}
pub fn gen_virtual_call_ir(
&self,
object_ptr: &str,
vtable_index: u32,
func_signature: &str,
) -> String {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let idx_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
" %vtable = load {}*, {}** {}\n\
\x20 %vfunc_slot = getelementptr inbounds {}, {}* %vtable, {} {}\n\
\x20 %vfunc_ptr = load {}, {}* %vfunc_slot\n\
\x20 %vfunc = inttoptr {} {}* %vfunc_ptr to {}*\n\
\x20 call {} %vfunc(i8* {})\n",
ptr_ty,
ptr_ty,
object_ptr,
ptr_ty,
ptr_ty,
idx_ty,
vtable_index,
ptr_ty,
ptr_ty,
ptr_ty,
ptr_ty,
func_signature,
func_signature,
object_ptr,
)
}
}
impl Default for X86VTableEmitter {
fn default() -> Self {
Self::new(
Mangler::default(),
true,
"x86_64-unknown-linux-gnu".to_string(),
)
}
}
pub struct X86RTTIEmitter {
pub mangler: Mangler,
pub descriptors: Vec<X86RTTIDescriptor>,
pub is_64bit: bool,
pub triple: String,
pub typeinfo_vtable: Option<String>,
pub config: RttiEmissionConfig,
pub base_infos: std::collections::HashMap<String, Vec<X86BaseClassInfo>>,
}
#[derive(Debug, Clone)]
pub struct X86RTTIDescriptor {
pub type_name: String,
pub typeinfo_sym: String,
pub name_sym: String,
pub kind: X86RTTIKind,
pub vtable_ptr: Option<String>,
pub name_string: String,
pub base_type: Option<String>,
pub base_info: Vec<X86BaseClassInfo>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RTTIKind {
Fundamental,
Pointer,
PointerToMember,
Array,
Function,
Enum,
Class,
SiClass,
VmiClass,
}
impl X86RTTIKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Fundamental => "fundamental",
Self::Pointer => "pointer",
Self::PointerToMember => "ptr_to_member",
Self::Array => "array",
Self::Function => "function",
Self::Enum => "enum",
Self::Class => "class",
Self::SiClass => "si_class",
Self::VmiClass => "vmi_class",
}
}
}
#[derive(Debug, Clone)]
pub struct X86BaseClassInfo {
pub typeinfo_sym: String,
pub flags: u32,
pub offset: i64,
}
impl X86RTTIEmitter {
pub fn new(mangler: Mangler, is_64bit: bool, triple: String) -> Self {
Self {
mangler,
descriptors: Vec::new(),
is_64bit,
triple,
typeinfo_vtable: None,
config: RttiEmissionConfig::new(&triple),
base_infos: std::collections::HashMap::new(),
}
}
pub fn set_typeinfo_vtable(&mut self, vtable_sym: &str) {
self.typeinfo_vtable = Some(vtable_sym.to_string());
}
pub fn ptr_type(&self) -> &'static str {
if self.is_64bit {
"i64"
} else {
"i32"
}
}
pub fn build_fundamental(&mut self, type_name: &str) -> &X86RTTIDescriptor {
let typeinfo_sym = self.mangler.mangle_typeinfo(type_name);
let name_sym = self.mangler.mangle_typeinfo_name(type_name);
let desc = X86RTTIDescriptor {
type_name: type_name.to_string(),
typeinfo_sym: typeinfo_sym.clone().into_string(),
name_sym: name_sym.into_string(),
kind: X86RTTIKind::Fundamental,
vtable_ptr: None,
name_string: type_name.to_string(),
base_type: None,
base_info: Vec::new(),
};
self.descriptors.push(desc);
self.descriptors.last().unwrap()
}
pub fn build_class(
&mut self,
type_name: &str,
class_name: &str,
vtable_sym: &str,
bases: &[X86BaseClassInfo],
) -> &X86RTTIDescriptor {
let typeinfo_sym = self.mangler.mangle_typeinfo(class_name);
let name_sym = self.mangler.mangle_typeinfo_name(class_name);
let kind = if bases.is_empty() {
X86RTTIKind::Class
} else if bases.len() == 1 {
X86RTTIKind::SiClass
} else {
X86RTTIKind::VmiClass
};
let desc = X86RTTIDescriptor {
type_name: type_name.to_string(),
typeinfo_sym: typeinfo_sym.clone().into_string(),
name_sym: name_sym.into_string(),
kind,
vtable_ptr: Some(vtable_sym.to_string()),
name_string: class_name.to_string(),
base_type: None,
base_info: bases.to_vec(),
};
self.descriptors.push(desc);
self.descriptors.last().unwrap()
}
pub fn build_pointer(
&mut self,
pointee_type: &str,
pointee_typeinfo_sym: &str,
) -> &X86RTTIDescriptor {
let typeinfo_sym = self.mangler.mangle_typeinfo(&format!("P{}", pointee_type));
let name_sym = self
.mangler
.mangle_typeinfo_name(&format!("P{}", pointee_type));
let desc = X86RTTIDescriptor {
type_name: format!("{}*", pointee_type),
typeinfo_sym: typeinfo_sym.clone().into_string(),
name_sym: name_sym.into_string(),
kind: X86RTTIKind::Pointer,
vtable_ptr: None,
name_string: format!("P{}", pointee_type),
base_type: Some(pointee_typeinfo_sym.to_string()),
base_info: Vec::new(),
};
self.descriptors.push(desc);
self.descriptors.last().unwrap()
}
pub fn build_array(
&mut self,
element_type: &str,
element_typeinfo_sym: &str,
bounds: u64,
) -> &X86RTTIDescriptor {
let type_name = format!("A{}_{}", bounds, element_type);
let typeinfo_sym = self.mangler.mangle_typeinfo(&type_name);
let name_sym = self.mangler.mangle_typeinfo_name(&type_name);
let desc = X86RTTIDescriptor {
type_name,
typeinfo_sym: typeinfo_sym.clone().into_string(),
name_sym: name_sym.into_string(),
kind: X86RTTIKind::Array,
vtable_ptr: None,
name_string: format!("{}[{}]", element_type, bounds),
base_type: Some(element_typeinfo_sym.to_string()),
base_info: Vec::new(),
};
self.descriptors.push(desc);
self.descriptors.last().unwrap()
}
pub fn emit_all(&self) -> String {
let mut ir = String::new();
ir.push_str("; === X86 RTTI ===\n");
for desc in &self.descriptors {
ir.push_str(&self.emit_descriptor_ir(desc));
ir.push('\n');
}
ir
}
fn emit_descriptor_ir(&self, desc: &X86RTTIDescriptor) -> String {
let ptr_ty = self.ptr_type();
let mut ir = String::new();
match desc.kind {
X86RTTIKind::Fundamental => {
ir.push_str(&format!(
"@{} = linkonce_odr constant {{ i8* }} {{\n i8* getelementptr inbounds ",
desc.typeinfo_sym,
));
ir.push_str(&format!(
"({{ [{} x i8] }}, {{ [{} x i8] }}* @{}, i{} 0, i{} 0)\n",
desc.name_string.len() + 1,
desc.name_string.len() + 1,
desc.name_sym.into_string(),
if self.is_64bit { "64" } else { "32" },
if self.is_64bit { "64" } else { "32" },
));
ir.push_str("}, align 8\n");
}
X86RTTIKind::Class | X86RTTIKind::SiClass | X86RTTIKind::VmiClass => {
ir.push_str(&self.emit_class_typeinfo_ir(desc));
}
X86RTTIKind::Pointer => {
ir.push_str(&format!(
"@{} = linkonce_odr constant {{ i8*, i8* }} {{\n",
desc.typeinfo_sym,
));
ir.push_str(&format!(
" i8* getelementptr inbounds ({{ [{} x i8] }}, ",
desc.name_string.len() + 1,
));
ir.push_str(&format!(
"{{ [{} x i8] }}* @{}, i{} 0, i{} 0),\n",
desc.name_string.len() + 1,
desc.name_sym.into_string(),
if self.is_64bit { "64" } else { "32" },
if self.is_64bit { "64" } else { "32" },
));
if let Some(ref base) = desc.base_type {
ir.push_str(&format!(" i8* bitcast ({{ i8* }}* @{} to i8*)\n", base,));
}
ir.push_str("}, align 8\n");
}
X86RTTIKind::Array => {
ir.push_str(&self.emit_array_typeinfo_ir(desc));
}
_ => {
ir.push_str(&format!("@{} = external constant i8\n", desc.typeinfo_sym,));
}
}
ir.push_str(&format!(
"@{} = linkonce_odr constant [{} x i8] c\"{}\\00\"\n",
desc.name_sym.into_string(),
desc.name_string.len() + 1,
desc.name_string,
));
ir
}
fn emit_class_typeinfo_ir(&self, desc: &X86RTTIDescriptor) -> String {
let ptr_ty = self.ptr_type();
let mut ir = String::new();
ir.push_str(&format!(
"@{} = linkonce_odr constant {{ i8*, i8* }} {{\n",
desc.typeinfo_sym,
));
ir.push_str(&format!(
" i8* getelementptr inbounds ({{ [{} x i8] }}, ",
desc.name_string.len() + 1,
));
ir.push_str(&format!(
"{{ [{} x i8] }}* @{}, i{} 0, i{} 0),\n",
desc.name_string.len() + 1,
desc.name_sym.into_string(),
if self.is_64bit { "64" } else { "32" },
if self.is_64bit { "64" } else { "32" },
));
if let Some(ref vtable) = desc.vtable_ptr {
ir.push_str(&format!(" i8* bitcast ({{ ... }}* @{} to i8*)\n", vtable,));
}
ir.push_str("}, align 8\n");
ir
}
fn emit_array_typeinfo_ir(&self, desc: &X86RTTIDescriptor) -> String {
let ptr_ty = self.ptr_type();
let mut ir = String::new();
ir.push_str(&format!(
"@{} = linkonce_odr constant {{ i8*, i8* }} {{\n",
desc.typeinfo_sym,
));
ir.push_str(&format!(
" i8* getelementptr inbounds ({{ [{} x i8] }}, ",
desc.name_string.len() + 1,
));
ir.push_str(&format!(
"{{ [{} x i8] }}* @{}, i{} 0, i{} 0),\n",
desc.name_string.len() + 1,
desc.name_sym.into_string(),
if self.is_64bit { "64" } else { "32" },
if self.is_64bit { "64" } else { "32" },
));
if let Some(ref base) = desc.base_type {
ir.push_str(&format!(" i8* bitcast ({{ i8* }}* @{} to i8*)\n", base,));
}
ir.push_str("}, align 8\n");
ir
}
pub fn add_base_class_info(
&mut self,
class_name: &str,
base_typeinfo_sym: &str,
flags: u32,
offset: i64,
) {
let info = X86BaseClassInfo {
typeinfo_sym: base_typeinfo_sym.to_string(),
flags,
offset,
};
self.base_infos
.entry(class_name.to_string())
.or_insert_with(Vec::new)
.push(info);
}
pub fn get_descriptor(&self, type_name: &str) -> Option<&X86RTTIDescriptor> {
self.descriptors.iter().find(|d| d.type_name == type_name)
}
pub fn rtti_enabled(&self) -> bool {
self.config.rtti_enabled
}
pub fn disable_rtti(&mut self) {
self.config.disable_rtti();
}
}
impl Default for X86RTTIEmitter {
fn default() -> Self {
Self::new(
Mangler::default(),
true,
"x86_64-unknown-linux-gnu".to_string(),
)
}
}
pub struct X86EHCodeGen {
pub base_eh: EHCodeGen,
pub is_64bit: bool,
pub triple: String,
pub lsdas: Vec<X86LSDA>,
pub personality: X86PersonalityKind,
pub lp_counter: u32,
pub type_infos: Vec<String>,
pub call_sites: Vec<X86CallSiteEntry>,
pub actions: Vec<X86ActionRecord>,
pub type_table: Vec<String>,
pub use_dwarf_eh: bool,
pub use_seh: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86PersonalityKind {
ItaniumGCC,
ItaniumSEH,
MSVC,
CSpecific,
}
impl X86PersonalityKind {
pub fn function_name(&self) -> &'static str {
match self {
Self::ItaniumGCC => "__gxx_personality_v0",
Self::ItaniumSEH => "__gxx_personality_seh0",
Self::MSVC => "__CxxFrameHandler3",
Self::CSpecific => "__C_specific_handler",
}
}
pub fn is_itanium(&self) -> bool {
matches!(self, Self::ItaniumGCC | Self::ItaniumSEH)
}
pub fn is_msvc(&self) -> bool {
matches!(self, Self::MSVC)
}
}
#[derive(Debug, Clone)]
pub struct X86LSDA {
pub function_name: String,
pub lp_start_offset: i32,
pub ttype_offset: u8,
pub call_sites: Vec<X86CallSiteEntry>,
pub actions: Vec<X86ActionRecord>,
pub type_table: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86CallSiteEntry {
pub try_begin_offset: u32,
pub try_end_offset: u32,
pub landing_pad_offset: u32,
pub action_record: u32,
}
#[derive(Debug, Clone)]
pub struct X86ActionRecord {
pub filter_index: i32,
pub next_action: u32,
}
#[derive(Debug, Clone)]
pub struct X86LandingPad {
pub label: String,
pub catch_types: Vec<String>,
pub has_cleanup: bool,
pub is_catch_all: bool,
pub catch_selectors: Vec<u32>,
}
impl X86EHCodeGen {
pub fn new(base_eh: EHCodeGen, is_64bit: bool, triple: String) -> Self {
let use_seh = triple.contains("windows") || triple.contains("msvc");
let personality = if use_seh {
X86PersonalityKind::MSVC
} else {
X86PersonalityKind::ItaniumGCC
};
Self {
base_eh,
is_64bit,
triple,
lsdas: Vec::new(),
personality,
lp_counter: 0,
type_infos: Vec::new(),
call_sites: Vec::new(),
actions: Vec::new(),
type_table: Vec::new(),
use_dwarf_eh: !use_seh,
use_seh,
}
}
pub fn new_itanium(base_eh: EHCodeGen, is_64bit: bool) -> Self {
Self::new(base_eh, is_64bit, "x86_64-unknown-linux-gnu".to_string())
}
pub fn new_seh(base_eh: EHCodeGen, is_64bit: bool) -> Self {
Self::new(base_eh, is_64bit, "x86_64-pc-windows-msvc".to_string())
}
pub fn next_lp_label(&mut self) -> String {
self.lp_counter += 1;
format!("L.lpad{}", self.lp_counter)
}
pub fn register_typeinfo(&mut self, typeinfo_sym: &str) {
if !self.type_infos.contains(&typeinfo_sym.to_string()) {
self.type_infos.push(typeinfo_sym.to_string());
}
}
pub fn add_call_site(
&mut self,
try_begin: u32,
try_end: u32,
landing_pad: u32,
action_index: u32,
) {
self.call_sites.push(X86CallSiteEntry {
try_begin_offset: try_begin,
try_end_offset: try_end,
landing_pad_offset: landing_pad,
action_record: action_index,
});
}
pub fn add_catch_all(&mut self, try_begin: u32, try_end: u32, landing_pad: u32) {
self.call_sites.push(X86CallSiteEntry {
try_begin_offset: try_begin,
try_end_offset: try_end,
landing_pad_offset: landing_pad,
action_record: 1,
});
}
pub fn add_type(&mut self, typeinfo_sym: &str) {
if !self.type_table.contains(&typeinfo_sym.to_string()) {
self.type_table.push(typeinfo_sym.to_string());
}
}
pub fn emit_all(&self) -> String {
let mut ir = String::new();
ir.push_str("; === X86 Exception Handling Tables ===\n");
ir.push_str(&self.emit_personality_decl());
ir.push_str(&self.emit_runtime_decls());
for lsda in &self.lsdas {
ir.push_str(&self.emit_lsda_ir(lsda));
ir.push('\n');
}
ir
}
pub fn emit_personality_decl(&self) -> String {
format!(
"declare dso_local i32 @{}(i8*, i8*, i8*, i8*)\n",
self.personality.function_name(),
)
}
pub fn emit_runtime_decls(&self) -> String {
let mut ir = String::new();
ir.push_str("declare dso_local i8* @__cxa_begin_catch(i8*)\n");
ir.push_str("declare dso_local void @__cxa_end_catch()\n");
ir.push_str("declare dso_local void @__cxa_rethrow()\n");
ir.push_str("declare dso_local void @__cxa_throw(i8*, i8*, i8*)\n");
ir.push_str("declare dso_local i8* @__cxa_allocate_exception(i64)\n");
ir.push_str("declare dso_local void @__cxa_free_exception(i8*)\n");
ir.push_str("declare dso_local i8* @llvm.eh.exception.pointer(i8)\n");
ir.push_str("declare dso_local i32 @llvm.eh.selector(i8*, i8*, ...)\n");
ir.push_str("declare dso_local i32 @llvm.eh.typeid.for(i8*)\n");
ir.push_str("declare dso_local void @__clang_call_terminate(i8*)\n");
ir
}
pub fn emit_landing_pad_ir(&self, lp: &X86LandingPad) -> String {
let mut ir = String::new();
ir.push_str(&format!("{}:\n", lp.label));
if self.is_64bit {
ir.push_str(" %eh.ptr = landingpad { i8*, i32 }\n");
} else {
ir.push_str(" %eh.ptr = landingpad { i8*, i32 }\n");
}
if lp.has_cleanup {
ir.push_str(" cleanup\n");
}
for (i, catch_type) in lp.catch_types.iter().enumerate() {
ir.push_str(&format!(
" catch i8* bitcast ({{ i8*, i8* }}* @{} to i8*)\n",
catch_type,
));
lp.catch_selectors.push(i as u32);
}
if lp.is_catch_all {
ir.push_str(" catch i8* null\n");
}
ir.push_str(" %eh.exn = extractvalue { i8*, i32 } %eh.ptr, 0\n");
ir.push_str(" %eh.sel = extractvalue { i8*, i32 } %eh.ptr, 1\n");
if !lp.catch_types.is_empty() || lp.is_catch_all {
ir.push_str(" %eh.bc = call i8* @__cxa_begin_catch(i8* %eh.exn)\n");
}
ir
}
pub fn emit_throw_ir(
&self,
exception_ptr: &str,
exception_typeinfo: &str,
exception_dtor: Option<&str>,
) -> String {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let size = if self.is_64bit { "i64 8" } else { "i32 4" };
let mut ir = String::new();
ir.push_str(&format!(
" %throw.typeinfo = bitcast {{ i8*, i8* }}* @{} to i8*\n",
exception_typeinfo,
));
if let Some(dtor) = exception_dtor {
ir.push_str(&format!(
" call void @__cxa_throw(i8* {}, i8* %throw.typeinfo, i8* bitcast (void (i8*)* @{} to i8*))\n",
exception_ptr, dtor,
));
} else {
ir.push_str(&format!(
" call void @__cxa_throw(i8* {}, i8* %throw.typeinfo, i8* null)\n",
exception_ptr,
));
}
ir.push_str(" unreachable\n");
ir
}
pub fn emit_rethrow_ir(&self) -> String {
let mut ir = String::new();
ir.push_str(" call void @__cxa_rethrow()\n");
ir.push_str(" unreachable\n");
ir
}
pub fn emit_begin_catch_ir(&self, exception_ptr: &str) -> String {
format!(
" %catch.ptr = call i8* @__cxa_begin_catch(i8* {})\n",
exception_ptr,
)
}
pub fn emit_end_catch_ir(&self) -> String {
" call void @__cxa_end_catch()\n".to_string()
}
pub fn emit_terminate_ir(&self) -> String {
" call void @__clang_call_terminate(i8* null)\n unreachable\n".to_string()
}
fn emit_lsda_ir(&self, lsda: &X86LSDA) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"@gcc_except_table_{} = linkonce_odr constant {{ ... }}\n",
lsda.function_name,
));
ir.push_str("; LSDA header + call sites + actions + type table\n");
ir
}
pub fn build_lsda(&mut self, function_name: &str) -> X86LSDA {
X86LSDA {
function_name: function_name.to_string(),
lp_start_offset: 0,
ttype_offset: 2, call_sites: std::mem::take(&mut self.call_sites),
actions: std::mem::take(&mut self.actions),
type_table: std::mem::take(&mut self.type_table),
}
}
pub fn begin_function(&mut self) {
self.lp_counter = 0;
self.call_sites.clear();
self.actions.clear();
self.type_table.clear();
}
pub fn end_function(&mut self, function_name: &str) -> X86LSDA {
let lsda = self.build_lsda(function_name);
self.lsdas.push(lsda.clone());
lsda
}
pub fn lower_try_catch(
&mut self,
try_label: &str,
catch_labels: &[String],
catch_types: &[String],
end_label: &str,
) -> String {
let lp_label = self.next_lp_label();
let mut ir = String::new();
ir.push_str(&format!("{}:\n", try_label));
ir.push_str(&format!(" invoke void @try_body()\n"));
ir.push_str(&format!(
" to label %{} unwind label %{}\n",
end_label, lp_label
));
let mut lp = X86LandingPad {
label: lp_label.clone(),
catch_types: catch_types.to_vec(),
has_cleanup: false,
is_catch_all: catch_types.is_empty(),
catch_selectors: Vec::new(),
};
ir.push_str(&self.emit_landing_pad_ir(&lp));
for (i, (label, _)) in catch_labels.iter().zip(catch_types.iter()).enumerate() {
ir.push_str(&format!(" %match{} = icmp eq i32 %eh.sel, {}\n", i, i + 1,));
ir.push_str(&format!(
" br i1 %match{}, label %{}, label %dispatch_next{}\n",
i, label, i,
));
ir.push_str(&format!("dispatch_next{}:\n", i));
}
ir.push_str(&format!(" br label %rethrow_or_end\n",));
ir.push_str("rethrow_or_end:\n");
ir.push_str(" call void @__cxa_rethrow()\n");
ir.push_str(" unreachable\n");
ir
}
}
impl Default for X86EHCodeGen {
fn default() -> Self {
Self::new(
EHCodeGen::itanium(),
true,
"x86_64-unknown-linux-gnu".to_string(),
)
}
}
pub struct X86GuardEmission {
pub is_64bit: bool,
pub triple: String,
pub guard_variables: Vec<X86GuardVariable>,
pub guard_counter: u64,
}
#[derive(Debug, Clone)]
pub struct X86GuardVariable {
pub mangled_name: String,
pub protected_var: String,
pub size: u32,
pub emitted: bool,
pub initial_value: u64,
pub acquired_value: u64,
}
impl X86GuardVariable {
pub const UNINITIALIZED: u64 = 0;
pub const INITIALIZING: u64 = 1;
pub const INITIALIZED: u64 = u64::MAX;
}
impl X86GuardEmission {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
guard_variables: Vec::new(),
guard_counter: 0,
}
}
pub fn create_guard(&mut self, mangler: &Mangler, var_name: &str) -> X86GuardVariable {
let cxx_name = CXXName::new(var_name);
let guard_name = mangler.mangle_guard_variable(&cxx_name).into_string();
let guard = X86GuardVariable {
mangled_name: guard_name,
protected_var: var_name.to_string(),
size: if self.is_64bit { 8 } else { 4 },
emitted: false,
initial_value: X86GuardVariable::UNINITIALIZED,
acquired_value: X86GuardVariable::INITIALIZING,
};
self.guard_variables.push(guard.clone());
guard
}
pub fn gen_guard_acquire_ir(&self, guard: &X86GuardVariable) -> String {
let guard_ty = if self.is_64bit { "i64" } else { "i32" };
let idx_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"; Guard acquire for {}\n\
\x20 %guard = load atomic {}, {}* @{} acquire, align {}\n\
\x20 %is_init = icmp eq {} %guard, {}\n\
\x20 br i1 %is_init, label %init_done, label %do_init\n\
\x20do_init:\n\
\x20 %acquired = cmpxchg {}* @{}, {} 0, {} 1 acquire acquire\n\
\x20 %was_acquired = extractvalue {{ {}, i1 }} %acquired, 1\n\
\x20 br i1 %was_acquired, label %init_body, label %init_wait\n\
\x20init_wait:\n\
\x20 ; Spin-wait loop (simplified — real impl uses __cxa_guard_acquire)\n\
\x20 br label %init_done\n\
\x20init_body:\n",
guard.protected_var,
guard_ty,
guard_ty,
guard.mangled_name,
guard.size,
guard_ty,
X86GuardVariable::INITIALIZED,
guard_ty,
guard.mangled_name,
X86GuardVariable::UNINITIALIZED,
X86GuardVariable::INITIALIZING,
guard_ty,
)
}
pub fn gen_guard_release_ir(&self, guard: &X86GuardVariable) -> String {
let guard_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"; Guard release for {}\n\
\x20 store atomic {} {}, {}* @{} release, align {}\n\
\x20 br label %init_done\n\
\x20init_done:\n",
guard.protected_var,
guard_ty,
X86GuardVariable::INITIALIZED,
guard_ty,
guard.mangled_name,
guard.size,
)
}
pub fn gen_guard_decl_ir(&self, guard: &X86GuardVariable) -> String {
let guard_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"@{} = linkonce_odr dso_local global {} 0, align {}\n",
guard.mangled_name, guard_ty, guard.size,
)
}
pub fn emit_all(&self) -> String {
let mut ir = String::new();
ir.push_str("; === X86 Guard Variables ===\n");
for guard in &self.guard_variables {
ir.push_str(&self.gen_guard_decl_ir(guard));
}
ir
}
pub fn get_guard(&self, var_name: &str) -> Option<&X86GuardVariable> {
self.guard_variables
.iter()
.find(|g| g.protected_var == var_name)
}
pub fn mark_emitted(&mut self, var_name: &str) {
if let Some(guard) = self
.guard_variables
.iter_mut()
.find(|g| g.protected_var == var_name)
{
guard.emitted = true;
}
}
}
impl Default for X86GuardEmission {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86TypeInfoEmitter {
pub mangler: Mangler,
pub is_64bit: bool,
pub triple: String,
pub records: Vec<X86TypeInfoRecord>,
pub use_comdat: bool,
}
#[derive(Debug, Clone)]
pub struct X86TypeInfoRecord {
pub type_name: String,
pub typeinfo_sym: String,
pub name_sym: String,
pub kind: X86RTTIKind,
pub vtable_sym: Option<String>,
pub base_typeinfo: Option<String>,
pub emitted: bool,
}
impl X86TypeInfoEmitter {
pub fn new(mangler: Mangler, is_64bit: bool, triple: String) -> Self {
Self {
mangler,
is_64bit,
triple,
records: Vec::new(),
use_comdat: true,
}
}
pub fn register(
&mut self,
type_name: &str,
kind: X86RTTIKind,
vtable_sym: Option<&str>,
base_typeinfo: Option<&str>,
) {
let typeinfo_sym = self.mangler.mangle_typeinfo(type_name);
let name_sym = self.mangler.mangle_typeinfo_name(type_name);
let record = X86TypeInfoRecord {
type_name: type_name.to_string(),
typeinfo_sym: typeinfo_sym.into_string(),
name_sym: name_sym.into_string(),
kind,
vtable_sym: vtable_sym.map(|s| s.to_string()),
base_typeinfo: base_typeinfo.map(|s| s.to_string()),
emitted: false,
};
if !self
.records
.iter()
.any(|r| r.typeinfo_sym == record.typeinfo_sym)
{
self.records.push(record);
}
}
pub fn emit_all(&self) -> String {
let mut ir = String::new();
ir.push_str("; === X86 Type Info ===\n");
for record in &self.records {
ir.push_str(&format!(
"@{} = linkonce_odr constant [{} x i8] c\"{}\\00\"\n",
record.name_sym.into_string(),
record.type_name.len() + 1,
record.type_name,
));
}
for record in &self.records {
ir.push_str(&self.emit_typeinfo_struct_ir(record));
}
ir
}
fn emit_typeinfo_struct_ir(&self, record: &X86TypeInfoRecord) -> String {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
let linkage = if self.use_comdat {
"linkonce_odr"
} else {
"weak_odr"
};
match record.kind {
X86RTTIKind::Fundamental | X86RTTIKind::Function | X86RTTIKind::Enum => {
ir.push_str(&format!(
"@{} = {} constant {{ i8* }} {{\n",
record.typeinfo_sym, linkage,
));
ir.push_str(&format!(
" i8* getelementptr inbounds ([{} x i8], [{} x i8]* @{}, i32 0, i32 0)\n",
record.type_name.len() + 1,
record.type_name.len() + 1,
record.name_sym.into_string(),
));
ir.push_str("}, align 8\n");
}
X86RTTIKind::Class | X86RTTIKind::SiClass | X86RTTIKind::VmiClass => {
ir.push_str(&format!(
"@{} = {} constant {{ i8*, i8* }} {{\n",
record.typeinfo_sym, linkage,
));
ir.push_str(&format!(
" i8* getelementptr inbounds ([{} x i8], [{} x i8]* @{}, i32 0, i32 0),\n",
record.type_name.len() + 1,
record.type_name.len() + 1,
record.name_sym.into_string(),
));
if let Some(ref vtable) = record.vtable_sym {
ir.push_str(&format!(" i8* bitcast ({{ ... }}* @{} to i8*)\n", vtable,));
} else {
ir.push_str(" i8* null\n");
}
ir.push_str("}, align 8\n");
}
X86RTTIKind::Pointer | X86RTTIKind::Array => {
ir.push_str(&format!(
"@{} = {} constant {{ i8*, i8* }} {{\n",
record.typeinfo_sym, linkage,
));
ir.push_str(&format!(
" i8* getelementptr inbounds ([{} x i8], [{} x i8]* @{}, i32 0, i32 0),\n",
record.type_name.len() + 1,
record.type_name.len() + 1,
record.name_sym.into_string(),
));
if let Some(ref base) = record.base_typeinfo {
ir.push_str(&format!(" i8* bitcast ({{ i8* }}* @{} to i8*)\n", base,));
} else {
ir.push_str(" i8* null\n");
}
ir.push_str("}, align 8\n");
}
_ => {
ir.push_str(&format!(
"@{} = external constant i8\n",
record.typeinfo_sym,
));
}
}
ir
}
pub fn get_typeinfo_sym(&self, type_name: &str) -> Option<&str> {
self.records
.iter()
.find(|r| r.type_name == type_name)
.map(|r| r.typeinfo_sym.as_str())
}
pub fn disable_comdat(&mut self) {
self.use_comdat = false;
}
pub fn set_mangler(&mut self, mangler: Mangler) {
self.mangler = mangler;
}
}
impl Default for X86TypeInfoEmitter {
fn default() -> Self {
Self::new(
Mangler::default(),
true,
"x86_64-unknown-linux-gnu".to_string(),
)
}
}
pub struct X86CtorDtorLowering {
pub is_64bit: bool,
pub triple: String,
pub ctor_variants: Vec<X86CtorRecord>,
pub dtor_variants: Vec<X86DtorRecord>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CtorVariant {
Complete,
Base,
Allocating,
}
impl X86CtorVariant {
pub fn suffix(&self) -> &'static str {
match self {
Self::Complete => "C1",
Self::Base => "C2",
Self::Allocating => "C3",
}
}
pub fn requires_vptr_init(&self) -> bool {
matches!(self, Self::Complete | Self::Base)
}
pub fn requires_allocation(&self) -> bool {
matches!(self, Self::Allocating)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DtorVariant {
Deleting,
Complete,
Base,
}
impl X86DtorVariant {
pub fn suffix(&self) -> &'static str {
match self {
Self::Deleting => "D0",
Self::Complete => "D1",
Self::Base => "D2",
}
}
pub fn calls_delete(&self) -> bool {
matches!(self, Self::Deleting)
}
pub fn adjusts_vptr(&self) -> bool {
matches!(self, Self::Complete | Self::Deleting)
}
}
#[derive(Debug, Clone)]
pub struct X86CtorRecord {
pub class_name: String,
pub variant: X86CtorVariant,
pub mangled_name: String,
pub param_types: Vec<String>,
pub has_virtual_bases: bool,
pub init_members: bool,
}
#[derive(Debug, Clone)]
pub struct X86DtorRecord {
pub class_name: String,
pub variant: X86DtorVariant,
pub mangled_name: String,
pub has_virtual_bases: bool,
pub destroy_members: bool,
}
impl X86CtorDtorLowering {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
ctor_variants: Vec::new(),
dtor_variants: Vec::new(),
}
}
pub fn register_ctor(
&mut self,
class_name: &str,
variant: X86CtorVariant,
mangled_name: &str,
param_types: &[String],
has_virtual_bases: bool,
) {
self.ctor_variants.push(X86CtorRecord {
class_name: class_name.to_string(),
variant,
mangled_name: mangled_name.to_string(),
param_types: param_types.to_vec(),
has_virtual_bases,
init_members: true,
});
}
pub fn register_dtor(
&mut self,
class_name: &str,
variant: X86DtorVariant,
mangled_name: &str,
has_virtual_bases: bool,
) {
self.dtor_variants.push(X86DtorRecord {
class_name: class_name.to_string(),
variant,
mangled_name: mangled_name.to_string(),
has_virtual_bases,
destroy_members: true,
});
}
pub fn lower_constructor(
&self,
class_name: &str,
variant: X86CtorVariant,
) -> Result<String, String> {
let ctor = self
.ctor_variants
.iter()
.find(|c| c.class_name == class_name && c.variant == variant)
.ok_or_else(|| format!("Constructor not found: {} {:?}", class_name, variant))?;
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let this_reg = if self.is_64bit { "%rdi" } else { "%ecx" };
let mut ir = String::new();
ir.push_str(&format!(
"define dso_local void @{}(i8* %this",
ctor.mangled_name,
));
for (i, pty) in ctor.param_types.iter().enumerate() {
ir.push_str(&format!(", {} %param{}", pty, i));
}
ir.push_str(") {\n");
if variant.requires_allocation() {
if self.is_64bit {
ir.push_str(" %alloc = call i8* @_Znwm(i64 8)\n");
} else {
ir.push_str(" %alloc = call i8* @_Znwj(i32 4)\n");
}
ir.push_str(&format!(" %this = i8* %alloc\n"));
}
ir.push_str(&format!("entry:\n"));
if ctor.has_virtual_bases {
ir.push_str(" ; Initialize virtual bases\n");
ir.push_str(&format!(
" %vptr = bitcast i8* %this to {}**\n",
if self.is_64bit { "i8" } else { "i32" },
));
ir.push_str(&format!(
" store {}* @_ZTV{} (i8* %this), {}** %vptr\n",
if self.is_64bit { "i8" } else { "i32" },
ctor.class_name,
if self.is_64bit { "i8" } else { "i32" },
));
}
ir.push_str(&format!(
" %vt.slot = bitcast i8* %this to {}**\n",
if self.is_64bit { "..." } else { "i8" },
));
ir.push_str(&format!(
" store {}* getelementptr inbounds ({{ ... }}, {{ ... }}* @_ZTV{}, i32 0, i32 2), ",
if self.is_64bit { "i8" } else { "i32" },
ctor.class_name,
));
ir.push_str(&format!(
"{}** %vt.slot\n",
if self.is_64bit { "..." } else { "i8" },
));
if ctor.init_members {
ir.push_str(" ; Initialize data members\n");
ir.push_str(" ; (member initialization goes here)\n");
}
ir.push_str(" ; Call base class constructors\n");
ir.push_str(" ret void\n");
ir.push_str("}\n");
Ok(ir)
}
pub fn lower_destructor(
&self,
class_name: &str,
variant: X86DtorVariant,
) -> Result<String, String> {
let dtor = self
.dtor_variants
.iter()
.find(|d| d.class_name == class_name && d.variant == variant)
.ok_or_else(|| format!("Destructor not found: {} {:?}", class_name, variant))?;
let mut ir = String::new();
ir.push_str(&format!(
"define dso_local void @{}(i8* %this) {{\n",
dtor.mangled_name,
));
ir.push_str(&format!("entry:\n"));
if variant.adjusts_vptr() {
ir.push_str(&format!(
" %vt.slot = bitcast i8* %this to {}**\n",
if self.is_64bit { "..." } else { "i8" },
));
ir.push_str(&format!(
" store {}* getelementptr inbounds ({{ ... }}, {{ ... }}* @_ZTV{}, i32 0, i32 2)",
if self.is_64bit { "i8" } else { "i32" },
dtor.class_name,
));
ir.push_str(&format!(
", {}** %vt.slot\n",
if self.is_64bit { "..." } else { "i8" },
));
}
if dtor.destroy_members {
ir.push_str(" ; Destroy data members\n");
}
if dtor.has_virtual_bases {
ir.push_str(" ; Call virtual base destructors\n");
}
if variant.calls_delete() {
ir.push_str(" call void @_ZdlPv(i8* %this)\n");
}
ir.push_str(" ret void\n");
ir.push_str("}\n");
Ok(ir)
}
pub fn gen_vptr_init(&self, class_name: &str, this_ptr: &str) -> String {
let mut ir = String::new();
ir.push_str(&format!(
" %vt.slot = bitcast i8* {} to {}**\n",
this_ptr,
if self.is_64bit { "..." } else { "i8" },
));
ir.push_str(&format!(
" store {}* getelementptr inbounds ({{ ... }}, {{ ... }}* @_ZTV{}, i32 0, i32 2), ",
if self.is_64bit { "i8" } else { "i32" },
class_name,
));
ir.push_str(&format!(
"{}** %vt.slot\n",
if self.is_64bit { "..." } else { "i8" },
));
ir
}
}
impl Default for X86CtorDtorLowering {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86VirtualDispatch {
pub is_64bit: bool,
pub triple: String,
pub dispatches: Vec<X86DispatchRecord>,
}
#[derive(Debug, Clone)]
pub struct X86DispatchRecord {
pub class_name: String,
pub method_name: String,
pub vtable_index: u32,
pub this_adjustment: i64,
pub return_type: String,
pub param_types: Vec<String>,
}
impl X86VirtualDispatch {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
dispatches: Vec::new(),
}
}
pub fn register(
&mut self,
class_name: &str,
method_name: &str,
vtable_index: u32,
this_adjustment: i64,
return_type: &str,
param_types: &[String],
) {
self.dispatches.push(X86DispatchRecord {
class_name: class_name.to_string(),
method_name: method_name.to_string(),
vtable_index,
this_adjustment,
return_type: return_type.to_string(),
param_types: param_types.to_vec(),
});
}
pub fn gen_dispatch(
&self,
class_name: &str,
method_name: &str,
vtable_index: u32,
) -> Result<String, String> {
let dispatch = self
.dispatches
.iter()
.find(|d| {
d.class_name == class_name
&& d.method_name == method_name
&& d.vtable_index == vtable_index
})
.ok_or_else(|| {
format!(
"Dispatch not found: {}::{}@{}",
class_name, method_name, vtable_index,
)
})?;
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let idx_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str(&format!(
"; Virtual dispatch: {}::{} (vtable_index: {})\n",
class_name, method_name, vtable_index,
));
ir.push_str(&format!(
" %vtable.ptr = load {}*, {}** %this.ptr\n",
ptr_ty, ptr_ty,
));
let vtable_slot_offset = 2 + vtable_index; ir.push_str(&format!(
" %vfunc.slot = getelementptr inbounds {}, {}* %vtable.ptr, {} {}\n",
ptr_ty, ptr_ty, idx_ty, vtable_slot_offset,
));
ir.push_str(&format!(
" %vfunc.ptr = load {}, {}* %vfunc.slot\n",
ptr_ty, ptr_ty,
));
let func_sig = self.build_function_signature(&dispatch.return_type, &dispatch.param_types);
ir.push_str(&format!(
" %vfunc = inttoptr {} %vfunc.ptr to {} ({}, i8*)*\n",
ptr_ty, func_sig, dispatch.return_type,
));
let this_ptr = if dispatch.this_adjustment != 0 {
ir.push_str(&format!(
" %this.adjusted = getelementptr inbounds i8, i8* %this.ptr, {} {}\n",
idx_ty, dispatch.this_adjustment,
));
"%this.adjusted"
} else {
"%this.ptr"
};
ir.push_str(&format!(
" %result = call {} %vfunc(i8* {})\n",
dispatch.return_type, this_ptr,
));
Ok(ir)
}
fn build_function_signature(&self, return_type: &str, param_types: &[String]) -> String {
let mut sig = return_type.to_string();
sig.push_str(" (");
for (i, pty) in param_types.iter().enumerate() {
if i > 0 {
sig.push_str(", ");
}
sig.push_str(pty);
}
sig.push(')');
sig
}
pub fn gen_pure_virtual_call(&self) -> String {
let mut ir = String::new();
ir.push_str("define void @__cxa_pure_virtual() {\n");
ir.push_str(" call void @__clang_call_terminate(i8* null)\n");
ir.push_str(" unreachable\n");
ir.push_str("}\n");
ir
}
pub fn vtable_offset(&self, index: u32) -> u32 {
2 + index
}
}
impl Default for X86VirtualDispatch {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86MemberPointer {
pub is_64bit: bool,
pub triple: String,
pub data_member_ptrs: Vec<X86DataMemberPtr>,
pub func_member_ptrs: Vec<X86FuncMemberPtr>,
}
#[derive(Debug, Clone)]
pub struct X86DataMemberPtr {
pub class_name: String,
pub member_name: String,
pub member_type: String,
pub offset: i64,
}
#[derive(Debug, Clone)]
pub struct X86FuncMemberPtr {
pub class_name: String,
pub method_name: String,
pub func_ptr: u64,
pub this_adjustment: i64,
pub is_virtual: bool,
pub vtable_index: Option<u32>,
}
impl X86MemberPointer {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
data_member_ptrs: Vec::new(),
func_member_ptrs: Vec::new(),
}
}
pub fn register_data_member(
&mut self,
class_name: &str,
member_name: &str,
member_type: &str,
offset: i64,
) {
self.data_member_ptrs.push(X86DataMemberPtr {
class_name: class_name.to_string(),
member_name: member_name.to_string(),
member_type: member_type.to_string(),
offset,
});
}
pub fn register_func_member(
&mut self,
class_name: &str,
method_name: &str,
func_ptr: u64,
this_adjustment: i64,
is_virtual: bool,
vtable_index: Option<u32>,
) {
self.func_member_ptrs.push(X86FuncMemberPtr {
class_name: class_name.to_string(),
method_name: method_name.to_string(),
func_ptr,
this_adjustment,
is_virtual,
vtable_index,
});
}
pub fn lower(
&self,
class_name: &str,
member_name: &str,
member_type: &str,
) -> Result<String, String> {
if let Some(data) = self
.data_member_ptrs
.iter()
.find(|p| p.class_name == class_name && p.member_name == member_name)
{
return Ok(self.lower_data_member_ir(data));
}
if let Some(func) = self
.func_member_ptrs
.iter()
.find(|p| p.class_name == class_name && p.method_name == member_name)
{
return Ok(self.lower_func_member_ir(func));
}
Err(format!("Member not found: {}::{}", class_name, member_name))
}
fn lower_data_member_ir(&self, ptr: &X86DataMemberPtr) -> String {
let idx_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"; Data member pointer: {}::{} (offset: {})\n\
\x20 %member.ptr = getelementptr inbounds i8, i8* %this, {} {}\n\
\x20 %member.val = bitcast i8* %member.ptr to {}*\n\
\x20 %result = load {}, {}* %member.val\n",
ptr.class_name,
ptr.member_name,
ptr.offset,
idx_ty,
ptr.offset,
ptr.member_type,
ptr.member_type,
ptr.member_type,
)
}
fn lower_func_member_ir(&self, ptr: &X86FuncMemberPtr) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"; Function member pointer: {}::{}\n",
ptr.class_name, ptr.method_name,
));
if ptr.is_virtual {
if let Some(vtable_idx) = ptr.vtable_index {
let ptr_ty = if self.is_64bit { "i64" } else { "i32" };
let idx_ty = if self.is_64bit { "i64" } else { "i32" };
ir.push_str(&format!(
" %vtable = load {}*, {}** %this.ptr\n",
ptr_ty, ptr_ty,
));
ir.push_str(&format!(
" %vfunc.slot = getelementptr inbounds {}, {}* %vtable, {} {}\n",
ptr_ty,
ptr_ty,
idx_ty,
2 + vtable_idx,
));
ir.push_str(&format!(
" %vfunc.ptr = load {}, {}* %vfunc.slot\n",
ptr_ty, ptr_ty,
));
ir.push_str(&format!(
" %vfunc = inttoptr {} %vfunc.ptr to void (i8*)*\n",
ptr_ty,
));
}
}
if ptr.this_adjustment != 0 {
let idx_ty = if self.is_64bit { "i64" } else { "i32" };
ir.push_str(&format!(
" %this.adj = getelementptr inbounds i8, i8* %this.ptr, {} {}\n",
idx_ty, ptr.this_adjustment,
));
}
ir
}
pub fn gen_compare(&self, lhs: &X86FuncMemberPtr, rhs: &X86FuncMemberPtr) -> String {
format!(
"; Member pointer comparison\n\
\x20 %lhs.ptr = extractvalue {{ i64, i64 }} %lhs, 0\n\
\x20 %lhs.adj = extractvalue {{ i64, i64 }} %lhs, 1\n\
\x20 %rhs.ptr = extractvalue {{ i64, i64 }} %rhs, 0\n\
\x20 %rhs.adj = extractvalue {{ i64, i64 }} %rhs, 1\n\
\x20 %cmp.ptr = icmp eq i64 %lhs.ptr, %rhs.ptr\n\
\x20 %cmp.adj = icmp eq i64 %lhs.adj, %rhs.adj\n\
\x20 %result = and i1 %cmp.ptr, %cmp.adj\n",
)
}
pub fn gen_memptr_to_bool(&self) -> String {
"; Convert member pointer to bool: check if non-null\n\
\x20 %ptr = extractvalue { i64, i64 } %memptr, 0\n\
\x20 %is.valid = icmp ne i64 %ptr, 0\n\
\x20 br i1 %is.valid, label %valid, label %null\n"
.to_string()
}
}
impl Default for X86MemberPointer {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86LambdaLowering {
pub is_64bit: bool,
pub triple: String,
pub lambdas: Vec<X86LambdaRecord>,
pub lambda_counter: u64,
}
#[derive(Debug, Clone)]
pub struct X86LambdaRecord {
pub index: u64,
pub scope: String,
pub captures: Vec<X86LambdaCapture>,
pub is_mutable: bool,
pub return_type: Option<String>,
pub param_types: Vec<String>,
pub closure_type_name: String,
pub call_operator_name: String,
}
#[derive(Debug, Clone)]
pub struct X86LambdaCapture {
pub var_name: String,
pub var_type: String,
pub kind: X86CaptureKind,
pub is_ref: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CaptureKind {
ByValue,
ByRef,
ByMove,
This,
InitCapture,
InitCaptureRef,
}
impl X86LambdaLowering {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
lambdas: Vec::new(),
lambda_counter: 0,
}
}
pub fn lower(&mut self, captures: &[LambdaCapture], body: &str) -> Result<String, String> {
let index = self.lambda_counter;
self.lambda_counter += 1;
let scope = format!("__lambda_{}", index);
let closure_type_name = format!("class.anon.{}", index);
let call_operator_name = format!("_ZZ{}_FvvE", scope);
let mut x86_captures = Vec::new();
for cap in captures {
let (var_name, var_type, is_ref) = match cap {
LambdaCapture::DefaultByValue => ("__default".to_string(), "i8".to_string(), false),
LambdaCapture::DefaultByRef => {
("__default_ref".to_string(), "i8".to_string(), true)
}
LambdaCapture::Capture { name, by_ref, .. } => {
(name.clone(), "i8".to_string(), *by_ref)
}
LambdaCapture::ThisByValue | LambdaCapture::This => {
("this".to_string(), "i8*".to_string(), false)
}
LambdaCapture::ThisByRef | LambdaCapture::ThisRef => {
("this".to_string(), "i8*".to_string(), true)
}
};
x86_captures.push(X86LambdaCapture {
var_name,
var_type,
kind: if is_ref {
X86CaptureKind::ByRef
} else {
X86CaptureKind::ByValue
},
is_ref,
});
}
let lambda = X86LambdaRecord {
index,
scope: scope.clone(),
captures: x86_captures,
is_mutable: false,
return_type: None,
param_types: Vec::new(),
closure_type_name: closure_type_name.clone(),
call_operator_name: call_operator_name.clone(),
};
self.lambdas.push(lambda);
let mut ir = String::new();
ir.push_str(&format!("; Lambda {} in {}\n", index, scope,));
ir.push_str(&format!("%{} = type {{ ", closure_type_name,));
for cap in &self.lambdas.last().unwrap().captures {
ir.push_str(&format!("{}, ", cap.var_type));
if cap.kind == X86CaptureKind::This {
ir.push_str(&format!("{}*, ", if self.is_64bit { "i8" } else { "i32" },));
}
}
ir.push_str("}\n");
ir.push_str(&format!(
"define internal {} @{}(%{}* %this",
if self.is_64bit { "void" } else { "void" },
call_operator_name,
closure_type_name,
));
ir.push_str(") {\n");
ir.push_str(&format!("entry:\n"));
for (i, cap) in self.lambdas.last().unwrap().captures.iter().enumerate() {
ir.push_str(&format!(
" %cap.{}.ptr = getelementptr inbounds %{}, %{}* %this, i32 0, i32 {}\n",
cap.var_name, closure_type_name, closure_type_name, i,
));
if cap.is_ref {
ir.push_str(&format!(
" %cap.{}.val = load {}, {}* %cap.{}.ptr\n",
cap.var_name, cap.var_type, cap.var_type, cap.var_name,
));
} else {
ir.push_str(&format!(
" %cap.{}.val = load {}, {}* %cap.{}.ptr\n",
cap.var_name, cap.var_type, cap.var_type, cap.var_name,
));
}
}
ir.push_str(&format!(" ; Body: {}\n", body));
ir.push_str(" ret void\n");
ir.push_str("}\n");
Ok(ir)
}
pub fn gen_stateless_conversion(
&self,
lambda_idx: u64,
return_type: &str,
param_types: &[String],
) -> String {
let mut ir = String::new();
let lambda = self.lambdas.iter().find(|l| l.index == lambda_idx);
let op_name = lambda.map_or(format!("_ZZ__lambda_{}_FvvE", lambda_idx), |l| {
l.call_operator_name.clone()
});
let mut sig = format!("{} (", return_type);
for (i, pty) in param_types.iter().enumerate() {
if i > 0 {
sig.push_str(", ");
}
sig.push_str(pty);
}
sig.push(')');
ir.push_str(&format!(
"define internal {} @__lambda_{}_to_fn() {{\n",
sig, lambda_idx,
));
ir.push_str(&format!(
" ret {}* bitcast (void (%class.anon.{}*)* @{} to {}*)\n",
sig, lambda_idx, op_name, sig,
));
ir.push_str("}\n");
ir
}
pub fn gen_closure_construct(&self, lambda_idx: u64, captures: &[X86LambdaCapture]) -> String {
let mut ir = String::new();
ir.push_str(&format!("; Construct closure for lambda {}\n", lambda_idx,));
ir.push_str(&format!(" %closure = alloca %class.anon.{}\n", lambda_idx,));
for (i, cap) in captures.iter().enumerate() {
ir.push_str(&format!(
" %cap.{}.slot = getelementptr inbounds %class.anon.{}, ",
cap.var_name, lambda_idx,
));
ir.push_str(&format!(
"%class.anon.{}* %closure, i32 0, i32 {}\n",
lambda_idx, i,
));
if cap.is_ref {
ir.push_str(&format!(
" store {}* %{}, {}** %cap.{}.slot\n",
cap.var_type, cap.var_name, cap.var_type, cap.var_name,
));
} else {
ir.push_str(&format!(
" store {} %{}, {}* %cap.{}.slot\n",
cap.var_type, cap.var_name, cap.var_type, cap.var_name,
));
}
}
ir
}
}
impl Default for X86LambdaLowering {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86NewDelete {
pub is_64bit: bool,
pub triple: String,
pub new_records: Vec<X86NewRecord>,
pub delete_records: Vec<X86DeleteRecord>,
pub use_array_cookie: bool,
pub support_nothrow: bool,
}
#[derive(Debug, Clone)]
pub struct X86NewRecord {
pub alloc_type: String,
pub size: u64,
pub alignment: u32,
pub is_array: bool,
pub is_nothrow: bool,
pub is_placement: bool,
pub result_var: String,
}
#[derive(Debug, Clone)]
pub struct X86DeleteRecord {
pub delete_type: String,
pub size: u64,
pub is_array: bool,
pub ptr_name: String,
}
impl X86NewDelete {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
new_records: Vec::new(),
delete_records: Vec::new(),
use_array_cookie: true,
support_nothrow: true,
}
}
pub fn gen_new(&mut self, size: u64, align: u32, nothrow: bool) -> Result<String, String> {
let mut ir = String::new();
let size_ty = if self.is_64bit { "i64" } else { "i32" };
let func_name = if nothrow {
if self.is_64bit {
"_ZnwmRKSt9nothrow_t"
} else {
"_ZnwjRKSt9nothrow_t"
}
} else {
if self.is_64bit {
"_Znwm"
} else {
"_Znwj"
}
};
ir.push_str(&format!(
"; Operator new ({}size={}, align={}, nothrow={})\n",
if self.is_64bit { "x86-64 " } else { "x86-32 " },
size,
align,
nothrow,
));
if self.is_64bit {
ir.push_str(&format!(
" %new.ptr = call i8* @{}(i64 {})\n",
func_name, size,
));
} else {
ir.push_str(&format!(
" %new.ptr = call i8* @{}(i32 {})\n",
func_name, size,
));
}
if nothrow {
ir.push_str(" %new.isnull = icmp eq i8* %new.ptr, null\n");
ir.push_str(" br i1 %new.isnull, label %new.null, label %new.ok\n");
ir.push_str("new.null:\n");
ir.push_str(" ret i8* null\n");
ir.push_str("new.ok:\n");
}
ir.push_str(&format!(
" %new.cast = bitcast i8* %new.ptr to {}*\n",
if self.is_64bit { "i8" } else { "i32" },
));
Ok(ir)
}
pub fn gen_delete(&mut self, ptr_name: &str, size: u64) -> Result<String, String> {
let func_name = if self.is_64bit { "_ZdlPv" } else { "_ZdjPv" };
let mut ir = String::new();
ir.push_str(&format!(
"; Operator delete (ptr={}, size={})\n",
ptr_name, size,
));
ir.push_str(&format!(" %del.isnull = icmp eq i8* {}, null\n", ptr_name,));
ir.push_str(" br i1 %del.isnull, label %del.done, label %del.do\n");
ir.push_str("del.do:\n");
if self.is_64bit {
ir.push_str(&format!(" call void @{}(i8* {})\n", func_name, ptr_name,));
} else {
ir.push_str(&format!(" call void @{}(i8* {})\n", func_name, ptr_name,));
}
ir.push_str(" br label %del.done\n");
ir.push_str("del.done:\n");
Ok(ir)
}
pub fn gen_array_new_with_cookie(
&mut self,
element_size: u64,
count: u64,
element_type: &str,
) -> Result<String, String> {
let size_ty = if self.is_64bit { "i64" } else { "i32" };
let cookie_size = if self.is_64bit { 8u64 } else { 4u64 };
let total_size = element_size * count + cookie_size;
let mut ir = String::new();
ir.push_str(&format!(
"; Array new with cookie: {} elements of {} ({} bytes + {} cookie)\n",
count, element_type, element_size, cookie_size,
));
ir.push_str(&format!(
" %arr.raw = call i8* @_Znam({} {})\n",
size_ty, total_size,
));
ir.push_str(&format!(
" %arr.cookie.ptr = bitcast i8* %arr.raw to {}*\n",
size_ty,
));
ir.push_str(&format!(
" store {} {}, {}* %arr.cookie.ptr\n",
size_ty, count, size_ty,
));
ir.push_str(&format!(
" %arr.data = getelementptr inbounds i8, i8* %arr.raw, {} {}\n",
size_ty, cookie_size,
));
ir.push_str(&format!(" %arr.loop.count = alloca {}\n", size_ty,));
ir.push_str(&format!(
" store {} 0, {}* %arr.loop.count\n",
size_ty, size_ty,
));
ir.push_str(" br label %arr.ctor.loop\n");
ir.push_str("arr.ctor.loop:\n");
ir.push_str(&format!(
" %arr.idx = load {}, {}* %arr.loop.count\n",
size_ty, size_ty,
));
ir.push_str(&format!(
" %arr.done = icmp uge {} %arr.idx, {}\n",
size_ty, count,
));
ir.push_str(" br i1 %arr.done, label %arr.ctor.done, label %arr.ctor.body\n");
ir.push_str("arr.ctor.body:\n");
ir.push_str(&format!(
" %arr.elem.ptr = getelementptr inbounds {}, {}* %arr.data, {} %arr.idx\n",
element_type, element_type, size_ty,
));
ir.push_str(&format!(
" call void @_ZN{}({}* %arr.elem.ptr)\n",
element_type, element_type,
));
ir.push_str(&format!(" %arr.next = add {} %arr.idx, 1\n", size_ty,));
ir.push_str(&format!(
" store {} %arr.next, {}* %arr.loop.count\n",
size_ty, size_ty,
));
ir.push_str(" br label %arr.ctor.loop\n");
ir.push_str("arr.ctor.done:\n");
Ok(ir)
}
pub fn gen_sized_delete(&mut self, ptr_name: &str, size: u64) -> String {
let func_name = if self.is_64bit { "_ZdlPvm" } else { "_ZdjPvj" };
let size_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"; Sized delete: {} ({} bytes)\n\
\x20 call void @{}(i8* {}, {} {})\n",
ptr_name, size, func_name, ptr_name, size_ty, size,
)
}
}
impl Default for X86NewDelete {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86ArrayCtorDtor {
pub is_64bit: bool,
pub triple: String,
pub constructions: Vec<X86ArrayCtorRecord>,
pub destructions: Vec<X86ArrayDtorRecord>,
}
#[derive(Debug, Clone)]
pub struct X86ArrayCtorRecord {
pub class_name: String,
pub count: u64,
pub base_ptr: String,
pub has_cookie: bool,
}
#[derive(Debug, Clone)]
pub struct X86ArrayDtorRecord {
pub class_name: String,
pub count: u64,
pub base_ptr: String,
pub has_cookie: bool,
}
impl X86ArrayCtorDtor {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
constructions: Vec::new(),
destructions: Vec::new(),
}
}
pub fn register_construction(
&mut self,
class_name: &str,
count: u64,
base_ptr: &str,
has_cookie: bool,
) {
self.constructions.push(X86ArrayCtorRecord {
class_name: class_name.to_string(),
count,
base_ptr: base_ptr.to_string(),
has_cookie,
});
}
pub fn register_destruction(
&mut self,
class_name: &str,
count: u64,
base_ptr: &str,
has_cookie: bool,
) {
self.destructions.push(X86ArrayDtorRecord {
class_name: class_name.to_string(),
count,
base_ptr: base_ptr.to_string(),
has_cookie,
});
}
pub fn lower_construction(&self, class_name: &str, count: u64) -> Result<String, String> {
let ctor = self
.constructions
.iter()
.find(|c| c.class_name == class_name && c.count == count)
.ok_or_else(|| format!("Array construction not found: {}[{}]", class_name, count))?;
let size_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str(&format!(
"; Array construction: {} elements of {}\n",
count, class_name,
));
ir.push_str(&format!(" %arr.ctor.idx = alloca {}\n", size_ty,));
ir.push_str(&format!(
" store {} 0, {}* %arr.ctor.idx\n",
size_ty, size_ty,
));
ir.push_str(" br label %arr.ctor.header\n");
ir.push_str("arr.ctor.header:\n");
ir.push_str(&format!(
" %arr.ctor.i = load {}, {}* %arr.ctor.idx\n",
size_ty, size_ty,
));
ir.push_str(&format!(
" %arr.ctor.end = icmp eq {} %arr.ctor.i, {}\n",
size_ty, count,
));
ir.push_str(" br i1 %arr.ctor.end, label %arr.ctor.done, label %arr.ctor.body\n");
ir.push_str("arr.ctor.body:\n");
ir.push_str(&format!(
" %arr.ctor.elem = getelementptr inbounds %class.{}, %class.{}* {}, {} %arr.ctor.i\n",
class_name, class_name, ctor.base_ptr, size_ty,
));
ir.push_str(&format!(
" call void @_ZN{}C1Ev(%class.{}* %arr.ctor.elem)\n",
class_name, class_name,
));
ir.push_str(&format!(
" %arr.ctor.next = add {} %arr.ctor.i, 1\n",
size_ty,
));
ir.push_str(&format!(
" store {} %arr.ctor.next, {}* %arr.ctor.idx\n",
size_ty, size_ty,
));
ir.push_str(" br label %arr.ctor.header\n");
ir.push_str("arr.ctor.done:\n");
Ok(ir)
}
pub fn lower_destruction(&self, class_name: &str, count: u64) -> Result<String, String> {
let dtor = self
.destructions
.iter()
.find(|d| d.class_name == class_name && d.count == count)
.ok_or_else(|| format!("Array destruction not found: {}[{}]", class_name, count))?;
let size_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str(&format!(
"; Array destruction: {} elements of {} (reverse order)\n",
count, class_name,
));
ir.push_str(&format!(" %arr.dtor.idx = alloca {}\n", size_ty,));
ir.push_str(&format!(
" store {} {}, {}* %arr.dtor.idx\n",
size_ty, count, size_ty,
));
ir.push_str(" br label %arr.dtor.header\n");
ir.push_str("arr.dtor.header:\n");
ir.push_str(&format!(
" %arr.dtor.i = load {}, {}* %arr.dtor.idx\n",
size_ty, size_ty,
));
ir.push_str(&format!(
" %arr.dtor.zero = icmp eq {} %arr.dtor.i, 0\n",
size_ty,
));
ir.push_str(" br i1 %arr.dtor.zero, label %arr.dtor.done, label %arr.dtor.body\n");
ir.push_str("arr.dtor.body:\n");
ir.push_str(&format!(
" %arr.dtor.prev = sub {} %arr.dtor.i, 1\n",
size_ty,
));
ir.push_str(&format!(
" store {} %arr.dtor.prev, {}* %arr.dtor.idx\n",
size_ty, size_ty,
));
ir.push_str(&format!(
" %arr.dtor.elem = getelementptr inbounds %class.{}, %class.{}* {}, {} %arr.dtor.prev\n",
class_name, class_name, dtor.base_ptr, size_ty,
));
ir.push_str(&format!(
" call void @_ZN{}D2Ev(%class.{}* %arr.dtor.elem)\n",
class_name, class_name,
));
ir.push_str(" br label %arr.dtor.header\n");
ir.push_str("arr.dtor.done:\n");
Ok(ir)
}
pub fn gen_array_ctor_with_eh_cleanup(
&self,
class_name: &str,
count: u64,
base_ptr: &str,
) -> String {
let size_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str(&format!(
"; Exception-safe array construction: {} elements of {}\n",
count, class_name,
));
ir.push_str(&format!(" %arr.eh.constructed = alloca {}\n", size_ty,));
ir.push_str(&format!(
" store {} 0, {}* %arr.eh.constructed\n",
size_ty, size_ty,
));
ir.push_str(" invoke void @__array_ctor_body()\n");
ir.push_str(" to label %arr.ok unwind label %arr.eh.lpad\n");
ir.push_str("arr.eh.lpad:\n");
ir.push_str(" %arr.eh.ptr = landingpad { i8*, i32 } cleanup\n");
ir.push_str(" ; Destroy already-constructed elements\n");
ir.push_str(&format!(
" %arr.eh.n = load {}, {}* %arr.eh.constructed\n",
size_ty, size_ty,
));
ir.push_str(" br label %arr.cleanup.loop\n");
ir.push_str("arr.cleanup.loop:\n");
ir.push_str(&format!(
" %arr.c.i = phi {} [ %arr.eh.n, %arr.eh.lpad ], [ %arr.c.next, %arr.cleanup.body ]\n",
size_ty,
));
ir.push_str(&format!(
" %arr.c.done = icmp eq {} %arr.c.i, 0\n",
size_ty,
));
ir.push_str(" br i1 %arr.c.done, label %arr.cleanup.done, label %arr.cleanup.body\n");
ir.push_str("arr.cleanup.body:\n");
ir.push_str(&format!(" %arr.c.prev = sub {} %arr.c.i, 1\n", size_ty,));
ir.push_str(&format!(
" %arr.c.elem = getelementptr inbounds %class.{}, %class.{}* {}, {} %arr.c.prev\n",
class_name, class_name, base_ptr, size_ty,
));
ir.push_str(&format!(
" call void @_ZN{}D2Ev(%class.{}* %arr.c.elem)\n",
class_name, class_name,
));
ir.push_str(&format!(" %arr.c.next = sub {} %arr.c.i, 1\n", size_ty,));
ir.push_str(" br label %arr.cleanup.loop\n");
ir.push_str("arr.cleanup.done:\n");
ir.push_str(" resume { i8*, i32 } %arr.eh.ptr\n");
ir.push_str("arr.ok:\n");
ir
}
}
impl Default for X86ArrayCtorDtor {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86DynamicCast {
pub is_64bit: bool,
pub triple: String,
pub casts: Vec<X86DynamicCastRecord>,
}
#[derive(Debug, Clone)]
pub struct X86DynamicCastRecord {
pub source_type: String,
pub target_type: String,
pub kind: X86DynCastKind,
pub src_typeinfo: String,
pub dst_typeinfo: String,
pub src_ptr: String,
pub offset_hint: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DynCastKind {
Identity,
Downcast,
CrossCast,
ToVoid,
}
impl X86DynamicCast {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
casts: Vec::new(),
}
}
pub fn register(
&mut self,
source_type: &str,
target_type: &str,
src_typeinfo: &str,
dst_typeinfo: &str,
src_ptr: &str,
kind: X86DynCastKind,
offset_hint: i64,
) {
self.casts.push(X86DynamicCastRecord {
source_type: source_type.to_string(),
target_type: target_type.to_string(),
kind,
src_typeinfo: src_typeinfo.to_string(),
dst_typeinfo: dst_typeinfo.to_string(),
src_ptr: src_ptr.to_string(),
offset_hint,
});
}
pub fn gen_cast(
&self,
expr_ptr: &str,
source_type: &str,
target_type: &str,
) -> Result<String, String> {
let cast = self
.casts
.iter()
.find(|c| {
c.source_type == source_type
&& c.target_type == target_type
&& c.src_ptr == expr_ptr
})
.ok_or_else(|| {
format!(
"dynamic_cast not registered: {} -> {}",
source_type, target_type,
)
})?;
let mut ir = String::new();
ir.push_str(&format!(
"; dynamic_cast: {}* -> {}*\n",
source_type, target_type,
));
ir.push_str(&format!(
" %dc.nullcheck = icmp eq i8* {}, null\n",
expr_ptr,
));
ir.push_str(" br i1 %dc.nullcheck, label %dc.null, label %dc.do\n");
ir.push_str("dc.null:\n ret i8* null\n");
ir.push_str("dc.do:\n");
match cast.kind {
X86DynCastKind::Identity => {
ir.push_str(&format!(" ret i8* {}\n", expr_ptr,));
}
X86DynCastKind::ToVoid => {
ir.push_str(&format!(
" %dc.vptr = load {}*, {}** {}\n",
if self.is_64bit { "i64" } else { "i32" },
if self.is_64bit { "i64" } else { "i32" },
expr_ptr,
));
ir.push_str(&format!(
" %dc.offset.slot = getelementptr inbounds i8, {}* %dc.vptr, i64 -8\n",
if self.is_64bit { "i64" } else { "i32" },
));
ir.push_str(&format!(" %dc.offset = load i64, i64* %dc.offset.slot\n",));
ir.push_str(&format!(
" %dc.void = getelementptr inbounds i8, i8* {}, i64 %dc.offset\n",
expr_ptr,
));
ir.push_str(" ret i8* %dc.void\n");
}
X86DynCastKind::Downcast | X86DynCastKind::CrossCast => {
ir.push_str(&format!(
" %dc.src.ti = bitcast {{ i8*, i8* }}* @{} to i8*\n",
cast.src_typeinfo,
));
ir.push_str(&format!(
" %dc.dst.ti = bitcast {{ i8*, i8* }}* @{} to i8*\n",
cast.dst_typeinfo,
));
if self.is_64bit {
ir.push_str(&format!(
" %dc.result = call i8* @__dynamic_cast(i8* {}, i8* %dc.src.ti, ",
expr_ptr,
));
ir.push_str(&format!(
"i8* %dc.dst.ti, i64 {})\n",
if cast.kind == X86DynCastKind::Downcast {
cast.offset_hint
} else {
-1
},
));
} else {
ir.push_str(&format!(
" %dc.result = call i8* @__dynamic_cast(i8* {}, i8* %dc.src.ti, ",
expr_ptr,
));
ir.push_str(&format!(
"i8* %dc.dst.ti, i64 {})\n",
if cast.kind == X86DynCastKind::Downcast {
cast.offset_hint
} else {
-1
},
));
}
ir.push_str(&format!(
" %dc.cast = bitcast i8* %dc.result to {}*\n",
target_type,
));
ir.push_str(&format!(" ret {}* %dc.cast\n", target_type,));
}
}
Ok(ir)
}
pub fn gen_runtime_decl(&self) -> String {
if self.is_64bit {
"declare dso_local i8* @__dynamic_cast(i8*, i8*, i8*, i64)\n".to_string()
} else {
"declare dso_local i8* @__dynamic_cast(i8*, i8*, i8*, i32)\n".to_string()
}
}
}
impl Default for X86DynamicCast {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86TypeID {
pub is_64bit: bool,
pub triple: String,
pub records: Vec<X86TypeIDRecord>,
}
#[derive(Debug, Clone)]
pub struct X86TypeIDRecord {
pub expr_type: String,
pub is_dynamic: bool,
pub typeinfo_sym: String,
pub requires_rtti: bool,
}
impl X86TypeID {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
records: Vec::new(),
}
}
pub fn register(&mut self, expr_type: &str, is_dynamic: bool, typeinfo_sym: &str) {
self.records.push(X86TypeIDRecord {
expr_type: expr_type.to_string(),
is_dynamic,
typeinfo_sym: typeinfo_sym.to_string(),
requires_rtti: is_dynamic,
});
}
pub fn gen_typeid(&self, expr: &str) -> Result<String, String> {
let record = self
.records
.iter()
.find(|r| r.expr_type == expr)
.ok_or_else(|| format!("typeid not registered for: {}", expr))?;
let mut ir = String::new();
ir.push_str(&format!("; typeid({})\n", expr,));
if record.is_dynamic {
ir.push_str(&format!(
" %tid.vptr = load {}*, {}** {}\n",
if self.is_64bit { "i64" } else { "i32" },
if self.is_64bit { "i64" } else { "i32" },
expr,
));
ir.push_str(&format!(
" %tid.rtti.slot = getelementptr inbounds i8, {}* %tid.vptr, i64 -8\n",
if self.is_64bit { "i64" } else { "i32" },
));
ir.push_str(&format!(
" %tid.rtti.ptr = load {}*, {}** %tid.rtti.slot\n",
if self.is_64bit { "i64" } else { "i32" },
if self.is_64bit { "i64" } else { "i32" },
));
ir.push_str(&format!(
" %tid.rtti = bitcast {}* %tid.rtti.ptr to {{ i8*, i8* }}*\n",
if self.is_64bit { "i64" } else { "i32" },
));
} else {
ir.push_str(&format!(
" %tid.rtti = bitcast {{ i8*, i8* }}* @{} to {{ i8*, i8* }}*\n",
record.typeinfo_sym,
));
}
Ok(ir)
}
pub fn gen_typeid_compare(&self, lhs_typeinfo: &str, rhs_typeinfo: &str) -> String {
format!(
"; typeid comparison\n\
\x20 %lhs.ti = bitcast {{ i8*, i8* }}* @{} to i8*\n\
\x20 %rhs.ti = bitcast {{ i8*, i8* }}* @{} to i8*\n\
\x20 %cmp = icmp eq i8* %lhs.ti, %rhs.ti\n\
\x20 ret i1 %cmp\n",
lhs_typeinfo, rhs_typeinfo,
)
}
pub fn gen_runtime_decl(&self) -> String {
"declare dso_local i8* @__cxa_bad_typeid()\n".to_string()
}
}
impl Default for X86TypeID {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86StaticInit {
pub is_64bit: bool,
pub triple: String,
pub inits: Vec<X86StaticInitRecord>,
pub use_cxa_guard: bool,
pub thread_safe: bool,
}
#[derive(Debug, Clone)]
pub struct X86StaticInitRecord {
pub var_name: String,
pub guard_var: String,
pub init_expr: String,
pub is_local_static: bool,
}
impl X86StaticInit {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
inits: Vec::new(),
use_cxa_guard: true,
thread_safe: true,
}
}
pub fn register(
&mut self,
var_name: &str,
guard_var: &str,
init_expr: &str,
is_local_static: bool,
) {
self.inits.push(X86StaticInitRecord {
var_name: var_name.to_string(),
guard_var: guard_var.to_string(),
init_expr: init_expr.to_string(),
is_local_static,
});
}
pub fn gen_guard(&self, var_name: &str) -> Result<String, String> {
let init = self
.inits
.iter()
.find(|i| i.var_name == var_name)
.ok_or_else(|| format!("Static init not found: {}", var_name))?;
let guard_ty = if self.is_64bit { "i64" } else { "i32" };
let mut ir = String::new();
ir.push_str(&format!("; Static init guard for {}\n", var_name,));
ir.push_str(&format!(
" %guard.val = load atomic {}, {}* @{} acquire, align 8\n",
guard_ty, guard_ty, init.guard_var,
));
ir.push_str(&format!(
" %guard.init = icmp eq {} %guard.val, {}\n",
guard_ty,
if self.is_64bit { "256" } else { "1" },
));
ir.push_str(" br i1 %guard.init, label %init.done, label %init.check\n");
if self.use_cxa_guard {
ir.push_str("init.check:\n");
ir.push_str(&format!(
" %guard.acq = call i32 @__cxa_guard_acquire({}* @{})\n",
guard_ty, init.guard_var,
));
ir.push_str(" %guard.acquired = icmp ne i32 %guard.acq, 0\n");
ir.push_str(" br i1 %guard.acquired, label %init.body, label %init.done\n");
} else {
ir.push_str("init.check:\n");
ir.push_str(&format!(
" %guard.cmpxchg = cmpxchg {}* @{}, {} 0, {} 1 acquire acquire\n",
guard_ty, init.guard_var, guard_ty, guard_ty,
));
ir.push_str(&format!(
" %guard.old = extractvalue {{ {}, i1 }} %guard.cmpxchg, 0\n",
guard_ty,
));
ir.push_str(&format!(
" %guard.old.iszero = icmp eq {} %guard.old, 0\n",
guard_ty,
));
ir.push_str(" br i1 %guard.old.iszero, label %init.body, label %init.wait\n");
ir.push_str("init.wait:\n");
ir.push_str(" ; Spin-wait\n");
ir.push_str(&format!(
" %guard.spin = load atomic {}, {}* @{} acquire, align 8\n",
guard_ty, guard_ty, init.guard_var,
));
ir.push_str(&format!(
" %guard.spin.done = icmp eq {} %guard.spin, {}\n",
guard_ty,
if self.is_64bit { "256" } else { "1" },
));
ir.push_str(" br i1 %guard.spin.done, label %init.done, label %init.wait\n");
}
ir.push_str("init.body:\n");
ir.push_str(&format!(" ; Init expression: {}\n", init.init_expr,));
if self.use_cxa_guard {
ir.push_str(&format!(
" call void @__cxa_guard_release({}* @{})\n",
guard_ty, init.guard_var,
));
} else {
ir.push_str(&format!(
" store atomic {} {}, {}* @{} release, align 8\n",
guard_ty,
if self.is_64bit { "256" } else { "1" },
guard_ty,
init.guard_var,
));
}
ir.push_str(" br label %init.done\n");
ir.push_str("init.done:\n");
Ok(ir)
}
pub fn gen_guard_decl(&self, guard_var: &str) -> String {
let guard_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"@{} = linkonce_odr dso_local global {} 0, align 8\n",
guard_var, guard_ty,
)
}
pub fn gen_runtime_decl(&self) -> String {
let guard_ty = if self.is_64bit { "i64" } else { "i32" };
format!(
"declare dso_local i32 @__cxa_guard_acquire({}*)\n\
declare dso_local void @__cxa_guard_release({}*)\n\
declare dso_local void @__cxa_guard_abort({}*)\n",
guard_ty, guard_ty, guard_ty,
)
}
pub fn gen_guard_abort(&self, guard_var: &str) -> String {
format!(
" call void @__cxa_guard_abort({}* @{})\n",
if self.is_64bit { "i64" } else { "i32" },
guard_var,
)
}
}
impl Default for X86StaticInit {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86ThreadLocalInit {
pub is_64bit: bool,
pub triple: String,
pub tls_vars: Vec<X86TLSVariable>,
pub use_elf_tls: bool,
pub tls_model: X86TLSModel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TLSModel {
GeneralDynamic,
LocalDynamic,
InitialExec,
LocalExec,
}
impl X86TLSModel {
pub fn as_str(&self) -> &'static str {
match self {
Self::GeneralDynamic => "general-dynamic",
Self::LocalDynamic => "local-dynamic",
Self::InitialExec => "initial-exec",
Self::LocalExec => "local-exec",
}
}
}
#[derive(Debug, Clone)]
pub struct X86TLSVariable {
pub var_name: String,
pub var_type: String,
pub mangled_name: String,
pub needs_init: bool,
pub model: X86TLSModel,
}
impl X86ThreadLocalInit {
pub fn new(is_64bit: bool, triple: String) -> Self {
let use_elf_tls = !triple.contains("windows");
Self {
is_64bit,
triple,
tls_vars: Vec::new(),
use_elf_tls,
tls_model: X86TLSModel::GeneralDynamic,
}
}
pub fn register(
&mut self,
var_name: &str,
var_type: &str,
mangled_name: &str,
needs_init: bool,
model: X86TLSModel,
) {
self.tls_vars.push(X86TLSVariable {
var_name: var_name.to_string(),
var_type: var_type.to_string(),
mangled_name: mangled_name.to_string(),
needs_init,
model,
});
}
pub fn gen_init(&self, var_name: &str) -> Result<String, String> {
let tls = self
.tls_vars
.iter()
.find(|t| t.var_name == var_name)
.ok_or_else(|| format!("TLS variable not found: {}", var_name))?;
let mut ir = String::new();
ir.push_str(&format!("; Thread-local init for {}\n", var_name,));
if self.use_elf_tls {
match tls.model {
X86TLSModel::GeneralDynamic => {
if self.is_64bit {
ir.push_str(&format!(
" %tls.addr = call {}* @__tls_get_addr({}* @{})\n",
tls.var_type,
if self.is_64bit { "i64" } else { "i32" },
tls.mangled_name,
));
} else {
ir.push_str(&format!(
" %tls.addr = call {}* @___tls_get_addr({}* @{})\n",
tls.var_type,
if self.is_64bit { "i64" } else { "i32" },
tls.mangled_name,
));
}
}
X86TLSModel::LocalDynamic => {
if self.is_64bit {
ir.push_str(&format!(
" %tls.ld = call i8* @__tls_get_addr(i8* blockaddress(@{}, %0))\n",
tls.mangled_name,
));
ir.push_str(&format!(
" %tls.addr = bitcast i8* %tls.ld to {}*\n",
tls.var_type,
));
}
}
X86TLSModel::InitialExec => {
if self.is_64bit {
ir.push_str(&format!(
" %tls.rel = thread_local({}) {}*\n",
tls.model.as_str(),
tls.var_type,
));
ir.push_str(&format!(
" %tls.addr = {}* @{}\n",
tls.var_type, tls.mangled_name,
));
}
}
X86TLSModel::LocalExec => {
if self.is_64bit {
ir.push_str(&format!(
" %tls.addr = thread_local({}) {}* @{}\n",
tls.model.as_str(),
tls.var_type,
tls.mangled_name,
));
}
}
}
} else {
ir.push_str(&format!(" %tls.idx = load i32, i32* @_tls_index\n",));
ir.push_str(" %tls.seg = call i8* @llvm.x86.seg.read.fs.i64(i64 88)\n");
ir.push_str(" %tls.array = inttoptr i64 %tls.seg to i8**\n");
ir.push_str(&format!(
" %tls.slot = getelementptr inbounds i8*, i8** %tls.array, i32 %tls.idx\n",
));
ir.push_str(" %tls.base = load i8*, i8** %tls.slot\n");
ir.push_str(&format!(
" %tls.offset = ptrtoint {}* @{} to i64\n",
tls.var_type, tls.mangled_name,
));
ir.push_str(&format!(
" %tls.addr = getelementptr inbounds i8, i8* %tls.base, i64 %tls.offset\n",
));
ir.push_str(&format!(
" %tls.cast = bitcast i8* %tls.addr to {}*\n",
tls.var_type,
));
}
if tls.needs_init {
ir.push_str(" ; Initialize if first access\n");
ir.push_str(" ; Register atexit cleanup\n");
}
Ok(ir)
}
pub fn gen_tls_decl(&self, var_name: &str, var_type: &str, init_val: Option<&str>) -> String {
let init = init_val.unwrap_or("zeroinitializer");
format!(
"@{} = thread_local dso_local global {} {}, align 8\n",
var_name, var_type, init,
)
}
pub fn gen_runtime_decl(&self) -> String {
if self.is_64bit {
"declare dso_local i8* @__tls_get_addr(i64*)\n".to_string()
} else {
"declare dso_local i8* @___tls_get_addr(i32*)\n".to_string()
}
}
}
impl Default for X86ThreadLocalInit {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86CXXABISupport {
pub is_64bit: bool,
pub triple: String,
pub abi: X86CXXABI,
pub subtarget: X86Subtarget,
pub gcc_compatible_layout: bool,
pub vtable_strategy: X86VtableStrategy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CXXABI {
Itanium,
Microsoft,
iOS,
}
impl X86CXXABI {
pub fn as_str(&self) -> &'static str {
match self {
Self::Itanium => "itanium",
Self::Microsoft => "microsoft",
Self::iOS => "ios",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86VtableStrategy {
Itanium,
Microsoft,
Compact,
}
impl X86CXXABISupport {
pub fn new(is_64bit: bool, triple: String, subtarget: X86Subtarget) -> Self {
let abi = if triple.contains("windows") || triple.contains("msvc") {
X86CXXABI::Microsoft
} else {
X86CXXABI::Itanium
};
let vtable_strategy = match abi {
X86CXXABI::Itanium => X86VtableStrategy::Itanium,
X86CXXABI::Microsoft => X86VtableStrategy::Microsoft,
X86CXXABI::iOS => X86VtableStrategy::Itanium,
};
Self {
is_64bit,
triple,
abi,
subtarget,
gcc_compatible_layout: true,
vtable_strategy,
}
}
pub fn is_itanium_abi(&self) -> bool {
matches!(self.abi, X86CXXABI::Itanium)
}
pub fn is_msvc_abi(&self) -> bool {
matches!(self.abi, X86CXXABI::Microsoft)
}
pub fn ptr_size(&self) -> u32 {
if self.is_64bit {
8
} else {
4
}
}
pub fn this_register(&self) -> &'static str {
match self.abi {
X86CXXABI::Itanium => {
if self.is_64bit {
"%rdi"
} else {
"%ecx"
}
}
X86CXXABI::Microsoft => {
if self.is_64bit {
"%rcx"
} else {
"%ecx"
}
}
X86CXXABI::iOS => "%rdi",
}
}
pub fn rtti_vtable_offset(&self) -> i32 {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => 1, X86CXXABI::Microsoft => -1, }
}
pub fn first_vfunc_index(&self) -> u32 {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => 2,
X86CXXABI::Microsoft => 0, }
}
pub fn offset_to_top_index(&self) -> i32 {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => 0,
X86CXXABI::Microsoft => -1, }
}
pub fn vtable_layout_abi(&self, class_name: &str, num_vfuncs: u32) -> Vec<String> {
let mut layout = Vec::new();
match self.abi {
X86CXXABI::Itanium => {
layout.push("offset_to_top: 0".to_string());
layout.push(format!("rtti: _ZTI{}", class_name));
for i in 0..num_vfuncs {
layout.push(format!("vfunc_{}: _ZN{}{}Ev", i, class_name, i));
}
}
X86CXXABI::Microsoft => {
for i in 0..num_vfuncs {
layout.push(format!("vfunc_{}: ?{{}}@@UEAAXXZ", class_name));
}
}
X86CXXABI::iOS => {
layout.push("offset_to_top: 0".to_string());
layout.push(format!("rtti: _ZTI{}", class_name));
for i in 0..num_vfuncs {
layout.push(format!("vfunc_{}: _ZN{}{}Ev", i, class_name, i));
}
}
}
layout
}
pub fn personality_function(&self) -> &'static str {
match self.abi {
X86CXXABI::Itanium => "__gxx_personality_v0",
X86CXXABI::iOS => "__gxx_personality_v0",
X86CXXABI::Microsoft => "__CxxFrameHandler3",
}
}
pub fn mangling_scheme(&self) -> X86ManglingScheme {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => X86ManglingScheme::Itanium,
X86CXXABI::Microsoft => X86ManglingScheme::Microsoft,
}
}
pub fn record_alignment(&self) -> u32 {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => {
if self.is_64bit {
8
} else {
4
}
}
X86CXXABI::Microsoft => {
if self.is_64bit {
8
} else {
4
}
}
}
}
pub fn array_cookie_size(&self) -> u32 {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => {
if self.is_64bit {
8
} else {
4
}
}
X86CXXABI::Microsoft => {
if self.is_64bit {
8
} else {
4
}
}
}
}
pub fn requires_array_cookie(&self, has_destructor: bool) -> bool {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => has_destructor,
X86CXXABI::Microsoft => true, }
}
pub fn member_ptr_rep(&self) -> X86MemberPtrRep {
match self.abi {
X86CXXABI::Itanium => {
if self.is_64bit {
X86MemberPtrRep::Pair16
} else {
X86MemberPtrRep::Pair8
}
}
X86CXXABI::Microsoft => {
if self.is_64bit {
X86MemberPtrRep::Single8
} else {
X86MemberPtrRep::Single4
}
}
X86CXXABI::iOS => X86MemberPtrRep::Pair16,
}
}
pub fn gen_function_prologue_abi(&self, is_constructor: bool) -> String {
let mut ir = String::new();
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => {
if is_constructor {
ir.push_str(" ; ABI: Initialize vptr in constructor\n");
}
}
X86CXXABI::Microsoft => {
if is_constructor {
ir.push_str(" ; MSVC ABI: Initialize vfptr in constructor\n");
}
}
}
ir
}
pub fn gen_function_epilogue_abi(&self, is_destructor: bool) -> String {
let mut ir = String::new();
if is_destructor {
match self.abi {
X86CXXABI::Itanium | X86CXXABI::iOS => {
ir.push_str(" ; ABI: Itanium destructor epilogue\n");
}
X86CXXABI::Microsoft => {
ir.push_str(" ; MSVC ABI: destructor epilogue\n");
}
}
}
ir
}
}
impl Default for X86CXXABISupport {
fn default() -> Self {
Self::new(
true,
"x86_64-unknown-linux-gnu".to_string(),
X86Subtarget::new("x86_64-unknown-linux-gnu", "generic", ""),
)
}
}
pub struct X86NameMangling {
pub mangler: Mangler,
pub is_64bit: bool,
pub triple: String,
pub scheme: X86ManglingScheme,
pub cache: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ManglingScheme {
Itanium,
Microsoft,
}
impl X86NameMangling {
pub fn new(mangler: Mangler, is_64bit: bool, triple: String) -> Self {
let scheme = if triple.contains("windows") || triple.contains("msvc") {
X86ManglingScheme::Microsoft
} else {
X86ManglingScheme::Itanium
};
Self {
mangler,
is_64bit,
triple,
scheme,
cache: std::collections::HashMap::new(),
}
}
pub fn mangle(&mut self, name: &str, types: &[String], is_ctor: bool, is_dtor: bool) -> String {
let cache_key = format!(
"{}:{},{:?}:{}:{}",
name,
types.join(","),
is_ctor,
is_dtor,
self.scheme as u8
);
if let Some(cached) = self.cache.get(&cache_key) {
return cached.clone();
}
let cxx_name = CXXName::new(name);
let mangled = match self.scheme {
X86ManglingScheme::Itanium => {
if is_ctor {
self.mangler
.mangle_function(&cxx_name, types, None, true, false, None)
.into_string()
} else if is_dtor {
self.mangler
.mangle_function(&cxx_name, types, None, false, true, None)
.into_string()
} else {
self.mangler
.mangle_function(&cxx_name, types, None, false, false, None)
.into_string()
}
}
X86ManglingScheme::Microsoft => {
self.mangle_msvc_placeholder(name, types, is_ctor, is_dtor)
}
};
self.cache.insert(cache_key, mangled.clone());
mangled
}
pub fn mangle_vtable(&mut self, class_name: &str) -> String {
match self.scheme {
X86ManglingScheme::Itanium => {
self.mangler.mangle_vtable(class_name, None).into_string()
}
X86ManglingScheme::Microsoft => format!("??_7{}@@6B@", class_name),
}
}
pub fn mangle_typeinfo(&mut self, type_name: &str) -> String {
match self.scheme {
X86ManglingScheme::Itanium => self.mangler.mangle_typeinfo(type_name).into_string(),
X86ManglingScheme::Microsoft => format!("??_R0{}@@8", type_name),
}
}
pub fn mangle_typeinfo_name(&mut self, type_name: &str) -> String {
match self.scheme {
X86ManglingScheme::Itanium => {
self.mangler.mangle_typeinfo_name(type_name).into_string()
}
X86ManglingScheme::Microsoft => format!("??_R0{}@8", type_name),
}
}
pub fn mangle_guard(&mut self, var_name: &str) -> String {
match self.scheme {
X86ManglingScheme::Itanium => {
let cxx_name = CXXName::new(var_name);
self.mangler.mangle_guard_variable(&cxx_name).into_string()
}
X86ManglingScheme::Microsoft => format!("??_B{}@@51", var_name),
}
}
pub fn mangle_thunk(
&mut self,
target: &str,
this_adjustment: i64,
vcall_offset: i64,
) -> String {
match self.scheme {
X86ManglingScheme::Itanium => {
let cxx_name = CXXName::new(target);
self.mangler
.mangle_thunk(&cxx_name, this_adjustment, Some(vcall_offset))
.into_string()
}
X86ManglingScheme::Microsoft => format!(
"?thunk_{}_adj{}_vcall{}@@",
target, this_adjustment, vcall_offset,
),
}
}
fn mangle_msvc_placeholder(
&self,
name: &str,
_types: &[String],
is_ctor: bool,
is_dtor: bool,
) -> String {
if is_ctor {
format!("??0{}@@QEAA@XZ", name)
} else if is_dtor {
format!("??1{}@@QEAA@XZ", name)
} else {
format!("?{}@@YAXXZ", name)
}
}
pub fn add_abi_tag(&self, mangled: &str, tag: &str) -> String {
format!("{}B5cxx11{}", mangled, tag)
}
}
impl Default for X86NameMangling {
fn default() -> Self {
Self::new(
Mangler::default(),
true,
"x86_64-unknown-linux-gnu".to_string(),
)
}
}
pub struct X86TemplateInstantiation {
pub is_64bit: bool,
pub triple: String,
pub instantiations: Vec<X86TemplateInstRecord>,
pub odr_merger: X86TemplateODRMerger,
pub use_linkonce_odr: bool,
}
#[derive(Debug, Clone)]
pub struct X86TemplateInstRecord {
pub template_name: String,
pub args: Vec<String>,
pub is_explicit: bool,
pub is_extern: bool,
pub mangled_name: String,
pub ir: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct X86TemplateODRMerger {
pub instantiated: std::collections::HashSet<String>,
}
impl X86TemplateODRMerger {
pub fn is_duplicate(&self, key: &str) -> bool {
self.instantiated.contains(key)
}
pub fn register(&mut self, key: &str) {
self.instantiated.insert(key.to_string());
}
}
impl X86TemplateInstantiation {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
instantiations: Vec::new(),
odr_merger: X86TemplateODRMerger::default(),
use_linkonce_odr: true,
}
}
pub fn register(
&mut self,
template_name: &str,
args: &[String],
is_explicit: bool,
is_extern: bool,
mangled_name: &str,
) {
let key = format!("{}<{}>", template_name, args.join(","));
self.instantiations.push(X86TemplateInstRecord {
template_name: template_name.to_string(),
args: args.to_vec(),
is_explicit,
is_extern,
mangled_name: mangled_name.to_string(),
ir: None,
});
}
pub fn instantiate(&mut self, template_name: &str, args: &[String]) -> Result<String, String> {
let key = format!("{}<{}>", template_name, args.join(","));
if self.odr_merger.is_duplicate(&key) {
return Ok(format!(
"; Template {} already instantiated (ODR)\n\
declare external void @_Z{}_{}v()\n",
key,
template_name,
args.join("_"),
));
}
self.odr_merger.register(&key);
let linkage = if self.use_linkonce_odr {
"linkonce_odr"
} else {
"weak_odr"
};
let mut ir = String::new();
ir.push_str(&format!(
"; Template instantiation: {}<{}>\n",
template_name,
args.join(", "),
));
ir.push_str(&format!(
"define {} dso_local void @_Z{}_{}v() {{\n",
linkage,
template_name,
args.join("_"),
));
ir.push_str(&format!(
"entry:\n ; Instantiation body for {}<{}>\n",
template_name,
args.join(", "),
));
for (i, arg) in args.iter().enumerate() {
ir.push_str(&format!(" ; T{} = {}\n", i, arg,));
}
ir.push_str(" ret void\n");
ir.push_str("}\n");
Ok(ir)
}
pub fn gen_explicit_instantiation_decl(&self, template_name: &str, args: &[String]) -> String {
format!(
"declare dso_local void @_Z{}_{}v()\n",
template_name,
args.join("_"),
)
}
pub fn gen_extern_template_decl(&self, template_name: &str, args: &[String]) -> String {
format!(
"declare external void @_Z{}_{}v()\n",
template_name,
args.join("_"),
)
}
pub fn gen_comdat_fold(&self, mangled_name: &str, body_ir: &str) -> String {
format!(
"$comdat_{} = comdat any\n\
define linkonce_odr void @{}() comdat {{\n\
{}\n\
}}\n",
mangled_name, mangled_name, body_ir,
)
}
}
impl Default for X86TemplateInstantiation {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
pub struct X86CXXModules {
pub is_64bit: bool,
pub triple: String,
pub modules: Vec<X86ModuleRecord>,
pub module_map: std::collections::HashMap<String, X86ModuleInfo>,
pub current_module: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86ModuleRecord {
pub name: String,
pub is_interface: bool,
pub is_partition: bool,
pub parent_module: Option<String>,
pub owned_entities: Vec<String>,
pub imports: Vec<String>,
pub exports: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct X86ModuleInfo {
pub name: String,
pub bmi_path: Option<String>,
pub compiled: bool,
pub dependencies: Vec<String>,
}
impl X86CXXModules {
pub fn new(is_64bit: bool, triple: String) -> Self {
Self {
is_64bit,
triple,
modules: Vec::new(),
module_map: std::collections::HashMap::new(),
current_module: None,
}
}
pub fn register(
&mut self,
name: &str,
is_interface: bool,
is_partition: bool,
parent_module: Option<&str>,
) {
let module = X86ModuleRecord {
name: name.to_string(),
is_interface,
is_partition,
parent_module: parent_module.map(|s| s.to_string()),
owned_entities: Vec::new(),
imports: Vec::new(),
exports: Vec::new(),
};
self.module_map.insert(
name.to_string(),
X86ModuleInfo {
name: name.to_string(),
bmi_path: None,
compiled: false,
dependencies: Vec::new(),
},
);
self.modules.push(module);
}
pub fn add_import(&mut self, from_module: &str, import_module: &str) {
if let Some(module) = self.modules.iter_mut().find(|m| m.name == from_module) {
if !module.imports.contains(&import_module.to_string()) {
module.imports.push(import_module.to_string());
}
}
if let Some(info) = self.module_map.get_mut(from_module) {
if !info.dependencies.contains(&import_module.to_string()) {
info.dependencies.push(import_module.to_string());
}
}
}
pub fn process(&mut self, module_name: &str, source: &str) -> Result<String, String> {
let module = self
.modules
.iter()
.find(|m| m.name == module_name)
.ok_or_else(|| format!("Module not found: {}", module_name))?;
let mut ir = String::new();
ir.push_str(&format!(
"; C++20 Module: {} (interface: {})\n",
module_name, module.is_interface,
));
if module.is_interface {
ir.push_str(&format!("source_filename = \"{}.cppm\"\n", module_name,));
} else {
ir.push_str(&format!("source_filename = \"{}.cpp\"\n", module_name,));
}
ir.push_str(&format!(
"!llvm.module.flags = !{{!0}}\n\
!0 = !{{i32 1, !\"Module Name\", !\"{}\"}}\n",
module_name,
));
for import in &module.imports {
ir.push_str(&format!("; import {};\n", import,));
ir.push_str(&format!(
"@__module_import_{} = external global i8\n",
import,
));
}
ir.push_str(&format!("; Module source:\n{}\n", source,));
if let Some(info) = self.module_map.get_mut(module_name) {
info.compiled = true;
}
Ok(ir)
}
pub fn gen_module_init(&self, module_name: &str) -> String {
format!(
"define internal void @_ZGIW3{0}v() {{\n\
entry:\n\
\x20 ; Module initializer for {0}\n\
\x20 ; Call initializers of imported modules\n\
\x20 ret void\n\
}}\n",
module_name,
)
}
pub fn gen_module_ownership_check(&self, entity: &str, module_name: &str) -> String {
format!(
"; Check ownership: {} belongs to module {}\n\
\x20 ; Ownership verified at compile time\n",
entity, module_name,
)
}
pub fn gen_global_module_fragment(&self) -> String {
"module;\n; Global module fragment\n".to_string()
}
pub fn gen_private_module_fragment(&self) -> String {
"module :private;\n; Private module fragment\n".to_string()
}
pub fn set_current_module(&mut self, module_name: Option<&str>) {
self.current_module = module_name.map(|s| s.to_string());
}
pub fn current_module_name(&self) -> Option<&str> {
self.current_module.as_deref()
}
}
impl Default for X86CXXModules {
fn default() -> Self {
Self::new(true, "x86_64-unknown-linux-gnu".to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MemberPtrRep {
Single4,
Single8,
Pair8,
Pair16,
}
impl X86MemberPtrRep {
pub fn size_bytes(&self) -> u32 {
match self {
Self::Single4 => 4,
Self::Single8 => 8,
Self::Pair8 => 8,
Self::Pair16 => 16,
}
}
pub fn is_pair(&self) -> bool {
matches!(self, Self::Pair8 | Self::Pair16)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cxx86_codegen_default() {
let codegen = CXX86CodeGen::new_x86_64("test", CppStandard::Cpp17);
assert!(codegen.is_64bit);
assert_eq!(codegen.triple, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_cxx86_codegen_x86_32() {
let codegen = CXX86CodeGen::new_x86_32("test", CppStandard::Cpp17);
assert!(!codegen.is_64bit);
}
#[test]
fn test_cxx86_codegen_mangle_x86_name() {
let mut codegen = CXX86CodeGen::new_x86_64("test", CppStandard::Cpp17);
let mangled = codegen.mangle_x86_name("myFunc", &[], false, false);
assert!(!mangled.is_empty());
}
#[test]
fn test_cxx86_codegen_is_itanium_abi() {
let codegen = CXX86CodeGen::new_x86_64("test", CppStandard::Cpp17);
assert!(codegen.is_itanium_abi());
assert!(!codegen.is_msvc_abi());
}
#[test]
fn test_compilation_result_merge() {
let result = X86CompilationResult {
ir: "define void @f() { ret void }".to_string(),
vtables: "; vtables\n".to_string(),
rtti: "; rtti\n".to_string(),
eh_tables: "; eh\n".to_string(),
guard_variables: "; guards\n".to_string(),
typeinfo: "; typeinfo\n".to_string(),
triple: "x86_64-linux".to_string(),
};
let merged = result.to_single_module();
assert!(merged.contains("Main IR"));
assert!(merged.contains("Vtables"));
assert!(merged.contains("RTTI"));
assert!(merged.contains("EH Tables"));
assert!(merged.contains("Guard Variables"));
assert!(merged.contains("Type Info"));
}
#[test]
fn test_compilation_result_size() {
let result = X86CompilationResult {
ir: "12345".to_string(),
vtables: "67890".to_string(),
rtti: "abcde".to_string(),
eh_tables: "fghij".to_string(),
guard_variables: "klmno".to_string(),
typeinfo: "pqrst".to_string(),
triple: "x86_64".to_string(),
};
assert_eq!(result.ir_size(), 30);
}
#[test]
fn test_vtable_emitter_default() {
let emitter = X86VTableEmitter::default();
assert!(emitter.is_64bit);
assert_eq!(emitter.ptr_size(), 8);
assert_eq!(emitter.ptr_align(), 8);
}
#[test]
fn test_vtable_emitter_32bit() {
let emitter = X86VTableEmitter::new(
Mangler::default(),
false,
"i386-unknown-linux-gnu".to_string(),
);
assert!(!emitter.is_64bit);
assert_eq!(emitter.ptr_size(), 4);
}
#[test]
fn test_vtable_entry_kind_as_str() {
assert_eq!(X86VtableEntryKind::OffsetToTop.as_str(), "offset_to_top");
assert_eq!(X86VtableEntryKind::RttiPtr.as_str(), "rtti_ptr");
assert_eq!(X86VtableEntryKind::VFuncPtr.as_str(), "vfunc_ptr");
assert_eq!(
X86VtableEntryKind::VirtualBaseOffset.as_str(),
"vbase_offset"
);
assert_eq!(X86VtableEntryKind::CompleteDtor.as_str(), "complete_dtor");
assert_eq!(X86VtableEntryKind::DeletingDtor.as_str(), "deleting_dtor");
assert_eq!(X86VtableEntryKind::BaseObjectDtor.as_str(), "base_dtor");
}
#[test]
fn test_vtable_emission_empty() {
let emitter = X86VTableEmitter::default();
let ir = emitter.emit_all();
assert!(ir.contains("X86 Vtables"));
}
#[test]
fn test_vtable_emission_with_entries() {
let mut emitter = X86VTableEmitter::default();
let mut layout = VtableLayout::new("MyClass", MangledName::new("_ZTV7MyClass".into()));
layout.add_offset_to_top(0);
layout.add_rtti_ptr(MangledName::new("_ZTIMyClass".into()));
layout.num_entries = 2;
emitter.add_vtable("MyClass", layout);
let ir = emitter.emit_all();
assert!(ir.contains("MyClass"));
}
#[test]
fn test_thunk_emission() {
let emitter = X86VTableEmitter::default();
let mut emitter_with_thunk =
X86VTableEmitter::new(Mangler::default(), true, "x86_64".to_string());
emitter_with_thunk.add_thunk("_ZThn8_N3Foo3barEv", "_ZN3Foo3barEv", 8, 0);
let ir = emitter_with_thunk.emit_all();
assert!(ir.contains("_ZThn8_N3Foo3barEv"));
}
#[test]
fn test_vtt_emission() {
let mut emitter = X86VTableEmitter::default();
emitter.add_vtt_entry("Base", "_ZTVBase", 0);
let ir = emitter.emit_all();
assert!(ir.contains("VTT"));
}
#[test]
fn test_this_register_name() {
let reg = X86ThisRegister::Default;
assert_eq!(reg.register_name(true), "%rdi");
assert_eq!(reg.register_name(false), "%ecx");
}
#[test]
fn test_secondary_vtable() {
let mut emitter = X86VTableEmitter::default();
let layout = VtableLayout::new("Base", MangledName::new("_ZTV4Base".into()));
emitter.add_secondary_vtable("Derived", "Base", 16, layout);
assert_eq!(emitter.secondary_vtables.len(), 1);
let ir = emitter.emit_all();
assert!(ir.contains("_ZTSBase"));
}
#[test]
fn test_rtti_emitter_default() {
let emitter = X86RTTIEmitter::default();
assert!(emitter.is_64bit);
assert_eq!(emitter.ptr_type(), "i64");
}
#[test]
fn test_rtti_build_fundamental() {
let mut emitter = X86RTTIEmitter::default();
let desc = emitter.build_fundamental("int");
assert_eq!(desc.kind, X86RTTIKind::Fundamental);
assert_eq!(desc.type_name, "int");
}
#[test]
fn test_rtti_build_class() {
let mut emitter = X86RTTIEmitter::default();
let bases: Vec<X86BaseClassInfo> = Vec::new();
let desc = emitter.build_class("MyClass", "MyClass", "_ZTVMyClass", &bases);
assert_eq!(desc.kind, X86RTTIKind::Class);
}
#[test]
fn test_rtti_build_class_single_base() {
let mut emitter = X86RTTIEmitter::default();
let base = X86BaseClassInfo {
typeinfo_sym: "_ZTIBase".to_string(),
flags: 0,
offset: 0,
};
let desc = emitter.build_class("Derived", "Derived", "_ZTVDerived", &[base]);
assert_eq!(desc.kind, X86RTTIKind::SiClass);
}
#[test]
fn test_rtti_build_class_multiple_bases() {
let mut emitter = X86RTTIEmitter::default();
let base1 = X86BaseClassInfo {
typeinfo_sym: "A".into(),
flags: 0,
offset: 0,
};
let base2 = X86BaseClassInfo {
typeinfo_sym: "B".into(),
flags: 0,
offset: 16,
};
let desc = emitter.build_class("C", "C", "_ZTVC", &[base1, base2]);
assert_eq!(desc.kind, X86RTTIKind::VmiClass);
}
#[test]
fn test_rtti_build_pointer() {
let mut emitter = X86RTTIEmitter::default();
let desc = emitter.build_pointer("int", "_ZTIi");
assert_eq!(desc.kind, X86RTTIKind::Pointer);
assert_eq!(desc.type_name, "int*");
}
#[test]
fn test_rtti_build_array() {
let mut emitter = X86RTTIEmitter::default();
let desc = emitter.build_array("int", "_ZTIi", 10);
assert_eq!(desc.kind, X86RTTIKind::Array);
assert_eq!(desc.type_name, "A10_int");
}
#[test]
fn test_rtti_emission_all() {
let mut emitter = X86RTTIEmitter::default();
emitter.build_fundamental("int");
emitter.build_fundamental("float");
let ir = emitter.emit_all();
assert!(ir.contains("X86 RTTI"));
}
#[test]
fn test_rtti_get_descriptor() {
let mut emitter = X86RTTIEmitter::default();
emitter.build_fundamental("int");
let desc = emitter.get_descriptor("int");
assert!(desc.is_some());
assert_eq!(desc.unwrap().type_name, "int");
}
#[test]
fn test_rtti_disable() {
let mut emitter = X86RTTIEmitter::default();
emitter.disable_rtti();
assert!(!emitter.rtti_enabled());
}
#[test]
fn test_rtti_kind_as_str() {
assert_eq!(X86RTTIKind::Fundamental.as_str(), "fundamental");
assert_eq!(X86RTTIKind::Pointer.as_str(), "pointer");
assert_eq!(X86RTTIKind::Class.as_str(), "class");
assert_eq!(X86RTTIKind::SiClass.as_str(), "si_class");
assert_eq!(X86RTTIKind::VmiClass.as_str(), "vmi_class");
}
#[test]
fn test_eh_codegen_default() {
let eh = X86EHCodeGen::default();
assert!(eh.is_64bit);
assert_eq!(eh.personality, X86PersonalityKind::ItaniumGCC);
}
#[test]
fn test_eh_codegen_itanium() {
let base = EHCodeGen::itanium();
let eh = X86EHCodeGen::new_itanium(base, true);
assert!(eh.personality.is_itanium());
}
#[test]
fn test_eh_codegen_seh() {
let base = EHCodeGen::new(PersonalityKind::Seh);
let eh = X86EHCodeGen::new_seh(base, true);
assert!(eh.personality.is_msvc());
}
#[test]
fn test_eh_personality_function_name() {
assert_eq!(
X86PersonalityKind::ItaniumGCC.function_name(),
"__gxx_personality_v0"
);
assert_eq!(
X86PersonalityKind::MSVC.function_name(),
"__CxxFrameHandler3"
);
}
#[test]
fn test_eh_next_lp_label() {
let base = EHCodeGen::itanium();
let mut eh = X86EHCodeGen::new_itanium(base, true);
let label1 = eh.next_lp_label();
let label2 = eh.next_lp_label();
assert!(label1 != label2);
assert!(label1.starts_with("L.lpad"));
}
#[test]
fn test_eh_register_typeinfo() {
let base = EHCodeGen::itanium();
let mut eh = X86EHCodeGen::new_itanium(base, true);
eh.register_typeinfo("_ZTIi");
assert_eq!(eh.type_infos.len(), 1);
eh.register_typeinfo("_ZTIi"); assert_eq!(eh.type_infos.len(), 1);
}
#[test]
fn test_eh_add_call_site() {
let base = EHCodeGen::itanium();
let mut eh = X86EHCodeGen::new_itanium(base, true);
eh.add_call_site(0, 10, 15, 1);
assert_eq!(eh.call_sites.len(), 1);
}
#[test]
fn test_eh_add_type() {
let base = EHCodeGen::itanium();
let mut eh = X86EHCodeGen::new_itanium(base, true);
eh.add_type("_ZTIi");
eh.add_type("_ZTIf");
assert_eq!(eh.type_table.len(), 2);
}
#[test]
fn test_eh_emit_personality_decl() {
let base = EHCodeGen::itanium();
let eh = X86EHCodeGen::new_itanium(base, true);
let decl = eh.emit_personality_decl();
assert!(decl.contains("__gxx_personality_v0"));
}
#[test]
fn test_eh_emit_runtime_decls() {
let base = EHCodeGen::itanium();
let eh = X86EHCodeGen::new_itanium(base, true);
let decls = eh.emit_runtime_decls();
assert!(decls.contains("__cxa_begin_catch"));
assert!(decls.contains("__cxa_throw"));
assert!(decls.contains("llvm.eh"));
}
#[test]
fn test_eh_landing_pad_ir() {
let base = EHCodeGen::itanium();
let eh = X86EHCodeGen::new_itanium(base, true);
let lp = X86LandingPad {
label: "L.lpad1".to_string(),
catch_types: vec!["_ZTIi".to_string()],
has_cleanup: true,
is_catch_all: false,
catch_selectors: Vec::new(),
};
let ir = eh.emit_landing_pad_ir(&lp);
assert!(ir.contains("landingpad"));
assert!(ir.contains("cleanup"));
assert!(ir.contains("__cxa_begin_catch"));
}
#[test]
fn test_eh_throw_ir() {
let base = EHCodeGen::itanium();
let eh = X86EHCodeGen::new_itanium(base, true);
let ir = eh.emit_throw_ir("%exn", "_ZTIi", None);
assert!(ir.contains("__cxa_throw"));
assert!(ir.contains("unreachable"));
}
#[test]
fn test_eh_begin_end_function() {
let base = EHCodeGen::itanium();
let mut eh = X86EHCodeGen::new_itanium(base, true);
eh.begin_function();
assert_eq!(eh.lp_counter, 0);
let lsda = eh.end_function("test_fn");
assert_eq!(lsda.function_name, "test_fn");
}
#[test]
fn test_guard_emission_default() {
let guard = X86GuardEmission::default();
assert!(guard.is_64bit);
}
#[test]
fn test_guard_variable_constants() {
assert_eq!(X86GuardVariable::UNINITIALIZED, 0);
assert_eq!(X86GuardVariable::INITIALIZING, 1);
assert_eq!(X86GuardVariable::INITIALIZED, u64::MAX);
}
#[test]
fn test_guard_create() {
let mut guard_emitter = X86GuardEmission::default();
let mangler = Mangler::default();
let g = guard_emitter.create_guard(&mangler, "myStatic");
assert_eq!(g.protected_var, "myStatic");
assert_eq!(g.size, 8);
assert!(!g.emitted);
}
#[test]
fn test_guard_acquire_ir() {
let guard = X86GuardEmission::default();
let gv = X86GuardVariable {
mangled_name: "_ZGVZ3fooE1x".to_string(),
protected_var: "x".to_string(),
size: 8,
emitted: false,
initial_value: 0,
acquired_value: 1,
};
let ir = guard.gen_guard_acquire_ir(&gv);
assert!(ir.contains("@_ZGVZ3fooE1x"));
assert!(ir.contains("cmpxchg"));
}
#[test]
fn test_guard_release_ir() {
let guard = X86GuardEmission::default();
let gv = X86GuardVariable {
mangled_name: "_ZGVZ3fooE1x".to_string(),
protected_var: "x".to_string(),
size: 8,
emitted: false,
initial_value: 0,
acquired_value: 1,
};
let ir = guard.gen_guard_release_ir(&gv);
assert!(ir.contains("@_ZGVZ3fooE1x"));
assert!(ir.contains("store atomic"));
}
#[test]
fn test_guard_decl_ir() {
let guard = X86GuardEmission::default();
let gv = X86GuardVariable {
mangled_name: "_ZGVZ3fooE1x".to_string(),
protected_var: "x".to_string(),
size: 8,
emitted: false,
initial_value: 0,
acquired_value: 1,
};
let ir = guard.gen_guard_decl_ir(&gv);
assert!(ir.contains("linkonce_odr"));
assert!(ir.contains("global i64 0"));
}
#[test]
fn test_guard_emit_all() {
let mut guard = X86GuardEmission::default();
let mangler = Mangler::default();
guard.create_guard(&mangler, "x");
guard.create_guard(&mangler, "y");
let ir = guard.emit_all();
assert!(ir.contains("X86 Guard Variables"));
}
#[test]
fn test_guard_get_and_mark() {
let mut guard = X86GuardEmission::default();
let mangler = Mangler::default();
let gv = guard.create_guard(&mangler, "x");
assert!(guard.get_guard("x").is_some());
assert!(guard.get_guard("y").is_none());
guard.mark_emitted("x");
assert!(guard.get_guard("x").unwrap().emitted);
}
#[test]
fn test_typeinfo_emitter_default() {
let emitter = X86TypeInfoEmitter::default();
assert!(emitter.is_64bit);
assert!(emitter.use_comdat);
}
#[test]
fn test_typeinfo_register() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.register("int", X86RTTIKind::Fundamental, None, None);
emitter.register("MyClass", X86RTTIKind::Class, Some("_ZTVMyClass"), None);
assert_eq!(emitter.records.len(), 2);
}
#[test]
fn test_typeinfo_register_dedup() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.register("int", X86RTTIKind::Fundamental, None, None);
emitter.register("int", X86RTTIKind::Fundamental, None, None);
assert_eq!(emitter.records.len(), 1);
}
#[test]
fn test_typeinfo_emit_all() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.register("int", X86RTTIKind::Fundamental, None, None);
let ir = emitter.emit_all();
assert!(ir.contains("X86 Type Info"));
}
#[test]
fn test_typeinfo_get_sym() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.register("int", X86RTTIKind::Fundamental, None, None);
let sym = emitter.get_typeinfo_sym("int");
assert!(sym.is_some());
assert!(emitter.get_typeinfo_sym("float").is_none());
}
#[test]
fn test_typeinfo_disable_comdat() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.disable_comdat();
assert!(!emitter.use_comdat);
}
#[test]
fn test_ctor_dtor_default() {
let lowering = X86CtorDtorLowering::default();
assert!(lowering.is_64bit);
}
#[test]
fn test_ctor_variant_suffix() {
assert_eq!(X86CtorVariant::Complete.suffix(), "C1");
assert_eq!(X86CtorVariant::Base.suffix(), "C2");
assert_eq!(X86CtorVariant::Allocating.suffix(), "C3");
}
#[test]
fn test_dtor_variant_suffix() {
assert_eq!(X86DtorVariant::Deleting.suffix(), "D0");
assert_eq!(X86DtorVariant::Complete.suffix(), "D1");
assert_eq!(X86DtorVariant::Base.suffix(), "D2");
}
#[test]
fn test_ctor_variant_requires_vptr_init() {
assert!(X86CtorVariant::Complete.requires_vptr_init());
assert!(X86CtorVariant::Base.requires_vptr_init());
assert!(!X86CtorVariant::Allocating.requires_vptr_init());
}
#[test]
fn test_ctor_variant_requires_allocation() {
assert!(!X86CtorVariant::Complete.requires_allocation());
assert!(!X86CtorVariant::Base.requires_allocation());
assert!(X86CtorVariant::Allocating.requires_allocation());
}
#[test]
fn test_dtor_variant_calls_delete() {
assert!(X86DtorVariant::Deleting.calls_delete());
assert!(!X86DtorVariant::Complete.calls_delete());
assert!(!X86DtorVariant::Base.calls_delete());
}
#[test]
fn test_dtor_variant_adjusts_vptr() {
assert!(X86DtorVariant::Deleting.adjusts_vptr());
assert!(X86DtorVariant::Complete.adjusts_vptr());
assert!(!X86DtorVariant::Base.adjusts_vptr());
}
#[test]
fn test_register_ctor() {
let mut lowering = X86CtorDtorLowering::default();
lowering.register_ctor(
"MyClass",
X86CtorVariant::Complete,
"_ZN7MyClassC1Ev",
&[],
false,
);
assert_eq!(lowering.ctor_variants.len(), 1);
}
#[test]
fn test_register_dtor() {
let mut lowering = X86CtorDtorLowering::default();
lowering.register_dtor(
"MyClass",
X86DtorVariant::Complete,
"_ZN7MyClassD1Ev",
false,
);
assert_eq!(lowering.dtor_variants.len(), 1);
}
#[test]
fn test_lower_constructor_not_found() {
let lowering = X86CtorDtorLowering::default();
let result = lowering.lower_constructor("NoSuchClass", X86CtorVariant::Complete);
assert!(result.is_err());
}
#[test]
fn test_lower_destructor_not_found() {
let lowering = X86CtorDtorLowering::default();
let result = lowering.lower_destructor("NoSuchClass", X86DtorVariant::Complete);
assert!(result.is_err());
}
#[test]
fn test_lower_constructor_with_virtual_bases() {
let mut lowering = X86CtorDtorLowering::default();
lowering.register_ctor(
"MyClass",
X86CtorVariant::Complete,
"_ZN7MyClassC1Ev",
&[],
true,
);
let ir = lowering
.lower_constructor("MyClass", X86CtorVariant::Complete)
.unwrap();
assert!(ir.contains("@_ZTVMyClass"));
}
#[test]
fn test_lower_destructor_with_members() {
let mut lowering = X86CtorDtorLowering::default();
lowering.register_dtor(
"MyClass",
X86DtorVariant::Complete,
"_ZN7MyClassD1Ev",
false,
);
let ir = lowering
.lower_destructor("MyClass", X86DtorVariant::Complete)
.unwrap();
assert!(ir.contains("@_ZTVMyClass"));
}
#[test]
fn test_virtual_dispatch_default() {
let dispatch = X86VirtualDispatch::default();
assert!(dispatch.is_64bit);
}
#[test]
fn test_virtual_dispatch_register() {
let mut dispatch = X86VirtualDispatch::default();
dispatch.register("MyClass", "foo", 0, 0, "void", &[]);
assert_eq!(dispatch.dispatches.len(), 1);
}
#[test]
fn test_virtual_dispatch_gen() {
let mut dispatch = X86VirtualDispatch::default();
dispatch.register("MyClass", "foo", 0, 0, "void", &[]);
let ir = dispatch.gen_dispatch("MyClass", "foo", 0).unwrap();
assert!(ir.contains("vtable.ptr"));
assert!(ir.contains("vfunc"));
}
#[test]
fn test_virtual_dispatch_not_found() {
let dispatch = X86VirtualDispatch::default();
let result = dispatch.gen_dispatch("NoClass", "foo", 0);
assert!(result.is_err());
}
#[test]
fn test_virtual_dispatch_with_adjustment() {
let mut dispatch = X86VirtualDispatch::default();
dispatch.register("Derived", "bar", 1, 8, "i32", &[]);
let ir = dispatch.gen_dispatch("Derived", "bar", 1).unwrap();
assert!(ir.contains("this.adjusted"));
}
#[test]
fn test_pure_virtual_call() {
let dispatch = X86VirtualDispatch::default();
let ir = dispatch.gen_pure_virtual_call();
assert!(ir.contains("__cxa_pure_virtual"));
assert!(ir.contains("__clang_call_terminate"));
}
#[test]
fn test_vtable_offset() {
let dispatch = X86VirtualDispatch::default();
assert_eq!(dispatch.vtable_offset(0), 2);
assert_eq!(dispatch.vtable_offset(3), 5);
assert_eq!(dispatch.vtable_offset(10), 12);
}
#[test]
fn test_member_pointer_default() {
let mp = X86MemberPointer::default();
assert!(mp.is_64bit);
}
#[test]
fn test_register_data_member() {
let mut mp = X86MemberPointer::default();
mp.register_data_member("MyClass", "x", "i32", 4);
assert_eq!(mp.data_member_ptrs.len(), 1);
}
#[test]
fn test_register_func_member() {
let mut mp = X86MemberPointer::default();
mp.register_func_member("MyClass", "foo", 0x400000, 0, false, None);
assert_eq!(mp.func_member_ptrs.len(), 1);
}
#[test]
fn test_lower_data_member() {
let mut mp = X86MemberPointer::default();
mp.register_data_member("MyClass", "x", "i32", 4);
let ir = mp.lower("MyClass", "x", "i32").unwrap();
assert!(ir.contains("getelementptr"));
assert!(ir.contains("%this"));
}
#[test]
fn test_lower_not_found() {
let mp = X86MemberPointer::default();
let result = mp.lower("NoClass", "x", "i32");
assert!(result.is_err());
}
#[test]
fn test_lower_func_member_virtual() {
let mut mp = X86MemberPointer::default();
mp.register_func_member("MyClass", "vfunc", 0, 0, true, Some(1));
let ir = mp.lower("MyClass", "vfunc", "void").unwrap();
assert!(ir.contains("vtable"));
}
#[test]
fn test_memptr_compare() {
let mp = X86MemberPointer::default();
let lhs = X86FuncMemberPtr {
class_name: "X".into(),
method_name: "f".into(),
func_ptr: 0x1000,
this_adjustment: 0,
is_virtual: false,
vtable_index: None,
};
let rhs = X86FuncMemberPtr {
class_name: "X".into(),
method_name: "f".into(),
func_ptr: 0x1000,
this_adjustment: 0,
is_virtual: false,
vtable_index: None,
};
let ir = mp.gen_compare(&lhs, &rhs);
assert!(ir.contains("icmp eq"));
}
#[test]
fn test_lambda_lowering_default() {
let lowering = X86LambdaLowering::default();
assert!(lowering.is_64bit);
assert_eq!(lowering.lambda_counter, 0);
}
#[test]
fn test_lambda_lower_no_captures() {
let mut lowering = X86LambdaLowering::default();
let result = lowering.lower(&[], "x + 1");
assert!(result.is_ok());
let ir = result.unwrap();
assert!(ir.contains("Lambda 0"));
assert!(ir.contains("class.anon.0"));
}
#[test]
fn test_lambda_lower_with_capture() {
let mut lowering = X86LambdaLowering::default();
let result = lowering.lower(&[], "body");
assert!(result.is_ok());
}
#[test]
fn test_lambda_counter_increments() {
let mut lowering = X86LambdaLowering::default();
let _ = lowering.lower(&[], "a");
let _ = lowering.lower(&[], "b");
assert_eq!(lowering.lambda_counter, 2);
}
#[test]
fn test_lambda_stateless_conversion() {
let lowering = X86LambdaLowering::default();
let _ = lowering.lambdas.len(); let ir = lowering.gen_stateless_conversion(0, "void", &[]);
assert!(ir.contains("to_fn"));
}
#[test]
fn test_closure_construct() {
let lowering = X86LambdaLowering::default();
let captures = vec![X86LambdaCapture {
var_name: "x".into(),
var_type: "i32".into(),
kind: X86CaptureKind::ByValue,
is_ref: false,
}];
let ir = lowering.gen_closure_construct(0, &captures);
assert!(ir.contains("closure"));
}
#[test]
fn test_new_delete_default() {
let nd = X86NewDelete::default();
assert!(nd.is_64bit);
assert!(nd.use_array_cookie);
assert!(nd.support_nothrow);
}
#[test]
fn test_gen_new_basic() {
let mut nd = X86NewDelete::default();
let ir = nd.gen_new(8, 8, false).unwrap();
assert!(ir.contains("_Znwm"));
assert!(!ir.contains("_ZnwmRKSt9nothrow_t"));
}
#[test]
fn test_gen_new_nothrow() {
let mut nd = X86NewDelete::default();
let ir = nd.gen_new(8, 8, true).unwrap();
assert!(ir.contains("_ZnwmRKSt9nothrow_t"));
assert!(ir.contains("new.isnull"));
}
#[test]
fn test_gen_delete_basic() {
let mut nd = X86NewDelete::default();
let ir = nd.gen_delete("%ptr", 8).unwrap();
assert!(ir.contains("_ZdlPv"));
assert!(ir.contains("del.isnull"));
}
#[test]
fn test_gen_array_new_with_cookie() {
let mut nd = X86NewDelete::default();
let ir = nd.gen_array_new_with_cookie(4, 10, "int").unwrap();
assert!(ir.contains("_Znam"));
assert!(ir.contains("arr.ctor.loop"));
}
#[test]
fn test_gen_sized_delete() {
let mut nd = X86NewDelete::default();
let ir = nd.gen_sized_delete("%ptr", 16);
assert!(ir.contains("_ZdlPvm"));
}
#[test]
fn test_array_ctor_dtor_default() {
let act = X86ArrayCtorDtor::default();
assert!(act.is_64bit);
}
#[test]
fn test_array_register_construction() {
let mut act = X86ArrayCtorDtor::default();
act.register_construction("MyClass", 10, "%arr", false);
assert_eq!(act.constructions.len(), 1);
}
#[test]
fn test_array_lower_construction() {
let mut act = X86ArrayCtorDtor::default();
act.register_construction("MyClass", 5, "%arr", false);
let ir = act.lower_construction("MyClass", 5).unwrap();
assert!(ir.contains("arr.ctor"));
assert!(ir.contains("loop"));
}
#[test]
fn test_array_lower_destruction() {
let mut act = X86ArrayCtorDtor::default();
act.register_destruction("MyClass", 5, "%arr", false);
let ir = act.lower_destruction("MyClass", 5).unwrap();
assert!(ir.contains("arr.dtor"));
assert!(ir.contains("reverse"));
}
#[test]
fn test_array_construction_not_found() {
let act = X86ArrayCtorDtor::default();
let result = act.lower_construction("NoClass", 10);
assert!(result.is_err());
}
#[test]
fn test_array_destruction_not_found() {
let act = X86ArrayCtorDtor::default();
let result = act.lower_destruction("NoClass", 10);
assert!(result.is_err());
}
#[test]
fn test_array_ctor_with_eh_cleanup() {
let act = X86ArrayCtorDtor::default();
let ir = act.gen_array_ctor_with_eh_cleanup("MyClass", 5, "%arr");
assert!(ir.contains("landingpad"));
assert!(ir.contains("cleanup"));
}
#[test]
fn test_dynamic_cast_default() {
let dc = X86DynamicCast::default();
assert!(dc.is_64bit);
}
#[test]
fn test_dynamic_cast_register() {
let mut dc = X86DynamicCast::default();
dc.register(
"Base",
"Derived",
"_ZTIBase",
"_ZTIDerived",
"%obj",
X86DynCastKind::Downcast,
0,
);
assert_eq!(dc.casts.len(), 1);
}
#[test]
fn test_dynamic_cast_identity() {
let mut dc = X86DynamicCast::default();
dc.register(
"X",
"X",
"_ZTIX",
"_ZTIX",
"%obj",
X86DynCastKind::Identity,
0,
);
let ir = dc.gen_cast("%obj", "X", "X").unwrap();
assert!(ir.contains("dc.nullcheck"));
}
#[test]
fn test_dynamic_cast_not_found() {
let dc = X86DynamicCast::default();
let result = dc.gen_cast("%obj", "A", "B");
assert!(result.is_err());
}
#[test]
fn test_dynamic_cast_to_void() {
let mut dc = X86DynamicCast::default();
dc.register(
"Base",
"void",
"_ZTIBase",
"",
"%obj",
X86DynCastKind::ToVoid,
0,
);
let ir = dc.gen_cast("%obj", "Base", "void").unwrap();
assert!(ir.contains("vptr"));
}
#[test]
fn test_dynamic_cast_runtime_decl() {
let dc = X86DynamicCast::default();
let decl = dc.gen_runtime_decl();
assert!(decl.contains("__dynamic_cast"));
}
#[test]
fn test_typeid_default() {
let tid = X86TypeID::default();
assert!(tid.is_64bit);
}
#[test]
fn test_typeid_register() {
let mut tid = X86TypeID::default();
tid.register("int", false, "_ZTIi");
assert_eq!(tid.records.len(), 1);
}
#[test]
fn test_typeid_gen_static() {
let mut tid = X86TypeID::default();
tid.register("int", false, "_ZTIi");
let ir = tid.gen_typeid("int").unwrap();
assert!(ir.contains("typeid"));
}
#[test]
fn test_typeid_not_found() {
let tid = X86TypeID::default();
let result = tid.gen_typeid("unknown");
assert!(result.is_err());
}
#[test]
fn test_typeid_compare() {
let tid = X86TypeID::default();
let ir = tid.gen_typeid_compare("_ZTIi", "_ZTIf");
assert!(ir.contains("icmp eq"));
}
#[test]
fn test_typeid_runtime_decl() {
let tid = X86TypeID::default();
let decl = tid.gen_runtime_decl();
assert!(decl.contains("__cxa_bad_typeid"));
}
#[test]
fn test_static_init_default() {
let si = X86StaticInit::default();
assert!(si.is_64bit);
assert!(si.use_cxa_guard);
assert!(si.thread_safe);
}
#[test]
fn test_static_init_register() {
let mut si = X86StaticInit::default();
si.register("myVar", "_ZGVZ1xvE", "42", true);
assert_eq!(si.inits.len(), 1);
}
#[test]
fn test_static_init_gen_guard() {
let mut si = X86StaticInit::default();
si.register("myVar", "_ZGVZ1xvE", "42", true);
let ir = si.gen_guard("myVar").unwrap();
assert!(ir.contains("__cxa_guard_acquire"));
assert!(ir.contains("__cxa_guard_release"));
}
#[test]
fn test_static_init_not_found() {
let si = X86StaticInit::default();
let result = si.gen_guard("noSuchVar");
assert!(result.is_err());
}
#[test]
fn test_static_init_guard_decl() {
let si = X86StaticInit::default();
let decl = si.gen_guard_decl("_ZGVZ1xvE");
assert!(decl.contains("linkonce_odr"));
assert!(decl.contains("global i64 0"));
}
#[test]
fn test_static_init_runtime_decl() {
let si = X86StaticInit::default();
let decl = si.gen_runtime_decl();
assert!(decl.contains("__cxa_guard_acquire"));
assert!(decl.contains("__cxa_guard_release"));
assert!(decl.contains("__cxa_guard_abort"));
}
#[test]
fn test_thread_local_init_default() {
let tli = X86ThreadLocalInit::default();
assert!(tli.is_64bit);
assert!(tli.use_elf_tls);
}
#[test]
fn test_thread_local_init_register() {
let mut tli = X86ThreadLocalInit::default();
tli.register(
"myTls",
"i32",
"_ZTW5myTls",
false,
X86TLSModel::GeneralDynamic,
);
assert_eq!(tli.tls_vars.len(), 1);
}
#[test]
fn test_thread_local_init_gen() {
let mut tli = X86ThreadLocalInit::default();
tli.register(
"myTls",
"i32",
"_ZTW5myTls",
false,
X86TLSModel::GeneralDynamic,
);
let ir = tli.gen_init("myTls").unwrap();
assert!(ir.contains("__tls_get_addr"));
}
#[test]
fn test_thread_local_init_not_found() {
let tli = X86ThreadLocalInit::default();
let result = tli.gen_init("noVar");
assert!(result.is_err());
}
#[test]
fn test_thread_local_decl() {
let tli = X86ThreadLocalInit::default();
let decl = tli.gen_tls_decl("myTls", "i32", Some("42"));
assert!(decl.contains("thread_local"));
}
#[test]
fn test_tls_model_as_str() {
assert_eq!(X86TLSModel::GeneralDynamic.as_str(), "general-dynamic");
assert_eq!(X86TLSModel::LocalDynamic.as_str(), "local-dynamic");
assert_eq!(X86TLSModel::InitialExec.as_str(), "initial-exec");
assert_eq!(X86TLSModel::LocalExec.as_str(), "local-exec");
}
#[test]
fn test_abi_support_default() {
let abi = X86CXXABISupport::default();
assert!(abi.is_itanium_abi());
assert!(!abi.is_msvc_abi());
assert_eq!(abi.ptr_size(), 8);
}
#[test]
fn test_abi_support_msvc() {
let abi = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".to_string(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert!(abi.is_msvc_abi());
assert!(!abi.is_itanium_abi());
}
#[test]
fn test_abi_this_register() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.this_register(), "%rdi");
}
#[test]
fn test_abi_rtti_vtable_offset() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.rtti_vtable_offset(), 1);
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert_eq!(msvc.rtti_vtable_offset(), -1);
}
#[test]
fn test_abi_first_vfunc_index() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.first_vfunc_index(), 2);
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert_eq!(msvc.first_vfunc_index(), 0);
}
#[test]
fn test_abi_personality_function() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.personality_function(), "__gxx_personality_v0");
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert_eq!(msvc.personality_function(), "__CxxFrameHandler3");
}
#[test]
fn test_abi_mangling_scheme() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.mangling_scheme(), X86ManglingScheme::Itanium);
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert_eq!(msvc.mangling_scheme(), X86ManglingScheme::Microsoft);
}
#[test]
fn test_abi_record_alignment() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.record_alignment(), 8);
}
#[test]
fn test_abi_array_cookie() {
let abi = X86CXXABISupport::default();
assert!(!abi.requires_array_cookie(false));
assert!(abi.requires_array_cookie(true));
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert!(msvc.requires_array_cookie(false));
}
#[test]
fn test_abi_member_ptr_rep() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.member_ptr_rep(), X86MemberPtrRep::Pair16);
}
#[test]
fn test_abi_vtable_layout() {
let abi = X86CXXABISupport::default();
let layout = abi.vtable_layout_abi("Foo", 3);
assert_eq!(layout.len(), 5); }
#[test]
fn test_name_mangling_default() {
let nm = X86NameMangling::default();
assert!(nm.is_64bit);
assert_eq!(nm.scheme, X86ManglingScheme::Itanium);
}
#[test]
fn test_name_mangling_mangle_function() {
let mut nm = X86NameMangling::default();
let mangled = nm.mangle("myFunc", &[], false, false);
assert!(!mangled.is_empty());
}
#[test]
fn test_name_mangling_mangle_ctor() {
let mut nm = X86NameMangling::default();
let mangled = nm.mangle("MyClass", &[], true, false);
assert!(!mangled.is_empty());
}
#[test]
fn test_name_mangling_mangle_vtable() {
let mut nm = X86NameMangling::default();
let mangled = nm.mangle_vtable("MyClass");
assert!(mangled.starts_with("_ZTV"));
}
#[test]
fn test_name_mangling_mangle_typeinfo() {
let mut nm = X86NameMangling::default();
let mangled = nm.mangle_typeinfo("int");
assert!(mangled.starts_with("_ZTI"));
}
#[test]
fn test_name_mangling_mangle_guard() {
let mut nm = X86NameMangling::default();
let mangled = nm.mangle_guard("myVar");
assert!(mangled.starts_with("_ZGV"));
}
#[test]
fn test_name_mangling_mangle_thunk() {
let mut nm = X86NameMangling::default();
let mangled = nm.mangle_thunk("_ZN3Foo3barEv", 8, 0);
assert!(!mangled.is_empty());
}
#[test]
fn test_name_mangling_add_abi_tag() {
let nm = X86NameMangling::default();
let tagged = nm.add_abi_tag("_Z3foov", "cxx11");
assert!(tagged.contains("cxx11"));
}
#[test]
fn test_name_mangling_msvc_placeholder() {
let nm = X86NameMangling::new(
Mangler::default(),
true,
"x86_64-pc-windows-msvc".to_string(),
);
assert_eq!(nm.scheme, X86ManglingScheme::Microsoft);
}
#[test]
fn test_template_inst_default() {
let ti = X86TemplateInstantiation::default();
assert!(ti.is_64bit);
assert!(ti.use_linkonce_odr);
}
#[test]
fn test_template_inst_register() {
let mut ti = X86TemplateInstantiation::default();
ti.register(
"myTemplate",
&["int".to_string()],
false,
false,
"_Z10myTemplateIiEvv",
);
assert_eq!(ti.instantiations.len(), 1);
}
#[test]
fn test_template_inst_instantiate() {
let mut ti = X86TemplateInstantiation::default();
let ir = ti.instantiate("myTemplate", &["int".to_string()]).unwrap();
assert!(ir.contains("myTemplate<int>"));
assert!(ir.contains("linkonce_odr"));
}
#[test]
fn test_template_inst_dedup() {
let mut ti = X86TemplateInstantiation::default();
let _ = ti.instantiate("T", &["int".to_string()]);
let ir2 = ti.instantiate("T", &["int".to_string()]).unwrap();
assert!(ir2.contains("already instantiated"));
}
#[test]
fn test_template_inst_explicit_decl() {
let ti = X86TemplateInstantiation::default();
let decl = ti.gen_explicit_instantiation_decl("T", &["int".to_string()]);
assert!(decl.contains("declare"));
}
#[test]
fn test_template_inst_extern_decl() {
let ti = X86TemplateInstantiation::default();
let decl = ti.gen_extern_template_decl("T", &["int".to_string()]);
assert!(decl.contains("declare external"));
}
#[test]
fn test_template_inst_comdat_fold() {
let ti = X86TemplateInstantiation::default();
let ir = ti.gen_comdat_fold("_Z3foov", " ret void");
assert!(ir.contains("comdat"));
}
#[test]
fn test_odr_merger() {
let mut merger = X86TemplateODRMerger::default();
assert!(!merger.is_duplicate("foo"));
merger.register("foo");
assert!(merger.is_duplicate("foo"));
}
#[test]
fn test_cxx_modules_default() {
let modules = X86CXXModules::default();
assert!(modules.is_64bit);
assert!(modules.current_module.is_none());
}
#[test]
fn test_modules_register() {
let mut modules = X86CXXModules::default();
modules.register("MyModule", true, false, None);
assert_eq!(modules.modules.len(), 1);
assert_eq!(modules.module_map.len(), 1);
}
#[test]
fn test_modules_add_import() {
let mut modules = X86CXXModules::default();
modules.register("A", true, false, None);
modules.register("B", false, false, None);
modules.add_import("A", "B");
assert_eq!(modules.modules[0].imports.len(), 1);
}
#[test]
fn test_modules_process() {
let mut modules = X86CXXModules::default();
modules.register("MyModule", true, false, None);
let result = modules.process("MyModule", "int x = 1;");
assert!(result.is_ok());
let ir = result.unwrap();
assert!(ir.contains("C++20 Module: MyModule"));
assert!(ir.contains("Module Name"));
}
#[test]
fn test_modules_process_not_found() {
let mut modules = X86CXXModules::default();
let result = modules.process("NoModule", "");
assert!(result.is_err());
}
#[test]
fn test_modules_gen_init() {
let modules = X86CXXModules::default();
let ir = modules.gen_module_init("TestMod");
assert!(ir.contains("Module initializer for TestMod"));
}
#[test]
fn test_modules_ownership_check() {
let modules = X86CXXModules::default();
let ir = modules.gen_module_ownership_check("MyClass", "MyModule");
assert!(ir.contains("MyClass"));
assert!(ir.contains("MyModule"));
}
#[test]
fn test_modules_global_fragment() {
let modules = X86CXXModules::default();
let gf = modules.gen_global_module_fragment();
assert!(gf.contains("module;"));
}
#[test]
fn test_modules_private_fragment() {
let modules = X86CXXModules::default();
let pf = modules.gen_private_module_fragment();
assert!(pf.contains("module :private"));
}
#[test]
fn test_modules_set_current() {
let mut modules = X86CXXModules::default();
assert!(modules.current_module_name().is_none());
modules.set_current_module(Some("TestMod"));
assert_eq!(modules.current_module_name(), Some("TestMod"));
modules.set_current_module(None);
assert!(modules.current_module_name().is_none());
}
#[test]
fn test_member_ptr_rep_sizes() {
assert_eq!(X86MemberPtrRep::Single4.size_bytes(), 4);
assert_eq!(X86MemberPtrRep::Single8.size_bytes(), 8);
assert_eq!(X86MemberPtrRep::Pair8.size_bytes(), 8);
assert_eq!(X86MemberPtrRep::Pair16.size_bytes(), 16);
}
#[test]
fn test_member_ptr_rep_is_pair() {
assert!(!X86MemberPtrRep::Single4.is_pair());
assert!(!X86MemberPtrRep::Single8.is_pair());
assert!(X86MemberPtrRep::Pair8.is_pair());
assert!(X86MemberPtrRep::Pair16.is_pair());
}
#[test]
fn test_cxx_abi_as_str() {
assert_eq!(X86CXXABI::Itanium.as_str(), "itanium");
assert_eq!(X86CXXABI::Microsoft.as_str(), "microsoft");
assert_eq!(X86CXXABI::iOS.as_str(), "ios");
}
#[test]
fn test_capture_kind() {
assert_eq!(X86CaptureKind::ByValue as u8, 0);
assert_eq!(X86CaptureKind::ByRef as u8, 1);
}
#[test]
fn test_compilation_result_default_fields() {
let result = X86CompilationResult {
ir: String::new(),
vtables: String::new(),
rtti: String::new(),
eh_tables: String::new(),
guard_variables: String::new(),
typeinfo: String::new(),
triple: "x86_64".to_string(),
};
assert_eq!(result.ir_size(), 0);
}
#[test]
fn test_x86_guard_emission_32bit() {
let guard = X86GuardEmission::new(false, "i386-linux".to_string());
assert!(!guard.is_64bit);
}
#[test]
fn test_vtable_emitter_ptr_size_32bit() {
let emitter = X86VTableEmitter::new(Mangler::new(), false, "i386-pc-linux-gnu".to_string());
assert_eq!(emitter.ptr_size(), 4);
assert_eq!(emitter.ptr_align(), 4);
}
#[test]
fn test_vtable_empty_entries() {
let mut emitter = X86VTableEmitter::default();
let layout = VtableLayout::new("EmptyClass", MangledName::new("_ZTV10EmptyClass".into()));
emitter.add_vtable("EmptyClass", layout);
let ir = emitter.emit_all();
assert!(ir.contains("EmptyClass"));
}
#[test]
fn test_rtti_set_vtable() {
let mut emitter = X86RTTIEmitter::default();
emitter.set_typeinfo_vtable("__ZTVN10__cxxabiv117__class_type_infoE");
assert_eq!(
emitter.typeinfo_vtable,
Some("__ZTVN10__cxxabiv117__class_type_infoE".to_string())
);
}
#[test]
fn test_rtti_add_base_info() {
let mut emitter = X86RTTIEmitter::default();
emitter.add_base_class_info("Derived", "_ZTI4Base", 0, 0);
emitter.add_base_class_info("Derived", "_ZTI4Base2", 0, 16);
assert_eq!(emitter.base_infos.get("Derived").unwrap().len(), 2);
}
#[test]
fn test_eh_itanium_personality_flag() {
let base = EHCodeGen::itanium();
let eh = X86EHCodeGen::new_itanium(base, true);
assert!(eh.use_dwarf_eh);
assert!(!eh.use_seh);
}
#[test]
fn test_eh_seh_personality_flag() {
let base = EHCodeGen::new(PersonalityKind::Seh);
let eh = X86EHCodeGen::new_seh(base, false);
assert!(!eh.use_dwarf_eh);
assert!(eh.use_seh);
}
#[test]
fn test_eh_end_function_resets() {
let base = EHCodeGen::itanium();
let mut eh = X86EHCodeGen::new_itanium(base, true);
eh.add_call_site(0, 10, 15, 1);
eh.add_type("_ZTIi");
assert_eq!(eh.call_sites.len(), 1);
assert_eq!(eh.type_table.len(), 1);
let _lsda = eh.end_function("test");
assert_eq!(eh.call_sites.len(), 0);
assert_eq!(eh.type_table.len(), 0);
}
#[test]
fn test_guard_emission_32bit_size() {
let mut guard = X86GuardEmission::new(false, "i386-linux".to_string());
let mangler = Mangler::new();
let gv = guard.create_guard(&mangler, "x");
assert_eq!(gv.size, 4);
}
#[test]
fn test_typeinfo_emitter_32bit_check() {
let emitter = X86TypeInfoEmitter::new(Mangler::new(), false, "i386-linux".to_string());
assert!(!emitter.is_64bit);
}
#[test]
fn test_typeinfo_register_vtable() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.register("MyClass", X86RTTIKind::Class, Some("_ZTV7MyClass"), None);
assert_eq!(
emitter.records[0].vtable_sym,
Some("_ZTV7MyClass".to_string())
);
}
#[test]
fn test_typeinfo_register_base() {
let mut emitter = X86TypeInfoEmitter::default();
emitter.register("int*", X86RTTIKind::Pointer, None, Some("_ZTIi"));
assert_eq!(emitter.records[0].base_typeinfo, Some("_ZTIi".to_string()));
}
#[test]
fn test_ctor_allocating() {
let mut lowering = X86CtorDtorLowering::default();
lowering.register_ctor(
"MyClass",
X86CtorVariant::Allocating,
"_ZN7MyClassC3Ev",
&[],
false,
);
let ir = lowering
.lower_constructor("MyClass", X86CtorVariant::Allocating)
.unwrap();
assert!(ir.contains("_Znwm"));
}
#[test]
fn test_dtor_deleting() {
let mut lowering = X86CtorDtorLowering::default();
lowering.register_dtor(
"MyClass",
X86DtorVariant::Deleting,
"_ZN7MyClassD0Ev",
false,
);
let ir = lowering
.lower_destructor("MyClass", X86DtorVariant::Deleting)
.unwrap();
assert!(ir.contains("_ZdlPv"));
}
#[test]
fn test_vptr_init_gen() {
let lowering = X86CtorDtorLowering::default();
let ir = lowering.gen_vptr_init("MyClass", "%this");
assert!(ir.contains("@_ZTVMyClass"));
}
#[test]
fn test_virtual_dispatch_multi_register() {
let mut dispatch = X86VirtualDispatch::default();
dispatch.register("A", "f1", 0, 0, "void", &[]);
dispatch.register("A", "f2", 1, 0, "i32", &[]);
dispatch.register("B", "g", 0, 8, "void", &[]);
assert_eq!(dispatch.dispatches.len(), 3);
}
#[test]
fn test_virtual_dispatch_large_offset() {
let dispatch = X86VirtualDispatch::default();
assert_eq!(dispatch.vtable_offset(100), 102);
assert_eq!(dispatch.vtable_offset(0), 2);
assert_eq!(dispatch.vtable_offset(1), 3);
}
#[test]
fn test_member_ptr_func_virtual_register() {
let mut mp = X86MemberPointer::default();
mp.register_func_member("Base", "vfunc", 0, 0, true, Some(3));
assert_eq!(mp.func_member_ptrs[0].vtable_index, Some(3));
assert!(mp.func_member_ptrs[0].is_virtual);
}
#[test]
fn test_memptr_to_bool_ir() {
let mp = X86MemberPointer::default();
let ir = mp.gen_memptr_to_bool();
assert!(ir.contains("extractvalue"));
assert!(ir.contains("icmp ne"));
}
#[test]
fn test_lambda_multi_captures() {
let mut lowering = X86LambdaLowering::default();
let captures = vec![
LambdaCapture::Capture {
name: "x".into(),
by_ref: false,
capture_default: None,
init: None,
},
LambdaCapture::Capture {
name: "y".into(),
by_ref: true,
capture_default: None,
init: None,
},
];
let result = lowering.lower(&captures, "x + y");
assert!(result.is_ok());
}
#[test]
fn test_lambda_this_cap() {
let mut lowering = X86LambdaLowering::default();
let captures = vec![LambdaCapture::This];
let result = lowering.lower(&captures, "this->x");
assert!(result.is_ok());
}
#[test]
fn test_new_delete_32bit_new_op() {
let mut nd = X86NewDelete::new(false, "i386-linux".to_string());
let ir = nd.gen_new(4, 4, false).unwrap();
assert!(ir.contains("_Znwj"));
}
#[test]
fn test_new_delete_32bit_delete_op() {
let mut nd = X86NewDelete::new(false, "i386-linux".to_string());
let ir = nd.gen_delete("%ptr", 8).unwrap();
assert!(ir.contains("_ZdjPv"));
}
#[test]
fn test_array_ctor_dtor_both_register() {
let mut act = X86ArrayCtorDtor::default();
act.register_construction("MyClass", 5, "%arr", true);
act.register_destruction("MyClass", 5, "%arr", true);
assert_eq!(act.constructions.len(), 1);
assert_eq!(act.destructions.len(), 1);
}
#[test]
fn test_dynamic_cast_downcast_offset() {
let mut dc = X86DynamicCast::default();
dc.register(
"Base",
"Derived",
"_ZTI4Base",
"_ZTI7Derived",
"%obj",
X86DynCastKind::Downcast,
16,
);
let ir = dc.gen_cast("%obj", "Base", "Derived").unwrap();
assert!(ir.contains("__dynamic_cast"));
}
#[test]
fn test_dynamic_cast_cross() {
let mut dc = X86DynamicCast::default();
dc.register(
"A",
"B",
"_ZTI1A",
"_ZTI1B",
"%obj",
X86DynCastKind::CrossCast,
-1,
);
let ir = dc.gen_cast("%obj", "A", "B").unwrap();
assert!(ir.contains("__dynamic_cast"));
}
#[test]
fn test_typeid_dynamic_poly() {
let mut tid = X86TypeID::default();
tid.register("MyClass*", true, "_ZTI7MyClass");
let ir = tid.gen_typeid("MyClass*").unwrap();
assert!(ir.contains("vptr"));
}
#[test]
fn test_static_init_local_var() {
let mut si = X86StaticInit::default();
si.register("count", "_ZGVZ4funce1c", "0", true);
let ir = si.gen_guard("count").unwrap();
assert!(ir.contains("__cxa_guard_acquire"));
}
#[test]
fn test_static_init_thread_safe_check() {
let mut si = X86StaticInit::new(true, "x86_64-linux".to_string());
assert!(si.thread_safe);
si.register("x", "_ZGVZ1xvE", "42", true);
let ir = si.gen_guard("x").unwrap();
assert!(ir.contains("acquire"));
}
#[test]
fn test_thread_local_model_initial_exec() {
let mut tli = X86ThreadLocalInit::default();
tli.register("v", "i32", "_ZTW1v", true, X86TLSModel::InitialExec);
let ir = tli.gen_init("v").unwrap();
assert!(ir.contains("thread_local"));
}
#[test]
fn test_thread_local_windows_tls_model() {
let tli = X86ThreadLocalInit::new(true, "x86_64-pc-windows-msvc".to_string());
assert!(!tli.use_elf_tls);
}
#[test]
fn test_abi_support_ios_triple() {
let abi = X86CXXABISupport::new(
true,
"x86_64-apple-ios".to_string(),
X86Subtarget::new("x86_64-apple-ios", "generic", ""),
);
assert_eq!(abi.abi, X86CXXABI::Itanium);
}
#[test]
fn test_abi_offset_to_top_msvc() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.offset_to_top_index(), 0);
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert_eq!(msvc.offset_to_top_index(), -1);
}
#[test]
fn test_abi_prologue_epilogue_gen() {
let abi = X86CXXABISupport::default();
let prologue = abi.gen_function_prologue_abi(true);
assert!(prologue.contains("vptr"));
let epilogue = abi.gen_function_epilogue_abi(true);
assert!(epilogue.contains("epilogue"));
}
#[test]
fn test_abi_member_ptr_msvc() {
let msvc = X86CXXABISupport::new(
true,
"x86_64-pc-windows-msvc".into(),
X86Subtarget::new("x86_64-pc-windows-msvc", "generic", ""),
);
assert_eq!(msvc.member_ptr_rep(), X86MemberPtrRep::Single8);
}
#[test]
fn test_abi_cookie_size_32bit() {
let abi = X86CXXABISupport::default();
assert_eq!(abi.array_cookie_size(), 8);
let abi32 = X86CXXABISupport::new(
false,
"i386-linux".into(),
X86Subtarget::new("i386-linux", "generic", ""),
);
assert_eq!(abi32.array_cookie_size(), 4);
}
#[test]
fn test_name_mangling_cache() {
let mut nm = X86NameMangling::default();
let m1 = nm.mangle("foo", &[], false, false);
let m2 = nm.mangle("foo", &[], false, false);
assert_eq!(m1, m2);
}
#[test]
fn test_name_mangling_ctor_dtor_diff() {
let mut nm = X86NameMangling::default();
let ctor = nm.mangle("Foo", &[], true, false);
let dtor = nm.mangle("Foo", &[], false, true);
assert_ne!(ctor, dtor);
}
#[test]
fn test_name_mangling_msvc_vt() {
let mut nm =
X86NameMangling::new(Mangler::new(), true, "x86_64-pc-windows-msvc".to_string());
let vt = nm.mangle_vtable("Foo");
assert!(vt.starts_with("??_7"));
}
#[test]
fn test_template_inst_multi_args() {
let mut ti = X86TemplateInstantiation::default();
let ir = ti
.instantiate("Pair", &["int".to_string(), "float".to_string()])
.unwrap();
assert!(ir.contains("Pair<int, float>"));
}
#[test]
fn test_template_extern_vs_explicit() {
let ti = X86TemplateInstantiation::default();
let ed = ti.gen_extern_template_decl("T", &["int".to_string()]);
let xd = ti.gen_explicit_instantiation_decl("T", &["int".to_string()]);
assert!(ed.contains("external"));
assert!(!xd.contains("external"));
}
#[test]
fn test_modules_partition_decl() {
let mut modules = X86CXXModules::default();
modules.register("MyModule:Part", true, true, Some("MyModule"));
assert_eq!(
modules.modules[0].parent_module,
Some("MyModule".to_string())
);
assert!(modules.modules[0].is_partition);
}
#[test]
fn test_modules_import_chain_test() {
let mut modules = X86CXXModules::default();
modules.register("A", true, false, None);
modules.register("B", false, false, None);
modules.register("C", false, false, None);
modules.add_import("A", "B");
modules.add_import("A", "C");
assert_eq!(modules.modules[0].imports.len(), 2);
}
#[test]
fn test_modules_compiled() {
let mut modules = X86CXXModules::default();
modules.register("TestMod", true, false, None);
assert!(!modules.module_map.get("TestMod").unwrap().compiled);
let _ = modules.process("TestMod", "int x = 42;");
assert!(modules.module_map.get("TestMod").unwrap().compiled);
}
pub struct X86ABITagSupport {
pub tags: Vec<String>,
pub use_cxx11_tag: bool,
pub use_cxx14_tag: bool,
pub use_cxx17_tag: bool,
}
impl X86ABITagSupport {
pub fn new() -> Self {
Self {
tags: Vec::new(),
use_cxx11_tag: true,
use_cxx14_tag: false,
use_cxx17_tag: false,
}
}
pub fn add_tag(&mut self, tag: &str) {
if !self.tags.contains(&tag.to_string()) {
self.tags.push(tag.to_string());
}
}
pub fn has_tag(&self, tag: &str) -> bool {
self.tags.contains(&tag.to_string())
}
pub fn encode_tags(&self) -> String {
let mut result = String::new();
for tag in &self.tags {
result.push_str(&format!("B{}", tag.len()));
result.push_str(tag);
}
result
}
pub fn needs_abi_tag(&self) -> bool {
!self.tags.is_empty()
}
}
impl Default for X86ABITagSupport {
fn default() -> Self {
Self::new()
}
}
pub struct X86InlineAsmSupport {
pub constraints: Vec<String>,
pub clobbers: Vec<String>,
pub intel_syntax: bool,
}
impl X86InlineAsmSupport {
pub fn new() -> Self {
Self {
constraints: vec![
"r".into(),
"m".into(),
"i".into(),
"g".into(),
"a".into(),
"b".into(),
"c".into(),
"d".into(),
"S".into(),
"D".into(),
"q".into(),
"Q".into(),
],
clobbers: vec![
"ax".into(),
"bx".into(),
"cx".into(),
"dx".into(),
"si".into(),
"di".into(),
"bp".into(),
"sp".into(),
"r8".into(),
"r9".into(),
"r10".into(),
"r11".into(),
"memory".into(),
"cc".into(),
"flags".into(),
],
intel_syntax: false,
}
}
pub fn gen_asm_directive(&self, asm_str: &str) -> String {
let syntax = if self.intel_syntax {
"inteldialect"
} else {
"attdialect"
};
format!(
"call void asm sideeffect \"{}\", \"~{{dirflag}},~{{fpsr}},~{{flags}}\"()\n",
asm_str,
)
}
pub fn gen_msvc_asm(&self, asm_str: &str) -> String {
format!(" ; MSVC __asm {{\n ; {}\n ; }}\n", asm_str,)
}
}
impl Default for X86InlineAsmSupport {
fn default() -> Self {
Self::new()
}
}
pub struct X86ClassLayout {
pub class_name: String,
pub field_offsets: HashMap<String, u32>,
pub base_offsets: HashMap<String, u32>,
pub vbase_offsets: HashMap<String, u32>,
pub total_size: u32,
pub alignment: u32,
pub has_vptr: bool,
pub has_virtual_bases: bool,
pub vtable_alignment: u32,
}
impl X86ClassLayout {
pub fn new(class_name: &str, is_64bit: bool) -> Self {
Self {
class_name: class_name.to_string(),
field_offsets: HashMap::new(),
base_offsets: HashMap::new(),
vbase_offsets: HashMap::new(),
total_size: 0,
alignment: if is_64bit { 8 } else { 4 },
has_vptr: false,
has_virtual_bases: false,
vtable_alignment: if is_64bit { 8 } else { 4 },
}
}
pub fn add_field(&mut self, name: &str, size: u32, align: u32) -> u32 {
let align_mask = align - 1;
let aligned = (self.total_size + align_mask) & !align_mask;
self.field_offsets.insert(name.to_string(), aligned);
self.total_size = aligned + size;
self.alignment = self.alignment.max(align);
aligned
}
pub fn add_base(&mut self, base_name: &str, base_offset: u32) {
self.base_offsets.insert(base_name.to_string(), base_offset);
}
pub fn add_virtual_base(&mut self, vbase_name: &str, offset: u32) {
self.has_virtual_bases = true;
self.vbase_offsets.insert(vbase_name.to_string(), offset);
}
pub fn set_has_vptr(&mut self) {
self.has_vptr = true;
}
pub fn finalize_size(&mut self) {
let align_mask = self.alignment - 1;
self.total_size = (self.total_size + align_mask) & !align_mask;
}
pub fn get_field_offset(&self, name: &str) -> Option<u32> {
self.field_offsets.get(name).copied()
}
pub fn get_base_offset(&self, name: &str) -> Option<u32> {
self.base_offsets.get(name).copied()
}
pub fn vtable_ptr_offset(&self) -> u32 {
if self.has_vptr {
0
} else {
0
}
}
}
impl Default for X86ClassLayout {
fn default() -> Self {
Self::new("Default", true)
}
}
pub struct X86DllExportSupport {
pub exported: Vec<String>,
pub imported: Vec<String>,
pub building_dll: bool,
pub using_dll: bool,
pub dll_storage_class: X86DllStorageClass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86DllStorageClass {
DllExport,
DllImport,
Default,
}
impl X86DllExportSupport {
pub fn new() -> Self {
Self {
exported: Vec::new(),
imported: Vec::new(),
building_dll: false,
using_dll: false,
dll_storage_class: X86DllStorageClass::Default,
}
}
pub fn mark_export(&mut self, symbol: &str) {
if !self.exported.contains(&symbol.to_string()) {
self.exported.push(symbol.to_string());
}
}
pub fn mark_import(&mut self, symbol: &str) {
if !self.imported.contains(&symbol.to_string()) {
self.imported.push(symbol.to_string());
}
}
pub fn is_exported(&self, symbol: &str) -> bool {
self.exported.contains(&symbol.to_string())
}
pub fn is_imported(&self, symbol: &str) -> bool {
self.imported.contains(&symbol.to_string())
}
pub fn gen_dllimport_decl(&self, symbol: &str, ty: &str) -> String {
format!("@{} = external dllimport global {}\n", symbol, ty,)
}
pub fn gen_dllexport_decl(&self, symbol: &str, ty: &str) -> String {
format!("@{} = dllexport global {}\n", symbol, ty,)
}
pub fn gen_dllimport_func(&self, symbol: &str, sig: &str) -> String {
format!("declare dllimport {} @{}(...)\n", sig, symbol,)
}
pub fn gen_dllexport_func(&self, symbol: &str, sig: &str) -> String {
format!(
"define dllexport {} @{}(...) {{\n ret {} undef\n}}\n",
sig, symbol, sig,
)
}
}
impl Default for X86DllExportSupport {
fn default() -> Self {
Self::new()
}
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_abi_tag_add() {
let mut tags = X86ABITagSupport::new();
tags.add_tag("cxx11");
assert!(tags.has_tag("cxx11"));
assert!(!tags.has_tag("cxx14"));
}
#[test]
fn test_abi_tag_duplicate() {
let mut tags = X86ABITagSupport::new();
tags.add_tag("cxx11");
tags.add_tag("cxx11");
assert_eq!(tags.tags.len(), 1);
}
#[test]
fn test_abi_tag_encode() {
let mut tags = X86ABITagSupport::new();
tags.add_tag("cxx11");
let encoded = tags.encode_tags();
assert!(encoded.contains("B5cxx11"));
}
#[test]
fn test_abi_tag_needs_tag() {
let mut tags = X86ABITagSupport::new();
assert!(!tags.needs_abi_tag());
tags.add_tag("cxx11");
assert!(tags.needs_abi_tag());
}
#[test]
fn test_inline_asm_directive() {
let asm = X86InlineAsmSupport::new();
let ir = asm.gen_asm_directive("nop");
assert!(ir.contains("asm sideeffect"));
}
#[test]
fn test_inline_asm_intel_syntax() {
let mut asm = X86InlineAsmSupport::new();
asm.intel_syntax = true;
let ir = asm.gen_asm_directive("nop");
assert!(ir.contains("inteldialect"));
}
#[test]
fn test_inline_asm_msvc() {
let asm = X86InlineAsmSupport::new();
let ir = asm.gen_msvc_asm("mov eax, 1");
assert!(ir.contains("MSVC __asm"));
}
#[test]
fn test_class_layout_add_field() {
let mut layout = X86ClassLayout::new("Test", true);
let offset = layout.add_field("x", 4, 4);
assert_eq!(offset, 0);
assert_eq!(layout.total_size, 4);
}
#[test]
fn test_class_layout_alignment() {
let mut layout = X86ClassLayout::new("Test", true);
let offset = layout.add_field("x", 1, 1);
let offset2 = layout.add_field("y", 4, 4);
assert_eq!(offset, 0);
assert_eq!(offset2, 4); }
#[test]
fn test_class_layout_vptr() {
let mut layout = X86ClassLayout::new("Test", true);
layout.set_has_vptr();
assert!(layout.has_vptr);
assert_eq!(layout.vtable_ptr_offset(), 0);
}
#[test]
fn test_class_layout_virtual_base() {
let mut layout = X86ClassLayout::new("Test", true);
layout.add_virtual_base("VBase", 16);
assert!(layout.has_virtual_bases);
assert_eq!(layout.vbase_offsets.get("VBase"), Some(&16));
}
#[test]
fn test_class_layout_finalize() {
let mut layout = X86ClassLayout::new("Test", true);
layout.add_field("a", 1, 1);
layout.add_field("b", 4, 4);
layout.finalize_size();
assert_eq!(layout.total_size % layout.alignment, 0);
}
#[test]
fn test_class_layout_get_offsets() {
let mut layout = X86ClassLayout::new("Test", true);
layout.add_field("x", 8, 8);
layout.add_base("Base", 0);
assert_eq!(layout.get_field_offset("x"), Some(0));
assert_eq!(layout.get_base_offset("Base"), Some(0));
assert_eq!(layout.get_field_offset("nonexistent"), None);
}
#[test]
fn test_dll_mark_export() {
let mut dll = X86DllExportSupport::new();
dll.mark_export("?foo@@YAXXZ");
assert!(dll.is_exported("?foo@@YAXXZ"));
assert!(!dll.is_imported("?foo@@YAXXZ"));
}
#[test]
fn test_dll_mark_import() {
let mut dll = X86DllExportSupport::new();
dll.mark_import("?bar@@YAXXZ");
assert!(dll.is_imported("?bar@@YAXXZ"));
assert!(!dll.is_exported("?bar@@YAXXZ"));
}
#[test]
fn test_dll_dllexport_decl() {
let dll = X86DllExportSupport::new();
let ir = dll.gen_dllexport_decl("?x@@3HA", "i32 42");
assert!(ir.contains("dllexport"));
}
#[test]
fn test_dll_dllimport_decl() {
let dll = X86DllExportSupport::new();
let ir = dll.gen_dllimport_decl("?x@@3HA", "i32");
assert!(ir.contains("dllimport"));
assert!(ir.contains("external"));
}
#[test]
fn test_dll_func_export() {
let dll = X86DllExportSupport::new();
let ir = dll.gen_dllexport_func("?f@@YAXXZ", "void");
assert!(ir.contains("dllexport"));
}
#[test]
fn test_dll_func_import() {
let dll = X86DllExportSupport::new();
let ir = dll.gen_dllimport_func("?f@@YAXXZ", "void");
assert!(ir.contains("dllimport"));
}
#[test]
fn test_dll_storage_class() {
assert_eq!(X86DllStorageClass::DllExport as u8, 0);
assert_eq!(X86DllStorageClass::DllImport as u8, 1);
assert_eq!(X86DllStorageClass::Default as u8, 2);
}
pub struct X86SanitizerIntegration {
pub asan_enabled: bool,
pub ubsan_enabled: bool,
pub tsan_enabled: bool,
pub stack_protector: bool,
pub cfi_enabled: bool,
pub safestack: bool,
}
impl X86SanitizerIntegration {
pub fn new() -> Self {
Self {
asan_enabled: false,
ubsan_enabled: false,
tsan_enabled: false,
stack_protector: false,
cfi_enabled: false,
safestack: false,
}
}
pub fn enable_asan(&mut self) {
self.asan_enabled = true;
}
pub fn enable_ubsan(&mut self) {
self.ubsan_enabled = true;
}
pub fn enable_tsan(&mut self) {
self.tsan_enabled = true;
}
pub fn enable_stack_protector(&mut self) {
self.stack_protector = true;
}
pub fn gen_asan_load_hook(&self, ptr: &str, size: u32) -> String {
if !self.asan_enabled {
return String::new();
}
format!(
" call void @__asan_load{}(i8* {})\n",
if size == 1 {
"1"
} else if size == 2 {
"2"
} else if size == 4 {
"4"
} else if size == 8 {
"8"
} else {
"N"
},
ptr,
)
}
pub fn gen_asan_store_hook(&self, ptr: &str, size: u32) -> String {
if !self.asan_enabled {
return String::new();
}
format!(
" call void @__asan_store{}(i8* {})\n",
if size == 1 {
"1"
} else if size == 2 {
"2"
} else if size == 4 {
"4"
} else if size == 8 {
"8"
} else {
"N"
},
ptr,
)
}
pub fn gen_ubsan_divrem_overflow(&self, lhs: &str, rhs: &str, ty: &str) -> String {
if !self.ubsan_enabled {
return String::new();
}
format!(
" call void @__ubsan_handle_divrem_overflow(i8* null, {} {}, {} {})\n",
ty, lhs, ty, rhs,
)
}
pub fn gen_stack_canary(&self) -> String {
if !self.stack_protector {
return String::new();
}
" %stack_guard = load i64, i64* @__stack_chk_guard\n\
\x20 store i64 %stack_guard, i64* %stack_guard_slot\n"
.to_string()
}
pub fn gen_cfi_check(&self, func_ptr: &str) -> String {
if !self.cfi_enabled {
return String::new();
}
format!(" call void @__cfi_check(i8* {})\n", func_ptr,)
}
pub fn gen_runtime_decls(&self) -> String {
let mut ir = String::new();
if self.asan_enabled {
ir.push_str("declare void @__asan_load1(i8*)\n");
ir.push_str("declare void @__asan_load2(i8*)\n");
ir.push_str("declare void @__asan_load4(i8*)\n");
ir.push_str("declare void @__asan_load8(i8*)\n");
ir.push_str("declare void @__asan_store1(i8*)\n");
ir.push_str("declare void @__asan_store2(i8*)\n");
ir.push_str("declare void @__asan_store4(i8*)\n");
ir.push_str("declare void @__asan_store8(i8*)\n");
}
if self.ubsan_enabled {
ir.push_str("declare void @__ubsan_handle_divrem_overflow(i8*, i64, i64)\n");
ir.push_str("declare void @__ubsan_handle_type_mismatch(i8*, i8*)\n");
ir.push_str("declare void @__ubsan_handle_load_invalid_value(i8*, i8*)\n");
}
if self.stack_protector {
ir.push_str("declare i64 @__stack_chk_guard_read()\n");
}
ir
}
}
impl Default for X86SanitizerIntegration {
fn default() -> Self {
Self::new()
}
}
}
#[cfg(test)]
mod sanitizer_tests {
use super::*;
#[test]
fn test_sanitizer_default_off() {
let san = X86SanitizerIntegration::new();
assert!(!san.asan_enabled);
assert!(!san.ubsan_enabled);
assert!(!san.tsan_enabled);
}
#[test]
fn test_sanitizer_enable_asan() {
let mut san = X86SanitizerIntegration::new();
san.enable_asan();
assert!(san.asan_enabled);
}
#[test]
fn test_sanitizer_asan_load_hook() {
let mut san = X86SanitizerIntegration::new();
san.enable_asan();
let ir = san.gen_asan_load_hook("%ptr", 4);
assert!(ir.contains("__asan_load4"));
}
#[test]
fn test_sanitizer_asan_load_hook_disabled() {
let san = X86SanitizerIntegration::new();
let ir = san.gen_asan_load_hook("%ptr", 4);
assert!(ir.is_empty());
}
#[test]
fn test_sanitizer_asan_store_hook() {
let mut san = X86SanitizerIntegration::new();
san.enable_asan();
let ir = san.gen_asan_store_hook("%ptr", 8);
assert!(ir.contains("__asan_store8"));
}
#[test]
fn test_sanitizer_ubsan_divrem() {
let mut san = X86SanitizerIntegration::new();
san.enable_ubsan();
let ir = san.gen_ubsan_divrem_overflow("%a", "%b", "i32");
assert!(ir.contains("__ubsan_handle_divrem_overflow"));
}
#[test]
fn test_sanitizer_ubsan_disabled() {
let san = X86SanitizerIntegration::new();
let ir = san.gen_ubsan_divrem_overflow("%a", "%b", "i32");
assert!(ir.is_empty());
}
#[test]
fn test_sanitizer_stack_canary() {
let mut san = X86SanitizerIntegration::new();
san.enable_stack_protector();
let ir = san.gen_stack_canary();
assert!(ir.contains("__stack_chk_guard"));
}
#[test]
fn test_sanitizer_stack_canary_disabled() {
let san = X86SanitizerIntegration::new();
let ir = san.gen_stack_canary();
assert!(ir.is_empty());
}
#[test]
fn test_sanitizer_cfi_check() {
let mut san = X86SanitizerIntegration::new();
san.cfi_enabled = true;
let ir = san.gen_cfi_check("%fptr");
assert!(ir.contains("__cfi_check"));
}
#[test]
fn test_sanitizer_cfi_disabled() {
let san = X86SanitizerIntegration::new();
let ir = san.gen_cfi_check("%fptr");
assert!(ir.is_empty());
}
#[test]
fn test_sanitizer_runtime_decls() {
let mut san = X86SanitizerIntegration::new();
san.enable_asan();
san.enable_ubsan();
san.enable_stack_protector();
let decls = san.gen_runtime_decls();
assert!(decls.contains("__asan_load4"));
assert!(decls.contains("__ubsan_handle_divrem_overflow"));
assert!(decls.contains("__stack_chk_guard_read"));
}
#[test]
fn test_sanitizer_runtime_decls_disabled() {
let san = X86SanitizerIntegration::new();
let decls = san.gen_runtime_decls();
assert!(decls.trim().is_empty());
}
pub struct X86CXXAtomicSupport {
pub use_lock_prefix: bool,
pub emit_fences: bool,
pub load_ordering: X86MemOrder,
pub store_ordering: X86MemOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MemOrder {
Relaxed,
Consume,
Acquire,
Release,
AcqRel,
SeqCst,
}
impl X86MemOrder {
pub fn as_llvm_str(&self) -> &'static str {
match self {
Self::Relaxed => "monotonic",
Self::Consume => "consume",
Self::Acquire => "acquire",
Self::Release => "release",
Self::AcqRel => "acq_rel",
Self::SeqCst => "seq_cst",
}
}
pub fn needs_mfence(&self) -> bool {
matches!(self, Self::SeqCst)
}
}
impl X86CXXAtomicSupport {
pub fn new() -> Self {
Self {
use_lock_prefix: true,
emit_fences: true,
load_ordering: X86MemOrder::SeqCst,
store_ordering: X86MemOrder::SeqCst,
}
}
pub fn gen_atomic_load(&self, ptr: &str, ty: &str, order: X86MemOrder) -> String {
format!(
" %val = load atomic {}, {}* {} {}, align {}\n",
ty,
ty,
ptr,
order.as_llvm_str(),
if ty == "i64" { 8 } else { 4 },
)
}
pub fn gen_atomic_store(
&self,
ptr: &str,
val: &str,
ty: &str,
order: X86MemOrder,
) -> String {
format!(
" store atomic {} {}, {}* {} {}, align {}\n",
ty,
val,
ty,
ptr,
order.as_llvm_str(),
if ty == "i64" { 8 } else { 4 },
)
}
pub fn gen_atomic_cmpxchg(
&self,
ptr: &str,
cmp: &str,
new_val: &str,
ty: &str,
succ: X86MemOrder,
fail: X86MemOrder,
) -> String {
format!(
" %pair = cmpxchg {}* {}, {} {}, {} {} {} {}\n",
ty,
ptr,
ty,
cmp,
ty,
new_val,
succ.as_llvm_str(),
fail.as_llvm_str(),
)
}
pub fn gen_atomic_rmw(
&self,
ptr: &str,
val: &str,
ty: &str,
op: &str,
order: X86MemOrder,
) -> String {
format!(
" %old = atomicrmw {} {}* {}, {} {} {}\n",
op,
ty,
ptr,
ty,
val,
order.as_llvm_str(),
)
}
pub fn gen_fence(&self, order: X86MemOrder) -> String {
if order.needs_mfence() {
" fence seq_cst\n ; MFENCE on x86\n".to_string()
} else {
format!(" fence {}\n", order.as_llvm_str())
}
}
pub fn gen_lock_prefix(&self) -> &'static str {
if self.use_lock_prefix {
"lock "
} else {
""
}
}
}
impl Default for X86CXXAtomicSupport {
fn default() -> Self {
Self::new()
}
}
}
#[cfg(test)]
mod atomic_tests {
use super::*;
#[test]
fn test_atomic_load_relaxed() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_atomic_load("%ptr", "i32", X86MemOrder::Relaxed);
assert!(ir.contains("monotonic"));
}
#[test]
fn test_atomic_load_seq_cst() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_atomic_load("%ptr", "i64", X86MemOrder::SeqCst);
assert!(ir.contains("seq_cst"));
assert!(ir.contains("i64"));
}
#[test]
fn test_atomic_store_release() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_atomic_store("%ptr", "%val", "i32", X86MemOrder::Release);
assert!(ir.contains("release"));
}
#[test]
fn test_atomic_cmpxchg() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_atomic_cmpxchg(
"%ptr",
"%cmp",
"%new",
"i64",
X86MemOrder::AcqRel,
X86MemOrder::Acquire,
);
assert!(ir.contains("cmpxchg"));
}
#[test]
fn test_atomic_rmw_xadd() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_atomic_rmw("%ptr", "%val", "i32", "add", X86MemOrder::SeqCst);
assert!(ir.contains("atomicrmw"));
assert!(ir.contains("add"));
}
#[test]
fn test_atomic_fence() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_fence(X86MemOrder::SeqCst);
assert!(ir.contains("MFENCE"));
}
#[test]
fn test_atomic_fence_acquire() {
let atom = X86CXXAtomicSupport::new();
let ir = atom.gen_fence(X86MemOrder::Acquire);
assert!(ir.contains("fence acquire"));
}
#[test]
fn test_mem_order_as_llvm() {
assert_eq!(X86MemOrder::Relaxed.as_llvm_str(), "monotonic");
assert_eq!(X86MemOrder::Acquire.as_llvm_str(), "acquire");
assert_eq!(X86MemOrder::SeqCst.as_llvm_str(), "seq_cst");
}
#[test]
fn test_mem_order_needs_mfence() {
assert!(!X86MemOrder::Relaxed.needs_mfence());
assert!(!X86MemOrder::Acquire.needs_mfence());
assert!(X86MemOrder::SeqCst.needs_mfence());
}
#[test]
fn test_lock_prefix_flag() {
let atom = X86CXXAtomicSupport::new();
assert_eq!(atom.gen_lock_prefix(), "lock ");
}
}