Emit declarative LayoutMeta from the node macro into registry entries

This commit is contained in:
Dennis Kobert
2026-08-11 20:08:45 +00:00
parent 557f8020bb
commit b6f73b0691
5 changed files with 89 additions and 0 deletions

View File

@@ -242,6 +242,52 @@ pub fn empty_layout() -> &'static Layout {
EMPTY.get_or_init(Layout::default)
}
/// Declarative record-io metadata for a node type, emitted by the macro into
/// its registry entry so the compiler can fold each wire's layout without
/// running the node's constructor. [`fold`](LayoutMeta::fold) reproduces the
/// layout the constructor derives at wiring today; the compiler layout pass
/// calls it over the proto graph instead.
#[derive(Clone, Debug)]
pub struct LayoutMeta {
/// Whether the node derives its layout from a carrier input; `false` writes
/// a fresh record from `Layout::default`.
pub carrier: bool,
/// The output element: a concrete write, or carried through from the carrier.
pub element: ElementSpec,
/// The attributes the node writes at its acting level.
pub writes: Vec<FieldWrite>,
/// The attributes removed from the carrier's layout, as `(name, level)`.
pub removes: Vec<(&'static str, u8)>,
/// The depth change the node applies to its carrier: `0` for elementwise and
/// flip nodes, `+1` for a creator, `-1` for a reducer.
pub level_delta: i8,
}
/// Where a node's output element comes from, for [`LayoutMeta`].
#[derive(Clone, Debug)]
pub enum ElementSpec {
/// The node writes this concrete element.
Concrete(ElementWrite),
/// The node carries the carrier's element through unchanged.
Carried,
}
impl LayoutMeta {
/// Folds the node's output layout from its carrier's, reproducing what the
/// node's constructor derives at wiring. `carrier` is the carrier input's
/// layout, or `None` when the node writes a fresh record.
pub fn fold(&self, carrier: Option<&Layout>) -> Layout {
let carrier = carrier.filter(|_| self.carrier);
let base = carrier.map_or_else(Layout::default, |c| c.without(&self.removes));
let depth = (carrier.map_or(0, |c| c.depth) as i8 + self.level_delta).max(0) as u8;
let element = match &self.element {
ElementSpec::Concrete(element) => *element,
ElementSpec::Carried => carrier.map_or_else(ElementWrite::default, |c| c.element),
};
base.with_writes(depth, element, &self.writes)
}
}
/// A view of one record: a pointer whose layout is proven at wiring.
#[derive(Clone, Copy, Debug)]
pub struct Rec(*const u8);

View File

@@ -170,6 +170,11 @@ where
unsafe { self.ptr.as_ref() }.extent(input)
}
fn extent_at(&self, input: &Input, level: u8) -> crate::gpoll::GPoll<crate::gpoll::Extent> {
// SAFETY: as in eval.
unsafe { self.ptr.as_ref() }.extent_at(input, level)
}
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
// SAFETY: as in eval.
unsafe { self.ptr.as_ref() }.serialize()
@@ -278,6 +283,10 @@ pub type NodeConstructor = fn(Vec<EdgeHandle>) -> Result<EdgeHandle, Constructio
pub struct RegistryEntry {
pub io: NodeIOTypes,
pub constructor: NodeConstructor,
/// Declarative record-io metadata for the compiler layout pass; `None` for
/// nodes whose layout the pass does not yet fold (routing/opaque, hand-written
/// rows), which keep the construction-time path.
pub layout_meta: Option<crate::record::LayoutMeta>,
}
pub fn construct(entry: &RegistryEntry, inputs: Vec<EdgeHandle>) -> Result<EdgeHandle, ConstructionError> {
@@ -502,6 +511,7 @@ mod tests {
Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc<ErasedNode<u32>>))
}
let entry = RegistryEntry {
layout_meta: None,
io: NodeIOTypes::new(concrete!(Context), concrete!(u32), vec![edge_type::<String>()]),
constructor: construct_strlen,
};