Skip to content

Commit

Permalink
More robust type_eq_non_static implementation (#14)
Browse files Browse the repository at this point in the history
According to #6 (comment) in reference to the `type_id_of::<T> as usize`-based implementation:

> As for the function instantiation address taking thing: it simply looks broken/unsound - the only reason LLVM mergefunc would even consider them distinct is its implementation being too simple and nowhere near "graph isomorphism" (remember that we have `unnamed_addr` semantics for functions, which makes it valid to merge functions _even if their addresses are observed_).

> I am being told by lcnr (also working on polymorphization) that this will break at some point the future (assuming we get to turn that on by default), so this is not that theoretical.
  • Loading branch information
dtolnay authored Sep 19, 2023
1 parent e1a5321 commit 42f0024
Showing 1 changed file with 24 additions and 22 deletions.
46 changes: 24 additions & 22 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use core::{
any::{type_name, TypeId},
marker::PhantomData,
mem, ptr,
};

Expand All @@ -27,30 +28,31 @@ pub(crate) fn type_eq<T: 'static, U: 'static>() -> bool {
/// `Struct<'b>`, even if either `'a` or `'b` outlives the other.
#[inline(always)]
pub(crate) fn type_eq_non_static<T: ?Sized, U: ?Sized>() -> bool {
// Inline has a weird, but desirable result on this function. It can't be
// fully inlined everywhere since it creates a function pointer of itself.
// But in practice when used here, the act of taking the address will be
// inlined, thus avoiding a function call when comparing two types.
#[inline]
fn type_id_of<T: ?Sized>() -> usize {
type_id_of::<T> as usize
non_static_type_id::<T>() == non_static_type_id::<U>()
}

/// Produces type IDs that are compatible with `TypeId::of::<T>`, but without
/// `T: 'static` bound.
fn non_static_type_id<T: ?Sized>() -> TypeId {
trait NonStaticAny {
fn get_type_id(&self) -> TypeId
where
Self: 'static;
}

// What we're doing here is comparing two function pointers of the same
// generic function to see if they are identical. If they are not
// identical then `T` and `U` are not the same type.
//
// If they are equal, then they _might_ be the same type, unless an
// optimization step reduced two different functions to the same
// implementation due to having the same body. To avoid this we are using
// a function which references itself. This is something that LLVM cannot
// merge, since each monomorphized function has a reference to a different
// global alias.
type_id_of::<T>() == type_id_of::<U>()
// This is used as a sanity check more than anything. Our previous calls
// should not have any false positives, but if they did then the odds of
// them having the same type name as well is extremely unlikely.
&& type_name::<T>() == type_name::<U>()
impl<T: ?Sized> NonStaticAny for PhantomData<T> {
fn get_type_id(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<T>()
}
}

let phantom_data = PhantomData::<T>;
NonStaticAny::get_type_id(unsafe {
mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&phantom_data)
})
}

/// Reinterprets the bits of a value of one type as another type.
Expand Down

0 comments on commit 42f0024

Please sign in to comment.