don't compare function pointers -- they're non-deterministic #60

Merged
programmerjake merged 1 commit from programmerjake/fayalite:dont-compare-fn-ptrs into master 2026-01-12 11:19:58 +00:00
2 changed files with 75 additions and 18 deletions

View file

@ -2396,13 +2396,16 @@ impl ModuleBuilder {
#[track_caller] #[track_caller]
pub fn extern_module_simulation_fn< pub fn extern_module_simulation_fn<
Args: fmt::Debug + Clone + Hash + Eq + Send + Sync + 'static, Args: fmt::Debug + Clone + Hash + Eq + Send + Sync + 'static,
F: Copy + Fn(Args, crate::sim::ExternModuleSimulationState) -> Fut + Send + Sync + 'static,
Fut: IntoFuture<Output = ()> + 'static, Fut: IntoFuture<Output = ()> + 'static,
>( >(
&self, &self,
args: Args, args: Args,
f: fn(Args, crate::sim::ExternModuleSimulationState) -> Fut, f: F,
) { ) {
self.extern_module_simulation(crate::sim::SimGeneratorFn { args, f }); // use for compile-time side-effect
let _ = crate::sim::SimGeneratorFn::<Args, F, Fut>::ASSERT_F_IS_ZERO_SIZED;
self.extern_module_simulation(crate::sim::SimGeneratorFn::new(args, f));
} }
} }

View file

@ -54,7 +54,6 @@ use std::{
hash::Hash, hash::Hash,
mem, mem,
pin::{Pin, pin}, pin::{Pin, pin},
ptr,
rc::Rc, rc::Rc,
sync::{Arc, Mutex, MutexGuard}, sync::{Arc, Mutex, MutexGuard},
task::Poll, task::Poll,
@ -4065,13 +4064,48 @@ pub trait ExternModuleSimGenerator: Clone + Eq + Hash + Any + Send + Sync + fmt:
fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture<Output = ()> + 'a; fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture<Output = ()> + 'a;
} }
pub struct SimGeneratorFn<Args, Fut> { /// Type requirements: `F` must be zero-sized, this guarantees that the function called
pub args: Args, /// by `F` is entirely determined by the type `F` and not by its value, allowing us to
pub f: fn(Args, ExternModuleSimulationState) -> Fut, /// correctly leave `F` out of the stuff used for `Hash` and `Eq`.
///
/// Note we can't just use function pointers instead -- comparing them is non-deterministic
/// since Rust will duplicate and/or merge functions, even if those functions happen to have
/// different UB but just happen to compile to the same assembly language:
/// <https://github.com/rust-lang/unsafe-code-guidelines/issues/589#issuecomment-3515424930>
pub struct SimGeneratorFn<Args, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> {
args: Args,
f: F,
} }
impl<Args: fmt::Debug, Fut> fmt::Debug for SimGeneratorFn<Args, Fut> { impl<Args, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut>
SimGeneratorFn<Args, F, Fut>
{
pub const ASSERT_F_IS_ZERO_SIZED: () = {
if size_of::<F>() != 0 {
panic!(
"F must be zero-sized -- so it must be a closure with no captures or a function item, it can't be a function pointer"
);
}
};
pub const fn new(args: Args, f: F) -> Self {
// use for compile-time side-effect
let _ = Self::ASSERT_F_IS_ZERO_SIZED;
Self { args, f }
}
pub const fn args(&self) -> &Args {
&self.args
}
pub const fn f(&self) -> F {
self.f
}
}
impl<Args: fmt::Debug, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> fmt::Debug
for SimGeneratorFn<Args, F, Fut>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// use for compile-time side-effect
let _ = Self::ASSERT_F_IS_ZERO_SIZED;
let Self { args, f: _ } = self; let Self { args, f: _ } = self;
f.debug_struct("SimGeneratorFn") f.debug_struct("SimGeneratorFn")
.field("args", args) .field("args", args)
@ -4080,25 +4114,39 @@ impl<Args: fmt::Debug, Fut> fmt::Debug for SimGeneratorFn<Args, Fut> {
} }
} }
impl<Args: Hash, Fut> Hash for SimGeneratorFn<Args, Fut> { impl<Args: Hash, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> Hash
for SimGeneratorFn<Args, F, Fut>
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let Self { args, f } = self; // use for compile-time side-effect
let _ = Self::ASSERT_F_IS_ZERO_SIZED;
let Self { args, f: _ } = self;
args.hash(state); args.hash(state);
f.hash(state);
} }
} }
impl<Args: Eq, Fut> Eq for SimGeneratorFn<Args, Fut> {} impl<Args: Eq, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> Eq
for SimGeneratorFn<Args, F, Fut>
{
}
impl<Args: PartialEq, Fut> PartialEq for SimGeneratorFn<Args, Fut> { impl<Args: PartialEq, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> PartialEq
for SimGeneratorFn<Args, F, Fut>
{
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
let Self { args, f } = self; // use for compile-time side-effect
*args == other.args && ptr::fn_addr_eq(*f, other.f) let _ = Self::ASSERT_F_IS_ZERO_SIZED;
let Self { args, f: _ } = self;
*args == other.args
} }
} }
impl<Args: Clone, Fut> Clone for SimGeneratorFn<Args, Fut> { impl<Args: Clone, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> Clone
for SimGeneratorFn<Args, F, Fut>
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
// use for compile-time side-effect
let _ = Self::ASSERT_F_IS_ZERO_SIZED;
Self { Self {
args: self.args.clone(), args: self.args.clone(),
f: self.f, f: self.f,
@ -4106,14 +4154,20 @@ impl<Args: Clone, Fut> Clone for SimGeneratorFn<Args, Fut> {
} }
} }
impl<Args: Copy, Fut> Copy for SimGeneratorFn<Args, Fut> {} impl<Args: Copy, F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut, Fut> Copy
for SimGeneratorFn<Args, F, Fut>
{
}
impl< impl<
T: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, Args: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static,
F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut + Send + Sync + 'static,
Fut: IntoFuture<Output = ()> + 'static, Fut: IntoFuture<Output = ()> + 'static,
> ExternModuleSimGenerator for SimGeneratorFn<T, Fut> > ExternModuleSimGenerator for SimGeneratorFn<Args, F, Fut>
{ {
fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture<Output = ()> + 'a { fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture<Output = ()> + 'a {
// use for compile-time side-effect
let _ = Self::ASSERT_F_IS_ZERO_SIZED;
(self.f)(self.args.clone(), sim) (self.f)(self.args.clone(), sim)
} }
} }