From 53aadb7285469245bcc00d210eaf42724b3a80a0 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Fri, 18 Sep 2026 12:07:09 +0100 Subject: [PATCH 1/2] Stop linking LLVMSupport and LLVMDemangle into compiled programs Two things tied every AOT executable to LLVM: - The async runtime resumed coroutines on llvm::DefaultThreadPool. AsyncRuntimeCommon.inc now has its own std::thread pool with the same behaviour (one worker per hardware thread, started on demand; wait() covers tasks queued by other tasks). The JIT runtime shares the file. - Code for a shared-library import called LLVMLoadLibraryPermanently and LLVMSearchForAddressOfSymbol. They are renamed to tslang_load_library_permanently / tslang_search_for_address_of_symbol; TypeScriptAsyncRuntime now defines them for executables (LoadLibraryW/GetProcAddress, dlopen/dlsym), and TypeScriptRuntime exports them under the new names for the JIT. The driver, test runner, generated CMake template and debug script no longer link LLVMSupport, LLVMDemangle, ntdll or tinfo (the last two were only there for LLVMSupport). --llvm-lib-path / LLVM_LIB_PATH are still accepted, and ignored, so existing scripts keep working. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/how/debug/debug-shared.bat | 2 +- .../include/TypeScript/VSCodeTemplate/Files.h | 9 +- tslang/lib/AsyncRuntimeCommon.inc | 107 +++++++++++++++- tslang/lib/TypeScript/LowerToLLVM.cpp | 4 +- tslang/lib/TypeScript/MLIRGenModule.cpp | 7 +- .../lib/TypeScriptAsyncRuntime/CMakeLists.txt | 1 + .../TypeScriptAsyncRuntime/DynamicRuntime.cpp | 119 ++++++++++++++++++ tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp | 3 + .../lib/TypeScriptRuntime/DynamicRuntime.cpp | 4 +- .../TypeScriptRuntime/TypeScriptRuntime.def | 4 +- tslang/test/tester/test-runner.cpp | 38 +++--- tslang/tslang/exe.cpp | 37 ------ tslang/tslang/tslang.cpp | 2 +- 14 files changed, 259 insertions(+), 80 deletions(-) create mode 100644 tslang/lib/TypeScriptAsyncRuntime/DynamicRuntime.cpp diff --git a/README.md b/README.md index 99a6ac332..9b8e707f0 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/docs/how/debug/debug-shared.bat b/docs/how/debug/debug-shared.bat index ff5874334..ac3fea707 100644 --- a/docs/how/debug/debug-shared.bat +++ b/docs/how/debug/debug-shared.bat @@ -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%" diff --git a/tslang/include/TypeScript/VSCodeTemplate/Files.h b/tslang/include/TypeScript/VSCodeTemplate/Files.h index a83737359..3cd39dce9 100644 --- a/tslang/include/TypeScript/VSCodeTemplate/Files.h +++ b/tslang/include/TypeScript/VSCodeTemplate/Files.h @@ -350,7 +350,7 @@ 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. @@ -358,11 +358,8 @@ 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}) diff --git a/tslang/lib/AsyncRuntimeCommon.inc b/tslang/lib/AsyncRuntimeCommon.inc index eb424bb0c..9cca2db09 100644 --- a/tslang/lib/AsyncRuntimeCommon.inc +++ b/tslang/lib/AsyncRuntimeCommon.inc @@ -17,18 +17,18 @@ // //===----------------------------------------------------------------------===// +#include #include #include #include +#include #include #include +#include #include #include #include -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/ThreadPool.h" - #include "TypeScript/AsyncGCThreads.h" using namespace mlir::runtime; @@ -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 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 task) + { + { + std::unique_lock 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)(activeTasks + tasks.size(), maxConcurrency)) + { + workers.emplace_back([this] { processTasks(); }); + } + } + + taskAvailable.notify_one(); + } + + void wait() + { + std::unique_lock lock(mu); + allDone.wait(lock, [this] { return tasks.empty() && activeTasks == 0; }); + } + + unsigned getMaxConcurrency() const + { + return maxConcurrency; + } + + private: + void processTasks() + { + std::unique_lock 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> tasks; + std::vector workers; + size_t activeTasks; + bool stopping; + }; + // -------------------------------------------------------------------------- // // AsyncRuntime orchestrates all async operations and Async runtime API is built // on top of the default runtime instance. @@ -78,7 +175,7 @@ namespace return numRefCountedObjects.load(std::memory_order_relaxed); } - llvm::ThreadPoolInterface &getThreadPool() + ThreadPool &getThreadPool() { return threadPool; } @@ -98,7 +195,7 @@ namespace } std::atomic numRefCountedObjects; - llvm::DefaultThreadPool threadPool; + ThreadPool threadPool; }; // -------------------------------------------------------------------------- // diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 38ada7de4..ec984e0f3 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -296,7 +296,7 @@ class LoadLibraryPermanentlyOpLowering : public TsLlvmPattern(op, loadLibraryPermanentlyFuncOp, ValueRange{transformed.getFilename()}); return success(); @@ -318,7 +318,7 @@ class SearchForAddressOfSymbolOpLowering : public TsLlvmPattern(op, searchForAddressOfSymbolFuncOp, ValueRange{transformed.getSymbolName()}); return success(); diff --git a/tslang/lib/TypeScript/MLIRGenModule.cpp b/tslang/lib/TypeScript/MLIRGenModule.cpp index ae35b9fc4..d5558bb73 100644 --- a/tslang/lib/TypeScript/MLIRGenModule.cpp +++ b/tslang/lib/TypeScript/MLIRGenModule.cpp @@ -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); } diff --git a/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt b/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt index 178ff104b..a7164bc72 100644 --- a/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt +++ b/tslang/lib/TypeScriptAsyncRuntime/CMakeLists.txt @@ -7,6 +7,7 @@ endif() add_mlir_library(TypeScriptAsyncRuntime STATIC AsyncRuntime.cpp + DynamicRuntime.cpp EXCLUDE_FROM_LIBMLIR diff --git a/tslang/lib/TypeScriptAsyncRuntime/DynamicRuntime.cpp b/tslang/lib/TypeScriptAsyncRuntime/DynamicRuntime.cpp new file mode 100644 index 000000000..c54129574 --- /dev/null +++ b/tslang/lib/TypeScriptAsyncRuntime/DynamicRuntime.cpp @@ -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 +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#else +#include +#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 &loadedHandles() +{ + static std::vector 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(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(GetProcAddress(reinterpret_cast(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 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 lock(handlesMutex()); + for (auto handle : loadedHandles()) + { + if (auto address = findSymbol(handle, symbolName)) + { + return address; + } + } + + return nullptr; +} diff --git a/tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp b/tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp index ea0fe31ff..63ab7802b 100644 --- a/tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp +++ b/tslang/lib/TypeScriptRuntime/AsyncRuntime.cpp @@ -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" //===----------------------------------------------------------------------===// diff --git a/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp b/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp index 431ae256d..d151f8d08 100644 --- a/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp +++ b/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp @@ -34,8 +34,8 @@ void init_dynamicruntime(llvm::StringMap &exportSymbols) exportSymbols[name] = reinterpret_cast(ptr); }; - exportSymbol("LLVMLoadLibraryPermanently", &mlir::runtime::LoadLibraryPermanently); - exportSymbol("LLVMSearchForAddressOfSymbol", &mlir::runtime::SearchForAddressOfSymbol); + exportSymbol("tslang_load_library_permanently", &mlir::runtime::LoadLibraryPermanently); + exportSymbol("tslang_search_for_address_of_symbol", &mlir::runtime::SearchForAddressOfSymbol); } // NOLINTNEXTLINE(*-identifier-naming): externally called. diff --git a/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def b/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def index 572b8a425..95546b0bc 100644 --- a/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def +++ b/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def @@ -36,8 +36,8 @@ EXPORTS aligned_free=AlignedFree ; --- dynamic-library runtime (DynamicRuntime.cpp, extern "C") --- - LLVMLoadLibraryPermanently=LoadLibraryPermanently - LLVMSearchForAddressOfSymbol=SearchForAddressOfSymbol + tslang_load_library_permanently=LoadLibraryPermanently + tslang_search_for_address_of_symbol=SearchForAddressOfSymbol ; --- async runtime (AsyncRuntime.cpp, extern "C", names already match) --- mlirAsyncRuntimeAddRef diff --git a/tslang/test/tester/test-runner.cpp b/tslang/test/tester/test-runner.cpp index 8645307b4..01e32de7e 100644 --- a/tslang/test/tester/test-runner.cpp +++ b/tslang/test/tester/test-runner.cpp @@ -11,15 +11,13 @@ #endif #ifdef WIN32 #define TYPESCRIPT_LIB "TypeScriptAsyncRuntime.lib " -#define LLVM_LIBS "LLVMSupport.lib " //#define LIBS "msvcrt" _D_ ".lib ucrt" _D_ ".lib " // static CRT (/MT[d]) to match LLVM/TypeScript runtime libs and gc.lib; mixing static+dynamic CRT crashes at startup -#define LIBS "libcmt" _D_ ".lib libvcruntime" _D_ ".lib libucrt" _D_ ".lib ntdll.lib " +#define LIBS "libcmt" _D_ ".lib libvcruntime" _D_ ".lib libucrt" _D_ ".lib " #else // for Ubuntu 20.04 add -ldl and optionally -rdynamic -#define LIBS "-frtti -fexceptions -lstdc++ -lrt -ldl -lpthread -lm -ltinfo" +#define LIBS "-frtti -fexceptions -lstdc++ -lrt -ldl -lpthread -lm" #define TYPESCRIPT_LIB "-lTypeScriptAsyncRuntime " -#define LLVM_LIBS "-lLLVMSupport -lLLVMDemangle " #endif #ifdef WIN32 @@ -179,8 +177,8 @@ void createCompileBatchFile() batFile << "set GC_LIB_PATH=" << TEST_GCPATH << std::endl; batFile << "%TSLANGEXEPATH%\\tslang.exe --emit=obj --entry-point " << tslang_opt << " " << tslang_opt_ext << " %FILEPATH% -o=%FILENAME%.obj" << std::endl; batFile << "%LLVMEXEPATH%\\lld.exe -flavor link %FILENAME%.obj %LINKER_OPTS% " - << LIBS << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << CMAKE_C_STANDARD_LIBRARIES - << " /libpath:%GC_LIB_PATH% /libpath:%LLVM_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" + << LIBS << TYPESCRIPT_LIB << GC_LIB << CMAKE_C_STANDARD_LIBRARIES + << " /libpath:%GC_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" << " /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH%" << std::endl; batFile << "del %FILENAME%.obj" << std::endl; @@ -207,8 +205,8 @@ void createCompileBatchFile() batFile << "LLVM_LIBPATH=" << TEST_LLVM_LIBPATH << std::endl; batFile << "GC_LIB_PATH=" << TEST_GCPATH << std::endl; batFile << "$TSLANGEXEPATH/tslang --emit=obj --entry-point " << tslang_opt << " " << tslang_opt_ext << " $FILEPATH -relocation-model=pic -o=$FILENAME.o" << std::endl; - batFile << TEST_COMPILER << " -o $FILENAME $LINKER_OPTS -L$LLVM_LIBPATH -L$GC_LIB_PATH -L$TSLANG_LIB_PATH $FILENAME.o " - << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << LIBS << std::endl; + batFile << TEST_COMPILER << " -o $FILENAME $LINKER_OPTS -L$GC_LIB_PATH -L$TSLANG_LIB_PATH $FILENAME.o " + << TYPESCRIPT_LIB << GC_LIB << LIBS << std::endl; batFile << "./$FILENAME 1> $FILENAME.txt 2> $FILENAME.err" << std::endl; batFile << "echo $? > $FILENAME.code" << std::endl; batFile << "rm -f $FILENAME.o" << std::endl; @@ -425,8 +423,8 @@ void createMultiCompileBatchFile(std::string tempOutputFileNameNoExt, std::vecto } batFile << "%LLVMEXEPATH%\\lld.exe -flavor link /out:%FILENAME%.exe " << objs.str() << " " - << LIBS << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << CMAKE_C_STANDARD_LIBRARIES - << " /libpath:%GC_LIB_PATH% /libpath:%LLVM_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" + << LIBS << TYPESCRIPT_LIB << GC_LIB << CMAKE_C_STANDARD_LIBRARIES + << " /libpath:%GC_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" << " /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH%" << std::endl; @@ -462,8 +460,8 @@ void createMultiCompileBatchFile(std::string tempOutputFileNameNoExt, std::vecto } batFile << TEST_COMPILER << " -o $FILENAME " << objs.str() - << "-L$LLVM_LIBPATH -L$GC_LIB_PATH -L$TSLANG_LIB_PATH " - << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << LIBS << std::endl; + << "-L$GC_LIB_PATH -L$TSLANG_LIB_PATH " + << TYPESCRIPT_LIB << GC_LIB << LIBS << std::endl; batFile << "./$FILENAME 1> $FILENAME.txt 2> $FILENAME.err" << std::endl; batFile << "echo $? > $FILENAME.code" << std::endl; @@ -546,8 +544,8 @@ void createSharedMultiBatchFile(std::string tempOutputFileNameNoExt, std::vector batFile << sharedBat.str(); batFile << "%LLVMEXEPATH%\\lld.exe -flavor link /out:" << shared_filenameNoExt << ".dll " << linker_opt << " " << shared_objs.str() << " " - << LIBS << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << CMAKE_C_STANDARD_LIBRARIES - << " /libpath:%GC_LIB_PATH% /libpath:%LLVM_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" + << LIBS << TYPESCRIPT_LIB << GC_LIB << CMAKE_C_STANDARD_LIBRARIES + << " /libpath:%GC_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" << " /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH%" << std::endl; @@ -574,8 +572,8 @@ void createSharedMultiBatchFile(std::string tempOutputFileNameNoExt, std::vector batFile << shared_filenameNoExt << ".lib "; } - batFile << LIBS << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << CMAKE_C_STANDARD_LIBRARIES - << " /libpath:%GC_LIB_PATH% /libpath:%LLVM_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" + batFile << LIBS << TYPESCRIPT_LIB << GC_LIB << CMAKE_C_STANDARD_LIBRARIES + << " /libpath:%GC_LIB_PATH% /libpath:%TSLANG_LIB_PATH%" << " /libpath:%LIBPATH% /libpath:%SDKPATH% /libpath:%UCRTPATH%" << std::endl; @@ -648,8 +646,8 @@ void createSharedMultiBatchFile(std::string tempOutputFileNameNoExt, std::vector batFile << sharedBat.str(); batFile << TEST_COMPILER << " " << linker_opt << " -o lib" << shared_filenameNoExt << ".so " << shared_objs.str() - << "-L$LLVM_LIBPATH -L$GC_LIB_PATH -L$TSLANG_LIB_PATH " - << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << LIBS << std::endl; + << "-L$GC_LIB_PATH -L$TSLANG_LIB_PATH " + << TYPESCRIPT_LIB << GC_LIB << LIBS << std::endl; batFile << "rm -f " << shared_objs.str() << std::endl; if (jitRun) @@ -662,7 +660,7 @@ void createSharedMultiBatchFile(std::string tempOutputFileNameNoExt, std::vector { batFile << execBat.str(); batFile << TEST_COMPILER << " -o $FILENAME " << exec_objs.str() << " "; - batFile << "-L$LLVM_LIBPATH -L$GC_LIB_PATH -L$TSLANG_LIB_PATH "; + batFile << "-L$GC_LIB_PATH -L$TSLANG_LIB_PATH "; if (sharedLib) { // dynamics and compile-time shared modes both link the produced shared lib; @@ -670,7 +668,7 @@ void createSharedMultiBatchFile(std::string tempOutputFileNameNoExt, std::vector batFile << "-L`pwd` -Wl,-rpath=`pwd` -l" << shared_filenameNoExt << " "; } - batFile << TYPESCRIPT_LIB << GC_LIB << LLVM_LIBS << LIBS << std::endl; + batFile << TYPESCRIPT_LIB << GC_LIB << LIBS << std::endl; batFile << "rm -f " << exec_objs.str() << std::endl; diff --git a/tslang/tslang/exe.cpp b/tslang/tslang/exe.cpp index 27dfc06c0..80064765d 100644 --- a/tslang/tslang/exe.cpp +++ b/tslang/tslang/exe.cpp @@ -131,16 +131,6 @@ void checkGCLibPath(std::string path) checkFileExistsAtPath(path, libName); } -void checkLLVMLibPath(std::string path) -{ -#ifdef WIN32 - const auto libName = "LLVMSupport.lib"; -#else - const auto libName = "libLLVMSupport.a"; -#endif - checkFileExistsAtPath(path, libName); -} - void checkTslangLibPath(std::string path) { #ifdef WIN32 @@ -247,13 +237,11 @@ std::string getLLVMLibPath() { if (!llvmlibpath.empty()) { - checkLLVMLibPath(llvmlibpath); return llvmlibpath; } if (auto llvmLibEnvValue = llvm::sys::Process::GetEnv("LLVM_LIB_PATH")) { - checkLLVMLibPath(llvmLibEnvValue.value()); return llvmLibEnvValue.value(); } @@ -399,12 +387,10 @@ int buildExe(int argc, char **argv, std::string objFileName, std::string additio std::string gcLibPathOpt; std::string tslangLibPathOpt; - std::string llvmLibPathOpt; std::string emsdkSysRootPathOpt; std::string defaultLibPathOpt; std::string defaultLibFileOpt; - auto isLLVMLibNeeded = true; auto isTslangLibNeeded = true; auto os = TheTriple.getOS(); @@ -418,7 +404,6 @@ int buildExe(int argc, char **argv, std::string objFileName, std::string additio if (wasm) { - isLLVMLibNeeded = false; isTslangLibNeeded = false; } @@ -561,16 +546,6 @@ int buildExe(int argc, char **argv, std::string objFileName, std::string additio } } - // add logic to detect if libs are used and needed - if (isLLVMLibNeeded) - { - llvmLibPathOpt = getLibsPathOpt(getLLVMLibPath()); - if (!llvmLibPathOpt.empty()) - { - args.push_back(llvmLibPathOpt.c_str()); - } - } - if (isTslangLibNeeded) { tslangLibPathOpt = getLibsPathOpt(getTslangLibPath()); @@ -604,8 +579,6 @@ int buildExe(int argc, char **argv, std::string objFileName, std::string additio args.push_back("-llibvcruntimed"); args.push_back("-Wl,-nodefaultlib:libcmt"); } - - args.push_back("-lntdll"); } // tslang libs @@ -636,15 +609,6 @@ int buildExe(int argc, char **argv, std::string objFileName, std::string additio args.push_back("-lTypeScriptAsyncRuntime"); } - if (isLLVMLibNeeded) - { - args.push_back("-lLLVMSupport"); - if (!win) - { - args.push_back("-lLLVMDemangle"); - } - } - if (!win && !wasm) { if (RM && *RM == llvm::Reloc::PIC_) @@ -675,7 +639,6 @@ int buildExe(int argc, char **argv, std::string objFileName, std::string additio args.push_back("-lstdc++"); args.push_back("-lm"); args.push_back("-lpthread"); - args.push_back("-ltinfo"); args.push_back("-ldl"); args.push_back("-lrt"); diff --git a/tslang/tslang/tslang.cpp b/tslang/tslang/tslang.cpp index 6418e45da..3f28e56c0 100644 --- a/tslang/tslang/tslang.cpp +++ b/tslang/tslang/tslang.cpp @@ -139,7 +139,7 @@ cl::opt embedExportDeclarationsAction("embed-declarations", cl::desc("Embe cl::opt defaultlibpath("default-lib-path", cl::desc("JS library path. Should point to folder/directory with subfolder '" DEFAULT_LIB_DIR "' or DEFAULT_LIB_PATH environmental variable"), cl::value_desc("defaultlibpath"), cl::cat(TypeScriptCompilerBuildCategory)); cl::opt gclibpath("gc-lib-path", cl::desc("GC library path. Should point to file 'gc.lib' or GC_LIB_PATH environmental variable"), cl::value_desc("gclibpath"), cl::cat(TypeScriptCompilerBuildCategory)); cl::opt gcsharedlibpath("gc-shared-lib-path", cl::desc("Shared GC library path: the directory with gc.dll's import library 'gc.lib' (gc.dll beside it or in '../bin'). Used under -mm=gc for --emit=dll and for executables that import a shared library, so the process has one collector. Or GC_SHARED_LIB_PATH environmental variable; defaults to '/gcdll'"), cl::value_desc("gcsharedlibpath"), cl::cat(TypeScriptCompilerBuildCategory)); -cl::opt llvmlibpath("llvm-lib-path", cl::desc("LLVM library path. Should point to file 'LLVMSupport.lib' and 'LLVMDemangle' in linux or LLVM_LIB_PATH environmental variable"), cl::value_desc("llvmlibpath"), cl::cat(TypeScriptCompilerBuildCategory)); +cl::opt llvmlibpath("llvm-lib-path", cl::desc("LLVM library path (or LLVM_LIB_PATH environmental variable). Not needed any more: programs no longer link an LLVM library. Accepted so older scripts keep working"), cl::value_desc("llvmlibpath"), cl::cat(TypeScriptCompilerBuildCategory)); cl::opt tslanglibpath("tslang-lib-path", cl::desc("TypeScript Compiler Runtime library path. Should point to file 'TypeScriptAsyncRuntime.lib' or TSLANG_LIB_PATH environmental variable"), cl::value_desc("tslanglibpath"), cl::cat(TypeScriptCompilerBuildCategory)); cl::opt emsdksysrootpath("emsdk-sysroot-path", cl::desc("TypeScript Compiler Runtime library path. Should point to dir '<...>/emsdk/upstream/emscripten/cache/sysroot' or EMSDK_SYSROOT_PATH environmental variable. (used when '-mtriple=wasm32-pc-emscripten')"), cl::value_desc("emsdksysrootpath"), cl::cat(TypeScriptCompilerBuildCategory)); cl::list libs{"lib", cl::desc("Libraries to link statically. (used in --emit=exe)"), cl::ZeroOrMore, cl::MiscFlags::CommaSeparated, cl::cat(TypeScriptCompilerBuildCategory)}; From 425359ca994c2944e234376f4e1f76f68234cb94 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Fri, 18 Sep 2026 13:06:27 +0100 Subject: [PATCH 2/2] Define the shared-library loader symbols in the JIT itself On Linux every JIT run that imports a shared library failed with "Symbols not found: tslang_load_library_permanently, tslang_search_for_address_of_symbol". The old LLVM* names had resolved from tslang's own LLVMSupport; the new ones exist only in TypeScriptRuntime, which has no .def to rename exports on Linux and is not loaded at all under -mm=rc or -mm=none. The JIT now defines both on its main JITDylib, for every platform and memory model, over this process's llvm::sys::DynamicLibrary. The TypeScriptRuntime functions carry the generated names directly, so a Linux build exports them under those names too. Co-Authored-By: Claude Opus 5 --- .../lib/TypeScriptRuntime/DynamicRuntime.cpp | 10 +++--- .../TypeScriptRuntime/TypeScriptRuntime.def | 6 ++-- tslang/tslang/jit.cpp | 31 +++++++++++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp b/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp index d151f8d08..e4f018f73 100644 --- a/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp +++ b/tslang/lib/TypeScriptRuntime/DynamicRuntime.cpp @@ -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); } @@ -34,8 +36,8 @@ void init_dynamicruntime(llvm::StringMap &exportSymbols) exportSymbols[name] = reinterpret_cast(ptr); }; - exportSymbol("tslang_load_library_permanently", &mlir::runtime::LoadLibraryPermanently); - exportSymbol("tslang_search_for_address_of_symbol", &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. diff --git a/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def b/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def index 95546b0bc..0f60d133e 100644 --- a/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def +++ b/tslang/lib/TypeScriptRuntime/TypeScriptRuntime.def @@ -35,9 +35,9 @@ EXPORTS aligned_alloc=AlignedAlloc aligned_free=AlignedFree - ; --- dynamic-library runtime (DynamicRuntime.cpp, extern "C") --- - tslang_load_library_permanently=LoadLibraryPermanently - tslang_search_for_address_of_symbol=SearchForAddressOfSymbol + ; --- dynamic-library runtime (DynamicRuntime.cpp, extern "C", names already match) --- + tslang_load_library_permanently + tslang_search_for_address_of_symbol ; --- async runtime (AsyncRuntime.cpp, extern "C", names already match) --- mlirAsyncRuntimeAddRef diff --git a/tslang/tslang/jit.cpp b/tslang/tslang/jit.cpp index 3bc0fbbb9..e3f7185f5 100644 --- a/tslang/tslang/jit.cpp +++ b/tslang/tslang/jit.cpp @@ -287,6 +287,21 @@ static void jitEnableGCThreads() } } +// A program importing a shared library loads it and resolves its symbols through these two +// (tslang_load_library_permanently / tslang_search_for_address_of_symbol). An executable gets +// them from TypeScriptAsyncRuntime; under the JIT they are defined here, for every memory model, +// because TypeScriptRuntime is only loaded for `gc`. They go through this process's own +// DynamicLibrary, so a lookup also sees the libraries passed with `--shared-libs`. +static int jitLoadLibraryPermanently(const char *fileName) +{ + return llvm::sys::DynamicLibrary::LoadLibraryPermanently(fileName); +} + +static void *jitSearchForAddressOfSymbol(const char *symbolName) +{ + return llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(symbolName); +} + // A failing `assert` in compiled code calls `_assert`, and under --emit=jit that call lands // in whichever CRT the process resolver reaches first - ucrtbase.dll, whose report mode is // nobody's to set from here, and which puts the failure up as a modal message box. In an @@ -677,6 +692,22 @@ int runJit(int argc, char **argv, mlir::ModuleOp module, CompileOptions &compile jit->getMainJITDylib().addGenerator(std::move(*generator)); + // Definitions win over the generator, so these also shadow TypeScriptRuntime's copies + { + llvm::orc::MangleAndInterner interner(jit->getExecutionSession(), jit->getDataLayout()); + llvm::orc::SymbolMap dynamicLibrarySymbols; + dynamicLibrarySymbols[interner("tslang_load_library_permanently")] = { + llvm::orc::ExecutorAddr::fromPtr(&jitLoadLibraryPermanently), llvm::JITSymbolFlags::Exported}; + dynamicLibrarySymbols[interner("tslang_search_for_address_of_symbol")] = { + llvm::orc::ExecutorAddr::fromPtr(&jitSearchForAddressOfSymbol), llvm::JITSymbolFlags::Exported}; + if (auto err = jit->getMainJITDylib().define(llvm::orc::absoluteSymbols(std::move(dynamicLibrarySymbols)))) + { + llvm::WithColor::error(llvm::errs(), "tslang") << "failed to define the shared library loader, error: " << err << "\n"; + llvm::consumeError(std::move(err)); + return -1; + } + } + #ifdef _WIN32 // Bind CRT entry points to tslang.exe's static CRT (/MT[d]) explicitly: the // process generator above resolves from DLL export tables only, so without