Repository navigation
An interface is { vtable, this }: the type tag is the vtable's first slot - #668
Merged
Merged
Conversation
…slot
An interface value was { vtable, this, type }: the third word was the
runtime type tag of what `this` points at, which rc and own release
and retain the object through. Every interface vtable is made for one
concrete type - a class's (a subclass has its own), an object
literal's - so the tag now lives in the vtable instead, and the value
is two pointers again (16 bytes, not 24).
The interface vtable header is { type tag, `.instanceOf` }
(INTERFACE_VTABLE_TYPE_SLOT 0, INTERFACE_VTABLE_INSTANCEOF_SLOT 1,
INTERFACE_VTABLE_HEADER_SLOTS 2); members follow it as before. A
class's own vtable still starts with its `.instanceOf`
(CLASS_VTABLE_INSTANCEOF_SLOT), so `instanceof` names the slot of the
vtable it reads.
- The class, object-literal and null vtable builders put the tag in
(mlirGenInterfaceVTableTypeTag): the type's descriptor, or null when
the type owns no heap memory, and in a null vtable.
- NewInterface no longer makes a tag. releaseViaInterfaceTag reads it
through the vtable (a zeroed slot has no vtable, a null interface a
null tag: both release nothing); a method bound through an interface
takes the tag from the same vtable.
- A cast to an interface the value's type does not extend (#666) keeps
the header: the tag is the value's, whatever it is seen through.
- The own signature pass reads interface vtables past the two-slot
header.
- -mm=own: a record box that borrows its fields (spec 32) is released
by a routine of its own, which the tag must name. Its vtable is the
object literal's, shared by every record of its shape, so the
signature pass gives such a box a copy of that vtable
(`<vtable>.r<i>...vtbl`) whose descriptor names the borrowing
routine (giveBorrowingRecordsTheirVTables).
DefaultLib needs no source change; it is rebuilt by the new compiler.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.
An interface value was
{ vtable, this, type }: the third word was the runtime type tag of whatthispoints at, which-mm=rcand-mm=ownrelease and retain the object through. Every interface vtable is made for one concrete type (a class's, with a subclass getting its own, or an object literal's), so the tag now lives in the vtable. The value is two pointers again: 16 bytes, not 24.Layout
{ type tag, .instanceOf }, and members follow it as before:INTERFACE_VTABLE_TYPE_SLOT= 0INTERFACE_VTABLE_INSTANCEOF_SLOT= 1INTERFACE_VTABLE_HEADER_SLOTS= 2.instanceOf(CLASS_VTABLE_INSTANCEOF_SLOT).instanceofnow names the slot of the vtable it reads.mlirGenInterfaceVTableTypeTag). It is the type's descriptor, or null when the type owns no heap memory. It is always null in an interface's null vtable (A null class object, null or undefined given to an interface is a null interface whose methods throw #665).Example (rc):
Changes
ts.NewInterfaceno longer makes a tag.releaseViaInterfaceTagreads the tag through the vtable. A zeroed slot has no vtable and a null interface has a null tag; both release nothing.-mm=ownborrowing records (spec 32):<vtable>.r<i>...vtbl, whose descriptor names the borrowing routine (giveBorrowingRecordsTheirVTables).DefaultLib
No source change is needed: none of its C++ reads an interface's layout. I rebuilt it locally with this compiler for gc, rc, none and own (release), and ran its
tests.ps1(compile + JIT):weakref_basicis gc-only, skipped)weakref_basicis gc-only, skipped)All 105 own failures are compile-time refusals ("-mm=own cannot prove..."). The pre-#665 compiler in the main checkout gives the same first error on every one, so the change didn't cause them. There are no run errors.
Tests
00interface_type_tag_in_vtable.ts, registered as compile + JIT, plus the rc/none corpus. It covers a class, a subclass, an object literal, a cast to an interface the value's type doesn't extend, a copy whose original is given null, and dropping a null interface. Each object is reached only through interfaces, andchurn()reuses freed blocks before the field reads.assertion failed: a class object's field).ctest -C Release: 5408/5408 passed.Not in this PR
let n: J | null = iwherei: I | nullandIdoes not extendJis a plain copy:I's vtable is used asJ's with no remap, son.idreads a method slot as a field offset and crashes. It fails the same way before this change. It is theI | nullsibling of #665'sC | null→I | nulland needs its own fix.🤖 Generated with Claude Code