Structure record stack rewinds as scope guards and make reserve unsafe

This commit is contained in:
Dennis Kobert
2026-08-28 18:02:50 +00:00
parent e5b910f840
commit f4d37524c8
15 changed files with 187 additions and 169 deletions
+1 -9
View File
@@ -19,21 +19,13 @@ use raster_types::BitmapMut;
use raster_types::Image;
use raster_types::{CPU, Raster};
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, dyn_any::DynAny)]
pub struct BrushStampGenerator<P: Pixel + Alpha> {
color: P,
feather_exponent: f32,
transform: DAffine2,
}
// SAFETY: `Static` is `Self` with `P` at its own static projection.
unsafe impl<P: Pixel + Alpha + dyn_any::StaticTypeSized> dyn_any::StaticType for BrushStampGenerator<P>
where
P::Static: Pixel + Alpha,
{
type Static = BrushStampGenerator<P::Static>;
}
impl<P: Pixel + Alpha> Transform for BrushStampGenerator<P> {
fn transform(&self) -> DAffine2 {
self.transform
+2 -2
View File
@@ -246,8 +246,8 @@ mod tests {
}
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
core_types::record::stack::reserve(1 << 16);
EvalScope::new(Some(0.5), None, None, generations, arena)
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { core_types::record::stack::reserve(1 << 16); } EvalScope::new(Some(0.5), None, None, generations, arena)
}
fn element_layout<T: Clone + Send + Sync + core_types::StaticTypeSized>() -> core_types::record::Layout
+5 -6
View File
@@ -538,8 +538,8 @@ mod tests {
}
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
stack::reserve(1 << 16);
EvalScope::new(Some(0.5), None, None, generations, arena)
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { stack::reserve(1 << 16); } EvalScope::new(Some(0.5), None, None, generations, arena)
}
fn f64_layout(names: &[&'static str]) -> Layout {
@@ -579,8 +579,8 @@ mod tests {
}
fn reserve_for(layouts: &[&Layout]) {
stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12));
}
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12)); } }
fn install<N: Node<ContextImpl<'static>>>(mut node: N, meta: core_types::record::LayoutMeta, inputs: &[Option<&Layout>]) -> N {
// The fixtures wire constants into every eager input, which the compiler
@@ -1660,14 +1660,13 @@ mod tests {
let head = ctx.index_head();
for (lane, element) in [(0u64, 10.), (1, 11.)] {
let mark = stack::sp();
let _lane_scope = unsafe { stack::ScopeGuard::enter() };
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane)) else {
panic!("expected a final record at lane {lane}");
};
let rec = out.rec(&value);
assert_eq!(unsafe { rec.element::<f64>() }, element, "lane {lane}");
assert_eq!(unsafe { rec.read::<&[NodeId]>(offset) }, path.as_slice(), "lane {lane}");
unsafe { stack::rewind(mark) };
}
}
+5 -5
View File
@@ -264,8 +264,8 @@ mod tests {
}
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
stack::reserve(1 << 16);
EvalScope::new(Some(0.5), None, None, generations, arena)
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { stack::reserve(1 << 16); } EvalScope::new(Some(0.5), None, None, generations, arena)
}
fn install<N: Node<ContextImpl<'static>>>(mut node: N, meta: record::LayoutMeta, inputs: &[Option<&Layout>]) -> N {
@@ -841,13 +841,13 @@ mod tests {
let wrap_out = Node::<ContextImpl>::layout(&wrapped).clone();
let head = ctx.index_head();
let group = {
let mark = stack::sp();
// SAFETY: the element is cloned out inside the scope, so no borrow
// into the frame escapes it.
let _scope = unsafe { stack::ScopeGuard::enter() };
let GPoll::Final(value) = wrapped.eval(&ctx.promoted(&head, 0)) else {
panic!("expected a final record");
};
let group = unsafe { record::borrow_element::<Graphic>(wrap_out.rec(&value)) }.clone();
// SAFETY: the element was cloned out above, so no borrow into the frame remains.
unsafe { stack::rewind(mark) };
group
};
+2 -2
View File
@@ -267,8 +267,8 @@ mod tests {
let probe = core_types::record::RecordLift::<RenderOutput, _>::new(ProbeNode);
let layout = Node::<ContextImpl>::layout(&probe).clone();
core_types::record::stack::reserve(layout.frame_bytes().max(1 << 12));
let mut graph = CreateContextNode::new(probe, &layout);
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { core_types::record::stack::reserve(layout.frame_bytes().max(1 << 12)); } let mut graph = CreateContextNode::new(probe, &layout);
// The executor resolves and installs the node's own layout at wiring;
// without it the flip tail writes through the default empty layout.
Node::<ContextImpl>::set_layout(
+2 -2
View File
@@ -1044,8 +1044,8 @@ mod graphene_test {
}
fn reserve_for(layouts: &[&Layout]) {
stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12));
}
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12)); } }
/// Lifts a plain-element test source onto a record wire, returned beside its
/// element-only layout for the generated node's constructor.
@@ -70,8 +70,8 @@ mod test {
#[test]
fn test_image_color_palette() {
core_types::record::stack::reserve(1 << 16);
let arena = core_types::arena::Arena::new(1 << 22).unwrap();
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { core_types::record::stack::reserve(1 << 16); } let arena = core_types::arena::Arena::new(1 << 22).unwrap();
let generations = [];
let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena);
let ctx = core_types::context::ContextImpl::root(&scope);
+2 -2
View File
@@ -216,8 +216,8 @@ mod test {
}
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
stack::reserve(1 << 12);
EvalScope::new(Some(0.5), None, None, generations, arena)
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { stack::reserve(1 << 12); } EvalScope::new(Some(0.5), None, None, generations, arena)
}
struct VectorRows {
+2 -2
View File
@@ -3870,8 +3870,8 @@ mod test {
}
#[test]
fn path_length() {
core_types::record::stack::reserve(1 << 16);
let arena = core_types::arena::Arena::new(1 << 20).unwrap();
// SAFETY: between evaluations, nothing served on the stack is live.
unsafe { core_types::record::stack::reserve(1 << 16); } let arena = core_types::arena::Arena::new(1 << 20).unwrap();
let generations = [];
let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena);
let ctx = core_types::context::ContextImpl::root(&scope);