instanceof: slot 0 of every class and interface vtable is .instanceOf - #407
Merged
Merged
Conversation
… interface An object reached with no static type - an `any` being unboxed, `instanceof` on an opaque value - is asked what it is through slot 0 of its vtable (mlirGenInstanceOfOpaque), which MLIRGen already said "must be first element in VTable for Cast Any". getVirtualTable put a root class's interface vtables first, so for `class B implements I` slot 0 held I's vtable and `<B>anyValue` called it as a function: a segfault under every memory model. A root class now reserves slot 0 for .instanceOf ahead of its interfaces, and a derived class overrides it in place. This changes the vtable layout of every root class that implements an interface, so a prebuilt default library (its StringIterator implements ClassIterator) has to be rebuilt; DefaultLib rebuilt with this compiler passes 157/157 release compile and jit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…header OwnedReturnConsumptionPass decides whether a call through an interface hands back a +1 by reading slot `index` of every vtable global for that interface. The op's index is the member's; the global now has the .instanceOf header in front, so the lookup read the member before it. Member 0 asked .instanceOf, which returns no heap value, and a call to it kept its reference: a loop of `i.make()` grew from 4.6 MB to 10.8 MB under rc. Other members were right only by coincidence of their neighbour's classification. The lookup adds INTERFACE_VTABLE_HEADER_SLOTS. A new rc test checks that no interface call returning a class is left without __owned_result; a generator in it keeps the whole-module shortcut from hiding the lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 29, 2026
* rc: a getter read is settled like a call A getter retains what it returns, as every function does (§9.24), but OwnedReturnConsumptionPass only looked at ts.CallIndirect. A getter read is one of the accessor ops (ts.Accessor, ThisAccessor, ThisIndirect*, BoundIndirect*) until the affine lowering turns it into a call, so its +1 was never taken over or given back: `h.cc.x` leaked the C, and `let b = h.cc` retained a second time. own_getter_borrow read 12.6 MB under rc; it now reads 4.8 MB, as gc's 6.5 and own's 4.8 do. The accessor ops are classified as calls are - the getter named outright, every method of that member name for a virtual slot, the vtables for an interface slot, or the whole-module answer - and then marked, consumed by their receiver, or released at the end of the block. A getter read with no live use is left alone: an assignment through an accessor builds a read before rebuilding the op as a setter, and a release would keep that read, a call the program never made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Spec 15.3: rc's getter-temporary leak is fixed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Spec 15.7: the getter-temporary leak is no longer a known limit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Spec 15.7: the <B>anyValue segfault was fixed by #407 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 29, 2026
…helper #407 included MLIRRTTIHelperVC.h after MLIRGenImpl.h, and the helper undefines DEBUG_TYPE on the way out, so every LLVM_DEBUG in the file was an error in a Debug build (Release compiles LLVM_DEBUG away, so CI never saw it). MLIRGenStatements.cpp does the same after the same include. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 30, 2026
…411) * A type alias that contains itself is an error, not a stack overflow `type Node = [value: s32, next: Reference<Node>]`, `{ value: number, next: Node }`, mutual aliases, a generic `List<T>` inside itself: every mention of an alias expanded its declaration again, and one that names itself did so until the stack ran out - exit 0xC00000FD, no message. An alias being expanded is now remembered (expandTypeAlias, used by the plain and both generic resolutions), and one that meets itself is reported: "type alias 'Node' circularly references itself; use an interface or a class for a recursive type". A type here has a fixed shape - a tuple holds its fields in place - so none can contain itself, not even behind Reference<T>, whose tuple type would have to name itself; an interface or a class is the recursive type. The errors that only follow from it are left out: those raised while the expansion unwinds (a scoped handler swallows them until the outermost expansion of that alias ends), and "can't find type" / "can't resolve name" / "generic type ... can't be found" at later mentions of it. An exported alias is registered before it is resolved, so it is found - and found circular - the same way. Tests: nine shapes in type-alias-circular/ (tuple through Reference, object, optional field, declared function parameter, mutual, exported, generic, union through an array, in a namespace), each passing only on the message and failing on the follow-on ones; and positive.ts, aliases naming other aliases and themselves one after another, JIT and AOT. Fixes #370. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Debug build: MLIRGenCast.cpp defines DEBUG_TYPE again after the RTTI helper #407 included MLIRRTTIHelperVC.h after MLIRGenImpl.h, and the helper undefines DEBUG_TYPE on the way out, so every LLVM_DEBUG in the file was an error in a Debug build (Release compiles LLVM_DEBUG away, so CI never saw it). MLIRGenStatements.cpp does the same after the same include. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 30, 2026
…ed once; Debug DEBUG_TYPE fix (#412) * Debug build: MLIRGenCast.cpp defines DEBUG_TYPE again after the RTTI helper #407 included MLIRRTTIHelperVC.h after MLIRGenImpl.h, and the helper undefines DEBUG_TYPE on the way out, so every LLVM_DEBUG in the file was an error in a Debug build (Release compiles LLVM_DEBUG away, so CI never saw it). MLIRGenStatements.cpp does the same after the same include. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A function's attributes name each attribute once `@dllname("strlen") @Linkname("strlen")` both become DLL_NAME, and processFunctionAttributes pushed it once per decorator. Two entries of one name are an assert in a Debug build ("DictionaryAttr element names must be unique", test-{compile,jit}-declare-linkname-shared-symbol) and print twice in Release. checkLinkNameDecorators has already made sure they agree, so the first stands; the same holds for any decorator written twice. New test: --emit=mlir of declare_linkname_shared_symbol.ts fails on a repeated dllname. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Implement process-heap allocator for Windows modules and integrate with MLIR passes --------- 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.
Asking what an object is (
instanceof, casting down to a class, unboxing anany) reads.instanceOffrom slot 0 of the vtable. That slot was not always there.1. A class that implements an interface
A root class put its interface vtable pointers first, so slot 0 held an interface pointer and not
.instanceOf. Unboxing such an object fromany(<B>anyValue) called through the wrong pointer and crashed under every memory model. A root class now reserves slot 0 for.instanceOfbefore its interfaces.2. An interface value
Every interface vtable now starts with a header slot: slot 0 is the implementer's
.instanceOf. An object literal has no class to ask, so it gets a generated.instanceOf.nonethat returnsfalse.Members keep their logical indexes. Only the vtable builders and
InterfaceSymbolReflowering addINTERFACE_VTABLE_HEADER_SLOTS(1), so the index arithmetic forextendsis unchanged.i instanceof Bon an interface was decided at compile time and was alwaysfalse. It now asks the vtable.<B>i,i as B, and narrowing byinstanceofcrashed the compiler with an index assert incastFieldsToClass. They now return the very object when it is aB. When it is not aB, the cast still builds a newBfrom the interface's fields, but only if every field ofBis in the interface. Otherwise it throwsCan't cast from interface.<B>a, whereais ananyholding an interface, threwCan't cast from any type.___unboxnow has an interface branch. Ananyholding an object literal throws a catchable cast error.own:
borrowedParamsees through the interface cast andExtractInterfaceThis, andisInstanceOfSlotaccepts the interface vtable form. Interface values themselves stay rejected under-mm=own, as before.rc:
OwnedReturnConsumptionPassreads an interface call's possible implementations from the vtable globals, and now skips the header slot too. Reading at the member's own index looked at the member before it:i.make()(member 0) asked.instanceOfand leaked every result (4.6 → 10.8 MB). New test:own/rc_interface_call_owned_result.ts(JIT, AOT, and an IR check that every interface call returning a class carries__owned_result).ABI
Both vtable layouts change. A prebuilt DefaultLib must be rebuilt with this compiler; its
StringIteratorimplementsClassIterator.Tests
00any_unbox_interface_class.tsand00interface_instanceof.ts, each compile + JIT.-mm=owncorpus: unchanged at 303 compiling files.🤖 Generated with Claude Code