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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ checkout** — the `C:\dev\...` / `~/dev/...` values are only examples.
| Variable | Points to |
| --- | --- |
| `GC_LIB_PATH` | Boehm GC library (the garbage collector) |
| `LLVM_LIB_PATH` | LLVM/MLIR libraries |
| `LLVM_LIB_PATH` | Not needed any more: programs no longer link an LLVM library. Still accepted so older scripts keep working |
| `TSLANG_LIB_PATH` | TSLANG runtime library |
| `DEFAULT_LIB_PATH` | Default library |

Expand Down
2 changes: 1 addition & 1 deletion docs/how/debug/debug-shared.bat
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ set "SDKPATH=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\um\x64"
set "UCRTPATH=C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\ucrt\x64"

rem --- STATIC CRT link line (matches the test-runner.cpp fix) ---
set "LIBS=libcmtd.lib libvcruntimed.lib libucrtd.lib ntdll.lib TypeScriptAsyncRuntime.lib gc.lib LLVMSupport.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib"
set "LIBS=libcmtd.lib libvcruntimed.lib libucrtd.lib TypeScriptAsyncRuntime.lib gc.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib"
set "LIBPATHS=/libpath:"%GC_LIB_PATH%" /libpath:"%LLVM_LIB_PATH%" /libpath:"%tslang_LIB_PATH%" /libpath:"%LIBPATH%" /libpath:"%SDKPATH%" /libpath:"%UCRTPATH%""

cd /d "%WORK%"
Expand Down
9 changes: 3 additions & 6 deletions tslang/include/TypeScript/VSCodeTemplate/Files.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,19 +350,16 @@ add_executable(${PROJECT_NAME}
)

# required libs
set(TSLANG_LINK_LIBS "TypeScriptDefaultLib" "TypeScriptAsyncRuntime" "LLVMSupport")
set(TSLANG_LINK_LIBS "TypeScriptDefaultLib" "TypeScriptAsyncRuntime")

# Boehm is only referenced by the gc default lib; the rc and none builds allocate through the
# CRT and must not drag a collector in.
if (TSLANG_MEMORY_MODEL STREQUAL "gc")
list(APPEND TSLANG_LINK_LIBS "gc")
endif()

# ntdll provides RtlGetLastNtStatus (pulled in by LLVMSupport) on Windows
if(WIN32)
list(APPEND TSLANG_LINK_LIBS "ntdll")
else()
list(APPEND TSLANG_LINK_LIBS "LLVMDemangle" "stdc++" "m" "pthread" "tinfo" "dl" "rt")
if(NOT WIN32)
list(APPEND TSLANG_LINK_LIBS "stdc++" "m" "pthread" "dl" "rt")
endif()

target_link_libraries(${PROJECT_NAME} ${TSLANG_LINK_LIBS})
Expand Down
107 changes: 102 additions & 5 deletions tslang/lib/AsyncRuntimeCommon.inc
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@
//
//===----------------------------------------------------------------------===//

#include <algorithm>
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <deque>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>

#include "llvm/ADT/StringMap.h"
#include "llvm/Support/ThreadPool.h"

#include "TypeScript/AsyncGCThreads.h"

using namespace mlir::runtime;
Expand All @@ -55,6 +55,103 @@ namespace
// Forward declare class defined below.
class RefCounted;

// -------------------------------------------------------------------------- //
// The pool the runtime resumes coroutines on. It stands in for llvm::DefaultThreadPool, which
// was the only thing that made every AOT executable link LLVMSupport (and LLVMDemangle on
// Linux). It behaves the same way: one worker per hardware thread, started only when a task
// is queued and no worker is idle, and `wait` returns once the queue is empty and no task is
// running, including tasks queued by other tasks.
// -------------------------------------------------------------------------- //

class ThreadPool
{
public:
ThreadPool() : maxConcurrency((std::max)(1u, std::thread::hardware_concurrency())), activeTasks(0), stopping(false)
{
}

~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(mu);
stopping = true;
}

taskAvailable.notify_all();
for (auto &worker : workers)
{
worker.join();
}
}

ThreadPool(const ThreadPool &) = delete;
ThreadPool &operator=(const ThreadPool &) = delete;

void async(std::function<void()> task)
{
{
std::unique_lock<std::mutex> lock(mu);
tasks.push_back(std::move(task));
// start a worker only if every existing one already has a task to run
if (workers.size() < (std::min<size_t>)(activeTasks + tasks.size(), maxConcurrency))
{
workers.emplace_back([this] { processTasks(); });
}
}

taskAvailable.notify_one();
}

