diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs
index 4c74b8163..8b7c56d55 100644
--- a/packages/core/src/properties.rs
+++ b/packages/core/src/properties.rs
@@ -155,7 +155,7 @@ where
}
fn fn_ptr(&self) -> usize {
- subsecond::HotFn::current(self.clone()).ptr_address() as usize
+ subsecond::HotFn::current(self.clone()).ptr_address().0 as usize
}
}
@@ -170,7 +170,7 @@ where
}
fn fn_ptr(&self) -> usize {
- subsecond::HotFn::current(self.clone()).ptr_address() as usize
+ subsecond::HotFn::current(self.clone()).ptr_address().0 as usize
}
}
diff --git a/packages/server/src/launch.rs b/packages/server/src/launch.rs
index 3b094ebdc..d4dc6cebc 100644
--- a/packages/server/src/launch.rs
+++ b/packages/server/src/launch.rs
@@ -163,7 +163,7 @@ async fn serve_server(
}
let hot_root = subsecond::HotFn::current(original_root);
- let new_root_addr = hot_root.ptr_address() as usize as *const ();
+ let new_root_addr = hot_root.ptr_address().0 as usize as *const ();
let new_root = unsafe {
std::mem::transmute::<*const (), fn() -> Element>(new_root_addr)
};
diff --git a/packages/subsecond/subsecond/src/lib.rs b/packages/subsecond/subsecond/src/lib.rs
index 6a2f71b90..bc502198d 100644
--- a/packages/subsecond/subsecond/src/lib.rs
+++ b/packages/subsecond/subsecond/src/lib.rs
@@ -313,6 +313,24 @@ pub struct HotFnPanic {
_backtrace: backtrace::Backtrace,
}
+/// A pointer to a hot patched function
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
+pub struct HotFnPtr(pub u64);
+
+impl HotFnPtr {
+ /// Create a new [`HotFnPtr`].
+ ///
+ /// The safe way to get one is through [`HotFn::ptr_address`].
+ ///
+ /// # Safety
+ ///
+ /// The underlying `u64` must point to a valid function.
+ pub unsafe fn new(index: u64) -> Self {
+ Self(index)
+ }
+}
+
/// A hot-reloadable function.
///
/// To call this function, use the [`HotFn::call`] method. This will automatically use the latest
@@ -353,20 +371,20 @@ impl> HotFn {
///
/// Note that Subsecond does not track this state over time, so it's up to the runtime integration
/// to track this state and diff it.
- pub fn ptr_address(&self) -> u64 {
+ pub fn ptr_address(&self) -> HotFnPtr {
if size_of::() == size_of:: ()>() {
let ptr: usize = unsafe { std::mem::transmute_copy(&self.inner) };
- return ptr as u64;
+ return HotFnPtr(ptr as u64);
}
let known_fn_ptr = >::call_it as *const () as usize;
if let Some(jump_table) = get_jump_table() {
if let Some(ptr) = jump_table.map.get(&(known_fn_ptr as u64)).cloned() {
- return ptr;
+ return HotFnPtr(ptr);
}
}
- known_fn_ptr as u64
+ HotFnPtr(known_fn_ptr as u64)
}
/// Attempt to call the function with the given arguments.
@@ -405,6 +423,42 @@ impl> HotFn {
Ok(self.inner.call_it(args))
}
}
+
+ /// Attempt to call the function with the given arguments, using the given [`HotFnPtr`].
+ ///
+ /// You can get a [`HotFnPtr`] from [`Self::ptr_address`].
+ ///
+ /// If this function is stale and can't be updated in place (ie, changes occurred above this call),
+ /// then this function will emit an [`HotFnPanic`] which can be unwrapped and handled by next [`call`]
+ /// instance.
+ ///
+ /// # Safety
+ ///
+ /// The [`HotFnPtr`] must be to a function whose arguments layouts haven't changed.
+ pub unsafe fn try_call_with_ptr(&mut self, ptr: HotFnPtr, args: A) -> Result {
+ if !cfg!(debug_assertions) {
+ return Ok(self.inner.call_it(args));
+ }
+
+ unsafe {
+ // Try to handle known function pointers. This is *really really* unsafe, but due to how
+ // rust trait objects work, it's impossible to make an arbitrary usize-sized type implement Fn()
+ // since that would require a vtable pointer, pushing out the bounds of the pointer size.
+ if size_of::() == size_of:: ()>() {
+ return Ok(self.inner.call_as_ptr(args));
+ }
+
+ // Handle trait objects. This will occur for sizes other than usize. Normal rust functions
+ // become ZST's and thus their ::call becomes a function pointer to the function.
+ //
+ // For non-zst (trait object) types, then there might be an issue. The real call function
+ // will likely end up in the vtable and will never be hot-reloaded since signature takes self.
+ // The type sig of the cast should match the call_it function
+ // Technically function pointers need to be aligned, but that alignment is 1 so we're good
+ let call_it = transmute::<*const (), fn(&F, A) -> F::Return>(ptr.0 as _);
+ Ok(call_it(&self.inner, args))
+ }
+ }
}
/// Apply the patch using a given jump table.