Repository navigation
-mm=own: an importer can build objects of an imported class - #410
Merged
Merged
Conversation
`new H()` of a class from a library calls the library's H..new and then H.constructor through a method bound to the new object: CreateBoundFunction(Cast(h to opaque), ctor), split straight back into GetThis and GetMethod. The inference read the cast as taking h, so every later use was "used after its value was moved". The cast is now a borrow when that is its only use and the object comes out again only as a call's argument - what ThisSymbolRef is for a method of this module. The object is also fresh when the library was built under own: its ..new is listed among the library's __own_no_drops, and the signature pass marks such a call __own_fresh_result, which isFresh reads. A library built under rc lists nothing, and its blocks carry a count its own module holds, so there a borrow under the object still ends at any unknown call. test-runner: in -shared mode -mm= reached only the library, and every `-shared -mm=...` test built its program under gc. It now goes into tslang_opt, like -x86's triple; the 302 -shared tests pass with it. Tests: import_own_class.ts + export_own_class.ts as a -shared pair under own, rc, none and gc, AOT and JIT; own-shared-no-drops.cmake checks the program against the same library built under rc is rejected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI failed test-jit-rc-shared-import-class and test-jit-own-shared-import-class with 0xC0000005. An object of a library's class under rc or own is made in the library and destroyed by the program, and the JIT bound the program's malloc and free to tslang.exe's own. With the prebuilt LLVM CI uses, those are rpmalloc's (LLVMSupport brings it as the process malloc, see scripts/llvm_prebuilt_common.ps1), while the library - like every module tslang builds, and TypeScriptRuntime.dll - allocates with the static release CRT, on the process heap. A block of one freed by the other faults. gc and none never free across the boundary, and a locally built LLVM has no rpmalloc, so only CI saw it. In a release build the JIT now takes malloc, calloc, realloc and free from ucrtbase.dll, the release CRT on the process heap. A debug build keeps its own: a debug CRT puts a header in front of every block, and the libraries it builds link the debug CRT too. Checked by binding the JIT to a private heap: exactly the CI pattern (own and rc fail, none and gc pass); with the fix all four pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s to After the previous commit, CI failed test-jit-rc-corpus-00async-gc-threading and test-jit-none-corpus-00async-gc-threading with 0xC0000374. The coroutine lowering takes a frame from `aligned_alloc` - TypeScriptRuntime.dll's shim, which called that DLL's `malloc` - and gives it back with plain `free`, which the JIT now binds to the process heap. TypeScriptRuntime.dll links LLVMSupport (add_mlir_library), so against the prebuilt LLVM its `malloc` is rpmalloc too. Before, both sides happened to be rpmalloc. Alloc, AlignedAlloc, Free and AlignedFree - the runtime's only exports that hand out memory - now take malloc and free from ucrtbase.dll in a release build, as jit.cpp does; a debug build keeps its own. Checked by putting those four on a private heap of their own, CI's condition: exactly those two tests fail; with the fix both pass. Windows 3253/3253. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 2, 2026
…s, spreads, declaration retains (#449) The catch-all ("takes a second reference; -mm=own cannot prove a move or a borrow here yet") was the first error of 100 corpus files. reportSecondReference now names the shape it is given, from the retained value's root, for all four places that report it: a function returning a borrow of its argument on some paths and another value on others (___unbox<string>, ___cast<A, string>, user code), a parameter returned or kept where its callers cannot learn the fact, a parameter assigned, a local that owns nothing given a value, a value merged from branches, a global's value, a captured variable's value, and an object literal holding a value it owns. Accepted on the way: - a string or an array widened to a union of it and null or undefined, or narrowed back, is a view; - a view of a value whose type owns no block holds none (a union's number payload made into another union), and a retain of it is erased; - a local's declaration retain of constant data is erased wherever the slot still holds its initializer, whatever is stored later (MLIRGen emits it only at the declaration); - a field of constant data is constant data; - an array a spread builds in a local of its own moves into the owning local it is read into; - an interface's `this` bound for a call of a function-typed field is a borrow (#410's shape). Corpus: 402 -> 416 of 592 (415 with --opt), none lost; the catch-all 100 -> 25. Spec section 21. 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.
Fixes the issue found in #409: an importer built under
-mm=owncouldn't create an object of a library's class.Cause
new H()of an imported class calls the library'sH..new, then runsH.constructorthrough a method bound to the new object:CreateBoundFunction(Cast(h to opaque), ctor), split straight back intoGetThis/GetMethodfor the call. The inference read that cast as takingh, so every later use ofhreported "used after its value was moved".Fix
ThisSymbolRefalready is for a method of this module...newis on the library's__own_no_dropslist (-mm=own: a library exports which of its functions destroy nothing #409), so the signature pass marks the call__own_fresh_result, whichisFreshreads.test-runner fix (affects every
-shared -mm=...test)In
-sharedmode,-mm=reached only the library, so every-shared -mm=rc/none/owntest built its program under gc and ran a mixed link. That includes #409's two-sharedtests; only its Windows script test actually checked the facts. The flag now goes intotslang_opt, as-x86's triple does, and all 302-sharedtests pass with it.Tests
own/import_own_class.ts+own/export_own_class.ts, as a-sharedpair under own, rc, none and gc, AOT and JIT. They cover construction with arguments, a field store, method and getter calls, and an imported function, with each object read after a churn.own-shared-no-drops.cmakealso checks that the program is rejected against the same library built under rc.-mm=owncorpus: unchanged at 303.JIT heap fix (CI failure on the first push)
test-jit-rc-shared-import-classandtest-jit-own-shared-import-classcrashed on Windows CI with 0xC0000005. It was a heap mismatch, not the change above:malloc/freeto tslang.exe's own.scripts/llvm_prebuilt_common.ps1).That first fix exposed a second pairing on CI.
test-jit-{rc,none}-corpus-00async-gc-threadingfailed with 0xC0000374:aligned_allocshim, which called that DLL'smalloc.mallocis rpmalloc too.free, which the JIT now binds to the process heap.The runtime's four allocation exports (
Alloc/AlignedAlloc/Free/AlignedFree) now use ucrtbase.dll in release builds as well. Checked by putting them on a private heap of their own, which is CI's condition: exactly those two tests fail, and with the fix both pass.In release builds the JIT now takes
malloc/calloc/realloc/freefrom ucrtbase.dll, the release CRT on the process heap. Debug builds are unchanged. Checked locally by binding the JIT to a private heap: that reproduces the CI pattern exactly (own and rc fail, none and gc pass), and with the fix all four pass. Windows 3253/3253.Known gap, recorded in §15.7, not fixed here
A value an own importer receives from an rc library is destroyed at the end of its owner's scope, because own's release skips only immortal blocks. That was already true of call results, and now applies to
newobjects too. If the library kept a reference of its own, that is a use-after-free, where the mixed-link policy promises a leak.🤖 Generated with Claude Code