Key materialization on declared list nesting and carriers on non-materialized subjects

This commit is contained in:
Dennis Kobert
2026-08-17 14:46:48 +00:00
parent 3aa018a15c
commit 4a042bade2
3 changed files with 32 additions and 13 deletions

View File

@@ -63,10 +63,10 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
(Some(crate::codegen::ir::NodeKind::Routing), crate::codegen::ir::Element::Generic(ident)) => Some(ident.clone()),
_ => None,
};
// A `_: ()` primary stays visible in the metadata but claims no struct field.
let record_unit_carrier = record_io && crate::codegen::classify::record_shape(parsed).is_some_and(|shape| shape.skips_carrier());
let record_skips_carrier = record_io && !carrier_present;
// Record nodes with a `_: ()` primary input have no carrier edge; the unit
// field stays visible in the metadata but claims no struct field.
let struct_regular_fields: Vec<_> = regular_fields.iter().skip(record_skips_carrier as usize).copied().collect();
let struct_regular_fields: Vec<_> = regular_fields.iter().skip(record_unit_carrier as usize).copied().collect();
let struct_regular_field_names: Vec<_> = struct_regular_fields.iter().map(|f| &f.pat_ident.ident).collect();
// Extract function generics used by data fields

View File

@@ -327,7 +327,7 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
let (prelude, new_layout_args, layout_meta) = match ir::node_kind(&node) {
ir::NodeKind::RecordIo => {
let carrier_arg = node.inputs.first().is_some_and(|input| input.subject).then(|| quote!(&__layout_0,));
let carrier_arg = (node.inputs.first().is_some_and(|input| input.subject) && ir::materialized_levels(&node, 0) == 0).then(|| quote!(&__layout_0,));
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
(quote!(), quote!(#carrier_arg #(#value_layout_args)*), quote!(Some(self::#layout_meta_fn())))
}

View File

@@ -215,7 +215,13 @@ fn ilist_inner(ty: &Type) -> Option<Type> {
/// Emits the `LayoutMeta` literal from the IR. `element_spec` is supplied by the
/// caller since it is the one row-dependent facet; the rest folds from the node.
pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_types: &TokenStream2) -> TokenStream2 {
let sources = node.inputs.iter().enumerate().filter(|(_, input)| input.subject).map(|(index, _)| index as u8);
// A materialized subject is folded, not carried: it contributes no layout.
let sources = node
.inputs
.iter()
.enumerate()
.filter(|(index, input)| input.subject && materialized_levels(node, *index) == 0)
.map(|(index, _)| index as u8);
let reads = node.inputs.iter().enumerate().filter_map(|(index, input)| {
(matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()).then(|| {
let descs = field_writes(&input.shape.attrs, core_types);
@@ -254,8 +260,15 @@ fn field_writes(attrs: &[LevelAttr], core_types: &TokenStream2) -> Vec<TokenStre
}
fn level_delta(node: &Node) -> i8 {
let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth as i8);
node.output.shape.depth as i8 - subject_depth
// A materialized subject contributes no base layout, so the delta is
// relative to the fresh (empty) base.
let base_depth = node
.inputs
.iter()
.enumerate()
.find(|(index, input)| input.subject && materialized_levels(node, *index) == 0)
.map_or(0, |(_, input)| input.shape.depth as i8);
node.output.shape.depth as i8 - base_depth
}
/// How an eager value input binds in eval.
@@ -323,11 +336,11 @@ fn has_attr_io(node: &Node) -> bool {
/// into a `List` before the kernel.
pub(crate) fn materialized_levels(node: &Node, index: usize) -> u8 {
let input = &node.inputs[index];
// A ranked subject folds the levels the output collapses; a ranked
// non-subject input is consumed whole.
match input.subject {
true => input.shape.depth.saturating_sub(node.output.shape.depth),
false => input.shape.depth,
// An eager input's declared `IList` nesting IS its materialization count,
// independent of the rank delta; lazy edges never materialize.
match input.evaluation {
Evaluation::Eager => input.shape.depth,
Evaluation::Lazy => 0,
}
}
@@ -472,7 +485,13 @@ mod tests {
};
let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth as i8);
Facts {
sources: node.inputs.iter().enumerate().filter(|(_, input)| input.subject).map(|(index, _)| index).collect(),
sources: node
.inputs
.iter()
.enumerate()
.filter(|(index, input)| input.subject && materialized_levels(node, *index) == 0)
.map(|(index, _)| index)
.collect(),
carried,
writes: markers(node.output.shape.attrs.iter().map(|attr| &attr.marker)),
removes: markers(node.output.removes.iter().map(|attr| &attr.marker)),