use std::{
collections::{BTreeSet, HashMap, HashSet, VecDeque},
ffi::{CStr, c_char, c_int, c_void},
marker::PhantomData,
path::PathBuf,
};
use llvm_sys::{
LLVMAtomicOrdering, LLVMDbgRecordKind, LLVMDiagnosticSeverity, LLVMIntPredicate, LLVMLinkage,
LLVMOpcode, LLVMTypeKind,
bit_reader::LLVMParseBitcodeInContext2,
core::*,
debuginfo::{
LLVMDIFileGetDirectory, LLVMDIFileGetFilename, LLVMDIFlagStaticMember,
LLVMDIGlobalVariableExpressionGetVariable, LLVMDILocationGetColumn,
LLVMDILocationGetInlinedAt, LLVMDILocationGetLine, LLVMDILocationGetScope,
LLVMDIScopeGetFile, LLVMDITypeGetFlags, LLVMDITypeGetName, LLVMDITypeGetOffsetInBits,
LLVMDITypeGetSizeInBits, LLVMDIVariableGetFile, LLVMDIVariableGetLine,
LLVMDIVariableGetScope, LLVMGetDINodeTag, LLVMGetMetadataKind, LLVMGetSubprogram,
LLVMInstructionGetDebugLoc, LLVMMetadataKind,
},
error::{LLVMDisposeErrorMessage, LLVMErrorRef, LLVMGetErrorMessage},
prelude::*,
target::{
LLVMABISizeOfType, LLVMGetModuleDataLayout, LLVMOffsetOfElement, LLVMPointerSizeForAS,
LLVMTargetDataRef,
},
transforms::pass_builder::{
LLVMCreatePassBuilderOptions, LLVMDisposePassBuilderOptions, LLVMRunPasses,
},
};
use rllvm_core::error::Error;
use crate::{
facts::*,
ir::*,
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);
fn LLVMDbgVariableRecordGetExpression(record: LLVMDbgRecordRef) -> LLVMMetadataRef;
}
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 slot_flows: Vec<SlotFlow>,
pub field_flows: Vec<FieldFlow>,
pub returned_addresses: Vec<ReturnedAddress>,
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);
}
let stamp_slot = |slot: &mut Slot| match slot {
Slot::Global {
module_id: Some(id),
..
} => *id = module_id.to_string(),
Slot::Global {
module_id: None, ..
} => {}
Slot::Param { function, .. } | Slot::Returned { function } => stamp(function),
};
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,
via_slot,
..
} => {
llvm_target_bound.iter_mut().flatten().for_each(stamp);
via_slot.iter_mut().for_each(stamp_slot);
}
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);
}
use_fact.into_slot.iter_mut().for_each(stamp_slot);
resolve(&mut use_fact.location);
}
for flow in &mut self.slot_flows {
stamp_slot(&mut flow.from);
stamp_slot(&mut flow.into);
}
for flow in &mut self.field_flows {
stamp_slot(&mut flow.from);
}
for returned in &mut self.returned_addresses {
stamp(&mut returned.function);
returned.global.iter_mut().for_each(stamp_slot);
}
}
}
fn resolve_status(
location: &mut SourceLocation,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) {
let state = source_status
.get(&(module_id.to_string(), location.resolved_path()))
.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) });
}
}
struct Parsed<'bytes> {
module: ParsedModule,
buffer: Buffer,
context: Context,
sink: DiagnosticSink,
bytes: PhantomData<&'bytes [u8]>,
}
impl Parsed<'_> {
fn finish(self) -> Vec<String> {
let Self {
module,
buffer,
context,
sink,
..
} = self;
drop(module);
drop(buffer);
drop(context);
sink.take()
}
}
unsafe fn parse_bitcode(bytes: &[u8]) -> Result<Parsed<'_>, Vec<String>> {
let sink = DiagnosticSink::new();
let context = Context(unsafe { LLVMContextCreate() });
unsafe { LLVMContextSetDiagnosticHandler(context.0, Some(collect_diagnostic), sink.0.cast()) };
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() {
drop(buffer);
drop(context);
return Err(sink.take());
}
Ok(Parsed {
module: ParsedModule(parsed),
buffer,
context,
sink,
bytes: PhantomData,
})
}
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 named_callee(called: LLVMValueRef) -> Option<String> {
unsafe {
let named = !LLVMIsAFunction(called).is_null()
|| (!LLVMIsAGlobalAlias(called).is_null() && alias_target(called).is_some());
named.then(|| value_name(called))
}
}
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 let Some(symbol) = unsafe { named_callee(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 { LLVMIsAInlineAsm(called) }.is_null() {
return CallTarget::InlineAsm;
}
}
let (via_field, via_slot) = if called.is_null() {
(None, None)
} else {
unsafe { trace_source(called, module_id, Some(fields)) }
};
CallTarget::Indirect {
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGetCalledFunctionType(
instruction,
)))
},
llvm_target_bound: unsafe { indirect_target_bound(instruction, callees_kind, module_id) },
via_field,
via_slot,
}
}
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 MAX_SLOT_TRACE_STEPS: usize = 8;
unsafe fn strip_pointer_casts(mut value: LLVMValueRef) -> LLVMValueRef {
loop {
let is_cast = unsafe {
!LLVMIsABitCastInst(value).is_null()
|| !LLVMIsAAddrSpaceCastInst(value).is_null()
|| (!LLVMIsAConstantExpr(value).is_null()
&& matches!(
LLVMGetConstOpcode(value),
LLVMOpcode::LLVMBitCast | LLVMOpcode::LLVMAddrSpaceCast
))
};
if !is_cast {
return value;
}
value = unsafe { LLVMGetOperand(value, 0) };
}
}
unsafe fn global_slot(global: LLVMValueRef, module_id: &str) -> Slot {
let local = matches!(
unsafe { LLVMGetLinkage(global) },
LLVMLinkage::LLVMInternalLinkage | LLVMLinkage::LLVMPrivateLinkage
);
Slot::Global {
module_id: local.then(|| module_id.to_string()),
name: unsafe { value_name(global) },
}
}
unsafe fn param_slot(argument: LLVMValueRef, module_id: &str) -> Option<Slot> {
unsafe {
let function = LLVMGetParamParent(argument);
let index =
(0..LLVMCountParams(function)).find(|&i| LLVMGetParam(function, i) == argument)?;
Some(Slot::Param {
function: FunctionId {
module_id: module_id.to_string(),
symbol: value_name(function),
},
index,
})
}
}
unsafe fn is_lifetime_marker(user: LLVMValueRef) -> bool {
unsafe {
if LLVMIsACallInst(user).is_null() {
return false;
}
let called = LLVMGetCalledValue(user);
!called.is_null()
&& !LLVMIsAFunction(called).is_null()
&& value_name(called).starts_with("llvm.lifetime.")
}
}
unsafe fn stored_values(alloca: LLVMValueRef) -> Option<Vec<LLVMValueRef>> {
let mut stored = Vec::new();
unsafe {
let mut current = LLVMGetFirstUse(alloca);
while !current.is_null() {
let user = LLVMGetUser(current);
if !LLVMIsAStoreInst(user).is_null() {
if LLVMGetOperand(user, 1) != alloca {
return None;
}
stored.push(LLVMGetOperand(user, 0));
} else if LLVMIsALoadInst(user).is_null() && !is_lifetime_marker(user) {
return None;
}
current = LLVMGetNextUse(current);
}
}
Some(stored)
}
unsafe fn sole_stored_value(alloca: LLVMValueRef) -> Option<LLVMValueRef> {
match unsafe { stored_values(alloca) }?.as_slice() {
[value] => Some(*value),
_ => None,
}
}
unsafe fn direct_callee(value: LLVMValueRef, module_id: &str) -> Option<FunctionId> {
unsafe {
if LLVMIsACallInst(value).is_null() && LLVMIsAInvokeInst(value).is_null() {
return None;
}
let called = LLVMGetCalledValue(value);
if called.is_null() || LLVMIsAFunction(called).is_null() {
return None;
}
let symbol = value_name(called);
(!symbol.starts_with("llvm.")).then(|| FunctionId {
module_id: module_id.to_string(),
symbol,
})
}
}
unsafe fn addressed_slot(pointer: LLVMValueRef, module_id: &str) -> Option<Slot> {
unsafe {
let pointer = strip_pointer_casts(pointer);
if !LLVMIsAGlobalVariable(pointer).is_null() {
return Some(global_slot(pointer, module_id));
}
direct_callee(pointer, module_id).map(|function| Slot::Returned { function })
}
}
unsafe fn trace_source(
value: LLVMValueRef,
module_id: &str,
fields: Option<&FieldReader>,
) -> (Option<FieldEvidence>, Option<Slot>) {
unsafe {
let mut value = strip_pointer_casts(value);
for _ in 0..MAX_SLOT_TRACE_STEPS {
if !LLVMIsAArgument(value).is_null() {
return (None, param_slot(value, module_id));
}
if let Some(field) = fields.and_then(|fields| fields.loaded_field(value)) {
return (Some(field), None);
}
if LLVMIsALoadInst(value).is_null() {
break;
}
if let Some(slot) = addressed_slot(LLVMGetOperand(value, 0), module_id) {
return (None, Some(slot));
}
let pointer = strip_pointer_casts(LLVMGetOperand(value, 0));
if LLVMIsAAllocaInst(pointer).is_null() {
break;
}
let Some(stored) = sole_stored_value(pointer) else {
break;
};
value = strip_pointer_casts(stored);
}
}
(None, None)
}
unsafe fn traced_slot(value: LLVMValueRef, module_id: &str) -> Option<Slot> {
unsafe { trace_source(value, module_id, None) }.1
}
unsafe fn is_pointer(value: LLVMValueRef) -> bool {
unsafe { LLVMGetTypeKind(LLVMTypeOf(value)) == LLVMTypeKind::LLVMPointerTypeKind }
}
#[derive(Default)]
struct Flows {
slots: Vec<SlotFlow>,
fields: Vec<FieldFlow>,
returned: Vec<ReturnedAddress>,
}
unsafe fn collect_flows(
instruction: LLVMValueRef,
module_id: &str,
fields: &FieldReader,
flows: &mut Flows,
) {
unsafe {
match LLVMGetInstructionOpcode(instruction) {
LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke => {
let Some(callee) = direct_callee(instruction, module_id) else {
return;
};
for index in 0..LLVMGetNumArgOperands(instruction) {
let argument = LLVMGetOperand(instruction, index);
if !is_pointer(argument) {
continue;
}
if let Some(from) = traced_slot(argument, module_id) {
flows.slots.push(SlotFlow {
from,
into: Slot::Param {
function: callee.clone(),
index,
},
});
}
}
}
LLVMOpcode::LLVMStore => {
let value = LLVMGetOperand(instruction, 0);
if !is_pointer(value) {
return;
}
let Some(from) = traced_slot(value, module_id) else {
return;
};
let pointer = LLVMGetOperand(instruction, 1);
if let Some(into) = addressed_slot(pointer, module_id) {
flows.slots.push(SlotFlow { from, into });
} else if let Some(into) = fields.access_field(instruction, pointer) {
flows.fields.push(FieldFlow { from, into });
}
}
LLVMOpcode::LLVMRet if LLVMGetNumOperands(instruction) == 1 => {
let value = LLVMGetOperand(instruction, 0);
if !is_pointer(value) {
return;
}
let Some(function) = enclosing_function(instruction, module_id) else {
return;
};
let mut pending = vec![(value, 0)];
while let Some((value, depth)) = pending.pop() {
let value = strip_pointer_casts(value);
if !LLVMIsAGlobalVariable(value).is_null() {
flows.returned.push(ReturnedAddress {
function: function.clone(),
global: Some(global_slot(value, module_id)),
field: None,
});
} else if let Some(field) = fields.gep_field(value) {
flows.returned.push(ReturnedAddress {
function: function.clone(),
global: None,
field: Some(fields.evidence(field, FieldBasis::StructGep)),
});
} else if depth < MAX_SLOT_TRACE_STEPS && !LLVMIsALoadInst(value).is_null() {
let pointer = strip_pointer_casts(LLVMGetOperand(value, 0));
if LLVMIsAAllocaInst(pointer).is_null() {
continue;
}
for stored in stored_values(pointer).into_iter().flatten() {
pending.push((stored, depth + 1));
}
}
}
}
_ => {}
}
}
}
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_NAME_OPERAND: usize = 1;
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 direct = !path
.iter()
.any(|&(wrapper, _)| unsafe { is_aggregate(wrapper) });
let into_slot = direct
.then(|| unsafe { use_slot(user, operand, kind, module_id) })
.flatten();
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,
into_slot,
});
}
}
unsafe fn use_slot(
user: LLVMValueRef,
operand: LLVMUseRef,
kind: UseKind,
module_id: &str,
) -> Option<Slot> {
unsafe {
match kind {
UseKind::StoredToMemory => (LLVMGetOperandUse(user, 0) == operand)
.then(|| addressed_slot(LLVMGetOperand(user, 1), module_id))
.flatten(),
UseKind::GlobalInitializer => Some(global_slot(user, module_id)),
UseKind::PassedAsArgument => {
let function = direct_callee(user, module_id)?;
let index = operand_index(user, operand)?;
(index < LLVMGetNumArgOperands(user)).then_some(Slot::Param { function, index })
}
UseKind::ReturnedValue | UseKind::Other => None,
}
}
}
fn parse_error(module: &LoadedModule, diagnostics: &[String]) -> Error {
let subject = match module.record.compiler.as_ref() {
Some(compiler) => format!(
"module {} was produced by {} and",
module.id, compiler.version
),
None => format!("module {}", module.id),
};
unreadable(&subject, diagnostics)
}
fn unreadable(subject: &str, diagnostics: &[String]) -> Error {
let reader = llvm_version();
let detail = if diagnostics.is_empty() {
String::new()
} else {
format!(" ({})", diagnostics.join("; "))
};
Error::InvalidArguments(format!("{subject} cannot be read by LLVM {reader}{detail}"))
}
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 parsed = unsafe { parse_bitcode(&module.bytes) }
.map_err(|diagnostics| parse_error(module, &diagnostics))?;
let mut pass_diagnostics = Vec::new();
unsafe {
let options = LLVMCreatePassBuilderOptions();
let error = LLVMRunPasses(
parsed.module.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(parsed.context.0, c"callees".as_ptr(), 7) };
let fields = unsafe { FieldReader::new(parsed.context.0, parsed.module.0) };
let producers = unsafe { read_producers(parsed.module.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 flows = Flows::default();
let mut function = unsafe { LLVMGetFirstFunction(parsed.module.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)
},
});
}
unsafe { collect_flows(instruction, NEUTRAL_MODULE_ID, &fields, &mut flows) };
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(parsed.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.module.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(parsed.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) };
}
let mut diagnostics = pass_diagnostics;
diagnostics.extend(parsed.finish());
Ok(ModuleFacts {
functions,
call_sites,
uses,
slot_flows: flows.slots,
field_flows: flows.fields,
returned_addresses: flows.returned,
producers,
diagnostics,
})
}
const GUARD_INTRINSICS: [(&str, GuardKind, &str); 8] = [
("llvm.umin.", GuardKind::Clamp, "umin"),
("llvm.umax.", GuardKind::Clamp, "umax"),
("llvm.smin.", GuardKind::Clamp, "smin"),
("llvm.smax.", GuardKind::Clamp, "smax"),
("llvm.uadd.sat.", GuardKind::Saturate, "uadd.sat"),
("llvm.usub.sat.", GuardKind::Saturate, "usub.sat"),
("llvm.sadd.sat.", GuardKind::Saturate, "sadd.sat"),
("llvm.ssub.sat.", GuardKind::Saturate, "ssub.sat"),
];
const LLVM_PREDICATES: [(LLVMIntPredicate, &str); 10] = [
(LLVMIntPredicate::LLVMIntEQ, "eq"),
(LLVMIntPredicate::LLVMIntNE, "ne"),
(LLVMIntPredicate::LLVMIntUGT, "ugt"),
(LLVMIntPredicate::LLVMIntUGE, "uge"),
(LLVMIntPredicate::LLVMIntULT, "ult"),
(LLVMIntPredicate::LLVMIntULE, "ule"),
(LLVMIntPredicate::LLVMIntSGT, "sgt"),
(LLVMIntPredicate::LLVMIntSGE, "sge"),
(LLVMIntPredicate::LLVMIntSLT, "slt"),
(LLVMIntPredicate::LLVMIntSLE, "sle"),
];
const EMPTY_EXPRESSION: &str = "!DIExpression()";
pub(crate) fn read_module_ir(bytes: &[u8]) -> Result<ModuleIr, Error> {
unsafe { read_module_ir_inner(bytes) }
}
unsafe fn read_module_ir_inner(bytes: &[u8]) -> Result<ModuleIr, Error> {
let parsed = unsafe { parse_bitcode(bytes) }
.map_err(|diagnostics| unreadable("bitcode", &diagnostics))?;
let mut functions = Vec::new();
let mut numbers = DebugNumbers::default();
let mut function = unsafe { LLVMGetFirstFunction(parsed.module.0) };
while !function.is_null() {
if unsafe { LLVMIsDeclaration(function) } == 0 {
functions.push(unsafe { read_function_ir(parsed.context.0, function, &mut numbers) });
}
function = unsafe { LLVMGetNextFunction(function) };
}
Ok(ModuleIr {
functions,
diagnostics: parsed.finish(),
})
}
#[derive(Default)]
struct DebugNumbers {
variables: HashMap<LLVMMetadataRef, u32>,
scopes: HashMap<LLVMMetadataRef, ScopeId>,
parents: HashMap<ScopeId, ScopeId>,
subprograms: HashSet<ScopeId>,
}
#[derive(Default)]
struct FunctionScopes {
links: HashMap<ScopeId, ScopeId>,
subprograms: HashSet<ScopeId>,
}
impl DebugNumbers {
unsafe fn scope(
&mut self,
context: LLVMContextRef,
scope: LLVMMetadataRef,
function: &mut FunctionScopes,
) -> Option<ScopeId> {
if scope.is_null() {
return None;
}
let id = match self.scopes.get(&scope) {
Some(&id) => id,
None => {
let id = self.scopes.len() as ScopeId;
self.scopes.insert(scope, id);
if matches!(
unsafe { LLVMGetMetadataKind(scope) },
LLVMMetadataKind::LLVMDISubprogramMetadataKind
) {
self.subprograms.insert(id);
}
let parent = unsafe {
matches!(
LLVMGetMetadataKind(scope),
LLVMMetadataKind::LLVMDILexicalBlockMetadataKind
| LLVMMetadataKind::LLVMDILexicalBlockFileMetadataKind
)
.then(|| {
let operands = md_node_operands(LLVMMetadataAsValue(context, scope));
operand_metadata(*operands.get(DI_SCOPE_OPERAND)?)
})
.flatten()
.and_then(|parent| self.scope(context, parent, function))
};
if let Some(parent) = parent {
self.parents.insert(id, parent);
}
id
}
};
let mut at = id;
while let Some(&parent) = self.parents.get(&at) {
if function.links.insert(at, parent).is_some() {
break;
}
at = parent;
}
if let Some(root) = outermost(id, |scope| self.parents.get(&scope).copied())
&& self.subprograms.contains(&root)
{
function.subprograms.insert(root);
}
Some(id)
}
}
fn outermost(scope: ScopeId, parent: impl Fn(ScopeId) -> Option<ScopeId>) -> Option<ScopeId> {
let mut seen = HashSet::new();
let mut at = scope;
while seen.insert(at) {
match parent(at) {
Some(up) => at = up,
None => return Some(at),
}
}
None
}
unsafe fn read_function_ir(
context: LLVMContextRef,
function: LLVMValueRef,
numbers: &mut DebugNumbers,
) -> FunctionIr {
let mut handles = Vec::new();
let mut blocks = Vec::new();
unsafe {
let layout = LLVMGetModuleDataLayout(LLVMGetGlobalParent(function));
let param_count = LLVMCountParams(function);
for index in 0..param_count {
handles.push((LLVMGetParam(function, index), 0));
}
let mut block = LLVMGetFirstBasicBlock(function);
while !block.is_null() {
let mut instruction = LLVMGetFirstInstruction(block);
while !instruction.is_null() {
handles.push((instruction, blocks.len()));
instruction = LLVMGetNextInstruction(instruction);
}
blocks.push(block);
block = LLVMGetNextBasicBlock(block);
}
let ids: HashMap<LLVMValueRef, ValueId> = handles
.iter()
.enumerate()
.map(|(id, &(handle, _))| (handle, id as ValueId))
.collect();
let block_ids: HashMap<LLVMBasicBlockRef, usize> = blocks
.iter()
.enumerate()
.map(|(index, &block)| (block, index))
.collect();
let mut block_irs: Vec<BlockIr> = blocks
.iter()
.map(|&block| BlockIr {
instrs: Vec::new(),
successors: successors(block, &block_ids),
})
.collect();
let mut values = Vec::with_capacity(handles.len());
let mut dbg_values = Vec::new();
let mut scopes = Vec::with_capacity(handles.len());
let mut function_scopes = FunctionScopes::default();
let subprogram = numbers.scope(context, LLVMGetSubprogram(function), &mut function_scopes);
for (id, &(handle, block)) in handles.iter().enumerate() {
let id = id as ValueId;
if id < param_count {
scopes.push(None);
values.push(ValueNode {
block,
opcode: OpcodeIr::Param,
operands: Vec::new(),
incoming: Vec::new(),
call: None,
guard: None,
location: None,
volatile: false,
ty: None,
element: None,
});
continue;
}
block_irs[block].instrs.push(id);
values.push(read_instruction(handle, block, &ids, &block_ids, layout));
let location = LLVMInstructionGetDebugLoc(handle);
scopes.push(if location.is_null() {
None
} else {
numbers.scope(
context,
LLVMDILocationGetScope(location),
&mut function_scopes,
)
});
read_bindings(
context,
handle,
id,
&ids,
numbers,
&mut function_scopes,
&mut dbg_values,
);
}
FunctionIr {
id: FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: value_name(function),
},
params: (0..param_count).collect(),
blocks: block_irs,
values,
dbg_values,
scopes,
scope_parents: function_scopes.links,
subprogram,
subprograms: function_scopes.subprograms,
}
}
}
unsafe fn successors(
block: LLVMBasicBlockRef,
block_ids: &HashMap<LLVMBasicBlockRef, usize>,
) -> Vec<usize> {
unsafe {
let terminator = LLVMGetBasicBlockTerminator(block);
if terminator.is_null() {
return Vec::new();
}
(0..LLVMGetNumSuccessors(terminator))
.filter_map(|index| block_ids.get(&LLVMGetSuccessor(terminator, index)).copied())
.collect()
}
}
unsafe fn read_instruction(
instruction: LLVMValueRef,
block: usize,
ids: &HashMap<LLVMValueRef, ValueId>,
block_ids: &HashMap<LLVMBasicBlockRef, usize>,
layout: LLVMTargetDataRef,
) -> ValueNode {
unsafe {
let opcode = LLVMGetInstructionOpcode(instruction);
let operands = (0..LLVMGetNumOperands(instruction) as u32)
.map(|index| operand_ir(LLVMGetOperand(instruction, index), ids))
.collect();
let incoming = if opcode == LLVMOpcode::LLVMPHI {
(0..LLVMCountIncoming(instruction))
.filter_map(|index| {
block_ids
.get(&LLVMGetIncomingBlock(instruction, index))
.copied()
})
.collect()
} else {
Vec::new()
};
let call = matches!(opcode, LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke)
.then(|| call_ir(instruction, ids));
let accessed = match opcode {
LLVMOpcode::LLVMLoad => Some(instruction),
LLVMOpcode::LLVMStore => Some(LLVMGetOperand(instruction, 0)),
_ => None,
};
ValueNode {
block,
opcode: opcode_ir(opcode),
operands,
incoming,
guard: guard_of(instruction, opcode, call.as_ref()),
call,
location: location_of(instruction),
volatile: accessed.is_some()
&& (LLVMGetVolatile(instruction) != 0
|| LLVMGetOrdering(instruction)
!= LLVMAtomicOrdering::LLVMAtomicOrderingNotAtomic),
ty: accessed.and_then(|value| type_tag(LLVMTypeOf(value), layout)),
element: (opcode == LLVMOpcode::LLVMGetElementPtr).then(|| {
owned_message(LLVMPrintTypeToString(LLVMGetGEPSourceElementType(
instruction,
)))
}),
}
}
}
const FLOAT_WIDTHS: [(LLVMTypeKind, u32); 5] = [
(LLVMTypeKind::LLVMHalfTypeKind, 16),
(LLVMTypeKind::LLVMFloatTypeKind, 32),
(LLVMTypeKind::LLVMDoubleTypeKind, 64),
(LLVMTypeKind::LLVMX86_FP80TypeKind, 80),
(LLVMTypeKind::LLVMFP128TypeKind, 128),
];
unsafe fn type_tag(ty: LLVMTypeRef, layout: LLVMTargetDataRef) -> Option<TypeTag> {
unsafe {
let kind = LLVMGetTypeKind(ty);
let (kind, bits) = match kind {
LLVMTypeKind::LLVMIntegerTypeKind => (ScalarKind::Int, LLVMGetIntTypeWidth(ty)),
LLVMTypeKind::LLVMPointerTypeKind if LLVMGetPointerAddressSpace(ty) == 0 => {
(ScalarKind::Ptr, LLVMPointerSizeForAS(layout, 0) * 8)
}
_ => (
ScalarKind::Float,
FLOAT_WIDTHS
.iter()
.find_map(|&(float, bits)| (float == kind).then_some(bits))?,
),
};
Some(TypeTag { kind, bits })
}
}
unsafe fn operand_ir(value: LLVMValueRef, ids: &HashMap<LLVMValueRef, ValueId>) -> Operand {
if let Some(&id) = ids.get(&value) {
return Operand::Value(id);
}
unsafe {
if value.is_null() {
Operand::Other
} else if !LLVMIsAConstantInt(value).is_null() {
Operand::Const(ConstVal {
value: constant_text(value),
bits: int_width(value),
})
} else if !LLVMIsAFunction(value).is_null() {
Operand::Func(FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol: value_name(value),
})
} else {
Operand::Other
}
}
}
unsafe fn int_width(value: LLVMValueRef) -> Option<u32> {
unsafe {
let ty = LLVMTypeOf(value);
(LLVMGetTypeKind(ty) == LLVMTypeKind::LLVMIntegerTypeKind).then(|| LLVMGetIntTypeWidth(ty))
}
}
unsafe fn constant_text(constant: LLVMValueRef) -> String {
let text = unsafe { owned_message(LLVMPrintValueToString(constant)) };
match text.split_once(' ') {
Some((_, value)) => value.to_string(),
None => text,
}
}
unsafe fn call_ir(instruction: LLVMValueRef, ids: &HashMap<LLVMValueRef, ValueId>) -> CallIr {
unsafe {
let called = LLVMGetCalledValue(instruction);
let named = (!called.is_null()).then(|| named_callee(called)).flatten();
CallIr {
callee: named.map(|symbol| FunctionId {
module_id: NEUTRAL_MODULE_ID.to_string(),
symbol,
}),
inline_asm: !called.is_null() && !LLVMIsAInlineAsm(called).is_null(),
returns_void: LLVMGetTypeKind(LLVMTypeOf(instruction))
== LLVMTypeKind::LLVMVoidTypeKind,
args: (0..LLVMGetNumArgOperands(instruction))
.map(|index| operand_ir(LLVMGetOperand(instruction, index), ids))
.collect(),
}
}
}
fn opcode_ir(opcode: LLVMOpcode) -> OpcodeIr {
use LLVMOpcode::*;
let binary = |op| OpcodeIr::BinOp { op };
let cast = |op| OpcodeIr::Cast { op };
match opcode {
LLVMCall | LLVMInvoke => OpcodeIr::Call,
LLVMCallBr => OpcodeIr::CallBr,
LLVMICmp => OpcodeIr::ICmp,
LLVMSelect => OpcodeIr::Select,
LLVMPHI => OpcodeIr::Phi,
LLVMGetElementPtr => OpcodeIr::Gep,
LLVMLoad => OpcodeIr::Load,
LLVMStore => OpcodeIr::Store,
LLVMRet => OpcodeIr::Ret,
LLVMUncondBr | LLVMCondBr => OpcodeIr::Br,
LLVMAdd => binary(BinaryOp::Add),
LLVMFAdd => binary(BinaryOp::FAdd),
LLVMSub => binary(BinaryOp::Sub),
LLVMFSub => binary(BinaryOp::FSub),
LLVMMul => binary(BinaryOp::Mul),
LLVMFMul => binary(BinaryOp::FMul),
LLVMUDiv => binary(BinaryOp::UDiv),
LLVMSDiv => binary(BinaryOp::SDiv),
LLVMFDiv => binary(BinaryOp::FDiv),
LLVMURem => binary(BinaryOp::URem),
LLVMSRem => binary(BinaryOp::SRem),
LLVMFRem => binary(BinaryOp::FRem),
LLVMShl => binary(BinaryOp::Shl),
LLVMLShr => binary(BinaryOp::LShr),
LLVMAShr => binary(BinaryOp::AShr),
LLVMAnd => binary(BinaryOp::And),
LLVMOr => binary(BinaryOp::Or),
LLVMXor => binary(BinaryOp::Xor),
LLVMTrunc => cast(CastOp::Trunc),
LLVMZExt => cast(CastOp::ZExt),
LLVMSExt => cast(CastOp::SExt),
LLVMFPToUI => cast(CastOp::FPToUI),
LLVMFPToSI => cast(CastOp::FPToSI),
LLVMUIToFP => cast(CastOp::UIToFP),
LLVMSIToFP => cast(CastOp::SIToFP),
LLVMFPTrunc => cast(CastOp::FPTrunc),
LLVMFPExt => cast(CastOp::FPExt),
LLVMPtrToInt => cast(CastOp::PtrToInt),
LLVMPtrToAddr => cast(CastOp::PtrToAddr),
LLVMIntToPtr => cast(CastOp::IntToPtr),
LLVMBitCast => cast(CastOp::BitCast),
LLVMAddrSpaceCast => cast(CastOp::AddrSpaceCast),
_ => OpcodeIr::Other,
}
}
unsafe fn guard_of(
instruction: LLVMValueRef,
opcode: LLVMOpcode,
call: Option<&CallIr>,
) -> Option<GuardOpRaw> {
let raw = |kind, predicate: Option<&str>, bound, bits| GuardOpRaw {
kind,
predicate: predicate.map(str::to_string),
bound,
bits,
};
unsafe {
match opcode {
LLVMOpcode::LLVMICmp => Some(raw(
GuardKind::Compare,
predicate_row(LLVMGetICmpPredicate(instruction)).map(|row| row.spelling),
constant_operand(instruction, &[0, 1]),
int_width(LLVMGetOperand(instruction, 0)),
)),
LLVMOpcode::LLVMSelect => select_clamp(instruction),
LLVMOpcode::LLVMCall | LLVMOpcode::LLVMInvoke => {
let call = call?;
let symbol = &call.callee.as_ref()?.symbol;
let &(_, kind, predicate) = GUARD_INTRINSICS
.iter()
.find(|(prefix, ..)| symbol.starts_with(prefix))?;
let (bound, bits) = call
.args
.iter()
.find_map(|arg| match arg {
Operand::Const(constant) => Some((constant.value.clone(), constant.bits)),
_ => None,
})
.unzip();
Some(raw(kind, Some(predicate), bound, bits.flatten()))
}
LLVMOpcode::LLVMAnd => constant_operand(instruction, &[0, 1]).map(|mask| {
let bits = int_width(LLVMGetOperand(instruction, 0));
raw(GuardKind::Mask, None, Some(mask), bits)
}),
LLVMOpcode::LLVMURem => constant_operand(instruction, &[1]).map(|divisor| {
let bits = int_width(LLVMGetOperand(instruction, 1));
raw(GuardKind::Modulo, Some("urem"), Some(divisor), bits)
}),
LLVMOpcode::LLVMSRem => constant_operand(instruction, &[1]).map(|divisor| {
let bits = int_width(LLVMGetOperand(instruction, 1));
raw(GuardKind::Modulo, Some("srem"), Some(divisor), bits)
}),
_ => None,
}
}
}
fn predicate_row(predicate: LLVMIntPredicate) -> Option<&'static IntPredicate> {
let (_, spelling) = LLVM_PREDICATES.iter().find(|row| row.0 == predicate)?;
int_predicate(spelling)
}
unsafe fn constant_operand(instruction: LLVMValueRef, indices: &[u32]) -> Option<String> {
indices.iter().find_map(|&index| {
unsafe {
let operand = LLVMGetOperand(instruction, index);
(!LLVMIsAConstantInt(operand).is_null()).then(|| constant_text(operand))
}
})
}
unsafe fn select_clamp(select: LLVMValueRef) -> Option<GuardOpRaw> {
unsafe {
let condition = LLVMGetOperand(select, 0);
if LLVMIsAICmpInst(condition).is_null() {
return None;
}
let arms = [LLVMGetOperand(select, 1), LLVMGetOperand(select, 2)];
let compared = [LLVMGetOperand(condition, 0), LLVMGetOperand(condition, 1)];
let picks = if arms == compared {
0
} else if arms == [compared[1], compared[0]] {
1
} else {
return None;
};
let clamps = predicate_row(LLVMGetICmpPredicate(condition))?.clamps?;
let bound = arms
.into_iter()
.find(|&arm| !LLVMIsAConstantInt(arm).is_null())
.map(|arm| constant_text(arm));
Some(GuardOpRaw {
kind: GuardKind::Clamp,
predicate: Some(clamps[picks].to_string()),
bound,
bits: int_width(arms[0]),
})
}
}
unsafe fn read_bindings(
context: LLVMContextRef,
instruction: LLVMValueRef,
at: ValueId,
ids: &HashMap<LLVMValueRef, ValueId>,
numbers: &mut DebugNumbers,
scopes: &mut FunctionScopes,
out: &mut Vec<DbgValue>,
) {
unsafe {
let mut record = LLVMGetFirstDbgRecord(instruction);
while !record.is_null() {
let kind = LLVMDbgRecordGetKind(record);
let variable = (kind != LLVMDbgRecordKind::LLVMDbgRecordLabel)
.then(|| LLVMDbgVariableRecordGetVariable(record));
let name = variable.and_then(|variable| variable_name(context, variable));
if let (Some(name), Some(variable)) = (name, variable) {
let next = numbers.variables.len() as u32;
let id = *numbers.variables.entry(variable).or_insert(next);
let scope = numbers.scope(context, LLVMDIVariableGetScope(variable), scopes);
let file = LLVMDIVariableGetFile(variable);
let declared_file = if file.is_null() {
String::new()
} else {
let mut length = 0;
owned(LLVMDIFileGetFilename(file, &mut length), length as usize)
};
let direct = kind != LLVMDbgRecordKind::LLVMDbgRecordDeclare
&& is_empty_expression(context, LLVMDbgVariableRecordGetExpression(record));
let value = if direct {
operand_ir(LLVMDbgVariableRecordGetValue(record, 0), ids)
} else {
Operand::Other
};
out.push(DbgValue {
name,
variable: id,
declared_file,
declared_line: LLVMDIVariableGetLine(variable),
scope,
value,
at,
});
}
record = LLVMGetNextDbgRecord(record);
}
}
}
unsafe fn variable_name(context: LLVMContextRef, variable: LLVMMetadataRef) -> Option<String> {
unsafe {
if variable.is_null()
|| !matches!(
LLVMGetMetadataKind(variable),
LLVMMetadataKind::LLVMDILocalVariableMetadataKind
)
{
return None;
}
let operands = md_node_operands(LLVMMetadataAsValue(context, variable));
operand_string(*operands.get(DI_VARIABLE_NAME_OPERAND)?).filter(|name| !name.is_empty())
}
}
unsafe fn is_empty_expression(context: LLVMContextRef, expression: LLVMMetadataRef) -> bool {
if expression.is_null() {
return false;
}
let text = unsafe {
owned_message(LLVMPrintValueToString(LLVMMetadataAsValue(
context, expression,
)))
};
text.trim() == EMPTY_EXPRESSION
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_cyclic_scope_chain_has_no_outermost_scope() {
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let parents = HashMap::from([(1, 2), (2, 1), (3, 1), (4, 5)]);
let root = |scope| outermost(scope, |at| parents.get(&at).copied());
let _ = sender.send([root(3), root(1), root(4), root(5)]);
});
let roots = receiver
.recv_timeout(std::time::Duration::from_secs(5))
.expect("the walk up a cyclic chain must end");
assert_eq!(roots, [None, None, Some(5), Some(5)]);
}
#[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")
);
}
const CLAMP_SOURCE: &str =
"unsigned f(unsigned len) { unsigned cap = 16; return len < cap ? len : cap; }\n";
const SALVAGED_SOURCE: &str = "int g(int x) { int y = x * 3; (void)y; return x; }\n";
const GUARD_SHAPES_IR: &str = r#"
declare i32 @llvm.uadd.sat.i32(i32, i32)
define i32 @shapes(i32 %x, i32 %y, ptr %fp) {
entry:
%lt = icmp ult i32 %x, 16
%min = select i1 %lt, i32 %x, i32 16
%gt = icmp sgt i32 %y, 100
%max = select i1 %gt, i32 100, i32 %y
%eq = icmp eq i32 %x, 0
%pick = select i1 %eq, i32 %x, i32 0
%sat = call i32 @llvm.uadd.sat.i32(i32 %x, i32 7)
%mask = and i32 %y, 255
%mod = urem i32 %x, 10
%smod = srem i32 %y, %x
%ind = call i32 %fp(i32 %mask)
%asm = call i32 asm "", "=r,r"(i32 %mod)
br label %loop
loop:
%i = phi i32 [ 0, %entry ], [ %next, %loop ]
%next = add i32 %i, 1
%done = icmp eq i32 %next, %min
br i1 %done, label %exit, label %loop
exit:
ret i32 %next
}
"#;
fn bitcode(name: &str, source: &str, flags: &[&str]) -> Vec<u8> {
let scratch = tempfile::tempdir().unwrap();
let path = scratch.path().join(name);
std::fs::write(&path, source).unwrap();
std::fs::read(rllvm_testkit::compile_bitcode_file(&path, flags)).unwrap()
}
fn defined<'a>(ir: &'a ModuleIr, symbol: &str) -> &'a FunctionIr {
ir.functions
.iter()
.find(|function| function.id.symbol == symbol)
.unwrap_or_else(|| panic!("no {symbol} in {ir:#?}"))
}
fn instruction(function: &FunctionIr, block: usize, n: usize) -> (ValueId, &ValueNode) {
let id = function.blocks[block].instrs[n];
(id, &function.values[id as usize])
}
fn guard(kind: GuardKind, predicate: Option<&str>, bound: Option<&str>) -> Option<GuardOpRaw> {
Some(GuardOpRaw {
kind,
predicate: predicate.map(str::to_string),
bound: bound.map(str::to_string),
bits: Some(32),
})
}
fn constant(value: &str) -> Operand {
Operand::Const(ConstVal {
value: value.to_string(),
bits: Some(32),
})
}
#[test]
fn read_module_ir_surfaces_a_clamp_and_its_comparison() {
let ir = read_module_ir(&bitcode("clamp.c", CLAMP_SOURCE, &["-O1", "-g"])).unwrap();
assert!(ir.diagnostics.is_empty(), "{:?}", ir.diagnostics);
assert_eq!(ir.functions.len(), 1, "the intrinsic is only declared");
let f = defined(&ir, "f");
assert_eq!(f.params, [0]);
assert_eq!(f.values[0].opcode, OpcodeIr::Param);
let (umin, node) = instruction(f, 0, 0);
assert_eq!(node.opcode, OpcodeIr::Call);
assert_eq!(
node.guard,
guard(GuardKind::Clamp, Some("umin"), Some("16"))
);
let call = node.call.as_ref().unwrap();
assert!(
call.callee
.as_ref()
.is_some_and(|callee| callee.symbol.starts_with("llvm.umin.")),
"{call:?}"
);
assert_eq!(call.args, [Operand::Value(f.params[0]), constant("16")]);
assert!(node.location.as_ref().is_some_and(|at| at.line == 1));
let (_, ret) = instruction(f, 0, 1);
assert_eq!(ret.opcode, OpcodeIr::Ret);
assert_eq!(ret.operands, [Operand::Value(umin)]);
let bindings: Vec<_> = f
.dbg_values
.iter()
.map(|binding| (binding.name.as_str(), &binding.value, binding.at))
.collect();
assert_eq!(
bindings,
[
("len", &Operand::Value(f.params[0]), umin),
("cap", &constant("16"), umin),
]
);
}
#[test]
fn unoptimized_code_keeps_its_comparison_and_branches() {
let ir = read_module_ir(&bitcode("clamp.c", CLAMP_SOURCE, &["-O0", "-g"])).unwrap();
let f = defined(&ir, "f");
let compare = f
.values
.iter()
.find(|node| node.opcode == OpcodeIr::ICmp)
.unwrap();
assert_eq!(compare.guard, guard(GuardKind::Compare, Some("ult"), None));
assert!(
f.values.iter().all(|node| node.opcode != OpcodeIr::Select),
"-O0 lowers `?:` to branches"
);
let successors: Vec<_> = f.blocks.iter().map(|block| &block.successors[..]).collect();
assert_eq!(successors, [&[1, 2][..], &[3], &[3], &[]]);
let (_, phi) = instruction(f, 3, 0);
assert_eq!(phi.opcode, OpcodeIr::Phi);
assert_eq!(phi.block, 3);
let names: Vec<_> = f.dbg_values.iter().map(|binding| &binding.name).collect();
assert_eq!(names, ["len", "cap"]);
assert!(
f.dbg_values
.iter()
.all(|binding| binding.value == Operand::Other)
);
}
#[test]
fn every_guard_shape_is_classified_and_near_misses_are_not() {
let ir = read_module_ir(&bitcode(
"shapes.ll",
GUARD_SHAPES_IR,
&["-Wno-override-module"],
))
.unwrap();
let f = defined(&ir, "shapes");
assert_eq!(f.params, [0, 1, 2]);
let guard_at = |n| instruction(f, 0, n).1.guard.clone();
assert_eq!(
guard_at(0),
guard(GuardKind::Compare, Some("ult"), Some("16"))
);
assert_eq!(
guard_at(1),
guard(GuardKind::Clamp, Some("umin"), Some("16"))
);
assert_eq!(
guard_at(3),
guard(GuardKind::Clamp, Some("smin"), Some("100")),
"picking the compared constant on true is the other bound"
);
assert_eq!(guard_at(5), None, "an equality select bounds nothing");
assert_eq!(
guard_at(6),
guard(GuardKind::Saturate, Some("uadd.sat"), Some("7"))
);
assert_eq!(guard_at(7), guard(GuardKind::Mask, None, Some("255")));
assert_eq!(
guard_at(8),
guard(GuardKind::Modulo, Some("urem"), Some("10"))
);
assert_eq!(guard_at(9), None, "a variable divisor bounds nothing");
assert_eq!(
instruction(f, 0, 7).1.opcode,
OpcodeIr::BinOp { op: BinaryOp::And }
);
let call_at = |n| instruction(f, 0, n).1.call.clone().unwrap();
let indirect = call_at(10);
assert_eq!((indirect.callee, indirect.inline_asm), (None, false));
assert_eq!(indirect.args, [Operand::Value(instruction(f, 0, 7).0)]);
let asm = call_at(11);
assert_eq!((asm.callee, asm.inline_asm), (None, true));
let (next, _) = instruction(f, 1, 1);
let (_, phi) = instruction(f, 1, 0);
assert_eq!(phi.operands, [constant("0"), Operand::Value(next)]);
let successors: Vec<_> = f.blocks.iter().map(|block| &block.successors[..]).collect();
assert_eq!(successors, [&[1][..], &[2, 1], &[]]);
assert_eq!(instruction(f, 1, 3).1.opcode, OpcodeIr::Br);
assert!(f.dbg_values.is_empty(), "no debug info");
}
#[test]
fn a_salvaged_binding_is_not_read_as_the_value() {
let ir = read_module_ir(&bitcode("salvaged.c", SALVAGED_SOURCE, &["-O1", "-g"])).unwrap();
let g = defined(&ir, "g");
let bindings: Vec<_> = g
.dbg_values
.iter()
.map(|binding| (binding.name.as_str(), &binding.value))
.collect();
assert_eq!(
bindings,
[("x", &Operand::Value(g.params[0])), ("y", &Operand::Other)]
);
}
#[test]
fn read_module_ir_keeps_what_llvm_reported() {
let scratch = tempfile::tempdir().unwrap();
let module = crate::testing::stale_debug_info_module(scratch.path());
let ir = read_module_ir(&std::fs::read(module).unwrap()).unwrap();
assert!(
ir.diagnostics
.iter()
.any(|diagnostic| diagnostic.contains("invalid version")),
"{:?}",
ir.diagnostics
);
assert!(
defined(&ir, "f")
.values
.iter()
.all(|v| v.location.is_none())
);
}
#[test]
fn unreadable_bytes_are_an_error_naming_the_reader() {
let error = read_module_ir(b"not bitcode").unwrap_err().to_string();
assert!(error.contains("cannot be read by LLVM"), "{error}");
}
}