use std::collections::HashMap;
use llvmkit_ir::{
BasicBlock, BlockSealState, Dyn, FunctionValue, Instruction, ReturnMarker, Value,
};
use crate::file_loc::{FileLoc, FileLocRange};
#[derive(Clone, PartialEq, Eq, Hash, Debug, thiserror::Error)]
pub enum LocationError {
#[error("duplicate location for the supplied IR handle")]
DuplicateHandle,
}
#[derive(Debug, Default)]
struct LocMap<'ctx> {
forward: HashMap<Value<'ctx>, FileLocRange>,
reverse: Vec<(FileLocRange, Value<'ctx>)>,
}
impl<'ctx> LocMap<'ctx> {
fn add(&mut self, value: Value<'ctx>, loc: FileLocRange) -> Result<(), LocationError> {
if self.forward.contains_key(&value) {
return Err(LocationError::DuplicateHandle);
}
self.forward.insert(value, loc);
let pos = self
.reverse
.binary_search_by(|(existing, _)| existing.start.cmp(&loc.start))
.unwrap_or_else(|e| e);
self.reverse.insert(pos, (loc, value));
Ok(())
}
fn location_of(&self, value: Value<'ctx>) -> Option<FileLocRange> {
self.forward.get(&value).copied()
}
fn handle_at(&self, loc: FileLoc) -> Option<Value<'ctx>> {
let pos = self
.reverse
.partition_point(|(range, _)| range.start <= loc);
if pos == 0 {
return None;
}
let (range, value) = self.reverse[pos - 1];
if range.contains_loc(loc) {
Some(value)
} else {
None
}
}
fn handle_at_range(&self, query: FileLocRange) -> Option<Value<'ctx>> {
let pos = self
.reverse
.partition_point(|(range, _)| range.start <= query.start);
if pos == 0 {
return None;
}
let (range, value) = self.reverse[pos - 1];
if range.start == query.start && range.end <= query.end {
Some(value)
} else {
None
}
}
}
#[derive(Debug, Default)]
pub struct AsmParserContext<'ctx> {
functions: LocMap<'ctx>,
blocks: LocMap<'ctx>,
instructions: LocMap<'ctx>,
}
impl<'ctx> AsmParserContext<'ctx> {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn function_location<R: ReturnMarker>(
&self,
f: FunctionValue<'ctx, R>,
) -> Option<FileLocRange> {
self.functions.location_of(f.as_value())
}
#[inline]
pub fn block_location<R: ReturnMarker, S: BlockSealState>(
&self,
b: BasicBlock<'ctx, R, S>,
) -> Option<FileLocRange> {
self.blocks.location_of(b.as_value())
}
#[inline]
pub fn instruction_location(&self, i: &Instruction<'ctx>) -> Option<FileLocRange> {
self.instructions.location_of(i.as_value())
}
#[inline]
pub fn function_at(&self, loc: FileLoc) -> Option<FunctionValue<'ctx, Dyn>> {
self.functions
.handle_at(loc)
.and_then(|v| FunctionValue::try_from(v).ok())
}
#[inline]
pub fn function_at_range(&self, query: FileLocRange) -> Option<FunctionValue<'ctx, Dyn>> {
self.functions
.handle_at_range(query)
.and_then(|v| FunctionValue::try_from(v).ok())
}
#[inline]
pub fn block_at(&self, loc: FileLoc) -> Option<BasicBlock<'ctx, Dyn, llvmkit_ir::Unsealed>> {
self.blocks
.handle_at(loc)
.and_then(|v| BasicBlock::try_from(v).ok())
}
#[inline]
pub fn block_at_range(
&self,
query: FileLocRange,
) -> Option<BasicBlock<'ctx, Dyn, llvmkit_ir::Unsealed>> {
self.blocks
.handle_at_range(query)
.and_then(|v| BasicBlock::try_from(v).ok())
}
#[inline]
pub fn instruction_at(&self, loc: FileLoc) -> Option<Value<'ctx>> {
self.instructions.handle_at(loc)
}
#[inline]
pub fn instruction_at_range(&self, query: FileLocRange) -> Option<Value<'ctx>> {
self.instructions.handle_at_range(query)
}
#[inline]
pub fn add_function_location<R: ReturnMarker>(
&mut self,
f: FunctionValue<'ctx, R>,
loc: FileLocRange,
) -> Result<(), LocationError> {
self.functions.add(f.as_value(), loc)
}
#[inline]
pub fn add_block_location<R: ReturnMarker, S: BlockSealState>(
&mut self,
b: BasicBlock<'ctx, R, S>,
loc: FileLocRange,
) -> Result<(), LocationError> {
self.blocks.add(b.as_value(), loc)
}
#[inline]
pub fn add_instruction_location(
&mut self,
i: &Instruction<'ctx>,
loc: FileLocRange,
) -> Result<(), LocationError> {
self.instructions.add(i.as_value(), loc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locmap_round_trip() {
llvmkit_ir::Module::with_new("locmap_round_trip", |m| {
let i32_ty = m.i32_type();
let g = m
.add_external_global("g", i32_ty.as_type())
.expect("fresh global");
let mut map: LocMap<'_> = LocMap::default();
let r = FileLocRange::new(FileLoc::new(2, 0), FileLoc::new(4, 0));
map.add(g.as_value(), r).unwrap();
assert_eq!(map.location_of(g.as_value()), Some(r));
assert_eq!(
map.add(g.as_value(), r),
Err(LocationError::DuplicateHandle)
);
});
}
#[test]
fn locmap_reverse_lookup_is_half_open() {
llvmkit_ir::Module::with_new("locmap_reverse_lookup_is_half_open", |m| {
let i32_ty = m.i32_type();
let g = m
.add_external_global("g", i32_ty.as_type())
.expect("fresh global");
let mut map: LocMap<'_> = LocMap::default();
let r = FileLocRange::new(FileLoc::new(1, 0), FileLoc::new(1, 5));
map.add(g.as_value(), r).unwrap();
assert_eq!(map.handle_at(FileLoc::new(1, 0)), Some(g.as_value()));
assert_eq!(map.handle_at(FileLoc::new(1, 4)), Some(g.as_value()));
assert_eq!(map.handle_at(FileLoc::new(1, 5)), None);
assert_eq!(map.handle_at(FileLoc::new(0, 0)), None);
});
}
#[test]
fn locmap_reverse_range_lookup() {
llvmkit_ir::Module::with_new("locmap_reverse_range_lookup", |m| {
let i32_ty = m.i32_type();
let g_inner = m
.add_external_global("g_inner", i32_ty.as_type())
.expect("fresh global");
let g_far = m
.add_external_global("g_far", i32_ty.as_type())
.expect("fresh global");
let mut map: LocMap<'_> = LocMap::default();
let inner = FileLocRange::new(FileLoc::new(1, 0), FileLoc::new(2, 0));
let far = FileLocRange::new(FileLoc::new(5, 0), FileLoc::new(6, 0));
map.add(g_inner.as_value(), inner).unwrap();
map.add(g_far.as_value(), far).unwrap();
let outer = FileLocRange::new(FileLoc::new(1, 0), FileLoc::new(3, 0));
assert_eq!(map.handle_at_range(outer), Some(g_inner.as_value()));
let shifted = FileLocRange::new(FileLoc::new(1, 1), FileLoc::new(3, 0));
assert_eq!(map.handle_at_range(shifted), None);
});
}
}