use std::collections::HashMap;
use super::name_mangling::{MangledName, Mangler};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RttiKind {
Fundamental,
Pointer,
PointerToMember,
Array,
Function,
Enum,
Class,
SiClass,
VmiClass,
}
impl RttiKind {
pub fn class_name(&self) -> &'static str {
match self {
RttiKind::Fundamental => "type_info",
RttiKind::Pointer => "__pointer_type_info",
RttiKind::PointerToMember => "__pointer_to_member_type_info",
RttiKind::Array => "__array_type_info",
RttiKind::Function => "__function_type_info",
RttiKind::Enum => "__enum_type_info",
RttiKind::Class => "__class_type_info",
RttiKind::SiClass => "__si_class_type_info",
RttiKind::VmiClass => "__vmi_class_type_info",
}
}
}
#[derive(Debug, Clone)]
pub struct RttiDescriptor {
pub type_name: String,
pub typeinfo_symbol: MangledName,
pub typeinfo_name_symbol: MangledName,
pub kind: RttiKind,
pub vtable_ptr: String,
pub name_string: String,
pub base_type: Option<Box<RttiDescriptor>>,
pub base_info: Vec<BaseClassTypeInfo>,
pub pointee_type: Option<Box<RttiDescriptor>>,
}
#[derive(Debug, Clone)]
pub struct BaseClassTypeInfo {
pub typeinfo: Box<RttiDescriptor>,
pub flags: u32,
pub offset: i64,
}
impl RttiDescriptor {
pub fn new(
type_name: &str,
typeinfo_symbol: MangledName,
typeinfo_name_symbol: MangledName,
kind: RttiKind,
) -> Self {
Self {
type_name: type_name.to_string(),
typeinfo_symbol,
typeinfo_name_symbol,
kind,
vtable_ptr: String::new(),
name_string: type_name.to_string(),
base_type: None,
base_info: Vec::new(),
pointee_type: None,
}
}
pub fn emit_name_ir(&self) -> String {
format!(
"@{} = private unnamed_addr constant [{} x i8] c\"{}\\00\", align 1\n",
self.typeinfo_name_symbol,
self.name_string.len() + 1,
self.name_string
)
}
pub fn emit_typeinfo_ir(&self) -> String {
let mut ir = String::new();
match self.kind {
RttiKind::Fundamental | RttiKind::Enum | RttiKind::Function => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr }} {{\n",
self.typeinfo_symbol
));
ir.push_str(&format!(" ptr {}, ; vtable\n", self.vtable_ptr));
ir.push_str(&format!(" ptr @{} ; name\n", self.typeinfo_name_symbol));
ir.push_str("}, align 8\n");
}
RttiKind::Pointer => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr, ptr }} {{\n",
self.typeinfo_symbol
));
ir.push_str(&format!(" ptr {}, ; vtable\n", self.vtable_ptr));
ir.push_str(&format!(" ptr @{}, ; name\n", self.typeinfo_name_symbol));
if let Some(ref pointee) = self.pointee_type {
ir.push_str(&format!(
" ptr @{} ; pointee typeinfo\n",
pointee.typeinfo_symbol
));
} else {
ir.push_str(" ptr null ; pointee typeinfo\n");
}
ir.push_str("}, align 8\n");
}
RttiKind::Class => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr }} {{\n",
self.typeinfo_symbol
));
ir.push_str(&format!(" ptr {}, ; vtable\n", self.vtable_ptr));
ir.push_str(&format!(" ptr @{} ; name\n", self.typeinfo_name_symbol));
ir.push_str("}, align 8\n");
}
RttiKind::SiClass => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr, ptr }} {{\n",
self.typeinfo_symbol
));
ir.push_str(&format!(" ptr {}, ; vtable\n", self.vtable_ptr));
ir.push_str(&format!(" ptr @{}, ; name\n", self.typeinfo_name_symbol));
if let Some(ref base) = self.base_type {
ir.push_str(&format!(
" ptr @{} ; base typeinfo\n",
base.typeinfo_symbol
));
} else {
ir.push_str(" ptr null ; base typeinfo\n");
}
ir.push_str("}, align 8\n");
}
RttiKind::VmiClass => {
let base_count = self.base_info.len();
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr, i32, i32, [{} x {{ ptr, i64 }}] }} {{\n",
self.typeinfo_symbol, base_count
));
ir.push_str(&format!(" ptr {}, ; vtable\n", self.vtable_ptr));
ir.push_str(&format!(" ptr @{}, ; name\n", self.typeinfo_name_symbol));
ir.push_str(&format!(" i32 {}, ; flags\n", 0u32));
ir.push_str(&format!(" i32 {}, ; base count\n", base_count));
ir.push_str(" [");
for (i, base) in self.base_info.iter().enumerate() {
if i > 0 {
ir.push_str(", ");
}
ir.push_str(&format!(
"{{ ptr @{}, i64 {} }}",
base.typeinfo.typeinfo_symbol, base.offset
));
}
ir.push_str("]\n}, align 8\n");
}
_ => {}
}
ir
}
}
pub struct RttiBuilder {
mangler: Mangler,
descriptors: HashMap<String, RttiDescriptor>,
typeinfo_vtable: String,
}
impl RttiBuilder {
pub fn new() -> Self {
Self {
mangler: Mangler::new(),
descriptors: HashMap::new(),
typeinfo_vtable: "null".to_string(),
}
}
pub fn set_typeinfo_vtable(&mut self, vtable: &str) {
self.typeinfo_vtable = vtable.to_string();
}
pub fn build_class_rtti(
&mut self,
class_name: &str,
bases: &[(String, bool, bool)], ) -> &RttiDescriptor {
let typeinfo_name = self.mangler.mangle_typeinfo_name(class_name);
let typeinfo = self.mangler.mangle_typeinfo(class_name);
let kind = if bases.is_empty() {
RttiKind::Class
} else if bases.len() == 1 && !bases[0].1 {
RttiKind::SiClass
} else {
RttiKind::VmiClass
};
let mut desc = RttiDescriptor::new(class_name, typeinfo, typeinfo_name, kind);
desc.vtable_ptr = self.typeinfo_vtable.clone();
desc.name_string = class_name.to_string();
match kind {
RttiKind::SiClass => {
let base_name = &bases[0].0;
self.build_class_rtti(base_name, &[]);
if let Some(base) = self.descriptors.get(base_name) {
desc.base_type = Some(Box::new(base.clone()));
}
}
RttiKind::VmiClass => {
for (base_name, is_virtual, _) in bases {
self.build_class_rtti(base_name, &[]);
if let Some(base) = self.descriptors.get(base_name) {
desc.base_info.push(BaseClassTypeInfo {
typeinfo: Box::new(base.clone()),
flags: if *is_virtual { 1u32 } else { 0u32 } | 2u32, offset: 0, });
}
}
}
_ => {}
}
self.descriptors
.insert(class_name.to_string(), desc.clone());
self.descriptors.get(class_name).unwrap()
}
pub fn build_fundamental_rtti(&mut self, type_name: &str) -> &RttiDescriptor {
let typeinfo_name = self.mangler.mangle_typeinfo_name(type_name);
let typeinfo = self.mangler.mangle_typeinfo(type_name);
let desc = RttiDescriptor::new(type_name, typeinfo, typeinfo_name, RttiKind::Fundamental);
self.descriptors.insert(type_name.to_string(), desc.clone());
self.descriptors.get(type_name).unwrap()
}
pub fn build_pointer_rtti(&mut self, pointee_name: &str) -> &RttiDescriptor {
let pointer_name = format!("P{}", pointee_name);
let typeinfo_name = self.mangler.mangle_typeinfo_name(&pointer_name);
let typeinfo = self.mangler.mangle_typeinfo(&pointer_name);
let mut desc =
RttiDescriptor::new(&pointer_name, typeinfo, typeinfo_name, RttiKind::Pointer);
desc.vtable_ptr = self.typeinfo_vtable.clone();
self.build_fundamental_rtti(pointee_name);
if let Some(pointee) = self.descriptors.get(pointee_name) {
desc.pointee_type = Some(Box::new(pointee.clone()));
}
let pointer_name_clone = pointer_name.clone();
self.descriptors.insert(pointer_name, desc.clone());
self.descriptors.get(&pointer_name_clone).unwrap()
}
pub fn get_descriptor(&self, name: &str) -> Option<&RttiDescriptor> {
self.descriptors.get(name)
}
pub fn emit_all_ir(&self) -> String {
let mut ir = String::new();
ir.push_str("; RTTI Type Descriptors\n");
for desc in self.descriptors.values() {
ir.push_str(&desc.emit_name_ir());
}
ir.push('\n');
for desc in self.descriptors.values() {
ir.push_str(&desc.emit_typeinfo_ir());
ir.push('\n');
}
ir
}
pub fn emit_dynamic_cast_helper(&self) -> String {
r#"
; Dynamic cast helper — walks the class hierarchy.
; In a full implementation, this would be in libcxxabi.
define ptr @__dynamic_cast(ptr %obj, ptr %src_typeinfo, ptr %dst_typeinfo, i64 %hint) {
entry:
; Simplified: just check if types are equal
%cmp = icmp eq ptr %src_typeinfo, %dst_typeinfo
br i1 %cmp, label %success, label %walk_bases
walk_bases:
; Check __si_class_type_info base
%si_vtable = getelementptr ptr, ptr %src_typeinfo, i32 0
%si_base_ptr = getelementptr ptr, ptr %src_typeinfo, i32 2
%si_base = load ptr, ptr %si_base_ptr
%has_base = icmp ne ptr %si_base, null
br i1 %has_base, label %recurse, label %fail
recurse:
%result = call ptr @__dynamic_cast(ptr %obj, ptr %si_base, ptr %dst_typeinfo, i64 0)
%found = icmp ne ptr %result, null
br i1 %found, label %success, label %fail
success:
ret ptr %obj
fail:
ret ptr null
}
"#
.to_string()
}
}
impl Default for RttiBuilder {
fn default() -> Self {
Self::new()
}
}
pub mod typeinfo_vtables {
pub const GCC_LINUX_TYPEINFO_VTABLE: &str = "_ZTVN10__cxxabiv117__class_type_infoE";
pub const GCC_LINUX_SI_CLASS_TYPEINFO_VTABLE: &str = "_ZTVN10__cxxabiv120__si_class_type_infoE";
pub const GCC_LINUX_VMI_CLASS_TYPEINFO_VTABLE: &str =
"_ZTVN10__cxxabiv121__vmi_class_type_infoE";
pub const GCC_LINUX_POINTER_TYPEINFO_VTABLE: &str = "_ZTVN10__cxxabiv119__pointer_type_infoE";
}
#[derive(Debug, Clone)]
pub struct TypeInfoGenerator {
pub class_name: String,
pub typeinfo_sym: String,
pub typeinfo_name_sym: String,
pub vtable_sym: String,
}
impl TypeInfoGenerator {
pub fn new(class_name: &str, mangler: &mut Mangler) -> Self {
let typeinfo_name_sym = mangler.mangle_typeinfo_name(class_name).into_string();
let typeinfo_sym = mangler.mangle_typeinfo(class_name).into_string();
Self {
class_name: class_name.to_string(),
typeinfo_sym,
typeinfo_name_sym,
vtable_sym: String::new(),
}
}
pub fn with_vtable(mut self, vtable: &str) -> Self {
self.vtable_sym = vtable.to_string();
self
}
pub fn emit_complete_ir(&self, kind: RttiKind) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"@{} = private unnamed_addr constant [{} x i8] c\"{}\\00\", align 1, section \".rodata\"\n",
self.typeinfo_name_sym,
self.class_name.len() + 1,
self.class_name
));
let section = get_rtti_section(kind);
match kind {
RttiKind::Class | RttiKind::Fundamental | RttiKind::Enum => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr }} {{ ptr {}, ptr @{} }}, align 8, section \"{}\"\n",
self.typeinfo_sym,
self.vtable_sym,
self.typeinfo_name_sym,
section
));
}
RttiKind::SiClass => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr, ptr }} {{ ptr {}, ptr @{}, ptr null }}, align 8, section \"{}\"\n",
self.typeinfo_sym,
self.vtable_sym,
self.typeinfo_name_sym,
section
));
}
RttiKind::VmiClass => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr, i32, i32, [0 x {{ ptr, i64 }}] }} {{ ptr {}, ptr @{}, i32 0, i32 0, zeroinitializer }}, align 8, section \"{}\"\n",
self.typeinfo_sym,
self.vtable_sym,
self.typeinfo_name_sym,
section
));
}
RttiKind::Pointer => {
ir.push_str(&format!(
"@{} = private constant {{ ptr, ptr, ptr }} {{ ptr {}, ptr @{}, ptr null }}, align 8, section \"{}\"\n",
self.typeinfo_sym,
self.vtable_sym,
self.typeinfo_name_sym,
section
));
}
_ => {}
}
ir
}
}
fn get_rtti_section(kind: RttiKind) -> &'static str {
match kind {
RttiKind::Fundamental
| RttiKind::Class
| RttiKind::SiClass
| RttiKind::VmiClass
| RttiKind::Pointer
| RttiKind::PointerToMember
| RttiKind::Array
| RttiKind::Function
| RttiKind::Enum => ".data.rel.ro",
}
}
#[derive(Debug, Clone)]
pub struct TypeInfoOrdering {
pub lhs: String,
pub rhs: String,
pub result: bool,
}
impl TypeInfoOrdering {
pub fn new(lhs: &str, rhs: &str) -> Self {
Self {
lhs: lhs.to_string(),
rhs: rhs.to_string(),
result: false,
}
}
pub fn compare(&mut self) -> bool {
self.result = self.lhs < self.rhs;
self.result
}
pub fn gen_before_ir(&self) -> String {
format!(
r#"; type_info::before comparison
%lhs.name.ptr = getelementptr inbounds {{ ptr, ptr }}, ptr %{}, i32 0, i32 1
%lhs.name = load ptr, ptr %lhs.name.ptr
%rhs.name.ptr = getelementptr inbounds {{ ptr, ptr }}, ptr %{}, i32 0, i32 1
%rhs.name = load ptr, ptr %rhs.name.ptr
%cmp = call i32 @strcmp(ptr %lhs.name, ptr %rhs.name)
%result = icmp slt i32 %cmp, 0
ret i1 %result
"#,
self.lhs, self.rhs
)
}
pub fn gen_equals_ir(lhs: &str, rhs: &str) -> String {
format!(" %eq = icmp eq ptr @{}, ptr @{}\n ret i1 %eq\n", lhs, rhs)
}
pub fn gen_not_equals_ir(lhs: &str, rhs: &str) -> String {
format!(
" %neq = icmp ne ptr @{}, ptr @{}\n ret i1 %neq\n",
lhs, rhs
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DynCastStrategy {
Identity,
Downcast,
CrossCast,
ToVoid,
}
#[derive(Debug, Clone)]
pub struct DynCastLowering {
pub source_type: String,
pub target_type: String,
pub strategy: DynCastStrategy,
pub is_ref_cast: bool,
pub hierarchy_depth: usize,
}
impl DynCastLowering {
pub fn new(source: &str, target: &str) -> Self {
let strategy = if source == target {
DynCastStrategy::Identity
} else if target == "void" {
DynCastStrategy::ToVoid
} else {
DynCastStrategy::Downcast
};
Self {
source_type: source.to_string(),
target_type: target.to_string(),
strategy,
is_ref_cast: false,
hierarchy_depth: 0,
}
}
pub fn gen_dynamic_cast_ir(&self, object: &str) -> String {
match self.strategy {
DynCastStrategy::Identity => {
format!(
" ; dynamic_cast: {} → {} (identity)\n ret ptr {}\n",
self.source_type, self.target_type, object
)
}
DynCastStrategy::ToVoid => {
format!(
" ; dynamic_cast: {} → void*\n %vtable = load ptr, ptr {}\n %offset = getelementptr inbounds i64, ptr %vtable, i64 -1\n %offset.val = load i64, ptr %offset\n %adj = getelementptr inbounds i8, ptr {}, i64 %offset.val\n ret ptr %adj\n",
self.source_type, object, object
)
}
DynCastStrategy::Downcast | DynCastStrategy::CrossCast => {
if self.is_ref_cast {
format!(
r#"; dynamic_cast throw-on-fail: {} → {}
%result = call ptr @__dynamic_cast(ptr {}, ptr @_ZTI{}, ptr @_ZTI{}, i64 0)
%is_null = icmp eq ptr %result, null
br i1 %is_null, label %throw_bad_cast, label %success
throw_bad_cast:
call void @__cxa_bad_cast()
unreachable
success:
ret ptr %result
"#,
self.source_type,
self.target_type,
object,
self.source_type,
self.target_type
)
} else {
format!(
" %result = call ptr @__dynamic_cast(ptr {}, ptr @_ZTI{}, ptr @_ZTI{}, i64 0)\n ret ptr %result\n",
object, self.source_type, self.target_type
)
}
}
}
}
pub fn check_hierarchy(source_hierarchy: &[String], target: &str) -> Option<DynCastStrategy> {
if source_hierarchy.is_empty() {
return None;
}
if source_hierarchy[0] == target || source_hierarchy.iter().any(|t| t == target) {
Some(if source_hierarchy[0] == target {
DynCastStrategy::Identity
} else {
DynCastStrategy::Downcast
})
} else if target == "void" {
Some(DynCastStrategy::ToVoid)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct TypeidOperator {
pub type_name: String,
pub is_type: bool,
pub is_dynamic: bool,
}
impl TypeidOperator {
pub fn new(type_name: &str) -> Self {
Self {
type_name: type_name.to_string(),
is_type: true,
is_dynamic: false,
}
}
pub fn dynamic(expr_type: &str) -> Self {
Self {
type_name: expr_type.to_string(),
is_type: false,
is_dynamic: true,
}
}
pub fn gen_static_typeid_ir(&self) -> String {
format!(
" ; typeid({})\n ret ptr @_ZTI{}\n",
self.type_name, self.type_name
)
}
pub fn gen_dynamic_typeid_ir(&self, object_ptr: &str) -> String {
format!(
r#"; typeid(dynamic expr of type {})
%vtable = load ptr, ptr {}
%typeinfo.ptr = getelementptr inbounds ptr, ptr %vtable, i64 -1
%typeinfo = load ptr, ptr %typeinfo.ptr
ret ptr %typeinfo
"#,
self.type_name, object_ptr
)
}
pub fn requires_rtti(&self) -> bool {
true }
pub fn gen_typeid_compare_ir(a_type: &str, b_type: &str) -> String {
format!(
" %eq = icmp eq ptr @_ZTI{}, ptr @_ZTI{}\n ret i1 %eq\n",
a_type, b_type
)
}
}
#[derive(Debug, Clone)]
pub struct RttiEmissionConfig {
pub target_triple: String,
pub use_comdat: bool,
pub data_section: String,
pub inline_vtable_ptrs: bool,
pub rtti_enabled: bool,
}
impl RttiEmissionConfig {
pub fn new(target_triple: &str) -> Self {
Self {
target_triple: target_triple.to_string(),
use_comdat: true,
data_section: ".data.rel.ro".to_string(),
inline_vtable_ptrs: false,
rtti_enabled: true,
}
}
pub fn disable_rtti(mut self) -> Self {
self.rtti_enabled = false;
self
}
pub fn emit_global_typeinfo(
&self,
sym: &str,
vtable: &str,
name_sym: &str,
kind: RttiKind,
) -> String {
if !self.rtti_enabled {
return format!("; RTTI disabled, skipping type_info for '{}'\n", sym);
}
let linkage = if self.use_comdat {
format!("linkonce_odr")
} else {
"private".to_string()
};
let section = &self.data_section;
match kind {
RttiKind::Class | RttiKind::Fundamental => {
format!(
"@{} = {} constant {{ ptr, ptr }} {{ ptr {}, ptr @{} }}, align 8, section \"{}\"\n",
sym, linkage, vtable, name_sym, section
)
}
RttiKind::SiClass => {
format!(
"@{} = {} constant {{ ptr, ptr, ptr }} {{ ptr {}, ptr @{}, ptr null }}, align 8, section \"{}\"\n",
sym, linkage, vtable, name_sym, section
)
}
_ => {
format!(
"@{} = {} constant {{ ptr, ptr }} {{ ptr {}, ptr @{} }}, align 8, section \"{}\"\n",
sym, linkage, vtable, name_sym, section
)
}
}
}
pub fn emit_name_string(&self, sym: &str, class_name: &str) -> String {
if !self.rtti_enabled {
return String::new();
}
format!(
"@{} = private unnamed_addr constant [{} x i8] c\"{}\\00\", align 1\n",
sym,
class_name.len() + 1,
class_name
)
}
pub fn emit_vtable_ptr_decl(sym: &str) -> String {
format!("@{} = external constant ptr ; type_info vtable\n", sym)
}
}
impl Default for RttiEmissionConfig {
fn default() -> Self {
Self::new("x86_64-unknown-linux-gnu")
}
}
pub fn emit_rtti_module(
config: &RttiEmissionConfig,
types: &[(String, RttiKind, Option<String>)],
) -> String {
let mut ir = String::new();
ir.push_str("; RTTI Module\n");
ir.push_str("; Target: ");
ir.push_str(&config.target_triple);
ir.push_str("\n\n");
for (name, kind, vtable) in types {
let name_sym = format!("_ZTS{}", name);
let typeinfo_sym = format!("_ZTI{}", name);
let vtable_sym = vtable.as_deref().unwrap_or("null");
ir.push_str(&config.emit_name_string(&name_sym, name));
ir.push_str(&config.emit_global_typeinfo(&typeinfo_sym, vtable_sym, &name_sym, *kind));
ir.push('\n');
}
ir
}
#[derive(Debug, Clone)]
pub struct ClassHierarchyWalker {
pub root: String,
pub hierarchy: Vec<HierarchyEntry>,
pub max_depth: usize,
}
#[derive(Debug, Clone)]
pub struct HierarchyEntry {
pub class_name: String,
pub typeinfo: Option<String>,
pub base: Option<String>,
pub is_virtual: bool,
pub offset: i64,
pub depth: usize,
}
impl ClassHierarchyWalker {
pub fn new(root: &str) -> Self {
Self {
root: root.to_string(),
hierarchy: vec![HierarchyEntry {
class_name: root.to_string(),
typeinfo: Some(format!("_ZTI{}", root)),
base: None,
is_virtual: false,
offset: 0,
depth: 0,
}],
max_depth: 0,
}
}
pub fn add_derived(&mut self, class: &str, base: &str, is_virtual: bool, offset: i64) {
let depth = self
.hierarchy
.iter()
.find(|e| e.class_name == base)
.map(|e| e.depth + 1)
.unwrap_or(0);
if depth > self.max_depth {
self.max_depth = depth;
}
self.hierarchy.push(HierarchyEntry {
class_name: class.to_string(),
typeinfo: Some(format!("_ZTI{}", class)),
base: Some(base.to_string()),
is_virtual,
offset,
depth,
});
}
pub fn find_path(&self, derived: &str, base: &str) -> Option<Vec<String>> {
if derived == base {
return Some(vec![derived.to_string()]);
}
let mut current = derived;
let mut path = vec![current.to_string()];
let mut visited = std::collections::HashSet::new();
visited.insert(current.to_string());
while let Some(entry) = self.hierarchy.iter().find(|e| e.class_name == current) {
if let Some(ref b) = entry.base {
if b == base {
path.push(b.clone());
return Some(path);
}
if visited.contains(b.as_str()) {
return None; }
visited.insert(b.clone());
path.push(b.clone());
current = b.as_str();
} else {
return None; }
}
None
}
pub fn calculate_offset(&self, derived: &str, base: &str) -> Option<i64> {
let mut total = 0i64;
let mut current = derived;
while let Some(entry) = self.hierarchy.iter().find(|e| e.class_name == current) {
if entry.class_name == base {
return Some(total);
}
if let Some(ref b) = entry.base {
total += entry.offset;
current = b;
} else {
return None;
}
}
None
}
pub fn gen_hierarchy_walk_ir(&self, obj: &str, target_typeinfo: &str) -> String {
let mut ir = String::new();
ir.push_str(&format!("; Hierarchy walk from '{}'\n", self.root));
for entry in &self.hierarchy {
if let Some(ref ti) = entry.typeinfo {
ir.push_str(&format!(
" %check.{} = icmp eq ptr @{}, ptr @{}\n",
entry.class_name, ti, target_typeinfo
));
ir.push_str(&format!(
" br i1 %check.{}, label %found.{}, label %next.{}\n",
entry.class_name, entry.class_name, entry.class_name
));
ir.push_str(&format!(
"found.{}:\n ret ptr {}\n\n",
entry.class_name, obj
));
ir.push_str(&format!("next.{}:\n", entry.class_name));
}
}
ir.push_str(" ret ptr null ; cast failed\n");
ir
}
}
#[derive(Debug, Clone)]
pub struct ComdatRttiEmitter {
pub comdat_counter: usize,
}
impl ComdatRttiEmitter {
pub fn new() -> Self {
Self { comdat_counter: 0 }
}
pub fn emit_comdat_typeinfo(
&mut self,
type_name: &str,
kind: RttiKind,
vtable_sym: &str,
) -> String {
self.comdat_counter += 1;
let comdat_key = format!("__typeinfo_{}", type_name);
let mut ir = String::new();
ir.push_str(&format!("${} = comdat any\n", comdat_key));
ir.push_str(&format!(
"@_ZTI{} = linkonce_odr constant {{ ptr, ptr }} {{ ptr {}, ptr @_ZTS{} }}, comdat(${})\n",
type_name, vtable_sym, type_name, comdat_key
));
ir
}
pub fn emit_comdat_name_string(&mut self, type_name: &str) -> String {
let comdat_key = format!("__typeinfo_name_{}", type_name);
format!(
"@_ZTS{} = linkonce_odr constant [{} x i8] c\"{}\\00\", comdat(${}), align 1\n",
type_name,
type_name.len() + 1,
type_name,
comdat_key
)
}
pub fn emit_typeinfo_pair(
&mut self,
type_name: &str,
kind: RttiKind,
vtable_sym: &str,
) -> String {
let mut ir = self.emit_comdat_name_string(type_name);
ir.push_str(&self.emit_comdat_typeinfo(type_name, kind, vtable_sym));
ir
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RttiBaseFlags(u32);
impl RttiBaseFlags {
pub const NONE: Self = Self(0);
pub const VIRTUAL: Self = Self(1 << 0);
pub const PUBLIC: Self = Self(1 << 1);
pub const PROTECTED: Self = Self(1 << 2);
pub const PRIVATE: Self = Self(1 << 3);
pub fn has(&self, flag: Self) -> bool {
(self.0 & flag.0) != 0
}
pub fn set(&mut self, flag: Self) {
self.0 |= flag.0;
}
pub fn raw(&self) -> u32 {
self.0
}
pub fn from_access(is_virtual: bool, is_public: bool, is_protected: bool) -> Self {
let mut flags = Self::NONE;
if is_virtual {
flags.set(Self::VIRTUAL);
}
if is_public {
flags.set(Self::PUBLIC);
} else if is_protected {
flags.set(Self::PROTECTED);
} else {
flags.set(Self::PRIVATE);
}
flags
}
}
#[derive(Debug, Clone)]
pub struct VmiBaseEncoder {
pub bases: Vec<VmiBaseEntry>,
pub flags: u32,
}
#[derive(Debug, Clone)]
pub struct VmiBaseEntry {
pub typeinfo: String,
pub offset_flags: u32,
pub offset: i64,
}
impl VmiBaseEncoder {
pub fn new() -> Self {
Self {
bases: Vec::new(),
flags: 0,
}
}
pub fn add_base(&mut self, typeinfo: &str, offset: i64, is_virtual: bool, is_public: bool) {
let flags = RttiBaseFlags::from_access(is_virtual, is_public, false);
self.bases.push(VmiBaseEntry {
typeinfo: typeinfo.to_string(),
offset_flags: flags.raw(),
offset,
});
}
pub fn gen_base_array_ir(&self) -> String {
let mut ir = String::new();
ir.push_str(&format!(" [{} x {{ ptr, i64 }}] [\n", self.bases.len()));
for (i, base) in self.bases.iter().enumerate() {
if i > 0 {
ir.push_str(",\n");
}
ir.push_str(&format!(
" {{ ptr @{}, i64 {} }}",
base.typeinfo, base.offset
));
}
ir.push_str("\n ]");
ir
}
pub fn gen_vmi_typeinfo_ir(&self, sym: &str, name_sym: &str, vtable: &str) -> String {
format!(
"@{} = private constant {{ ptr, ptr, i32, i32, [{} x {{ ptr, i64 }}] }} {{ ptr {}, ptr @{}, i32 {}, i32 {}, {}\n}}, align 8\n",
sym,
self.bases.len(),
vtable,
name_sym,
self.flags,
self.bases.len(),
self.gen_base_array_ir()
)
}
}
#[derive(Debug, Clone)]
pub struct PointerToMemberTypeInfo {
pub class_type: String,
pub member_type: String,
pub member_typeinfo: Option<String>,
}
impl PointerToMemberTypeInfo {
pub fn new(class_type: &str, member_type: &str) -> Self {
Self {
class_type: class_type.to_string(),
member_type: member_type.to_string(),
member_typeinfo: None,
}
}
pub fn emit_ir(&self, sym: &str, name_sym: &str, vtable: &str) -> String {
let member_ti = self.member_typeinfo.as_deref().unwrap_or("null");
format!(
"@{} = private constant {{ ptr, ptr, ptr, ptr }} {{ ptr {}, ptr @{}, ptr @_ZTI{}, ptr @{} }}\n",
sym, vtable, name_sym, self.class_type, member_ti
)
}
}
#[derive(Debug, Clone)]
pub struct ArrayTypeInfo {
pub element_type: String,
pub bounds: Option<usize>,
}
impl ArrayTypeInfo {
pub fn new(element_type: &str) -> Self {
Self {
element_type: element_type.to_string(),
bounds: None,
}
}
pub fn emit_ir(&self, sym: &str, name_sym: &str, vtable: &str) -> String {
let bounds_str = self
.bounds
.map(|n| n.to_string())
.unwrap_or_else(|| "0".to_string());
format!(
"@{} = private constant {{ ptr, ptr, ptr, i64 }} {{ ptr {}, ptr @{}, ptr @_ZTI{}, i64 {} }}, align 8\n",
sym, vtable, name_sym, self.element_type, bounds_str
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rtti_kind_class_name() {
assert_eq!(RttiKind::Fundamental.class_name(), "type_info");
assert_eq!(RttiKind::Class.class_name(), "__class_type_info");
assert_eq!(RttiKind::SiClass.class_name(), "__si_class_type_info");
assert_eq!(RttiKind::VmiClass.class_name(), "__vmi_class_type_info");
assert_eq!(RttiKind::Pointer.class_name(), "__pointer_type_info");
}
#[test]
fn test_build_fundamental_rtti() {
let mut builder = RttiBuilder::new();
let desc = builder.build_fundamental_rtti("int");
assert_eq!(desc.kind, RttiKind::Fundamental);
assert!(desc.typeinfo_symbol.as_str().starts_with("_ZTI"));
assert!(desc.typeinfo_name_symbol.as_str().starts_with("_ZTS"));
}
#[test]
fn test_build_class_rtti_no_bases() {
let mut builder = RttiBuilder::new();
let desc = builder.build_class_rtti("MyClass", &[]);
assert_eq!(desc.kind, RttiKind::Class);
assert!(desc.name_string.contains("MyClass"));
}
#[test]
fn test_build_class_rtti_single_base() {
let mut builder = RttiBuilder::new();
let desc = builder.build_class_rtti("Derived", &[("Base".into(), false, true)]);
assert_eq!(desc.kind, RttiKind::SiClass);
assert!(desc.base_type.is_some());
assert_eq!(desc.base_type.as_ref().unwrap().type_name, "Base");
}
#[test]
fn test_build_class_rtti_virtual_base() {
let mut builder = RttiBuilder::new();
let desc = builder.build_class_rtti(
"Derived",
&[("Base1".into(), false, true), ("Base2".into(), true, true)],
);
assert_eq!(desc.kind, RttiKind::VmiClass);
assert_eq!(desc.base_info.len(), 2);
}
#[test]
fn test_build_pointer_rtti() {
let mut builder = RttiBuilder::new();
let desc = builder.build_pointer_rtti("int");
assert_eq!(desc.kind, RttiKind::Pointer);
assert!(desc.pointee_type.is_some());
}
#[test]
fn test_emit_rtti_ir() {
let mut builder = RttiBuilder::new();
builder.build_class_rtti("Foo", &[]);
let ir = builder.emit_all_ir();
assert!(ir.contains("_ZTS3Foo"));
assert!(ir.contains("_ZTI3Foo"));
}
#[test]
fn test_emit_name_ir() {
let mut builder = RttiBuilder::new();
let desc = builder.build_class_rtti("Bar", &[]);
let ir = desc.emit_name_ir();
assert!(ir.contains("Bar"));
assert!(ir.contains("_ZTS"));
}
#[test]
fn test_rtti_descriptor_clone() {
let mut builder = RttiBuilder::new();
builder.build_fundamental_rtti("double");
let desc = builder.get_descriptor("double").unwrap();
let cloned = desc.clone();
assert_eq!(cloned.type_name, desc.type_name);
}
#[test]
fn test_typeinfo_vtable_constants() {
assert!(typeinfo_vtables::GCC_LINUX_TYPEINFO_VTABLE.contains("class_type_info"));
assert!(typeinfo_vtables::GCC_LINUX_SI_CLASS_TYPEINFO_VTABLE.contains("si_class_type_info"));
}
}