void wait()
{
std::unique_lock<std::mutex> lock(mu);
allDone.wait(lock, [this] { return tasks.empty() && activeTasks == 0; });
}

unsigned getMaxConcurrency() const
{
return maxConcurrency;
}

private:
void processTasks()
{
std::unique_lock<std::mutex> lock(mu);
while (true)
{
taskAvailable.wait(lock, [this] { return stopping || !tasks.empty(); });
if (tasks.empty())
{
// stopping, with nothing left to run
return;
}

auto task = std::move(tasks.front());
tasks.pop_front();
++activeTasks;

lock.unlock();
task();
lock.lock();

--activeTasks;
if (tasks.empty() && activeTasks == 0)
{
allDone.notify_all();
}
}
}

const unsigned maxConcurrency;
std::mutex mu;
std::condition_variable taskAvailable;
std::condition_variable allDone;
std::deque<std::function<void()>> tasks;
std::vector<std::thread> workers;
size_t activeTasks;
bool stopping;
};

// -------------------------------------------------------------------------- //
// AsyncRuntime orchestrates all async operations and Async runtime API is built
// on top of the default runtime instance.
Expand All @@ -78,7 +175,7 @@ namespace
return numRefCountedObjects.load(std::memory_order_relaxed);
}

llvm::ThreadPoolInterface &getThreadPool()
ThreadPool &getThreadPool()
{
return threadPool;
}
Expand All @@ -98,7 +195,7 @@ namespace
}

std::atomic<int64_t> numRefCountedObjects;
llvm::DefaultThreadPool threadPool;
ThreadPool threadPool;
};

// -------------------------------------------------------------------------- //
Expand Down
4 changes: 2 additions & 2 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class LoadLibraryPermanentlyOpLowering : public TsLlvmPattern<mlir_ts::LoadLibra

auto i8PtrTy = th.getPtrType();

auto loadLibraryPermanentlyFuncOp = ch.getOrInsertFunction("LLVMLoadLibraryPermanently", th.getFunctionType(rewriter.getI32Type(), {i8PtrTy}));
auto loadLibraryPermanentlyFuncOp = ch.getOrInsertFunction("tslang_load_library_permanently", th.getFunctionType(rewriter.getI32Type(), {i8PtrTy}));
rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, loadLibraryPermanentlyFuncOp, ValueRange{transformed.getFilename()});

return success();
Expand All @@ -318,7 +318,7 @@ class SearchForAddressOfSymbolOpLowering : public TsLlvmPattern<mlir_ts::SearchF

auto i8PtrTy = th.getPtrType();

auto searchForAddressOfSymbolFuncOp = ch.getOrInsertFunction("LLVMSearchForAddressOfSymbol", th.getFunctionType(i8PtrTy, {i8PtrTy}));
auto searchForAddressOfSymbolFuncOp = ch.getOrInsertFunction("tslang_search_for_address_of_symbol", th.getFunctionType(i8PtrTy, {i8PtrTy}));
rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, searchForAddressOfSymbolFuncOp, ValueRange{transformed.getSymbolName()});

return success();
Expand Down
7 changes: 4 additions & 3 deletions tslang/lib/TypeScript/MLIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1028,15 +1028,16 @@ namespace mlirgen
return mlir::failure();
}

// The shared-lib load + symbol resolution call into LLVM's
// sys::DynamicLibrary, which uses std::vector. In debug builds STL
// The shared-lib load + symbol resolution call into the runtime's
// library list (LLVM's sys::DynamicLibrary under the JIT), which uses
// std::vector. In debug builds STL
// iterators take a global lock that the CRT only initializes via its
// own '_Init_locks'/'initlocks' dynamic initializer (in .CRT$XCU).
// FIRST_GLOBAL_CONSTRUCTOR_PRIORITY (100) places this ctor BEFORE that
// CRT init -> entering an uninitialized CRITICAL_SECTION -> crash.
// Use the same band as the per-symbol __cctors (LAST) so it runs after
// 'initlocks'; it is emitted before them, so it still loads the library
// before any LLVMSearchForAddressOfSymbol runs.
// before any tslang_search_for_address_of_symbol runs.
addGlobalConstructor(location, fullInitGlobalFuncName);
}

