mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Resolve a named read's offset when the graph compiles
The fold already holds the name and the read input's finished layout, so the offset falls out there rather than at construction: `RecordLayout` carries the resolved numbers and `set_layout` copies them into the read slots. Constructors are untouched, and census-marker reads keep their current installation. A read meets the value type the name was written at, so a disagreement between a read here and a write upstream is the same graph error as two writes disagreeing; the one-name-one-type check now spans reads and writes together. An absent attribute stays absent and the read serves the forced default rather than reporting it. `read_attribute` is the catalog's get half, typed and never `Option` at the kernel boundary, with the name declared exactly as the write side declares it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -984,8 +984,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// more: the name is spent resolving the layout when the graph compiles, so
|
||||
// it reaches neither the kernel's parameters nor its call. The wire input
|
||||
// stays, since the fold reads the constant off it.
|
||||
let kernel_omits =
|
||||
|field: &ParsedField| injected_name(&field.pat_ident.ident) || matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { name_source: Some(_), .. }));
|
||||
let kernel_omits = |field: &ParsedField| injected_name(&field.pat_ident.ident) || matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { name_source: Some(_), .. }));
|
||||
let where_predicates: Vec<TokenStream2> = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect();
|
||||
|
||||
let NodeFields {
|
||||
@@ -1731,7 +1730,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// kernel gets a fresh one; reference-valued writes name their real
|
||||
// lifetime explicitly and pass through untouched. An async source's value
|
||||
// outlives the evaluation, so its writes are `'static` instead.
|
||||
let attr_injected = record_io.then(|| inject_attr_lifetimes(&parsed.output_type, if async_source { "'static" } else { "'__attr" })).flatten();
|
||||
let attr_injected = record_io
|
||||
.then(|| inject_attr_lifetimes(&parsed.output_type, if async_source { "'static" } else { "'__attr" }))
|
||||
.flatten();
|
||||
let attr_lifetime = (attr_injected.is_some() && !async_source).then(|| quote!('__attr,));
|
||||
let lane_injected = gather_carrier
|
||||
.then(|| crate::codegen::classify::inject_lane_lifetime(attr_injected.as_ref().unwrap_or(&parsed.output_type)))
|
||||
@@ -2545,9 +2546,24 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// A name-generic read's offset was resolved against the input it reads,
|
||||
// which only the compiler sees, so installing it is a copy.
|
||||
let mut folded_read = 0usize;
|
||||
let read_installs: Vec<TokenStream2> = flat_reads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, read))| crate::parsing::named_marker(&read.marker).is_some())
|
||||
.map(|(slot, _)| {
|
||||
let slot = format_ident!("__read_{slot}");
|
||||
let position = folded_read;
|
||||
folded_read += 1;
|
||||
quote!(self.#slot = __resolved.named_reads[#position];)
|
||||
})
|
||||
.collect();
|
||||
let plan = (!skips_carrier || gather_carrier).then(|| quote!(self.__plan = __resolved.plan;));
|
||||
Some(quote! {
|
||||
#(#write_installs)*
|
||||
#(#read_installs)*
|
||||
self.__frame_bytes = __resolved.frame_bytes;
|
||||
self.__lane_invariant = __resolved.lane_invariant;
|
||||
#plan
|
||||
@@ -2671,6 +2687,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let read_inits = flat_reads.iter().enumerate().map(|(slot, (owner, read))| {
|
||||
let marker = &read.marker;
|
||||
let slot = format_ident!("__read_{slot}");
|
||||
// A name-generic read has no marker name to look up here; the
|
||||
// compiler resolved its offset against the input's own layout, so
|
||||
// `set_layout` installs the number.
|
||||
if crate::parsing::named_marker(marker).is_some() {
|
||||
return quote!(let #slot = ::core::option::Option::None;);
|
||||
}
|
||||
let source = match !skips_carrier && *owner == 0 {
|
||||
true => quote!(__carrier_layout),
|
||||
false => format_ident!("__in_{owner}").to_token_stream(),
|
||||
|
||||
@@ -659,10 +659,7 @@ mod tests {
|
||||
.to_string();
|
||||
assert!(generated.contains("data : :: core_types :: list :: List < T >"), "the kernel takes the owned legacy list: {generated}");
|
||||
assert!(generated.contains("run_to_owned_list"), "the prologue snapshots the materialized level: {generated}");
|
||||
assert!(
|
||||
generated.contains("record :: materialize_batch"),
|
||||
"the level still materializes in the prologue: {generated}"
|
||||
);
|
||||
assert!(generated.contains("record :: materialize_batch"), "the level still materializes in the prologue: {generated}");
|
||||
for element in ["Vector", "Raster < CPU >", "Graphic"] {
|
||||
let row = format!("record_source_type :: < {element} > ()");
|
||||
assert!(generated.contains(&row), "the row carries the leveled element {element}: {generated}");
|
||||
|
||||
@@ -282,6 +282,7 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
||||
});
|
||||
let writes = field_writes(&node.output.shape.attrs, core_types);
|
||||
let named_writes = named_field_writes(node, core_types, assignments);
|
||||
let named_reads = named_field_reads(node, core_types, assignments);
|
||||
let removes = node.output.removes.iter().map(|attr| {
|
||||
let marker = &attr.marker;
|
||||
let level = attr.level;
|
||||
@@ -300,6 +301,8 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
||||
writes: ::std::vec![#(#writes),*],
|
||||
named_writes: ::std::vec![#(#named_writes),*],
|
||||
folded_names: ::std::vec![],
|
||||
named_reads: ::std::vec![#(#named_reads),*],
|
||||
folded_read_names: ::std::vec![],
|
||||
removes: ::std::vec![#(#removes),*],
|
||||
level_delta: #level_delta,
|
||||
folded: #folded,
|
||||
@@ -396,6 +399,28 @@ fn named_field_writes(node: &Node, core_types: &TokenStream2, assignments: &[(Id
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Emits one `NamedRead` per name-generic read, pairing the template minted
|
||||
/// from the concrete value type with the input read and the input its
|
||||
/// placeholder's name sits at.
|
||||
fn named_field_reads(node: &Node, core_types: &TokenStream2, assignments: &[(Ident, Type)]) -> Vec<TokenStream2> {
|
||||
node.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(input, source)| source.shape.attrs.iter().map(move |attr| (input, attr)))
|
||||
.filter_map(|(input, attr)| {
|
||||
let (placeholder, value) = crate::parsing::named_marker(&attr.marker)?;
|
||||
let value = qualify_projection(node, &crate::codegen::classify::substitute_ident_types(&value, assignments), assignments);
|
||||
if node.generics.iter().any(|generic| mentions_ident(&value, &generic.ident)) {
|
||||
return None;
|
||||
}
|
||||
let name = name_input(node, &placeholder)? as u8;
|
||||
let input = input as u8;
|
||||
let level = attr.level;
|
||||
Some(quote!(#core_types::record::NamedRead::of::<#placeholder, #value>(#input, #name, #level)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Rewrites `V::Assoc` into `<Row as Bound>::Assoc` once the row assigns `V`.
|
||||
/// A value type reached through an associated type needs the generic's own
|
||||
/// bound to name the projection, which only the signature carries.
|
||||
@@ -444,9 +469,7 @@ fn mentions_ident(ty: &Type, ident: &Ident) -> bool {
|
||||
/// The input position carrying `placeholder`'s name, which is the parameter
|
||||
/// declared at that placeholder.
|
||||
pub(crate) fn name_input(node: &Node, placeholder: &Type) -> Option<usize> {
|
||||
node.inputs
|
||||
.iter()
|
||||
.position(|input| input.name_source.as_ref().is_some_and(|declared| declared == placeholder))
|
||||
node.inputs.iter().position(|input| input.name_source.as_ref().is_some_and(|declared| declared == placeholder))
|
||||
}
|
||||
|
||||
fn level_delta(node: &Node) -> i8 {
|
||||
@@ -852,11 +875,7 @@ mod tests {
|
||||
let assignments = vec![(syn::parse_quote!(V), syn::parse_quote!(f64))];
|
||||
let emitted = named_field_writes(&node, "e!(gcore), &assignments);
|
||||
assert_eq!(emitted.len(), 1, "the row carries the named write, got {emitted:?}");
|
||||
assert!(
|
||||
emitted[0].to_string().contains("WireValue"),
|
||||
"the projection is qualified by the generic's bound, got {}",
|
||||
emitted[0]
|
||||
);
|
||||
assert!(emitted[0].to_string().contains("WireValue"), "the projection is qualified by the generic's bound, got {}", emitted[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1682,7 +1682,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
name_source: None,
|
||||
name_source: None,
|
||||
lend: None,
|
||||
list_levels: 0,
|
||||
ty: parse_quote!(DVec2),
|
||||
|
||||
@@ -127,7 +127,10 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
||||
_ => None,
|
||||
};
|
||||
if async_source && token.is_some() {
|
||||
emit_error!(carrier.pat_ident.span(), "an async source's element crosses the future boundary as a value; a passthrough generic element has none");
|
||||
emit_error!(
|
||||
carrier.pat_ident.span(),
|
||||
"an async source's element crosses the future boundary as a value; a passthrough generic element has none"
|
||||
);
|
||||
}
|
||||
let element = writes.as_ref().map(|writes| &writes.element).unwrap_or(&value);
|
||||
match &token {
|
||||
@@ -164,7 +167,10 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
||||
let mut seen_writes: Vec<String> = Vec::new();
|
||||
for write in &writes.markers {
|
||||
if write.owned && !async_source {
|
||||
emit_error!(parsed.output_type.span(), "an owned attribute crossing belongs to an async source; a synchronous write parks its value in the kernel");
|
||||
emit_error!(
|
||||
parsed.output_type.span(),
|
||||
"an owned attribute crossing belongs to an async source; a synchronous write parks its value in the kernel"
|
||||
);
|
||||
}
|
||||
let written = write.marker.to_token_stream().to_string();
|
||||
if seen_writes.contains(&written) {
|
||||
|
||||
Reference in New Issue
Block a user