use std::{
collections::{BTreeSet, HashMap},
ffi::{CStr, c_char, c_int, c_void},
path::PathBuf,
};
use llvm_sys::{
LLVMDiagnosticSeverity, LLVMLinkage, LLVMOpcode,
bit_reader::LLVMParseBitcodeInContext2,
core::*,
debuginfo::{
LLVMDIFileGetDirectory, LLVMDIFileGetFilename, LLVMDILocationGetColumn,
LLVMDILocationGetInlinedAt, LLVMDILocationGetLine, LLVMDILocationGetScope,
LLVMDIScopeGetFile, LLVMInstructionGetDebugLoc,
},
error::{LLVMDisposeErrorMessage, LLVMErrorRef, LLVMGetErrorMessage},
prelude::*,
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 !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}")
}
#[derive(Clone, Debug, Default)]
pub struct ModuleFacts {
pub functions: Vec<FunctionFact>,
pub call_sites: Vec<CallSiteFact>,
pub uses: Vec<UseFact>,
pub diagnostics: Vec<String>,
}
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,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) -> 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, module_id, source_status) });
metadata = outer;
}
}
Some(unsafe {
build_location(
file,
directory,
line,
LLVMGetDebugLocColumn(value),
module_id,
source_status,
inlined_at,
)
})
}
unsafe fn location_of_metadata(
location: LLVMMetadataRef,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
) -> 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),
module_id,
source_status,
Vec::new(),
)
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn build_location(
file: String,
directory: String,
line: u32,
column: u32,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
inlined_at: Vec<SourceLocation>,
) -> SourceLocation {
let file = PathBuf::from(file);
let full = if directory.is_empty() {
file.clone()
} else {
PathBuf::from(&directory).join(&file)
};
let state = source_status.get(&(module_id.to_string(), full)).copied();
SourceLocation {
file,
directory: (!directory.is_empty()).then(|| PathBuf::from(directory)),
line,
column,
source_status: state.map_or(SourceStatus::Unknown, |state| state.status),
status_basis: state.and_then(|state| state.basis),
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,
}
}
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 count = unsafe { LLVMGetNumOperands(node) } as usize;
let mut operands = vec![std::ptr::null_mut(); count];
unsafe { LLVMGetMDNodeOperands(node, operands.as_mut_ptr()) };
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) -> 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 { 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) },
}
}
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) },
})
}
unsafe fn collect_uses(
function: LLVMValueRef,
id: &FunctionId,
module_id: &str,
source_status: &HashMap<(String, PathBuf), SourceState>,
uses: &mut Vec<UseFact>,
) {
let mut current = unsafe { LLVMGetFirstUse(function) };
while !current.is_null() {
let user = unsafe { LLVMGetUser(current) };
current = unsafe { LLVMGetNextUse(current) };
if user.is_null() {
continue;
}
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) } == function {
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 !unsafe { LLVMIsAGlobalVariable(user) }.is_null() => UseKind::GlobalInitializer,
None => UseKind::Other,
};
uses.push(UseFact {
used: id.clone(),
in_function: is_instruction
.then(|| unsafe { enclosing_function(user, module_id) })
.flatten(),
location: is_instruction
.then(|| unsafe { location_of(user, module_id, source_status) })
.flatten(),
kind,
});
}
}
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(
module: &LoadedModule,
source_status: &HashMap<(String, PathBuf), SourceState>,
) -> Result<ModuleFacts, Error> {
unsafe { extract_inner(module, source_status) }
}
unsafe fn extract_inner(
module: &LoadedModule,
source_status: &HashMap<(String, PathBuf), SourceState>,
) -> 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 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: module.id.clone(),
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, &module.id, source_status) };
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, &module.id, callees_kind) },
});
}
instruction = unsafe { LLVMGetNextInstruction(instruction) };
instruction_index += 1;
}
block = unsafe { LLVMGetNextBasicBlock(block) };
block_index += 1;
}
functions.push(FunctionFact {
id: id.clone(),
is_definition: unsafe { LLVMIsDeclaration(function) } == 0,
linkage: linkage_of(unsafe { LLVMGetLinkage(function) }),
signature: unsafe {
owned_message(LLVMPrintTypeToString(LLVMGlobalGetValueType(function)))
},
location: unsafe { location_of(function, &module.id, source_status) },
mapped_lines,
});
unsafe { collect_uses(function, &id, &module.id, source_status, &mut uses) };
function = unsafe { LLVMGetNextFunction(function) };
}
drop(parsed);
drop(buffer);
drop(context);
let mut diagnostics = pass_diagnostics;
diagnostics.extend(sink.take());
Ok(ModuleFacts {
functions,
call_sites,
uses,
diagnostics,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[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,
"Rust's v0 scheme is not Itanium"
);
}
#[test]
fn a_legacy_rust_symbol_reads_back_with_its_hash() {
assert_eq!(
demangle("_ZN4core3fmt5write17h1234567890abcdefE").as_deref(),
Some("core::fmt::write::h1234567890abcdef")
);
}
}