use super::ast::QualType;
use std::collections::HashMap;
use crate::context::LLVMContext;
use crate::ir_builder::IRBuilder;
use crate::module::Module;
use crate::types::{Type, TypeKind};
use crate::value::ValueRef;
use super::cpp_ast::*;
use super::cpp_token::CppStandard;
use super::exception::EHCodeGen;
use super::name_mangling::{MangledName, Mangler};
use super::rtti::RttiBuilder;
use super::vtable::{ThunkGenerator, VtableBuilder};
pub struct CXXCodeGen {
pub module: Module,
pub context: LLVMContext,
pub builder: IRBuilder,
pub current_function: Option<ValueRef>,
pub named_values: HashMap<String, ValueRef>,
pub global_values: HashMap<String, ValueRef>,
pub functions: HashMap<String, (ValueRef, QualType)>,
pub struct_types: HashMap<String, Type>,
pub vtables: VtableBuilder,
pub rtti: RttiBuilder,
pub mangler: Mangler,
pub eh_gen: EHCodeGen,
pub thunk_gen: ThunkGenerator,
pub ir_buffer: String,
pub errors: Vec<String>,
pub standard: CppStandard,
}
impl CXXCodeGen {
pub fn new(module_name: &str, target_triple: &str, standard: CppStandard) -> Self {
let mut module = Module::new(module_name);
module.set_target_triple(target_triple);
let context = LLVMContext::new();
let builder = IRBuilder::new(&context);
Self {
module,
context,
builder,
current_function: None,
named_values: HashMap::new(),
global_values: HashMap::new(),
functions: HashMap::new(),
struct_types: HashMap::new(),
vtables: VtableBuilder::new(),
rtti: RttiBuilder::new(),
mangler: Mangler::new(),
eh_gen: EHCodeGen::itanium(),
thunk_gen: ThunkGenerator::new(),
ir_buffer: String::new(),
errors: Vec::new(),
standard,
}
}
pub fn compile(&mut self, tu: &CXXTranslationUnit) -> Result<Module, Vec<String>> {
self.errors.clear();
self.ir_buffer.clear();
for decl in &tu.decls {
self.declare_pass1(decl);
}
for decl in &tu.decls {
self.compile_decl(decl);
}
self.emit_vtables();
self.emit_rtti();
self.emit_eh_decls();
if self.errors.is_empty() {
Ok(self.module.clone())
} else {
Err(self.errors.clone())
}
}
fn declare_pass1(&mut self, decl: &CXXDecl) {
match decl {
CXXDecl::CXXRecord {
name,
bases,
members: _,
is_polymorphic,
..
} => {
let struct_ty = Type::new(TypeKind::Struct {
name: Some(name.clone()),
element_type_ids: vec![],
is_packed: false,
is_opaque: false,
});
self.struct_types.insert(name.clone(), struct_ty);
if *is_polymorphic {
self.vtables.build_vtable(decl);
}
let base_list: Vec<(String, bool, bool)> = bases
.iter()
.map(|b| (b.base_ty.name.clone(), b.is_virtual, true))
.collect();
self.rtti.build_class_rtti(name, &base_list);
}
CXXDecl::Function {
name: _,
return_ty,
params,
body: _,
is_virtual: _,
..
} => {
let mangled = self.mangle_function(decl);
self.ir_buffer.push_str(&format!(
"declare {} @{}(",
self.type_to_ir(return_ty),
mangled
));
for (i, p) in params.iter().enumerate() {
if i > 0 {
self.ir_buffer.push_str(", ");
}
self.ir_buffer.push_str(&self.type_to_ir(&p.ty));
}
self.ir_buffer.push_str(")\n");
}
CXXDecl::ConstructorDecl {
class_name, params, ..
} => {
let mangled = self.mangle_constructor(class_name, params);
self.ir_buffer
.push_str(&format!("declare void @{}(", mangled));
self.ir_buffer.push_str("ptr %this");
for (_i, p) in params.iter().enumerate() {
self.ir_buffer
.push_str(&format!(", {}", self.type_to_ir(&p.ty)));
}
self.ir_buffer.push_str(")\n");
}
CXXDecl::DestructorDecl { class_name, .. } => {
let mangled = self.mangle_destructor(class_name);
self.ir_buffer
.push_str(&format!("declare void @{}(ptr %this)\n", mangled));
}
CXXDecl::Variable {
name,
ty,
init,
is_constexpr: _,
..
} => {
let ir_ty = self.type_to_ir(ty);
if let Some(init_expr) = init {
self.ir_buffer.push_str(&format!(
"@{} = global {} {}\n",
name,
ir_ty,
self.expr_to_ir(init_expr)
));
} else {
self.ir_buffer
.push_str(&format!("@{} = external global {}\n", name, ir_ty));
}
}
CXXDecl::TemplateDeclaration { decl: _inner, .. } => {
}
CXXDecl::LinkageSpec { language, decls } => {
self.ir_buffer
.push_str(&format!("; extern \"{}\" {{\n", language));
for d in decls {
self.declare_pass1(d);
}
self.ir_buffer.push_str("; }\n");
}
_ => {}
}
}
fn compile_decl(&mut self, decl: &CXXDecl) {
match decl {
CXXDecl::Function {
name: _,
return_ty,
params,
body,
..
} => {
if let Some(_body_stmts) = body {
let mangled = self.mangle_function(decl);
self.ir_buffer.push_str(&format!(
"define {} @{}(",
self.type_to_ir(return_ty),
mangled
));
for (i, p) in params.iter().enumerate() {
if i > 0 {
self.ir_buffer.push_str(", ");
}
self.ir_buffer
.push_str(&format!("{} %{}", self.type_to_ir(&p.ty), p.name));
}
self.ir_buffer.push_str(") {\n");
self.ir_buffer.push_str("entry:\n");
self.ir_buffer.push_str(" ret void\n");
self.ir_buffer.push_str("}\n\n");
}
}
CXXDecl::ConstructorDecl {
class_name,
params,
body,
init_list,
..
} => {
if let Some(_body_stmts) = body {
let mangled = self.mangle_constructor(class_name, params);
self.ir_buffer
.push_str(&format!("define void @{}(ptr %this", mangled));
for (_i, p) in params.iter().enumerate() {
self.ir_buffer
.push_str(&format!(", {}", self.type_to_ir(&p.ty)));
}
self.ir_buffer.push_str(") {\n");
self.ir_buffer.push_str("entry:\n");
for init in init_list {
self.emit_ctor_init(init);
}
self.ir_buffer.push_str(" ret void\n");
self.ir_buffer.push_str("}\n\n");
}
}
CXXDecl::DestructorDecl {
class_name, body, ..
} => {
if let Some(_body_stmts) = body {
let mangled = self.mangle_destructor(class_name);
self.ir_buffer
.push_str(&format!("define void @{}(ptr %this) {{\n", mangled));
self.ir_buffer.push_str("entry:\n");
self.ir_buffer.push_str(" ret void\n");
self.ir_buffer.push_str("}\n\n");
}
}
_ => {}
}
}
fn expr_to_ir(&self, expr: &CXXExpr) -> String {
match expr {
CXXExpr::NullPtrLiteral => "null".into(),
CXXExpr::BoolLiteral(true) => "true".into(),
CXXExpr::BoolLiteral(false) => "false".into(),
CXXExpr::ThisExpr => "%this".into(),
CXXExpr::CXXNewExpr { .. } => "null".into(), CXXExpr::CXXDeleteExpr { .. } => "".into(),
CXXExpr::CXXThrowExpr(Some(_e)) => {
format!("call void @__cxa_throw(ptr null, ptr null, ptr null)\n unreachable")
}
_ => format!("{}", expr),
}
}
fn emit_ctor_init(&mut self, init: &CtorInit) {
match init {
CtorInit::Base { name, args: _ } => {
self.ir_buffer
.push_str(&format!(" ; call base constructor {}\n", name));
}
CtorInit::Member { name, init: _ } => {
self.ir_buffer
.push_str(&format!(" ; initialize member {}\n", name));
}
CtorInit::Delegating { .. } => {
self.ir_buffer.push_str(" ; delegating constructor call\n");
}
CtorInit::Simple { member, .. } => {
self.ir_buffer
.push_str(&format!(" ; simple init member {}\n", member));
}
}
}
fn type_to_ir(&self, ty: &QualType) -> String {
let s = format!("{}", ty.unqualified());
match s.as_str() {
"void" => "void".into(),
"bool" => "i1".into(),
"char" | "signed char" | "unsigned char" => "i8".into(),
"short" | "unsigned short" => "i16".into(),
"int" | "unsigned int" => "i32".into(),
"long" | "unsigned long" => "i64".into(),
"long long" | "unsigned long long" => "i64".into(),
"float" => "float".into(),
"double" => "double".into(),
s if s.ends_with('*') => "ptr".into(),
s if s.ends_with('&') => "ptr".into(),
_ => {
if self.struct_types.contains_key(&s) {
format!("%{}", s)
} else {
"ptr".into() }
}
}
}
fn mangle_function(&mut self, decl: &CXXDecl) -> MangledName {
match decl {
CXXDecl::Function { name, params, .. } => {
let cxx_name = CXXName::new(name);
let param_strs: Vec<String> =
params.iter().map(|p| self.type_to_ir_str(&p.ty)).collect();
self.mangler
.mangle_function(&cxx_name, ¶m_strs, None, false, false, None)
}
CXXDecl::ConstructorDecl {
class_name, params, ..
} => {
let cxx_name = CXXName::new(class_name);
let param_strs: Vec<String> =
params.iter().map(|p| self.type_to_ir_str(&p.ty)).collect();
self.mangler.mangle_function(
&cxx_name,
¶m_strs,
None,
true,
false,
Some(class_name),
)
}
CXXDecl::DestructorDecl { class_name, .. } => {
let cxx_name = CXXName::new(class_name);
self.mangler
.mangle_function(&cxx_name, &[], None, false, true, Some(class_name))
}
_ => MangledName::new(format!("_Z{}", "unknown")),
}
}
fn mangle_constructor(&mut self, class_name: &str, params: &[CXXParamDecl]) -> MangledName {
let cxx_name = CXXName::new(class_name);
let param_strs: Vec<String> = params.iter().map(|p| self.type_to_ir_str(&p.ty)).collect();
self.mangler
.mangle_function(&cxx_name, ¶m_strs, None, true, false, Some(class_name))
}
fn mangle_destructor(&mut self, class_name: &str) -> MangledName {
let cxx_name = CXXName::new(class_name);
self.mangler
.mangle_function(&cxx_name, &[], None, false, true, Some(class_name))
}
fn type_to_ir_str(&self, ty: &QualType) -> String {
format!("{}", ty)
}
fn emit_vtables(&mut self) {
let vtable_ir = self.vtables.emit_all_vtables_ir();
self.ir_buffer.push_str(&vtable_ir);
}
fn emit_rtti(&mut self) {
let rtti_ir = self.rtti.emit_all_ir();
self.ir_buffer.push_str(&rtti_ir);
}
fn emit_eh_decls(&mut self) {
self.ir_buffer
.push_str(&self.eh_gen.gen_personality_decl_ir());
self.ir_buffer.push_str(&self.eh_gen.gen_runtime_decls_ir());
}
pub fn gen_virtual_call(
&mut self,
object: &str,
vtable_index: usize,
args: &[&str],
return_ty: &str,
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; virtual call to vtable slot {}\n", vtable_index));
ir.push_str(&format!(" %vtable.ptr = load ptr, ptr {}\n", object));
ir.push_str(&format!(
" %vfn.ptr = getelementptr ptr, ptr %vtable.ptr, i32 {}\n",
vtable_index + 2 ));
ir.push_str(" %vfn = load ptr, ptr %vfn.ptr\n");
ir.push_str(&format!(
" %result = call {} %vfn(ptr {})",
return_ty, object
));
for arg in args {
ir.push_str(&format!(", {}", arg));
}
ir.push_str("\n");
ir
}
pub fn gen_global_init(&mut self, priority: u32) -> String {
format!(
r#"
define internal void @_GLOBAL__sub_I_this_module() {{
entry:
; Static initialization at priority {}
ret void
}}
"#,
priority
)
}
pub fn gen_global_fini(&mut self, priority: u32) -> String {
format!(
r#"
define internal void @_GLOBAL__D_a() {{
entry:
; Static destruction at priority {}
ret void
}}
"#,
priority
)
}
pub fn finalize_ir(&self) -> String {
self.ir_buffer.clone()
}
pub fn gen_virtual_call_full(
&mut self,
object_ptr: &str,
vtable_index: usize,
this_adjustment: Option<i64>,
return_ty: &str,
args: &[&str],
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; virtual call vtable[{}]\n", vtable_index));
ir.push_str(&format!(
" %vptr.addr = getelementptr inbounds ptr, ptr {}, i32 0\n",
object_ptr
));
ir.push_str(" %vptr = load ptr, ptr %vptr.addr\n");
let this_ptr = if let Some(adj) = this_adjustment {
if adj != 0 {
ir.push_str(&format!(
" %adjusted.this = getelementptr inbounds i8, ptr {}, i64 {}\n",
object_ptr, adj
));
"%adjusted.this"
} else {
object_ptr
}
} else {
object_ptr
};
let slot = vtable_index + 2; ir.push_str(&format!(
" %vfn.slot = getelementptr inbounds ptr, ptr %vptr, i32 {}\n",
slot
));
ir.push_str(" %vfn = load ptr, ptr %vfn.slot\n");
ir.push_str(&format!(
" %call.result = call {} %vfn(ptr {}",
return_ty, this_ptr
));
for arg in args {
ir.push_str(&format!(", {}", arg));
}
ir.push_str(")\n");
ir
}
pub fn gen_constructor(
&mut self,
class_name: &str,
_members: &[(String, QualType)],
bases: &[(String, bool)],
init_list: &[CtorInit],
has_vtable: bool,
body_ir: &str,
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; Constructor for class {}\n", class_name));
if has_vtable {
let _vtable_global = format!("@_ZTV{}", class_name);
ir.push_str(&format!(
" %vptr.addr = getelementptr inbounds ptr, ptr %this, i32 0\n"
));
ir.push_str(&format!(
" store ptr @_ZTV{}, ptr %vptr.addr\n",
class_name
));
}
for (i, (base_name, is_virtual)) in bases.iter().enumerate() {
if *is_virtual {
ir.push_str(&format!(
" ; call virtual base constructor for {}\n",
base_name
));
ir.push_str(&format!(
" %vbase.ptr.{} = getelementptr inbounds ptr, ptr %this, i32 {}\n",
i,
i + 2
));
ir.push_str(&format!(
" %vbase.obj.{} = load ptr, ptr %vbase.ptr.{}\n",
i, i
));
ir.push_str(&format!(
" call void @_ZN{}C1E(ptr %vbase.obj.{})\n",
base_name, i
));
} else {
ir.push_str(&format!(" ; call base constructor for {}\n", base_name));
ir.push_str(&format!(
" %base.ptr.{} = getelementptr inbounds i8, ptr %this, i32 {}\n",
i,
i * 8 + 8
));
ir.push_str(&format!(
" call void @_ZN{}C2E(ptr %base.ptr.{})\n",
base_name, i
));
}
}
for init in init_list {
match init {
CtorInit::Member {
name,
init: init_expr,
} => {
ir.push_str(&format!(" ; init member {}\n", name));
ir.push_str(&format!(
" %field.{} = getelementptr inbounds {{ ... }}, ptr %this, i32 0, i32 <offset>\n",
name
));
ir.push_str(&format!(
" store {} {}, ptr %field.{}\n",
"<type>", init_expr, name
));
}
CtorInit::Base { name, .. } => {
ir.push_str(&format!(" ; base init {} (handled above)\n", name));
}
CtorInit::Delegating { .. } => {
ir.push_str(" ; delegating to another constructor\n");
ir.push_str(&format!(" call void @_ZN{}C1E(ptr %this)\n", class_name));
ir.push_str(" ret void\n");
return ir;
}
CtorInit::Simple { member, .. } => {
ir.push_str(&format!(" ; simple init member {}\n", member));
}
}
}
ir.push_str(body_ir);
ir.push_str(" ret void\n");
ir
}
pub fn gen_destructor(
&mut self,
class_name: &str,
members: &[(String, QualType)],
bases: &[(String, bool)],
has_vtable: bool,
_is_virtual_dtor: bool,
body_ir: &str,
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; Destructor for class {}\n", class_name));
if has_vtable {
ir.push_str(&format!(
" %vptr.dtor = getelementptr inbounds ptr, ptr %this, i32 0\n"
));
ir.push_str(&format!(
" store ptr @_ZTV{}, ptr %vptr.dtor\n",
class_name
));
}
ir.push_str(body_ir);
for (i, (name, _ty)) in members.iter().enumerate().rev() {
ir.push_str(&format!(" ; destroy member {} (index {})\n", name, i));
}
for (i, (base_name, is_virtual)) in bases.iter().enumerate().rev() {
if *is_virtual {
ir.push_str(&format!(
" ; call virtual base destructor for {}\n",
base_name
));
ir.push_str(&format!(
" call void @_ZN{}D0E(ptr %vbase.obj.{})\n",
base_name, i
));
} else {
ir.push_str(&format!(" ; call base destructor for {}\n", base_name));
ir.push_str(&format!(
" call void @_ZN{}D2E(ptr %base.ptr.{})\n",
base_name, i
));
}
}
ir.push_str(" ret void\n");
ir
}
pub fn gen_deleting_destructor(&mut self, class_name: &str) -> String {
let mut ir = String::new();
ir.push_str(&format!("; Deleting destructor for {}\n", class_name));
ir.push_str(&format!(" call void @_ZN{}D1E(ptr %this)\n", class_name));
ir.push_str(" call void @_ZdlPv(ptr %this)\n");
ir.push_str(" ret void\n");
ir
}
pub fn gen_lambda_closure_type(
&mut self,
captures: &[(String, QualType)],
_is_generic: bool,
) -> String {
let mut ir = String::new();
ir.push_str("; Lambda closure type\n");
ir.push_str(&format!(
"%lambda.anon = type {{ {} }}\n",
captures
.iter()
.map(|(name, ty)| format!("{}: {}", name, self.type_to_ir(ty)))
.collect::<Vec<_>>()
.join(", ")
));
ir
}
pub fn gen_lambda_call_operator(
&mut self,
captures: &[(String, QualType)],
params: &[CXXParamDecl],
return_ty: &QualType,
body_ir: &str,
) -> String {
let mut ir = String::new();
ir.push_str("; Lambda operator()\n");
ir.push_str(&format!("define {} @_ZZN...E(", self.type_to_ir(return_ty)));
ir.push_str("ptr %this");
for p in params {
ir.push_str(&format!(", {} %{}", self.type_to_ir(&p.ty), p.name));
}
ir.push_str(") {\n");
ir.push_str("entry:\n");
for (i, (name, ty)) in captures.iter().enumerate() {
ir.push_str(&format!(
" %capture.{}.ptr = getelementptr inbounds %lambda.anon, ptr %this, i32 0, i32 {}\n",
name, i
));
ir.push_str(&format!(
" %capture.{} = load {}, ptr %capture.{}.ptr\n",
name,
self.type_to_ir(ty),
name
));
}
ir.push_str(body_ir);
ir.push_str(" ret void\n");
ir.push_str("}\n");
ir
}
pub fn gen_lambda_to_fn_ptr(
&mut self,
is_stateless: bool,
return_ty: &QualType,
params: &[CXXParamDecl],
) -> String {
if !is_stateless {
return "; lambda is not stateless, cannot convert to function pointer\n".into();
}
let mut ir = String::new();
ir.push_str(&format!(
"define {} @_ZZN..._conv(",
self.type_to_ir(return_ty)
));
for (i, p) in params.iter().enumerate() {
if i > 0 {
ir.push_str(", ");
}
ir.push_str(&format!("{} %{}", self.type_to_ir(&p.ty), p.name));
}
ir.push_str(") {\n");
ir.push_str("entry:\n");
ir.push_str(" %closure = alloca %lambda.anon\n");
ir.push_str(&format!(
" %result = call {} @_ZZN...E(ptr %closure",
self.type_to_ir(return_ty)
));
for p in params {
ir.push_str(&format!(", {} %{}", self.type_to_ir(&p.ty), p.name));
}
ir.push_str(")\n");
ir.push_str(&format!(" ret {} %result\n", self.type_to_ir(return_ty)));
ir.push_str("}\n");
ir
}
pub fn gen_operator_new(
&mut self,
size: usize,
is_array: bool,
placement: Option<&str>,
is_nothrow: bool,
) -> String {
let mut ir = String::new();
let func_name = if is_array { "_Znam" } else { "_Znwm" };
if let Some(placement_ptr) = placement {
ir.push_str(&format!("; placement new: return {}\n", placement_ptr));
ir.push_str(&format!(" ret ptr {}\n", placement_ptr));
} else if is_nothrow {
ir.push_str(&format!(
" %call = call ptr @{}RKSt9nothrow_t(i64 {})\n",
func_name, size
));
} else {
ir.push_str(&format!(
" %call = call ptr @{}(i64 {})\n",
func_name, size
));
}
ir
}
pub fn gen_array_new_with_cookie(
&mut self,
elem_size: usize,
elem_count: usize,
has_dtor: bool,
) -> String {
let mut ir = String::new();
let cookie_size = if has_dtor { 8 } else { 0 };
let total_size = elem_size * elem_count + cookie_size;
ir.push_str(&format!(
"; array new: {} elements of size {}, cookie {}\n",
elem_count, elem_size, cookie_size
));
ir.push_str(&format!(" %alloc = call ptr @_Znam(i64 {})\n", total_size));
if has_dtor {
ir.push_str(&format!(" store i64 {}, ptr %alloc\n", elem_count));
ir.push_str(&format!(
" %array.begin = getelementptr inbounds i8, ptr %alloc, i64 8\n"
));
} else {
ir.push_str(" %array.begin = %alloc\n");
}
ir
}
pub fn gen_operator_delete(&mut self, ptr: &str, is_array: bool, has_cookie: bool) -> String {
let func_name = if is_array { "_ZdaPv" } else { "_ZdlPv" };
let mut ir = String::new();
if has_cookie && is_array {
ir.push_str(&format!(
" %original.alloc = getelementptr inbounds i8, ptr {}, i64 -8\n",
ptr
));
ir.push_str(&format!(
" call void @{}(ptr %original.alloc)\n",
func_name
));
} else {
ir.push_str(&format!(" call void @{}(ptr {})\n", func_name, ptr));
}
ir
}
pub fn gen_data_member_ptr(&mut self, field_offset: usize) -> String {
format!(
" %member.ptr = getelementptr inbounds i8, ptr %obj, i32 {}\n",
field_offset
)
}
pub fn gen_function_member_ptr(&mut self, func_ptr: &str, this_adjustment: i64) -> String {
format!(
"; member function pointer: fn={}, adj={}\n %fn.member.ptr = insertvalue {{ ptr, i64 }} undef, ptr {}, 0\n %fn.member = insertvalue {{ ptr, i64 }} %fn.member.ptr, i64 {}, 1\n",
func_ptr, this_adjustment, func_ptr, this_adjustment
)
}
pub fn gen_member_ptr_call(
&mut self,
object_ptr: &str,
member_ptr: &str,
is_virtual: bool,
args: &[&str],
return_ty: &str,
) -> String {
let mut ir = String::new();
if is_virtual {
ir.push_str(&format!(" ; virtual call through member pointer\n"));
ir.push_str(&format!(
" %fn.ptr = extractvalue {{ ptr, i64 }} {}, 0\n",
member_ptr
));
ir.push_str(&format!(
" %adj = extractvalue {{ ptr, i64 }} {}, 1\n",
member_ptr
));
ir.push_str(&format!(
" %adjusted.this = getelementptr inbounds i8, ptr {}, i64 %adj\n",
object_ptr
));
ir.push_str(&format!(
" %result = call {} %fn.ptr(ptr %adjusted.this",
return_ty
));
for arg in args {
ir.push_str(&format!(", {}", arg));
}
ir.push_str(")\n");
} else {
ir.push_str(&format!(" ; non-virtual call through member pointer\n"));
ir.push_str(&format!(
" %fn.ptr = extractvalue {{ ptr, i64 }} {}, 0\n",
member_ptr
));
ir.push_str(&format!(
" %result = call {} %fn.ptr(ptr {}",
return_ty, object_ptr
));
for arg in args {
ir.push_str(&format!(", {}", arg));
}
ir.push_str(")\n");
}
ir
}
pub fn gen_vbase_load(&mut self, object_ptr: &str, vbase_offset_index: usize) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"; load virtual base pointer at vbase offset index {}\n",
vbase_offset_index
));
ir.push_str(&format!(
" %vbase.offset.ptr = getelementptr inbounds ptr, ptr {}, i32 {}\n",
object_ptr,
vbase_offset_index + 1
));
ir.push_str(&format!(
" %vbase.offset = load i64, ptr %vbase.offset.ptr\n"
));
ir.push_str(&format!(
" %vbase.ptr = getelementptr inbounds i8, ptr {}, i64 %vbase.offset\n",
object_ptr
));
ir
}
pub fn gen_construction_vtable(&mut self, class_name: &str, base_class: &str) -> String {
format!(
"@_ZTC{0}_{1} = constant [N x ptr] [\n ; construction vtable for {1} in {0}\n]\n",
class_name, base_class
)
}
pub fn gen_virtual_base_thunk(&mut self, target_fn: &str, this_adjustment: i64) -> String {
let mut ir = String::new();
ir.push_str(&format!(
"; virtual base thunk to {}, adjustment {}\n",
target_fn, this_adjustment
));
ir.push_str(&format!(
"define void @_ZT{}_{}(ptr %this) {{\n",
"h", target_fn
));
ir.push_str(&format!(
" %adjusted = getelementptr inbounds i8, ptr %this, i64 {}\n",
this_adjustment
));
ir.push_str(&format!(" tail call void @{}(ptr %adjusted)\n", target_fn));
ir.push_str(" ret void\n");
ir.push_str("}\n");
ir
}
pub fn gen_typeinfo_hierarchy(
&mut self,
class_name: &str,
base_classes: &[(String, i64)],
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; RTTI type_info for class {}\n", class_name));
ir.push_str(&format!(
"@_ZTI{} = constant {{ ptr, ptr }} {{\n",
class_name
));
ir.push_str(" ptr @_ZTVN10__cxxabiv117__class_type_infoE + 16,\n");
ir.push_str(&format!(" ptr @_ZTS{}\n", class_name));
ir.push_str("}\n");
for (i, (base_name, offset)) in base_classes.iter().enumerate() {
ir.push_str(&format!(
" ; base type_info {} at offset {}\n",
base_name, offset
));
ir.push_str(&format!(
" %base.{}.ti = load ptr, ptr @_ZTI{}\n",
i, base_name
));
}
ir
}
pub fn gen_typeinfo_name(&mut self, class_name: &str) -> String {
let name_len = class_name.len();
format!(
"@_ZTS{} = constant [{} x i8] c\"{}\\n00\"\n",
class_name,
name_len + 1,
class_name
)
}
pub fn gen_vtable_definition(
&mut self,
class_name: &str,
vtable_entries: &[(usize, String, Option<i64>)], _has_virtual_bases: bool,
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; Vtable for class {}\n", class_name));
ir.push_str(&format!(
"@_ZTV{} = constant [{} x ptr] [\n",
class_name,
vtable_entries.len() + 2
));
ir.push_str(" ptr null, ; offset-to-top (0)\n");
ir.push_str(&format!(" ptr @_ZTI{}, ; RTTI\n", class_name));
for (_i, (index, func_name, thunk_adj)) in vtable_entries.iter().enumerate() {
if let Some(adj) = thunk_adj {
ir.push_str(&format!(
" ptr @_ZThn{}_{}, ; vfn[{}]: {} (thunk adj {})\n",
adj, func_name, index, func_name, adj
));
} else {
ir.push_str(&format!(" ptr @{}, ; vfn[{}]\n", func_name, index));
}
}
ir.push_str("]\n");
ir
}
pub fn gen_static_guard(&mut self, var_name: &str) -> String {
format!("@_ZGVZ{} = internal global i64 0, align 8\n", var_name)
}
pub fn gen_static_init_guard_sequence(&mut self, var_name: &str, init_ir: &str) -> String {
let mut ir = String::new();
let guard = format!("_ZGVZ{}", var_name);
ir.push_str(&format!(
" %guard.val = load atomic i64, ptr @{} acquire\n",
guard
));
ir.push_str(" %guard.check = icmp eq i64 %guard.val, 0\n");
ir.push_str(" br i1 %guard.check, label %init, label %done\n\n");
ir.push_str("init:\n");
ir.push_str(init_ir);
ir.push_str(&format!(" store atomic i64 1, ptr @{} release\n", guard));
ir.push_str(" br label %done\n\n");
ir.push_str("done:\n");
ir
}
pub fn gen_derived_to_base_cast(&mut self, src: &str, offset: i64) -> String {
if offset == 0 {
return format!(" ; derived-to-base: no adjustment needed for {}\n", src);
}
format!(
" %base.ptr = getelementptr inbounds i8, ptr {}, i64 {}\n",
src, offset
)
}
pub fn gen_base_to_derived_cast(
&mut self,
src: &str,
src_type: &str,
target_type: &str,
offset: i64,
) -> String {
format!(
"; dynamic_cast from {} to {}\n %cast.result = getelementptr inbounds i8, ptr {}, i64 {}\n",
src_type, target_type, src, offset
)
}
pub fn gen_string_literal(&mut self, s: &str, id: usize) -> String {
let len = s.len() + 1;
let escaped: String = s
.chars()
.map(|c| match c {
'\\' => "\\\\".to_string(),
'"' => "\\\"".to_string(),
'\n' => "\\0A".to_string(),
'\r' => "\\0D".to_string(),
'\t' => "\\09".to_string(),
'\0' => "\\00".to_string(),
c if c.is_ascii_graphic() || c == ' ' => c.to_string(),
_ => format!("\\{:02X}", c as u8),
})
.collect();
format!(
"@.str.{} = private unnamed_addr constant [{} x i8] c\"{}\\n00\", align 1\n",
id, len, escaped
)
}
pub fn gen_string_ptr(&mut self, id: usize) -> String {
format!(
" %str.ptr.{} = getelementptr inbounds [N x i8], ptr @.str.{}, i64 0, i64 0\n",
id, id
)
}
pub fn gen_template_instantiation_cache(
&mut self,
mangled_name: &str,
body_ir: &str,
) -> String {
format!(
"; template instantiation cache for {}\ndefine linkonce_odr void @{}(ptr %this) {{\n{}\n ret void\n}}\n",
mangled_name, mangled_name, body_ir
)
}
pub fn gen_inline_asm(
&mut self,
asm_string: &str,
constraints: &str,
has_side_effects: bool,
) -> String {
let side_effect = if has_side_effects { " sideeffect" } else { "" };
format!(
" call void asm{} \"{}\", \"{}\"()\n",
side_effect, asm_string, constraints
)
}
pub fn gen_atomic_load(&mut self, ptr: &str, ordering: &str, ty: &str) -> String {
format!(
" %atomic.val = load atomic {}, ptr {} {}\n",
ty, ptr, ordering
)
}
pub fn gen_atomic_store(&mut self, ptr: &str, value: &str, ordering: &str) -> String {
format!(
" store atomic {} {}, ptr {} {}\n",
value, value, ptr, ordering
)
}
pub fn gen_atomic_cmpxchg(
&mut self,
ptr: &str,
expected: &str,
desired: &str,
ty: &str,
) -> String {
format!(
" %cmpxchg.result = cmpxchg ptr {}, {} {}, {} {} acquire acquire\n",
ptr, ty, expected, ty, desired
)
}
pub fn gen_atomic_rmw(&mut self, op: &str, ptr: &str, value: &str, ty: &str) -> String {
format!(
" %atomicrmw.val = atomicrmw {} ptr {}, {} {} acquire\n",
op, ptr, ty, value
)
}
pub fn gen_fence(&mut self, ordering: &str) -> String {
format!(" fence {}\n", ordering)
}
pub fn gen_thread_local_global(
&mut self,
name: &str,
ty: &str,
init_val: Option<&str>,
) -> String {
match init_val {
Some(val) => format!("@{} = thread_local global {} {}, align 8\n", name, ty, val),
None => format!("@{} = external thread_local global {}\n", name, ty),
}
}
pub fn gen_tls_load(&mut self, name: &str, ty: &str) -> String {
format!(" %tls.val = load {}, ptr @{}\n", ty, name)
}
pub fn gen_tls_store(&mut self, name: &str, value: &str) -> String {
format!(" store {} {}, ptr @{}\n", value, value, name)
}
pub fn gen_lsda(&mut self, func_name: &str, landing_pads: &[(String, i32)]) -> String {
let mut ir = String::new();
ir.push_str(&format!("; LSDA for function {}\n", func_name));
ir.push_str(&format!(
"@_ZLSDA{} = internal constant {{ ptr, ptr }} zeroinitializer\n",
func_name
));
for (pad_name, action) in landing_pads {
ir.push_str(&format!(" ; landing pad {} action {}\n", pad_name, action));
}
ir
}
pub fn gen_personality_import(&mut self, personality_fn: &str) -> String {
format!("declare i32 @{}(i32, i64, ptr, ptr)\n", personality_fn)
}
pub fn gen_aggregate_init(
&mut self,
dest_ptr: &str,
struct_ty: &str,
elements: &[&str],
) -> String {
let mut ir = String::new();
ir.push_str(&format!("; aggregate initialization of {}\n", struct_ty));
ir.push_str(&format!(" %agg.tmp = alloca {}\n", struct_ty));
for (i, elem) in elements.iter().enumerate() {
ir.push_str(&format!(
" %field.ptr.{} = getelementptr inbounds {}, ptr %agg.tmp, i32 0, i32 {}\n",
i, struct_ty, i
));
ir.push_str(&format!(
" store {} {}, ptr %field.ptr.{}\n",
"<type>", elem, i
));
}
ir.push_str(&format!(
" call void @llvm.memcpy.p0.p0.i64(ptr {}, ptr %agg.tmp, i64 <size>, i1 false)\n",
dest_ptr
));
ir
}
pub fn gen_dbg_param(
&mut self,
param_name: &str,
_param_ty: &str,
_arg_index: usize,
) -> String {
format!(
" call void @llvm.dbg.declare(metadata ptr %{}, metadata !DILocalVariable(), metadata !DIExpression())\n",
param_name
)
}
pub fn gen_dbg_line(&mut self, line: u32, column: u32) -> String {
format!(
" call void @llvm.dbg.value(metadata i32 0, metadata !DILocation(line: {}, column: {}))\n",
line, column
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CtorDtorVariant {
Complete,
Base,
Allocating,
}
impl CtorDtorVariant {
pub fn ctor_suffix(&self) -> &'static str {
match self {
CtorDtorVariant::Complete => "C1",
CtorDtorVariant::Base => "C2",
CtorDtorVariant::Allocating => "C3",
}
}
pub fn dtor_suffix(&self) -> &'static str {
match self {
CtorDtorVariant::Complete => "D1",
CtorDtorVariant::Base => "D2",
CtorDtorVariant::Allocating => "D0",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_codegen_create() {
let cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
assert!(cg.errors.is_empty());
}
#[test]
fn test_type_to_ir_basic() {
let cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
assert_eq!(cg.type_to_ir(&QualType::void()), "void");
assert_eq!(cg.type_to_ir(&QualType::int()), "i32");
assert_eq!(cg.type_to_ir(&QualType::pointer_to(QualType::int())), "ptr");
}
#[test]
fn test_mangle_function_simple() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let decl = CXXDecl::Function {
name: "foo".into(),
return_ty: QualType::void(),
params: vec![],
body: None,
is_virtual: false,
is_override: false,
is_final: false,
is_const: false,
is_noexcept: false,
is_constexpr: false,
is_consteval: false,
is_static: false,
is_inline: false,
is_explicit: false,
is_deleted: false,
is_defaulted: false,
ref_qualifier: None,
trailing_return_ty: None,
template_params: None,
is_volatile: false,
linkage: String::new(),
init_list: vec![],
};
let mangled = cg.mangle_function(&decl);
assert!(mangled.as_str().starts_with("_Z"));
assert!(mangled.as_str().contains("foo"));
}
#[test]
fn test_expr_to_ir_nullptr() {
let cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
assert_eq!(cg.expr_to_ir(&CXXExpr::NullPtrLiteral), "null");
assert_eq!(cg.expr_to_ir(&CXXExpr::BoolLiteral(true)), "true");
}
#[test]
fn test_ctor_dtor_variant_suffixes() {
assert_eq!(CtorDtorVariant::Complete.ctor_suffix(), "C1");
assert_eq!(CtorDtorVariant::Base.ctor_suffix(), "C2");
assert_eq!(CtorDtorVariant::Allocating.ctor_suffix(), "C3");
assert_eq!(CtorDtorVariant::Complete.dtor_suffix(), "D1");
assert_eq!(CtorDtorVariant::Base.dtor_suffix(), "D2");
assert_eq!(CtorDtorVariant::Allocating.dtor_suffix(), "D0");
}
#[test]
fn test_virtual_call_ir() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_virtual_call("%obj", 2, &["%arg1"], "i32");
assert!(ir.contains("vtable"));
assert!(ir.contains("load ptr"));
assert!(ir.contains("getelementptr"));
}
#[test]
fn test_global_init_ir() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_global_init(65535);
assert!(ir.contains("_GLOBAL__sub"));
}
#[test]
fn test_mangle_constructor() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let params = vec![CXXParamDecl::new("x", QualType::int())];
let mangled = cg.mangle_constructor("MyClass", ¶ms);
assert!(mangled.as_str().starts_with("_Z"));
assert!(mangled.as_str().contains("MyClass"));
assert!(mangled.as_str().contains("C1"));
}
#[test]
fn test_finalize_ir_empty() {
let cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.finalize_ir();
assert!(ir.is_empty());
}
#[test]
fn test_virtual_call_full() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_virtual_call_full("%obj", 3, Some(8), "i32", &["%arg1"]);
assert!(ir.contains("vptr"));
assert!(ir.contains("load ptr"));
assert!(ir.contains("getelementptr"));
assert!(ir.contains("adjusted.this"));
assert!(ir.contains("call.result"));
}
#[test]
fn test_virtual_call_no_adjustment() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_virtual_call_full("%obj", 0, Some(0), "void", &[]);
assert!(ir.contains("vptr"));
assert!(!ir.contains("adjusted"));
}
#[test]
fn test_constructor_basic() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_constructor(
"MyClass",
&[],
&[("Base".into(), false)],
&[],
false,
" ; body\n",
);
assert!(ir.contains("Constructor for class MyClass"));
assert!(ir.contains("call base constructor"));
assert!(ir.contains("ret void"));
}
#[test]
fn test_constructor_with_vtable() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_constructor("Poly", &[], &[], &[], true, " ; body\n");
assert!(ir.contains("vptr"));
assert!(ir.contains("_ZTVPoly"));
}
#[test]
fn test_destructor_basic() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_destructor("MyClass", &[], &[], false, false, " ; body\n");
assert!(ir.contains("Destructor for class MyClass"));
assert!(ir.contains("ret void"));
}
#[test]
fn test_destructor_with_bases() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_destructor(
"Derived",
&[],
&[("Base".into(), false)],
false,
false,
" ; body\n",
);
assert!(ir.contains("call base destructor"));
}
#[test]
fn test_deleting_destructor() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_deleting_destructor("Foo");
assert!(ir.contains("Deleting destructor"));
assert!(ir.contains("_ZdlPv"));
}
#[test]
fn test_operator_new_basic() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_operator_new(32, false, None, false);
assert!(ir.contains("_Znwm"));
assert!(!ir.contains("nothrow"));
}
#[test]
fn test_operator_new_array() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_operator_new(64, true, None, false);
assert!(ir.contains("_Znam"));
}
#[test]
fn test_operator_new_nothrow() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_operator_new(16, false, None, true);
assert!(ir.contains("nothrow"));
}
#[test]
fn test_operator_new_placement() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_operator_new(16, false, Some("%buf"), false);
assert!(ir.contains("placement new"));
assert!(ir.contains("%buf"));
}
#[test]
fn test_operator_delete_basic() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_operator_delete("%ptr", false, false);
assert!(ir.contains("_ZdlPv"));
}
#[test]
fn test_operator_delete_array_with_cookie() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_operator_delete("%arr", true, true);
assert!(ir.contains("_ZdaPv"));
assert!(ir.contains("original.alloc"));
}
#[test]
fn test_array_new_with_cookie() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_array_new_with_cookie(24, 10, true);
assert!(ir.contains("_Znam"));
assert!(ir.contains("cookie"));
assert!(ir.contains("array.begin"));
}
#[test]
fn test_data_member_ptr() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_data_member_ptr(16);
assert!(ir.contains("getelementptr"));
assert!(ir.contains("member.ptr"));
assert!(ir.contains("i32 16"));
}
#[test]
fn test_function_member_ptr() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_function_member_ptr("@fn", 8);
assert!(ir.contains("insertvalue"));
assert!(ir.contains("member function pointer"));
}
#[test]
fn test_member_ptr_call_virtual() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_member_ptr_call("%obj", "%mptr", true, &["%a"], "i32");
assert!(ir.contains("extractvalue"));
assert!(ir.contains("adjusted.this"));
}
#[test]
fn test_vbase_load() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_vbase_load("%obj", 2);
assert!(ir.contains("virtual base pointer"));
assert!(ir.contains("vbase.offset"));
}
#[test]
fn test_virtual_base_thunk() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_virtual_base_thunk("@_ZN3Foo3barEv", 16);
assert!(ir.contains("thunk"));
assert!(ir.contains("adjusted"));
assert!(ir.contains("tail call"));
}
#[test]
fn test_typeinfo_name() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_typeinfo_name("MyClass");
assert!(ir.contains("_ZTSMyClass"));
assert!(ir.contains("MyClass"));
}
#[test]
fn test_typeinfo_hierarchy() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_typeinfo_hierarchy("Derived", &[("Base".into(), 0)]);
assert!(ir.contains("_ZTIDerived"));
assert!(ir.contains("type_info"));
assert!(ir.contains("_ZTIBase"));
}
#[test]
fn test_vtable_definition() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_vtable_definition(
"Foo",
&[
(0, "_ZN3Foo3barEv".into(), None),
(1, "_ZN3Foo3bazEv".into(), Some(8)),
],
false,
);
assert!(ir.contains("_ZTVFoo"));
assert!(ir.contains("offset-to-top"));
assert!(ir.contains("_ZTIFoo"));
assert!(ir.contains("_ZThn8"));
}
#[test]
fn test_static_guard() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_static_guard("myVar");
assert!(ir.contains("_ZGVZmyVar"));
assert!(ir.contains("internal global"));
}
#[test]
fn test_static_init_guard_sequence() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_static_init_guard_sequence("myVar", " store i32 42, ptr @myVar\n");
assert!(ir.contains("load atomic"));
assert!(ir.contains("store atomic"));
assert!(ir.contains("init:"));
assert!(ir.contains("done:"));
}
#[test]
fn test_lambda_closure_type() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_lambda_closure_type(&[("x".into(), QualType::int())], false);
assert!(ir.contains("lambda.anon"));
assert!(ir.contains("type"));
}
#[test]
fn test_lambda_to_fn_ptr_stateless() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_lambda_to_fn_ptr(true, &QualType::void(), &[]);
assert!(ir.contains("_conv"));
assert!(ir.contains("alloca"));
}
#[test]
fn test_lambda_to_fn_ptr_stateful() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_lambda_to_fn_ptr(false, &QualType::void(), &[]);
assert!(ir.contains("cannot convert"));
}
#[test]
fn test_construction_vtable() {
let mut cg = CXXCodeGen::new("test", "x86_64-linux-gnu", CppStandard::Cxx17);
let ir = cg.gen_construction_vtable("Derived", "Base");
assert!(ir.contains("_ZTC"));
assert!(ir.contains("construction vtable"));
}
}