Expand Down
1 change: 1 addition & 0 deletions tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ endif()
add_mlir_library(TypeScriptAsyncRuntime
STATIC
AsyncRuntime.cpp
DynamicRuntime.cpp

EXCLUDE_FROM_LIBMLIR

Expand Down
119 changes: 119 additions & 0 deletions tslang/lib/TypeScriptAsyncRuntime/DynamicRuntime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//===- DynamicRuntime.cpp - Shared library loading for AOT executables -----===//
//
// A program that imports a shared library loads it from a global constructor and then resolves
// each imported symbol by name, through the two functions below. Under the JIT, TypeScriptRuntime
// supplies them by wrapping llvm::sys::DynamicLibrary (see TypeScriptRuntime/DynamicRuntime.cpp).
// An executable used to call LLVMLoadLibraryPermanently and LLVMSearchForAddressOfSymbol from
// LLVMSupport instead. These are here so that it does not have to link any LLVM library at all.
//
// They behave as LLVM's do for what an executable asks of them: a library is loaded once and
// kept for the life of the process, and a symbol is looked up in the loaded libraries in the
// order they were loaded.
//
//===----------------------------------------------------------------------===//

#include <mutex>
#include <string>
#include <vector>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#else
#include <dlfcn.h>
#endif

namespace
{

// Function-local statics: the library is loaded from a global constructor, which can run before
// this file's own namespace-scope objects are initialized.
std::mutex &handlesMutex()
{
static std::mutex mu;
return mu;
}

std::vector<void *> &loadedHandles()
{
static std::vector<void *> handles;
return handles;
}

void *openLibrary(const char *fileName)
{
#ifdef _WIN32
auto size = MultiByteToWideChar(CP_UTF8, 0, fileName, -1, nullptr, 0);
if (size <= 0)
{
return nullptr;
}

std::wstring wideFileName(size, L'\0');
if (MultiByteToWideChar(CP_UTF8, 0, fileName, -1, wideFileName.data(), size) <= 0)
{
return nullptr;
}

return reinterpret_cast<void *>(LoadLibraryW(wideFileName.c_str()));
#else
return dlopen(fileName, RTLD_LAZY | RTLD_GLOBAL);
#endif
}

void *findSymbol(void *handle, const char *symbolName)
{
#ifdef _WIN32
return reinterpret_cast<void *>(GetProcAddress(reinterpret_cast<HMODULE>(handle), symbolName));
#else
return dlsym(handle, symbolName);
#endif
}

} // namespace

// 0 when the library is loaded (or already was), 1 when it cannot be.
extern "C" int tslang_load_library_permanently(const char *fileName)
{
if (!fileName)
{
// LLVM reads a null name as "the process itself"; nothing the compiler emits asks for that
return 1;
}

auto handle = openLibrary(fileName);
if (!handle)
{
return 1;
}

std::lock_guard<std::mutex> lock(handlesMutex());
auto &handles = loadedHandles();
for (auto loaded : handles)
{
if (loaded == handle)
{
// the loader counts every open, and a library loaded for good is never closed, so
// opening it again changes nothing
return 0;
}
}

handles.push_back(handle);
return 0;
}

extern "C" void *tslang_search_for_address_of_symbol(const char *symbolName)
{
std::lock_guard<std::mutex> lock(handlesMutex());
for (auto handle : loadedHandles())
{
if (auto address = findSymbol(handle, symbolName))
{
return address;
}
}

return nullptr;
}
3 changes: 3 additions & 0 deletions tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

#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
10 changes: 6 additions & 4 deletions tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namespace mlir
namespace runtime
{

extern "C" int LoadLibraryPermanently(const char* fileName) {
// Named as the generated code calls them: Linux has no .def to rename an export, so the shared
// object's own name for a function is the one the JIT finds.
extern "C" int tslang_load_library_permanently(const char* fileName) {
return llvm::sys::DynamicLibrary::LoadLibraryPermanently(fileName);
}

extern "C" void *SearchForAddressOfSymbol(const char* symbolName) {
extern "C" void *tslang_search_for_address_of_symbol(const char* symbolName) {
return llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(symbolName);
}

Expand All @@ -34,8 +36,8 @@ void init_dynamicruntime(llvm::StringMap<void *> &exportSymbols)
exportSymbols[name] = reinterpret_cast<void *>(ptr);
};

exportSymbol("LLVMLoadLibraryPermanently", &mlir::runtime::LoadLibraryPermanently);
exportSymbol("LLVMSearchForAddressOfSymbol", &mlir::runtime::SearchForAddressOfSymbol);
exportSymbol("tslang_load_library_permanently", &mlir::runtime::tslang_load_library_permanently);
exportSymbol("tslang_search_for_address_of_symbol", &mlir::runtime::tslang_search_for_address_of_symbol);
}

// NOLINTNEXTLINE(*-identifier-naming): externally called.
Expand Down
Loading
Loading