mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 15:48:12 +08:00
Model the eval body as an EvalStep sequence with a structural stack discipline
This commit is contained in:
@@ -1239,7 +1239,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
.collect::<Vec<(usize, &AttributeRead)>>()
|
.collect::<Vec<(usize, &AttributeRead)>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
let eval_values = regular_fields.iter().enumerate().map(|(index, field)| {
|
let bind_body = |index: usize, field: &ParsedField| {
|
||||||
let name = &field.pat_ident.ident;
|
let name = &field.pat_ident.ident;
|
||||||
let regular_ty = || match &field.ty {
|
let regular_ty = || match &field.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
|
||||||
@@ -1261,10 +1261,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let ty = regular_ty();
|
let ty = regular_ty();
|
||||||
let slot = format_ident!("__in_{index}");
|
let slot = format_ident!("__in_{index}");
|
||||||
let rec_local = format_ident!("__rec_{index}");
|
let rec_local = format_ident!("__rec_{index}");
|
||||||
let mark = format_ident!("__mark_{index}");
|
|
||||||
let bindings: Vec<TokenStream2> = reads_of(index).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(#rec_local))).collect();
|
let bindings: Vec<TokenStream2> = reads_of(index).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(#rec_local))).collect();
|
||||||
quote! {
|
quote! {
|
||||||
let #mark = #core_types::record::stack::sp();
|
|
||||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(interrupt) => return interrupt.into(),
|
Err(interrupt) => return interrupt.into(),
|
||||||
@@ -1272,8 +1270,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let #rec_local = self.#slot.rec(&#name);
|
let #rec_local = self.#slot.rec(&#name);
|
||||||
#(#bindings)*
|
#(#bindings)*
|
||||||
let #name: #ty = unsafe { #core_types::record::read_element(#rec_local) };
|
let #name: #ty = unsafe { #core_types::record::read_element(#rec_local) };
|
||||||
// SAFETY: the element and declared attribute reads copied out by value, so no record above the mark is live.
|
|
||||||
unsafe { #core_types::record::stack::rewind(#mark) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The lend input's frame survives on the record stack until this
|
// The lend input's frame survives on the record stack until this
|
||||||
@@ -1291,21 +1287,17 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A flip value or a routing non-source value rides a record edge; the
|
// A flip value or a routing non-source value rides a record edge; the
|
||||||
// element copies out right after its eval, before any sibling eval can
|
// element copies out into `name`. The mark/rewind that reclaims the
|
||||||
// reuse the record stack.
|
// record's frame is applied by the step lowering (see `reads_out`).
|
||||||
InputRole::RecordValue => {
|
InputRole::RecordValue => {
|
||||||
let ty = regular_ty();
|
let ty = regular_ty();
|
||||||
let slot = format_ident!("__in_{index}");
|
let slot = format_ident!("__in_{index}");
|
||||||
let mark = format_ident!("__mark_{index}");
|
|
||||||
quote! {
|
quote! {
|
||||||
let #mark = #core_types::record::stack::sp();
|
|
||||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(interrupt) => return interrupt.into(),
|
Err(interrupt) => return interrupt.into(),
|
||||||
};
|
};
|
||||||
let #name: #ty = unsafe { #core_types::record::read_element(self.#slot.rec(&#name)) };
|
let #name: #ty = unsafe { #core_types::record::read_element(self.#slot.rec(&#name)) };
|
||||||
// SAFETY: the element copied out by value, so no record above the mark is live.
|
|
||||||
unsafe { #core_types::record::stack::rewind(#mark) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputRole::PlainValue => quote! {
|
InputRole::PlainValue => quote! {
|
||||||
@@ -1356,7 +1348,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
let clamp_tokens = |field: &ParsedField| {
|
let clamp_tokens = |field: &ParsedField| {
|
||||||
let ParsedFieldType::Regular(RegularParsedField { number_hard_min, number_hard_max, .. }) = &field.ty else {
|
let ParsedFieldType::Regular(RegularParsedField { number_hard_min, number_hard_max, .. }) = &field.ty else {
|
||||||
@@ -1372,12 +1364,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}
|
}
|
||||||
(!tokens.is_empty()).then_some(tokens)
|
(!tokens.is_empty()).then_some(tokens)
|
||||||
};
|
};
|
||||||
// A carrier primary binds in the flip tail; its clamp runs there too.
|
|
||||||
let clamps = regular_fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(index, _)| !(carrier_flip && *index == 0))
|
|
||||||
.filter_map(|(_, field)| clamp_tokens(field));
|
|
||||||
|
|
||||||
let call_args = regular_fields.iter().enumerate().filter(|(_, field)| !injected_name(&field.pat_ident.ident)).map(|(index, field)| {
|
let call_args = regular_fields.iter().enumerate().filter(|(_, field)| !injected_name(&field.pat_ident.ident)).map(|(index, field)| {
|
||||||
let name = &field.pat_ident.ident;
|
let name = &field.pat_ident.ident;
|
||||||
@@ -2005,6 +1991,41 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The eval body as an ordered step sequence: bind each input, clamp, then the
|
||||||
|
// tail. The record-stack mark/rewind of a read-out bind is applied here from
|
||||||
|
// the role, so the discipline is structural rather than per-arm.
|
||||||
|
let eval_steps: Vec<EvalStep> = regular_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, field)| EvalStep::Bind(index, field))
|
||||||
|
.chain(
|
||||||
|
regular_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(index, _)| !(carrier_flip && *index == 0))
|
||||||
|
.map(|(_, field)| EvalStep::Clamp(field)),
|
||||||
|
)
|
||||||
|
.chain(std::iter::once(EvalStep::Tail))
|
||||||
|
.collect();
|
||||||
|
let eval_body = eval_steps.iter().map(|step| match step {
|
||||||
|
EvalStep::Bind(index, field) => {
|
||||||
|
let body = bind_body(*index, field);
|
||||||
|
match roles[*index].reads_out() {
|
||||||
|
false => body,
|
||||||
|
true => {
|
||||||
|
let mark = format_ident!("__mark_{index}");
|
||||||
|
quote! {
|
||||||
|
let #mark = #core_types::record::stack::sp();
|
||||||
|
#body
|
||||||
|
unsafe { #core_types::record::stack::rewind(#mark) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EvalStep::Clamp(field) => clamp_tokens(field).unwrap_or_default(),
|
||||||
|
EvalStep::Tail => eval_tail.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
let top_level = quote! {
|
let top_level = quote! {
|
||||||
#cfg
|
#cfg
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
@@ -2023,9 +2044,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<Self::Output> {
|
fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<Self::Output> {
|
||||||
let __cell = #cell_constructor;
|
let __cell = #cell_constructor;
|
||||||
#reclaim_guard
|
#reclaim_guard
|
||||||
#(#eval_values)*
|
#(#eval_body)*
|
||||||
#(#clamps)*
|
|
||||||
#eval_tail
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#extent_impl
|
#extent_impl
|
||||||
@@ -2210,6 +2229,25 @@ pub(crate) enum InputRole {
|
|||||||
Lazy,
|
Lazy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InputRole {
|
||||||
|
/// A role that copies an element out of a record edge, so its record frame
|
||||||
|
/// must be reclaimed after the read. The step lowering wraps such a bind in
|
||||||
|
/// `mark`/`rewind`, making the stack discipline structural rather than
|
||||||
|
/// hand-threaded through each read-out arm.
|
||||||
|
fn reads_out(self) -> bool {
|
||||||
|
matches!(self, InputRole::ReadingSecondary | InputRole::RecordValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One statement group of a node's `eval` body, lowered in order: the input
|
||||||
|
/// binds first (one per input), then the numeric clamps, then the tail that
|
||||||
|
/// assembles the output record and closes the dialect.
|
||||||
|
enum EvalStep<'a> {
|
||||||
|
Bind(usize, &'a ParsedField),
|
||||||
|
Clamp(&'a ParsedField),
|
||||||
|
Tail,
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the signature declares record-tier attribute io: value-input reads
|
/// Whether the signature declares record-tier attribute io: value-input reads
|
||||||
/// or return-tuple writes. Reads on lazy inputs belong to the record lowering
|
/// or return-tuple writes. Reads on lazy inputs belong to the record lowering
|
||||||
/// of the flip class instead.
|
/// of the flip class instead.
|
||||||
|
|||||||
Reference in New Issue
Block a user