use std::collections::HashMap;
use super::cpp_ast::*;
use super::cpp_token::AccessSpecifier;
use super::name_mangling::{MangledName, Mangler};
use super::ast::QualType;
#[derive(Debug, Clone)]
pub enum VtableEntry {
OffsetToTop(i64),
RttiPtr(MangledName),
VFuncPtr {
mangled_name: MangledName,
this_adjustment: i64,
vcall_offset: Option<i64>,
},
VirtualBaseOffset(i64),
CompleteDtor(MangledName),
DeletingDtor(MangledName),
BaseObjectDtor(MangledName),
}
impl VtableEntry {
pub fn to_ir_constant(&self) -> String {
match self {
VtableEntry::OffsetToTop(offset) => format!("i64 {}", offset),
VtableEntry::RttiPtr(mangled) => format!("ptr @{}", mangled),
VtableEntry::VFuncPtr {
mangled_name,
this_adjustment,
..
} => {
if *this_adjustment != 0 {
format!("ptr @{} ;; adjusted by {}", mangled_name, this_adjustment)
} else {
format!("ptr @{}", mangled_name)
}
}
VtableEntry::VirtualBaseOffset(offset) => format!("i64 {}", offset),
VtableEntry::CompleteDtor(m) => format!("ptr @{} ; complete dtor", m),
VtableEntry::DeletingDtor(m) => format!("ptr @{} ; deleting dtor", m),
VtableEntry::BaseObjectDtor(m) => format!("ptr @{} ; base dtor", m),
}
}
pub fn kind_str(&self) -> &'static str {
match self {
VtableEntry::OffsetToTop(_) => "offset-to-top",
VtableEntry::RttiPtr(_) => "rtti",
VtableEntry::VFuncPtr { .. } => "vfunc",
VtableEntry::VirtualBaseOffset(_) => "vbase-offset",
VtableEntry::CompleteDtor(_) => "complete-dtor",
VtableEntry::DeletingDtor(_) => "deleting-dtor",
VtableEntry::BaseObjectDtor(_) => "base-dtor",
}
}
}
#[derive(Debug, Clone)]
pub struct VtableLayout {
pub class_name: String,
pub mangled_name: MangledName,
pub entries: Vec<VtableEntry>,
pub num_entries: usize,
pub construction_vtables: HashMap<String, VtableLayout>,
pub has_virtual_bases: bool,
pub offset_to_top: i64,
pub is_primary: bool,
pub is_construction_vtable: bool,
}
impl VtableLayout {
pub fn new(class_name: &str, mangled_name: MangledName) -> Self {
Self {
class_name: class_name.to_string(),
mangled_name,
entries: Vec::new(),
num_entries: 0,
construction_vtables: HashMap::new(),
has_virtual_bases: false,
offset_to_top: 0,
is_primary: true,
is_construction_vtable: false,
}
}
pub fn add_offset_to_top(&mut self, offset: i64) {
self.entries.push(VtableEntry::OffsetToTop(offset));
self.num_entries += 1;
}
pub fn add_rtti_ptr(&mut self, typeinfo: MangledName) {
self.entries.push(VtableEntry::RttiPtr(typeinfo));
self.num_entries += 1;
}
pub fn add_vfunc_ptr(
&mut self,
mangled_name: MangledName,
this_adjustment: i64,
vcall_offset: Option<i64>,
) {
self.entries.push(VtableEntry::VFuncPtr {
mangled_name,
this_adjustment,
vcall_offset,
});
self.num_entries += 1;
}
pub fn add_vbase_offset(&mut self, offset: i64) {
self.entries.push(VtableEntry::VirtualBaseOffset(offset));
self.num_entries += 1;
self.has_virtual_bases = true;
}
pub fn add_complete_dtor(&mut self, mangled: MangledName) {
self.entries.push(VtableEntry::CompleteDtor(mangled));
self.num_entries += 1;
}
pub fn add_deleting_dtor(&mut self, mangled: MangledName) {
self.entries.push(VtableEntry::DeletingDtor(mangled));
self.num_entries += 1;
}
pub fn add_base_object_dtor(&mut self, mangled: MangledName) {
self.entries.push(VtableEntry::BaseObjectDtor(mangled));
self.num_entries += 1;
}
pub fn to_ir_global(&self) -> String {
let linkage = if self.is_construction_vtable {
"internal"
} else {
"linkonce_odr"
};
let comdat = if !self.is_construction_vtable {
format!(", comdat($__vtable_comdat_{})", self.class_name)
} else {
String::new()
};
let mut ir = String::new();
ir.push_str(&format!(
"@{} = {} unnamed_addr constant [{} x ptr] [\n",
self.mangled_name, linkage, self.num_entries
));
let count = self.entries.len();
for (i, entry) in self.entries.iter().enumerate() {
let comma = if i < count - 1 { "," } else { "" };
ir.push_str(&format!(" {}{}\n", entry.to_ir_constant(), comma));
}
ir.push_str(&format!("], align 8{}\n", comdat));
ir
}
pub fn to_comdat_decl(&self) -> String {
if self.is_construction_vtable {
return String::new();
}
format!("$__vtable_comdat_{} = comdat any\n", self.class_name)
}
pub fn byte_size(&self) -> usize {
self.num_entries * 8
}
}
pub struct VtableBuilder {
mangler: Mangler,
layouts: HashMap<String, VtableLayout>,
secondary_vtables: HashMap<String, HashMap<String, VtableLayout>>,
vtt_entries: Vec<VTTEntry>,
}
#[derive(Debug, Clone)]
pub struct VTTEntry {
pub vtable_name: MangledName,
pub subobject_name: String,
}
impl VtableBuilder {
pub fn new() -> Self {
Self {
mangler: Mangler::new(),
layouts: HashMap::new(),
secondary_vtables: HashMap::new(),
vtt_entries: Vec::new(),
}
}
pub fn build_vtable(&mut self, record: &CXXDecl) -> Option<VtableLayout> {
match record {
CXXDecl::CXXRecord {
name,
bases,
members,
is_polymorphic,
..
} => {
if !is_polymorphic {
return None;
}
let vtable_name = self.mangler.mangle_vtable(name, None);
let mut layout = VtableLayout::new(name, vtable_name.clone());
layout.add_offset_to_top(0);
let rtti_name = self.mangler.mangle_typeinfo(name);
layout.add_rtti_ptr(rtti_name);
let virtual_funcs = self.collect_virtual_functions(record);
for (mangled, adjustment) in virtual_funcs {
layout.add_vfunc_ptr(mangled, adjustment, None);
}
let base_dtor = super::name_mangling::mangle_destructor(name);
let complete_dtor = super::name_mangling::mangle_destructor(name);
layout.add_base_object_dtor(base_dtor);
layout.add_complete_dtor(complete_dtor.clone());
let deleting_name = MangledName::new(format!("_Z{}D0Ev", name.len()));
layout.add_deleting_dtor(deleting_name);
for base in bases {
if base.is_virtual {
layout.add_vbase_offset(0);
}
}
self.build_secondary_vtables(record, &mut layout);
if layout.has_virtual_bases {
self.build_construction_vtables(record, &mut layout);
}
self.layouts.insert(name.clone(), layout.clone());
Some(layout)
}
_ => None,
}
}
fn build_secondary_vtables(&mut self, record: &CXXDecl, primary_layout: &mut VtableLayout) {
if let CXXDecl::CXXRecord {
name,
bases,
members,
..
} = record
{
if bases.len() <= 1 {
return;
}
for (idx, base) in bases.iter().enumerate() {
if idx == 0 {
continue;
}
let base_name = base.base_ty.name.clone();
let vtable_name = self
.mangler
.mangle_vtable(&format!("{}_secondary_{}", name, idx), None);
let mut sec_layout = VtableLayout::new(&base_name, vtable_name);
sec_layout.is_primary = false;
sec_layout.offset_to_top = (idx * 8) as i64;
sec_layout.add_offset_to_top(sec_layout.offset_to_top);
let funcs = self.collect_virtual_functions(record);
for (m, adj) in funcs {
sec_layout.add_vfunc_ptr(m, adj, None);
}
self.secondary_vtables
.entry(name.clone())
.or_insert_with(HashMap::new)
.insert(base_name.clone(), sec_layout);
}
}
}
fn build_construction_vtables(&mut self, record: &CXXDecl, layout: &mut VtableLayout) {
if let CXXDecl::CXXRecord { name, bases, .. } = record {
for base in bases {
if base.is_virtual {
let base_name = base.base_ty.name.clone();
let cv_name = format!("_ZTC{}_{}", name, base_name);
let mut cv_layout =
VtableLayout::new(&cv_name, MangledName::new(cv_name.clone()));
cv_layout.is_construction_vtable = true;
cv_layout.is_primary = false;
cv_layout.add_offset_to_top(0);
cv_layout.add_vbase_offset(0);
layout.construction_vtables.insert(base_name, cv_layout);
}
}
self.vtt_entries.push(VTTEntry {
vtable_name: layout.mangled_name.clone(),
subobject_name: name.clone(),
});
}
}
fn collect_virtual_functions(&mut self, decl: &CXXDecl) -> Vec<(MangledName, i64)> {
let mut funcs = Vec::new();
match decl {
CXXDecl::CXXRecord { members, name, .. } => {
for member in members {
match member {
CXXMemberDecl::Method {
name: method_name,
params,
is_virtual,
is_pure_virtual,
..
} => {
if *is_virtual || *is_pure_virtual {
let cxx_name = CXXName::new(method_name);
let param_strs: Vec<String> =
params.iter().map(|p| format!("{}", p.ty)).collect();
let mangled = self.mangler.mangle_function(
&cxx_name,
¶m_strs,
None,
false,
false,
Some(name),
);
funcs.push((mangled, 0));
}
}
CXXMemberDecl::Destructor { is_virtual, .. } => {
if *is_virtual {
let cxx_name = CXXName::new(name);
let mangled = self.mangler.mangle_function(
&cxx_name,
&[],
None,
false,
true,
Some(name),
);
funcs.push((mangled, 0));
}
}
_ => {}
}
}
}
_ => {}
}
funcs
}
pub fn get_vtable(&self, class_name: &str) -> Option<&VtableLayout> {
self.layouts.get(class_name)
}
pub fn all_layouts(&self) -> impl Iterator<Item = &VtableLayout> {
self.layouts.values()
}
pub fn get_secondary_vtables(
&self,
class_name: &str,
) -> Option<&HashMap<String, VtableLayout>> {
self.secondary_vtables.get(class_name)
}
pub fn emit_all_vtables_ir(&self) -> String {
let mut ir = String::new();
ir.push_str("; ── Vtable Globals ───────────────────────────────────────────\n");
for layout in self.layouts.values() {
if !layout.is_construction_vtable {
ir.push_str(&layout.to_comdat_decl());
}
}
for layout in self.layouts.values() {
ir.push_str(&layout.to_ir_global());
ir.push('\n');
for cv in layout.construction_vtables.values() {
ir.push_str(&cv.to_ir_global());
ir.push('\n');
}
}
for sec_map in self.secondary_vtables.values() {
for sec_layout in sec_map.values() {
ir.push_str(&sec_layout.to_ir_global());
ir.push('\n');
}
}
if !self.vtt_entries.is_empty() {
ir.push_str("; ── VTT (Virtual Table Table) ──────────────────────────────\n");
for entry in &self.vtt_entries {
ir.push_str(&format!(
"@{} = private constant [1 x ptr] [ptr @{}]\n",
entry.vtable_name, entry.vtable_name
));
}
}
ir
}
pub fn emit_virtual_call_ir(&self, class_name: &str, method_idx: usize) -> String {
let mut ir = String::new();
ir.push_str(&format!("; virtual call {}[{}]\n", class_name, method_idx));
ir.push_str(" %vtable.ptr = load ptr, ptr %this\n");
ir.push_str(&format!(
" %vfunc.gep = getelementptr ptr, ptr %vtable.ptr, i64 {}\n",
method_idx + 2
));
ir.push_str(" %vfunc.ptr = load ptr, ptr %vfunc.gep\n");
ir.push_str(" call void %vfunc.ptr(ptr %this)\n");
ir
}
pub fn emit_pure_virtual_call(&self) -> String {
let mut ir = String::new();
ir.push_str("; __cxa_pure_virtual call\n");
ir.push_str(" call void @__cxa_pure_virtual()\n");
ir.push_str(" unreachable\n");
ir
}
}
impl Default for VtableBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ThunkDesc {
pub target: MangledName,
pub thunk_name: MangledName,
pub this_adjustment: i64,
pub vcall_offset: Option<i64>,
pub return_type: String,
pub param_types: Vec<String>,
}
impl ThunkDesc {
pub fn to_ir(&self) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"define {} @{}(ptr %this",
self.return_type, self.thunk_name
));
for (i, pt) in self.param_types.iter().enumerate() {
ir.push_str(&format!(", {} %arg{}", pt, i));
}
ir.push_str(") {\nentry:\n");
if self.this_adjustment != 0 {
ir.push_str(&format!(
" %adjusted.this = getelementptr inbounds i8, ptr %this, i64 {}\n",
self.this_adjustment
));
ir.push_str(" %result = call ");
} else {
ir.push_str(" %result = call ");
}
ir.push_str(&format!("{} @{}(", self.return_type, self.target));
if self.this_adjustment != 0 {
ir.push_str("ptr %adjusted.this");
} else {
ir.push_str("ptr %this");
}
for i in 0..self.param_types.len() {
ir.push_str(&format!(", %arg{}", i));
}
ir.push_str(")\n");
if self.return_type != "void" {
ir.push_str(&format!(" ret {} %result\n", self.return_type));
} else {
ir.push_str(" ret void\n");
}
ir.push_str("}\n");
ir
}
pub fn to_ir_with_return_adjustment(&self, return_adjustment: i64) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"define {} @{}(ptr %this",
self.return_type, self.thunk_name
));
for (i, pt) in self.param_types.iter().enumerate() {
ir.push_str(&format!(", {} %arg{}", pt, i));
}
ir.push_str(") {\nentry:\n");
if self.this_adjustment != 0 {
ir.push_str(&format!(
" %adjusted.this = getelementptr inbounds i8, ptr %this, i64 {}\n",
self.this_adjustment
));
ir.push_str(" %raw.result = call ");
} else {
ir.push_str(" %raw.result = call ");
}
ir.push_str(&format!("{} @{}(", self.return_type, self.target));
if self.this_adjustment != 0 {
ir.push_str("ptr %adjusted.this");
} else {
ir.push_str("ptr %this");
}
for i in 0..self.param_types.len() {
ir.push_str(&format!(", %arg{}", i));
}
ir.push_str(")\n");
if return_adjustment != 0 {
ir.push_str(&format!(
" %result = getelementptr inbounds i8, ptr %raw.result, i64 {}\n",
return_adjustment
));
}
if self.return_type != "void" {
ir.push_str(&format!(" ret {} %result\n", self.return_type));
} else {
ir.push_str(" ret void\n");
}
ir.push_str("}\n");
ir
}
}
pub struct ThunkGenerator {
mangler: Mangler,
}
impl ThunkGenerator {
pub fn new() -> Self {
Self {
mangler: Mangler::new(),
}
}
pub fn generate_this_adjustment_thunk(
&mut self,
target_name: &CXXName,
this_adjustment: i64,
return_type: &str,
param_types: &[String],
) -> ThunkDesc {
let target = self.mangler.mangle_function(
target_name,
param_types,
Some(return_type),
false,
false,
None,
);
let thunk_name = self
.mangler
.mangle_thunk(target_name, this_adjustment, None);
ThunkDesc {
target,
thunk_name,
this_adjustment,
vcall_offset: None,
return_type: return_type.to_string(),
param_types: param_types.to_vec(),
}
}
pub fn generate_vcall_thunk(
&mut self,
target_name: &CXXName,
this_adjustment: i64,
vcall_offset: i64,
return_type: &str,
param_types: &[String],
) -> ThunkDesc {
let target = self.mangler.mangle_function(
target_name,
param_types,
Some(return_type),
false,
false,
None,
);
let thunk_name =
self.mangler
.mangle_thunk(target_name, this_adjustment, Some(vcall_offset));
ThunkDesc {
target,
thunk_name,
this_adjustment,
vcall_offset: Some(vcall_offset),
return_type: return_type.to_string(),
param_types: param_types.to_vec(),
}
}
pub fn generate_return_adjustment_thunk(
&mut self,
target_name: &CXXName,
return_adjustment: i64,
return_type: &str,
param_types: &[String],
) -> ThunkDesc {
let target = self.mangler.mangle_function(
target_name,
param_types,
Some(return_type),
false,
false,
None,
);
let thunk_name = MangledName::new(format!("_ZThn{}_{}", return_adjustment, target));
ThunkDesc {
target,
thunk_name,
this_adjustment: 0,
vcall_offset: Some(return_adjustment),
return_type: return_type.to_string(),
param_types: param_types.to_vec(),
}
}
}
impl Default for ThunkGenerator {
fn default() -> Self {
Self::new()
}
}
pub struct VtableAliasGenerator;
impl VtableAliasGenerator {
pub fn generate_alias_ir(vtable_mangled: &MangledName, alias_name: &str) -> String {
format!("@{} = alias ptr, ptr @{}\n", alias_name, vtable_mangled)
}
}
pub struct VirtualCallLowering;
impl VirtualCallLowering {
pub fn lower_virtual_call(
vtable_name: &str,
func_idx: usize,
return_ty: &str,
param_tys: &[String],
this_adjustment: i64,
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; virtual call via vtable {}\n", vtable_name));
if this_adjustment != 0 {
ir.push_str(&format!(
" %adj.this = getelementptr i8, ptr %this, i64 {}\n",
this_adjustment
));
}
ir.push_str(" %vtable = load ptr, ptr %this\n");
ir.push_str(&format!(
" %vfn.gep = getelementptr ptr, ptr %vtable, i64 {}\n",
func_idx
));
ir.push_str(" %vfn = load ptr, ptr %vfn.gep\n");
ir.push_str(&format!(" %call = call {} %vfn(ptr %this", return_ty));
for (i, pt) in param_tys.iter().enumerate() {
ir.push_str(&format!(", {} %arg{}", pt, i));
}
ir.push_str(")\n");
ir
}
pub fn lower_adjusted_virtual_call(
vtable_name: &str,
func_idx: usize,
return_ty: &str,
param_tys: &[String],
this_adjustment: i64,
) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"; adjusted virtual call via vtable {}\n",
vtable_name
));
ir.push_str(&format!(
" %adj.this = getelementptr i8, ptr %this, i64 {}\n",
this_adjustment
));
ir.push_str(" %vtable = load ptr, ptr %adj.this\n");
ir.push_str(&format!(
" %vfn.gep = getelementptr ptr, ptr %vtable, i64 {}\n",
func_idx
));
ir.push_str(" %vfn = load ptr, ptr %vfn.gep\n");
ir.push_str(&format!(" %call = call {} %vfn(ptr %adj.this", return_ty));
for (i, pt) in param_tys.iter().enumerate() {
ir.push_str(&format!(", {} %arg{}", pt, i));
}
ir.push_str(")\n");
ir
}
pub fn lower_pure_virtual_call() -> String {
" call void @__cxa_pure_virtual()\n unreachable\n".to_string()
}
}
pub struct ItaniumVtableIndices;
impl ItaniumVtableIndices {
pub const OFFSET_TO_TOP: usize = 0;
pub const RTTI_PTR: usize = 1;
pub const FIRST_VFUNC: usize = 2;
pub fn dtor_start(num_vfuncs: usize) -> usize {
ItaniumVtableIndices::FIRST_VFUNC + num_vfuncs
}
}
pub struct MicrosoftVtableIndices;
impl MicrosoftVtableIndices {
pub const FIRST_VFUNC: usize = 0;
pub const RTTI_OFFSET: isize = -1;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vtable_layout_basic() {
let mangled = MangledName::new("_ZTV3Foo".into());
let mut layout = VtableLayout::new("Foo", mangled);
layout.add_offset_to_top(0);
layout.add_rtti_ptr(MangledName::new("_ZTI3Foo".into()));
layout.add_vfunc_ptr(MangledName::new("_ZN3Foo3barEv".into()), 0, None);
assert_eq!(layout.num_entries, 3);
assert!(!layout.has_virtual_bases);
}
#[test]
fn test_vtable_to_ir() {
let mangled = MangledName::new("_ZTV3Foo".into());
let mut layout = VtableLayout::new("Foo", mangled);
layout.add_offset_to_top(0);
layout.add_rtti_ptr(MangledName::new("_ZTI3Foo".into()));
layout.add_vfunc_ptr(MangledName::new("_ZN3Foo3barEv".into()), 0, None);
let ir = layout.to_ir_global();
assert!(ir.contains("_ZTV3Foo"));
assert!(ir.contains("_ZTI3Foo"));
assert!(ir.contains("_ZN3Foo3barEv"));
}
#[test]
fn test_vtable_with_virtual_base() {
let mangled = MangledName::new("_ZTV3Bar".into());
let mut layout = VtableLayout::new("Bar", mangled);
layout.add_offset_to_top(0);
layout.add_rtti_ptr(MangledName::new("_ZTI3Bar".into()));
layout.add_vbase_offset(16);
assert!(layout.has_virtual_bases);
}
#[test]
fn test_vtable_with_destructors() {
let mangled = MangledName::new("_ZTV3Baz".into());
let mut layout = VtableLayout::new("Baz", mangled);
layout.add_offset_to_top(0);
layout.add_rtti_ptr(MangledName::new("_ZTI3Baz".into()));
layout.add_complete_dtor(MangledName::new("_ZN3BazD1Ev".into()));
layout.add_deleting_dtor(MangledName::new("_ZN3BazD0Ev".into()));
layout.add_base_object_dtor(MangledName::new("_ZN3BazD2Ev".into()));
assert_eq!(layout.num_entries, 5);
}
#[test]
fn test_thunk_ir_generation() {
let mut r#gen = ThunkGenerator::new();
let name = CXXName::new("foo");
let thunk = r#gen.generate_this_adjustment_thunk(&name, 8, "void", &["i32".into()]);
let ir = thunk.to_ir();
assert!(ir.contains("define void"));
assert!(ir.contains("getelementptr"));
assert!(ir.contains("i64 8"));
}
#[test]
fn test_thunk_return_adjustment() {
let mut r#gen = ThunkGenerator::new();
let name = CXXName::new("clone");
let thunk = r#gen.generate_return_adjustment_thunk(&name, 16, "ptr", &[]);
let ir = thunk.to_ir_with_return_adjustment(16);
assert!(ir.contains("getelementptr"));
}
#[test]
fn test_vtable_builder_empty_class() {
let mut builder = VtableBuilder::new();
let record = CXXDecl::CXXRecord {
name: "Empty".into(),
kind: CXXRecordKind::Class,
bases: vec![],
members: vec![],
is_final: false,
is_polymorphic: false,
is_abstract: false,
template_params: None,
};
assert!(builder.build_vtable(&record).is_none());
}
#[test]
fn test_vtable_builder_polymorphic() {
let mut builder = VtableBuilder::new();
let record = CXXDecl::CXXRecord {
name: "Poly".into(),
kind: CXXRecordKind::Class,
bases: vec![],
members: vec![CXXMemberDecl::Method {
name: "foo".into(),
return_ty: QualType::void(),
params: vec![],
body: None,
is_virtual: true,
is_pure_virtual: false,
is_override: false,
is_final: false,
is_const: false,
is_volatile: false,
is_static: false,
is_noexcept: false,
ref_qualifier: None,
is_explicit: false,
is_constexpr: false,
is_consteval: false,
trailing_return_ty: None,
access: AccessSpecifier::Public,
}],
is_final: false,
is_polymorphic: true,
is_abstract: false,
template_params: None,
};
let result = builder.build_vtable(&record);
assert!(result.is_some());
let layout = result.unwrap();
assert!(layout.num_entries >= 5); }
#[test]
fn test_thunk_with_vcall() {
let mut r#gen = ThunkGenerator::new();
let name = CXXName::new("clone");
let thunk = r#gen.generate_vcall_thunk(&name, 16, 8, "ptr", &[]);
assert!(thunk.vcall_offset.is_some());
assert_eq!(thunk.vcall_offset.unwrap(), 8);
assert_eq!(thunk.this_adjustment, 16);
}
#[test]
fn test_vtable_entries_kind_str() {
assert_eq!(VtableEntry::OffsetToTop(0).kind_str(), "offset-to-top");
assert_eq!(
VtableEntry::RttiPtr(MangledName::new("x".into())).kind_str(),
"rtti"
);
assert_eq!(VtableEntry::VirtualBaseOffset(8).kind_str(), "vbase-offset");
assert_eq!(
VtableEntry::CompleteDtor(MangledName::new("d".into())).kind_str(),
"complete-dtor"
);
}
#[test]
fn test_virtual_call_lowering() {
let ir = VirtualCallLowering::lower_virtual_call("_ZTV3Foo", 2, "void", &[], 0);
assert!(ir.contains("vtable _ZTV3Foo"));
assert!(ir.contains("load ptr"));
}
#[test]
fn test_adjusted_virtual_call() {
let ir = VirtualCallLowering::lower_adjusted_virtual_call("_ZTV3Foo", 2, "i32", &[], 8);
assert!(ir.contains("getelementptr i8"));
assert!(ir.contains("i64 8"));
}
#[test]
fn test_pure_virtual_call() {
let ir = VirtualCallLowering::lower_pure_virtual_call();
assert!(ir.contains("__cxa_pure_virtual"));
assert!(ir.contains("unreachable"));
}
#[test]
fn test_itanium_vtable_indices() {
assert_eq!(ItaniumVtableIndices::OFFSET_TO_TOP, 0);
assert_eq!(ItaniumVtableIndices::RTTI_PTR, 1);
assert_eq!(ItaniumVtableIndices::FIRST_VFUNC, 2);
assert_eq!(ItaniumVtableIndices::dtor_start(3), 5);
}
#[test]
fn test_vtable_byte_size() {
let mut layout = VtableLayout::new("Test", MangledName::new("_ZTV4Test".into()));
layout.add_offset_to_top(0);
layout.add_rtti_ptr(MangledName::new("_ZTI4Test".into()));
layout.add_vfunc_ptr(MangledName::new("f".into()), 0, None);
layout.add_complete_dtor(MangledName::new("D1".into()));
layout.add_deleting_dtor(MangledName::new("D0".into()));
layout.add_base_object_dtor(MangledName::new("D2".into()));
assert_eq!(layout.byte_size(), 48);
}
#[test]
fn test_vtable_alias_generation() {
let ir = VtableAliasGenerator::generate_alias_ir(
&MangledName::new("_ZTV3Foo".into()),
"_ZTV3Foo_local",
);
assert!(ir.contains("alias"));
assert!(ir.contains("_ZTV3Foo"));
}
}