Assert the deep element glue is registered before the executor evaluates

This commit is contained in:
Dennis Kobert
2026-09-08 13:15:06 +00:00
parent 49daf2895e
commit 5e388cb38f
4 changed files with 29 additions and 3 deletions

View File

@@ -33,6 +33,20 @@ fn over_budget(used: usize, budget: usize) -> bool {
used >= budget / 8 * 7
}
/// The deep element glue is filled by runtime registration: ctors at load
/// natively, host-invoked exports on wasm. A lifetime-carrying element whose
/// glue never registered takes the shallow clone-out path, which brands its
/// arena borrows `'static` for a memo to hold across a reset, so a missed
/// registration is caught here rather than at the first capture.
fn assert_deep_element_glue() {
for (name, registered) in [
("graphic", core_types::record::has_deep_element_glue(std::any::TypeId::of::<graphic_types::Graphic<'static>>())),
("artboard", core_types::record::has_deep_element_glue(std::any::TypeId::of::<graphic_types::Artboard<'static>>())),
] {
assert!(registered, "deep element glue for `{name}` is not registered; the host must run `__node_registry_deep_element_{name}` before evaluating");
}
}
fn new_arena(capacity: usize) -> Arena {
Arena::new(capacity).unwrap_or_else(|| {
log::error!("arena generations exhausted; continuing without frame caching");
@@ -66,6 +80,7 @@ fn noop_runtime() -> Arc<DynGraphRuntime> {
impl Default for DynamicExecutor {
fn default() -> Self {
assert_deep_element_glue();
Self {
output: Default::default(),
tree: Default::default(),
@@ -96,6 +111,7 @@ pub struct ResolvedDocumentNodeTypesDelta {
impl DynamicExecutor {
pub fn new(mut proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {
assert_deep_element_glue();
let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
typing_context.update(&mut proto_network)?;
let output = proto_network.output;

View File

@@ -515,8 +515,11 @@ where
if let Some(deep) = deep_element_glue(std::any::TypeId::of::<T::Static>()) {
return unsafe { (deep.clone_out)(ptr) };
}
// SAFETY: a lifetime-carrying element type registers deep glue, so
// this shallow path only erases borrow-free values.
// SAFETY: a lifetime-carrying element type registers deep glue, so this
// shallow path only erases borrow-free values. The registration is a
// whole-program convention rather than something this call can check:
// the executor asserts the in-tree types registered before it evaluates,
// which is where a missed wasm registration export is caught.
Box::new(unsafe { erase_static(read_element::<T>(Rec::new(ptr))) })
}
unsafe fn repark<T: Clone + Send + Sync + dyn_any::StaticTypeSized>(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &crate::arena::Arena) -> Option<()>

View File

@@ -28,7 +28,7 @@ pub use layout::{
ElToken, ElementSpec, ElementWrite, ElementWritePick, ElementWritePickHashed, ElementWritePickPlain, FieldDesc, FieldOffset, FieldWrite, InputReads, Layout, LayoutMeta, RecordLayout, copy_plan,
element_dims, element_parked, element_write, element_write_hashed, empty_layout,
};
pub use owned::{OwnedRecord, deepen_field_value, register_deep_element_clone, register_deep_field_value, replay_field_value};
pub use owned::{OwnedRecord, deepen_field_value, has_deep_element_glue, register_deep_element_clone, register_deep_field_value, replay_field_value};
pub use promote::{Promotion, assert_promoted, register_element_promote, register_field_promote, register_retained_heap};
pub use route::{RecordSource, SourcePlan};
pub use run::{Group, GroupItem, RunBuilder, RunColumn, RunView};

View File

@@ -32,6 +32,13 @@ pub(in crate::record) fn deep_element_glue(type_id: std::any::TypeId) -> Option<
DEEP_ELEMENT_CLONES.lock().unwrap().get(&type_id).copied()
}
/// Whether elements of a type registered deep glue. The shallow clone path is
/// only sound for types that did not need to, so a host that drives the
/// registration itself checks the types it owes before it evaluates anything.
pub fn has_deep_element_glue(type_id: std::any::TypeId) -> bool {
DEEP_ELEMENT_CLONES.lock().unwrap().contains_key(&type_id)
}
/// Deep-copy overrides for field values whose content borrows the
/// evaluation's arena (a graphic list holding native groups), keyed by the
/// field's owned value form. Consulted at the persistence seams only: