Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions tslang/lib/AsyncGCThreadsCommon.inc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ struct CallingThread

thread_local CallingThread callingThread;

std::once_flag collectorInitialized;
std::once_flag threadsEnabled;

} // namespace
Expand All @@ -63,11 +64,18 @@ extern "C" void GC_enable_threads();
// registers the threads it makes, but a library's host makes its own. GC_init registered the one
// it ran on; any other is registered on its first call.
//
// GC_enable_threads runs once here whatever ran before: GC_register_my_thread refuses every
// thread until GC_allow_register_threads was called ("Threads explicit registering is not
// previously enabled"), and the collector may well be initialized already without it - it
// initializes itself on first use, so a library with no top-level code (no GC_init injected at
// load) can find it running. Calling it again where GC_init's injection already did is harmless.
// The collector may well be initialized already: it initializes itself on first use, so a library
// with no top-level code (no GC_init injected at load) can find it running.
//
// GC_enable_threads runs only for a thread that has to be registered: GC_register_my_thread
// refuses every thread until GC_allow_register_threads was called ("Threads explicit registering
// is not previously enabled"). A registered thread must not run it here. An exported function can
// be called while the library is being loaded - a static constructor of an exported class, or
// top-level code calling an export - and on Windows that is inside DllMain, under the loader lock.
// GC_allow_register_threads starts the parallel marker threads and waits for them to come up, and
// a new thread cannot start until the loader lock is released: the load never finished (the
// default library hung every JIT run). The thread a library is loaded on is the one GC_init
// registered, or one its host registered.
extern "C" void __tslang_gc_enter()
{
auto &thread = callingThread;
Expand All @@ -76,20 +84,23 @@ extern "C" void __tslang_gc_enter()
return;
}

std::call_once(threadsEnabled, [] {
thread.checked = true;

std::call_once(collectorInitialized, [] {
if (!GC_is_init_called())
{
GC_init();
}

GC_enable_threads();
});

thread.checked = true;
if (!GC_thread_is_registered())
if (GC_thread_is_registered())
{
thread.registeredHere = registerThisThread();
return;
}

std::call_once(threadsEnabled, [] { GC_enable_threads(); });

thread.registeredHere = registerThisThread();
}

// Called once from the entry point: the GC pass injects the call beside GC_init, so it happens
Expand Down
21 changes: 20 additions & 1 deletion tslang/test/tester/foreign-thread-gc.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
# thousand calls collect many times, on every thread.
#
# Also with no top-level code: then nothing ran GC_init at load, and the first call does.
#
# And with exported functions called while the library loads - a static constructor of an
# exported class, top-level code calling an export - which on Windows runs inside DllMain, under
# the loader lock. The first call there enabled threads, which started the collector's parallel
# marker threads and waited for them; they could not start until the load finished, and the load
# never did (the default library hung every JIT run). The allocations escape into globals, so the
# optimizer cannot fold the calls away.

cmake_minimum_required(VERSION 3.17.3)

Expand Down Expand Up @@ -48,6 +55,18 @@ export function work(seed: number): number {

file(WRITE "${WORK_DIR}/with-top-level.ts" "${work}\nlet loaded: number = 1;\n")
file(WRITE "${WORK_DIR}/no-top-level.ts" "${work}")
file(WRITE "${WORK_DIR}/static-constructor-at-load.ts" "${work}
export class Registry {
static first: Node = new Node(1, undefined);
}
")
file(WRITE "${WORK_DIR}/export-called-at-load.ts" "${work}
export function makeNode(value: number): Node {
return new Node(value, undefined);
}

export let kept: Node = makeNode(1);
")

set(libs "--gc-lib-path=${GC_LIB}" "--tslang-lib-path=${TSLANG_LIB}" "--llvm-lib-path=${LLVM_LIB}")
if(DEFINED GC_SHARED_LIB)
Expand All @@ -65,7 +84,7 @@ else()
set(pic "-relocation-model=pic")
endif()

foreach(name with-top-level no-top-level)
foreach(name with-top-level no-top-level static-constructor-at-load export-called-at-load)
set(library "${WORK_DIR}/${prefix}${name}${suffix}")
execute_process(COMMAND "${TSLANG}" --emit=dll --opt -mm=gc --no-default-lib ${pic} ${libs} ${name}.ts -o "${library}"
WORKING_DIRECTORY "${WORK_DIR}"
Expand Down
Loading