One garbage collector per process: linkage choice, import checks, Linux, default-library tests - #310
Merged
Merged
Conversation
A user's shared library and the program that imports it still linked the static gc.lib unless --gc-lib-path was swapped by hand, so they got two collectors and one freed what the other held. Now --emit=dll, and --emit=exe for a program that imports a tslang shared library (CompileOptions::importsSharedLibrary, found as LoadLibraryPermanentlyOp before lowering), link gc.dll's import library and copy gc.dll beside the output. The shared collector is found through --gc-shared-lib-path, GC_SHARED_LIB_PATH, <gc-lib-path>/gcdll (the release package), or --gc-lib-path itself when it is a shared build; with none found the build stops instead of linking a collector of its own. A lone program keeps the static gc.lib. Linux unchanged. test-compile-gc-shared-auto drives tslang --emit=dll/exe itself (five cases). With the previous compiler a static-only library + importer fail the gc_single_collector assertion. Suite: 2,705 of 2,705. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… gc-* The previous commit registered test-compile-gc-shared-auto but its script, gc-shared-auto.cmake, matched the root .gitignore's gc-* (meant for the gc-8.x source folders) and was never added. Renamed to shared-collector-auto.cmake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s and enforce linkage rules
This was referenced Sep 13, 2026
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.
Summary
Steps 3, 4, 5 and 6 of
tslang/docs/single-gc-collector-design.md, plus Linux and the CMake template. The rule: one garbage collector per process. With two, one collector frees strings the other still holds, and the program reads wrong values without crashing.Step 3:
tslangpicks how to link the collector (Windows)After #309, the runtime and the default-library DLL take the collector from
gc.dll. A user's own shared library and the program that imports it still used the staticgc.libunless the user pointed--gc-lib-pathsomewhere else by hand.Under
-mm=gcon Windows:--emit=dll--emit=exefor a program that imports a tslang shared library.tslangtracks this inCompileOptions::importsSharedLibrary, set by findingLoadLibraryPermanentlyOpin the generated module before the passes lower it away.gc.lib:--emit=exefor a program that imports none. It still ships as a single file, and nogc.dllis copied.gc.dllbeside the output after a successful shared link. If it can't be copied, a warning names the file to ship.--gc-shared-lib-path(new)GC_SHARED_LIB_PATH<gc-lib-path>/gcdll, the release package layout--gc-lib-pathitself, whengc.dllsits beside it or in../bin. This keeps the default library'sbuild_core.batworking unchanged.gc.lib.Step 4: importing a library with its own collector is an error
Under
-mm=gcon Windows, importing a shared library that contains a collector fails at compile time, for--emit=exeand--emit=jitalike:The JIT runs the same check on
TypeScriptDefaultLib.dllbefore loading it. That catches a stale default library.This does not use the
__tsgc_marker the design proposed. A marker is written at compile time, and the compile cannot know how the library gets linked:test-runnerlinks its shared libraries with lld, the default-lib DLL carries no markers, and older libraries have none. Instead,Dump::containsGarbageCollectorlooks for the stringGC_INITIAL_HEAP_SIZEin the binary's data sections. Boehm'sGC_initreads that environment variable, so the string is in every binary that contains the collector and in none that only calls it. Checked against a static-gc default-lib DLL andgc.dll(present), and the releasegc/rcdefault-lib DLLs andTypeScriptRuntime.dll(absent).Linux
Measured in WSL with the v0.0-pre-alpha81 release, using the gc_single_collector pair:
.so, linked as before.so--whole-archive libgc.a+--export-dynamic-symbol=GC_*The JIT already works because
libTypeScriptRuntime.soexportsGC_*, and the.so's calls bind to it. A plain exe exports nothing, so the.soran its own copy.exe.cppnow links an importing--emit=exewith the whole collector, exported. The default library's.solinks no collector, so nothing else changes on Linux. This was verified with a hand link only. No Linux build of this branch was available locally, so the Linux CI run is the test of the compiler change.Step 6, template, debug default library
docs/memory-models.mdlists what to ship beside a program that loads a tslang DLL (gc.dll,TypeScriptDefaultLib.dll), shows the new error, and describes Linux. The zip already shipsgc.dllandgcdll/(One garbage collector per process: TypeScriptRuntime.dll on gc.dll (Windows) #309).docs/how/cmake_tslang): newTSLANG_SHARED_GCoption, off by default. On Windows it linksgcdll/gc.liband copiesgc.dllbeside the program. On Linux it adds the two export options. Not built as part of this PR.build_core.batalready links the debuggcdll. It now importsgc.dll.Tests
test-compile-gc-shared-auto(shared-collector-auto.cmake, Windows) runstslang --emit=dlland--emit=exeitself, becausetest-runnernever exercises the compiler's choice. It covers six cases:gc.dlland gets it copied beside it.--gc-lib-pathpointing at a shared build is enough on its own.gc.dllnear it, and none is copied.gc.dll.gc.libis refused when imported, by--emit=exeand by--emit=jit.Teeth: with the compiler from before step 3, a library and its importer built with only
--gc-lib-path=<static>fail the gc_single_collector assertion; with collection suppressed, the same binary prints 0 bad. Before step 4 there was no import check, so case 6's import succeeded.Step 5: tests with the real default library
Every
test-runnertest passes--no-default-lib, so the suite never had the default library in the same process as othergccode. Two new tests fix that:test-jit-gc-defaultlib-collectorandtest-compile-gc-defaultlib-collector(defaultlib-collector.cmake, sources intest/tester/defaultlib/). They runtslangwith the default library in four programs:padStart) and the churn (repeat) are both allocated in the default-lib DLL.TypeScriptDefaultLib.dll. The library builds the held strings and does the churn.The sources are outside
tests/because they don't compile without the default library, and the ownership verifier compiles every file intests/with--no-default-lib.CI: the compiler suite runs before any default library is built, so there the tests report skipped. Both jobs of
create-release.ymlgain a Test Compiler with Default Library step after Build Default Library. It setsDEFAULT_LIB_PATHto the library just built andTSLANG_REQUIRE_DEFAULT_LIB=1, which turns a missing library into a failure.Gap found by the tests: step 4 only checks libraries imported by name, and a user DLL links
TypeScriptDefaultLib.dllwithout importing it.--emit=dllundergcon Windows now refuses a default-lib DLL that contains a collector.Teeth, against a default library built by its own script with
GC_SHARED_LIB_PATHpointing at the staticgc.lib:bad: 0in all four programs--emit=dllrefuses the default-lib DLLbad: 0A machine whose
DEFAULT_LIB_PATHpoints at an installation with a default library from before #309 fails these two tests. That is intended:--emit=dlland JIT runs against that library now fail the same way.Test plan
test-compile-gc-shared-autopasses, including case 6test-jit-gc-defaultlib-collectorandtest-compile-gc-defaultlib-collectorpass against the rebuilt default library and fail against a static-collector onectest, Windows release, withDEFAULT_LIB_PATHat the rebuilt default library: 2,707 of 2,707exe.cppexport change)This branch's second commit also bumps
tag.bat/tag_del.battov0.0-pre-alpha82.🤖 Generated with Claude Code