mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Pass frame space by argument and delete the record stack
This commit is contained in:
@@ -175,6 +175,15 @@ impl Arena {
|
||||
Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
|
||||
}
|
||||
|
||||
/// The generation-checked handle for a region this arena holds, `None` for
|
||||
/// a pointer from anywhere else. The handle keeps the region's provenance,
|
||||
/// so a cache stores one where it would otherwise launder an address.
|
||||
pub fn handle_at(&self, ptr: *const u8) -> Option<ArenaWeak<u8>> {
|
||||
let offset = (ptr as usize).checked_sub(self.base() as usize)?;
|
||||
(offset < self.buf.len()).then_some(())?;
|
||||
ArenaWeak::new(self.generation(), offset)
|
||||
}
|
||||
|
||||
/// `false` once generations are exhausted, parking the arena on [`PARKED_GENERATION`]
|
||||
/// where every handle misses and further allocation is refused.
|
||||
pub fn reset(&mut self) -> bool {
|
||||
|
||||
@@ -105,10 +105,9 @@ pub struct RecordBatchMut<'a> {
|
||||
}
|
||||
|
||||
impl<'a> RecordBatchMut<'a> {
|
||||
/// # Safety
|
||||
/// `scratch` must start with `len` initialized records of `layout`, packed
|
||||
/// at `layout.lane_stride()` stride.
|
||||
pub unsafe fn new(scratch: &'a mut [MaybeUninit<u64>], len: usize, layout: &'a crate::record::Layout) -> Self {
|
||||
/// Minted only by a [`crate::record::SlotRun`] finishing its served lanes,
|
||||
/// which is what makes the initialized prefix a fact rather than a contract.
|
||||
pub(crate) fn new(scratch: &'a mut [MaybeUninit<u64>], len: usize, layout: &'a crate::record::Layout) -> Self {
|
||||
debug_assert!(len * layout.lane_stride() <= scratch.len() * 8);
|
||||
Self { scratch, len, layout }
|
||||
}
|
||||
@@ -318,9 +317,10 @@ pub trait Node<Input> {
|
||||
/// Serves the node's record through the caller's claim: the writes land
|
||||
/// in the claim and the returned proof is mintable only by its closing
|
||||
/// methods, so the served record is of the claimed layout by
|
||||
/// construction. The caller claims the frame at [`Node::layout`] before
|
||||
/// the call, so the node advances the record stack by exactly that frame.
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'l>) -> GPoll<crate::record::Served<'e>>
|
||||
/// construction. The caller claims the frame at [`Node::layout`] out of
|
||||
/// its own frame space, and the claim carries what is left, so the node
|
||||
/// takes exactly its own frame out of the caller's free space.
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'e, 'l>) -> GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>;
|
||||
|
||||
@@ -328,7 +328,7 @@ pub trait Node<Input> {
|
||||
/// leveled primitive a structure node overrides to report a pushed level's
|
||||
/// size; the scalar base is one item at every level. Uncertainty rides the
|
||||
/// `GPoll` status axis.
|
||||
fn extent_at<'e>(&self, _input: &Input, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'e>(&self, _input: &Input, _level: u8, _frames: &crate::record::Frames<'e>) -> GPoll<Extent>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
@@ -338,16 +338,17 @@ pub trait Node<Input> {
|
||||
/// The composite domain query derived from [`extent_at`](Node::extent_at):
|
||||
/// one level, the product of the levels below or above it, or the whole
|
||||
/// domain's flat count. Consumers query this; nodes only write `extent_at`.
|
||||
fn extent<'e>(&self, input: &Input, at: Level) -> GPoll<Extent>
|
||||
fn extent<'e>(&self, input: &Input, at: Level, frames: &crate::record::Frames<'e>) -> GPoll<Extent>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
let product = |range: core::ops::Range<u8>| range.fold(GPoll::Final(Extent::Exactly(1)), |acc, level| Extent::mul(acc, self.extent_at(input, level)));
|
||||
let product =
|
||||
|range: core::ops::Range<u8>, frames: &crate::record::Frames<'e>| range.fold(GPoll::Final(Extent::Exactly(1)), |acc, level| Extent::mul(acc, self.extent_at(input, level, frames)));
|
||||
match at {
|
||||
Level::At(level) => self.extent_at(input, level),
|
||||
Level::Below(level) => product(0..level),
|
||||
Level::Above(level) => product((level + 1)..self.depth()),
|
||||
Level::Total => product(0..self.depth()),
|
||||
Level::At(level) => self.extent_at(input, level, frames),
|
||||
Level::Below(level) => product(0..level, frames),
|
||||
Level::Above(level) => product((level + 1)..self.depth(), frames),
|
||||
Level::Total => product(0..self.depth(), frames),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,8 +365,8 @@ pub trait Node<Input> {
|
||||
|
||||
/// The record layout of this node's output; the shared empty layout for
|
||||
/// element-only producers. Consumers read their carrier's layout through
|
||||
/// this at wiring, and the wiring layer derives stack sizing from the same
|
||||
/// layouts, in the dynamic executor and exported source alike.
|
||||
/// this at wiring, and the wiring layer derives the root buffer's sizing from
|
||||
/// the same layouts, in the dynamic executor and exported source alike.
|
||||
fn layout(&self) -> &crate::record::Layout {
|
||||
crate::record::empty_layout()
|
||||
}
|
||||
@@ -379,11 +380,11 @@ pub trait Node<Input> {
|
||||
/// with copy-out ([`crate::record::fill_frames`]); overrides exist to beat
|
||||
/// that loop (resident lanes, direct fills, fewer erased calls), never for
|
||||
/// correctness.
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>) -> BatchStatus<'a>
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>, frames: &crate::record::Frames<'e>) -> BatchStatus<'a>
|
||||
where
|
||||
Input: InjectIndex + Copy + crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
let _ = (input, range, scratch);
|
||||
let _ = (input, range, scratch, frames);
|
||||
BatchStatus::Unbatched
|
||||
}
|
||||
}
|
||||
@@ -392,18 +393,18 @@ impl<Input, N> Node<Input> for &N
|
||||
where
|
||||
N: Node<Input> + ?Sized,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'l>) -> GPoll<crate::record::Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'e, 'l>) -> GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).serve(input, slot)
|
||||
}
|
||||
|
||||
fn extent_at<'e>(&self, input: &Input, level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'e>(&self, input: &Input, level: u8, frames: &crate::record::Frames<'e>) -> GPoll<Extent>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).extent_at(input, level)
|
||||
(**self).extent_at(input, level, frames)
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
@@ -414,11 +415,11 @@ where
|
||||
(**self).layout()
|
||||
}
|
||||
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>) -> BatchStatus<'a>
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>, frames: &crate::record::Frames<'e>) -> BatchStatus<'a>
|
||||
where
|
||||
Input: InjectIndex + Copy + crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).eval_batch(input, range, scratch)
|
||||
(**self).eval_batch(input, range, scratch, frames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,18 +427,18 @@ impl<Input, N> Node<Input> for Box<N>
|
||||
where
|
||||
N: Node<Input> + ?Sized,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'l>) -> GPoll<crate::record::Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'e, 'l>) -> GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).serve(input, slot)
|
||||
}
|
||||
|
||||
fn extent_at<'e>(&self, input: &Input, level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'e>(&self, input: &Input, level: u8, frames: &crate::record::Frames<'e>) -> GPoll<Extent>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).extent_at(input, level)
|
||||
(**self).extent_at(input, level, frames)
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
@@ -448,11 +449,11 @@ where
|
||||
(**self).layout()
|
||||
}
|
||||
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>) -> BatchStatus<'a>
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>, frames: &crate::record::Frames<'e>) -> BatchStatus<'a>
|
||||
where
|
||||
Input: InjectIndex + Copy + crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).eval_batch(input, range, scratch)
|
||||
(**self).eval_batch(input, range, scratch, frames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,18 +461,18 @@ impl<Input, N> Node<Input> for std::sync::Arc<N>
|
||||
where
|
||||
N: Node<Input> + ?Sized,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'l>) -> GPoll<crate::record::Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'e, 'l>) -> GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).serve(input, slot)
|
||||
}
|
||||
|
||||
fn extent_at<'e>(&self, input: &Input, level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'e>(&self, input: &Input, level: u8, frames: &crate::record::Frames<'e>) -> GPoll<Extent>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).extent_at(input, level)
|
||||
(**self).extent_at(input, level, frames)
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
@@ -482,11 +483,11 @@ where
|
||||
(**self).layout()
|
||||
}
|
||||
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>) -> BatchStatus<'a>
|
||||
fn eval_batch<'a, 'e>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<u64>]>, frames: &crate::record::Frames<'e>) -> BatchStatus<'a>
|
||||
where
|
||||
Input: InjectIndex + Copy + crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
(**self).eval_batch(input, range, scratch)
|
||||
(**self).eval_batch(input, range, scratch, frames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,15 +517,15 @@ impl StatusCell {
|
||||
Self { no_partial: true, ..Self::new() }
|
||||
}
|
||||
|
||||
/// Claims the edge's own frame, serves through it, and folds the poll's
|
||||
/// status into the cell. The claim is the caller's, so the edge's frame is
|
||||
/// entered exactly once per evaluation.
|
||||
/// Claims the edge's own frame out of `frames`, serves through it, and
|
||||
/// folds the poll's status into the cell. The claim is the caller's, so
|
||||
/// the edge's frame is claimed exactly once per evaluation.
|
||||
#[inline(always)]
|
||||
pub fn eval_input<'e, Input, N: Node<Input> + ?Sized>(&self, input_index: usize, node: &N, input: &Input) -> Result<crate::record::RecordValue<'e>, Interrupt>
|
||||
pub fn eval_input<'e, Input, N: Node<Input> + ?Sized>(&self, input_index: usize, node: &N, input: &Input, frames: &crate::record::Frames<'e>) -> Result<crate::record::RecordValue<'e>, Interrupt>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
let slot = crate::record::FrameClaim::enter(node.layout());
|
||||
let slot = frames.claim(node.layout());
|
||||
match node.serve(input, slot) {
|
||||
GPoll::Final(served) => Ok(served.value()),
|
||||
GPoll::Partial(_) if self.no_partial => Err(Interrupt::Pending),
|
||||
@@ -588,23 +589,25 @@ impl StatusCell {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LazyInput<'a, N> {
|
||||
pub struct LazyInput<'a, 'f, N> {
|
||||
node: &'a N,
|
||||
cell: &'a StatusCell,
|
||||
input_index: usize,
|
||||
frames: &'a crate::record::Frames<'f>,
|
||||
}
|
||||
|
||||
impl<'a, N> LazyInput<'a, N> {
|
||||
pub fn new(node: &'a N, cell: &'a StatusCell, input_index: usize) -> Self {
|
||||
Self { node, cell, input_index }
|
||||
impl<'a, 'f, N> LazyInput<'a, 'f, N> {
|
||||
pub fn new(node: &'a N, cell: &'a StatusCell, input_index: usize, frames: &'a crate::record::Frames<'f>) -> Self {
|
||||
Self { node, cell, input_index, frames }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn eval<'e, Input>(&self, ctx: &Input) -> Result<crate::record::RecordValue<'e>, Interrupt>
|
||||
where
|
||||
N: crate::record::DerivedRecordEdge<'e, Input>,
|
||||
'f: 'e,
|
||||
{
|
||||
self.node.eval_derived(self.cell, self.input_index, ctx)
|
||||
self.node.eval_derived(self.cell, self.input_index, ctx, self.frames)
|
||||
}
|
||||
|
||||
/// The edge's composite extent, for kernels that split or shift indices
|
||||
@@ -614,8 +617,9 @@ impl<'a, N> LazyInput<'a, N> {
|
||||
where
|
||||
N: Node<Input>,
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
'f: 'e,
|
||||
{
|
||||
self.node.extent(ctx, at)
|
||||
self.node.extent(ctx, at, self.frames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,12 +657,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_default_advertises_no_batch_support() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let input = TestInput { index: 0, arena: &arena };
|
||||
let mut scratch = [const { MaybeUninit::uninit() }; 4];
|
||||
let node = double();
|
||||
assert!(matches!(node.eval_batch(&input, 2..6, Some(&mut scratch)), BatchStatus::Unbatched));
|
||||
assert!(matches!(node.eval_batch(&input, 2..6, None), BatchStatus::Unbatched));
|
||||
assert!(matches!(node.eval_batch(&input, 2..6, Some(&mut scratch), &frames), BatchStatus::Unbatched));
|
||||
assert!(matches!(node.eval_batch(&input, 2..6, None, &frames), BatchStatus::Unbatched));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -668,15 +673,12 @@ mod tests {
|
||||
let node = double();
|
||||
let layout = Node::<TestInput>::layout(&node).clone();
|
||||
let erased: Box<dyn Node<TestInput>> = Box::new(node);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
crate::record::stack::reserve(1 << 12);
|
||||
}
|
||||
let GPoll::Final(value) = serve_edge(&*erased, &input) else {
|
||||
let frames = crate::record::test_frames(1 << 12);
|
||||
let GPoll::Final(value) = serve_edge(&*erased, &input, &frames) else {
|
||||
panic!("the erased edge must serve a final record");
|
||||
};
|
||||
// SAFETY: the record was served at `layout`, whose element is the output.
|
||||
assert_eq!(unsafe { crate::record::read_element::<u64>(layout.rec(&value)) }, 42);
|
||||
assert!(matches!(erased.eval_batch(&input, 0..2, None), BatchStatus::Unbatched));
|
||||
assert!(matches!(erased.eval_batch(&input, 0..2, None, &frames), BatchStatus::Unbatched));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -136,62 +136,24 @@ impl<Input, N> Node<Input> for SharedEdge<N>
|
||||
where
|
||||
N: Node<Input> + ?Sized,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'l>) -> crate::gpoll::GPoll<crate::record::Served<'e>>
|
||||
/// A node takes exactly its own frame out of its caller's free space: the
|
||||
/// caller minted the claim and kept the cursor, so the frame accounting is
|
||||
/// structural here and asserted where a claim is split.
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'e, 'l>) -> crate::gpoll::GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
// Every node advances the record stack by exactly its own frame: the
|
||||
// caller claimed it before the call, so serving must leave the stack
|
||||
// where it found it. A mismatch is a leaked or over-released frame.
|
||||
#[cfg(debug_assertions)]
|
||||
let sp_before = crate::record::stack::sp();
|
||||
#[cfg(debug_assertions)]
|
||||
let trace = {
|
||||
static TRACE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
|
||||
*TRACE.get_or_init(|| std::env::var_os("GRAPHENE_SP_DEBUG").is_some())
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
if trace {
|
||||
eprintln!(
|
||||
"sp> enter frame_bytes {} fields [{}] sp {}",
|
||||
self.layout().frame_bytes(),
|
||||
self.layout().fields.iter().map(|field| field.name.to_string()).collect::<Vec<_>>().join(", "),
|
||||
sp_before,
|
||||
);
|
||||
}
|
||||
// SAFETY: `own` keeps the payload alive for `self`'s lifetime and Arc
|
||||
// payloads are address stable.
|
||||
let result = unsafe { self.ptr.as_ref() }.serve(input, slot);
|
||||
#[cfg(debug_assertions)]
|
||||
if trace {
|
||||
eprintln!("sp> exit frame_bytes {} sp {} -> {}", self.layout().frame_bytes(), sp_before, crate::record::stack::sp());
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert_eq!(
|
||||
crate::record::stack::sp(),
|
||||
sp_before,
|
||||
"{} left the record stack misaligned (frame_bytes {}, depth {}, fields [{}], poll {})",
|
||||
std::any::type_name::<N>(),
|
||||
self.layout().frame_bytes(),
|
||||
self.layout().depth,
|
||||
self.layout().fields.iter().map(|field| field.name.to_string()).collect::<Vec<_>>().join(", "),
|
||||
match &result {
|
||||
crate::gpoll::GPoll::Final(_) => "Final",
|
||||
crate::gpoll::GPoll::Partial(_) => "Partial",
|
||||
crate::gpoll::GPoll::Fallback(_) => "Fallback",
|
||||
crate::gpoll::GPoll::Pending => "Pending",
|
||||
crate::gpoll::GPoll::Error(_) => "Error",
|
||||
},
|
||||
);
|
||||
result
|
||||
unsafe { self.ptr.as_ref() }.serve(input, slot)
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, input: &Input, level: u8) -> crate::gpoll::GPoll<crate::gpoll::Extent>
|
||||
fn extent_at<'x>(&self, input: &Input, level: u8, frames: &crate::record::Frames<'x>) -> crate::gpoll::GPoll<crate::gpoll::Extent>
|
||||
where
|
||||
Input: crate::context::ExtractArena<ArenaRef = &'x crate::arena::Arena>,
|
||||
{
|
||||
// SAFETY: as in serve.
|
||||
unsafe { self.ptr.as_ref() }.extent_at(input, level)
|
||||
unsafe { self.ptr.as_ref() }.extent_at(input, level, frames)
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
@@ -204,12 +166,18 @@ where
|
||||
unsafe { self.ptr.as_ref() }.layout()
|
||||
}
|
||||
|
||||
fn eval_batch<'a, 'x>(&'a self, input: &'a Input, range: std::ops::Range<u64>, scratch: Option<&'a mut [std::mem::MaybeUninit<u64>]>) -> crate::node::BatchStatus<'a>
|
||||
fn eval_batch<'a, 'x>(
|
||||
&'a self,
|
||||
input: &'a Input,
|
||||
range: std::ops::Range<u64>,
|
||||
scratch: Option<&'a mut [std::mem::MaybeUninit<u64>]>,
|
||||
frames: &crate::record::Frames<'x>,
|
||||
) -> crate::node::BatchStatus<'a>
|
||||
where
|
||||
Input: crate::context::InjectIndex + Copy + crate::context::ExtractArena<ArenaRef = &'x crate::arena::Arena>,
|
||||
{
|
||||
// SAFETY: as in serve.
|
||||
unsafe { self.ptr.as_ref() }.eval_batch(input, range, scratch)
|
||||
unsafe { self.ptr.as_ref() }.eval_batch(input, range, scratch, frames)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +318,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use crate::record::{FrameClaim, Layout, LiftedSource, Served, element_write, read_element, serve_edge, stack};
|
||||
use crate::record::{FrameClaim, Layout, LiftedSource, Served, element_write, read_element, serve_edge};
|
||||
|
||||
fn counting() -> LiftedSource<u32, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<u32>> {
|
||||
let count = AtomicU32::new(0);
|
||||
@@ -378,7 +346,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<Input: Ctx> Node<Input> for LendNode {
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &Input, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
Input: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
@@ -399,10 +367,7 @@ mod tests {
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
stack::reserve(1 << 12);
|
||||
}
|
||||
let frames = crate::record::test_frames(1 << 12);
|
||||
|
||||
let node = LendNode::new("held");
|
||||
let layout = Node::<ContextImpl>::layout(&node).clone();
|
||||
@@ -410,7 +375,7 @@ mod tests {
|
||||
assert_eq!(*handle.ty(), concrete!(String));
|
||||
|
||||
let wired = handle.downcast_erased::<ErasedRecordNode>(concrete!(String)).unwrap();
|
||||
let GPoll::Final(value) = serve_edge(&wired, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&wired, &ctx, &frames) else {
|
||||
panic!("borrow-carrying output must serve through the erased edge");
|
||||
};
|
||||
// SAFETY: the record was served at `layout`, whose element is the borrow.
|
||||
@@ -450,7 +415,7 @@ mod tests {
|
||||
Vec<T>: Send + Sync + dyn_any::StaticTypeSized,
|
||||
Node0: for<'x> crate::record::DerivedRecordEdge<'x, crate::context::Derived<'x, C>>,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, mut slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
@@ -458,11 +423,11 @@ mod tests {
|
||||
let spilled = input.index_head();
|
||||
let mut result = Vec::new();
|
||||
for index in 0..3 {
|
||||
// SAFETY: the element copies out by value, so the content's
|
||||
// frame is dead by the time the scope releases it.
|
||||
let _scope = unsafe { stack::ScopeGuard::enter() };
|
||||
// The element copies out by value, so the content's frame is
|
||||
// dead when the scope ends.
|
||||
let scope = slot.frames().scope();
|
||||
let derived = input.promoted(&spilled, index);
|
||||
match self.content.eval_derived(&cell, 0, &derived) {
|
||||
match self.content.eval_derived(&cell, 0, &derived, &scope) {
|
||||
// SAFETY: the content served at its own layout, whose
|
||||
// element is `T`.
|
||||
Ok(value) => result.push(unsafe { read_element::<T>(self.inner.rec(&value)) }),
|
||||
@@ -491,12 +456,9 @@ mod tests {
|
||||
let nested = RepeatNode::<_, Vec<Vec<usize>>>::new(inner, inner_layout);
|
||||
let layout = Node::<ContextImpl>::layout(&nested).clone();
|
||||
let erased: Box<ErasedRecordNode> = Box::new(nested);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
stack::reserve(1 << 12);
|
||||
}
|
||||
let frames = crate::record::test_frames(1 << 12);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&*erased, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&*erased, &ctx, &frames) else {
|
||||
panic!("nested repeat must evaluate");
|
||||
};
|
||||
// SAFETY: the record was served at `layout`, whose element is the output.
|
||||
@@ -529,7 +491,7 @@ mod tests {
|
||||
C: Ctx + crate::context::DeriveCtx + crate::context::ExtractFootprint,
|
||||
Node0: for<'x> crate::record::DerivedRecordEdge<'x, crate::context::Derived<'x, C>>,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, mut slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
@@ -537,12 +499,12 @@ mod tests {
|
||||
let cell = crate::node::StatusCell::new();
|
||||
let mut footprint = input.try_footprint().copied().unwrap_or(Footprint::DEFAULT);
|
||||
footprint.resolution.x += 7;
|
||||
// SAFETY: the element copies out by value, so the content's frame is
|
||||
// dead by the time the scope releases it.
|
||||
// The element copies out by value, so the content's frame is dead
|
||||
// when the scope ends.
|
||||
let value = {
|
||||
let _scope = unsafe { stack::ScopeGuard::enter() };
|
||||
let scope = slot.frames().scope();
|
||||
let derived = input.with_footprint(&footprint);
|
||||
match self.content.eval_derived(&cell, 0, &derived) {
|
||||
match self.content.eval_derived(&cell, 0, &derived, &scope) {
|
||||
// SAFETY: the content served at its own layout, whose
|
||||
// element is the resolution.
|
||||
Ok(value) => unsafe { read_element::<u32>(self.inner.rec(&value)) },
|
||||
@@ -566,10 +528,7 @@ mod tests {
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
stack::reserve(1 << 12);
|
||||
}
|
||||
let frames = crate::record::test_frames(1 << 12);
|
||||
|
||||
let resolution = LiftedSource::<u32, _>::new(|input: &ContextImpl| GPoll::Final(input.try_footprint().map(|footprint| footprint.resolution.x).unwrap_or(0)));
|
||||
let resolution_layout = Node::<ContextImpl>::layout(&resolution).clone();
|
||||
@@ -578,7 +537,7 @@ mod tests {
|
||||
let graph = ShiftFootprintNode::new(shifted, shifted_layout);
|
||||
let layout = Node::<ContextImpl>::layout(&graph).clone();
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("the footprint shift must reach the content");
|
||||
};
|
||||
// SAFETY: the record was served at `layout`, whose element is the resolution.
|
||||
@@ -626,19 +585,16 @@ mod tests {
|
||||
let handle = EdgeHandle::new_record::<u32>(Arc::new(counting) as Arc<ErasedRecordNode>);
|
||||
let duplicate = handle.duplicate();
|
||||
assert_eq!(*duplicate.ty(), record_edge_type::<u32>());
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
stack::reserve(1 << 12);
|
||||
}
|
||||
let frames = crate::record::test_frames(1 << 12);
|
||||
|
||||
let first = handle.downcast_record::<u32>().unwrap();
|
||||
let second = duplicate.downcast_record::<u32>().unwrap();
|
||||
// SAFETY: each record was served at `layout`, whose element is the count.
|
||||
let count = |value| unsafe { layout.rec(&value).element::<u32>() };
|
||||
assert_eq!(serve_edge(&first, &ctx).map(count), GPoll::Final(1));
|
||||
assert_eq!(serve_edge(&second, &ctx).map(count), GPoll::Final(2));
|
||||
assert_eq!(serve_edge(&first, &ctx, &frames).map(count), GPoll::Final(1));
|
||||
assert_eq!(serve_edge(&second, &ctx, &frames).map(count), GPoll::Final(2));
|
||||
|
||||
drop(first);
|
||||
assert_eq!(serve_edge(&second, &ctx).map(count), GPoll::Final(3));
|
||||
assert_eq!(serve_edge(&second, &ctx, &frames).map(count), GPoll::Final(3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ mod tests {
|
||||
use crate::context::{ContextImpl, Ctx, EvalScope, ExtractFootprint, ExtractVarArgs, VarArgLink, VarArgSlots};
|
||||
use crate::gpoll::GPoll;
|
||||
use crate::node::Node;
|
||||
use crate::record::{Layout, LiftedSource, RecordExtract, element_write, stack};
|
||||
use crate::record::{Layout, LiftedSource, RecordExtract, element_write};
|
||||
use crate::transform::Footprint;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
@@ -266,10 +266,6 @@ mod tests {
|
||||
where
|
||||
El::Static: Clone + Send + Sync,
|
||||
{
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
stack::reserve(1 << 12);
|
||||
}
|
||||
let layout = element_layout::<El>();
|
||||
graph.set_layout(crate::record::RecordLayout {
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
@@ -339,6 +335,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn async_source_spawns_once_and_lands_via_the_slot() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -354,18 +351,19 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert_eq!(SLOW_DOUBLE_RUNS.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(runtime.drain(), vec![7]);
|
||||
assert_eq!(SLOW_DOUBLE_RUNS.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
assert_eq!(SLOW_DOUBLE_RUNS.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_source_reports_the_placeholder_while_in_flight() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -381,13 +379,14 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Partial(-1.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Partial(-1.0));
|
||||
runtime.drain();
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_partial_maps_the_placeholder_frame_to_pending() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -403,13 +402,14 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
runtime.drain();
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prologue_runs_sync_and_spawns_once() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -425,17 +425,18 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert_eq!(STAGED_RUNS.load(Ordering::Relaxed), 1, "the prologue runs synchronously on the miss");
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert_eq!(STAGED_RUNS.load(Ordering::Relaxed), 1, "in flight must not rerun the prologue");
|
||||
assert_eq!(runtime.drain(), vec![8]);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
assert_eq!(STAGED_RUNS.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prologue_interrupt_defers_the_spawn() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -454,16 +455,17 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert_eq!(runtime.drain(), Vec::<SourceId>::new(), "an interrupted prologue must not spawn or claim the slot");
|
||||
gate.store(true, Ordering::Relaxed);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert_eq!(runtime.drain(), vec![9]);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_kernels_read_captured_varargs() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -485,13 +487,14 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
runtime.drain();
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(21.5));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(21.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_kernels_read_the_captured_context_snapshot() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -509,9 +512,9 @@ mod tests {
|
||||
&element_layout::<u64>(),
|
||||
));
|
||||
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
runtime.drain();
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(Footprint::DEFAULT.resolution.x));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(Footprint::DEFAULT.resolution.x));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -610,6 +613,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn an_immediately_ready_kernel_returns_final_on_the_first_eval() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let runtime = Arc::new(GraphRuntime::new(CollectSpawner::default()));
|
||||
runtime.retain_sources(&[13]);
|
||||
@@ -625,7 +629,7 @@ mod tests {
|
||||
let snapshot = runtime.snapshot();
|
||||
let scope = EvalScope::new(None, None, None, &snapshot, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Final(42.0));
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Final(42.0));
|
||||
assert!(!runtime.take_dirty());
|
||||
assert_eq!(runtime.snapshot(), vec![(13, 0)]);
|
||||
assert_eq!(runtime.spawner().drain(), 0);
|
||||
@@ -633,6 +637,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_source_slot_lands_through_the_runtime_while_downstream_keys_invalidate() {
|
||||
let frames = crate::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let runtime = Arc::new(GraphRuntime::new(CollectSpawner::default()));
|
||||
runtime.retain_sources(&[11]);
|
||||
@@ -648,7 +653,7 @@ mod tests {
|
||||
let snapshot = runtime.snapshot();
|
||||
let scope = EvalScope::new(None, None, None, &snapshot, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
assert_eq!(graph.eval(&ctx), GPoll::Pending);
|
||||
assert_eq!(graph.eval(&ctx, &frames), GPoll::Pending);
|
||||
assert!(!runtime.take_dirty());
|
||||
|
||||
assert_eq!(runtime.spawner().drain(), 1);
|
||||
@@ -658,7 +663,7 @@ mod tests {
|
||||
|
||||
let bumped_scope = EvalScope::new(None, None, None, &bumped, &arena);
|
||||
let bumped_ctx = ContextImpl::root(&bumped_scope);
|
||||
assert_eq!(graph.eval(&bumped_ctx), GPoll::Final(42.0), "the own-generation-excluded key replays the landed slot");
|
||||
assert_eq!(graph.eval(&bumped_ctx, &frames), GPoll::Final(42.0), "the own-generation-excluded key replays the landed slot");
|
||||
assert_eq!(runtime.spawner().drain(), 0, "a slot hit must not respawn");
|
||||
|
||||
let downstream_key = crate::registry::cache_key(&ContextImpl::root(&scope));
|
||||
|
||||
@@ -21,7 +21,7 @@ impl<C, T> crate::node::Node<C> for ValueSource<T>
|
||||
where
|
||||
T: Clone + Send + Sync + dyn_any::StaticTypeSized,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: crate::record::FrameClaim<'l>) -> crate::gpoll::GPoll<crate::record::Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: crate::record::FrameClaim<'e, 'l>) -> crate::gpoll::GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
@@ -65,7 +65,7 @@ where
|
||||
C: crate::context::ExtractIndex,
|
||||
T: Clone + Send + Sync + dyn_any::StaticTypeSized,
|
||||
{
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: crate::record::FrameClaim<'l>) -> crate::gpoll::GPoll<crate::record::Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: crate::record::FrameClaim<'e, 'l>) -> crate::gpoll::GPoll<crate::record::Served<'e>>
|
||||
where
|
||||
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
{
|
||||
@@ -75,7 +75,7 @@ where
|
||||
slot.lift_served(crate::gpoll::GPoll::Final(value.clone()), input.arena())
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, level: u8) -> crate::gpoll::GPoll<crate::gpoll::Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, level: u8, _frames: &crate::record::Frames<'x>) -> crate::gpoll::GPoll<crate::gpoll::Extent>
|
||||
where
|
||||
C: crate::context::ExtractArena<ArenaRef = &'x crate::arena::Arena>,
|
||||
{
|
||||
|
||||
@@ -24,12 +24,12 @@ pub enum LevelGroup<'e> {
|
||||
|
||||
/// The renderer's flip form: the wire's whole extent materialized into a
|
||||
/// group over the level's records, ready for the group render bridge.
|
||||
pub fn materialize_group<'a, 'e, C, N>(node: &'a N, input: &'a C, arena: &'a Arena) -> LevelGroup<'a>
|
||||
pub fn materialize_group<'a, 'e, C, N>(node: &'a N, input: &'a C, arena: &'a Arena, frames: &core_types::record::Frames<'e>) -> LevelGroup<'a>
|
||||
where
|
||||
C: InjectIndex + Copy + core_types::context::ExtractArena<ArenaRef = &'e Arena>,
|
||||
N: Node<C>,
|
||||
{
|
||||
match materialize_level(node, input, arena) {
|
||||
match materialize_level(node, input, arena, frames) {
|
||||
LevelStatus::Batch(batch, finality) => {
|
||||
// SAFETY: a materialized batch's frames are arena-resident.
|
||||
let item = unsafe { GroupItem::from_resident(batch) };
|
||||
|
||||
@@ -1647,8 +1647,11 @@ mod run_tests {
|
||||
drop(source);
|
||||
|
||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { core_types::record::stack::reserve(layout.frame_bytes()); } let value = owned.replay(&layout, &arena).expect("the arena holds the replay");
|
||||
let frames = core_types::record::test_frames(layout.frame_bytes());
|
||||
let mut slot = frames.claim(&layout);
|
||||
owned.replay_into(&mut slot, &arena).expect("the arena holds the replay");
|
||||
// SAFETY: the replay completes the record in the claimed frame.
|
||||
let value = unsafe { slot.finish() };
|
||||
// SAFETY: the replay wrote a record of `layout`.
|
||||
let served = unsafe { layout.rec(&value).read::<Option<&List<Graphic>>>(offset) }.expect("the fill replays present");
|
||||
assert_eq!(map_groups_to_legacy(served.element(0).unwrap()), expected);
|
||||
|
||||
Reference in New Issue
Block a user