Make Node::layout non-optional, defaulting to a shared empty layout

This commit is contained in:
Dennis Kobert
2026-08-11 09:04:08 +00:00
parent 5b02edfd9a
commit 5db7e1816a
7 changed files with 49 additions and 37 deletions

View File

@@ -71,12 +71,12 @@ pub trait Node<Input> {
None
}
/// The record layout of this node's output; `None` for element-only
/// producers. Consumers read their carrier's layout through this at
/// wiring, and the wiring layer derives stack sizing from the same
/// 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.
fn layout(&self) -> Option<&crate::record::Layout> {
None
fn layout(&self) -> &crate::record::Layout {
crate::record::empty_layout()
}
fn eval_batch<'a>(&self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<Self::Output>]>) -> BatchStatus<'a, Self::Output>
@@ -141,7 +141,7 @@ where
(**self).serialize()
}
fn layout(&self) -> Option<&crate::record::Layout> {
fn layout(&self) -> &crate::record::Layout {
(**self).layout()
}
@@ -171,7 +171,7 @@ where
(**self).serialize()
}
fn layout(&self) -> Option<&crate::record::Layout> {
fn layout(&self) -> &crate::record::Layout {
(**self).layout()
}
@@ -201,6 +201,10 @@ where
(**self).serialize()
}
fn layout(&self) -> &crate::record::Layout {
(**self).layout()
}
fn eval_batch<'a>(&self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<Self::Output>]>) -> BatchStatus<'a, Self::Output>
where
Input: InjectIndex + Copy,

View File

@@ -234,6 +234,14 @@ impl Layout {
#[derive(Clone, Copy, Debug, Default)]
pub struct ElToken;
/// The shared empty layout: `depth` 0, no element, no fields, so `frame_bytes`
/// is 0. The `Node::layout` default returns it for element-only and test nodes,
/// which carry no record.
pub fn empty_layout() -> &'static Layout {
static EMPTY: std::sync::OnceLock<Layout> = std::sync::OnceLock::new();
EMPTY.get_or_init(Layout::default)
}
/// A view of one record: a pointer whose layout is proven at wiring.
#[derive(Clone, Copy, Debug)]
pub struct Rec(*const u8);
@@ -1081,8 +1089,8 @@ where
lift_poll(self.edge.eval(input), &self.layout, input.arena())
}
fn layout(&self) -> Option<&Layout> {
Some(&self.layout)
fn layout(&self) -> &Layout {
&self.layout
}
}
@@ -1139,8 +1147,8 @@ where
}
}
fn layout(&self) -> Option<&Layout> {
Some(&self.union)
fn layout(&self) -> &Layout {
&self.union
}
}

View File

@@ -158,7 +158,7 @@ where
#[cfg(debug_assertions)]
debug_assert_eq!(
crate::record::stack::sp(),
sp_before + self.layout().map_or(0, |layout| layout.frame_bytes()),
sp_before + self.layout().frame_bytes(),
"{} left the record stack misaligned",
std::any::type_name::<N>(),
);
@@ -175,7 +175,7 @@ where
unsafe { self.ptr.as_ref() }.serialize()
}
fn layout(&self) -> Option<&crate::record::Layout> {
fn layout(&self) -> &crate::record::Layout {
// SAFETY: as in eval.
unsafe { self.ptr.as_ref() }.layout()
}
@@ -228,7 +228,7 @@ impl EdgeHandle {
node: Box::new(SharedEdge::new(node)),
share: |edge| Box::new(edge.downcast_ref::<SharedEdge<N>>().expect("share hook matches the stored edge type").share()),
serialize: |edge| Node::<ContextImpl>::serialize(edge.downcast_ref::<SharedEdge<N>>().expect("serialize hook matches the stored edge type")),
layout: |edge| Node::<ContextImpl>::layout(edge.downcast_ref::<SharedEdge<N>>().expect("layout hook matches the stored edge type")),
layout: |edge| Some(Node::<ContextImpl>::layout(edge.downcast_ref::<SharedEdge<N>>().expect("layout hook matches the stored edge type"))),
ty,
}
}

View File

@@ -40,8 +40,8 @@ where
crate::record::lift_poll(crate::gpoll::GPoll::Final(self.value.clone()), &self.layout, input.arena())
}
fn layout(&self) -> Option<&crate::record::Layout> {
Some(&self.layout)
fn layout(&self) -> &crate::record::Layout {
&self.layout
}
}