Skip to content

rc: a kept bound method counts the object it is bound to - #640

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-bound-method-count
Oct 22, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-rc-bound-method-count

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Under rc, let v = mkv(1); let m = v.get; v = mkv(2); m() ran on a freed object and printed 1003/1004, where gc prints 3. A bound method kept in a field (h.f = v.get) or in an element ([v.get]) failed the same way. Receivers already retain and release bound values through the tag at CLOSURE_TYPE_INDEX, but a bound method carried a null tag there, so keeping it counted nothing.

Fix (LowerToLLVM)

  • Under rc, a method bound to a class object now gets that class's type tag, the same way an interface over the object gets its tag at INTERFACE_TYPE_INDEX.
  • A bound value is still born with no count. Each receiver that keeps it (RetainSlot / field / element) counts the object, and its release gives that count back.
  • ThisSymbolRef and ThisVirtualSymbolRef cast this to an opaque pointer before building the CreateBoundFunction. So they record the object's type on it in a new attribute, BOUND_OBJECT_TYPE_ATTR_NAME (Defines.h).
  • own, gc and none keep the null tag. Under own a second reference to the object is already rejected.

Still open: const m = h.v.get, a const bound method over a field read, is folded and holds nothing. That's the const-folding side and is not changed here. #637 covers the const over a local.

Tests

  • tests/00bound_method_count.ts (default + rc/none corpus): a bound method in a let, in a field, in an element.
  • Teeth: with the new tag switched off, each case fails under rc (AOT).

Results

  • Windows: ctest -C Release, 4886/4886 passed
  • WSL (Linux, where the heap catches double frees): 4868/4868 passed
  • DefaultLib was not rebuilt under rc against this change.

🤖 Generated with Claude Code

`let m = v.get; v = mkv(2); m()` ran on a freed object under rc, and so
did a bound method kept in a field or an element. A bound method's value
carried a null type tag at CLOSURE_TYPE_INDEX, so its receivers' retains
and releases did nothing for `this`.

Under rc the lowering now gives a method bound to a class object that
class's tag, as an interface over the object gets its tag: a bound value
is born with no count, and each receiver that keeps it counts the object.
ThisSymbolRef / ThisVirtualSymbolRef cast `this` to an opaque pointer, so
they record the object's type on the CreateBoundFunction they build
(BOUND_OBJECT_TYPE_ATTR_NAME). own, gc and none keep the null tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77

Copy link
Copy Markdown
Owner Author

Two follow-up notes from checks after opening:

  • Checked: a bound method folded into a const and returned (pick() { const m = this.get; return m; }, keep() { const v = new V(1); const m = v.get; return m; }), called 100 times with churn: correct under rc on Windows (AOT, JIT, --opt) and on Linux with glibc's heap (AOT and JIT). No over-release.
  • Behaviour change: an object that stores a method bound to itself in its own field (this.f = this.m) is now a reference cycle under rc. It leaks instead of being freed, which is the usual cost of counting. The memory benchmark could see it.

@ASDAlexander77
ASDAlexander77 merged commit 5f5a2ec into main Oct 22, 2026
2 of 3 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-rc-bound-method-count branch October 22, 2026 17:14
ASDAlexander77 added a commit that referenced this pull request Oct 22, 2026
…count (#642)

`const m = h.v.get` and `const i: I = h.v` were folded into the view
itself, holding no reference of their own, so `h.v = d` freed the object
`m()` and `i.get()` then ran on (a crash under rc; `let m = h.v.get` was
fine). #637 gave such a const storage when its object is read out of a
local's slot, and #640 made a kept bound method count its object; a view
over a field read was left out. Under rc it now gets storage like a `let`,
as a const read out of a field already does (#635). Under own the folded
read stays a borrow.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant