use std::{
collections::{BTreeSet, HashMap, HashSet, VecDeque},
ffi::{CStr, c_char, c_int, c_void},
path::PathBuf,
};
use llvm_sys::{
LLVMDiagnosticSeverity, LLVMLinkage, LLVMOpcode, LLVMTypeKind,
bit_reader::LLVMParseBitcodeInContext2,
core::*,
debuginfo::{
LLVMDIFileGetDirectory, LLVMDIFileGetFilename, LLVMDIFlagStaticMember,
LLVMDIGlobalVariableExpressionGetVariable, LLVMDILocationGetColumn,
LLVMDILocationGetInlinedAt, LLVMDILocationGetLine, LLVMDILocationGetScope,
LLVMDIScopeGetFile, LLVMDITypeGetFlags, LLVMDITypeGetName, LLVMDITypeGetOffsetInBits,
LLVMDITypeGetSizeInBits, LLVMGetDINodeTag, LLVMGetMetadataKind, LLVMGetSubprogram,
LLVMInstructionGetDebugLoc, LLVMMetadataKind,
},
error::{LLVMDisposeErrorMessage, LLVMErrorRef, LLVMGetErrorMessage},
prelude::*,
target::{LLVMABISizeOfType, LLVMGetModuleDataLayout, LLVMOffsetOfElement, LLVMTargetDataRef},
transforms::pass_builder::{
LLVMCreatePassBuilderOptions, LLVMDisposePassBuilderOptions, LLVMRunPasses,
},
};
use rllvm_core::error::Error;
use crate::{
facts::*,
load::{LoadedModule, SourceState},
};
unsafe extern "C" {
fn __cxa_demangle(
name: *const c_char,
output: *mut c_char,
length: *mut usize,
status: *mut c_int,
) -> *mut c_char;
fn free(pointer: *mut c_void);
}
pub fn demangle(symbol: &str) -> Option<String> {
if let Some(rust) = symbol
.starts_with("_R")
.then(|| rustc_demangle::try_demangle(symbol).ok())
.flatten()
{
return Some(format!("{rust:#}"));
}
if !symbol.starts_with("_Z") {
return None;
}
let input = std::ffi::CString::new(symbol).ok()?;
let mut status: c_int = 0;
let output = unsafe {
__cxa_demangle(
input.as_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut status,
)
};
if output.is_null() {
return None;
}
let text = unsafe { CStr::from_ptr(output) }
.to_string_lossy()
.into_owned();
unsafe { free(output.cast()) };
(status == 0).then_some(text)
}
pub fn llvm_version() -> String {
let (mut major, mut minor, mut patch) = (0, 0, 0);
unsafe { llvm_sys::core::LLVMGetVersion(&mut major, &mut minor, &mut patch) };
format!("{major}.{minor}.{patch}")
}
const NEUTRAL_MODULE_ID: &str = "";
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ModuleFacts {
pub functions: Vec<FunctionFact>,
pub call_sites: Vec<CallSiteFact>,
pub uses: Vec<UseFact>,
pub producers: Vec<String>,
pub diagnostics: Vec<String>,
}
impl ModuleFacts {
pub fn bind_to_catalog(
&mut self,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) {
let stamp = |id: &mut FunctionId| id.module_id = module_id.to_string();
let resolve = |location: &mut Option<SourceLocation>| {
if let Some(location) = location {
resolve_status(location, module_id, source_status);
}
};
for function in &mut self.functions {
stamp(&mut function.id);
if let Some(target) = &mut function.alias_of {
stamp(target);
}
resolve(&mut function.location);
}
for site in &mut self.call_sites {
stamp(&mut site.id.function);
resolve(&mut site.location);
match &mut site.target {
CallTarget::Direct { callee } => stamp(callee),
CallTarget::Indirect {
llvm_target_bound: Some(bound),
..
} => bound.iter_mut().for_each(stamp),
CallTarget::Indirect { .. }
| CallTarget::Intrinsic { .. }
| CallTarget::InlineAsm => {}
}
}
for use_fact in &mut self.uses {
stamp(&mut use_fact.used);
if let Some(function) = &mut use_fact.in_function {
stamp(function);
}
resolve(&mut use_fact.location);
}
}
}
fn resolve_status(
location: &mut SourceLocation,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) {
let full = match &location.directory {
Some(directory) => directory.join(&location.file),
None => location.file.clone(),
};
let state = source_status.get(&(module_id.to_string(), full)).copied();
location.source_status = state.map_or(SourceStatus::Unknown, |state| state.status);
location.status_basis = state.and_then(|state| state.basis);
for frame in &mut location.inlined_at {
resolve_status(frame, module_id, source_status);
}
}
struct Context(LLVMContextRef);
impl Drop for Context {
fn drop(&mut self) {
unsafe { LLVMContextDispose(self.0) };
}
}
struct ParsedModule(LLVMModuleRef);
impl Drop for ParsedModule {
fn drop(&mut self) {
unsafe { LLVMDisposeModule(self.0) };
}
}
struct Buffer(LLVMMemoryBufferRef);
impl Drop for Buffer {
fn drop(&mut self) {
unsafe { LLVMDisposeMemoryBuffer(self.0) };
}
}
struct DiagnosticSink(*mut Vec<String>);
impl DiagnosticSink {
fn new() -> Self {
Self(Box::into_raw(Box::new(Vec::new())))
}
fn take(&self) -> Vec<String> {
unsafe { std::mem::take(&mut *self.0) }
}
}
impl Drop for DiagnosticSink {
fn drop(&mut self) {
drop(unsafe { Box::from_raw(self.0) });
}
}
extern "C" fn collect_diagnostic(info: LLVMDiagnosticInfoRef, context: *mut c_void) {
if info.is_null() || context.is_null() {
return;
}
let sink = unsafe { &mut *context.cast::<Vec<String>>() };
let description = unsafe { owned_message(LLVMGetDiagInfoDescription(info)) };
let severity = match unsafe { LLVMGetDiagInfoSeverity(info) } {
LLVMDiagnosticSeverity::LLVMDSError => "error",
LLVMDiagnosticSeverity::LLVMDSWarning => "warning",
LLVMDiagnosticSeverity::LLVMDSRemark => "remark",
LLVMDiagnosticSeverity::LLVMDSNote => "note",
};
sink.push(format!("{severity}: {description}"));
}
unsafe fn owned(pointer: *const c_char, length: usize) -> String {
if pointer.is_null() || length == 0 {
return String::new();
}
let bytes = unsafe { std::slice::from_raw_parts(pointer as *const u8, length) };
String::from_utf8_lossy(bytes).into_owned()
}
unsafe fn owned_message(pointer: *mut c_char) -> String {
if pointer.is_null() {
return String::new();
}
let text = unsafe { CStr::from_ptr(pointer) }
.to_string_lossy()
.into_owned();
unsafe { LLVMDisposeMessage(pointer) };
text
}
unsafe fn owned_error_message(error: LLVMErrorRef) -> String {
if error.is_null() {
return String::new();
}
let message = unsafe { LLVMGetErrorMessage(error) };
if message.is_null() {
return String::new();
}
let text = unsafe { CStr::from_ptr(message) }
.to_string_lossy()
.into_owned();
unsafe { LLVMDisposeErrorMessage(message) };
text
}
unsafe fn value_name(value: LLVMValueRef) -> String {
let mut length = 0usize;
unsafe { owned(LLVMGetValueName2(value, &mut length), length) }
}
unsafe fn location_of(value: LLVMValueRef) -> Option<SourceLocation> {
let line = unsafe { LLVMGetDebugLocLine(value) };
if line == 0 {
return None;
}
let mut length = 0;
let file = unsafe { owned(LLVMGetDebugLocFilename(value, &mut length), length as usize) };
let mut length = 0;
let directory = unsafe {
owned(
LLVMGetDebugLocDirectory(value, &mut length),
length as usize,
)
};
let mut inlined_at = Vec::new();
if !unsafe { LLVMIsAInstruction(value) }.is_null() {
let mut metadata = unsafe { LLVMInstructionGetDebugLoc(value) };
while !metadata.is_null() {
let outer = unsafe { LLVMDILocationGetInlinedAt(metadata) };
if outer.is_null() {
break;
}
inlined_at.push(unsafe { location_of_metadata(outer) });
metadata = outer;
}
}
Some(unsafe {
build_location(
file,
directory,
line,
LLVMGetDebugLocColumn(value),
inlined_at,
)
})
}
unsafe fn location_of_metadata(location: LLVMMetadataRef) -> SourceLocation {
let scope = unsafe { LLVMDILocationGetScope(location) };
let scope_file = unsafe { LLVMDIScopeGetFile(scope) };
let mut length = 0;
let file = unsafe {
owned(
LLVMDIFileGetFilename(scope_file, &mut length),
length as usize,
)
};
let mut length = 0;
let directory = unsafe {
owned(
LLVMDIFileGetDirectory(scope_file, &mut length),
length as usize,
)
};
unsafe {
build_location(
file,
directory,
LLVMDILocationGetLine(location),
LLVMDILocationGetColumn(location),
Vec::new(),
)
}
}
fn build_location(
file: String,
directory: String,
line: u32,
column: u32,
inlined_at: Vec<SourceLocation>,
) -> SourceLocation {
SourceLocation {
file: PathBuf::from(file),
directory: (!directory.is_empty()).then(|| PathBuf::from(directory)),
line,
column,
source_status: SourceStatus::Unknown,
status_basis: None,
inlined_at,
}
}
fn linkage_of(linkage: LLVMLinkage) -> Linkage {
match linkage {
LLVMLinkage::LLVMExternalLinkage => Linkage::External,
LLVMLinkage::LLVMInternalLinkage | LLVMLinkage::LLVMPrivateLinkage => Linkage::Internal,
LLVMLinkage::LLVMWeakODRLinkage | LLVMLinkage::LLVMLinkOnceODRLinkage => Linkage::Odr,
LLVMLinkage::LLVMWeakAnyLinkage | LLVMLinkage::LLVMLinkOnceAnyLinkage => Linkage::Weak,
LLVMLinkage::LLVMAvailableExternallyLinkage => Linkage::AvailableExternally,
_ => Linkage::Other,
}
}
const RUSTC_PRODUCER_PREFIX: &str = "rustc version ";
const SUBPROGRAM_UNIT_OPERAND: usize = 5;
const DWARF_LANGUAGE_KEYS: [(&str, &str); 2] = [
("language: DW_LANG_", "Rust"),
("sourceLanguageName: DW_LNAME_", "Rust"),
];
fn producer_language(producers: &[String]) -> Option<Language> {
let rustc = producers
.iter()
.filter(|producer| producer.starts_with(RUSTC_PRODUCER_PREFIX))
.count();
if producers.is_empty() || (rustc != 0 && rustc != producers.len()) {
None
} else if rustc == 0 {
Some(Language::Other)
} else {
Some(Language::Rust)
}
}
fn dwarf_language(unit: &str) -> Option<Language> {
DWARF_LANGUAGE_KEYS.iter().find_map(|(key, rust)| {
let start = unit.find(key)? + key.len();
let name = unit[start..].split([',', ')']).next()?.trim();
if name.is_empty() {
None
} else if name == *rust {
Some(Language::Rust)
} else {
Some(Language::Other)
}
})
}
unsafe fn md_node_operands(node: LLVMValueRef) -> Vec<LLVMValueRef> {
let count = unsafe { LLVMGetMDNodeNumOperands(node) } as usize;
let mut operands = vec![std::ptr::null_mut(); count];
unsafe { LLVMGetMDNodeOperands(node, operands.as_mut_ptr()) };
operands
}
unsafe fn operand_metadata(operand: LLVMValueRef) -> Option<LLVMMetadataRef> {
(!operand.is_null() && !unsafe { LLVMIsAMDNode(operand) }.is_null())
.then(|| unsafe { LLVMValueAsMetadata(operand) })
}
unsafe fn operand_string(operand: LLVMValueRef) -> Option<String> {
if operand.is_null() {
return None;
}
let mut length = 0;
let text = unsafe { LLVMGetMDString(operand, &mut length) };
(!text.is_null()).then(|| unsafe { owned(text, length as usize) })
}
unsafe fn operand_integer(operand: LLVMValueRef) -> Option<u64> {
(!operand.is_null() && !unsafe { LLVMIsAConstantInt(operand) }.is_null())
.then(|| unsafe { LLVMConstIntGetZExtValue(operand) })
}
unsafe fn read_producers(module: LLVMModuleRef) -> Vec<String> {
let name = c"llvm.ident";
let count = unsafe { LLVMGetNamedMetadataNumOperands(module, name.as_ptr()) } as usize;
let mut nodes = vec![std::ptr::null_mut(); count];
unsafe { LLVMGetNamedMetadataOperands(module, name.as_ptr(), nodes.as_mut_ptr()) };
nodes
.into_iter()
.filter(|node| !node.is_null())
.filter_map(|node| unsafe { operand_string(*md_node_operands(node).first()?) })
.collect()
}
const MAX_ALIAS_DEPTH: usize = 16;
unsafe fn alias_target(alias: LLVMValueRef) -> Option<LLVMValueRef> {
let mut value = alias;
for _ in 0..MAX_ALIAS_DEPTH {
if !unsafe { LLVMIsAFunction(value) }.is_null() {
return Some(value);
}
if unsafe { LLVMIsAGlobalAlias(value) }.is_null() {
return None;
}
value = unsafe { LLVMAliasGetAliasee(value) };
if value.is_null() {
return None;
}
}
None
}
unsafe fn definition_language(
context: LLVMContextRef,
function: LLVMValueRef,
units: &mut HashMap<LLVMMetadataRef, Option<Language>>,
module_language: Option<Language>,
) -> Option<SourceLanguage> {
unsafe { debug_info_language(context, function, units) }
.map(|name| SourceLanguage {
name,
basis: LanguageBasis::DebugInfo,
})
.or(module_language.map(|name| SourceLanguage {
name,
basis: LanguageBasis::Producer,
}))
}
unsafe fn debug_info_language(
context: LLVMContextRef,
function: LLVMValueRef,
units: &mut HashMap<LLVMMetadataRef, Option<Language>>,
) -> Option<Language> {
let subprogram = unsafe { LLVMGetSubprogram(function) };
if subprogram.is_null() {
return None;
}
let node = unsafe { LLVMMetadataAsValue(context, subprogram) };
let unit = *unsafe { md_node_operands(node) }.get(SUBPROGRAM_UNIT_OPERAND)?;
if unit.is_null() {
return None;
}
let metadata = unsafe { LLVMValueAsMetadata(unit) };
let kind = unsafe { LLVMGetMetadataKind(metadata) };
if !matches!(kind, LLVMMetadataKind::LLVMDICompileUnitMetadataKind) {
return None;
}
*units.entry(metadata).or_insert_with(|| {
dwarf_language(&unsafe { owned_message(LLVMPrintValueToString(unit)) })
})
}
unsafe fn indirect_target_bound(
instruction: LLVMValueRef,
callees_kind: u32,
module_id: &str,
) -> Option<Vec<FunctionId>> {
let node = unsafe { LLVMGetMetadata(instruction, callees_kind) };
if node.is_null() {
return None;
}
let operands = unsafe { md_node_operands(node) };
Some(
operands
.into_iter()
.filter(|operand| !operand.is_null())
.map(|operand| FunctionId {
module_id: module_id.to_string(),
symbol: unsafe { value_name(operand) },
})
.collect(),
)
}
unsafe fn call_target(
instruction: LLVMValueRef,
module_id: &str,
callees_kind: u32,
fields: &FieldReader,
) -> CallTarget {
let called = unsafe { LLVMGetCalledValue(instruction) };
if !called.is_null() {
if !unsafe { LLVMIsAFunction(called) }.is_null() {
let symbol = unsafe { value_name(called) };
return if symbol.starts_with("llvm.") {
CallTarget::Intrinsic { name: symbol }
} else {
CallTarget::Direct {
callee: FunctionId {
module_id: module_id.to_string(),
symbol,
},
}
};
}
if !unsafe { LLVMIsAGlobalAlias(called) }.is_null()
&& unsafe { alias_target(called) }.is_some()
{
return CallTarget::Direct {
callee: FunctionId {
module_id: module_id.to_string(),
symbol: unsafe { value_name(called) },
},
};
}
if !unsafe { LLVMIsAInlineAsm(called) }.is_null() {
return CallTarget::InlineAsm;
}
}
CallTarget::Indirect {
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGetCalledFunctionType(
instruction,
)))
},
llvm_target_bound: unsafe { indirect_target_bound(instruction, callees_kind, module_id) },
via_field: unsafe { fields.loaded_field(called) },
}
}
unsafe fn enclosing_function(instruction: LLVMValueRef, module_id: &str) -> Option<FunctionId> {
let block = unsafe { LLVMGetInstructionParent(instruction) };
if block.is_null() {
return None;
}
let function = unsafe { LLVMGetBasicBlockParent(block) };
if function.is_null() {
return None;
}
Some(FunctionId {
module_id: module_id.to_string(),
symbol: unsafe { value_name(function) },
})
}
const RECORD_PREFIXES: [&str; 3] = ["struct.", "class.", "union."];
const ANONYMOUS_RECORD: &str = "anon";
const TYPEINFO_NAME_MARKER: &str = "_ZTS";
const TYPEINFO_NAME_PREFIX: &str = "typeinfo name for ";
const MAX_TBAA_DEPTH: usize = 64;
const MAX_DEBUG_TYPE_DEPTH: usize = 64;
const DW_TAG_ARRAY_TYPE: u16 = 0x01;
const DW_TAG_MEMBER: u16 = 0x0d;
const DW_TAG_TYPEDEF: u16 = 0x16;
const DW_TAG_CONST_TYPE: u16 = 0x26;
const DW_TAG_VOLATILE_TYPE: u16 = 0x35;
const DW_RECORD_TAGS: [u16; 3] = [0x02, 0x13, 0x17];
const DI_TYPE_OPERANDS: usize = 5;
const DI_SCOPE_OPERAND: usize = 1;
const DI_BASE_TYPE_OPERAND: usize = DI_TYPE_OPERANDS;
const DI_COMPOSITE_ELEMENTS_OPERAND: usize = DI_TYPE_OPERANDS + 1;
const DI_COMPOSITE_IDENTIFIER_OPERAND: usize = DI_TYPE_OPERANDS + 4;
const DI_SUBROUTINE_TYPES_OPERAND: usize = DI_TYPE_OPERANDS;
const DI_VARIABLE_TYPE_OPERAND: usize = 3;
const SUBPROGRAM_TYPE_OPERAND: usize = 4;
fn normalize_record(raw: &str) -> Option<String> {
let name = RECORD_PREFIXES
.iter()
.find_map(|prefix| raw.strip_prefix(prefix))
.unwrap_or(raw);
let name = match name.rsplit_once('.') {
Some((stem, suffix))
if !suffix.is_empty() && suffix.bytes().all(|byte| byte.is_ascii_digit()) =>
{
stem
}
_ => name,
};
let name = match name.strip_prefix(TYPEINFO_NAME_MARKER) {
Some(mangled) => {
let demangled = demangle(&format!("_Z{mangled}"))?;
match demangled.strip_prefix(TYPEINFO_NAME_PREFIX) {
Some(stripped) => stripped.to_string(),
None => demangled,
}
}
None => name.to_string(),
};
(!name.is_empty() && name != ANONYMOUS_RECORD).then_some(name)
}
unsafe fn struct_record(ty: LLVMTypeRef) -> Option<String> {
unsafe {
if !matches!(LLVMGetTypeKind(ty), LLVMTypeKind::LLVMStructTypeKind)
|| LLVMIsLiteralStruct(ty) != 0
{
return None;
}
let name = LLVMGetStructName(ty);
if name.is_null() {
return None;
}
normalize_record(&CStr::from_ptr(name).to_string_lossy())
}
}
unsafe fn is_aggregate(value: LLVMValueRef) -> bool {
unsafe {
!LLVMIsAConstantStruct(value).is_null()
|| !LLVMIsAConstantArray(value).is_null()
|| !LLVMIsAConstantVector(value).is_null()
}
}
unsafe fn operand_index(user: LLVMValueRef, operand: LLVMUseRef) -> Option<u32> {
let count = unsafe { LLVMGetNumOperands(user) }.max(0) as u32;
(0..count).find(|&index| unsafe { LLVMGetOperandUse(user, index) } == operand)
}
type WrapPath = Vec<(LLVMValueRef, LLVMUseRef)>;
struct FieldReader {
context: LLVMContextRef,
layout: LLVMTargetDataRef,
tbaa_kind: u32,
dbg_kind: u32,
member_names: HashMap<FieldRef, Option<String>>,
}
impl FieldReader {
unsafe fn new(context: LLVMContextRef, module: LLVMModuleRef) -> Self {
let mut reader = unsafe {
Self {
context,
layout: LLVMGetModuleDataLayout(module),
tbaa_kind: LLVMGetMDKindIDInContext(context, c"tbaa".as_ptr(), 4),
dbg_kind: LLVMGetMDKindIDInContext(context, c"dbg".as_ptr(), 3),
member_names: HashMap::new(),
}
};
reader.member_names = unsafe { reader.read_member_names(module) };
reader
}
fn evidence(&self, field: FieldRef, basis: FieldBasis) -> FieldEvidence {
let name = self.member_names.get(&field).cloned().flatten();
FieldEvidence { field, basis, name }
}
unsafe fn loaded_field(&self, called: LLVMValueRef) -> Option<FieldEvidence> {
unsafe {
if called.is_null() || LLVMIsALoadInst(called).is_null() {
return None;
}
self.access_field(called, LLVMGetOperand(called, 0))
}
}
unsafe fn access_field(
&self,
access: LLVMValueRef,
pointer: LLVMValueRef,
) -> Option<FieldEvidence> {
if let Some(field) = unsafe { self.gep_field(pointer) } {
return Some(self.evidence(field, FieldBasis::StructGep));
}
let field = unsafe { self.tbaa_field(access) }?;
Some(self.evidence(field, FieldBasis::Tbaa))
}
unsafe fn gep_field(&self, pointer: LLVMValueRef) -> Option<FieldRef> {
unsafe {
let is_gep = !LLVMIsAGetElementPtrInst(pointer).is_null()
|| (!LLVMIsAConstantExpr(pointer).is_null()
&& matches!(LLVMGetConstOpcode(pointer), LLVMOpcode::LLVMGetElementPtr));
if !is_gep {
return None;
}
let source = LLVMGetGEPSourceElementType(pointer);
struct_record(source)?;
let indices = (2..LLVMGetNumOperands(pointer).max(0) as u32)
.map(|index| {
let operand = LLVMGetOperand(pointer, index);
(!LLVMIsAConstantInt(operand).is_null())
.then(|| LLVMConstIntGetSExtValue(operand))
})
.collect::<Option<Vec<_>>>()?;
self.walk_field(source, &indices, None)
}
}
unsafe fn walk_field(
&self,
mut ty: LLVMTypeRef,
indices: &[i64],
mut record: Option<String>,
) -> Option<FieldRef> {
let mut offset = 0u64;
for &index in indices {
unsafe {
match LLVMGetTypeKind(ty) {
LLVMTypeKind::LLVMStructTypeKind => {
let element = u32::try_from(index)
.ok()
.filter(|&element| element < LLVMCountStructElementTypes(ty))?;
let element_offset = LLVMOffsetOfElement(self.layout, ty, element);
if LLVMIsLiteralStruct(ty) == 0 {
record = struct_record(ty);
offset = element_offset;
} else {
offset = offset.checked_add(element_offset)?;
}
ty = LLVMStructGetTypeAtIndex(ty, element);
}
LLVMTypeKind::LLVMArrayTypeKind => {
let element = LLVMGetElementType(ty);
let step = u64::try_from(index)
.ok()?
.checked_mul(LLVMABISizeOfType(self.layout, element))?;
offset = offset.checked_add(step)?;
ty = element;
}
_ => return None,
}
}
}
Some(FieldRef {
record: record?,
offset,
})
}
unsafe fn tbaa_field(&self, access: LLVMValueRef) -> Option<FieldRef> {
let tag = unsafe { LLVMGetMetadata(access, self.tbaa_kind) };
if tag.is_null() {
return None;
}
let (base, access_type, offset) = unsafe {
let operands = md_node_operands(tag);
(
operand_metadata(*operands.first()?)?,
operand_metadata(*operands.get(1)?)?,
operand_integer(*operands.get(2)?)?,
)
};
let ancestors = unsafe { self.tbaa_ancestors(access_type) };
if ancestors.contains(&base) {
return None;
}
let (mut node, mut remaining) = (base, offset);
for _ in 0..MAX_TBAA_DEPTH {
let (name, members) = unsafe { self.tbaa_type(node) }?;
let &(member, member_offset) = members
.iter()
.filter(|(_, member_offset)| *member_offset <= remaining)
.max_by_key(|(member, member_offset)| {
(*member_offset, ancestors.contains(member))
})?;
if ancestors.contains(&member) {
return Some(FieldRef {
record: normalize_record(&name)?,
offset: remaining,
});
}
node = member;
remaining -= member_offset;
}
None
}
unsafe fn tbaa_type(
&self,
node: LLVMMetadataRef,
) -> Option<(String, Vec<(LLVMMetadataRef, u64)>)> {
unsafe {
if !matches!(
LLVMGetMetadataKind(node),
LLVMMetadataKind::LLVMMDTupleMetadataKind
) {
return None;
}
let operands = md_node_operands(LLVMMetadataAsValue(self.context, node));
let name = operand_string(*operands.first()?)?;
let members = operands[1..]
.as_chunks::<2>()
.0
.iter()
.map(|&[member, offset]| {
Some((operand_metadata(member)?, operand_integer(offset)?))
})
.collect::<Option<Vec<_>>>()?;
Some((name, members))
}
}
unsafe fn tbaa_ancestors(&self, access_type: LLVMMetadataRef) -> Vec<LLVMMetadataRef> {
let mut ancestors = vec![access_type];
for _ in 0..MAX_TBAA_DEPTH {
let current = ancestors[ancestors.len() - 1];
let Some((_, members)) = (unsafe { self.tbaa_type(current) }) else {
break;
};
match members.first() {
Some(&(parent, _)) if !ancestors.contains(&parent) => ancestors.push(parent),
_ => break,
}
}
ancestors
}
unsafe fn stored_field(
&self,
store: LLVMValueRef,
operand: LLVMUseRef,
path: &WrapPath,
) -> Option<FieldEvidence> {
unsafe {
if LLVMGetOperandUse(store, 0) != operand
|| path.iter().any(|&(wrapper, _)| is_aggregate(wrapper))
{
return None;
}
self.access_field(store, LLVMGetOperand(store, 1))
}
}
unsafe fn initializer_field(
&self,
global: LLVMValueRef,
path: &WrapPath,
) -> Option<FieldEvidence> {
unsafe {
let lowest = path
.iter()
.position(|&(wrapper, _)| is_aggregate(wrapper))?;
let aggregates = &path[lowest..];
if !aggregates.iter().all(|&(wrapper, _)| is_aggregate(wrapper)) {
return None;
}
let indices = aggregates
.iter()
.map(|&(wrapper, operand)| operand_index(wrapper, operand).map(i64::from))
.collect::<Option<Vec<_>>>()?;
let below = |level: usize| indices[..=level].iter().rev().copied().collect::<Vec<_>>();
let is_struct = |wrapper| !LLVMIsAConstantStruct(wrapper).is_null();
if let Some(level) = aggregates.iter().position(|&(wrapper, _)| {
is_struct(wrapper) && struct_record(LLVMTypeOf(wrapper)).is_some()
}) {
let ty = LLVMTypeOf(aggregates[level].0);
let field = self.walk_field(ty, &below(level), None)?;
return Some(self.evidence(field, FieldBasis::Initializer));
}
let level = aggregates
.iter()
.position(|&(wrapper, _)| is_struct(wrapper))?;
let above = &aggregates[level + 1..];
if !above
.iter()
.all(|&(wrapper, _)| !LLVMIsAConstantArray(wrapper).is_null())
{
return None;
}
let ty = LLVMTypeOf(aggregates[level].0);
let record = self.debug_info_record(global, above.len(), ty)?;
let field = self.walk_field(ty, &below(level), Some(record))?;
Some(self.evidence(field, FieldBasis::DebugInfo))
}
}
unsafe fn global_debug_type(&self, global: LLVMValueRef) -> Option<LLVMMetadataRef> {
unsafe {
let mut count = 0;
let entries = LLVMGlobalCopyAllMetadata(global, &mut count);
if entries.is_null() {
return None;
}
let expression = (0..count as u32)
.find(|&index| LLVMValueMetadataEntriesGetKind(entries, index) == self.dbg_kind)
.map(|index| LLVMValueMetadataEntriesGetMetadata(entries, index));
LLVMDisposeValueMetadataEntries(entries);
let expression = expression.filter(|&expression| {
matches!(
LLVMGetMetadataKind(expression),
LLVMMetadataKind::LLVMDIGlobalVariableExpressionMetadataKind
)
})?;
let variable = LLVMDIGlobalVariableExpressionGetVariable(expression);
if variable.is_null()
|| !matches!(
LLVMGetMetadataKind(variable),
LLVMMetadataKind::LLVMDIGlobalVariableMetadataKind
)
{
return None;
}
self.node_operand(variable, DI_VARIABLE_TYPE_OPERAND)
}
}
unsafe fn debug_info_record(
&self,
global: LLVMValueRef,
mut arrays: usize,
literal: LLVMTypeRef,
) -> Option<String> {
unsafe {
let mut ty = self.global_debug_type(global)?;
let mut typedef = None;
for _ in 0..MAX_DEBUG_TYPE_DEPTH {
match LLVMGetMetadataKind(ty) {
LLVMMetadataKind::LLVMDIDerivedTypeMetadataKind => match LLVMGetDINodeTag(ty) {
DW_TAG_TYPEDEF => typedef = debug_type_name(ty),
DW_TAG_CONST_TYPE | DW_TAG_VOLATILE_TYPE => {}
_ => return None,
},
LLVMMetadataKind::LLVMDICompositeTypeMetadataKind => match LLVMGetDINodeTag(ty)
{
DW_TAG_ARRAY_TYPE => {
let dimensions = self
.node_operand(ty, DI_COMPOSITE_ELEMENTS_OPERAND)
.map_or(0, |elements| self.node_elements(elements).len())
.max(1);
arrays = arrays.checked_sub(dimensions)?;
typedef = None;
}
tag if DW_RECORD_TAGS.contains(&tag) => {
let size = LLVMDITypeGetSizeInBits(ty);
if arrays != 0 || size != LLVMABISizeOfType(self.layout, literal) * 8 {
return None;
}
return self.debug_record_name(ty, typedef);
}
_ => return None,
},
_ => return None,
}
ty = self.node_operand(ty, DI_BASE_TYPE_OPERAND)?;
}
None
}
}
unsafe fn debug_record_name(
&self,
record: LLVMMetadataRef,
typedef: Option<String>,
) -> Option<String> {
unsafe {
let operands = md_node_operands(LLVMMetadataAsValue(self.context, record));
if let Some(identifier) = operands
.get(DI_COMPOSITE_IDENTIFIER_OPERAND)
.and_then(|&identifier| operand_string(identifier))
{
return normalize_record(&identifier);
}
let at_file_scope = self
.node_operand(record, DI_SCOPE_OPERAND)
.is_none_or(|scope| {
matches!(
LLVMGetMetadataKind(scope),
LLVMMetadataKind::LLVMDIFileMetadataKind
| LLVMMetadataKind::LLVMDICompileUnitMetadataKind
)
});
if !at_file_scope {
return None;
}
debug_type_name(record)
.and_then(|name| normalize_record(&name))
.or(typedef)
}
}
unsafe fn node_operand(&self, node: LLVMMetadataRef, index: usize) -> Option<LLVMMetadataRef> {
unsafe {
let operands = md_node_operands(LLVMMetadataAsValue(self.context, node));
operand_metadata(*operands.get(index)?)
}
}
unsafe fn node_elements(&self, tuple: LLVMMetadataRef) -> Vec<LLVMMetadataRef> {
unsafe {
if !matches!(
LLVMGetMetadataKind(tuple),
LLVMMetadataKind::LLVMMDTupleMetadataKind
) {
return Vec::new();
}
md_node_operands(LLVMMetadataAsValue(self.context, tuple))
.into_iter()
.filter_map(|operand| operand_metadata(operand))
.collect()
}
}
unsafe fn read_member_names(&self, module: LLVMModuleRef) -> HashMap<FieldRef, Option<String>> {
let mut pending: Vec<(LLVMMetadataRef, Option<String>)> = Vec::new();
unsafe {
let mut function = LLVMGetFirstFunction(module);
while !function.is_null() {
let subprogram = LLVMGetSubprogram(function);
if LLVMIsDeclaration(function) == 0
&& !subprogram.is_null()
&& matches!(
LLVMGetMetadataKind(subprogram),
LLVMMetadataKind::LLVMDISubprogramMetadataKind
)
&& let Some(ty) = self.node_operand(subprogram, SUBPROGRAM_TYPE_OPERAND)
{
pending.push((ty, None));
}
function = LLVMGetNextFunction(function);
}
let mut global = LLVMGetFirstGlobal(module);
while !global.is_null() {
if let Some(ty) = self.global_debug_type(global) {
pending.push((ty, None));
}
global = LLVMGetNextGlobal(global);
}
}
let mut names: HashMap<FieldRef, Option<String>> = HashMap::new();
let mut visited = HashSet::new();
while let Some((ty, typedef)) = pending.pop() {
if !visited.insert(ty) {
continue;
}
unsafe {
let base = || self.node_operand(ty, DI_BASE_TYPE_OPERAND);
match LLVMGetMetadataKind(ty) {
LLVMMetadataKind::LLVMDISubroutineTypeMetadataKind => {
if let Some(types) = self.node_operand(ty, DI_SUBROUTINE_TYPES_OPERAND) {
pending
.extend(self.node_elements(types).into_iter().map(|ty| (ty, None)));
}
}
LLVMMetadataKind::LLVMDIDerivedTypeMetadataKind => {
let typedef = (LLVMGetDINodeTag(ty) == DW_TAG_TYPEDEF)
.then(|| debug_type_name(ty))
.flatten();
pending.extend(base().map(|base| (base, typedef)));
}
LLVMMetadataKind::LLVMDICompositeTypeMetadataKind => {
let tag = LLVMGetDINodeTag(ty);
if !DW_RECORD_TAGS.contains(&tag) {
pending.extend(base().map(|base| (base, None)));
continue;
}
let record = self.debug_record_name(ty, typedef);
let members = self
.node_operand(ty, DI_COMPOSITE_ELEMENTS_OPERAND)
.map(|elements| self.node_elements(elements))
.unwrap_or_default();
for member in members {
if !matches!(
LLVMGetMetadataKind(member),
LLVMMetadataKind::LLVMDIDerivedTypeMetadataKind
) {
continue;
}
pending.extend(
self.node_operand(member, DI_BASE_TYPE_OPERAND)
.map(|base| (base, None)),
);
let is_field = LLVMGetDINodeTag(member) == DW_TAG_MEMBER
&& LLVMDITypeGetFlags(member) & LLVMDIFlagStaticMember == 0;
if let (true, Some(record), Some(name)) =
(is_field, &record, debug_type_name(member))
{
let field = FieldRef {
record: record.clone(),
offset: LLVMDITypeGetOffsetInBits(member) / 8,
};
names
.entry(field)
.and_modify(|known| {
if known.as_deref() != Some(name.as_str()) {
*known = None;
}
})
.or_insert(Some(name));
}
}
}
_ => {}
}
}
}
names
}
}
unsafe fn debug_type_name(ty: LLVMMetadataRef) -> Option<String> {
let mut length = 0;
let name = unsafe { owned(LLVMDITypeGetName(ty, &mut length), length) };
(!name.is_empty()).then_some(name)
}
const WRAPPING_CONSTANTS: [unsafe extern "C" fn(LLVMValueRef) -> LLVMValueRef; 4] = [
LLVMIsAConstantExpr,
LLVMIsAConstantStruct,
LLVMIsAConstantArray,
LLVMIsAConstantVector,
];
struct Occurrence {
used: LLVMValueRef,
user: LLVMValueRef,
operand: LLVMUseRef,
path: WrapPath,
}
unsafe fn users_of(value: LLVMValueRef, path: &WrapPath) -> Vec<Occurrence> {
let mut users = Vec::new();
let mut current = unsafe { LLVMGetFirstUse(value) };
while !current.is_null() {
let user = unsafe { LLVMGetUser(current) };
if !user.is_null() {
users.push(Occurrence {
used: value,
user,
operand: current,
path: path.clone(),
});
}
current = unsafe { LLVMGetNextUse(current) };
}
users
}
unsafe fn collect_uses(
function: LLVMValueRef,
id: &FunctionId,
module_id: &str,
fields: &FieldReader,
uses: &mut Vec<UseFact>,
) {
let mut pending: VecDeque<_> = unsafe { users_of(function, &Vec::new()) }.into();
while let Some(occurrence) = pending.pop_front() {
let Occurrence {
used,
user,
operand,
path,
} = occurrence;
let is_instruction = !unsafe { LLVMIsAInstruction(user) }.is_null();
let opcode = is_instruction.then(|| unsafe { LLVMGetInstructionOpcode(user) });
let is_call = matches!(opcode, Some(LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke));
if is_call && unsafe { LLVMGetCalledValue(user) } == used {
continue;
}
let is_global = !unsafe { LLVMIsAGlobalVariable(user) }.is_null();
let wraps = !is_instruction
&& !is_global
&& WRAPPING_CONSTANTS
.iter()
.any(|is_a| !unsafe { is_a(user) }.is_null());
if wraps {
let mut path = path;
path.push((user, operand));
pending.extend(unsafe { users_of(user, &path) });
continue;
}
let kind = match opcode {
Some(LLVMOpcode::LLVMStore) => UseKind::StoredToMemory,
Some(LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke) => UseKind::PassedAsArgument,
Some(LLVMOpcode::LLVMRet) => UseKind::ReturnedValue,
Some(_) => UseKind::Other,
None if is_global => UseKind::GlobalInitializer,
None => UseKind::Other,
};
let field = match kind {
UseKind::StoredToMemory => unsafe { fields.stored_field(user, operand, &path) },
UseKind::GlobalInitializer => unsafe { fields.initializer_field(user, &path) },
_ => None,
};
uses.push(UseFact {
used: id.clone(),
in_function: is_instruction
.then(|| unsafe { enclosing_function(user, module_id) })
.flatten(),
in_global: is_global.then(|| unsafe { value_name(user) }),
location: is_instruction
.then(|| unsafe { location_of(user) })
.flatten(),
kind,
field,
});
}
}
fn parse_error(module: &LoadedModule, diagnostics: &[String]) -> Error {
let reader = llvm_version();
let detail = if diagnostics.is_empty() {
String::new()
} else {
format!(" ({})", diagnostics.join("; "))
};
match module.record.compiler.as_ref() {
Some(compiler) => Error::InvalidArguments(format!(
"module {} was produced by {} and cannot be read by LLVM {reader}{detail}",
module.id, compiler.version
)),
None => Error::InvalidArguments(format!(
"module {} cannot be read by LLVM {reader}{detail}",
module.id
)),
}
}
pub fn extract_neutral(module: &LoadedModule) -> Result<ModuleFacts, Error> {
unsafe { extract_inner(module) }
}
pub fn extract(
module: &LoadedModule,
source_status: &HashMap<(String, PathBuf), SourceState>,
) -> Result<ModuleFacts, Error> {
let mut facts = extract_neutral(module)?;
facts.bind_to_catalog(&module.id, source_status);
Ok(facts)
}
unsafe fn extract_inner(module: &LoadedModule) -> Result<ModuleFacts, Error> {
let sink = DiagnosticSink::new();
let context = Context(unsafe { LLVMContextCreate() });
unsafe { LLVMContextSetDiagnosticHandler(context.0, Some(collect_diagnostic), sink.0.cast()) };
let bytes = &module.bytes;
let buffer = Buffer(unsafe {
LLVMCreateMemoryBufferWithMemoryRange(
bytes.as_ptr().cast::<c_char>(),
bytes.len(),
c"rllvm-module".as_ptr(),
0,
)
});
let mut parsed: LLVMModuleRef = std::ptr::null_mut();
let failed = unsafe { LLVMParseBitcodeInContext2(context.0, buffer.0, &mut parsed) } != 0;
if failed || parsed.is_null() {
return Err(parse_error(module, &sink.take()));
}
let parsed = ParsedModule(parsed);
let mut pass_diagnostics = Vec::new();
unsafe {
let options = LLVMCreatePassBuilderOptions();
let error = LLVMRunPasses(
parsed.0,
c"called-value-propagation".as_ptr(),
std::ptr::null_mut(),
options,
);
LLVMDisposePassBuilderOptions(options);
if !error.is_null() {
let message = owned_error_message(error);
pass_diagnostics.push(format!("called-value-propagation did not run: {message}"));
}
}
let callees_kind = unsafe { LLVMGetMDKindIDInContext(context.0, c"callees".as_ptr(), 7) };
let fields = unsafe { FieldReader::new(context.0, parsed.0) };
let producers = unsafe { read_producers(parsed.0) };
let module_language = producer_language(&producers);
let mut units = HashMap::new();
let mut functions = Vec::new();
let mut call_sites = Vec::new();
let mut uses = Vec::new();
let mut function = unsafe { LLVMGetFirstFunction(parsed.0) };
while !function.is_null() {
let id = FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: unsafe { value_name(function) },
};
let mut mapped_lines = BTreeSet::new();
let mut block_index = 0u32;
let mut block = unsafe { LLVMGetFirstBasicBlock(function) };
while !block.is_null() {
let mut instruction_index = 0u32;
let mut instruction = unsafe { LLVMGetFirstInstruction(block) };
while !instruction.is_null() {
let location = unsafe { location_of(instruction) };
if let Some(location) = &location {
mapped_lines.insert((location.file.clone(), location.line));
}
let opcode = unsafe { LLVMGetInstructionOpcode(instruction) };
if matches!(opcode, LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke) {
call_sites.push(CallSiteFact {
id: CallSiteId {
function: id.clone(),
block_index,
instruction_index,
},
location,
target: unsafe {
call_target(instruction, NEUTRAL_MODULE_ID, callees_kind, &fields)
},
});
}
instruction = unsafe { LLVMGetNextInstruction(instruction) };
instruction_index += 1;
}
block = unsafe { LLVMGetNextBasicBlock(block) };
block_index += 1;
}
let is_definition = unsafe { LLVMIsDeclaration(function) } == 0;
functions.push(FunctionFact {
id: id.clone(),
is_definition,
linkage: linkage_of(unsafe { LLVMGetLinkage(function) }),
language: if is_definition {
unsafe { definition_language(context.0, function, &mut units, module_language) }
} else {
None
},
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGlobalGetValueType(function)))
},
location: unsafe { location_of(function) },
mapped_lines,
alias_of: None,
});
unsafe { collect_uses(function, &id, NEUTRAL_MODULE_ID, &fields, &mut uses) };
function = unsafe { LLVMGetNextFunction(function) };
}
let mut alias = unsafe { LLVMGetFirstGlobalAlias(parsed.0) };
while !alias.is_null() {
if let Some(target) = unsafe { alias_target(alias) } {
functions.push(FunctionFact {
id: FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: unsafe { value_name(alias) },
},
is_definition: true,
linkage: linkage_of(unsafe { LLVMGetLinkage(alias) }),
language: unsafe {
definition_language(context.0, target, &mut units, module_language)
},
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGlobalGetValueType(alias)))
},
location: unsafe { location_of(target) },
mapped_lines: BTreeSet::new(),
alias_of: Some(FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: unsafe { value_name(target) },
}),
});
}
alias = unsafe { LLVMGetNextGlobalAlias(alias) };
}
drop(parsed);
drop(buffer);
drop(context);
let mut diagnostics = pass_diagnostics;
diagnostics.extend(sink.take());
Ok(ModuleFacts {
functions,
call_sites,
uses,
producers,
diagnostics,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn producers_decide_a_module_only_when_they_agree() {
let rustc = "rustc version 1.98.0".to_string();
let clang = "clang version 23.1.1".to_string();
assert_eq!(
producer_language(std::slice::from_ref(&rustc)),
Some(Language::Rust)
);
assert_eq!(
producer_language(std::slice::from_ref(&clang)),
Some(Language::Other)
);
assert_eq!(producer_language(&[rustc, clang]), None, "merged");
assert_eq!(producer_language(&[]), None, "no producer named");
}
#[test]
fn a_printed_unit_names_its_language_under_either_key() {
let unit = |text: &str| format!("distinct !DICompileUnit({text}, file: !1)");
assert_eq!(
dwarf_language(&unit("language: DW_LANG_Rust")),
Some(Language::Rust)
);
assert_eq!(
dwarf_language(&unit("language: DW_LANG_C11")),
Some(Language::Other)
);
assert_eq!(
dwarf_language(&unit("sourceLanguageName: DW_LNAME_Rust")),
Some(Language::Rust)
);
assert_eq!(dwarf_language("distinct !DICompileUnit(file: !1)"), None);
}
#[test]
fn demangling_reads_cxx_names_and_refuses_everything_else() {
assert_eq!(
demangle("_Z5twiceIiET_S0_").as_deref(),
Some("int twice<int>(int)"),
"the template instantiation from the ODR repro in #184"
);
assert_eq!(demangle("_ZN3FooC1Ev").as_deref(), Some("Foo::Foo()"));
assert_eq!(demangle("main"), None, "a C name is not mangled");
assert_eq!(demangle(""), None);
assert_eq!(
demangle("_Znotreallymangled"),
None,
"a name that only looks mangled must not produce a guess"
);
assert_eq!(
demangle("_RNvC6foo3bar"),
None,
"malformed v0: a crate root carries a disambiguator"
);
}
#[test]
fn demangling_reads_rust_v0_names() {
assert_eq!(
demangle("_RNvMs2_NtCs8f0ESrtUyjS_6quiche3tlsNtB5_9Handshake12do_handshake").as_deref(),
Some("<quiche::tls::Handshake>::do_handshake"),
);
assert!(
demangle("_ZN4core3fmt5Debug3fmt17h0123456789abcdefE")
.is_some_and(|name| name.contains("core::fmt")),
);
}
#[test]
fn record_names_normalize_across_ir_spellings() {
for (raw, expected) in [
("struct.ops", Some("ops")),
("struct.ops.12", Some("ops")),
("struct.n::S", Some("n::S")),
("_ZTSN1n1SE", Some("n::S")),
("_ZTS6ns_ops", Some("ns_ops")),
("ops", Some("ops")),
("", None),
("struct.anon", None),
("union.anon.1", None),
] {
assert_eq!(normalize_record(raw).as_deref(), expected, "{raw:?}");
}
}
#[test]
fn a_legacy_rust_symbol_reads_back_with_its_hash() {
assert_eq!(
demangle("_ZN4core3fmt5write17h1234567890abcdefE").as_deref(),
Some("core::fmt::write::h1234567890abcdef")
);
}
}