fix(interp): harden heap ownership and cycle collection#143
Conversation
📝 WalkthroughWalkthroughThe interpreter now uses exact reference counting with trial-deletion cycle collection. Heap ownership, reset behavior, marshaling cleanup, cloned state, host-integration guidance, architecture documentation, and liveness tests were updated accordingly. ChangesMemory lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Interpreter
participant TraceableRefs
participant Heap
Interpreter->>Heap: copy exact rc into trial
Interpreter->>TraceableRefs: read heap-to-heap Refs edges
TraceableRefs-->>Interpreter: return reference edges
Interpreter->>Heap: mark externally reachable objects
Interpreter->>Heap: sweep unreachable cycles
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #143 +/- ##
==========================================
+ Coverage 26.98% 27.13% +0.14%
==========================================
Files 83 83
Lines 59196 59275 +79
==========================================
+ Hits 15974 16082 +108
+ Misses 41999 41979 -20
+ Partials 1223 1214 -9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
interp/interp.go (1)
634-656: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftRetain nested refs when
Storealiases a heap object
StoreturnsBoxRef(ref)intoi.heap[ref], so writing a traced heap value into another slot adds a second owner without increasing the children’s refcounts. That can drop shared children to zero too early. Retain the aliased value’s refs before assigning it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@interp/interp.go` around lines 634 - 656, Update Store’s types.Boxed KindRef handling so that when val aliases i.heap[ref], it retains all references owned by the aliased heap value before assigning it to i.heap[addr]. Preserve the existing validity checks, self-assignment fast path, and cleanup of the old value; use the existing reference-retention mechanism rather than manually adjusting child counts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@interp/interp_test.go`:
- Around line 3224-3237: Add a subtest alongside “ignores same-address
reference” that allocates separate live values, stores BoxRef(otherAddr) at
addr, and asserts the expected child-reference ownership and finalization counts
after releasing or closing the relevant addresses. Use the existing
trackedValue, Alloc, Store, Load, and cleanup patterns to verify the
different-address aliasing path in Store.
---
Outside diff comments:
In `@interp/interp.go`:
- Around line 634-656: Update Store’s types.Boxed KindRef handling so that when
val aliases i.heap[ref], it retains all references owned by the aliased heap
value before assigning it to i.heap[addr]. Preserve the existing validity
checks, self-assignment fast path, and cleanup of the old value; use the
existing reference-retention mechanism rather than manually adjusting child
counts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b19ba8fa-904b-42c9-9bcc-f5c05e151615
📒 Files selected for processing (7)
docs/architecture.mddocs/host-integration.mddocs/memory-model.mdinterp/interp.gointerp/interp_test.gointerp/marshal.gointerp/trace.go
There was a problem hiding this comment.
🧹 Nitpick comments (1)
interp/interp_test.go (1)
3693-3731: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the pacing constants driving these GC-timing assertions.
The thresholds (
capacity - 2, the 62-alloc pre-fill, and the65th alloc that flipscycle.closedfrom0→1) are derived from the internalheapRunway/pace()schedule but carry no explanation. Any future tweak to the adaptive goal will make these assertions fail with no hint at why the numbers were chosen. A short comment tying the counts to the goal/runway math would keep the intent legible. The same undocumented-constant pattern repeats in the sibling subtests ("paces from live set" Lines 3770-3776 and "resets adaptive goal" Line 3805).Setup is intentionally inlined per subtest, which matches the test-style guideline — this is only about annotating the magic numbers, not restructuring.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@interp/interp_test.go` around lines 3693 - 3731, Add concise comments in the adaptive-GC timing assertions, including the “collects cycles at adaptive goal” and sibling subtests “paces from live set” and “resets adaptive goal.” Explain how capacity - 2, the 62-allocation prefill, and the final 65th allocation derive from the heapRunway/pace() schedule and adaptive-goal transitions, without restructuring the inlined setup.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@interp/interp_test.go`:
- Around line 3693-3731: Add concise comments in the adaptive-GC timing
assertions, including the “collects cycles at adaptive goal” and sibling
subtests “paces from live set” and “resets adaptive goal.” Explain how capacity
- 2, the 62-allocation prefill, and the final 65th allocation derive from the
heapRunway/pace() schedule and adaptive-goal transitions, without restructuring
the inlined setup.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c2432c92-1449-4446-a3ce-32097fbd566e
📒 Files selected for processing (3)
docs/memory-model.mdinterp/interp.gointerp/interp_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/memory-model.md
- interp/interp.go
Changes Made
Alloc,Push,SetGlobal,SetLocal, andStore, including validated refs, unique concrete-pointer ownership, same-value no-ops, and deterministic finalizationResetRelated Issues
Additional Information
recountnow rebuilds exact baseline counts after construction and reset, and the collector rejects negative residual counts before marking.Storecould shallow-alias another heap slot, duplicate finalizers, and undercount nested refs; different-address refs and already-owned concrete pointers are now rejected, whileAlloc(existingRef)remains the explicit sharing path.SetGlobalandSetLocalnow reject invalid heap refs before mutating or releasing the existing slot.make lintgo test -count=1 -race ./...Summary by CodeRabbit
Documentation
Reset/Close.Bug Fixes
SetGlobal,SetLocal,Store, andAlloc, including self-assignment no-ops and proper release/finalization on replacement.Resetbehavior for more reliable reclamation.Tests
Resetfinalization, ownership transfer, and multiple cycle-collection regression scenarios.