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:
@@ -359,27 +359,27 @@ macro_rules! tagged_value {
|
||||
}
|
||||
|
||||
/// Evaluates a typed edge and converts the landed value into a tagged value, with the coverage of [`Self::try_from_any`].
|
||||
pub fn from_edge(handle: EdgeHandle, ctx: &Context) -> Result<GPoll<Self>, String> {
|
||||
pub fn from_edge<'f>(handle: EdgeHandle, ctx: &Context<'f>, frames: &core_types::record::Frames<'f>) -> Result<GPoll<Self>, String> {
|
||||
let ty = handle.ty().clone();
|
||||
// =======================
|
||||
// RECORD WIRES, WHICH LAND AS THEIR ELEMENT
|
||||
// =======================
|
||||
if ty == core_types::registry::record_edge_type::<()>() {
|
||||
let edge = handle.downcast_record::<()>().map_err(|e| format!("{e:?}"))?;
|
||||
return Ok(core_types::record::serve_edge(&edge, ctx).map(|_| TaggedValue::None));
|
||||
return Ok(core_types::record::serve_edge(&edge, ctx, frames).map(|_| TaggedValue::None));
|
||||
}
|
||||
$(
|
||||
if ty == core_types::registry::record_edge_type::<$ty>() {
|
||||
let layout = handle.layout().clone();
|
||||
let edge = handle.downcast_record::<$ty>().map_err(|e| format!("{e:?}"))?;
|
||||
return Ok(core_types::record::serve_edge(&edge, ctx)
|
||||
return Ok(core_types::record::serve_edge(&edge, ctx, frames)
|
||||
.map(|value| TaggedValue::$identifier(unsafe { core_types::record::read_element::<$ty>(layout.rec(&value)) })));
|
||||
}
|
||||
)*
|
||||
if ty == core_types::registry::record_edge_type::<RenderOutput>() {
|
||||
let layout = handle.layout().clone();
|
||||
let edge = handle.downcast_record::<RenderOutput>().map_err(|e| format!("{e:?}"))?;
|
||||
return Ok(core_types::record::serve_edge(&edge, ctx)
|
||||
return Ok(core_types::record::serve_edge(&edge, ctx, frames)
|
||||
.map(|value| TaggedValue::RenderOutput(unsafe { core_types::record::read_element::<RenderOutput>(layout.rec(&value)) })));
|
||||
}
|
||||
Err(format!("Cannot convert edge of type {ty} to TaggedValue"))
|
||||
|
||||
@@ -33,6 +33,9 @@ pub struct DynamicExecutor {
|
||||
// This allows us to keep the nodes around for one more frame which is used for introspection
|
||||
orphaned_nodes: HashSet<NodeId>,
|
||||
arena: Mutex<Arena>,
|
||||
/// The record frame space, grow-only across evaluations and lent to the
|
||||
/// root by `&mut`.
|
||||
frames: Mutex<core_types::record::FrameArena>,
|
||||
runtime: Arc<DynGraphRuntime>,
|
||||
live_sources: Vec<core_types::SourceId>,
|
||||
}
|
||||
@@ -49,6 +52,7 @@ impl Default for DynamicExecutor {
|
||||
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
|
||||
orphaned_nodes: HashSet::new(),
|
||||
arena: Mutex::new(new_arena()),
|
||||
frames: Mutex::new(core_types::record::FrameArena::new()),
|
||||
runtime: noop_runtime(),
|
||||
live_sources: Vec::new(),
|
||||
}
|
||||
@@ -85,6 +89,7 @@ impl DynamicExecutor {
|
||||
typing_context,
|
||||
orphaned_nodes: HashSet::new(),
|
||||
arena: Mutex::new(new_arena()),
|
||||
frames: Mutex::new(core_types::record::FrameArena::new()),
|
||||
runtime,
|
||||
live_sources: sources,
|
||||
})
|
||||
@@ -177,26 +182,25 @@ impl DynamicExecutor {
|
||||
.and_then(EdgeHandle::record_edge)
|
||||
.ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?;
|
||||
let arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(self.tree.stack_need());
|
||||
}
|
||||
let mut buffer = self.frames.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
buffer.reserve(self.tree.stack_need());
|
||||
let frames = buffer.frames();
|
||||
let generations = self.runtime.snapshot();
|
||||
let scope = EvalScope::new(snapshot.try_real_time(), snapshot.try_animation_time(), snapshot.try_pointer_position(), &generations, &arena);
|
||||
let Some(ctx) = snapshot.rehydrate(&scope) else {
|
||||
return Err(IntrospectError::NoData);
|
||||
};
|
||||
let layout = core_types::node::Node::<ContextImpl>::layout(&edge);
|
||||
// SAFETY: the read closure finishes inside the scope, so no record
|
||||
// above the entry survives it.
|
||||
let _scope = unsafe { core_types::record::stack::ScopeGuard::enter() };
|
||||
// The batch borrows the frames the read closure is handed, so the read
|
||||
// cannot outlive the scope that owns them.
|
||||
let frames = frames.scope();
|
||||
let result = if layout.depth > 0 {
|
||||
match core_types::record::materialize_level(&edge, &ctx, &arena) {
|
||||
match core_types::record::materialize_level(&edge, &ctx, &arena, &frames) {
|
||||
core_types::record::LevelStatus::Batch(batch, _) => read(layout, batch, &arena),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
match core_types::record::serve_edge(&edge, &ctx) {
|
||||
match core_types::record::serve_edge(&edge, &ctx, &frames) {
|
||||
GPoll::Final(value) | GPoll::Partial(value) => {
|
||||
let rec = layout.rec(&value);
|
||||
// SAFETY: the serve produced one live record of the edge's layout.
|
||||
@@ -248,10 +252,13 @@ where
|
||||
return Err("Output node not found in executor".into());
|
||||
};
|
||||
let mut arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { core_types::record::stack::reserve(self.tree.stack_need()); } let result = eval_root(&mut arena, &self.runtime, &input, |ctx| match TaggedValue::from_edge(handle.duplicate(), ctx) {
|
||||
Ok(poll) => poll.map(Ok),
|
||||
Err(error) => GPoll::Final(Err(error)),
|
||||
let mut buffer = self.frames.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
buffer.reserve(self.tree.stack_need());
|
||||
let result = eval_root(&mut arena, &mut buffer, &self.runtime, &input, |ctx, frames| {
|
||||
match TaggedValue::from_edge(handle.duplicate(), ctx, frames) {
|
||||
Ok(poll) => poll.map(Ok),
|
||||
Err(error) => GPoll::Final(Err(error)),
|
||||
}
|
||||
});
|
||||
match result {
|
||||
GPoll::Final(value) => Ok(GPoll::Final(value?)),
|
||||
@@ -265,7 +272,17 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn eval_root<S, T>(arena: &mut Arena, runtime: &GraphRuntime<S>, call_argument: DynSlot, eval: impl FnOnce(&ContextImpl) -> GPoll<T>) -> GPoll<T> {
|
||||
/// One evaluation over the arena and the frame buffer, which the caller sized
|
||||
/// to the graph's frame need: both are the evaluation's lifetime, so a record
|
||||
/// served anywhere in the cone lives exactly as long as the arena it may
|
||||
/// reference.
|
||||
pub fn eval_root<S, T>(
|
||||
arena: &mut Arena,
|
||||
buffer: &mut core_types::record::FrameArena,
|
||||
runtime: &GraphRuntime<S>,
|
||||
call_argument: DynSlot,
|
||||
eval: impl for<'e> FnOnce(&ContextImpl<'e>, &core_types::record::Frames<'e>) -> GPoll<T>,
|
||||
) -> GPoll<T> {
|
||||
arena.reset();
|
||||
let generations = runtime.snapshot();
|
||||
let scope = EvalScope::new(None, None, None, &generations, arena);
|
||||
@@ -275,7 +292,8 @@ pub fn eval_root<S, T>(arena: &mut Arena, runtime: &GraphRuntime<S>, call_argume
|
||||
outer: None,
|
||||
};
|
||||
let ctx = root.with_varargs(&link);
|
||||
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| eval(&ctx))) {
|
||||
let frames = buffer.frames();
|
||||
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| eval(&ctx, &frames))) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
arena.reset();
|
||||
@@ -568,9 +586,10 @@ mod test {
|
||||
#[test]
|
||||
fn eval_root_builds_the_bare_root_with_the_call_argument_as_vararg_0() {
|
||||
let mut arena = Arena::new(64).unwrap();
|
||||
let mut buffer = core_types::record::FrameArena::new();
|
||||
let runtime = GraphRuntime::new(InertSpawner);
|
||||
let argument = 21.5f64;
|
||||
let result = eval_root(&mut arena, &runtime, &argument, |ctx| {
|
||||
let result = eval_root(&mut arena, &mut buffer, &runtime, &argument, |ctx, _frames| {
|
||||
assert!(ctx.try_footprint().is_none(), "the bare root carries no axes");
|
||||
GPoll::Final(ctx.vararg(0).ok().and_then(|slot| slot.downcast_ref::<f64>()).copied().unwrap_or(0.))
|
||||
});
|
||||
@@ -580,15 +599,16 @@ mod test {
|
||||
#[test]
|
||||
fn eval_root_resets_the_arena_at_eval_start() {
|
||||
let mut arena = Arena::new(64).unwrap();
|
||||
let mut buffer = core_types::record::FrameArena::new();
|
||||
let runtime = GraphRuntime::new(InertSpawner);
|
||||
let cell = ArenaCell::new();
|
||||
eval_root(&mut arena, &runtime, &(), |ctx| {
|
||||
eval_root(&mut arena, &mut buffer, &runtime, &(), |ctx, _frames| {
|
||||
let (_, weak) = ctx.scope().arena().alloc(5u32).unwrap();
|
||||
cell.store(weak);
|
||||
GPoll::Final(())
|
||||
});
|
||||
assert!(cell.load(&arena).is_some(), "the introspection window spans until the next eval");
|
||||
eval_root(&mut arena, &runtime, &(), |ctx| {
|
||||
eval_root(&mut arena, &mut buffer, &runtime, &(), |ctx, _frames| {
|
||||
assert!(cell.load(ctx.scope().arena()).is_none(), "the reset at eval start reclaims the previous frame");
|
||||
GPoll::Final(())
|
||||
});
|
||||
@@ -597,16 +617,17 @@ mod test {
|
||||
#[test]
|
||||
fn a_panicking_eval_reports_the_error_and_resets_the_arena() {
|
||||
let mut arena = Arena::new(64).unwrap();
|
||||
let mut buffer = core_types::record::FrameArena::new();
|
||||
let runtime = GraphRuntime::new(InertSpawner);
|
||||
let cell = ArenaCell::new();
|
||||
let result: GPoll<()> = eval_root(&mut arena, &runtime, &(), |ctx| {
|
||||
let result: GPoll<()> = eval_root(&mut arena, &mut buffer, &runtime, &(), |ctx, _frames| {
|
||||
let (_, weak) = ctx.scope().arena().alloc(5u32).unwrap();
|
||||
cell.store(weak);
|
||||
panic!("mid-eval");
|
||||
});
|
||||
assert_eq!(result, GPoll::panicked());
|
||||
assert!(cell.load(&arena).is_none(), "reset-on-panic leaves no stale records");
|
||||
assert_eq!(eval_root(&mut arena, &runtime, &(), |_| GPoll::Final(7u32)), GPoll::Final(7));
|
||||
assert_eq!(eval_root(&mut arena, &mut buffer, &runtime, &(), |_, _| GPoll::Final(7u32)), GPoll::Final(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -623,11 +644,8 @@ mod test {
|
||||
let generations = [];
|
||||
let scope = EvalScope::new(None, None, None, &generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(layout.frame_bytes());
|
||||
}
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
let frames = core_types::record::test_frames(layout.frame_bytes());
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(unsafe { core_types::record::read_element::<u32>(layout.rec(&value)) }, 2);
|
||||
@@ -671,11 +689,8 @@ mod test {
|
||||
let handle = executor.tree().get(NodeId(1)).unwrap();
|
||||
let layout = handle.layout().clone();
|
||||
let edge = handle.duplicate().downcast_record::<f64>().unwrap();
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(executor.tree().stack_need());
|
||||
}
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
let frames = core_types::record::test_frames(executor.tree().stack_need());
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("the flipped clone must evaluate over record wires, got a non-final poll");
|
||||
};
|
||||
assert_eq!(unsafe { core_types::record::read_element::<f64>(layout.rec(&value)) }, 7.);
|
||||
@@ -701,11 +716,8 @@ mod test {
|
||||
let scope = EvalScope::new(None, None, None, &generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
let edge = executor.tree().get(NodeId(2)).unwrap().downcast_record::<graphene_std::raster::color::Color>().unwrap();
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(executor.tree().stack_need());
|
||||
}
|
||||
let result = core_types::record::serve_edge(&edge, &ctx);
|
||||
let frames = core_types::record::test_frames(executor.tree().stack_need());
|
||||
let result = core_types::record::serve_edge(&edge, &ctx, &frames);
|
||||
// The empty raster level folds to an empty palette: past-end at lane 0.
|
||||
assert!(
|
||||
matches!(&result, GPoll::Error(error) if error.kind == core_types::gpoll::ErrorKind::PastEnd),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -21,6 +21,22 @@ use metadata::generate_node_input_references;
|
||||
|
||||
static NODE_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Binds in evaluation order with the lazy wrappers last: a wrapper takes the
|
||||
/// free space as it stands when it is built, so every input whose record the
|
||||
/// kernel still holds must have claimed its frame by then.
|
||||
fn lazy_last<'a>(binds: impl Iterator<Item = (&'a &'a ParsedField, TokenStream2)>, lazy_entry: &TokenStream2) -> Vec<TokenStream2> {
|
||||
let mut ordered: Vec<(bool, TokenStream2)> = binds.map(|(field, body)| (matches!(field.ty, ParsedFieldType::Node(_)), body)).collect();
|
||||
ordered.sort_by_key(|(lazy, _)| *lazy);
|
||||
let mut declared = false;
|
||||
ordered
|
||||
.into_iter()
|
||||
.map(|(lazy, body)| match lazy && !std::mem::replace(&mut declared, true) {
|
||||
true => quote!(#lazy_entry #body),
|
||||
false => body,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The regular inputs that materialize whole in the eval prologue, which is
|
||||
/// where the per-node batch cache slots attach.
|
||||
fn materialized_indices(regular_fields: &[&ParsedField], node: &ir::Node) -> Vec<usize> {
|
||||
@@ -271,7 +287,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// per-lane evals share one materialization.
|
||||
record_state_fields.extend(materialized_indices(&struct_regular_fields, &node).into_iter().map(|index| {
|
||||
let slot = format_ident!("__mat_cache_{index}");
|
||||
quote!(pub(super) #slot: ::std::sync::Arc<::std::sync::Mutex<::core::option::Option<(u64, u64, usize, usize)>>>)
|
||||
quote!(pub(super) #slot: ::std::sync::Arc<::std::sync::Mutex<::core::option::Option<(u64, #core_types::record::MaterializedSpan)>>>)
|
||||
}));
|
||||
|
||||
let async_source = parsed.injects_async_source_fields();
|
||||
@@ -866,6 +882,25 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let wants_record_lifetime = routing_generic.is_some() || ((record_io || flip) && kernel_lazy);
|
||||
let ctx_declares_arena = ctx_param.is_some_and(|ctx_param| ctx_param.bounds.iter().any(extracts_arena));
|
||||
let bind_record_arena = wants_record_lifetime && !ctx_declares_arena;
|
||||
// The lifetime a lazy wrapper's frame space is named at: the kernel's own
|
||||
// arena lifetime, since the records it hands back live in that space.
|
||||
let declared_arena_lifetime = ctx_param.and_then(|ctx_param| {
|
||||
ctx_param.bounds.iter().find_map(|bound| match bound {
|
||||
TypeParamBound::Trait(trait_bound) if extracts_arena(bound) => match &trait_bound.path.segments.last().expect("checked by the predicate").arguments {
|
||||
PathArguments::AngleBracketed(args) => args.args.iter().find_map(|arg| match arg {
|
||||
GenericArgument::Lifetime(lifetime) => Some(lifetime.clone()),
|
||||
_ => None,
|
||||
}),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
let frames_lifetime = match (&declared_arena_lifetime, wants_record_lifetime) {
|
||||
(Some(lifetime), _) => quote!(#lifetime),
|
||||
(None, true) => quote!('__record),
|
||||
(None, false) => quote!('_),
|
||||
};
|
||||
if bind_record_arena {
|
||||
ctx_bounds.push(quote!(#core_types::context::ExtractArena<ArenaRef = &'__record #core_types::arena::Arena>));
|
||||
}
|
||||
@@ -1089,19 +1124,19 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
||||
let source_generic = format_ident!("__Source{index}");
|
||||
match (ir::lazy_binding(&node, index), raw_lazy) {
|
||||
(LazyBinding::DeriveRouting, _) => quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>),
|
||||
(LazyBinding::DeriveRouting, _) => quote!(#pat: #core_types::record::RecordLazyInput<'_, #frames_lifetime, #source_generic>),
|
||||
(LazyBinding::DeriveCarrier, _) => {
|
||||
let out = lazy_read_out(field, output_type);
|
||||
quote!(#pat: #core_types::record::DerivedLazyInput<'_, '__record, #out, #source_generic>)
|
||||
quote!(#pat: #core_types::record::DerivedLazyInput<'_, #frames_lifetime, #out, #source_generic>)
|
||||
}
|
||||
(LazyBinding::OpaqueRecord, _) => quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #source_generic>),
|
||||
(LazyBinding::OpaqueRecord, _) => quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #frames_lifetime, #source_generic>),
|
||||
(LazyBinding::Element, true) => {
|
||||
let out = lazy_read_out(field, output_type);
|
||||
quote!(#pat: &#core_types::record::ElementEdge<'_, #out, #source_generic>)
|
||||
quote!(#pat: &#core_types::record::ElementEdge<'_, #frames_lifetime, #out, #source_generic>)
|
||||
}
|
||||
(LazyBinding::Element, false) => {
|
||||
let out = lazy_read_out(field, output_type);
|
||||
quote!(#pat: #core_types::record::ElementLazyInput<'_, #out, #source_generic>)
|
||||
quote!(#pat: #core_types::record::ElementLazyInput<'_, #frames_lifetime, #out, #source_generic>)
|
||||
}
|
||||
(LazyBinding::Generic, true) => {
|
||||
let bound = lazy_bound();
|
||||
@@ -1109,7 +1144,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
(LazyBinding::Generic, false) => {
|
||||
let bound = lazy_bound();
|
||||
quote!(#pat: #core_types::node::LazyInput<'_, impl #bound>)
|
||||
quote!(#pat: #core_types::node::LazyInput<'_, #frames_lifetime, impl #bound>)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1218,33 +1253,35 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
.collect::<Vec<(usize, &AttributeRead)>>()
|
||||
};
|
||||
|
||||
// Every exit closes through the frame claim's drop, so no exit path
|
||||
// reclaims by hand.
|
||||
let interrupt_close = quote!();
|
||||
let frame_entry = quote! {
|
||||
#[allow(unused_mut, unused_variables)]
|
||||
let mut __frame = __slot;
|
||||
};
|
||||
// The batch loop is not a serve, so each lane claims its own frame.
|
||||
// The batch loop is not a serve: the lane's own frame is its region of the
|
||||
// run's slab, so it serves in place.
|
||||
let lane_frame_entry = quote! {
|
||||
#[allow(unused_mut, unused_variables)]
|
||||
let mut __frame = #core_types::record::FrameClaim::enter(__node_layout);
|
||||
let mut __frame = __run.slot(__lane, &__lane_frames);
|
||||
};
|
||||
let bind_body = |index: usize, field: &ParsedField, batch_mode: bool| {
|
||||
// A lazy edge claims beyond every input frame this node holds, and its
|
||||
// cursor is shared, so the edges a kernel drives claim past each other.
|
||||
let lazy_frames_entry = quote! {
|
||||
let __lazy_frames = __frame.frames().reborrow();
|
||||
};
|
||||
let bind_body = |index: usize, field: &ParsedField, batch_mode: bool, frames: &TokenStream2| {
|
||||
let name = &field.pat_ident.ident;
|
||||
// The bind's failure exits return through the enclosing fn: `GPoll` in
|
||||
// `eval`, `BatchStatus` in the generated `eval_batch`.
|
||||
let close = interrupt_close.clone();
|
||||
let pending = match batch_mode {
|
||||
false => quote!({ #close return #core_types::gpoll::GPoll::Pending; }),
|
||||
false => quote!(return #core_types::gpoll::GPoll::Pending),
|
||||
true => quote!(return #core_types::node::BatchStatus::Pending),
|
||||
};
|
||||
let fail = |error: TokenStream2| match batch_mode {
|
||||
false => quote!({ #close return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#error)); }),
|
||||
false => quote!(return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#error))),
|
||||
true => quote!(return #core_types::node::BatchStatus::Error(#error)),
|
||||
};
|
||||
let interrupt_return = match batch_mode {
|
||||
false => quote!({ #close return interrupt.into(); }),
|
||||
false => quote!(return interrupt.into()),
|
||||
true => quote!(return interrupt.into()),
|
||||
};
|
||||
match &field.ty {
|
||||
@@ -1273,19 +1310,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
#core_types::context::InjectIndex::set_index(&mut __keyed, 0);
|
||||
#core_types::registry::cache_key(&__keyed)
|
||||
};
|
||||
let __mat_generation = __arena.generation();
|
||||
let __mat_hit = match *self.#cache_slot.lock().unwrap() {
|
||||
::core::option::Option::Some((__key, __generation, __base, __len)) if __key == __mat_key && __generation == __mat_generation => {
|
||||
::core::option::Option::Some((__base, __len))
|
||||
}
|
||||
::core::option::Option::Some((__key, __span)) if __key == __mat_key => __span.batch(__arena, #core_types::node::Node::<#ctx_ident>::layout(&self.#name)),
|
||||
_ => ::core::option::Option::None,
|
||||
};
|
||||
let __batch = match __mat_hit {
|
||||
// SAFETY: within the generation the cached batch stays
|
||||
// live, immutable, and of this edge's layout.
|
||||
::core::option::Option::Some((__base, __len)) => unsafe { #core_types::node::RecordBatch::new(__base as *const u8, __len, #core_types::node::Node::<#ctx_ident>::layout(&self.#name)) },
|
||||
::core::option::Option::Some(__batch) => __batch,
|
||||
::core::option::Option::None => {
|
||||
let __sized = match #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Total) {
|
||||
let __sized = match #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Total, #frames) {
|
||||
#core_types::gpoll::GPoll::Final(#core_types::gpoll::Extent::Exactly(__count)) => ::core::result::Result::Ok(__count),
|
||||
#core_types::gpoll::GPoll::Final(#core_types::gpoll::Extent::AtLeast(__bound)) => ::core::result::Result::Err(__bound),
|
||||
#core_types::gpoll::GPoll::Pending => #pending,
|
||||
@@ -1294,7 +1326,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let __fresh = match __sized {
|
||||
::core::result::Result::Ok(__count) => {
|
||||
let __start: u64 = 0;
|
||||
match #core_types::record::materialize_batch(&self.#name, __input, __start..__start + __count as u64, __arena) {
|
||||
match #core_types::record::materialize_batch(&self.#name, __input, __start..__start + __count as u64, __arena, #frames) {
|
||||
#core_types::node::BatchStatus::Lent(__batch, ..) => __batch,
|
||||
#core_types::node::BatchStatus::Filled(__batch, ..) => __batch.into_shared(),
|
||||
#core_types::node::BatchStatus::Pending => #pending,
|
||||
@@ -1308,7 +1340,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
::core::result::Result::Err(__bound) => {
|
||||
let mut __guess = __bound.max(16);
|
||||
loop {
|
||||
let (__batch, __hint) = match #core_types::record::materialize_batch(&self.#name, __input, 0..__guess as u64, __arena) {
|
||||
let (__batch, __hint) = match #core_types::record::materialize_batch(&self.#name, __input, 0..__guess as u64, __arena, #frames) {
|
||||
#core_types::node::BatchStatus::Lent(__batch, _, __hint) => (__batch, __hint),
|
||||
#core_types::node::BatchStatus::Filled(__batch, _, __hint) => (__batch.into_shared(), __hint),
|
||||
#core_types::node::BatchStatus::Pending => #pending,
|
||||
@@ -1328,11 +1360,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
}
|
||||
};
|
||||
let __base = match __fresh.len() {
|
||||
0 => 0usize,
|
||||
_ => __fresh.get(0).rec().ptr() as usize,
|
||||
};
|
||||
*self.#cache_slot.lock().unwrap() = ::core::option::Option::Some((__mat_key, __mat_generation, __base, __fresh.len()));
|
||||
if let ::core::option::Option::Some(__span) = #core_types::record::MaterializedSpan::of(&__fresh, __arena) {
|
||||
*self.#cache_slot.lock().unwrap() = ::core::option::Option::Some((__mat_key, __span));
|
||||
}
|
||||
__fresh
|
||||
}
|
||||
};
|
||||
@@ -1340,14 +1370,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
}
|
||||
// A reading secondary input claims a record edge: the element and
|
||||
// the declared reads copy out right after its eval, before any
|
||||
// later sibling eval can reuse the record stack.
|
||||
// the declared reads copy out right after its eval.
|
||||
ValueBinding::ReadingSecondary => {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
let rec_local = format_ident!("__rec_{index}");
|
||||
let bindings: Vec<TokenStream2> = reads_of(index).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(#rec_local))).collect();
|
||||
quote! {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input, #frames) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => #interrupt_return,
|
||||
};
|
||||
@@ -1356,13 +1385,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let #name: #ty = unsafe { #core_types::record::read_element(#rec_local) };
|
||||
}
|
||||
}
|
||||
// The lend input's frame survives on the record stack until this
|
||||
// node's frame is reclaimed, so the borrow stays valid in place.
|
||||
// The lend input's frame is claimed out of this node's own claim and
|
||||
// lives as long as it does, so the borrow stays valid in place.
|
||||
ValueBinding::Lend => {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
let record_local = format_ident!("__record_{index}");
|
||||
quote! {
|
||||
let #record_local = match __cell.eval_input(#index, &self.#name, __input) {
|
||||
let #record_local = match __cell.eval_input(#index, &self.#name, __input, #frames) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => #interrupt_return,
|
||||
};
|
||||
@@ -1375,7 +1404,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
ValueBinding::RecordElement => {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
quote! {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input, #frames) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => #interrupt_return,
|
||||
};
|
||||
@@ -1394,7 +1423,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input, #frames) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => #interrupt_return,
|
||||
};
|
||||
@@ -1406,20 +1435,20 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// A raw poll edge is threaded straight through, so it does not bind here.
|
||||
(LazyBinding::Generic, true) => quote!(),
|
||||
(LazyBinding::DeriveRouting, _) => quote! {
|
||||
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels));
|
||||
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &__lazy_frames);
|
||||
},
|
||||
(LazyBinding::DeriveCarrier, _) => {
|
||||
let reads = reads_of(index);
|
||||
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
||||
match reads.is_empty() {
|
||||
true => quote! {
|
||||
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &[], #core_types::record::token_only);
|
||||
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &[], #core_types::record::token_only, &__lazy_frames);
|
||||
},
|
||||
false => {
|
||||
let slot_idents: Vec<Ident> = reads.iter().map(|(slot, _)| format_ident!("__read_{slot}")).collect();
|
||||
quote! {
|
||||
let __carrier_reads = [#(self.#slot_idents),*];
|
||||
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &__carrier_reads, self::#read_fn);
|
||||
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &__carrier_reads, self::#read_fn, &__lazy_frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1428,13 +1457,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let slot = format_ident!("__in_{index}");
|
||||
match field.attribute_reads.is_empty() {
|
||||
true => quote! {
|
||||
let #name = #core_types::record::ElementEdge::<#output_type, _>::new(&self.#name, &self.#slot);
|
||||
let #name = #core_types::record::ElementEdge::<#output_type, _>::new(&self.#name, &self.#slot, &__lazy_frames);
|
||||
},
|
||||
false => {
|
||||
let arr = format_ident!("__reads_{index}");
|
||||
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
||||
quote! {
|
||||
let #name = #core_types::record::ElementEdge::with_reads(&self.#name, &self.#slot, &self.#arr, self::#read_fn);
|
||||
let #name = #core_types::record::ElementEdge::with_reads(&self.#name, &self.#slot, &self.#arr, self::#read_fn, &__lazy_frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1443,22 +1472,22 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let slot = format_ident!("__in_{index}");
|
||||
match field.attribute_reads.is_empty() {
|
||||
true => quote! {
|
||||
let #name = #core_types::record::ElementLazyInput::<#output_type, _>::new(&self.#name, &__cell, #index, &self.#slot);
|
||||
let #name = #core_types::record::ElementLazyInput::<#output_type, _>::new(&self.#name, &__cell, #index, &self.#slot, &__lazy_frames);
|
||||
},
|
||||
false => {
|
||||
let arr = format_ident!("__reads_{index}");
|
||||
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
||||
quote! {
|
||||
let #name = #core_types::record::ElementLazyInput::with_reads(&self.#name, &__cell, #index, &self.#slot, &self.#arr, self::#read_fn);
|
||||
let #name = #core_types::record::ElementLazyInput::with_reads(&self.#name, &__cell, #index, &self.#slot, &self.#arr, self::#read_fn, &__lazy_frames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(LazyBinding::OpaqueRecord, _) => quote! {
|
||||
let #name = #core_types::record::RecordEdgeInput::new(&self.#name, &self.__layout);
|
||||
let #name = #core_types::record::RecordEdgeInput::new(&self.#name, &self.__layout, &__lazy_frames);
|
||||
},
|
||||
(LazyBinding::Generic, false) => quote! {
|
||||
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
||||
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index, &__lazy_frames);
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1466,7 +1495,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
|
||||
// A bind whose element copies out reclaims the edge's frame; a forwarded
|
||||
// record must outlive the bind, so its frame stays.
|
||||
let reads_out_at = |index: usize| match ®ular_fields[index].ty {
|
||||
let _reads_out_at = |index: usize| match ®ular_fields[index].ty {
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => !routing_source(ty) && ir::value_binding(&node, index).reads_out(),
|
||||
ParsedFieldType::Node(_) => false,
|
||||
};
|
||||
@@ -1519,7 +1548,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let query = format_ident!("__extent_query_{index}");
|
||||
let extent_edge = |query: &Ident, arg: &Ident| {
|
||||
quote! {
|
||||
let #query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl);
|
||||
let #query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl, &__frames.scope());
|
||||
let #arg = #core_types::extent::ExtentIn::new(&#query);
|
||||
}
|
||||
};
|
||||
@@ -1529,7 +1558,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let #query = |__copy: u64, __lvl: u8| {
|
||||
let mut __frame = #core_types::context::IndexLink { index: 0, outer: None };
|
||||
let __derived = #core_types::context::DeriveCtx::push_level(__input, &mut __frame, __copy, 0);
|
||||
#core_types::record::DerivedRecordEdge::extent_at_derived(&self.#name, &__derived, __lvl)
|
||||
#core_types::record::DerivedRecordEdge::extent_at_derived(&self.#name, &__derived, __lvl, &__frames.scope())
|
||||
};
|
||||
let #arg = #core_types::extent::ExtentIn::new(&#query);
|
||||
},
|
||||
@@ -1544,12 +1573,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
quote! {
|
||||
let #query = || {
|
||||
let __arena = #core_types::context::ExtractArena::arena(__input);
|
||||
let __count = match #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Total) {
|
||||
let __count = match #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Total, &__frames.scope()) {
|
||||
#core_types::gpoll::GPoll::Final(#core_types::gpoll::Extent::Exactly(__count)) => __count,
|
||||
#core_types::gpoll::GPoll::Pending => return #core_types::gpoll::GPoll::Pending,
|
||||
_ => return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#core_types::gpoll::GraphError::new("extent over a non-exact ranked input"))),
|
||||
};
|
||||
match #core_types::record::materialize_batch(&self.#name, __input, 0..__count as u64, __arena) {
|
||||
match #core_types::record::materialize_batch(&self.#name, __input, 0..__count as u64, __arena, __frames) {
|
||||
#core_types::node::BatchStatus::Lent(__batch, ..) => #core_types::gpoll::GPoll::Final(unsafe { #core_types::node::List::<#ty>::new(__batch) }),
|
||||
#core_types::node::BatchStatus::Filled(__batch, ..) => #core_types::gpoll::GPoll::Final(unsafe { #core_types::node::List::<#ty>::new(__batch.into_shared()) }),
|
||||
#core_types::node::BatchStatus::Pending => #core_types::gpoll::GPoll::Pending,
|
||||
@@ -1557,7 +1586,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
_ => #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#core_types::gpoll::GraphError::new("extent could not materialize a ranked input"))),
|
||||
}
|
||||
};
|
||||
let __total = || #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Total);
|
||||
let __total = || #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Total, &__frames.scope());
|
||||
let #arg = #core_types::extent::ListIn::new(&#query, &__total);
|
||||
}
|
||||
}
|
||||
@@ -1574,10 +1603,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
};
|
||||
quote! {
|
||||
let #query = || {
|
||||
// SAFETY: the element copies out by value; extent
|
||||
// queries leave the record stack untouched.
|
||||
let __scope = unsafe { #core_types::record::stack::ScopeGuard::enter() };
|
||||
#core_types::record::serve_edge(&self.#name, __input)
|
||||
// The element copies out by value, so the edge's
|
||||
// claim dies with the query.
|
||||
let __scope = __frames.scope();
|
||||
#core_types::record::serve_edge(&self.#name, __input, &__scope)
|
||||
.map(|__value| unsafe { #core_types::record::read_element::<#ty>(#layout.rec(&__value)) })
|
||||
};
|
||||
let #arg = #core_types::extent::ValueIn::new(&#query);
|
||||
@@ -1592,7 +1621,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
arg_names.push(arg);
|
||||
}
|
||||
quote! {
|
||||
fn extent_at<'__serve>(&self, __input: &#ctx_ident, __level: u8) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
fn extent_at<'__serve>(&self, __input: &#ctx_ident, __level: u8, __frames: &#core_types::record::Frames<'__serve>) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
where
|
||||
#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__serve #core_types::arena::Arena>,
|
||||
{
|
||||
@@ -1603,7 +1632,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
} else if let Some(path) = &parsed.attributes.extent_raw {
|
||||
quote! {
|
||||
fn extent_at<'__serve>(&self, __input: &#ctx_ident, __level: u8) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
fn extent_at<'__serve>(&self, __input: &#ctx_ident, __level: u8, _: &#core_types::record::Frames<'__serve>) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
where
|
||||
#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__serve #core_types::arena::Arena>,
|
||||
{
|
||||
@@ -1621,19 +1650,19 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let __query = |_: u64, __lvl: u8| {
|
||||
let __head = #core_types::context::DeriveCtx::index_head(__input);
|
||||
let __derived = #core_types::context::DeriveCtx::replaced(__input, __head.index);
|
||||
#core_types::record::DerivedRecordEdge::extent_at_derived(&self.#name, &__derived, __lvl)
|
||||
#core_types::record::DerivedRecordEdge::extent_at_derived(&self.#name, &__derived, __lvl, &__frames.scope())
|
||||
};
|
||||
},
|
||||
_ => quote! {
|
||||
let __query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl);
|
||||
let __query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl, &__frames.scope());
|
||||
},
|
||||
},
|
||||
ParsedFieldType::Regular(_) => quote! {
|
||||
let __query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl);
|
||||
let __query = |_: u64, __lvl: u8| #core_types::node::Node::extent_at(&self.#name, __input, __lvl, &__frames.scope());
|
||||
},
|
||||
};
|
||||
quote! {
|
||||
fn extent_at<'__serve>(&self, __input: &#ctx_ident, __level: u8) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
fn extent_at<'__serve>(&self, __input: &#ctx_ident, __level: u8, __frames: &#core_types::record::Frames<'__serve>) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
where
|
||||
#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__serve #core_types::arena::Arena>,
|
||||
{
|
||||
@@ -1647,7 +1676,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// A leveled output without an extent fn reports a lower bound;
|
||||
// consumers size it by draining to the past-end signal.
|
||||
quote! {
|
||||
fn extent_at<'__serve>(&self, _: &#ctx_ident, _: u8) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
fn extent_at<'__serve>(&self, _: &#ctx_ident, _: u8, _: &#core_types::record::Frames<'__serve>) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent>
|
||||
where
|
||||
#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__serve #core_types::arena::Arena>,
|
||||
{
|
||||
@@ -1676,6 +1705,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
__input: &'__batch #ctx_ident,
|
||||
__range: ::std::ops::Range<u64>,
|
||||
__scratch: Option<&'__batch mut [::std::mem::MaybeUninit<u64>]>,
|
||||
__frames: &#core_types::record::Frames<'__serve>,
|
||||
) -> #core_types::node::BatchStatus<'__batch>
|
||||
where
|
||||
#ctx_ident: #core_types::context::InjectIndex + Copy + #core_types::context::ExtractArena<ArenaRef = &'__serve #core_types::arena::Arena>,
|
||||
@@ -1758,7 +1788,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
quote! {
|
||||
match #kernel_call {
|
||||
Ok(value) => __cell.finish(#served),
|
||||
Err(interrupt) => { #interrupt_close interrupt.into() }
|
||||
Err(interrupt) => interrupt.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1793,7 +1823,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
};
|
||||
let clamp = clamp_tokens(field);
|
||||
quote! {
|
||||
let __src = match __cell.eval_input(0, &self.#name, __input) {
|
||||
let __src = match __cell.eval_input(0, &self.#name, __input, __frame.frames()) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => return interrupt.into(),
|
||||
};
|
||||
@@ -1889,9 +1919,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let carrier_eval = (!skips_carrier && !lazy_carrier).then(|| {
|
||||
let name = ®ular_fields[0].pat_ident.ident;
|
||||
quote! {
|
||||
let __src = match __cell.eval_input(0, &self.#name, __input) {
|
||||
let __src = match __cell.eval_input(0, &self.#name, __input, __frame.frames()) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => { #interrupt_close return interrupt.into(); }
|
||||
Err(interrupt) => return interrupt.into()
|
||||
};
|
||||
let __src_rec = self.__carrier.rec(&__src);
|
||||
}
|
||||
@@ -1922,7 +1952,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
Dialect::Interrupt => quote! {
|
||||
match #record_kernel_call {
|
||||
Ok(__value) => __value,
|
||||
Err(__interrupt) => { #interrupt_close return __interrupt.into(); }
|
||||
Err(__interrupt) => return __interrupt.into()
|
||||
}
|
||||
},
|
||||
_ => quote!(#record_kernel_call),
|
||||
@@ -2062,7 +2092,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
Dialect::FutureInterrupt => quote! {
|
||||
let __future = match #kernel_call {
|
||||
Ok(future) => future,
|
||||
Err(interrupt) => { #interrupt_close return interrupt.into(); }
|
||||
Err(interrupt) => return interrupt.into()
|
||||
};
|
||||
},
|
||||
_ => quote!(let __future = #kernel_call;),
|
||||
@@ -2088,16 +2118,16 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// base lane, so the per-lane loop runs only the kernel and the carrier;
|
||||
// eager inputs are batch-invariant by contract (per-lane variance rides
|
||||
// lazy carriers).
|
||||
// The fill loop copies each lane's frame out, so it needs the record, not
|
||||
// the serving proof.
|
||||
// Each lane serves in place into its own region of the run, so the loop
|
||||
// collects the serving proofs.
|
||||
let hoisted_lane_poll = match tail_form {
|
||||
Tail::Record => record_tail_core.clone().map(|core| {
|
||||
quote! {
|
||||
#core
|
||||
let __poll = __cell.finish(__value).map(#core_types::record::Served::value);
|
||||
let __poll = __cell.finish(__value);
|
||||
}
|
||||
}),
|
||||
Tail::Forward if routing_generic.is_some() => Some(quote!(let __poll = #lift.map(#core_types::record::Served::value);)),
|
||||
Tail::Forward if routing_generic.is_some() => Some(quote!(let __poll = #lift;)),
|
||||
_ => None,
|
||||
};
|
||||
// A serving-lifetime element rides the per-lane fill loop: the hoisted
|
||||
@@ -2112,7 +2142,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
(Some(path), ..) => quote! {
|
||||
#batch_signature
|
||||
{
|
||||
#path(self, __input, __range, __scratch)
|
||||
#path(self, __input, __range, __scratch, __frames)
|
||||
}
|
||||
},
|
||||
(None, true, Some(lane_poll)) => {
|
||||
@@ -2124,21 +2154,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, field)| matches!(field.ty, ParsedFieldType::Regular(_)) && hoists(*index))
|
||||
.map(|(index, field)| {
|
||||
let body = bind_body(index, field, true);
|
||||
match reads_out_at(index) {
|
||||
false => body,
|
||||
true => {
|
||||
let mark = format_ident!("__scope_{index}");
|
||||
quote! {
|
||||
// SAFETY: the bind's reads copy out by value.
|
||||
let #mark = unsafe { #core_types::record::stack::ScopeGuard::enter() };
|
||||
#body
|
||||
drop(#mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|(index, field)| bind_body(index, field, true, "e!((&*__frames))))
|
||||
.collect();
|
||||
let hoisted_clamps: Vec<TokenStream2> = regular_fields
|
||||
.iter()
|
||||
@@ -2146,34 +2162,38 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
.filter(|(index, field)| matches!(field.ty, ParsedFieldType::Regular(_)) && hoists(*index))
|
||||
.filter_map(|(_, field)| clamp_tokens(field))
|
||||
.collect();
|
||||
let lane_binds: Vec<TokenStream2> = regular_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, field)| match field.ty {
|
||||
ParsedFieldType::Node(_) => true,
|
||||
ParsedFieldType::Regular(_) => !hoists(*index) && !matches!(ir::value_binding(&node, *index), ValueBinding::Carrier),
|
||||
})
|
||||
.map(|(index, field)| {
|
||||
let body = bind_body(index, field, true);
|
||||
let clamp = clamp_tokens(field);
|
||||
quote!(#body #clamp)
|
||||
})
|
||||
.collect();
|
||||
let lane_binds: Vec<TokenStream2> = lazy_last(
|
||||
regular_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, field)| match field.ty {
|
||||
ParsedFieldType::Node(_) => true,
|
||||
ParsedFieldType::Regular(_) => !hoists(*index) && !matches!(ir::value_binding(&node, *index), ValueBinding::Carrier),
|
||||
})
|
||||
.map(|(index, field)| {
|
||||
let body = bind_body(index, field, true, "e!(__frame.frames()));
|
||||
let clamp = clamp_tokens(field);
|
||||
(field, quote!(#body #clamp))
|
||||
}),
|
||||
&lazy_frames_entry,
|
||||
);
|
||||
// The rebind path with nothing hoisted: every non-carrier input binds
|
||||
// fresh per lane, so an index-dependent edge reaches its own lane.
|
||||
let rebound_lane_binds: Vec<TokenStream2> = regular_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, field)| match field.ty {
|
||||
ParsedFieldType::Node(_) => true,
|
||||
ParsedFieldType::Regular(_) => !matches!(ir::value_binding(&node, *index), ValueBinding::Carrier),
|
||||
})
|
||||
.map(|(index, field)| {
|
||||
let body = bind_body(index, field, true);
|
||||
let clamp = clamp_tokens(field);
|
||||
quote!(#body #clamp)
|
||||
})
|
||||
.collect();
|
||||
let rebound_lane_binds: Vec<TokenStream2> = lazy_last(
|
||||
regular_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(index, field)| match field.ty {
|
||||
ParsedFieldType::Node(_) => true,
|
||||
ParsedFieldType::Regular(_) => !matches!(ir::value_binding(&node, *index), ValueBinding::Carrier),
|
||||
})
|
||||
.map(|(index, field)| {
|
||||
let body = bind_body(index, field, true, "e!(__frame.frames()));
|
||||
let clamp = clamp_tokens(field);
|
||||
(field, quote!(#body #clamp))
|
||||
}),
|
||||
&lazy_frames_entry,
|
||||
);
|
||||
// A hoisted value is moved into every lane's kernel call, so each
|
||||
// lane consumes a clone; view and borrow binds copy freely.
|
||||
let lane_rebinds: Vec<TokenStream2> = regular_fields
|
||||
@@ -2203,23 +2223,24 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
quote! {
|
||||
#(#hoisted)*
|
||||
#(#clamps)*
|
||||
let __frames = __scratch.as_mut_ptr().cast::<u8>();
|
||||
let ::core::option::Option::Some(mut __run) = __frames.run(__scratch, __len, __node_layout) else {
|
||||
return #core_types::node::BatchStatus::InvalidRange;
|
||||
};
|
||||
let mut __finality = #core_types::gpoll::Finality::AllFinal;
|
||||
let mut __filled = __len;
|
||||
let mut __hint = #core_types::gpoll::Extent::AtLeast(__range.end as usize);
|
||||
let mut __lane_ctx = __base_ctx;
|
||||
for __lane in 0..__len {
|
||||
#core_types::context::InjectIndex::set_index(&mut __lane_ctx, __range.start + __lane as u64);
|
||||
let __input = &__lane_ctx;
|
||||
// SAFETY: the lane's record copies out before the scope
|
||||
// releases it.
|
||||
let __lane_scope = unsafe { #core_types::record::stack::ScopeGuard::enter() };
|
||||
// The lane's inputs claim beyond its slab region, and their
|
||||
// space is free again at the next lane.
|
||||
let __lane_frames = __frames.scope();
|
||||
#lane_frame_entry
|
||||
let __cell = __cell.snapshot();
|
||||
#(#rebinds)*
|
||||
#(#binds)*
|
||||
#lane_poll
|
||||
let __value = match __poll {
|
||||
let __served = match __poll {
|
||||
#core_types::gpoll::GPoll::Final(__value) => __value,
|
||||
#core_types::gpoll::GPoll::Partial(__value) => {
|
||||
__finality = #core_types::gpoll::Finality::Partial;
|
||||
@@ -2230,21 +2251,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// A lane past a lower-bound level ends the data: the fill
|
||||
// comes back short and the hint turns exact.
|
||||
#core_types::gpoll::GPoll::Error(__error) if __error.kind == #core_types::gpoll::ErrorKind::PastEnd => {
|
||||
__filled = __lane;
|
||||
__hint = #core_types::gpoll::Extent::Exactly(__range.start as usize + __lane);
|
||||
break;
|
||||
}
|
||||
#core_types::gpoll::GPoll::Error(__error) => return #core_types::node::BatchStatus::Error(*__error),
|
||||
};
|
||||
// SAFETY: the lane region is in-bounds by the scratch check,
|
||||
// and the frame is fully copied out before the lane scope
|
||||
// releases it.
|
||||
unsafe { ::core::ptr::copy_nonoverlapping(__node_layout.rec(&__value).ptr(), __frames.add(__lane * __stride), __stride) };
|
||||
__run.served(__lane, &__served);
|
||||
}
|
||||
drop(__entry_scope);
|
||||
// SAFETY: the first `__filled` lanes were filled above with
|
||||
// records of the node's layout.
|
||||
#core_types::node::BatchStatus::Filled(unsafe { #core_types::node::RecordBatchMut::new(__scratch, __filled, __node_layout) }, __finality, __hint)
|
||||
#core_types::node::BatchStatus::Filled(__run.finish(), __finality, __hint)
|
||||
}
|
||||
};
|
||||
let hoisted_fill = fill_loop(hoisted_binds, hoisted_clamps, lane_rebinds, lane_binds);
|
||||
@@ -2275,12 +2289,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
return #core_types::node::BatchStatus::InvalidRange;
|
||||
};
|
||||
let __node_layout = <Self as #core_types::node::Node<#ctx_ident>>::layout(self);
|
||||
let __stride = __node_layout.lane_stride();
|
||||
if __scratch.len() * 8 < __len * __stride {
|
||||
return #core_types::node::BatchStatus::InvalidRange;
|
||||
}
|
||||
// SAFETY: every lane copies into the caller's scratch.
|
||||
let __entry_scope = unsafe { #core_types::record::stack::ScopeGuard::enter() };
|
||||
// The batch's own claims are free again when it returns, so
|
||||
// the caller's free space comes back as it was lent.
|
||||
let __frames = __frames.scope();
|
||||
let __cell = #cell_constructor;
|
||||
let __base_ctx = {
|
||||
let mut __ctx = *__input;
|
||||
@@ -2297,7 +2308,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
(None, true, None) => quote! {
|
||||
#batch_signature
|
||||
{
|
||||
#core_types::record::fill_frames(self, __input, __range, __scratch)
|
||||
#core_types::record::fill_frames(self, __input, __range, __scratch, __frames)
|
||||
}
|
||||
},
|
||||
(None, false, _) => quote!(),
|
||||
@@ -2580,12 +2591,17 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
});
|
||||
|
||||
// The eval body as an ordered step sequence: bind each input, clamp, then the
|
||||
// tail. The record-stack mark/rewind of a read-out bind is applied here from
|
||||
// the role, so the discipline is structural rather than per-arm.
|
||||
let eval_steps: Vec<EvalStep> = regular_fields
|
||||
// tail. Every input's frame is claimed out of this node's own claim and
|
||||
// stays claimed until it dies, which is the sizing the wiring layer derives.
|
||||
let mut bind_order: Vec<(bool, usize, &&ParsedField)> = regular_fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| EvalStep::Bind(index, field))
|
||||
.map(|(index, field)| (matches!(field.ty, ParsedFieldType::Node(_)), index, field))
|
||||
.collect();
|
||||
bind_order.sort_by_key(|(lazy, ..)| *lazy);
|
||||
let eval_steps: Vec<EvalStep> = bind_order
|
||||
.iter()
|
||||
.map(|(_, index, field)| EvalStep::Bind(*index, field))
|
||||
.chain(
|
||||
regular_fields
|
||||
.iter()
|
||||
@@ -2595,26 +2611,21 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
)
|
||||
.chain(std::iter::once(EvalStep::Tail(tail_form)))
|
||||
.collect();
|
||||
let eval_body = eval_steps.iter().map(|step| match step {
|
||||
EvalStep::Bind(index, field) => {
|
||||
let body = bind_body(*index, field, false);
|
||||
let reads_out = reads_out_at(*index);
|
||||
match reads_out {
|
||||
false => body,
|
||||
true => {
|
||||
let mark = format_ident!("__scope_{index}");
|
||||
quote! {
|
||||
// SAFETY: the bind's reads copy out by value.
|
||||
let #mark = unsafe { #core_types::record::stack::ScopeGuard::enter() };
|
||||
#body
|
||||
drop(#mark);
|
||||
}
|
||||
let mut lazy_declared = false;
|
||||
let eval_body: Vec<TokenStream2> = eval_steps
|
||||
.iter()
|
||||
.map(|step| match step {
|
||||
EvalStep::Bind(index, field) => {
|
||||
let body = bind_body(*index, field, false, "e!(__frame.frames()));
|
||||
match matches!(field.ty, ParsedFieldType::Node(_)) && !std::mem::replace(&mut lazy_declared, true) {
|
||||
true => quote!(#lazy_frames_entry #body),
|
||||
false => body,
|
||||
}
|
||||
}
|
||||
}
|
||||
EvalStep::Clamp(field) => clamp_tokens(field).unwrap_or_default(),
|
||||
EvalStep::Tail(form) => lower_tail(*form),
|
||||
});
|
||||
EvalStep::Clamp(field) => clamp_tokens(field).unwrap_or_default(),
|
||||
EvalStep::Tail(form) => lower_tail(*form),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let top_level = quote! {
|
||||
#cfg
|
||||
@@ -2629,29 +2640,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
#(#flip_bounds,)*
|
||||
#(#where_predicates,)*
|
||||
{
|
||||
fn serve<'__serve, '__slot>(&self, __input: &#ctx_ident, __slot: #core_types::record::FrameClaim<'__slot>) -> #core_types::gpoll::GPoll<#core_types::record::Served<'__serve>>
|
||||
fn serve<'__serve, '__slot>(&self, __input: &#ctx_ident, __slot: #core_types::record::FrameClaim<'__serve, '__slot>) -> #core_types::gpoll::GPoll<#core_types::record::Served<'__serve>>
|
||||
where
|
||||
#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__serve #core_types::arena::Arena>,
|
||||
{
|
||||
// The exit trace rides a guard so early returns report too, which
|
||||
// is what pins a frame leak to its node.
|
||||
#[cfg(debug_assertions)]
|
||||
let __sp_trace = {
|
||||
static __SP_TRACE: ::std::sync::OnceLock<bool> = ::std::sync::OnceLock::new();
|
||||
struct __SpTrace(&'static str, usize);
|
||||
impl ::core::ops::Drop for __SpTrace {
|
||||
fn drop(&mut self) {
|
||||
::std::eprintln!("node> {} exit sp {} -> {}", self.0, self.1, #core_types::record::stack::sp());
|
||||
}
|
||||
}
|
||||
match *__SP_TRACE.get_or_init(|| ::std::env::var_os("GRAPHENE_SP_DEBUG").is_some()) {
|
||||
true => {
|
||||
::std::eprintln!("node> {} enter sp {}", ::std::stringify!(#fn_name), #core_types::record::stack::sp());
|
||||
Some(__SpTrace(::std::stringify!(#fn_name), #core_types::record::stack::sp()))
|
||||
}
|
||||
false => None,
|
||||
}
|
||||
};
|
||||
#frame_entry
|
||||
let __cell = #cell_constructor;
|
||||
#(#eval_body)*
|
||||
|
||||
@@ -771,7 +771,7 @@ mod tests {
|
||||
assert_bridge(
|
||||
quote!(category("")),
|
||||
quote! {
|
||||
fn memo<'e, 'l>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>>, slot: FrameClaim<'l>) -> GPoll<Served<'e>> { content.serve(&(), slot) }
|
||||
fn memo<'e, 'l>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>>, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>> { content.serve(&(), slot) }
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1013,7 +1013,7 @@ mod tests {
|
||||
assert_bindings(
|
||||
quote!(category("")),
|
||||
quote!(
|
||||
fn memo<'e, 'l>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>>, slot: FrameClaim<'l>) -> GPoll<Served<'e>> {
|
||||
fn memo<'e, 'l>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>>, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>> {
|
||||
content.serve(&(), slot)
|
||||
}
|
||||
),
|
||||
|
||||
@@ -3,7 +3,7 @@ use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll, ModifyIndex};
|
||||
use core_types::frame_table::{FrameTable, Lookup};
|
||||
use core_types::gpoll::{Finality, GPoll};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::record::{FrameClaim, LevelStatus, OwnedRecord, Served, copy_record_bytes};
|
||||
use core_types::record::{FrameClaim, LevelStatus, MaterializedSpan, OwnedRecord, Served, copy_record_bytes};
|
||||
use core_types::registry::cache_key;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
@@ -14,9 +14,9 @@ use std::sync::Mutex;
|
||||
#[derive(Debug)]
|
||||
pub struct MemoLevel {
|
||||
key: u64,
|
||||
generation: u64,
|
||||
frames: usize,
|
||||
stride: usize,
|
||||
/// The arena region the level materialized into, resolvable only while its
|
||||
/// generation is live.
|
||||
span: Option<MaterializedSpan>,
|
||||
lanes: Vec<OwnedRecord>,
|
||||
finality: Finality,
|
||||
}
|
||||
@@ -33,7 +33,7 @@ fn memoize<'e, 'l>(
|
||||
ctx: impl Ctx + CacheHash + DeriveCtx + ExtractArena<'e> + ModifyIndex + Copy,
|
||||
#[data] cache: Arc<Mutex<Option<MemoLevel>>>,
|
||||
content: impl Node<Context<'_>>,
|
||||
slot: FrameClaim<'l>,
|
||||
slot: FrameClaim<'e, 'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
// A scalar wire's value may depend on the consuming lane (index readers),
|
||||
// so only a leveled wire, whose level covers every lane by construction,
|
||||
@@ -57,15 +57,17 @@ fn memoize<'e, 'l>(
|
||||
};
|
||||
// The claim is this node's output frame: a hit fills it from the cached
|
||||
// bytes, and every valueless exit drops it with the frame still claimed.
|
||||
let serve = |entry: &MemoLevel, mut slot: FrameClaim<'l>| {
|
||||
let serve = |entry: &MemoLevel, mut slot: FrameClaim<'e, 'l>| {
|
||||
if lane >= entry.lanes.len() {
|
||||
// The cached level ends here; the past-end signal serves drains.
|
||||
return GPoll::Error(Box::new(core_types::gpoll::GraphError::past_end()));
|
||||
}
|
||||
if entry.generation == ctx.arena().generation() {
|
||||
// SAFETY: within the generation the materialized batch stays live,
|
||||
// immutable, and laid out at the recorded stride.
|
||||
unsafe { slot.fill_copy((entry.frames + lane * entry.stride) as *const u8) };
|
||||
if let Some(span) = entry.span
|
||||
&& let Some(src) = span.lane(ctx.arena(), lane, content.layout())
|
||||
{
|
||||
// SAFETY: the span resolved in generation, so the lane is live and
|
||||
// immutable at the layout it was materialized under.
|
||||
unsafe { slot.fill_copy(src) };
|
||||
// SAFETY: the copy images a complete record of this layout.
|
||||
return finalized(unsafe { slot.finish_served() }, &entry.finality);
|
||||
}
|
||||
@@ -88,12 +90,7 @@ fn memoize<'e, 'l>(
|
||||
let lanes: Vec<OwnedRecord> = (0..batch.len()).map(|index| unsafe { OwnedRecord::copy_out(layout, batch.get(index).rec()) }).collect();
|
||||
let entry = MemoLevel {
|
||||
key,
|
||||
generation: ctx.arena().generation(),
|
||||
frames: match batch.len() {
|
||||
0 => 0,
|
||||
_ => batch.get(0).rec().ptr() as usize,
|
||||
},
|
||||
stride: layout.lane_stride(),
|
||||
span: MaterializedSpan::of(&batch, ctx.arena()),
|
||||
lanes,
|
||||
finality,
|
||||
};
|
||||
@@ -119,9 +116,7 @@ fn memoize<'e, 'l>(
|
||||
key,
|
||||
// A scalar record replays from the deep copy; the value the serve
|
||||
// returned already lives in this frame.
|
||||
generation: u64::MAX,
|
||||
frames: 0,
|
||||
stride: 0,
|
||||
span: None,
|
||||
lanes: vec![copy],
|
||||
finality,
|
||||
});
|
||||
@@ -134,7 +129,7 @@ fn frame_memo<'e, 'l>(
|
||||
ctx: impl Ctx + CacheHash + ExtractArena<'e>,
|
||||
#[data] cell: ArenaCell<FrameTable<Box<[u8]>, 32>>,
|
||||
content: impl Node<Context<'_>>,
|
||||
frame: FrameClaim<'l>,
|
||||
frame: FrameClaim<'e, 'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
let arena = ctx.arena();
|
||||
let table = match cell.load(arena) {
|
||||
@@ -150,7 +145,7 @@ fn frame_memo<'e, 'l>(
|
||||
// SAFETY: published bytes are same-frame copies of this edge's records,
|
||||
// so they carry the edge's layout with live parked references, and the
|
||||
// claim is that layout's frame.
|
||||
let revive = |mut frame: FrameClaim<'l>, bytes: &Box<[u8]>| unsafe {
|
||||
let revive = |mut frame: FrameClaim<'e, 'l>, bytes: &Box<[u8]>| unsafe {
|
||||
frame.fill_copy(bytes.as_ptr());
|
||||
frame.finish_served()
|
||||
};
|
||||
@@ -191,7 +186,7 @@ fn monitor<'e, 'l>(
|
||||
ctx: impl Ctx + DeriveCtx + ExtractAll + ExtractArena<'e> + ModifyIndex + Copy,
|
||||
#[data] io: MonitorValue,
|
||||
content: impl Node<Context<'_>>,
|
||||
slot: FrameClaim<'l>,
|
||||
slot: FrameClaim<'e, 'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
if ctx.index() == 0 {
|
||||
*io.lock().unwrap() = Some(CtxSnapshot::capture(ctx));
|
||||
@@ -233,10 +228,6 @@ mod tests {
|
||||
}
|
||||
|
||||
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -249,6 +240,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn monitor_serialize_recreates_the_value_from_its_snapshot() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -260,14 +252,14 @@ mod tests {
|
||||
assert!(handle.serialize().is_none(), "no snapshot before the first eval");
|
||||
|
||||
let edge = handle.duplicate().downcast_record::<u32>().unwrap();
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
|
||||
let io = handle.serialize().expect("the eval landed a snapshot");
|
||||
let snapshot = io.downcast_ref::<CtxSnapshot>().expect("the monitor serializes its context snapshot");
|
||||
let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains");
|
||||
let GPoll::Final(served) = core_types::record::capture(&edge, &ctx) else {
|
||||
let GPoll::Final(served) = core_types::record::capture(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(served.element::<u32>(), 11);
|
||||
@@ -275,6 +267,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_leveled_monitor_recreates_the_whole_extent() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 12).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -286,14 +279,14 @@ mod tests {
|
||||
let handle = EdgeHandle::new_record::<u32>(Arc::new(monitor) as Arc<ErasedRecordNode>);
|
||||
|
||||
let edge = handle.duplicate().downcast_record::<u32>().unwrap();
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
|
||||
let io = handle.serialize().expect("the eval landed a snapshot");
|
||||
let snapshot = io.downcast_ref::<CtxSnapshot>().expect("the monitor serializes its context snapshot");
|
||||
let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains");
|
||||
let LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&edge, &ctx, &arena) else {
|
||||
let LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&edge, &ctx, &arena, &frames) else {
|
||||
panic!("expected a materialized level");
|
||||
};
|
||||
assert_eq!(batch.len(), 3, "the recreation holds the whole extent, not the addressed lane");
|
||||
@@ -304,8 +297,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn memo_copy_out_consults_the_deep_element_clone() {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(dyn_any::DynAny)]
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
#[derive(Clone, Debug, PartialEq, dyn_any::DynAny)]
|
||||
struct Payload(String, u32);
|
||||
unsafe fn deep(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
let value = unsafe { core_types::record::borrow_element::<Payload>(core_types::record::Rec::new(ptr)) };
|
||||
@@ -326,12 +319,17 @@ mod tests {
|
||||
let memoized = MemoizeNode::new(lifted::<Payload>(Payload("deep".to_string(), 0)), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<Payload, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 0)), "the miss serves the live value");
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 2)), "the hit replays through both halves of the deep glue");
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Final(Payload("deep".to_string(), 0)), "the miss serves the live value");
|
||||
assert_eq!(
|
||||
memoized.eval(&ctx, &frames),
|
||||
GPoll::Final(Payload("deep".to_string(), 2)),
|
||||
"the hit replays through both halves of the deep glue"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memoize_caches_across_evals() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -341,12 +339,13 @@ mod tests {
|
||||
let memoized = MemoizeNode::new(counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Final(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_invalidates_on_generation_bump() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let source: SourceId = 7;
|
||||
let before = [(source, 1)];
|
||||
@@ -358,13 +357,14 @@ mod tests {
|
||||
let memoized = MemoizeNode::new(counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_after)), GPoll::Final(2));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before), &frames), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before), &frames), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_after), &frames), GPoll::Final(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_replays_partiality_on_hit() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -374,12 +374,13 @@ mod tests {
|
||||
let memoized = MemoizeNode::new(partial_counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Partial(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Partial(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memoized_edges_stack_and_rewire() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -391,12 +392,13 @@ mod tests {
|
||||
let stacked = MemoizeNode::new(memoized.downcast_record::<u32>().unwrap(), &layout);
|
||||
let stacked = core_types::record::RecordExtract::<u32, _>::new(stacked, &layout);
|
||||
|
||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(stacked.eval(&ctx, &frames), GPoll::Final(1));
|
||||
assert_eq!(stacked.eval(&ctx, &frames), GPoll::Final(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_memo_shares_one_record_copy_per_frame() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(4096).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -405,10 +407,10 @@ mod tests {
|
||||
let layout = element_layout::<String>();
|
||||
let memo = FrameMemoNode::new(lifted::<String>("lent out".to_string()), &layout);
|
||||
|
||||
let GPoll::Final(first) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
let GPoll::Final(first) = core_types::record::serve_edge(&memo, &ctx, &frames) else {
|
||||
panic!("the miss must fill the frame table");
|
||||
};
|
||||
let GPoll::Final(second) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
let GPoll::Final(second) = core_types::record::serve_edge(&memo, &ctx, &frames) else {
|
||||
panic!("the hit must revive the published record");
|
||||
};
|
||||
let first: &String = unsafe { core_types::record::borrow_element(layout.rec(&first)) };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -225,7 +225,7 @@ mod tests {
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{self, FrameClaim, Layout, RecordSource, Served, stack};
|
||||
use core_types::record::{self, FrameClaim, Layout, RecordSource, Served};
|
||||
use core_types::value::ValueSource;
|
||||
|
||||
struct GraphicSource {
|
||||
@@ -234,20 +234,22 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for GraphicSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let (graphic, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()];
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(graphic.clone());
|
||||
frame.attr::<Transform>(*transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(graphic.clone(), arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<Transform>(&mut frame, &self.layout, *transform);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -259,9 +261,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a field at the layout's resolved offset, the wiring-proven pairing
|
||||
/// a generated node performs.
|
||||
fn write_field_at<T: Copy + 'static>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, name: &str, level: u8, value: T) {
|
||||
let field = layout
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| field.name == name && field.level == level)
|
||||
.expect("the layout carries the written field");
|
||||
assert_eq!(field.type_id, std::any::TypeId::of::<T>(), "the field was declared at this value type");
|
||||
// SAFETY: the offset is this layout's own, at the field's declared type.
|
||||
unsafe { frame.attr_at(field.offset, value) };
|
||||
}
|
||||
|
||||
/// [`write_field_at`] for a census marker at level 0.
|
||||
fn write_attr_at<A: core_types::attribute::Attribute>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, value: A::Value<'static>)
|
||||
where
|
||||
A::Value<'static>: Copy + 'static,
|
||||
{
|
||||
write_field_at(frame, layout, A::NAME, 0, value);
|
||||
}
|
||||
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { stack::reserve(1 << 16); } EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
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 {
|
||||
@@ -294,7 +315,7 @@ mod tests {
|
||||
Graphic::Text(label.to_string())
|
||||
}
|
||||
|
||||
fn group(children: Vec<(Graphic<'static>, DAffine2)>) -> Graphic {
|
||||
fn group(children: Vec<(Graphic<'static>, DAffine2)>) -> Graphic<'static> {
|
||||
let mut list = List::new();
|
||||
for (index, (child, transform)) in children.into_iter().enumerate() {
|
||||
list.push(Item::new_from_element(child));
|
||||
@@ -362,7 +383,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex + core_types::ExtractVarArgs> Node<C> for PerRowSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
@@ -372,15 +393,17 @@ mod tests {
|
||||
let lane = input.innermost_index();
|
||||
let graphic = text(&format!("{label}{lane}"));
|
||||
let translated = DAffine2::from_translation(glam::DVec2::new(lane as f64, 0.));
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(graphic);
|
||||
frame.attr::<Transform>(translated);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(graphic, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<Transform>(&mut frame, &self.layout, translated);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -415,6 +438,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn map_scans_ragged_rows() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -441,12 +465,12 @@ mod tests {
|
||||
assert_eq!(out.depth, 2);
|
||||
// The extent-fn-less levels report a lower bound; addressing below
|
||||
// proves the lanes are all reachable regardless.
|
||||
assert_eq!(node.extent_at(&ctx, 1), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(node.extent_at(&ctx, 1, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -457,6 +481,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flat_map_matches_flatten_of_map() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -504,18 +529,18 @@ mod tests {
|
||||
assert_eq!(composed_out.depth, 1);
|
||||
// Both spellings report the same lower bound; the lane loop below is
|
||||
// the law.
|
||||
assert_eq!(flat.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(composed.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(flat.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(composed.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() {
|
||||
let scoped = ctx.promoted(&head, lane as u64);
|
||||
let GPoll::Final(direct) = record::capture(&flat, &scoped) else {
|
||||
let GPoll::Final(direct) = record::capture(&flat, &scoped, &frames) else {
|
||||
panic!("expected a final record from flat_map");
|
||||
};
|
||||
let direct_label = text_of(&direct.element::<Graphic>()).to_string();
|
||||
let direct_x: DAffine2 = direct.attr::<Transform>();
|
||||
let GPoll::Final(value) = record::capture(&composed, &scoped) else {
|
||||
let GPoll::Final(value) = record::capture(&composed, &scoped, &frames) else {
|
||||
panic!("expected a final record from flatten(map)");
|
||||
};
|
||||
assert_eq!(text_of(&value.element::<Graphic>()), direct_label, "lane {lane}");
|
||||
@@ -540,6 +565,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flat_map_batch_matches_per_lane_eval() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -567,14 +593,14 @@ mod tests {
|
||||
let scoped = ctx.promoted(&head, 0);
|
||||
|
||||
let mut scratch = vec![std::mem::MaybeUninit::<u64>::uninit(); 5 * out.lane_stride() / 8];
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..5, Some(&mut scratch)) else {
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..5, Some(&mut scratch), &frames) else {
|
||||
panic!("expected a filled batch");
|
||||
};
|
||||
let batch = batch.into_shared();
|
||||
assert_eq!(batch.len(), 5);
|
||||
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
|
||||
for lane in 0..5 {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let single = text_of(&record.element::<Graphic>()).to_string();
|
||||
@@ -587,6 +613,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_expands_one_level() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -596,13 +623,13 @@ mod tests {
|
||||
let node = build!(layout, fixture_rows(), false);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(out.depth, 1);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
// Lane 2 is the unexpanded subgroup H, riding as a leaf at G's depth.
|
||||
let expected: [(&str, f64); 2] = [("a", 1.), ("b", 20.5)];
|
||||
for (lane, &(label, x)) in expected.iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -610,7 +637,7 @@ mod tests {
|
||||
assert_eq!(transform.translation.x, x, "lane {lane}");
|
||||
}
|
||||
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, 2)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, 2), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Graphic(children) = record.element::<Graphic>() else {
|
||||
@@ -629,13 +656,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_wire_materializes_into_a_group_for_the_renderer() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let source = core_types::value::LeveledValueSource::new(vec![text("a"), text("b")]);
|
||||
match graphic_types::boundary::materialize_group(&source, &ctx, &arena) {
|
||||
match graphic_types::boundary::materialize_group(&source, &ctx, &arena, &frames) {
|
||||
graphic_types::boundary::LevelGroup::Group(group, _) => {
|
||||
let list = graphic_types::graphic::group_to_legacy_list(&group);
|
||||
assert_eq!(list.len(), 2);
|
||||
@@ -646,6 +674,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_level_batch_converts_to_its_legacy_list() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -653,7 +682,7 @@ mod tests {
|
||||
|
||||
let source = core_types::value::LeveledValueSource::new(vec![1.5f64, 2.5]);
|
||||
let layout = Node::<ContextImpl>::layout(&source).clone();
|
||||
let record::LevelStatus::Batch(batch, _) = record::materialize_level(&source, &ctx, &arena) else {
|
||||
let record::LevelStatus::Batch(batch, _) = record::materialize_level(&source, &ctx, &arena, &frames) else {
|
||||
panic!("expected a batch");
|
||||
};
|
||||
let legacy = graphic_types::boundary::batch_to_legacy(&layout, batch, &arena).expect("f64 is in the legacy vocabulary");
|
||||
@@ -665,6 +694,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn wrap_collects_the_level_into_a_group() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -679,10 +709,10 @@ mod tests {
|
||||
);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(out.depth, 1);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(1)), "the group is the level's single lane");
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(1)), "the group is the level's single lane");
|
||||
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(out.rec(&value)) }) else {
|
||||
@@ -702,6 +732,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_group_element_deep_copies_to_its_owned_form_and_replays() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -717,7 +748,7 @@ mod tests {
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let copy = unsafe { (out.element.clone_out)(out.rec(&value).ptr()) };
|
||||
@@ -742,25 +773,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn colors_fold_into_evenly_spaced_stops() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
struct ColorSource {
|
||||
layout: Layout,
|
||||
colors: Vec<Color>,
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for ColorSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let color = self.colors[input.innermost_index() as usize];
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(color);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(color, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -782,7 +816,7 @@ mod tests {
|
||||
let build = |colors: Vec<Color>| install_flip(ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout), &out);
|
||||
let stops_of = |colors: Vec<Color>| {
|
||||
let node = build(colors);
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
record.element::<GradientStops>()
|
||||
@@ -801,6 +835,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_group_converts_to_its_legacy_list() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -815,7 +850,7 @@ mod tests {
|
||||
);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(out.rec(&value)) }) else {
|
||||
@@ -832,6 +867,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_reverses_wrap() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -849,8 +885,8 @@ mod tests {
|
||||
let group = {
|
||||
// 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) = record::serve_edge(&wrapped, &ctx.promoted(&head, 0)) else {
|
||||
let scope = frames.scope();
|
||||
let GPoll::Final(value) = record::serve_edge(&wrapped, &ctx.promoted(&head, 0), &scope) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let group = unsafe { record::borrow_element::<Graphic>(wrap_out.rec(&value)) }.clone();
|
||||
@@ -860,11 +896,11 @@ mod tests {
|
||||
// One row holding the wrapped group flattens back to the lanes, the
|
||||
// group's identity transform composed onto each child's.
|
||||
let node = build!(layout, vec![(group, DAffine2::IDENTITY)], false);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(2)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(2)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, &(label, x)) in [("a", 1.), ("b", 2.)].iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -875,6 +911,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_fully_composes_the_path() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -884,12 +921,12 @@ mod tests {
|
||||
rows.push((group(vec![]), translation(9.)));
|
||||
let layout = graphic_layout();
|
||||
let node = build!(layout, rows, true);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves");
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves");
|
||||
|
||||
let head = ctx.index_head();
|
||||
let expected: [(&str, f64); 3] = [("a", 1.), ("b", 20.5), ("c", 4300.5)];
|
||||
for (lane, &(label, x)) in expected.iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -900,6 +937,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_batch_matches_per_lane_eval() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -912,14 +950,14 @@ mod tests {
|
||||
let scoped = ctx.promoted(&head, 0);
|
||||
|
||||
let mut scratch = vec![std::mem::MaybeUninit::<u64>::uninit(); 3 * out.lane_stride() / 8];
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..3, Some(&mut scratch)) else {
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..3, Some(&mut scratch), &frames) else {
|
||||
panic!("expected a filled batch");
|
||||
};
|
||||
let batch = batch.into_shared();
|
||||
assert_eq!(batch.len(), 3);
|
||||
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
|
||||
for lane in 0..3 {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let single = text_of(&record.element::<Graphic>()).to_string();
|
||||
|
||||
@@ -261,10 +261,7 @@ mod tests {
|
||||
|
||||
let probe = core_types::record::LiftedSource::<RenderOutput, _>::new(probe);
|
||||
let layout = Node::<ContextImpl>::layout(&probe).clone();
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(layout.frame_bytes().max(1 << 12));
|
||||
}
|
||||
let frames = core_types::record::test_frames(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.
|
||||
@@ -277,7 +274,7 @@ mod tests {
|
||||
lane_invariant: u32::MAX,
|
||||
},
|
||||
);
|
||||
let GPoll::Final(result) = core_types::record::serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(result) = core_types::record::serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("create_context must complete synchronously");
|
||||
};
|
||||
let output: &RenderOutput = unsafe { core_types::record::borrow_element(layout.rec(&result)) };
|
||||
|
||||
@@ -1014,7 +1014,7 @@ mod graphene_test {
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::gpoll::{Finality, GPoll};
|
||||
use core_types::node::{BatchStatus, Node};
|
||||
use core_types::record::{Layout, LiftedSource, RecordValue, serve_edge, stack};
|
||||
use core_types::record::{Layout, LiftedSource, RecordValue, serve_edge};
|
||||
use core_types::registry::{ErasedRecordNode, construct};
|
||||
use core_types::value::record_value_edge;
|
||||
use std::mem::MaybeUninit;
|
||||
@@ -1023,9 +1023,9 @@ mod graphene_test {
|
||||
EvalScope::new(None, None, None, &[], arena)
|
||||
}
|
||||
|
||||
fn reserve_for(layouts: &[&Layout]) {
|
||||
// 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 frames_for(layouts: &[&Layout]) -> core_types::record::Frames<'static> {
|
||||
core_types::record::test_frames(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.
|
||||
@@ -1071,9 +1071,9 @@ mod graphene_test {
|
||||
let (b, lb) = lifted(|_: &ContextImpl| GPoll::Final(2.0f64));
|
||||
let out = out_layout::<f64>();
|
||||
let graph = installed(AddNode::<_, _, f64, f64>::new(a, b, &la, &lb), &out);
|
||||
reserve_for(&[&la, &lb, &out]);
|
||||
let frames = frames_for(&[&la, &lb, &out]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 3.0);
|
||||
@@ -1089,12 +1089,12 @@ mod graphene_test {
|
||||
let (src, ls) = lifted(|_: &ContextImpl| GPoll::Final(10.0f64));
|
||||
let out = out_layout::<f64>();
|
||||
let node = installed(AddNode::<_, _, f64, f64>::new(index, src, &li, &ls), &out);
|
||||
reserve_for(&[&li, &ls, &out]);
|
||||
let frames = frames_for(&[&li, &ls, &out]);
|
||||
|
||||
let erased: Box<ErasedRecordNode> = Box::new(node);
|
||||
// One u64 word per lane at the element-only layout.
|
||||
let mut scratch = [const { MaybeUninit::uninit() }; 4];
|
||||
let status = erased.eval_batch(&ctx, 2..6, Some(&mut scratch));
|
||||
let status = erased.eval_batch(&ctx, 2..6, Some(&mut scratch), &frames);
|
||||
let BatchStatus::Filled(batch, finality, _) = status else {
|
||||
panic!("expected filled, got {status:?}");
|
||||
};
|
||||
@@ -1120,9 +1120,9 @@ mod graphene_test {
|
||||
lane_invariant: u32::MAX,
|
||||
});
|
||||
let edge = wired.downcast_record::<bool>().unwrap();
|
||||
reserve_for(&[&layout]);
|
||||
let frames = frames_for(&[&layout]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert!(element::<bool>(&layout, &value));
|
||||
@@ -1166,9 +1166,9 @@ mod graphene_test {
|
||||
lane_invariant: u32::MAX,
|
||||
});
|
||||
let edge = wired.downcast_record::<f64>().unwrap();
|
||||
reserve_for(&[&layout]);
|
||||
let frames = frames_for(&[&layout]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&layout, &value), 4.0);
|
||||
@@ -1213,9 +1213,9 @@ mod graphene_test {
|
||||
let union = core_types::record::Layout::union(&[<, &lf]);
|
||||
let graph = SwitchNode::new(cond, if_true, if_false, &union, &lc);
|
||||
let out = Node::<ContextImpl>::layout(&graph).clone();
|
||||
reserve_for(&[&lc, <, &lf, &out]);
|
||||
let frames = frames_for(&[&lc, <, &lf, &out]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 1.0);
|
||||
@@ -1239,10 +1239,10 @@ mod graphene_test {
|
||||
let (pa2, lpa2) = lifted(|_: &ContextImpl| GPoll::Partial(7.0f64));
|
||||
let partial = SwitchNode::new(c2, p2, pa2, &core_types::record::Layout::union(&[&lp2, &lpa2]), &lc2);
|
||||
let out = Node::<ContextImpl>::layout(&partial).clone();
|
||||
reserve_for(&[&lc1, &lp1, &lpa1, &lc2, &lp2, &lpa2, &out]);
|
||||
let frames = frames_for(&[&lc1, &lp1, &lpa1, &lc2, &lp2, &lpa2, &out]);
|
||||
|
||||
assert!(matches!(serve_edge(&pending, &ctx), GPoll::Pending));
|
||||
let GPoll::Partial(value) = serve_edge(&partial, &ctx) else {
|
||||
assert!(matches!(serve_edge(&pending, &ctx, &frames), GPoll::Pending));
|
||||
let GPoll::Partial(value) = serve_edge(&partial, &ctx, &frames) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 7.0);
|
||||
@@ -1260,9 +1260,9 @@ mod graphene_test {
|
||||
let union = core_types::record::Layout::union(&[<, &lf]);
|
||||
let graph = SwitchNode::new(cond, if_true, if_false, &union, &lc);
|
||||
let out = Node::<ContextImpl>::layout(&graph).clone();
|
||||
reserve_for(&[&lc, <, &lf, &out]);
|
||||
let frames = frames_for(&[&lc, <, &lf, &out]);
|
||||
|
||||
let GPoll::Partial(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Partial(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 1.0);
|
||||
@@ -1278,9 +1278,9 @@ mod graphene_test {
|
||||
let (src, ls) = lifted(|_: &ContextImpl| GPoll::Final(5.0f64));
|
||||
let out = out_layout::<f64>();
|
||||
let graph = installed(AddNode::<_, _, f64, f64>::new(fallback, src, &lfb, &ls), &out);
|
||||
reserve_for(&[&lfb, &ls, &out]);
|
||||
let frames = frames_for(&[&lfb, &ls, &out]);
|
||||
|
||||
let GPoll::Fallback(boxed) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Fallback(boxed) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("fallback must propagate with the computed stand-in");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &boxed.0), 5.0);
|
||||
|
||||
@@ -70,8 +70,8 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_image_color_palette() {
|
||||
// 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 frames = core_types::record::test_frames(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);
|
||||
@@ -83,7 +83,7 @@ mod test {
|
||||
base64_string: None,
|
||||
});
|
||||
let source = core_types::value::LeveledValueSource::new(vec![raster]);
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena) else {
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena, &frames) else {
|
||||
panic!("materialize failed")
|
||||
};
|
||||
let image = unsafe { core_types::node::List::<Raster<CPU>>::new(batch) };
|
||||
|
||||
@@ -183,7 +183,7 @@ mod test {
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{FieldWrite, FrameBuilder, FrameClaim, Layout, RecordSource, Served, capture, element_write, stack};
|
||||
use core_types::record::{FieldWrite, FrameClaim, Layout, RecordSource, Served, capture, element_write};
|
||||
use core_types::value::ValueSource;
|
||||
use vector_types::subpath::Subpath;
|
||||
|
||||
@@ -194,16 +194,18 @@ mod test {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for TransformSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let mut frame = FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(self.element);
|
||||
frame.attr::<TransformAttr>(self.transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(self.element, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<TransformAttr>(&mut frame, &self.layout, self.transform);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
@@ -211,9 +213,28 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a field at the layout's resolved offset, the wiring-proven pairing
|
||||
/// a generated node performs.
|
||||
fn write_field_at<T: Copy + 'static>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, name: &str, level: u8, value: T) {
|
||||
let field = layout
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| field.name == name && field.level == level)
|
||||
.expect("the layout carries the written field");
|
||||
assert_eq!(field.type_id, std::any::TypeId::of::<T>(), "the field was declared at this value type");
|
||||
// SAFETY: the offset is this layout's own, at the field's declared type.
|
||||
unsafe { frame.attr_at(field.offset, value) };
|
||||
}
|
||||
|
||||
/// [`write_field_at`] for a census marker at level 0.
|
||||
fn write_attr_at<A: core_types::attribute::Attribute>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, value: A::Value<'static>)
|
||||
where
|
||||
A::Value<'static>: Copy + 'static,
|
||||
{
|
||||
write_field_at(frame, layout, A::NAME, 0, value);
|
||||
}
|
||||
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { stack::reserve(1 << 12); } EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
struct VectorRows {
|
||||
@@ -222,20 +243,22 @@ mod test {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for VectorRows {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let (vector, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()];
|
||||
let mut frame = FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(vector.clone());
|
||||
frame.attr::<TransformAttr>(*transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(vector.clone(), arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<TransformAttr>(&mut frame, &self.layout, *transform);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -256,17 +279,19 @@ mod test {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex + core_types::context::ExtractPosition> Node<C> for PositionProbe {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let position = input.try_position().and_then(|mut positions| positions.next()).unwrap_or(DVec2::ZERO);
|
||||
let mut frame = FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(position.x);
|
||||
frame.attr::<TransformAttr>(DAffine2::IDENTITY);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(position.x, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<TransformAttr>(&mut frame, &self.layout, DAffine2::IDENTITY);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
@@ -280,6 +305,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_array_composes_the_step_onto_each_copys_transform() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -302,12 +328,12 @@ mod test {
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_array_layout_meta().resolve(&[Some(&layout)]));
|
||||
let leveled = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(leveled.depth, 1, "the IList return pushed one rank level above the content");
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for copy in 0..3u64 {
|
||||
let lane = ctx.promoted(&head, copy);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(record.element::<f64>(), 7.);
|
||||
@@ -320,6 +346,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_radial_rotates_each_copy_around_the_center() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -335,12 +362,12 @@ mod test {
|
||||
|
||||
let mut node = RepeatRadialNode::new(RecordSource::new(content, &layout, &layout), ValueSource::new(90.0f64), ValueSource::new(2.0f64), ValueSource::new(4u32), &layout);
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_radial_layout_meta().resolve(&[Some(&layout)]));
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(4)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(4)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for copy in 0..4u64 {
|
||||
let lane = ctx.promoted(&head, copy);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(record.element::<f64>(), 7.);
|
||||
@@ -354,6 +381,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_on_points_lands_each_copy_on_its_transformed_point() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -376,14 +404,18 @@ mod test {
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_on_points_layout_meta().resolve(&[Some(&content_layout)]));
|
||||
let leveled = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(leveled.depth, 1);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(5)), "the pushed level flattens both rows' points");
|
||||
assert_eq!(
|
||||
node.extent_at(&ctx, 0, &frames.reborrow()),
|
||||
GPoll::Final(Extent::Exactly(5)),
|
||||
"the pushed level flattens both rows' points"
|
||||
);
|
||||
|
||||
let expected: Vec<DVec2> = row0.iter().map(|&point| row0_transform.transform_point2(point)).chain(row1.iter().copied()).collect();
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (flat, &point) in expected.iter().enumerate() {
|
||||
let lane = ctx.promoted(&head, flat as u64);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
// The content saw the pushed position, and the output transform lands on it.
|
||||
@@ -395,6 +427,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_on_points_reverse_flips_each_rows_points() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -416,7 +449,7 @@ mod test {
|
||||
let head = ctx.index_head();
|
||||
for (flat, &point) in expected.iter().enumerate() {
|
||||
let lane = ctx.promoted(&head, flat as u64);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let composed: DAffine2 = record.attr::<TransformAttr>();
|
||||
|
||||
@@ -3870,8 +3870,8 @@ mod test {
|
||||
}
|
||||
#[test]
|
||||
fn path_length() {
|
||||
// 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 frames = core_types::record::test_frames(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);
|
||||
@@ -3882,7 +3882,7 @@ mod test {
|
||||
// Element-only lanes read identity lane transforms; the transform term
|
||||
// rides the demo gate.
|
||||
let source = core_types::value::LeveledValueSource::new(vec![row; 5]);
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena) else {
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena, &frames) else {
|
||||
panic!("materialize failed")
|
||||
};
|
||||
let list = unsafe { core_types::node::List::<Vector>::new(batch) };
|
||||
|
||||
Reference in New Issue
Block a user