use std::{collections::HashMap, fmt};
use allocative::{ident_key, Allocative};
use internment::Intern;
use serde::{Deserialize, Serialize};
use crate::rustc_portable::*;
#[cfg(feature = "rustc")]
use crate::rustc_proxies;
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum RichLocation {
#[cfg_attr(feature = "rustc", serde(with = "rustc_proxies::Location"))]
Location(Location),
Start,
End,
}
impl Allocative for RichLocation {
fn visit<'a, 'b: 'a>(&self, visitor: &'a mut allocative::Visitor<'b>) {
visitor.visit_simple_sized::<Self>();
}
}
impl RichLocation {
pub fn is_start(self) -> bool {
matches!(self, RichLocation::Start)
}
pub fn is_end(self) -> bool {
matches!(self, RichLocation::End)
}
pub fn is_real(self) -> bool {
matches!(self, RichLocation::Location(_))
}
pub fn unwrap_location(self) -> Location {
self.as_location()
.expect("RichLocation was unexpectedly Start")
}
pub fn as_location(self) -> Option<Location> {
match self {
RichLocation::Location(location) => Some(location),
RichLocation::Start | RichLocation::End => None,
}
}
}
impl fmt::Display for RichLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RichLocation::Location(loc) => write!(f, "{loc:?}"),
RichLocation::Start => write!(f, "start"),
RichLocation::End => write!(f, "end"),
}
}
}
impl From<Location> for RichLocation {
fn from(value: Location) -> Self {
RichLocation::Location(value)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, Serialize, Deserialize, Allocative)]
pub struct GlobalLocation {
#[cfg_attr(feature = "rustc", serde(with = "rustc_proxies::DefId"))]
#[allocative(visit = allocative_visit_simple_sized)]
pub function: DefId,
pub location: RichLocation,
}
#[cfg(not(feature = "rustc"))]
impl fmt::Display for GlobalLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}::{}", self.function, self.location)
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct CallString(Intern<CallStringInner>);
impl Allocative for CallString {
fn visit<'a, 'b: 'a>(&self, visitor: &'a mut allocative::Visitor<'b>) {
let mut visitor = visitor.enter_self_sized::<Self>();
allocative_visit_intern_t(self.0, &mut visitor);
visitor.exit();
}
}
thread_local! {
static TRACK_INTERN_AS_UNIQUE: bool = {
if let Ok(val) = std::env::var("ALLOCATIVE_TRACK_INTERN_AS_UNIQUE") {
val == "true" || val == "1"
} else {
false
}
}
}
pub fn allocative_visit_intern_t<T: Allocative + ?Sized>(
intern: Intern<T>,
visitor: &mut allocative::Visitor<'_>,
) {
let track_as_unique = TRACK_INTERN_AS_UNIQUE.with(|v| *v);
let ptr_size = std::mem::size_of::<*const T>();
if !track_as_unique {
let mut visitor = visitor.enter_self_sized::<Intern<T>>();
{
let ptr: &T = intern.as_ref();
let as_ptr = ptr as *const T as *const ();
let inner_visitor = visitor.enter_shared(ident_key!(intern_value), ptr_size, as_ptr);
if let Some(mut visitor) = inner_visitor {
ptr.visit(&mut visitor);
visitor.exit();
}
}
visitor.exit();
} else {
let mut visitor = visitor.enter_self_sized::<Intern<T>>();
let inner: &T = intern.as_ref();
{
let mut visitor = visitor.enter_unique(ident_key!(pointee), ptr_size);
inner.visit(&mut visitor);
visitor.exit();
}
visitor.exit();
}
}
impl Serialize for CallString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.as_ref().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CallString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let locs = <Box<[GlobalLocation]>>::deserialize(deserializer)?;
Ok(CallString::new(locs.as_ref()))
}
}
type CallStringInner = [GlobalLocation];
impl CallString {
pub fn new(locs: &CallStringInner) -> Self {
CallString(Intern::from(locs))
}
pub fn pop(self) -> (GlobalLocation, Option<CallString>) {
let (last, rest) = self
.0
.split_last()
.expect("Invariant broken, call strings must have at least length 1");
(*last, (!rest.is_empty()).then(|| CallString::new(rest)))
}
pub fn single(loc: GlobalLocation) -> Self {
Self::new(&[loc])
}
pub fn leaf(self) -> GlobalLocation {
*self.0.last().unwrap()
}
pub fn caller(self) -> Option<Self> {
self.pop().1
}
pub fn iter(&self) -> impl DoubleEndedIterator<Item = GlobalLocation> + '_ {
self.0.iter().rev().copied()
}
pub fn push(self, loc: GlobalLocation) -> Self {
let string = self.0.iter().copied().chain(Some(loc)).collect::<Box<_>>();
CallString::new(&string)
}
pub fn push_front(self, loc: GlobalLocation) -> Self {
CallString::new(
[loc]
.into_iter()
.chain(self.0.iter().copied())
.collect::<Box<_>>()
.as_ref(),
)
}
pub fn is_at_root(self) -> bool {
self.0.len() == 1
}
pub fn root(self) -> GlobalLocation {
*self.0.first().unwrap()
}
pub fn stable_id(self) -> usize {
let r: &'static CallStringInner = self.0.as_ref();
r.as_ptr() as usize
}
pub fn iter_from_root(&self) -> impl DoubleEndedIterator<Item = GlobalLocation> + '_ {
self.0.iter().copied()
}
pub fn len(self) -> usize {
self.0.len()
}
pub fn is_empty(self) -> bool {
self.0.is_empty()
}
}
impl fmt::Display for CallString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, loc) in self.0.iter().enumerate() {
if i > 0 {
write!(f, "←")?;
}
loc.fmt(f)?;
}
Ok(())
}
}
pub fn allocative_visit_map_coerce_key<'a, K, V: Allocative>(
map: &'a HashMap<K, V>,
visitor: &mut allocative::Visitor<'a>,
) {
let coerced: &HashMap<SimpleSizedAllocativeWrapper<K>, V> = unsafe { std::mem::transmute(map) };
coerced.visit(visitor);
}
pub fn allocative_visit_simple_sized<T>(_: &T, visitor: &mut allocative::Visitor<'_>) {
visitor.visit_simple_sized::<T>();
}
#[repr(transparent)]
pub struct SimpleSizedAllocativeWrapper<T>(T);
impl<T> Allocative for SimpleSizedAllocativeWrapper<T> {
fn visit<'a, 'b: 'a>(&self, visitor: &'a mut allocative::Visitor<'b>) {
visitor.visit_simple_sized::<T>();
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
pub enum Constant {
Int(i64),
Uint(u64),
Bool(bool),
String(Intern<String>),
Zst(Intern<String>),
}
impl Constant {
pub fn int(i: impl Into<i64>) -> Self {
Self::Int(i.into())
}
pub fn uint(u: impl Into<u64>) -> Self {
Self::Uint(u.into())
}
pub fn bool(b: impl Into<bool>) -> Self {
Self::Bool(b.into())
}
pub fn string(s: impl AsRef<str>) -> Self {
Self::String(Intern::from_ref(s.as_ref()))
}
pub fn zst(s: impl AsRef<str>) -> Self {
Self::Zst(Intern::from_ref(s.as_ref()))
}
}
impl std::fmt::Display for Constant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::{Debug, Display};
match self {
Self::Bool(b) => Display::fmt(b, f),
Self::Int(i) => Display::fmt(i, f),
Self::Uint(u) => Display::fmt(u, f),
Self::String(s) => Debug::fmt(s, f),
Self::Zst(s) => f.write_str(s),
}
}
}
impl Allocative for Constant {
fn visit<'a, 'b: 'a>(&self, visitor: &'a mut allocative::Visitor<'b>) {
let mut visitor = visitor.enter_self_sized::<Self>();
if let Self::String(s) = self {
allocative_visit_intern_t(*s, &mut visitor);
}
visitor.exit();
}
}