WIP: DESTROY support for blessed objects#450
Closed
fglock wants to merge 1 commit into
Closed
Conversation
Call DESTROY on blessed objects when:
- delete $hash{key} removes a blessed reference (RuntimeHash)
- undef $var explicitly discards a blessed reference (RuntimeScalar)
- Scope exit in loop bodies (existing scopeExitCleanup path)
Add destroyCalled flag on RuntimeBase to prevent double-DESTROY.
Exceptions in DESTROY are caught and warned "(in cleanup)".
Note: Without reference counting, DESTROY may fire early if other
references to the object still exist. A future reference-counting
implementation will make scope-exit DESTROY safe for all contexts.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2 tasks
fglock
added a commit
that referenced
this pull request
Apr 8, 2026
Add comprehensive design document for implementing DESTROY support for blessed objects and weaken/isweak/unweaken for weak references. Key design decisions: - Targeted reference counting on RuntimeBase (only blessed objects with DESTROY, not all objects) - Three-state refCount field: 0=untracked, >0=tracked, MIN_VALUE=destroyed (eliminates need for separate destroyCalled boolean) - Zero overhead for hot path (non-reference assignments) - GC safety net via Cleaner sentinel pattern for escaped references - External WeakRefRegistry (no per-scalar memory cost) Incorporates lessons from PR #450 (eager DESTROY without refcounting caused 20+ test failures and the DestroyManager attempt (proxy reconstruction fundamentally broken). Supersedes dev/design/object_lifecycle.md. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> EOF )
2 tasks
fglock
added a commit
that referenced
this pull request
Apr 8, 2026
Add comprehensive design document for implementing DESTROY support for blessed objects and weaken/isweak/unweaken for weak references. Key design decisions: - Targeted reference counting on RuntimeBase (only blessed objects with DESTROY, not all objects) - Three-state refCount field: 0=untracked, >0=tracked, MIN_VALUE=destroyed (eliminates need for separate destroyCalled boolean) - Zero overhead for hot path (non-reference assignments) - GC safety net via Cleaner sentinel pattern for escaped references - External WeakRefRegistry (no per-scalar memory cost) Incorporates lessons from PR #450 (eager DESTROY without refcounting caused 20+ test failures and the DestroyManager attempt (proxy reconstruction fundamentally broken). Supersedes dev/design/object_lifecycle.md. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> EOF )
fglock
added a commit
that referenced
this pull request
Apr 8, 2026
* design: DESTROY and weaken() implementation plan Add comprehensive design document for implementing DESTROY support for blessed objects and weaken/isweak/unweaken for weak references. Key design decisions: - Targeted reference counting on RuntimeBase (only blessed objects with DESTROY, not all objects) - Three-state refCount field: 0=untracked, >0=tracked, MIN_VALUE=destroyed (eliminates need for separate destroyCalled boolean) - Zero overhead for hot path (non-reference assignments) - GC safety net via Cleaner sentinel pattern for escaped references - External WeakRefRegistry (no per-scalar memory cost) Incorporates lessons from PR #450 (eager DESTROY without refcounting caused 20+ test failures and the DestroyManager attempt (proxy reconstruction fundamentally broken). Supersedes dev/design/object_lifecycle.md. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> EOF ) * design: update destroy_weaken_plan.md to v3.0 Updated the design document with findings from bytecode trace analysis: - Revised refCount encoding: four-state (-1=untracked, 0=tracked/zero containers, >0=N containers, MIN_VALUE=destroyed) instead of three-state - Changed bless-time initialization from refCount=1 to refCount=0, since the bless-time RuntimeScalar is a temporary that travels through the return chain without being explicitly cleaned up - Fixed DESTROY ordering: save old referent BEFORE assignment, decrement AFTER assignment (Perl 5 semantics: DESTROY sees new variable state) - Added Section 4A documenting bytecode trace findings (return chain, scope exit, overcounting analysis) - Updated all sections (6-18) for consistency with v3.0 encoding - Clarified Phase numbering (Cleaner is Phase 4, not Phase 5) - Cleaned up default field initialization documentation Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: add DESTROY/weaken sandbox tests (196 tests, 8 files) Comprehensive test suite covering all special cases from the design doc. All tests validated against system Perl 5.42. DESTROY tests: - destroy_basic.t (18): scope exit, undef, overwrite, hash delete, multiple refs, blessed array/scalar refs - destroy_edge_cases.t (22): resurrection, re-bless, exception-in-DESTROY, nested DESTROY, local(), overwrite ordering - destroy_return.t (24): single/two/three-boundary returns, implicit return, list context, void context, method chaining - destroy_collections.t (22): array clear/pop/shift/splice, hash clear, nested structures, shared refs, closures - destroy_inheritance.t (10): parent/child/grandparent DESTROY, SUPER, AUTOLOAD fallback, C3 MRO, dynamic @isa weaken tests: - weaken_basic.t (34): isweak, weaken, unweaken, copy-is-strong, different ref types, double weaken, multiple weak refs - weaken_destroy.t (24): DESTROY + weak ref interaction, circular refs, self-ref, tree back-pointers, weak-only ref - weaken_edge_cases.t (42): error on non-ref, overwrite, re-bless, nested structures, resurrection, unweaken restore Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * design: update destroy_weaken_plan.md to v4.0 Review fixes: Cleaner sentinel reachability, WeakRefRegistry pinning, missing refcount hooks, VarHandle CAS for thread safety. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * design: update destroy_weaken_plan.md to v5.1 Replaced Cleaner sentinel pattern with stash-walking at shutdown to avoid pinning objects in memory. Global destruction matches Perl 5 semantics for circular references and missed decrements. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * design: update destroy_weaken_plan.md to v5.2 with review corrections Key changes based on runtime code review: - Fix dynamicRestoreState: don't increment restored value (prevents permanent +1 overcounting that would block DESTROY for local'd globals - Add MortalList defer-decrement mechanism (§6.2A) for delete/pop/shift/splice return values — equivalent to Perl 5's FREETMPS mortal system - Add interpreter scope-exit cleanup (§6.5) with SCOPE_EXIT_CLEANUP opcode (interpreter backend had no equivalent of emitScopeExitNullStores) - Fix pop/shift docs: they return raw elements, not copies - Fix splice location: Operator.java, not RuntimeArray.java - Defer WeakReferenceWrapper to Phase 5 (all bundled module uses are blessed) - Update Phase 2 into 2a/2b/2c, expand test plan and file list Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> EOF ) * design: v5.3 — simplify MortalList scope based on blocked-module survey Key changes: - Scope initial MortalList to RuntimeHash.delete() only. Survey of all blocked modules (POE, DBIx::Class, Moo, Template Toolkit, Log4perl, Data::Printer, Test::Deep) found no real-world pattern needing deterministic DESTROY from pop/shift/splice of blessed objects. - Add MortalList.active boolean gate — false until first bless() into a class with DESTROY. Zero cost for programs without DESTROY. - Defer RuntimeArray.pop/shift and Operator.splice hooks to Phase 5. - Update Phase 2b, Phase 5, test plan, risks, and file list. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add DESTROY support for blessed objects. When a blessed reference is discarded, its DESTROY method is called if one exists in the class hierarchy.
Triggers
undef $obj- explicitly discards a blessed referencedelete $hash{key}- removes a blessed reference from a hashscopeExitCleanuppath now also fires DESTROYImplementation
RuntimeBase.java: AddeddestroyCalledboolean flag to prevent double-DESTROYRuntimeScalar.java: AddedcallDestroyIfNeeded()static method that finds DESTROY viaInheritanceResolver.findMethodInHierarchy(), calls it in void context, and catches exceptions as "(in cleanup)" warningsRuntimeHash.java: CallscallDestroyIfNeeded()on values removed bydelete()EmitStatement.java: Updated comments to reflect DESTROY in scope-exit cleanupLimitations
Without reference counting, DESTROY may fire early if other references to the object still exist. The
destroyCalledflag prevents double-DESTROY but does not solve the fundamental problem.Scope-exit DESTROY is currently limited to loop bodies only. Extending it to all scopes (subroutines, eval blocks, if/else) requires reference counting to avoid destroying objects that are still referenced elsewhere. This was tested and caused 20+ unit test failures.
Future work
Targeted reference counting on blessed objects would allow safe scope-exit DESTROY for all contexts. Key points:
refCountfield toRuntimeBase, increment on copy/bless, decrement on overwrite/scope-exitsetLarge())Test plan
makepassesGenerated with Devin