From f195e363a5342b5aa6fcc068c90e57bc378fe38c Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 13 Aug 2026 16:11:58 +0000 Subject: [PATCH] Emit layout metadata from the intent IR --- node-graph/node-macro/src/codegen.rs | 25 +--------- node-graph/node-macro/src/codegen/entries.rs | 32 ++++--------- node-graph/node-macro/src/codegen/ir.rs | 48 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 45 deletions(-) diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index fcbf0b2ce1..5dbc1dda24 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1743,35 +1743,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn }, }; let layout_meta_fn = format_ident!("{}_layout_meta", fn_name); - let carrier_present = !shape.skips_carrier(); let element_spec = match &shape.element_write { Some(ty) => quote!(#core_types::record::ElementSpec::Concrete(#core_types::record::element_write::<#ty>())), None => quote!(#core_types::record::ElementSpec::Carried), }; - let sources = if carrier_present { quote!(::std::vec![0u8]) } else { quote!(::std::vec![]) }; - let reads_meta: Vec = regular_fields - .iter() - .enumerate() - .filter(|(_, field)| !field.attribute_reads.is_empty()) - .map(|(index, field)| { - let descs = field.attribute_reads.iter().map(|read| { - let marker = &read.marker; - quote!(#core_types::record::FieldWrite::of::<#marker>(0)) - }); - let index = index as u8; - quote!(#core_types::record::InputReads { input: #index, reads: ::std::vec![#(#descs),*] }) - }) - .collect(); + let layout_meta = crate::codegen::ir::layout_meta_tokens(&crate::codegen::ir::build(parsed), element_spec, core_types); let layout_meta_def = quote! { #vis fn #layout_meta_fn() -> #core_types::record::LayoutMeta { - #core_types::record::LayoutMeta { - sources: #sources, - reads: ::std::vec![#(#reads_meta),*], - element: #element_spec, - writes: ::std::vec![#(#write_descs),*], - removes: ::std::vec![#(#remove_pairs),*], - level_delta: 0, - } + #layout_meta } }; let reading_secondaries = reading_secondary_indices(®ular_fields, shape); diff --git a/node-graph/node-macro/src/codegen/entries.rs b/node-graph/node-macro/src/codegen/entries.rs index 703622fcf7..7fe3230f6b 100644 --- a/node-graph/node-macro/src/codegen/entries.rs +++ b/node-graph/node-macro/src/codegen/entries.rs @@ -54,8 +54,8 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field let arity = regular_fields.len(); let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect(); let node_underscores: Vec = regular_fields.iter().map(|_| quote!(_)).collect(); - let carrier_present = flip_carrier(parsed); - let sources = if carrier_present { quote!(::std::vec![0u8]) } else { quote!(::std::vec![]) }; + let node = crate::codegen::ir::build(parsed); + let core_types = quote!(gcore); // Shorthand associated types in the output only resolve against the // generics' bounds, so rows name the output through a bounded alias. Only @@ -142,16 +142,11 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field let layout = format_ident!("__layout_{index}"); quote!(&#layout,) }); + let element_spec = quote!(gcore::record::ElementSpec::Concrete(gcore::record::element_write::<#row_output>())); + let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, &core_types); Some(quote! { gcore::registry::RegistryEntry { - layout_meta: Some(gcore::record::LayoutMeta { - sources: #sources, - reads: ::std::vec::Vec::new(), - element: gcore::record::ElementSpec::Concrete(gcore::record::element_write::<#row_output>()), - writes: ::std::vec::Vec::new(), - removes: ::std::vec::Vec::new(), - level_delta: 0, - }), + layout_meta: Some(#layout_meta), io: gcore::registry::NodeIOTypes::new( gcore::concrete!(gcore::context::ContextImpl<'static>), gcore::registry::record_type::<#row_output>(), @@ -325,18 +320,11 @@ fn single_row_entries(parsed: &ParsedNodeFn, class: &Class, struct_name: &Ident, quote!(&#layout,) }); - let carried_meta = |sources: &[usize]| { - let sources = sources.iter().map(|index| *index as u8); - quote! { - Some(gcore::record::LayoutMeta { - sources: ::std::vec![#(#sources),*], - reads: ::std::vec::Vec::new(), - element: gcore::record::ElementSpec::Carried, - writes: ::std::vec::Vec::new(), - removes: ::std::vec::Vec::new(), - level_delta: 0, - }) - } + let node = crate::codegen::ir::build(parsed); + let core_types = quote!(gcore); + let carried_meta = |_sources: &[usize]| { + let meta = crate::codegen::ir::layout_meta_tokens(&node, quote!(gcore::record::ElementSpec::Carried), &core_types); + quote!(Some(#meta)) }; let (io_output, wrap, prelude, new_layout_args, layout_meta) = match class { diff --git a/node-graph/node-macro/src/codegen/ir.rs b/node-graph/node-macro/src/codegen/ir.rs index e37960b550..d79172bea3 100644 --- a/node-graph/node-macro/src/codegen/ir.rs +++ b/node-graph/node-macro/src/codegen/ir.rs @@ -6,6 +6,8 @@ use crate::codegen::classify::{ }; use crate::codegen::entries::implementation_rows; use crate::parsing::{AttributeRead, NodeParsedField, ParsedField, ParsedFieldType, ParsedNodeFn, RecordWrites, RegularParsedField, record_writes}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; use syn::{GenericArgument, GenericParam, Ident, PathArguments, Type, TypeParamBound}; pub(crate) fn build(parsed: &ParsedNodeFn) -> Node { @@ -165,6 +167,52 @@ fn ilist_inner(ty: &Type) -> Option { }) } +/// 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); + let reads = node.inputs.iter().enumerate().filter_map(|(index, input)| { + (!input.shape.attrs.is_empty()).then(|| { + let descs = field_writes(&input.shape.attrs, core_types); + let index = index as u8; + quote!(#core_types::record::InputReads { input: #index, reads: ::std::vec![#(#descs),*] }) + }) + }); + let writes = field_writes(&node.output.shape.attrs, core_types); + let removes = node.output.removes.iter().map(|attr| { + let marker = &attr.marker; + let level = attr.level; + quote!((<#marker as #core_types::attribute::Attribute>::NAME, #level)) + }); + let level_delta = level_delta(node); + quote! { + #core_types::record::LayoutMeta { + sources: ::std::vec![#(#sources),*], + reads: ::std::vec![#(#reads),*], + element: #element_spec, + writes: ::std::vec![#(#writes),*], + removes: ::std::vec![#(#removes),*], + level_delta: #level_delta, + } + } +} + +fn field_writes(attrs: &[LevelAttr], core_types: &TokenStream2) -> Vec { + attrs + .iter() + .map(|attr| { + let marker = &attr.marker; + let level = attr.level; + quote!(#core_types::record::FieldWrite::of::<#marker>(#level)) + }) + .collect() +} + +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 +} + pub(crate) struct Node { pub(crate) kernel: Kernel, pub(crate) generics: Vec,