Change stack return to standart sret semantics

This commit is contained in:
Dennis Kobert
2026-08-08 07:33:33 +00:00
parent 9c7a633d5b
commit 63cf0151e2
5 changed files with 191 additions and 45 deletions

View File

@@ -361,7 +361,7 @@ pub fn lift_poll<'e, T: Send + Sync + 'static>(poll: GPoll<T>, layout: &Layout,
} else {
let dst = stack::push(layout.frame_bytes());
let written = unsafe { write_element(dst, element, arena) };
stack::pop(dst);
stack::truncate_above(dst, layout.frame_bytes());
written.map(|()| RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }))
}
};
@@ -612,7 +612,8 @@ pub mod stack {
}
/// Returns the stack pointer to `frame`, a pointer earlier returned by
/// [`push`] on this thread, releasing everything above it.
/// [`push`] on this thread, releasing it and everything above it. Resets to
/// a checkpoint between repeated evaluations.
pub fn pop(frame: *mut u8) {
STACK.with(|stack| {
let offset = frame as usize - stack.base.get() as usize;
@@ -620,6 +621,66 @@ pub mod stack {
stack.sp.set(offset);
});
}
/// Releases everything above `frame`'s `bytes`-sized region, keeping the
/// region itself. A node reclaims its inputs' frames on return but leaves
/// its own output readable for its consumer.
pub fn truncate_above(frame: *mut u8, bytes: usize) {
STACK.with(|stack| {
let top = frame as usize - stack.base.get() as usize + bytes.next_multiple_of(8);
debug_assert!(top <= stack.sp.get(), "truncate target must lie within the claimed stack");
stack.sp.set(top);
});
}
/// The current stack pointer, a checkpoint to [`rewind`] to.
pub fn sp() -> usize {
STACK.with(|stack| stack.sp.get())
}
/// Resets the stack pointer to an earlier [`sp`] checkpoint, so a loop that
/// evaluates a subtree per iteration reuses the same slots each time.
///
/// # Safety
/// No `Rec` or `RecordValue` into the region above `mark` may be used after
/// this call. The caller must have copied out everything it still needs.
pub unsafe fn rewind(mark: usize) {
STACK.with(|stack| {
debug_assert!(mark <= stack.sp.get(), "rewind target above the stack pointer");
stack.sp.set(mark);
});
}
}
/// Reclaims the frames an inline node's inputs push. An inline node returns
/// its output by value rather than on the stack, so it has no frame whose
/// `truncate_above` would release its inputs; this guard captures the entry
/// pointer and rewinds to it on drop instead. Inactive (a no-op) for spilled
/// nodes, which release their inputs through their own frame.
pub struct ReclaimGuard {
target: usize,
}
impl ReclaimGuard {
/// # Safety
/// When `active`, the node must return its output by value, so that no
/// record into the region above the entry pointer is live once its eval
/// returns and the guard rewinds.
pub unsafe fn new(active: bool) -> Self {
Self {
target: if active { stack::sp() } else { usize::MAX },
}
}
}
impl Drop for ReclaimGuard {
fn drop(&mut self) {
if self.target != usize::MAX {
// SAFETY: an inline node returns its output by value, so no record into
// the reclaimed region is live once its eval returns.
unsafe { stack::rewind(self.target) };
}
}
}
/// Field-by-field carry from `from`'s layout into `to`'s, computed at
@@ -664,7 +725,7 @@ pub unsafe fn write_field<T>(dst: *mut u8, offset: usize, value: T) {
pub unsafe fn lift_poll_into<'e, T: Send + Sync + 'static>(poll: GPoll<T>, dst: *mut u8, frame_bytes: usize, arena: &'e crate::arena::Arena) -> GPoll<RecordValue<'e>> {
let release = || {
if frame_bytes != 0 {
stack::pop(dst);
stack::truncate_above(dst, frame_bytes);
}
};
let build = |element: T| {
@@ -750,27 +811,6 @@ pub unsafe fn read_element<T: Clone>(rec: Rec) -> T {
unsafe { borrow_element::<T>(rec) }.clone()
}
/// Borrows a record's element for the rest of the evaluation. Parked elements
/// borrow the arena, inline elements borrow their record value in place, and
/// a byte-carried spilled element copies into the arena first: its stack
/// region dies with the next push, and a borrow taken before the consumer's
/// own frame push always has one coming. `None` reports arena exhaustion.
///
/// # Safety
/// The record's element must be a `T` in the form [`element_parked`] picks,
/// and for inline layouts the record value must outlive the borrow.
pub unsafe fn borrow_or_park<'e, T: Send + Sync + 'static>(rec: Rec, layout: &Layout, arena: &'e crate::arena::Arena) -> Option<&'e T> {
if element_parked::<T>() {
return Some(unsafe { rec.element::<&T>() });
}
if layout.is_inline() {
return Some(unsafe { &*rec.ptr().cast::<T>() });
}
// A bitwise read duplicates soundly: byte-carried elements have no drop
// glue.
let value = unsafe { rec.ptr().cast::<T>().read() };
arena.alloc(value).map(|(parked, _)| parked)
}
/// # Safety
/// `dst` must be fresh element storage of a record whose element is `T`.
@@ -867,6 +907,7 @@ impl SourcePlan {
pub struct RecordSource<N> {
edge: N,
plan: Option<SourcePlan>,
union: Layout,
}
impl<N> RecordSource<N> {
@@ -874,6 +915,7 @@ impl<N> RecordSource<N> {
Self {
edge,
plan: SourcePlan::new(source, union),
union: union.clone(),
}
}
}
@@ -985,7 +1027,7 @@ impl OwnedRecord {
};
let written = self.write_into(layout, dst, arena);
if layout.frame_bytes() != 0 {
stack::pop(dst);
stack::truncate_above(dst, layout.frame_bytes());
value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) });
}
written.map(|()| value)
@@ -1094,6 +1136,10 @@ where
}
}
}
fn layout(&self) -> Option<&Layout> {
Some(&self.union)
}
}
#[cfg(test)]

View File

@@ -147,9 +147,22 @@ where
type Output = N::Output;
fn eval(&self, input: &Input) -> crate::gpoll::GPoll<Self::Output> {
// Every node advances the record stack by exactly its own frame: it keeps
// its output and reclaims its inputs. A mismatch is a leaked or
// over-released frame.
#[cfg(debug_assertions)]
let sp_before = crate::record::stack::sp();
// SAFETY: `own` keeps the payload alive for `self`'s lifetime and Arc
// payloads are address stable.
unsafe { self.ptr.as_ref() }.eval(input)
let result = unsafe { self.ptr.as_ref() }.eval(input);
#[cfg(debug_assertions)]
debug_assert_eq!(
crate::record::stack::sp(),
sp_before + self.layout().map_or(0, |layout| layout.frame_bytes()),
"{} left the record stack misaligned",
std::any::type_name::<N>(),
);
result
}
fn extent(&self, input: &Input) -> crate::gpoll::GPoll<crate::gpoll::Extent> {