mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-26 11:18:12 +08:00
Inline records of at most 16 bytes into the two-word RecordValue with layout-resolved storage
This commit is contained in:
@@ -481,7 +481,7 @@ impl BorrowTree {
|
|||||||
pub fn stack_need(&self) -> usize {
|
pub fn stack_need(&self) -> usize {
|
||||||
self.nodes
|
self.nodes
|
||||||
.values()
|
.values()
|
||||||
.map(|(handle, _)| handle.layout().map_or(0, |layout| layout.size.next_multiple_of(8)))
|
.map(|(handle, _)| handle.layout().map_or(0, |layout| layout.frame_bytes()))
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -825,7 +825,9 @@ mod node_registry_macros {
|
|||||||
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||||
}
|
}
|
||||||
let mut inputs = inputs.into_iter();
|
let mut inputs = inputs.into_iter();
|
||||||
let node = core_types::record::RecordExtract::<$type, _>::new(inputs.next().unwrap().downcast_record::<$type>()?);
|
let edge = inputs.next().unwrap();
|
||||||
|
let layout = edge.layout().ok_or(ConstructionError::MissingLayout)?.clone();
|
||||||
|
let node = core_types::record::RecordExtract::<$type, _>::new(edge.downcast_record::<$type>()?, &layout);
|
||||||
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$type>>))
|
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$type>>))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
//! The packed-record tier at rank 0. A record is the element at offset 0
|
//! The packed-record tier at rank 0. A record is the element at offset 0
|
||||||
//! plus one field per written attribute; its [`Layout`] is computed at
|
//! plus one field per written attribute; its [`Layout`] is computed at
|
||||||
//! wiring from the upstream write set and never serialized. Records live as
|
//! wiring from the upstream write set and never serialized. Records of
|
||||||
|
//! inline layouts live in the [`RecordValue`] itself; larger ones live as
|
||||||
//! per-lane views on the per-thread record [`stack`], claimed per
|
//! per-lane views on the per-thread record [`stack`], claimed per
|
||||||
//! evaluation, and kernels route them as opaque [`RecordValue`]s that carry
|
//! evaluation. Kernels route them as opaque [`RecordValue`]s that carry
|
||||||
//! their provenance. Only generated or wiring code touches offsets, so a
|
//! their provenance. Only generated or wiring code touches offsets, so a
|
||||||
//! safe kernel cannot misalign a field.
|
//! safe kernel cannot misalign a field.
|
||||||
|
|
||||||
@@ -74,6 +75,23 @@ impl Layout {
|
|||||||
self.fields.iter().find(|field| field.name == name && field.level == level).map(|field| field.offset)
|
self.fields.iter().find(|field| field.name == name && field.level == level).map(|field| field.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_inline(&self) -> bool {
|
||||||
|
self.size <= 16 && self.align <= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame_bytes(&self) -> usize {
|
||||||
|
if self.is_inline() { 0 } else { self.size.next_multiple_of(8) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a value of this layout, which must be its wiring-proven one,
|
||||||
|
/// to its record bytes.
|
||||||
|
pub fn rec(&self, value: &RecordValue<'_>) -> Rec {
|
||||||
|
match self.is_inline() {
|
||||||
|
true => Rec((&raw const *value).cast()),
|
||||||
|
false => Rec(value.ptr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The union of this layout's fields and `writes` over an element of
|
/// The union of this layout's fields and `writes` over an element of
|
||||||
/// (size, align) at `depth`, in canonical order. A (name, level) written
|
/// (size, align) at `depth`, in canonical order. A (name, level) written
|
||||||
/// at a different size is a type conflict and panics; the census keeps
|
/// at a different size is a type conflict and panics; the census keeps
|
||||||
@@ -190,23 +208,48 @@ impl Rec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An opaque record value: element and attributes traveling as one unit.
|
/// An opaque, tagless record value: inline layouts live in the value's own
|
||||||
/// Lazy record inputs yield one per evaluation, kernels route them as
|
/// two words, spilled layouts ride the pointer, and only [`Layout::rec`]
|
||||||
/// ordinary values, and the returned value's record is the node's output, so
|
/// tells them apart. Two scalar fields are load-bearing: a union or an
|
||||||
/// provenance rides the value itself. The eval lifetime keeps it out of node
|
/// over-aligned repr demotes the return to memory. Both constructors
|
||||||
/// state; the field is private, so it is unforgeable and uninspectable.
|
/// initialize all 16 bytes, and a raw pointer field accepts any bit
|
||||||
#[derive(Clone, Copy, Debug)]
|
/// pattern, so inline bytes overlay the fields soundly.
|
||||||
pub struct RecordValue<'e>(Rec, std::marker::PhantomData<&'e ()>);
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RecordValue<'e> {
|
||||||
|
ptr: *const u8,
|
||||||
|
_extra: usize,
|
||||||
|
_lifetime: std::marker::PhantomData<&'e ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for RecordValue<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("RecordValue(..)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'e> RecordValue<'e> {
|
impl<'e> RecordValue<'e> {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn from_rec(rec: Rec) -> Self {
|
pub fn zeroed() -> Self {
|
||||||
RecordValue(rec, std::marker::PhantomData)
|
RecordValue {
|
||||||
|
ptr: std::ptr::null(),
|
||||||
|
_extra: 0,
|
||||||
|
_lifetime: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The inline storage under construction; writes land in the value itself.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn as_mut_ptr(&mut self) -> *mut u8 {
|
||||||
|
(&raw mut *self).cast()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn rec(self) -> Rec {
|
pub fn spilled(rec: Rec) -> Self {
|
||||||
self.0
|
RecordValue {
|
||||||
|
ptr: rec.ptr(),
|
||||||
|
_extra: 0,
|
||||||
|
_lifetime: std::marker::PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +393,8 @@ fn default_fill_bytes(name: &str, size: usize) -> Box<[u8]> {
|
|||||||
pub struct SourcePlan {
|
pub struct SourcePlan {
|
||||||
moves: Vec<(usize, usize, usize)>,
|
moves: Vec<(usize, usize, usize)>,
|
||||||
fills: Vec<(usize, Box<[u8]>)>,
|
fills: Vec<(usize, Box<[u8]>)>,
|
||||||
union_bytes: usize,
|
source: Layout,
|
||||||
|
union: Layout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourcePlan {
|
impl SourcePlan {
|
||||||
@@ -368,7 +412,8 @@ impl SourcePlan {
|
|||||||
Some(SourcePlan {
|
Some(SourcePlan {
|
||||||
moves,
|
moves,
|
||||||
fills,
|
fills,
|
||||||
union_bytes: union.size,
|
source: source.clone(),
|
||||||
|
union: union.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,7 +505,7 @@ where
|
|||||||
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
||||||
let value = self.edge.eval(input);
|
let value = self.edge.eval(input);
|
||||||
if let GPoll::Final(record) | GPoll::Partial(record) = &value {
|
if let GPoll::Final(record) | GPoll::Partial(record) = &value {
|
||||||
let bytes: Box<[u8]> = unsafe { std::slice::from_raw_parts(record.rec().ptr(), self.layout.size) }.into();
|
let bytes: Box<[u8]> = unsafe { std::slice::from_raw_parts(self.layout.rec(record).ptr(), self.layout.size) }.into();
|
||||||
let capture = input.arena().alloc(bytes).map(|(_, weak)| RecordCapture {
|
let capture = input.arena().alloc(bytes).map(|(_, weak)| RecordCapture {
|
||||||
layout: self.layout.clone(),
|
layout: self.layout.clone(),
|
||||||
bytes: weak,
|
bytes: weak,
|
||||||
@@ -486,18 +531,14 @@ where
|
|||||||
pub struct RecordLift<El, N> {
|
pub struct RecordLift<El, N> {
|
||||||
edge: N,
|
edge: N,
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
frame_bytes: usize,
|
|
||||||
_marker: std::marker::PhantomData<fn() -> El>,
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<El: Copy + 'static, N> RecordLift<El, N> {
|
impl<El: Copy + 'static, N> RecordLift<El, N> {
|
||||||
pub fn new(edge: N) -> Self {
|
pub fn new(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 {
|
Self {
|
||||||
edge,
|
edge,
|
||||||
layout,
|
layout: Layout::default().with_writes(0, (size_of::<El>(), align_of::<El>()), &[]),
|
||||||
frame_bytes,
|
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,10 +553,17 @@ where
|
|||||||
type Output = RecordValue<'e>;
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
||||||
let dst = stack::push(self.frame_bytes);
|
if self.layout.is_inline() {
|
||||||
|
return self.edge.eval(input).map(|element| {
|
||||||
|
let mut value = RecordValue::zeroed();
|
||||||
|
unsafe { write_field(value.as_mut_ptr(), 0, element) };
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let dst = stack::push(self.layout.frame_bytes());
|
||||||
let value = self.edge.eval(input).map(|element| {
|
let value = self.edge.eval(input).map(|element| {
|
||||||
unsafe { write_field(dst, 0, element) };
|
unsafe { write_field(dst, 0, element) };
|
||||||
RecordValue::from_rec(unsafe { Rec::new(dst.cast_const()) })
|
RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })
|
||||||
});
|
});
|
||||||
stack::pop(dst);
|
stack::pop(dst);
|
||||||
value
|
value
|
||||||
@@ -529,13 +577,15 @@ where
|
|||||||
/// Extracts the element from a record wire for a plain consumer.
|
/// Extracts the element from a record wire for a plain consumer.
|
||||||
pub struct RecordExtract<El, N> {
|
pub struct RecordExtract<El, N> {
|
||||||
edge: N,
|
edge: N,
|
||||||
|
layout: Layout,
|
||||||
_marker: std::marker::PhantomData<fn() -> El>,
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<El, N> RecordExtract<El, N> {
|
impl<El, N> RecordExtract<El, N> {
|
||||||
pub fn new(edge: N) -> Self {
|
pub fn new(edge: N, layout: &Layout) -> Self {
|
||||||
Self {
|
Self {
|
||||||
edge,
|
edge,
|
||||||
|
layout: layout.clone(),
|
||||||
_marker: std::marker::PhantomData,
|
_marker: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,7 +599,7 @@ where
|
|||||||
type Output = El;
|
type Output = El;
|
||||||
|
|
||||||
fn eval(&self, input: &C) -> GPoll<El> {
|
fn eval(&self, input: &C) -> GPoll<El> {
|
||||||
self.edge.eval(input).map(|value| unsafe { value.rec().element::<El>() })
|
self.edge.eval(input).map(|value| unsafe { self.layout.rec(&value).element::<El>() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,10 +612,15 @@ where
|
|||||||
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
||||||
match &self.plan {
|
match &self.plan {
|
||||||
None => self.edge.eval(input),
|
None => self.edge.eval(input),
|
||||||
|
Some(plan) if plan.union.is_inline() => self.edge.eval(input).map(|value| {
|
||||||
|
let mut out = RecordValue::zeroed();
|
||||||
|
unsafe { plan.translate(plan.source.rec(&value), out.as_mut_ptr()) };
|
||||||
|
out
|
||||||
|
}),
|
||||||
Some(plan) => {
|
Some(plan) => {
|
||||||
let dst = stack::push(plan.union_bytes);
|
let dst = stack::push(plan.union.frame_bytes());
|
||||||
let value = self.edge.eval(input);
|
let value = self.edge.eval(input);
|
||||||
value.map(|value| RecordValue::from_rec(unsafe { plan.translate(value.rec(), dst) }))
|
value.map(|value| RecordValue::spilled(unsafe { plan.translate(plan.source.rec(&value), dst) }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,6 +687,36 @@ mod tests {
|
|||||||
assert!(SourcePlan::new(&layout, &layout.clone()).is_none());
|
assert!(SourcePlan::new(&layout, &layout.clone()).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_values_are_two_words() {
|
||||||
|
assert_eq!(size_of::<RecordValue>(), 16);
|
||||||
|
assert_eq!(align_of::<RecordValue>(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn layouts_resolve_inline_and_spilled_values() {
|
||||||
|
let inline = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity")]);
|
||||||
|
assert!(inline.is_inline());
|
||||||
|
assert_eq!(inline.frame_bytes(), 0);
|
||||||
|
let mut value = RecordValue::zeroed();
|
||||||
|
unsafe {
|
||||||
|
write_field(value.as_mut_ptr(), 0, 4f64);
|
||||||
|
write_field(value.as_mut_ptr(), inline.offset_of("opacity", 0).unwrap(), 0.5f64);
|
||||||
|
}
|
||||||
|
let rec = inline.rec(&value);
|
||||||
|
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
||||||
|
assert_eq!(unsafe { rec.read::<f64>(inline.offset_of("opacity", 0).unwrap()) }, 0.5);
|
||||||
|
|
||||||
|
let spilled = Layout::default().with_writes(0, (8, 8), &[f64_field("opacity"), f64_field("length")]);
|
||||||
|
assert!(!spilled.is_inline());
|
||||||
|
assert_eq!(spilled.frame_bytes(), 24);
|
||||||
|
let record = [1f64, 2., 3.];
|
||||||
|
let value = RecordValue::spilled(unsafe { Rec::new(record.as_ptr().cast()) });
|
||||||
|
assert_eq!(unsafe { spilled.rec(&value).element::<f64>() }, 1.);
|
||||||
|
assert_eq!(unsafe { spilled.rec(&value).read::<f64>(spilled.offset_of("length", 0).unwrap()) }, 2.);
|
||||||
|
assert_eq!(unsafe { spilled.rec(&value).read::<f64>(spilled.offset_of("opacity", 0).unwrap()) }, 3.);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stack_frames_nest_and_release() {
|
fn stack_frames_nest_and_release() {
|
||||||
stack::reserve(64);
|
stack::reserve(64);
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
Some(shape) => {
|
Some(shape) => {
|
||||||
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout)];
|
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout)];
|
||||||
if !shape.skips_carrier() {
|
if !shape.skips_carrier() {
|
||||||
|
state.push(quote!(pub(super) __carrier: gcore::record::Layout));
|
||||||
state.push(quote!(pub(super) __plan: ::std::vec::Vec<(usize, usize, usize)>));
|
state.push(quote!(pub(super) __plan: ::std::vec::Vec<(usize, usize, usize)>));
|
||||||
}
|
}
|
||||||
state.push(quote!(pub(super) __frame_bytes: usize));
|
state.push(quote!(pub(super) __frame_bytes: usize));
|
||||||
@@ -1113,7 +1114,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(interrupt) => return interrupt.into(),
|
Err(interrupt) => return interrupt.into(),
|
||||||
};
|
};
|
||||||
let __src_rec = #core_types::record::RecordValue::rec(__src);
|
let __src_rec = self.__carrier.rec(&__src);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let carry = (!shape.skips_carrier()).then(|| quote!(unsafe { #core_types::record::apply_plan(__src_rec, __dst, &self.__plan) };));
|
let carry = (!shape.skips_carrier()).then(|| quote!(unsafe { #core_types::record::apply_plan(__src_rec, __dst, &self.__plan) };));
|
||||||
@@ -1152,7 +1153,11 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
quote!(unsafe { #core_types::record::write_field(__dst, self.#slot, #binder) };)
|
quote!(unsafe { #core_types::record::write_field(__dst, self.#slot, #binder) };)
|
||||||
});
|
});
|
||||||
quote! {
|
quote! {
|
||||||
let __dst = #core_types::record::stack::push(self.__frame_bytes);
|
let mut __value = #core_types::record::RecordValue::zeroed();
|
||||||
|
let __dst = match self.__frame_bytes {
|
||||||
|
0 => __value.as_mut_ptr(),
|
||||||
|
__bytes => #core_types::record::stack::push(__bytes),
|
||||||
|
};
|
||||||
#carrier_eval
|
#carrier_eval
|
||||||
#carry
|
#carry
|
||||||
#(#read_bindings)*
|
#(#read_bindings)*
|
||||||
@@ -1160,8 +1165,11 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
#destructure
|
#destructure
|
||||||
#element_store
|
#element_store
|
||||||
#(#attr_stores)*
|
#(#attr_stores)*
|
||||||
#core_types::record::stack::pop(__dst);
|
if self.__frame_bytes != 0 {
|
||||||
__cell.finish(#core_types::record::RecordValue::from_rec(unsafe { #core_types::record::Rec::new(__dst.cast_const()) }))
|
#core_types::record::stack::pop(__dst);
|
||||||
|
__value = #core_types::record::RecordValue::spilled(unsafe { #core_types::record::Rec::new(__dst.cast_const()) });
|
||||||
|
}
|
||||||
|
__cell.finish(__value)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let eval_tail = match (async_fn, future_kernel) {
|
let eval_tail = match (async_fn, future_kernel) {
|
||||||
@@ -1288,6 +1296,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let name = &field.pat_ident.ident;
|
let name = &field.pat_ident.ident;
|
||||||
quote!(#name,)
|
quote!(#name,)
|
||||||
});
|
});
|
||||||
|
let carrier_init = (!shape.skips_carrier()).then(|| quote!(__carrier: __carrier_layout.clone(),)).into_iter();
|
||||||
let plan_init = (!shape.skips_carrier()).then(|| quote!(__plan,)).into_iter();
|
let plan_init = (!shape.skips_carrier()).then(|| quote!(__plan,)).into_iter();
|
||||||
let read_names = (0..parsed.attribute_reads.len()).map(|index| format_ident!("__read_{index}")).map(|slot| quote!(#slot,));
|
let read_names = (0..parsed.attribute_reads.len()).map(|index| format_ident!("__read_{index}")).map(|slot| quote!(#slot,));
|
||||||
let write_names = (0..shape.write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot,));
|
let write_names = (0..shape.write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot,));
|
||||||
@@ -1302,10 +1311,11 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
#plan_binding
|
#plan_binding
|
||||||
#(#read_inits)*
|
#(#read_inits)*
|
||||||
#(#write_inits)*
|
#(#write_inits)*
|
||||||
let __frame_bytes = __layout.size.next_multiple_of(8);
|
let __frame_bytes = __layout.frame_bytes();
|
||||||
Self {
|
Self {
|
||||||
#(#data_inits)*
|
#(#data_inits)*
|
||||||
#(#edge_inits)*
|
#(#edge_inits)*
|
||||||
|
#(#carrier_init)*
|
||||||
__layout,
|
__layout,
|
||||||
#(#plan_init)*
|
#(#plan_init)*
|
||||||
__frame_bytes,
|
__frame_bytes,
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct RecordSourceNode<E> {
|
struct RecordSourceNode<E> {
|
||||||
frame_bytes: usize,
|
layout: Layout,
|
||||||
element: E,
|
element: E,
|
||||||
fields: Vec<(usize, f64)>,
|
fields: Vec<(usize, f64)>,
|
||||||
partial: bool,
|
partial: bool,
|
||||||
@@ -114,15 +114,21 @@ mod tests {
|
|||||||
type Output = RecordValue<'e>;
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
fn eval(&self, _input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
fn eval(&self, _input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||||
let dst = stack::push(self.frame_bytes);
|
let mut value = RecordValue::zeroed();
|
||||||
let value = unsafe {
|
let dst = match self.layout.frame_bytes() {
|
||||||
dst.cast::<E>().write(self.element);
|
0 => value.as_mut_ptr(),
|
||||||
for (offset, value) in &self.fields {
|
bytes => stack::push(bytes),
|
||||||
dst.add(*offset).cast::<f64>().write(*value);
|
|
||||||
}
|
|
||||||
RecordValue::from_rec(Rec::new(dst))
|
|
||||||
};
|
};
|
||||||
stack::pop(dst);
|
unsafe {
|
||||||
|
dst.cast::<E>().write(self.element);
|
||||||
|
for (offset, field) in &self.fields {
|
||||||
|
dst.add(*offset).cast::<f64>().write(*field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.layout.frame_bytes() != 0 {
|
||||||
|
stack::pop(dst);
|
||||||
|
value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) });
|
||||||
|
}
|
||||||
match self.partial {
|
match self.partial {
|
||||||
true => GPoll::Partial(value),
|
true => GPoll::Partial(value),
|
||||||
false => GPoll::Final(value),
|
false => GPoll::Final(value),
|
||||||
@@ -148,17 +154,13 @@ mod tests {
|
|||||||
Layout::default().with_writes(0, (8, 8), &writes)
|
Layout::default().with_writes(0, (8, 8), &writes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame_bytes(layout: &Layout) -> usize {
|
|
||||||
layout.size.next_multiple_of(8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reserve_for(layouts: &[&Layout]) {
|
fn reserve_for(layouts: &[&Layout]) {
|
||||||
stack::reserve(layouts.iter().map(|layout| frame_bytes(layout)).sum());
|
stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bare_source(layout: &Layout, element: f64) -> RecordSourceNode<f64> {
|
fn bare_source(layout: &Layout, element: f64) -> RecordSourceNode<f64> {
|
||||||
RecordSourceNode {
|
RecordSourceNode {
|
||||||
frame_bytes: frame_bytes(layout),
|
layout: layout.clone(),
|
||||||
element,
|
element,
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
partial: false,
|
partial: false,
|
||||||
@@ -186,7 +188,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = stacked.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 2.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 2.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(stacked.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
assert_eq!(unsafe { rec.read::<f64>(stacked.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
||||||
}
|
}
|
||||||
@@ -206,7 +208,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = measured.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, -2.);
|
assert_eq!(unsafe { rec.element::<f64>() }, -2.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.);
|
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.);
|
||||||
}
|
}
|
||||||
@@ -227,7 +229,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = measured.rec(&value);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.);
|
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.);
|
||||||
}
|
}
|
||||||
@@ -248,13 +250,13 @@ mod tests {
|
|||||||
let GPoll::Final(value) = bare.eval(&ctx) else {
|
let GPoll::Final(value) = bare.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
assert_eq!(unsafe { value.rec().element::<f64>() }, 4.);
|
assert_eq!(unsafe { source_layout.rec(&value).element::<f64>() }, 4.);
|
||||||
|
|
||||||
let chain = ShadeNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), &modified);
|
let chain = ShadeNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), &modified);
|
||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = shaded.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 2.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 2.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(shaded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(shaded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||||
}
|
}
|
||||||
@@ -276,13 +278,13 @@ mod tests {
|
|||||||
let GPoll::Final(value) = wide.eval(&ctx) else {
|
let GPoll::Final(value) = wide.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = f64_faded.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 8.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 8.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(f64_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(f64_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||||
|
|
||||||
let narrow = FadeNode::new(
|
let narrow = FadeNode::new(
|
||||||
RecordSourceNode {
|
RecordSourceNode {
|
||||||
frame_bytes: frame_bytes(&u32_source),
|
layout: u32_source.clone(),
|
||||||
element: 7u32,
|
element: 7u32,
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
partial: false,
|
partial: false,
|
||||||
@@ -293,7 +295,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = narrow.eval(&ctx) else {
|
let GPoll::Final(value) = narrow.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = u32_faded.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<u32>() }, 7);
|
assert_eq!(unsafe { rec.element::<u32>() }, 7);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(u32_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
assert_eq!(unsafe { rec.read::<f64>(u32_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
||||||
}
|
}
|
||||||
@@ -313,7 +315,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = node.eval(&ctx) else {
|
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = layout.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 3.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 3.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
||||||
}
|
}
|
||||||
@@ -331,7 +333,7 @@ mod tests {
|
|||||||
|
|
||||||
let chain = MultiplyOpacityNode::new(
|
let chain = MultiplyOpacityNode::new(
|
||||||
RecordSourceNode {
|
RecordSourceNode {
|
||||||
frame_bytes: frame_bytes(&source_layout),
|
layout: source_layout.clone(),
|
||||||
element: 1.,
|
element: 1.,
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
partial: true,
|
partial: true,
|
||||||
@@ -342,7 +344,7 @@ mod tests {
|
|||||||
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a partial record");
|
panic!("expected a partial record");
|
||||||
};
|
};
|
||||||
assert_eq!(unsafe { value.rec().read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { modified.rec(&value).read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -360,7 +362,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = ok.eval(&ctx) else {
|
let GPoll::Final(value) = ok.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
assert_eq!(unsafe { value.rec().read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { modified.rec(&value).read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||||
|
|
||||||
let failing = CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout);
|
let failing = CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout);
|
||||||
let GPoll::Error(error) = failing.eval(&ctx) else {
|
let GPoll::Error(error) = failing.eval(&ctx) else {
|
||||||
@@ -401,7 +403,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = scaled.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 6.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 6.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(scaled.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(scaled.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||||
}
|
}
|
||||||
@@ -426,7 +428,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = relabeled.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
||||||
assert_eq!(unsafe { rec.read::<&str>(relabeled.offset_of(Label::NAME, 0).unwrap()) }, "ab");
|
assert_eq!(unsafe { rec.read::<&str>(relabeled.offset_of(Label::NAME, 0).unwrap()) }, "ab");
|
||||||
}
|
}
|
||||||
@@ -446,7 +448,7 @@ mod tests {
|
|||||||
|
|
||||||
fn f64_record_source(layout: &Layout, element: f64, fields: Vec<(usize, f64)>) -> RecordSourceNode<f64> {
|
fn f64_record_source(layout: &Layout, element: f64, fields: Vec<(usize, f64)>) -> RecordSourceNode<f64> {
|
||||||
RecordSourceNode {
|
RecordSourceNode {
|
||||||
frame_bytes: frame_bytes(layout),
|
layout: layout.clone(),
|
||||||
element,
|
element,
|
||||||
fields,
|
fields,
|
||||||
partial: false,
|
partial: false,
|
||||||
@@ -468,7 +470,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = monitor.eval(&ctx) else {
|
let GPoll::Final(value) = monitor.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
assert_eq!(unsafe { value.rec().element::<f64>() }, 4.);
|
assert_eq!(unsafe { layout.rec(&value).element::<f64>() }, 4.);
|
||||||
}
|
}
|
||||||
|
|
||||||
let capture = Node::<ContextImpl>::serialize(&monitor).unwrap();
|
let capture = Node::<ContextImpl>::serialize(&monitor).unwrap();
|
||||||
@@ -505,7 +507,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = taken(false).eval(&ctx) else {
|
let GPoll::Final(value) = taken(false).eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = union.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 0.5);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 0.);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 0.);
|
||||||
@@ -513,7 +515,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = taken(true).eval(&ctx) else {
|
let GPoll::Final(value) = taken(true).eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = union.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 3.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 3.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 1.);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 1.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 3.);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 3.);
|
||||||
@@ -540,12 +542,39 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = union.rec(&value);
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 0.5);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 0.5);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 0.);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 0.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_records_survive_sibling_evaluations_by_value() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let layout_a = f64_layout(&["opacity"]);
|
||||||
|
let layout_b = f64_layout(&[]);
|
||||||
|
let union = Layout::union(&[&layout_a, &layout_b]);
|
||||||
|
assert!(union.is_inline());
|
||||||
|
reserve_for(&[&layout_a, &layout_b, &union, &union]);
|
||||||
|
|
||||||
|
let chain = HoldFirstNode::new(
|
||||||
|
ValueNode(false),
|
||||||
|
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
||||||
|
RecordSource::new(f64_record_source(&layout_b, 3., vec![]), &layout_b, &union),
|
||||||
|
);
|
||||||
|
|
||||||
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
let rec = union.rec(&value);
|
||||||
|
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
||||||
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("opacity", 0).unwrap()) }, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn identity_layouts_forward_the_record_pointer() {
|
fn identity_layouts_forward_the_record_pointer() {
|
||||||
let arena = Arena::new(1024).unwrap();
|
let arena = Arena::new(1024).unwrap();
|
||||||
@@ -553,7 +582,7 @@ mod tests {
|
|||||||
let scope = scope_fixture(&generations, &arena);
|
let scope = scope_fixture(&generations, &arena);
|
||||||
let ctx = ContextImpl::root(&scope);
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
let layout = f64_layout(&["opacity"]);
|
let layout = f64_layout(&["opacity", "length"]);
|
||||||
reserve_for(&[&layout]);
|
reserve_for(&[&layout]);
|
||||||
let base = stack::push(0);
|
let base = stack::push(0);
|
||||||
stack::pop(base);
|
stack::pop(base);
|
||||||
@@ -567,7 +596,7 @@ mod tests {
|
|||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
};
|
};
|
||||||
let rec = value.rec();
|
let rec = layout.rec(&value);
|
||||||
assert_eq!(rec.ptr(), base.cast_const());
|
assert_eq!(rec.ptr(), base.cast_const());
|
||||||
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
||||||
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of("opacity", 0).unwrap()) }, 0.25);
|
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of("opacity", 0).unwrap()) }, 0.25);
|
||||||
@@ -585,7 +614,7 @@ mod tests {
|
|||||||
|
|
||||||
let chain = ForwardRecordNode::new(RecordSource::new(
|
let chain = ForwardRecordNode::new(RecordSource::new(
|
||||||
RecordSourceNode {
|
RecordSourceNode {
|
||||||
frame_bytes: frame_bytes(&layout),
|
layout: layout.clone(),
|
||||||
element: 4.,
|
element: 4.,
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
partial: true,
|
partial: true,
|
||||||
@@ -597,6 +626,6 @@ mod tests {
|
|||||||
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a partial record");
|
panic!("expected a partial record");
|
||||||
};
|
};
|
||||||
assert_eq!(unsafe { value.rec().element::<f64>() }, 4.);
|
assert_eq!(unsafe { layout.rec(&value).element::<f64>() }, 4.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user