From a682c0890d23782562d8011ac26c5614973547cd Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 21 Oct 2026 20:08:44 +0100 Subject: [PATCH 1/3] JIT: find the GC runtime next to tslang, and say so when it is missing The JIT looked for TypeScriptRuntime in --tslang-lib-path, ../lib and the current folder, never in tslang's own folder - where the release package puts it. Run from a project folder (a VS Code launch with cwd = workspace) it found none and went on without one, so on Linux the default library failed mid-run with "symbol lookup error: ... undefined symbol: GC_malloc". tslang's own folder is now the last place it looks, and -mm=gc with no runtime anywhere is an error naming the file and the two ways to pass it. The Linux collector test runs a hard-linked tslang from a package-like folder with no --tslang-lib-path: refused without the runtime next to it, passing with. Co-Authored-By: Claude Opus 5.5 --- tslang/test/tester/defaultlib-collector.cmake | 25 +++++++++ tslang/tslang/jit.cpp | 55 ++++++++++--------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/tslang/test/tester/defaultlib-collector.cmake b/tslang/test/tester/defaultlib-collector.cmake index 8d13609ee..73689bc03 100644 --- a/tslang/test/tester/defaultlib-collector.cmake +++ b/tslang/test/tester/defaultlib-collector.cmake @@ -138,6 +138,31 @@ if(MODE STREQUAL "jit") if(NOT WIN32) run_program("JIT, default library, runtime not passed" "${WORK_DIR}/alone" "${TSLANG}" --emit=jit ${common} "${alone}") + + # Laid out as the release package is, the runtime next to tslang, and run from another + # folder with no --tslang-lib-path: tslang finds it in its own folder. Without it there, + # a clear error rather than the loader's "undefined symbol: GC_malloc" mid-run. A hard + # link, so /proc/self/exe names the package folder (a symlink resolves to the build). + set(package "${WORK_DIR}/package") + file(REMOVE_RECURSE "${package}") + file(MAKE_DIRECTORY "${package}") + file(CREATE_LINK "${TSLANG}" "${package}/tslang" COPY_ON_ERROR) + set(no_lib_path ${common}) + list(FILTER no_lib_path EXCLUDE REGEX "^--tslang-lib-path=") + + execute_process(COMMAND "${package}/tslang" --emit=jit ${no_lib_path} "${alone}" + WORKING_DIRECTORY "${WORK_DIR}/alone" + OUTPUT_VARIABLE out + ERROR_VARIABLE err + RESULT_VARIABLE status) + if(status EQUAL 0 OR NOT err MATCHES "needs the GC runtime, libTypeScriptRuntime.so") + message(FATAL_ERROR "JIT with no runtime to find: exit ${status}\n${out}\n${err}") + endif() + message(STATUS "JIT with no runtime to find: refused") + + file(COPY "${runtime}" DESTINATION "${package}") + run_program("JIT, default library, runtime next to tslang" "${WORK_DIR}/alone" + "${package}/tslang" --emit=jit ${no_lib_path} "${alone}") endif() elseif(MODE STREQUAL "compile") run("--emit=exe alone, default library" "${WORK_DIR}/alone" diff --git a/tslang/tslang/jit.cpp b/tslang/tslang/jit.cpp index 50b012e1a..f687936b7 100644 --- a/tslang/tslang/jit.cpp +++ b/tslang/tslang/jit.cpp @@ -74,6 +74,7 @@ std::string mergeWithDefaultLibPath(std::string, std::string); std::string getDefaultLibSubDir(bool, bool, const char *, const llvm::Triple &); std::string getDefaultLibBinary(bool, const CompileOptions &, const llvm::Triple &); std::string makeAbsolutePath(std::string); +std::string getExecutablePath(const char *); int registerMLIRDialects(mlir::ModuleOp); std::function getTransformer(bool, int, int, CompileOptions&); @@ -649,39 +650,41 @@ static int prepareJitProcess(CompileOptions &compileOptions, bool nativeImage) return llvm::sys::path::stem(libPath).contains_insensitive("TypeScriptRuntime"); }); - std::string pathTypeScriptLib("../lib/" LIB_NAME "TypeScriptRuntime." LIB_EXT); if (compileOptions.needsGCRuntime() && !hasTypeScriptRuntime && !nativeImage) { - auto absPath3 = makeAbsolutePath(mergeWithDefaultLibPath(getTslangLibPath(), LIB_NAME "TypeScriptRuntime." LIB_EXT)); - if (absPath3.empty()) + // --tslang-lib-path (TSLANG_LIB_PATH), then ../lib and the current folder, then the folder + // of tslang itself - where the release package puts it, and the build its bin folder. A + // run started from a project folder finds it only there. + llvm::SmallString<256> exeRuntimePath(getExecutablePath("tslang")); + llvm::sys::path::remove_filename(exeRuntimePath); + llvm::sys::path::append(exeRuntimePath, LIB_NAME "TypeScriptRuntime." LIB_EXT); + + std::string runtimePath; + for (auto candidate : {mergeWithDefaultLibPath(getTslangLibPath(), LIB_NAME "TypeScriptRuntime." LIB_EXT), + std::string("../lib/" LIB_NAME "TypeScriptRuntime." LIB_EXT), + std::string(LIB_NAME "TypeScriptRuntime." LIB_EXT), + exeRuntimePath.str().str()}) { - auto absPath = makeAbsolutePath(pathTypeScriptLib); - if (absPath.empty()) - { - pathTypeScriptLib = LIB_NAME "TypeScriptRuntime." LIB_EXT; - auto absPath2 = makeAbsolutePath(pathTypeScriptLib); - if (absPath2.empty()) - { - /* - llvm::WithColor::error(llvm::errs(), "tslang") << "JIT initialization failed. Missing GC library. Did you forget to provide it via " - "'--shared-libs=" LIB_NAME "TypeScriptRuntime." LIB_EXT "'? or you can switch it off by using '-mm=none'\n"; - return -1; - */ - } - else - { - clSharedLibs.push_back(absPath2); - } - } - else + runtimePath = makeAbsolutePath(candidate); + if (!runtimePath.empty()) { - clSharedLibs.push_back(absPath); + break; } - } - else + } + + // Not left to fail later: the default library and the program call GC_malloc & co., and + // with no runtime loaded that surfaces as an unresolved symbol (on Linux a "symbol lookup + // error" from the loader in the middle of the run) that never names the missing file. + if (runtimePath.empty()) { - clSharedLibs.push_back(absPath3); + llvm::WithColor::error(llvm::errs(), "tslang") + << "-mm=gc needs the GC runtime, " LIB_NAME "TypeScriptRuntime." LIB_EXT ", and it is not " + << "next to tslang, in the current folder or in --tslang-lib-path. Pass its folder with " + << "--tslang-lib-path, or the file with --shared-libs.\n"; + return -1; } + + clSharedLibs.push_back(runtimePath); } // After the runtime: on Linux the default library leaves GC_* undefined and takes them from From 17b305a3cf9331afabc20ee8e43750036b8beb32 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 21 Oct 2026 20:11:47 +0100 Subject: [PATCH 2/3] defaultlib-collector: copy the runtime's symlink chain into the package folder Co-Authored-By: Claude Opus 5.5 --- tslang/test/tester/defaultlib-collector.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tslang/test/tester/defaultlib-collector.cmake b/tslang/test/tester/defaultlib-collector.cmake index 73689bc03..67e9179c1 100644 --- a/tslang/test/tester/defaultlib-collector.cmake +++ b/tslang/test/tester/defaultlib-collector.cmake @@ -160,7 +160,8 @@ if(MODE STREQUAL "jit") endif() message(STATUS "JIT with no runtime to find: refused") - file(COPY "${runtime}" DESTINATION "${package}") + # the build's runtime is a symlink to its versioned file + file(COPY "${runtime}" DESTINATION "${package}" FOLLOW_SYMLINK_CHAIN) run_program("JIT, default library, runtime next to tslang" "${WORK_DIR}/alone" "${package}/tslang" --emit=jit ${no_lib_path} "${alone}") endif() From e658a301225c4ad08ab32464f92b9e2564dae7a6 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Wed, 21 Oct 2026 20:16:11 +0100 Subject: [PATCH 3/3] JIT: also look for the runtime in ../lib beside tslang (the Linux build layout) Without it, a JIT run in the Linux build tree with no runtime passed stopped on the missing-runtime error before lowering could report the program's own error (test-jit-main-argv-string-array-error). Co-Authored-By: Claude Opus 5.5 --- tslang/tslang/jit.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tslang/tslang/jit.cpp b/tslang/tslang/jit.cpp index f687936b7..96c0b8599 100644 --- a/tslang/tslang/jit.cpp +++ b/tslang/tslang/jit.cpp @@ -652,18 +652,23 @@ static int prepareJitProcess(CompileOptions &compileOptions, bool nativeImage) if (compileOptions.needsGCRuntime() && !hasTypeScriptRuntime && !nativeImage) { - // --tslang-lib-path (TSLANG_LIB_PATH), then ../lib and the current folder, then the folder - // of tslang itself - where the release package puts it, and the build its bin folder. A - // run started from a project folder finds it only there. - llvm::SmallString<256> exeRuntimePath(getExecutablePath("tslang")); - llvm::sys::path::remove_filename(exeRuntimePath); + // --tslang-lib-path (TSLANG_LIB_PATH), then ../lib and the current folder, then beside + // tslang itself: its own folder - where the release package puts it, and the Windows build + // its bin folder - and ../lib, where the Linux build does. A run started from a project + // folder finds it only there. + llvm::SmallString<256> exeDir(getExecutablePath("tslang")); + llvm::sys::path::remove_filename(exeDir); + llvm::SmallString<256> exeRuntimePath(exeDir); llvm::sys::path::append(exeRuntimePath, LIB_NAME "TypeScriptRuntime." LIB_EXT); + llvm::SmallString<256> exeLibRuntimePath(exeDir); + llvm::sys::path::append(exeLibRuntimePath, "..", "lib", LIB_NAME "TypeScriptRuntime." LIB_EXT); std::string runtimePath; for (auto candidate : {mergeWithDefaultLibPath(getTslangLibPath(), LIB_NAME "TypeScriptRuntime." LIB_EXT), std::string("../lib/" LIB_NAME "TypeScriptRuntime." LIB_EXT), std::string(LIB_NAME "TypeScriptRuntime." LIB_EXT), - exeRuntimePath.str().str()}) + exeRuntimePath.str().str(), + exeLibRuntimePath.str().str()}) { runtimePath = makeAbsolutePath(candidate); if (!runtimePath.empty())