Write an attribute under a name the graph supplies

`Named<X>` in parameter position declares where a placeholder's name is
wired, and the macro gives that input constant text; `Attr<Named<X, V>>`
in the return writes under it. The kernel is handed the bare placeholder,
since the name is spent resolving the layout and a folded offset is all
the write needs, so the hot path matches a marker node's exactly.

A name-generic write names its value type through the wired generic,
which only an implementations row resolves, so those nodes emit their
layout meta per row rather than sharing one across rows.

`write_attribute` is the catalog's set half, restoring the identifier
master's documents carry with its input positions. Its name input is a
constant, so those documents resolve without migration, and the reset
marker that stood in for the missing node retires with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dennis Kobert
2026-09-09 09:26:17 +00:00
parent 348e9022a6
commit 9b92edc0e9
10 changed files with 363 additions and 34 deletions

View File

@@ -2013,6 +2013,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let value_args = regular_fields.iter().skip(if skips_carrier { 0 } else { 1 }).map(|field| {
let name = &field.pat_ident.ident;
match &field.ty {
// A name input's text is spent when the graph compiles, so the
// kernel takes the bare placeholder, not the wired string.
ParsedFieldType::Regular(RegularParsedField { name_source: Some(_), .. }) => quote!(::core::default::Default::default()),
// A lend param binds an owned input; the kernel borrows the
// evaluated value.
ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) => quote!(&#name),
@@ -2517,12 +2520,28 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
self.__layout = __resolved.layout;
})
} else if record_io {
let write_installs = write_markers.iter().enumerate().map(|(index, marker)| {
let slot = format_ident!("__write_{index}");
quote! {
self.#slot = __resolved.layout.offset_of(<#marker as #core_types::attribute::Attribute>::NAME, 0).expect("a written attribute is always part of the wired layout");
}
});
// A name-generic write has no marker name to look up: the compiler
// folded its name out of the graph, so the offset resolves through the
// name the resolved layout carries.
let mut folded = 0usize;
let write_installs: Vec<TokenStream2> = write_markers
.iter()
.enumerate()
.map(|(index, marker)| {
let slot = format_ident!("__write_{index}");
let name = match crate::parsing::named_marker(marker).is_some() {
true => {
let position = folded;
folded += 1;
quote!(__resolved.named_writes[#position])
}
false => quote!(<#marker as #core_types::attribute::Attribute>::NAME),
};
quote! {
self.#slot = __resolved.layout.offset_of(#name, 0).expect("a written attribute is always part of the wired layout");
}
})
.collect();
let plan = (!skips_carrier || gather_carrier).then(|| quote!(self.__plan = __resolved.plan;));
Some(quote! {
#(#write_installs)*
@@ -2565,7 +2584,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}
None => quote!(#core_types::record::ElementSpec::Carried),
};
let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types);
let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types, &[]);
// A flipped shader node's struct and impl are std-gated; its layout meta must be too.
let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes);
quote! {
@@ -2581,8 +2600,11 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let record_wiring = record_io.then(|| {
let layout_fn = format_ident!("{}_layout", fn_name);
// A name-generic write is absent here: this free layout fn derives a
// layout without a graph, and only the graph carries the name.
let write_descs: Vec<TokenStream2> = write_markers
.iter()
.filter(|marker| crate::parsing::named_marker(marker).is_none())
.map(|marker| quote!(#core_types::record::FieldWrite::of::<#marker>(0)))
.collect();
let remove_pairs: Vec<TokenStream2> = removes
@@ -2619,7 +2641,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}
None => quote!(#core_types::record::ElementSpec::Carried),
};
let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types);
let layout_meta = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types, &[]);
let layout_meta_def = quote! {
#vis fn #layout_meta_fn() -> #core_types::record::LayoutMeta {
#layout_meta