Add the Record wire type, record registry rows, lift and extract adapters, and executor stack reservation

This commit is contained in:
Dennis Kobert
2026-08-05 12:24:20 +00:00
parent abe2a565d1
commit c91be74a55
9 changed files with 318 additions and 3 deletions

View File

@@ -352,6 +352,79 @@ impl<N> RecordSource<N> {
}
}
/// Lifts a plain producer onto a record wire: the element lands at offset 0
/// of a fresh element-only record. `Copy` elements only until droppable
/// elements ride records.
pub struct RecordLift<El, N> {
edge: N,
layout: Layout,
frame_bytes: usize,
_marker: std::marker::PhantomData<fn() -> El>,
}
impl<El: Copy + 'static, N> RecordLift<El, N> {
pub fn wire(edge: N) -> Self {
let layout = Layout::default().with_writes(0, (size_of::<El>(), align_of::<El>()), &[]);
let frame_bytes = layout.size.next_multiple_of(8);
Self {
edge,
layout,
frame_bytes,
_marker: std::marker::PhantomData,
}
}
}
impl<'e, C, El, N> Node<C> for RecordLift<El, N>
where
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
El: Copy + 'static,
N: Node<C, Output = El>,
{
type Output = RecordValue<'e>;
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
let dst = stack::push(self.frame_bytes);
let value = self.edge.eval(input).map(|element| {
unsafe { write_field(dst, 0, element) };
RecordValue::from_rec(unsafe { Rec::new(dst.cast_const()) })
});
stack::pop(dst);
value
}
fn layout(&self) -> Option<&Layout> {
Some(&self.layout)
}
}
/// Extracts the element from a record wire for a plain consumer.
pub struct RecordExtract<El, N> {
edge: N,
_marker: std::marker::PhantomData<fn() -> El>,
}
impl<El, N> RecordExtract<El, N> {
pub fn wire(edge: N) -> Self {
Self {
edge,
_marker: std::marker::PhantomData,
}
}
}
impl<'e, C, El, N> Node<C> for RecordExtract<El, N>
where
El: Copy + 'static,
N: Node<C, Output = RecordValue<'e>>,
{
type Output = El;
fn eval(&self, input: &C) -> GPoll<El> {
self.edge.eval(input).map(|value| unsafe { value.rec().element::<El>() })
}
}
impl<'e, C, N> Node<C> for RecordSource<N>
where
N: Node<C, Output = RecordValue<'e>>,

View File

@@ -79,6 +79,12 @@ pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T> +
#[cfg(target_family = "wasm")]
pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T>;
/// Element-independent by erasure; the wire's `Type::Record(El)` keeps element reads proven at wiring.
#[cfg(not(target_family = "wasm"))]
pub type ErasedRecordNode = dyn for<'c> Node<ContextImpl<'c>, Output = crate::record::RecordValue<'c>> + Send + Sync;
#[cfg(target_family = "wasm")]
pub type ErasedRecordNode = dyn for<'c> Node<ContextImpl<'c>, Output = crate::record::RecordValue<'c>>;
#[cfg(not(target_family = "wasm"))]
type DynEdge = dyn std::any::Any + Send + Sync;
#[cfg(target_family = "wasm")]
@@ -96,6 +102,22 @@ pub fn lend_edge_type<T: 'static>() -> Type {
Type::Fn(Box::new(concrete!(Context)), Box::new(ref_type::<T>()))
}
pub fn record_type<T: 'static>() -> Type {
Type::Record(Box::new(concrete!(T)))
}
pub fn record_edge_type<T: 'static>() -> Type {
Type::Fn(Box::new(concrete!(Context)), Box::new(record_type::<T>()))
}
/// The record edge type of a token row, generic over the element.
pub fn generic_record_edge_type(name: &'static str) -> Type {
Type::Fn(
Box::new(concrete!(Context)),
Box::new(Type::Record(Box::new(Type::Generic(std::borrow::Cow::Borrowed(name))))),
)
}
pub fn cache_key<C: CacheHash + ?Sized>(ctx: &C) -> u64 {
let mut hasher = graphene_hash::FxHasher64::new();
ctx.cache_hash(&mut hasher);
@@ -106,6 +128,7 @@ pub fn cache_key<C: CacheHash + ?Sized>(ctx: &C) -> u64 {
pub enum ConstructionError {
Arity { expected: usize, got: usize },
Type { expected: Box<Type>, found: Box<Type> },
MissingLayout,
}
pub struct SharedEdge<N: ?Sized> {
@@ -198,6 +221,10 @@ impl EdgeHandle {
Self::new_erased(node, lend_edge_type::<T>())
}
pub fn new_record<T: 'static>(node: std::sync::Arc<ErasedRecordNode>) -> Self {
Self::new_erased(node, record_edge_type::<T>())
}
pub fn new_erased<N>(node: std::sync::Arc<N>, ty: Type) -> Self
where
N: ?Sized + 'static + for<'c> Node<ContextImpl<'c>>,
@@ -242,6 +269,10 @@ impl EdgeHandle {
self.downcast_erased(lend_edge_type::<T>())
}
pub fn downcast_record<T: 'static>(self) -> Result<SharedEdge<ErasedRecordNode>, ConstructionError> {
self.downcast_erased(record_edge_type::<T>())
}
pub fn downcast_erased<N: ?Sized + 'static>(self, expected: Type) -> Result<SharedEdge<N>, ConstructionError> {
let found = self.ty;
self.node.downcast::<SharedEdge<N>>().map(|edge| *edge).map_err(|_| ConstructionError::Type {

View File

@@ -236,6 +236,8 @@ pub enum Type {
/// Represents a future which promises to return the inner type.
Future(Box<Type>),
Ref(Box<Type>),
/// A packed record wire over the element type; the layout stays node-resident metadata.
Record(Box<Type>),
}
impl Default for Type {
@@ -310,6 +312,7 @@ impl Type {
Self::Fn(_, _) => None,
Self::Future(_) => None,
Self::Ref(_) => None,
Self::Record(_) => None,
}
}
@@ -320,6 +323,7 @@ impl Type {
Self::Fn(_, _) => None,
Self::Future(_) => None,
Self::Ref(_) => None,
Self::Record(_) => None,
}
}
@@ -330,6 +334,7 @@ impl Type {
Self::Fn(_, output) => output.nested_type(),
Self::Future(output) => output.nested_type(),
Self::Ref(inner) => inner.nested_type(),
Self::Record(inner) => inner.nested_type(),
}
}
@@ -343,6 +348,7 @@ impl Type {
Self::Fn(_, output) => output.replace_nested(f),
Self::Future(output) => output.replace_nested(f),
Self::Ref(inner) => inner.replace_nested(f),
Self::Record(inner) => inner.replace_nested(f),
}
}
@@ -353,6 +359,7 @@ impl Type {
Type::Fn(call_arg, return_value) => format!("{} called with {}", return_value.identifier_name(), call_arg.identifier_name()),
Type::Future(ty) => ty.identifier_name(),
Type::Ref(ty) => ty.identifier_name(),
Type::Record(ty) => ty.identifier_name(),
}
}
}
@@ -448,6 +455,7 @@ impl std::fmt::Display for Type {
Type::Fn(_, return_value) => write!(f, "{return_value}"),
Type::Future(ty) => write!(f, "{ty}"),
Type::Ref(ty) => write!(f, "{ty}"),
Type::Record(ty) => write!(f, "{ty}"),
}
}
}