use crate::module::Module;
use crate::types::{Type, TypeId, TypeKind};
use crate::value::ValueRef;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct LLVMContext {
type_cache: HashMap<TypeKind, Type>,
pub modules: Vec<Module>,
int_types: HashMap<u32, Type>,
pointer_types: HashMap<u32, Type>,
named_structs: HashMap<String, Type>,
}
impl LLVMContext {
pub fn new() -> Self {
let mut ctx = Self {
type_cache: HashMap::new(),
modules: Vec::new(),
int_types: HashMap::new(),
pointer_types: HashMap::new(),
named_structs: HashMap::new(),
};
ctx.i1();
ctx.i8();
ctx.i32();
ctx.i64();
ctx.void_ty();
ctx.float_ty();
ctx.double_ty();
ctx.label_ty();
ctx
}
pub fn get_type(&mut self, kind: TypeKind) -> Type {
self.type_cache
.entry(kind.clone())
.or_insert_with(|| Type::new(kind))
.clone()
}
pub fn i1(&mut self) -> Type {
self.get_type(Type::i1().kind)
}
pub fn i8(&mut self) -> Type {
self.get_type(Type::i8().kind)
}
pub fn i32(&mut self) -> Type {
self.get_type(Type::i32().kind)
}
pub fn i64(&mut self) -> Type {
self.get_type(Type::i64().kind)
}
pub fn void_ty(&mut self) -> Type {
self.get_type(TypeKind::Void)
}
pub fn float_ty(&mut self) -> Type {
self.get_type(TypeKind::Float)
}
pub fn double_ty(&mut self) -> Type {
self.get_type(TypeKind::Double)
}
pub fn label_ty(&mut self) -> Type {
self.get_type(TypeKind::Label)
}
pub fn create_module(&mut self, name: &str) -> &mut Module {
self.modules.push(Module::new(name));
self.modules.last_mut().unwrap()
}
}
impl Default for LLVMContext {
fn default() -> Self {
Self::new()
}
}
impl LLVMContext {
pub fn label_ty_cached(&self) -> Option<&Type> {
self.int_types.get(&0); None
}
pub fn metadata_ty(&mut self) -> Type {
self.get_type(TypeKind::Metadata)
}
pub fn token_ty(&mut self) -> Type {
self.get_type(TypeKind::Token)
}
pub fn x86_mmx_ty(&mut self) -> Type {
self.get_type(TypeKind::X86MMX)
}
pub fn bfloat_ty(&mut self) -> Type {
self.get_type(TypeKind::BFloat)
}
pub fn x86_fp80_ty(&mut self) -> Type {
self.get_type(TypeKind::X86FP80)
}
pub fn fp128_ty(&mut self) -> Type {
self.get_type(TypeKind::FP128)
}
pub fn ppc_fp128_ty(&mut self) -> Type {
self.get_type(TypeKind::PPCFP128)
}
pub fn int_ty(&mut self, bits: u32) -> Type {
if let Some(ty) = self.int_types.get(&bits) {
return ty.clone();
}
let ty = Type::int(bits);
self.int_types.insert(bits, ty.clone());
ty
}
pub fn pointer_ty(&mut self, addr_space: u32) -> Type {
if let Some(ty) = self.pointer_types.get(&addr_space) {
return ty.clone();
}
let ty = Type::pointer(addr_space);
self.pointer_types.insert(addr_space, ty.clone());
ty
}
pub fn array_ty(&mut self, elem: &Type, len: u64) -> Type {
let kind = TypeKind::Array {
len,
element_type_id: elem.id,
};
self.get_type(kind)
}
pub fn vector_ty(&mut self, elem: &Type, len: u32) -> Type {
let kind = TypeKind::FixedVector {
len,
element_type_id: elem.id,
};
self.get_type(kind)
}
pub fn struct_ty(&mut self, elements: &[Type], is_packed: bool) -> Type {
let element_ids: Vec<_> = elements.iter().map(|t| t.id).collect();
let kind = TypeKind::Struct {
name: None,
is_packed,
is_opaque: false,
element_type_ids: element_ids,
};
self.get_type(kind)
}
pub fn named_struct_ty(&mut self, name: &str, is_packed: bool) -> Type {
if let Some(ty) = self.named_structs.get(name) {
return ty.clone();
}
let kind = TypeKind::Struct {
name: Some(name.to_string()),
is_packed,
is_opaque: true, element_type_ids: Vec::new(),
};
let ty = self.get_type(kind);
self.named_structs.insert(name.to_string(), ty.clone());
ty
}
pub fn function_ty(&mut self, ret: &Type, params: &[Type], is_vararg: bool) -> Type {
let param_ids: Vec<_> = params.iter().map(|t| t.id).collect();
let kind = TypeKind::Function {
return_type_id: ret.id,
param_type_ids: param_ids,
is_vararg,
};
self.get_type(kind)
}
pub fn get_cached_type(&self, kind: &TypeKind) -> Option<Type> {
self.type_cache.get(kind).cloned()
}
pub fn cache_type(&mut self, ty: Type) {
self.type_cache.insert(ty.kind.clone(), ty);
}
pub fn create_module_owned(&self, name: &str) -> Module {
Module::new(name)
}
pub fn const_int(&self, ty: &Type, value: u64) -> crate::constants::Constant {
crate::constants::Constant::UInt(value, ty.clone())
}
pub fn const_float(&self, ty: &Type, value: f64) -> crate::constants::Constant {
crate::constants::Constant::Float(value, ty.clone())
}
pub fn const_null(&self, ty: &Type) -> crate::constants::Constant {
crate::constants::Constant::Null(ty.clone())
}
pub fn const_undef(&self, ty: &Type) -> crate::constants::Constant {
crate::constants::Constant::Undef(ty.clone())
}
pub fn const_poison(&self, ty: &Type) -> crate::constants::Constant {
crate::constants::Constant::Poison(ty.clone())
}
pub fn const_zeroinitializer(&self, ty: &Type) -> crate::constants::Constant {
crate::constants::Constant::ZeroInitializer(ty.clone())
}
pub fn half_ty(&mut self) -> Type {
self.get_type(TypeKind::Half)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DiagSeverity {
Ignored,
Note,
Remark,
Warning,
Error,
Fatal,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub severity: DiagSeverity,
pub message: String,
pub file: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
pub source_range: Option<(u32, u32, u32, u32)>, pub notes: Vec<String>,
pub fixit_hints: Vec<FixItHint>,
}
#[derive(Debug, Clone)]
pub struct FixItHint {
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
pub replacement: String,
}
impl Diagnostic {
pub fn new(severity: DiagSeverity, message: impl Into<String>) -> Self {
Self {
severity,
message: message.into(),
file: None,
line: None,
column: None,
source_range: None,
notes: Vec::new(),
fixit_hints: Vec::new(),
}
}
pub fn at(mut self, file: &str, line: u32, col: u32) -> Self {
self.file = Some(file.to_string());
self.line = Some(line);
self.column = Some(col);
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
pub fn with_fixit(mut self, hint: FixItHint) -> Self {
self.fixit_hints.push(hint);
self
}
pub fn is_error(&self) -> bool {
matches!(self.severity, DiagSeverity::Error | DiagSeverity::Fatal)
}
pub fn is_warning(&self) -> bool {
self.severity == DiagSeverity::Warning
}
}
#[derive(Debug, Clone, Default)]
pub struct DiagnosticEngine {
pub diagnostics: Vec<Diagnostic>,
pub error_count: u32,
pub warning_count: u32,
pub suppress_warnings: bool,
pub warnings_as_errors: bool,
pub error_limit: Option<u32>,
}
impl DiagnosticEngine {
pub fn new() -> Self {
Self {
diagnostics: Vec::new(),
error_count: 0,
warning_count: 0,
suppress_warnings: false,
warnings_as_errors: false,
error_limit: Some(20),
}
}
pub fn emit(&mut self, mut diag: Diagnostic) -> bool {
if diag.severity == DiagSeverity::Warning && self.suppress_warnings {
return true;
}
if diag.severity == DiagSeverity::Warning && self.warnings_as_errors {
diag.severity = DiagSeverity::Error;
}
match diag.severity {
DiagSeverity::Error | DiagSeverity::Fatal => {
self.error_count += 1;
if let Some(limit) = self.error_limit {
if self.error_count > limit {
self.diagnostics.push(diag);
return false; }
}
}
DiagSeverity::Warning => self.warning_count += 1,
_ => {}
}
self.diagnostics.push(diag);
true
}
pub fn has_errors(&self) -> bool {
self.error_count > 0
}
pub fn has_warnings(&self) -> bool {
self.warning_count > 0
}
pub fn clear(&mut self) {
self.diagnostics.clear();
self.error_count = 0;
self.warning_count = 0;
}
pub fn errors(&self) -> Vec<&Diagnostic> {
self.diagnostics.iter().filter(|d| d.is_error()).collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct ConstantPool {
int_constants: HashMap<(u32, i64, u64), ValueRef>,
float_constants: HashMap<(bool, u64), ValueRef>,
null_constants: HashMap<TypeId, ValueRef>,
undef_constants: HashMap<TypeId, ValueRef>,
poison_constants: HashMap<TypeId, ValueRef>,
zero_constants: HashMap<TypeId, ValueRef>,
aggregate_constants: HashMap<u64, ValueRef>,
pub count: usize,
}
impl ConstantPool {
pub fn new() -> Self {
Self {
int_constants: HashMap::new(),
float_constants: HashMap::new(),
null_constants: HashMap::new(),
undef_constants: HashMap::new(),
poison_constants: HashMap::new(),
zero_constants: HashMap::new(),
aggregate_constants: HashMap::new(),
count: 0,
}
}
pub fn get_int(&mut self, ty: &Type, signed: i64, unsigned: u64, bits: u32) -> ValueRef {
let key = (bits, signed, unsigned);
if let Some(c) = self.int_constants.get(&key) {
return c.clone();
}
let c = crate::constants::const_int(ty.clone(), signed);
self.int_constants.insert(key, c.clone());
self.count += 1;
c
}
pub fn get_float(&mut self, ty: &Type, is_double: bool, bits: u64) -> ValueRef {
let key = (is_double, bits);
if let Some(c) = self.float_constants.get(&key) {
return c.clone();
}
let val = if is_double {
f64::from_bits(bits)
} else {
f32::from_bits(bits as u32) as f64
};
let c = if is_double {
crate::constants::const_double(val)
} else {
crate::constants::const_float(val)
};
self.float_constants.insert(key, c.clone());
self.count += 1;
c
}
pub fn get_null(&mut self, ty: &Type) -> ValueRef {
let key = ty.id;
if let Some(c) = self.null_constants.get(&key) {
return c.clone();
}
let c = crate::constants::const_null_ptr(ty.clone());
self.null_constants.insert(key, c.clone());
self.count += 1;
c
}
pub fn get_undef(&mut self, ty: &Type) -> ValueRef {
let key = ty.id;
if let Some(c) = self.undef_constants.get(&key) {
return c.clone();
}
let c = crate::constants::undef_value(ty.clone());
self.undef_constants.insert(key, c.clone());
self.count += 1;
c
}
pub fn get_poison(&mut self, ty: &Type) -> ValueRef {
let key = ty.id;
if let Some(c) = self.poison_constants.get(&key) {
return c.clone();
}
let c = crate::constants::poison_value(ty.clone());
self.poison_constants.insert(key, c.clone());
self.count += 1;
c
}
pub fn clear(&mut self) {
self.int_constants.clear();
self.float_constants.clear();
self.null_constants.clear();
self.undef_constants.clear();
self.poison_constants.clear();
self.zero_constants.clear();
self.aggregate_constants.clear();
self.count = 0;
}
}
#[derive(Debug, Clone, Default)]
pub struct NamedMDNode {
pub name: String,
pub operands: Vec<crate::constants::MDNode>,
}
impl NamedMDNode {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
operands: Vec::new(),
}
}
pub fn add_operand(&mut self, node: crate::constants::MDNode) {
self.operands.push(node);
}
pub fn num_operands(&self) -> usize {
self.operands.len()
}
pub fn is_empty(&self) -> bool {
self.operands.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct NamedMetadataStore {
pub nodes: HashMap<String, NamedMDNode>,
}
impl NamedMetadataStore {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
}
}
pub fn get_or_create(&mut self, name: &str) -> &mut NamedMDNode {
self.nodes
.entry(name.to_string())
.or_insert_with(|| NamedMDNode::new(name))
}
pub fn get(&self, name: &str) -> Option<&NamedMDNode> {
self.nodes.get(name)
}
pub fn remove(&mut self, name: &str) -> Option<NamedMDNode> {
self.nodes.remove(name)
}
pub fn clear(&mut self) {
self.nodes.clear();
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct TypeCache {
pub int_types: HashMap<u32, Type>,
pub pointer_types: HashMap<u32, Type>,
pub named_structs: HashMap<String, Type>,
}
impl TypeCache {
pub fn new() -> Self {
Self {
int_types: HashMap::new(),
pointer_types: HashMap::new(),
named_structs: HashMap::new(),
}
}
pub fn is_empty(&self) -> bool {
self.int_types.is_empty() && self.pointer_types.is_empty() && self.named_structs.is_empty()
}
pub fn len(&self) -> usize {
self.int_types.len() + self.pointer_types.len() + self.named_structs.len()
}
pub fn clear(&mut self) {
self.int_types.clear();
self.pointer_types.clear();
self.named_structs.clear();
}
pub fn get_int_type(&self, bits: u32) -> Option<&Type> {
self.int_types.get(&bits)
}
pub fn get_pointer_type(&self, addr_space: u32) -> Option<&Type> {
self.pointer_types.get(&addr_space)
}
pub fn get_named_struct(&self, name: &str) -> Option<&Type> {
self.named_structs.get(name)
}
pub fn merge_into_context(&self, ctx: &mut LLVMContext) {
for (&bits, ty) in &self.int_types {
ctx.int_types.entry(bits).or_insert_with(|| ty.clone());
}
for (&addr, ty) in &self.pointer_types {
ctx.pointer_types.entry(addr).or_insert_with(|| ty.clone());
}
for (name, ty) in &self.named_structs {
ctx.named_structs
.entry(name.clone())
.or_insert_with(|| ty.clone());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metadata_ty() {
let mut ctx = LLVMContext::new();
let mt = ctx.metadata_ty();
assert!(mt.is_metadata());
}
#[test]
fn test_token_ty() {
let mut ctx = LLVMContext::new();
let tt = ctx.token_ty();
assert!(tt.is_token());
}
#[test]
fn test_x86_mmx_ty() {
let mut ctx = LLVMContext::new();
let mmx = ctx.x86_mmx_ty();
assert!(!mmx.is_void());
}
#[test]
fn test_bfloat_ty() {
let mut ctx = LLVMContext::new();
let bf = ctx.bfloat_ty();
assert!(bf.is_floating_point());
}
#[test]
fn test_x86_fp80_ty() {
let mut ctx = LLVMContext::new();
let fp80 = ctx.x86_fp80_ty();
assert!(fp80.is_floating_point());
}
#[test]
fn test_fp128_ty() {
let mut ctx = LLVMContext::new();
let fp128 = ctx.fp128_ty();
assert!(fp128.is_floating_point());
}
#[test]
fn test_ppc_fp128_ty() {
let mut ctx = LLVMContext::new();
let ppc = ctx.ppc_fp128_ty();
assert!(ppc.is_floating_point());
}
#[test]
fn test_half_ty() {
let mut ctx = LLVMContext::new();
let half = ctx.half_ty();
assert!(half.is_floating_point());
}
#[test]
fn test_int_ty_caching() {
let mut ctx = LLVMContext::new();
let i32_a = ctx.int_ty(32);
let i32_b = ctx.int_ty(32);
assert!(Type::is_same_type(&i32_a, &i32_b));
let i64_a = ctx.int_ty(64);
let i64_b = ctx.int_ty(64);
assert!(Type::is_same_type(&i64_a, &i64_b));
assert!(!Type::is_same_type(&i32_a, &i64_a));
}
#[test]
fn test_pointer_ty_caching() {
let mut ctx = LLVMContext::new();
let p0_a = ctx.pointer_ty(0);
let p0_b = ctx.pointer_ty(0);
assert!(Type::is_same_type(&p0_a, &p0_b));
let p1 = ctx.pointer_ty(1);
assert!(!Type::is_same_type(&p0_a, &p1));
}
#[test]
fn test_array_ty() {
let mut ctx = LLVMContext::new();
let i32 = ctx.i32();
let arr = ctx.array_ty(&i32, 10);
assert!(arr.is_array());
let arr2 = ctx.array_ty(&i32, 10);
assert!(Type::is_same_type(&arr, &arr2));
}
#[test]
fn test_vector_ty() {
let mut ctx = LLVMContext::new();
let i32 = ctx.i32();
let v4i32 = ctx.vector_ty(&i32, 4);
assert!(v4i32.is_vector());
let v4i32_2 = ctx.vector_ty(&i32, 4);
assert!(Type::is_same_type(&v4i32, &v4i32_2));
}
#[test]
fn test_struct_ty() {
let mut ctx = LLVMContext::new();
let i32 = ctx.i32();
let f64 = ctx.double_ty();
let st = ctx.struct_ty(&[i32.clone(), f64.clone()], false);
assert!(st.is_struct());
let st2 = ctx.struct_ty(&[i32.clone(), f64.clone()], false);
assert!(Type::is_same_type(&st, &st2));
}
#[test]
fn test_struct_ty_packed() {
let mut ctx = LLVMContext::new();
let i32 = ctx.i32();
let st_packed = ctx.struct_ty(&[i32.clone()], true);
let st_unpacked = ctx.struct_ty(&[i32.clone()], false);
assert!(!Type::is_same_type(&st_packed, &st_unpacked));
}
#[test]
fn test_named_struct_ty() {
let mut ctx = LLVMContext::new();
let st1 = ctx.named_struct_ty("MyStruct", false);
let st2 = ctx.named_struct_ty("MyStruct", false);
assert!(Type::is_same_type(&st1, &st2));
let st3 = ctx.named_struct_ty("OtherStruct", false);
assert!(!Type::is_same_type(&st1, &st3));
}
#[test]
fn test_function_ty() {
let mut ctx = LLVMContext::new();
let i32 = ctx.i32();
let f64 = ctx.double_ty();
let void = ctx.void_ty();
let fn_ty = ctx.function_ty(&i32, &[f64.clone()], false);
assert!(fn_ty.is_function());
let fn_ty2 = ctx.function_ty(&i32, &[f64.clone()], false);
assert!(Type::is_same_type(&fn_ty, &fn_ty2));
let fn_vararg = ctx.function_ty(&void, &[i32.clone()], true);
assert!(fn_vararg.is_function());
assert!(fn_vararg.is_vararg_function());
}
#[test]
fn test_get_cached_type() {
let mut ctx = LLVMContext::new();
let i32 = ctx.i32();
let cached = ctx.get_cached_type(&i32.kind);
assert!(cached.is_some());
assert!(Type::is_same_type(&cached.unwrap(), &i32));
let unknown_kind = TypeKind::Integer { bits: 99 };
assert!(ctx.get_cached_type(&unknown_kind).is_none());
ctx.get_type(TypeKind::Integer { bits: 99 });
assert!(ctx
.get_cached_type(&TypeKind::Integer { bits: 99 })
.is_some());
}
#[test]
fn test_cache_type() {
let mut ctx = LLVMContext::new();
let custom = Type::int(17);
ctx.cache_type(custom.clone());
let cached = ctx.get_cached_type(&custom.kind);
assert!(cached.is_some());
}
#[test]
fn test_const_creation_methods() {
let ctx = LLVMContext::new();
let i32 = Type::i32();
let c_int = ctx.const_int(&i32, 42);
assert!(matches!(c_int, crate::constants::Constant::UInt(42, _)));
let c_float = ctx.const_float(&Type::double(), 3.14);
assert!(
matches!(c_float, crate::constants::Constant::Float(v, _) if (v - 3.14).abs() < 0.001)
);
let c_null = ctx.const_null(&i32);
assert!(matches!(c_null, crate::constants::Constant::Null(_)));
let c_undef = ctx.const_undef(&i32);
assert!(matches!(c_undef, crate::constants::Constant::Undef(_)));
let c_poison = ctx.const_poison(&i32);
assert!(matches!(c_poison, crate::constants::Constant::Poison(_)));
let c_zero = ctx.const_zeroinitializer(&i32);
assert!(matches!(
c_zero,
crate::constants::Constant::ZeroInitializer(_)
));
}
#[test]
fn test_type_cache_empty() {
let cache = TypeCache::new();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_type_cache_clear() {
let mut cache = TypeCache::new();
cache.int_types.insert(32, Type::i32());
cache.pointer_types.insert(0, Type::pointer(0));
assert!(!cache.is_empty());
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_type_cache_merge_into_context() {
let mut cache = TypeCache::new();
cache.int_types.insert(16, Type::i16());
cache.pointer_types.insert(0, Type::pointer(0));
let mut ctx = LLVMContext::new();
cache.merge_into_context(&mut ctx);
let i16_cached = ctx.int_types.get(&16);
assert!(i16_cached.is_some());
assert!(i16_cached.unwrap().is_integer());
assert_eq!(i16_cached.unwrap().integer_bit_width(), 16);
let ptr_cached = ctx.pointer_types.get(&0);
assert!(ptr_cached.is_some());
assert!(ptr_cached.unwrap().is_pointer());
}
#[test]
fn test_type_cache_get_methods() {
let mut cache = TypeCache::new();
cache.int_types.insert(32, Type::i32());
cache.pointer_types.insert(0, Type::pointer(0));
cache.named_structs.insert("Foo".to_string(), Type::int(64));
assert!(cache.get_int_type(32).is_some());
assert!(cache.get_int_type(99).is_none());
assert!(cache.get_pointer_type(0).is_some());
assert!(cache.get_pointer_type(1).is_none());
assert!(cache.get_named_struct("Foo").is_some());
assert!(cache.get_named_struct("Bar").is_none());
}
#[test]
fn test_type_cache_default() {
let cache = TypeCache::default();
assert!(cache.is_empty());
}
#[test]
fn test_create_module_owned() {
let ctx = LLVMContext::new();
let module = ctx.create_module_owned("my_module");
assert_eq!(module.name, "my_module");
}
}