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
+3 -3
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()), (Some(crate::codegen::ir::NodeKind::Routing), crate::codegen::ir::Element::Generic(ident)) => Some(ident.clone()),
_ => None, _ => 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; let record_skips_carrier = record_io && !carrier_present;
// Record nodes with a `_: ()` primary input have no carrier edge; the unit let struct_regular_fields: Vec<_> = regular_fields.iter().skip(record_unit_carrier as usize).copied().collect();
// 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_field_names: Vec<_> = struct_regular_fields.iter().map(|f| &f.pat_ident.ident).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 // Extract function generics used by data fields
+1 -1
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) { let (prelude, new_layout_args, layout_meta) = match ir::node_kind(&node) {
ir::NodeKind::RecordIo => { 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); let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
(quote!(), quote!(#carrier_arg #(#value_layout_args)*), quote!(Some(self::#layout_meta_fn()))) (quote!(), quote!(#carrier_arg #(#value_layout_args)*), quote!(Some(self::#layout_meta_fn())))
} }
+28 -9
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 /// 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. /// 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 { 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)| { let reads = node.inputs.iter().enumerate().filter_map(|(index, input)| {
(matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()).then(|| { (matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()).then(|| {
let descs = field_writes(&input.shape.attrs, core_types); 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 { 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); // A materialized subject contributes no base layout, so the delta is
node.output.shape.depth as i8 - subject_depth // 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. /// 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. /// into a `List` before the kernel.
pub(crate) fn materialized_levels(node: &Node, index: usize) -> u8 { pub(crate) fn materialized_levels(node: &Node, index: usize) -> u8 {
let input = &node.inputs[index]; let input = &node.inputs[index];
// A ranked subject folds the levels the output collapses; a ranked // An eager input's declared `IList` nesting IS its materialization count,
// non-subject input is consumed whole. // independent of the rank delta; lazy edges never materialize.
match input.subject { match input.evaluation {
true => input.shape.depth.saturating_sub(node.output.shape.depth), Evaluation::Eager => input.shape.depth,
false => 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); let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth as i8);
Facts { 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, carried,
writes: markers(node.output.shape.attrs.iter().map(|attr| &attr.marker)), writes: markers(node.output.shape.attrs.iter().map(|attr| &attr.marker)),
removes: markers(node.output.removes.iter().map(|attr| &attr.marker)), removes: markers(node.output.removes.iter().map(|attr| &attr.marker)),