mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 18:58:12 +08:00
Derive input bindings from the intent IR
This commit is contained in:
@@ -16,6 +16,7 @@ mod ir;
|
|||||||
mod metadata;
|
mod metadata;
|
||||||
pub(crate) use classify::*;
|
pub(crate) use classify::*;
|
||||||
use entries::entries_tokens;
|
use entries::entries_tokens;
|
||||||
|
use ir::{LazyBinding, ValueBinding};
|
||||||
use metadata::generate_node_input_references;
|
use metadata::generate_node_input_references;
|
||||||
|
|
||||||
static NODE_ID: AtomicU64 = AtomicU64::new(0);
|
static NODE_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
@@ -886,39 +887,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
|
|
||||||
let routing_source = |ty: &Type| matches!((&routing, ty), (Some(routing), Type::Path(path)) if path.path.get_ident() == Some(&routing.generic));
|
let routing_source = |ty: &Type| matches!((&routing, ty), (Some(routing), Type::Path(path)) if path.path.get_ident() == Some(&routing.generic));
|
||||||
|
|
||||||
let field_role = |index: usize, field: &ParsedField| match &field.ty {
|
let node = crate::codegen::ir::build(parsed);
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => {
|
|
||||||
if record.is_some() && !skips_carrier && index == 0 {
|
|
||||||
InputRole::RecordCarrier
|
|
||||||
} else if carrier_flip && index == 0 {
|
|
||||||
InputRole::FlipCarrier
|
|
||||||
} else if flip && lend.is_some() {
|
|
||||||
InputRole::LendBorrow
|
|
||||||
} else if record.is_some() && !field.attribute_reads.is_empty() {
|
|
||||||
InputRole::ReadingSecondary
|
|
||||||
} else if flip || (routing.is_some() && !routing_source(ty)) {
|
|
||||||
InputRole::RecordValue
|
|
||||||
} else {
|
|
||||||
InputRole::PlainValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
|
||||||
if derive_routing && routing_source(output_type) {
|
|
||||||
InputRole::DeriveRoutingSource
|
|
||||||
} else if flip && raw_lazy {
|
|
||||||
InputRole::FlipRawLazyEdge
|
|
||||||
} else if flip {
|
|
||||||
InputRole::FlipLazy
|
|
||||||
} else if opaque && raw_lazy && is_record_value(output_type) {
|
|
||||||
InputRole::OpaqueRecordEdge
|
|
||||||
} else if raw_lazy {
|
|
||||||
InputRole::RawLazy
|
|
||||||
} else {
|
|
||||||
InputRole::Lazy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let roles: Vec<InputRole> = regular_fields.iter().enumerate().map(|(index, field)| field_role(index, field)).collect();
|
|
||||||
|
|
||||||
let lazy_read_out = |field: &ParsedField, output_type: &Type| {
|
let lazy_read_out = |field: &ParsedField, output_type: &Type| {
|
||||||
let attr_tys = field.attribute_reads.iter().map(|read| {
|
let attr_tys = field.attribute_reads.iter().map(|read| {
|
||||||
@@ -950,26 +919,25 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
||||||
let source_generic = format_ident!("__Source{index}");
|
let source_generic = format_ident!("__Source{index}");
|
||||||
match roles[index] {
|
match (ir::lazy_binding(&node, index), raw_lazy) {
|
||||||
InputRole::DeriveRoutingSource => quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>),
|
(LazyBinding::DeriveRouting, _) => quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>),
|
||||||
InputRole::OpaqueRecordEdge => quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #source_generic>),
|
(LazyBinding::OpaqueRecord, _) => quote!(#pat: &#core_types::record::RecordEdgeInput<'_, #source_generic>),
|
||||||
InputRole::FlipRawLazyEdge => {
|
(LazyBinding::Element, true) => {
|
||||||
let out = lazy_read_out(field, output_type);
|
let out = lazy_read_out(field, output_type);
|
||||||
quote!(#pat: &#core_types::record::ElementEdge<'_, #out, #source_generic>)
|
quote!(#pat: &#core_types::record::ElementEdge<'_, #out, #source_generic>)
|
||||||
}
|
}
|
||||||
InputRole::FlipLazy => {
|
(LazyBinding::Element, false) => {
|
||||||
let out = lazy_read_out(field, output_type);
|
let out = lazy_read_out(field, output_type);
|
||||||
quote!(#pat: #core_types::record::ElementLazyInput<'_, #out, #source_generic>)
|
quote!(#pat: #core_types::record::ElementLazyInput<'_, #out, #source_generic>)
|
||||||
}
|
}
|
||||||
InputRole::RawLazy => {
|
(LazyBinding::Plain, true) => {
|
||||||
let bound = lazy_bound(output_type);
|
let bound = lazy_bound(output_type);
|
||||||
quote!(#pat: &impl #bound)
|
quote!(#pat: &impl #bound)
|
||||||
}
|
}
|
||||||
InputRole::Lazy => {
|
(LazyBinding::Plain, false) => {
|
||||||
let bound = lazy_bound(output_type);
|
let bound = lazy_bound(output_type);
|
||||||
quote!(#pat: #core_types::node::LazyInput<'_, impl #bound>)
|
quote!(#pat: #core_types::node::LazyInput<'_, impl #bound>)
|
||||||
}
|
}
|
||||||
_ => unreachable!("value role on a lazy input"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1078,111 +1046,103 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
|
|
||||||
let bind_body = |index: usize, field: &ParsedField| {
|
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 {
|
match &field.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => match ir::value_binding(&node, index) {
|
||||||
_ => unreachable!("value role on a lazy input"),
|
// A carrier primary evaluates beyond the node's own frame (in the
|
||||||
};
|
// record/flip tail), so it does not bind here.
|
||||||
let node_output = || match &field.ty {
|
ValueBinding::Carrier => quote!(),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type.clone(),
|
// A reading secondary input claims a record edge: the element and
|
||||||
_ => unreachable!("lazy role on a value input"),
|
// the declared reads copy out right after its eval, before any
|
||||||
};
|
// later sibling eval can reuse the record stack.
|
||||||
match roles[index] {
|
ValueBinding::ReadingSecondary => {
|
||||||
// A carrier primary evaluates beyond the node's own frame (in the
|
let slot = format_ident!("__in_{index}");
|
||||||
// record/flip tail), and a raw poll edge is threaded straight through,
|
let rec_local = format_ident!("__rec_{index}");
|
||||||
// so none bind here.
|
let bindings: Vec<TokenStream2> = reads_of(index).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(#rec_local))).collect();
|
||||||
InputRole::RecordCarrier | InputRole::FlipCarrier | InputRole::RawLazy => quote!(),
|
quote! {
|
||||||
// A reading secondary input claims a record edge: the element and
|
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||||
// the declared reads copy out right after its eval, before any
|
Ok(value) => value,
|
||||||
// later sibling eval can reuse the record stack.
|
Err(interrupt) => return interrupt.into(),
|
||||||
InputRole::ReadingSecondary => {
|
};
|
||||||
let ty = regular_ty();
|
let #rec_local = self.#slot.rec(&#name);
|
||||||
let slot = format_ident!("__in_{index}");
|
#(#bindings)*
|
||||||
let rec_local = format_ident!("__rec_{index}");
|
let #name: #ty = unsafe { #core_types::record::read_element(#rec_local) };
|
||||||
let bindings: Vec<TokenStream2> = reads_of(index).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(#rec_local))).collect();
|
}
|
||||||
quote! {
|
}
|
||||||
|
// The lend input's frame survives on the record stack until this
|
||||||
|
// node's frame is reclaimed, so the borrow stays valid in place.
|
||||||
|
ValueBinding::Lend => {
|
||||||
|
let slot = format_ident!("__in_{index}");
|
||||||
|
let record_local = format_ident!("__record_{index}");
|
||||||
|
quote! {
|
||||||
|
let #record_local = match __cell.eval_input(#index, &self.#name, __input) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(interrupt) => return interrupt.into(),
|
||||||
|
};
|
||||||
|
let #name = unsafe { #core_types::record::borrow_element::<#ty>(self.#slot.rec(&#record_local)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A flip value or a routing non-source value rides a record edge; the
|
||||||
|
// element copies out into `name`. The mark/rewind that reclaims the
|
||||||
|
// record's frame is applied by the step lowering (see `reads_out`).
|
||||||
|
ValueBinding::RecordElement => {
|
||||||
|
let slot = format_ident!("__in_{index}");
|
||||||
|
quote! {
|
||||||
|
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(interrupt) => return interrupt.into(),
|
||||||
|
};
|
||||||
|
let #name: #ty = unsafe { #core_types::record::read_element(self.#slot.rec(&#name)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueBinding::Plain => quote! {
|
||||||
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 #rec_local = self.#slot.rec(&#name);
|
},
|
||||||
#(#bindings)*
|
|
||||||
let #name: #ty = unsafe { #core_types::record::read_element(#rec_local) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The lend input's frame survives on the record stack until this
|
|
||||||
// node's frame is reclaimed, so the borrow stays valid in place.
|
|
||||||
InputRole::LendBorrow => {
|
|
||||||
let ty = regular_ty();
|
|
||||||
let slot = format_ident!("__in_{index}");
|
|
||||||
let record_local = format_ident!("__record_{index}");
|
|
||||||
quote! {
|
|
||||||
let #record_local = match __cell.eval_input(#index, &self.#name, __input) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(interrupt) => return interrupt.into(),
|
|
||||||
};
|
|
||||||
let #name = unsafe { #core_types::record::borrow_element::<#ty>(self.#slot.rec(&#record_local)) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A flip value or a routing non-source value rides a record edge; the
|
|
||||||
// element copies out into `name`. The mark/rewind that reclaims the
|
|
||||||
// record's frame is applied by the step lowering (see `reads_out`).
|
|
||||||
InputRole::RecordValue => {
|
|
||||||
let ty = regular_ty();
|
|
||||||
let slot = format_ident!("__in_{index}");
|
|
||||||
quote! {
|
|
||||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(interrupt) => return interrupt.into(),
|
|
||||||
};
|
|
||||||
let #name: #ty = unsafe { #core_types::record::read_element(self.#slot.rec(&#name)) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputRole::PlainValue => quote! {
|
|
||||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(interrupt) => return interrupt.into(),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
InputRole::DeriveRoutingSource => quote! {
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => match (ir::lazy_binding(&node, index), raw_lazy) {
|
||||||
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index);
|
// A raw poll edge is threaded straight through, so it does not bind here.
|
||||||
},
|
(LazyBinding::Plain, true) => quote!(),
|
||||||
InputRole::FlipRawLazyEdge => {
|
(LazyBinding::DeriveRouting, _) => quote! {
|
||||||
let output_type = node_output();
|
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index);
|
||||||
let slot = format_ident!("__in_{index}");
|
},
|
||||||
match field.attribute_reads.is_empty() {
|
(LazyBinding::Element, true) => {
|
||||||
true => quote! {
|
let slot = format_ident!("__in_{index}");
|
||||||
let #name = #core_types::record::ElementEdge::<#output_type, _>::new(&self.#name, &self.#slot);
|
match field.attribute_reads.is_empty() {
|
||||||
},
|
true => quote! {
|
||||||
false => {
|
let #name = #core_types::record::ElementEdge::<#output_type, _>::new(&self.#name, &self.#slot);
|
||||||
let arr = format_ident!("__reads_{index}");
|
},
|
||||||
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
false => {
|
||||||
quote! {
|
let arr = format_ident!("__reads_{index}");
|
||||||
let #name = #core_types::record::ElementEdge::with_reads(&self.#name, &self.#slot, &self.#arr, self::#read_fn);
|
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
||||||
|
quote! {
|
||||||
|
let #name = #core_types::record::ElementEdge::with_reads(&self.#name, &self.#slot, &self.#arr, self::#read_fn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
(LazyBinding::Element, false) => {
|
||||||
InputRole::FlipLazy => {
|
let slot = format_ident!("__in_{index}");
|
||||||
let output_type = node_output();
|
match field.attribute_reads.is_empty() {
|
||||||
let slot = format_ident!("__in_{index}");
|
true => quote! {
|
||||||
match field.attribute_reads.is_empty() {
|
let #name = #core_types::record::ElementLazyInput::<#output_type, _>::new(&self.#name, &__cell, #index, &self.#slot);
|
||||||
true => quote! {
|
},
|
||||||
let #name = #core_types::record::ElementLazyInput::<#output_type, _>::new(&self.#name, &__cell, #index, &self.#slot);
|
false => {
|
||||||
},
|
let arr = format_ident!("__reads_{index}");
|
||||||
false => {
|
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
||||||
let arr = format_ident!("__reads_{index}");
|
quote! {
|
||||||
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
let #name = #core_types::record::ElementLazyInput::with_reads(&self.#name, &__cell, #index, &self.#slot, &self.#arr, self::#read_fn);
|
||||||
quote! {
|
}
|
||||||
let #name = #core_types::record::ElementLazyInput::with_reads(&self.#name, &__cell, #index, &self.#slot, &self.#arr, self::#read_fn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
(LazyBinding::OpaqueRecord, _) => quote! {
|
||||||
InputRole::OpaqueRecordEdge => quote! {
|
let #name = #core_types::record::RecordEdgeInput::new(&self.#name, &self.__layout);
|
||||||
let #name = #core_types::record::RecordEdgeInput::new(&self.#name, &self.__layout);
|
},
|
||||||
},
|
(LazyBinding::Plain, false) => quote! {
|
||||||
InputRole::Lazy => quote! {
|
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
||||||
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1209,9 +1169,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// evaluated value.
|
// evaluated value.
|
||||||
ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) if !flip => quote!(&#name),
|
ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }) if !flip => quote!(&#name),
|
||||||
ParsedFieldType::Regular(_) => quote!(#name),
|
ParsedFieldType::Regular(_) => quote!(#name),
|
||||||
ParsedFieldType::Node(_) => match roles[index] {
|
ParsedFieldType::Node(_) => match (ir::lazy_binding(&node, index), raw_lazy) {
|
||||||
InputRole::FlipRawLazyEdge | InputRole::OpaqueRecordEdge => quote!(&#name),
|
(LazyBinding::Element, true) | (LazyBinding::OpaqueRecord, _) => quote!(&#name),
|
||||||
InputRole::RawLazy => quote!(&self.#name),
|
(LazyBinding::Plain, true) => quote!(&self.#name),
|
||||||
_ => quote!(#name),
|
_ => quote!(#name),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1869,7 +1829,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let eval_body = eval_steps.iter().map(|step| match step {
|
let eval_body = eval_steps.iter().map(|step| match step {
|
||||||
EvalStep::Bind(index, field) => {
|
EvalStep::Bind(index, field) => {
|
||||||
let body = bind_body(*index, field);
|
let body = bind_body(*index, field);
|
||||||
match roles[*index].reads_out() {
|
let reads_out = matches!(&field.ty, ParsedFieldType::Regular(_)) && ir::value_binding(&node, *index).reads_out();
|
||||||
|
match reads_out {
|
||||||
false => body,
|
false => body,
|
||||||
true => {
|
true => {
|
||||||
let mark = format_ident!("__mark_{index}");
|
let mark = format_ident!("__mark_{index}");
|
||||||
|
|||||||
@@ -100,36 +100,6 @@ pub(crate) fn analyze(parsed: &ParsedNodeFn) -> Option<NodeModel> {
|
|||||||
Some(NodeModel { class, dialect: dialect(parsed) })
|
Some(NodeModel { class, dialect: dialect(parsed) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The per-field binding role, resolved once per regular field from the node
|
|
||||||
/// class and field shape. Drives the eval bindings and the lazy-edge input
|
|
||||||
/// types. The `lend` and `reads` axes stay field properties the value arms of
|
|
||||||
/// `kernel_params`/`call_args`/`value_args` consult, since they cross roles.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(crate) enum InputRole {
|
|
||||||
RecordCarrier,
|
|
||||||
FlipCarrier,
|
|
||||||
ReadingSecondary,
|
|
||||||
LendBorrow,
|
|
||||||
RecordValue,
|
|
||||||
PlainValue,
|
|
||||||
DeriveRoutingSource,
|
|
||||||
OpaqueRecordEdge,
|
|
||||||
FlipRawLazyEdge,
|
|
||||||
FlipLazy,
|
|
||||||
RawLazy,
|
|
||||||
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.
|
|
||||||
pub(crate) fn reads_out(self) -> bool {
|
|
||||||
matches!(self, InputRole::ReadingSecondary | InputRole::RecordValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The tail form of a node's eval, selected from its class and dialect: forward
|
/// The tail form of a node's eval, selected from its class and dialect: forward
|
||||||
/// the kernel's own record, assemble a record (io or flip carrier), or spawn a
|
/// the kernel's own record, assemble a record (io or flip carrier), or spawn a
|
||||||
/// source and lift its completion.
|
/// source and lift its completion.
|
||||||
|
|||||||
@@ -21,9 +21,18 @@ pub(crate) fn build(parsed: &ParsedNodeFn) -> Node {
|
|||||||
output: output(parsed, &generic_idents),
|
output: output(parsed, &generic_idents),
|
||||||
generics,
|
generics,
|
||||||
effect: effect(parsed),
|
effect: effect(parsed),
|
||||||
|
derives: derives(parsed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn derives(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
context_param(parsed).is_some_and(|ctx| {
|
||||||
|
ctx.bounds
|
||||||
|
.iter()
|
||||||
|
.any(|bound| matches!(bound, TypeParamBound::Trait(trait_bound) if trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx")))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn generics(parsed: &ParsedNodeFn) -> Vec<Generic> {
|
fn generics(parsed: &ParsedNodeFn) -> Vec<Generic> {
|
||||||
let ctx = context_param(parsed).map(|param| param.ident.clone());
|
let ctx = context_param(parsed).map(|param| param.ident.clone());
|
||||||
parsed
|
parsed
|
||||||
@@ -55,6 +64,7 @@ fn inputs(parsed: &ParsedNodeFn, fields: &[&ParsedField], generics: &[Ident]) ->
|
|||||||
evaluation,
|
evaluation,
|
||||||
shape: item_shape(field_element_type(field), &field.attribute_reads, generics),
|
shape: item_shape(field_element_type(field), &field.attribute_reads, generics),
|
||||||
subject: subject(index, field, carrier_subject, routing.as_ref()),
|
subject: subject(index, field, carrier_subject, routing.as_ref()),
|
||||||
|
lend: matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. })),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -172,7 +182,7 @@ fn ilist_inner(ty: &Type) -> Option<Type> {
|
|||||||
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);
|
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)| {
|
let reads = node.inputs.iter().enumerate().filter_map(|(index, input)| {
|
||||||
(!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);
|
||||||
let index = index as u8;
|
let index = index as u8;
|
||||||
quote!(#core_types::record::InputReads { input: #index, reads: ::std::vec![#(#descs),*] })
|
quote!(#core_types::record::InputReads { input: #index, reads: ::std::vec![#(#descs),*] })
|
||||||
@@ -213,6 +223,94 @@ fn level_delta(node: &Node) -> i8 {
|
|||||||
node.output.shape.depth as i8 - subject_depth
|
node.output.shape.depth as i8 - subject_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How an eager value input binds in eval.
|
||||||
|
pub(crate) enum ValueBinding {
|
||||||
|
Carrier,
|
||||||
|
Lend,
|
||||||
|
ReadingSecondary,
|
||||||
|
RecordElement,
|
||||||
|
Plain,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How a lazy (`impl Node`) input binds in eval. The `Poll` effect further
|
||||||
|
/// selects the borrowed vs `__cell`-driven form within `Element`/`Plain`.
|
||||||
|
pub(crate) enum LazyBinding {
|
||||||
|
Element,
|
||||||
|
Plain,
|
||||||
|
DeriveRouting,
|
||||||
|
OpaqueRecord,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueBinding {
|
||||||
|
/// Copies an element out of a record edge, so the frame is reclaimed after.
|
||||||
|
pub(crate) fn reads_out(&self) -> bool {
|
||||||
|
matches!(self, ValueBinding::ReadingSecondary | ValueBinding::RecordElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NodeKind {
|
||||||
|
Flip,
|
||||||
|
RecordIo,
|
||||||
|
Routing,
|
||||||
|
Opaque,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_kind(node: &Node) -> NodeKind {
|
||||||
|
if matches!(node.output.shape.element, Element::Opaque) {
|
||||||
|
NodeKind::Opaque
|
||||||
|
} else if has_attr_io(node) {
|
||||||
|
NodeKind::RecordIo
|
||||||
|
} else if is_routing(node) {
|
||||||
|
NodeKind::Routing
|
||||||
|
} else {
|
||||||
|
NodeKind::Flip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Routing forwards an unbounded generic from a source whole; a bounded generic
|
||||||
|
/// or one transformed into a different output type works on the element and flips.
|
||||||
|
fn is_routing(node: &Node) -> bool {
|
||||||
|
let Element::Generic(output) = &node.output.shape.element else { return false };
|
||||||
|
node.monomorphizations.is_empty()
|
||||||
|
&& node.generics.iter().any(|generic| &generic.ident == output && generic.bounds.is_empty())
|
||||||
|
&& node.inputs.iter().any(|input| input.subject && matches!(&input.shape.element, Element::Generic(generic) if generic == output))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_attr_io(node: &Node) -> bool {
|
||||||
|
// Reads on lazy inputs ride the flip; only eager reads make a record-io node.
|
||||||
|
node.inputs.iter().any(|input| matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()) || !node.output.shape.attrs.is_empty() || !node.output.removes.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn value_binding(node: &Node, index: usize) -> ValueBinding {
|
||||||
|
let input = &node.inputs[index];
|
||||||
|
let kind = node_kind(node);
|
||||||
|
if matches!(kind, NodeKind::RecordIo | NodeKind::Flip) && index == 0 && input.subject {
|
||||||
|
ValueBinding::Carrier
|
||||||
|
} else if matches!(kind, NodeKind::Flip) && input.lend {
|
||||||
|
ValueBinding::Lend
|
||||||
|
} else if matches!(kind, NodeKind::RecordIo) && !input.shape.attrs.is_empty() {
|
||||||
|
ValueBinding::ReadingSecondary
|
||||||
|
} else if matches!(kind, NodeKind::Flip) || (matches!(kind, NodeKind::Routing) && !input.subject) {
|
||||||
|
ValueBinding::RecordElement
|
||||||
|
} else {
|
||||||
|
ValueBinding::Plain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn lazy_binding(node: &Node, index: usize) -> LazyBinding {
|
||||||
|
let input = &node.inputs[index];
|
||||||
|
let kind = node_kind(node);
|
||||||
|
if node.derives && matches!(kind, NodeKind::Routing) && input.subject {
|
||||||
|
LazyBinding::DeriveRouting
|
||||||
|
} else if matches!(kind, NodeKind::Flip) {
|
||||||
|
LazyBinding::Element
|
||||||
|
} else if matches!(input.shape.element, Element::Opaque) {
|
||||||
|
LazyBinding::OpaqueRecord
|
||||||
|
} else {
|
||||||
|
LazyBinding::Plain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct Node {
|
pub(crate) struct Node {
|
||||||
pub(crate) kernel: Kernel,
|
pub(crate) kernel: Kernel,
|
||||||
pub(crate) generics: Vec<Generic>,
|
pub(crate) generics: Vec<Generic>,
|
||||||
@@ -221,6 +319,8 @@ pub(crate) struct Node {
|
|||||||
pub(crate) inputs: Vec<Input>,
|
pub(crate) inputs: Vec<Input>,
|
||||||
pub(crate) output: Output,
|
pub(crate) output: Output,
|
||||||
pub(crate) effect: Effect,
|
pub(crate) effect: Effect,
|
||||||
|
/// The context is derived (a `DeriveCtx` bound), so routing sources rebind it.
|
||||||
|
pub(crate) derives: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The kernel fn the node wraps.
|
/// The kernel fn the node wraps.
|
||||||
@@ -244,6 +344,8 @@ pub(crate) struct Input {
|
|||||||
pub(crate) shape: ItemShape,
|
pub(crate) shape: ItemShape,
|
||||||
/// This input's layout folds into the output.
|
/// This input's layout folds into the output.
|
||||||
pub(crate) subject: bool,
|
pub(crate) subject: bool,
|
||||||
|
/// Written `&T`; the kernel borrows the evaluated element.
|
||||||
|
pub(crate) lend: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Lazy` = `impl Node<..>`, the kernel drives it.
|
/// `Lazy` = `impl Node<..>`, the kernel drives it.
|
||||||
@@ -288,7 +390,7 @@ pub(crate) enum Effect {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::codegen::classify::{Class, analyze};
|
use crate::codegen::classify::{Class, Dialect, analyze, context_param, dialect};
|
||||||
use crate::parsing::parse_node_fn;
|
use crate::parsing::parse_node_fn;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{ToTokens, quote};
|
use quote::{ToTokens, quote};
|
||||||
@@ -424,6 +526,161 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ctx_derives(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
context_param(parsed).is_some_and(|ctx| {
|
||||||
|
ctx.bounds
|
||||||
|
.iter()
|
||||||
|
.any(|bound| matches!(bound, TypeParamBound::Trait(trait_bound) if trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx")))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The frozen `field_role` classification the IR bindings must reproduce.
|
||||||
|
fn reference_label(parsed: &ParsedNodeFn, class: &Class, raw: bool, index: usize, field: &ParsedField) -> &'static str {
|
||||||
|
let record = matches!(class, Class::RecordIo(_));
|
||||||
|
let skips_carrier = matches!(class, Class::RecordIo(shape) if shape.skips_carrier());
|
||||||
|
let carrier_flip = matches!(class, Class::Flip { carrier: true });
|
||||||
|
let flip = matches!(class, Class::Flip { .. });
|
||||||
|
let opaque = matches!(class, Class::Opaque);
|
||||||
|
let routing = matches!(class, Class::Routing(_));
|
||||||
|
let derives = ctx_derives(parsed);
|
||||||
|
let routing_source = |ty: &Type| matches!(class, Class::Routing(routing) if bare_ident(ty) == Some(&routing.generic));
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => {
|
||||||
|
if record && !skips_carrier && index == 0 {
|
||||||
|
"carrier"
|
||||||
|
} else if carrier_flip && index == 0 {
|
||||||
|
"carrier"
|
||||||
|
} else if flip && lend.is_some() {
|
||||||
|
"lend"
|
||||||
|
} else if record && !field.attribute_reads.is_empty() {
|
||||||
|
"reading"
|
||||||
|
} else if flip || (routing && !routing_source(ty)) {
|
||||||
|
"record"
|
||||||
|
} else {
|
||||||
|
"plain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
||||||
|
if derives && routing && routing_source(output_type) {
|
||||||
|
"derive-routing"
|
||||||
|
} else if flip && raw {
|
||||||
|
"flip-raw"
|
||||||
|
} else if flip {
|
||||||
|
"flip-lazy"
|
||||||
|
} else if opaque && raw && is_record_value(output_type) {
|
||||||
|
"opaque-record"
|
||||||
|
} else if raw {
|
||||||
|
"raw-lazy"
|
||||||
|
} else {
|
||||||
|
"lazy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ir_label(node: &Node, index: usize, field: &ParsedField, raw: bool) -> &'static str {
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(_) => match value_binding(node, index) {
|
||||||
|
ValueBinding::Carrier => "carrier",
|
||||||
|
ValueBinding::Lend => "lend",
|
||||||
|
ValueBinding::ReadingSecondary => "reading",
|
||||||
|
ValueBinding::RecordElement => "record",
|
||||||
|
ValueBinding::Plain => "plain",
|
||||||
|
},
|
||||||
|
ParsedFieldType::Node(_) => match (lazy_binding(node, index), raw) {
|
||||||
|
(LazyBinding::DeriveRouting, _) => "derive-routing",
|
||||||
|
(LazyBinding::OpaqueRecord, _) => "opaque-record",
|
||||||
|
(LazyBinding::Element, true) => "flip-raw",
|
||||||
|
(LazyBinding::Element, false) => "flip-lazy",
|
||||||
|
(LazyBinding::Plain, true) => "raw-lazy",
|
||||||
|
(LazyBinding::Plain, false) => "lazy",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_bindings(attr: TokenStream2, item: TokenStream2) {
|
||||||
|
let mut parsed = parse_node_fn(attr, item).unwrap();
|
||||||
|
parsed.replace_impl_trait_in_input();
|
||||||
|
let model = analyze(&parsed).expect("representative resolves to a class");
|
||||||
|
let raw = matches!(dialect(&parsed), Dialect::Poll);
|
||||||
|
let node = build(&parsed);
|
||||||
|
let fields: Vec<&ParsedField> = parsed.fields.iter().filter(|field| !field.is_data_field).collect();
|
||||||
|
for (index, field) in fields.iter().enumerate() {
|
||||||
|
assert_eq!(
|
||||||
|
ir_label(&node, index, field, raw),
|
||||||
|
reference_label(&parsed, &model.class, raw, index, field),
|
||||||
|
"field {index} of {}",
|
||||||
|
parsed.fn_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_flip() {
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn negate(_: impl Ctx, x: f64) -> f64 { -x }));
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn add2(_: impl Ctx, a: f64, b: f64) -> f64 { a + b }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_lend() {
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn borrow(_: impl Ctx, prim: f64, other: &f64) -> f64 { prim + *other }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_reading_secondary() {
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn read_op(_: impl Ctx, carrier: f64, (other, op): (f64, Attr<Opacity>)) -> f64 { carrier + other }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_flip_lazy() {
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn apply(_: impl Ctx, inner: impl Node<(), Output = f64>) -> f64 { inner.eval(()) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_flip_lazy_reads() {
|
||||||
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(fn apply_reads(_: impl Ctx, carrier: f64, inner: impl Node<(), Output = (f64, Attr<Opacity>)>) -> f64 { carrier + inner.eval(()).0 }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_flip_raw() {
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn poll_apply(_: impl Ctx, inner: impl Node<(), Output = f64>) -> GPoll<f64> { inner.eval(()) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_skip_impl_generic() {
|
||||||
|
// A bounded generic forwarded whole (passthrough) flips, not routes.
|
||||||
|
assert_bindings(quote!(category(""), skip_impl), quote!(fn passthrough<T: Send>(_: impl Ctx, content: T) -> T { content }));
|
||||||
|
// A generic transformed into a different output type flips.
|
||||||
|
assert_bindings(
|
||||||
|
quote!(category(""), skip_impl),
|
||||||
|
quote!(fn into_ty<T: Send + Into<O>, O: Send>(_: impl Ctx, value: T, #[data] _out: PhantomData<O>) -> O { value.into() }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_routing() {
|
||||||
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(fn switch<T>(_: impl Ctx, condition: bool, off: impl Node<(), Output = T>, on: impl Node<(), Output = T>) -> T { if condition { on.eval(()) } else { off.eval(()) } }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_derive_routing() {
|
||||||
|
assert_bindings(quote!(category("")), quote!(fn ctx_mod<T>(_: impl Ctx + DeriveCtx, inner: impl Node<(), Output = T>) -> T { inner.eval(()) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_opaque() {
|
||||||
|
assert_bindings(
|
||||||
|
quote!(category("")),
|
||||||
|
quote!(fn memo<'e>(_: impl Ctx, #[data] cache: Store, content: impl Node<Context<'_>, Output = RecordValue<'e>>) -> GPoll<RecordValue<'e>> { content.eval(()) }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn monomorphizations_key_by_generic() {
|
fn monomorphizations_key_by_generic() {
|
||||||
let node = assert_bridge(
|
let node = assert_bridge(
|
||||||
|
|||||||
Reference in New Issue
Block a user