Skip to content

An async program links without the collector under rc, none and own - #419

Merged
ASDAlexander77 merged 1 commit into
mainfrom
async-without-collector
Sep 30, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
async-without-collector

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

Built ahead of time under -mm=rc, none or own, any program that awaits anything failed to link. The first failure seen was DefaultLib's sleep.ts under rc:

TypeScriptAsyncRuntime.lib(AsyncRuntime.obj) : error LNK2019: unresolved external symbol
GC_allow_register_threads referenced in function GC_enable_threads

GC_register_my_thread, GC_unregister_my_thread and GC_get_stack_base were missing too.

Why the collector's functions were in an rc build

  • What's there: since Reference counting memory model (-mm=rc) and related codegen/runtime fixes #308, the async runtime registers its pool threads with the collector under gc (AsyncGCThreads.h). Without that, Boehm's allocator runs unlocked across threads, and a long chain of awaits faulted.
  • Why it's harmless at run time: under the other models that code never runs. It switches on only through GC_enable_threads, and only the GC pass injects that call.
  • Why it broke the link: the code sat in the scheduler's own object file, which every async program links, and only gc programs link gc.lib. So the linker needed the collector's thread functions in programs that don't have a collector.
  • Regression: none linked before Reference counting memory model (-mm=rc) and related codegen/runtime fixes #308.
  • Why the JIT didn't show it: TypeScriptRuntime.dll has the collector built in.

Fix

  • The split: GC_enable_threads and the collector calls move to an object file of their own, AsyncGCThreadsCommon.inc. It's built as AsyncGCThreads.cpp in both TypeScriptAsyncRuntime and TypeScriptRuntime.
  • The hooks: the scheduler reaches that code through two hooks that GC_enable_threads installs. Only the call in a gc program pulls the object in.
  • The scheduler no longer includes gc.h.

The archive now shows the split: AsyncRuntime.obj references no GC_* function, and only AsyncGCThreads.obj does.

Tests

test-runner links every model with lld and gc.lib itself. That's why the rc and none corpus runs of 00async_await.ts passed.

The new test-compile-async-without-collector (async-without-collector.cmake, Windows) builds 00async_await.ts with tslang --emit=exe under rc, none and own, with GC_LIB_PATH cleared, and runs it.

Results

Release ctest 3313/3313
00async_await.ts, tslang --emit=exe, gc/rc/none/own at -O0 and -O3 builds and runs (rc/none/own failed to link before)
DefaultLib sleep.ts, rc / none / gc builds and runs
00async_gc_threading.ts under gc, 20 runs at -O0 and at -O3 0 failures (about 6 in 10 at -O0 without the registration)

Not in this PR

  • weakref_basic under rc: DefaultLib's WeakRef calls Boehm's weak-link API directly, and the test calls GC_gcollect() itself. That's a DefaultLib change, and an rc design question (rc document §9.8).
  • Linux: the same split applies to the static library there. The new test is Windows-only.

🤖 Generated with Claude Code

Every program that awaits anything, built ahead of time under rc, none or
own, failed to link:

  TypeScriptAsyncRuntime.lib(AsyncRuntime.obj) : error LNK2019: unresolved
  external symbol GC_allow_register_threads referenced in function
  GC_enable_threads (and GC_register_my_thread, GC_unregister_my_thread,
  GC_get_stack_base)

The async runtime registers its pool threads with the collector under gc
(AsyncGCThreads.h, from #308). At run time that is inert under the other
models - it only switches on through GC_enable_threads, which only the GC
pass injects - but it sat in the scheduler's own object file, which every
async program links, and only gc programs link gc.lib. `none` linked before
#308.

GC_enable_threads and the collector calls move to an object file of their
own (AsyncGCThreadsCommon.inc, built as AsyncGCThreads.cpp in both
TypeScriptAsyncRuntime and TypeScriptRuntime). The scheduler reaches them
through two hooks that GC_enable_threads installs, so only the call in a
gc program pulls that object in. The scheduler no longer includes gc.h.

test-runner links every model with lld and gc.lib itself, which is why the
rc and none corpus runs of 00async_await.ts passed. New
test-compile-async-without-collector (async-without-collector.cmake) builds
00async_await.ts with `tslang --emit=exe` under rc, none and own, with no
GC_LIB_PATH, and runs it.

DefaultLib's sleep.ts, which failed the same way under rc, links and runs.
00async_gc_threading.ts under gc (the fault the registration fixed, about 6
runs in 10 at -O0 without it): 0 of 20 failures at -O0 and at -O3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 66f160d into main Sep 30, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the async-without-collector branch September 30, 2026 19:52
ASDAlexander77 added a commit that referenced this pull request Sep 30, 2026
#419's split of the async runtime applies to the static library on Linux
as well, and before it the failure was the same there: checked in WSL at
#418, the scheduler's object (AsyncRuntime.cpp.o) referenced
GC_allow_register_threads, GC_register_my_thread, GC_unregister_my_thread
and GC_get_stack_base, and 00async_await.ts built with `tslang --emit=exe`
under rc, none and own failed to link ("undefined reference to
`GC_register_my_thread'"). At main only AsyncGCThreads.cpp.o references
them, and all four models link and run at -O0 and -O3.

The test was registered for Windows only; async-without-collector.cmake
passes as is on Linux, so the guard goes. Its comments name both
platforms' messages and linkers.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77 added a commit that referenced this pull request Oct 7, 2026
Since #419 an async program links without the collector, so the
exact-output await_order case now runs under -mm=rc, -mm=none and
-mm=own as well as gc. The comments that said rc/none fail to link are
updated.

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