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
47 changes: 23 additions & 24 deletions tslang/include/TypeScript/AsyncGCThreads.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,34 @@
// registers can be freed underneath it. Hence the registration below.
//
// Only a `gc` build calls GC_enable_threads - the GC pass injects the call beside GC_init, and
// that pass runs for no other model - so `rc` and `none` reach GCThreadRegistration with the flag
// still false and do nothing, which is right: they never initialize the collector at all.
// Boehm's own GC_is_init_called cannot stand in for the flag, because the collector initializes
// itself on first use and so answers yes in programs that never meant to collect anything.

#define GC_THREADS
#include "gc.h"
// that pass runs for no other model. Boehm's own GC_is_init_called cannot stand in for that,
// because the collector initializes itself on first use and so answers yes in programs that never
// meant to collect anything.
//
// Nothing here touches the collector itself: GC_enable_threads, and the registration it installs
// as the hooks below, are in AsyncGCThreadsCommon.inc - an object file of its own, which only the
// call to GC_enable_threads pulls in. Every async program links the scheduler; had the scheduler
// called GC_register_my_thread itself, every `rc`, `none` and `own` program that awaits anything
// would need the collector to link, and they do not link it.

namespace typescript
{
namespace asyncgc
{

inline bool &threadsEnabledFlag()
// Left null unless GC_enable_threads ran, which it does once, from the entry point, before any
// coroutine can be handed to the pool.
struct ThreadHooks
{
static bool enabled = false;
return enabled;
}
// true when this call registered the thread (and so has to unregister it)
bool (*registerThread)() = nullptr;
void (*unregisterThread)() = nullptr;
};

// Called once, from the entry point, before any coroutine can be handed to the pool.
inline void enableThreads()
inline ThreadHooks &threadHooks()
{
GC_allow_register_threads();
threadsEnabledFlag() = true;
static ThreadHooks hooks;
return hooks;
}

// Registered per task rather than per thread because the pool offers no thread-entry hook.
Expand All @@ -55,23 +59,18 @@ class ThreadRegistration
public:
ThreadRegistration() : registered(false)
{
if (!threadsEnabledFlag())
{
return;
}

struct GC_stack_base sb;
if (GC_get_stack_base(&sb) == GC_SUCCESS)
auto registerThread = threadHooks().registerThread;
if (registerThread != nullptr)
{
registered = GC_register_my_thread(&sb) == GC_SUCCESS;
registered = registerThread();
}
}

~ThreadRegistration()
{
if (registered)
{
GC_unregister_my_thread();
threadHooks().unregisterThread();
}
}

Expand Down
39 changes: 39 additions & 0 deletions tslang/lib/AsyncGCThreadsCommon.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// The async runtime's side of the collector, in an object file of its own - see AsyncGCThreads.h.
//
// Shared by TypeScriptRuntime/AsyncGCThreads.cpp (the JIT's DLL) and
// TypeScriptAsyncRuntime/AsyncGCThreads.cpp (the static library ahead-of-time builds link). In the
// static library nothing but GC_enable_threads refers to this object, and only a `gc` program
// calls that, so only a `gc` program pulls it in - and with it the collector's thread API.

#define GC_THREADS
#include "gc.h"

#include "TypeScript/AsyncGCThreads.h"

namespace
{

bool registerThisThread()
{
struct GC_stack_base sb;
return GC_get_stack_base(&sb) == GC_SUCCESS && GC_register_my_thread(&sb) == GC_SUCCESS;
}

void unregisterThisThread()
{
GC_unregister_my_thread();
}

} // namespace

// Called once from the entry point: the GC pass injects the call beside GC_init, so it happens
// only in a `gc` build. When linked into the shared TypeScriptRuntime, this symbol is also
// exported under this name in TypeScriptRuntime.def for the JIT to resolve.
extern "C" void GC_enable_threads()
{
GC_allow_register_threads();

auto &hooks = typescript::asyncgc::threadHooks();
hooks.registerThread = &registerThisThread;
hooks.unregisterThread = &unregisterThisThread;
}
9 changes: 1 addition & 8 deletions tslang/lib/AsyncRuntimeCommon.inc
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,11 @@
#include <thread>
#include <vector>

// only the hooks: GC_enable_threads and the collector calls are in AsyncGCThreadsCommon.inc
#include "TypeScript/AsyncGCThreads.h"

using namespace mlir::runtime;

// Called once from the entry point: the GC pass injects the call beside GC_init, so it happens
// only in a `gc` build. When linked into the shared TypeScriptRuntime, this symbol is also
// exported under this name in TypeScriptRuntime.def for the JIT to resolve.
extern "C" void GC_enable_threads()
{
typescript::asyncgc::enableThreads();
}

//===----------------------------------------------------------------------===//
// Async runtime API.
//===----------------------------------------------------------------------===//
Expand Down
4 changes: 4 additions & 0 deletions tslang/lib/TypeScriptAsyncRuntime/AsyncGCThreads.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// GC_enable_threads and the pool's thread registration with the collector, kept out of
// AsyncRuntime.cpp so that only a `gc` program - the one caller of GC_enable_threads - links them.
// See AsyncGCThreads.h.
#include "../AsyncGCThreadsCommon.inc"
3 changes: 2 additions & 1 deletion tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
add_mlir_library(TypeScriptAsyncRuntime
STATIC
AsyncRuntime.cpp
AsyncGCThreads.cpp
ProcessHeap.cpp
DynamicRuntime.cpp

EXCLUDE_FROM_LIBMLIR

# For gc.h only - AsyncGCThreads.h registers the pool's workers with the collector when the
# For gc.h only - AsyncGCThreads.cpp registers the pool's workers with the collector when the
# program is a `gc` one. The collector itself is linked into the executable, not into here.
LINK_LIBS PRIVATE
BDWgc::gc
Expand Down
3 changes: 3 additions & 0 deletions tslang/lib/TypeScriptRuntime/AsyncGCThreads.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// GC_enable_threads and the pool's thread registration with the collector - the same file the
// static TypeScriptAsyncRuntime builds, where it has to be an object of its own. See AsyncGCThreads.h.
#include "../AsyncGCThreadsCommon.inc"
1 change: 0 additions & 1 deletion tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

#ifdef MLIR_ASYNCRUNTIME_DEFINE_FUNCTIONS

// before the .inc: gc.h, which it includes, brings windows.h and its min/max macros
#include "llvm/ADT/StringMap.h"

#include "../AsyncRuntimeCommon.inc"
Expand Down
1 change: 1 addition & 0 deletions tslang/lib/TypeScriptRuntime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_mlir_library(TypeScriptRuntime
gc.cpp
MemRuntime.cpp
AsyncRuntime.cpp
AsyncGCThreads.cpp
DynamicRuntime.cpp
mlir_init.cpp

Expand Down
2 changes: 1 addition & 1 deletion tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ EXPORTS
GC_gcollect=_mlir__GC_gcollect

; --- tells the collector it is multi-threaded and lets the async runtime's pool workers
; --- register themselves (AsyncRuntime.cpp / AsyncGCThreads.h); injected beside GC_init
; --- register themselves (AsyncGCThreads.cpp / AsyncGCThreads.h); injected beside GC_init
GC_enable_threads

; --- memory runtime (MemRuntime.cpp, extern "C") ---
Expand Down
19 changes: 19 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,25 @@ if (WIN32)
-P ${CMAKE_CURRENT_SOURCE_DIR}/jit-throw-without-runtime.cmake)
endif()

# An async program linked by tslang itself (test-runner links with gc.lib whatever the model) under
# every model but gc - see async-without-collector.cmake.
if (WIN32)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
set(async_without_collector_opt "--opt;--opt_level=3")
else()
set(async_without_collector_opt "--di;--opt_level=0")
endif()
add_test(NAME test-compile-async-without-collector
COMMAND ${CMAKE_COMMAND}
"-DTSLANG=$<TARGET_FILE:tslang>"
"-DTESTS_DIR=${PROJECT_SOURCE_DIR}/test/tester/tests"
"-DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/async-without-collector"
"-DLLVM_LIB=${LLVM_LIBRARY_DIR}"
"-DTSLANG_LIB=${CMAKE_BINARY_DIR}/lib"
"-DOPT=${async_without_collector_opt}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/async-without-collector.cmake")
endif()

# tslang choosing the collector's linkage itself under -mm=gc - see shared-collector-auto.cmake.
if (WIN32)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
Expand Down
68 changes: 68 additions & 0 deletions tslang/test/tester/async-without-collector.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# An async program built by `tslang --emit=exe` under every model but gc - which links no collector.
#
# The async runtime registers its pool threads with the collector under gc (AsyncGCThreads.h). That
# code used to sit in the scheduler's own object file, so every program that awaits anything pulled
# in references to GC_allow_register_threads, GC_register_my_thread, GC_unregister_my_thread and
# GC_get_stack_base - and under rc, none and own the compiler links no gc.lib, so none of them
# linked: "unresolved external symbol GC_allow_register_threads". It is an object file of its own
# now, pulled in only by the call to GC_enable_threads that the GC pass puts in a gc program.
#
# This drives `tslang --emit=exe` itself, which `test-runner` does not: it links with lld directly,
# and always with gc.lib, so the rc and none corpus runs of the same file never saw it.

cmake_minimum_required(VERSION 3.17.3)

foreach(var TSLANG TESTS_DIR WORK_DIR LLVM_LIB TSLANG_LIB OPT)
if(NOT DEFINED ${var})
message(FATAL_ERROR "${var} is required")
endif()
endforeach()

# Only what the command line says: an inherited GC_LIB_PATH would put the collector back in reach.
set(ENV{GC_LIB_PATH} "")

file(REMOVE_RECURSE "${WORK_DIR}")
file(MAKE_DIRECTORY "${WORK_DIR}")

set(name 00async_await)

set(failures "")
foreach(model rc none own)
execute_process(
COMMAND "${TSLANG}" --emit=exe -mm=${model} --no-default-lib ${OPT}
"--llvm-lib-path=${LLVM_LIB}" "--tslang-lib-path=${TSLANG_LIB}"
"${TESTS_DIR}/${name}.ts" -o=${name}.${model}.exe
WORKING_DIRECTORY "${WORK_DIR}"
OUTPUT_VARIABLE out
ERROR_VARIABLE err
RESULT_VARIABLE status)

if(NOT status EQUAL 0)
# the first error, not a warning printed ahead of it
if("${out}${err}" MATCHES "[^\n]*error[^\n]*")
set(first_line "${CMAKE_MATCH_0}")
else()
string(REGEX REPLACE "\n.*" "" first_line "${err}")
endif()
list(APPEND failures " -mm=${model}: --emit=exe: exit ${status}: ${first_line}")
continue()
endif()

execute_process(
COMMAND "${WORK_DIR}/${name}.${model}.exe"
WORKING_DIRECTORY "${WORK_DIR}"
OUTPUT_VARIABLE out
ERROR_VARIABLE err
RESULT_VARIABLE status)

if(NOT status EQUAL 0 OR NOT out MATCHES "done\\.")
list(APPEND failures " -mm=${model}: the program: exit ${status}")
endif()
endforeach()

if(failures)
string(REPLACE ";" "\n" report "${failures}")
message(FATAL_ERROR "an async program without the collector:\n${report}")
endif()

message(STATUS "an async program links and runs without the collector under rc, none and own")
Loading