From 42f0024072b902768f8a464d9c3ac454fa71f12c Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Tue, 19 Sep 2023 12:57:57 -0700 Subject: [PATCH] More robust type_eq_non_static implementation (#14) According to https://github.com/sagebind/castaway/pull/6#issuecomment-1150952050 in reference to the `type_id_of:: 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. --- src/utils.rs | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 48e449d..b40db69 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ use core::{ any::{type_name, TypeId}, + marker::PhantomData, mem, ptr, }; @@ -27,30 +28,31 @@ pub(crate) fn type_eq() -> bool { /// `Struct<'b>`, even if either `'a` or `'b` outlives the other. #[inline(always)] pub(crate) fn type_eq_non_static() -> 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() -> usize { - type_id_of:: as usize + non_static_type_id::() == non_static_type_id::() +} + +/// Produces type IDs that are compatible with `TypeId::of::`, but without +/// `T: 'static` bound. +fn non_static_type_id() -> 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::() == type_id_of::() - // 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::() == type_name::() + impl NonStaticAny for PhantomData { + fn get_type_id(&self) -> TypeId + where + Self: 'static, + { + TypeId::of::() + } + } + + let phantom_data = PhantomData::; + 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.