diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index f75c7f28a3..2d43e9b937 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -2510,13 +2510,25 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn type Output = #trait_output; fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll { + // The exit trace rides a guard so early returns report too, which + // is what pins a frame leak to its node. #[cfg(debug_assertions)] - { + let __sp_trace = { static __SP_TRACE: ::std::sync::OnceLock = ::std::sync::OnceLock::new(); - if *__SP_TRACE.get_or_init(|| ::std::env::var_os("GRAPHENE_SP_DEBUG").is_some()) { - ::std::eprintln!("node> {} enter sp {}", ::std::stringify!(#fn_name), #core_types::record::stack::sp()); + struct __SpTrace(&'static str, usize); + impl ::core::ops::Drop for __SpTrace { + fn drop(&mut self) { + ::std::eprintln!("node> {} exit sp {} -> {}", self.0, self.1, #core_types::record::stack::sp()); + } } - } + match *__SP_TRACE.get_or_init(|| ::std::env::var_os("GRAPHENE_SP_DEBUG").is_some()) { + true => { + ::std::eprintln!("node> {} enter sp {}", ::std::stringify!(#fn_name), #core_types::record::stack::sp()); + Some(__SpTrace(::std::stringify!(#fn_name), #core_types::record::stack::sp())) + } + false => None, + } + }; let _entry_sp = #core_types::record::stack::sp(); let __cell = #cell_constructor; #reclaim_guard diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index a36eabf2b2..034527b60b 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -35,6 +35,7 @@ fn memoize<'e>( #[data] cache: Arc>>, content: impl Node, Output = RecordValue<'e>>, ) -> GPoll> { + let entry_sp = core_types::record::stack::sp(); // A scalar wire's value may depend on the consuming lane (index readers), // so only a leveled wire, whose level covers every lane by construction, // keys with the lane normalized away. @@ -99,12 +100,16 @@ fn memoize<'e>( *cache.lock().unwrap() = Some(entry); result } + // A valueless materialization caches nothing, so the frames it left + // behind have no reader and must not be counted against this node. LevelStatus::Pending => { - claim_frame(content.layout()); + // SAFETY: nothing borrows the frames above the entry mark. + unsafe { core_types::record::interrupt_frame(entry_sp, content.layout()) }; GPoll::Pending } LevelStatus::Error(error) => { - claim_frame(content.layout()); + // SAFETY: nothing borrows the frames above the entry mark. + unsafe { core_types::record::interrupt_frame(entry_sp, content.layout()) }; GPoll::Error(Box::new(error)) } };