Repository navigation
Stop linking LLVMSupport and LLVMDemangle into compiled programs - #333
Merged
Merged
Conversation
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 21, 2026
…334) * Add design for 32-bit compilation Four-phase plan for compiling to 32-bit targets, with i686-pc-windows-msvc as the proof target and width-correctness that generalizes to any 32-bit triple. Probing the current build first moved most of the scope. #333 removing the LLVMSupport dependency turns out to have been the gate: -mm=none already produces a working 32-bit .exe. What remains is a 32-bit Boehm and runtime, the __stdcall decoration on _CxxThrowException, and the image-base subtraction in the Win32 RTTI helpers, which x86 must not do. Two assumptions the probing disproved are recorded in the spec, since the emitted IR reads the other way: the async runtime's i64 parameters are int64_t rather than size_t and so are already correct on x86, and the ThrowInfo/CatchableType layouts coincide between the two ABIs because an x86 absolute pointer is the same width as an x64 RVA. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add Phase 1 implementation plan for 32-bit support Six TDD tasks: TargetInfo and its derivation from the triple, carrying it on CompileOptions and deleting the rotted arch list, target-width accessors on TypeHelper, the data layout on the emitted module, the i64 audit, and the --emit=jit guard. Deviates from the spec on one point. The spec calls threading CompileOptions into TypeHelper "a wide but mechanical diff"; counting found 152 construction sites, nearly all of which only ask for width-independent types. The options are optional instead, and getSizeType() asserts rather than guessing, so only the ~20 sites that need a width are touched. Every LLVM and MLIR API the plan calls was checked against the headers in 3rdParty/llvm/x64/release rather than written from memory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fix three defects the pre-flight scan found in the Phase 1 plan The JIT guard cited a line inside the dumpToObjectFile path, reached only after the engine is built - it would never have fired on the ordinary JIT path. Moved to the top of runJit. The gate demanded the two RTTI helpers show an empty diff, which Task 2 necessarily violates: it has to change sizeBits to sizeBits() in both. Narrowed to what the spec actually reserves for Phase 3, the getI64Type arithmetic. The audit's count of 20 included the definition of getI64Type itself. Nineteen are call sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Derive target width and policy from the triple TargetInfo answers what the compiler needs to know about a target: pointer width, and the policy questions Win32 EH and the JIT ask. Named for the decisions rather than the arches, so a new target answers them instead of each call site growing an arch comparison. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Correct the unittest binary path in the Phase 1 plan MLIRGenTests.exe lands under unittests/MLIRGen/Debug/, not bin/. bin/ exists and holds tslang.exe, which is why the wrong path looked plausible. Found by the Task 1 implementer; fixed here so Tasks 2, 3 and 5 do not each rediscover it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Diff x64 IR against a stored baseline, not a stashed rebuild Tasks 2 and 5 verified no-x64-change by stashing the work and rebuilding Release to regenerate a 'before' - a second full LLVM link each time, with uncommitted work on the stash while it ran. The release binary predating Phase 1 was still on disk, so the baseline is captured once from it and stored in the plan's workspace. Same check, one rebuild instead of two, and nothing goes on the stash. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Take pointer width from the triple, not from an arch list opts.cpp listed 23 architectures it considered 64-bit. The list had already rotted: loongarch64 was on it, riscv32 appeared nowhere. Triple already knows, so ask it. sizeBits stays, as an accessor over TargetInfo, so it cannot drift from the one place the width is decided. Updated every reader of compileOptions.sizeBits to call it (grep found the same ~20 call sites the spec's audit listed, no more). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Correct why the arch list was wrong I wrote that the list had rotted because loongarch64 was on it and riscv32 was absent. Both are correct: the list marks arches as 64-bit and defaults to 32, so loongarch64 belongs on it and riscv32 belongs off it. Neither was evidence of anything. The real defect, found by enumerating all 20 entries against LLVM's width table: aarch64_32 was marked 64-bit. ARM64_32 is ILP32 and its pointers are 32 bits. Replacing the list with getArchPointerBitWidth() fixes that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Give TypeHelper target-width accessors getSizeType() is for things that are as wide as a pointer; getI64Type() stays for things that are 64-bit whatever the target is. Keeping both means the audit in the next commit can record which each site meant. The options are optional because 152 sites construct TypeHelper and almost none need a width. getSizeType() asserts rather than guessing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fail identifiably when getSizeType() has no target in Release assert() alone is a no-op under NDEBUG, and this project's CI builds Release, so a missing CompileOptions was guessing a width via a null dereference with no message naming the function or the cause. Keep the assert for fast local diagnosis, add report_fatal_error so the failure is identifiable in every build configuration. Also record in getPointerIntType()'s comment why it stays a separate name from getSizeType() despite currently just delegating to it: the call-site audit needs to tell the two concepts apart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Set the data layout on the emitted module The layout was only set in obj.cpp, from the TargetMachine, which is after --emit=llvm has printed. So IR emitted for a 32-bit triple carried LLVM's default 64-bit layout, and anything reading sizes between MLIRGen and obj.cpp was guessing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Make unresolvable-triple datalayout lookup fatal, not silent Task 4 review, round 1. If TargetRegistry::lookupTarget or createTargetMachine failed, the module was emitted anyway with a target triple but no datalayout -- exactly the bug this phase exists to fix, and reachable only from --emit=llvm since dump.cpp never consults the target registry itself. Both failure modes now emit a diagnostic naming the triple and fail the codegen, matching how obj.cpp treats the same lookup. Also: use the non-deprecated lookupTarget(const Triple&, std::string&) overload instead of the StringRef one slated for removal after LLVM 22 branches, and qualify the comment's claim that the derived layout matches obj.cpp's -- true for x86, not guaranteed once obj.cpp threads a -target-abi into TargetOptions for ABI-sensitive backends. Added a fourth case to check-datalayout.sh: a garbage triple must now fail with a non-zero exit and an error naming the triple. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Pin the audit to a verified list of 18 call sites A grep for getI64Type returns 21 hits outside the reserved RTTI helpers, but three are not call sites: two definitions and one comment mentioning the name. The plan's count drifted twice for this reason. The list is now generated once, with exclusions applied, and the task reads it instead of re-deriving it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Classify every i64 in lowering as fixed-width or target-width Each of the 18 non-EH getI64Type() call sites now says which it meant. Thirteen are pointer and size arithmetic and now ask the target; five are genuinely 64-bit and keep i64 with a comment saying why. The one that needed looking up rather than guessing was Boehm's GC_descr: it is a typedef of GC_word, which gc.h defines as the integer the size of void *, not a fixed 64 bits. So the whole typed-GC descriptor group moves together - the global, the bitmap method's return type, the GCMakeDescriptorOp result and the two extern declarations - or the op and its callee would disagree on width. The interface -1 slot sentinels move for the same reason: the lowering that tests them already builds its -1 at compileOptions.sizeBits(). Left alone: the bigint literal type and the numeric promotion ladder (the language's own 64-bit integer), _i64toa's first parameter (long long, not size_t), and the createI64ConstantOf/getI64AttrValue helpers, whose width is their contract. x64 IR is unchanged, which is the check that the classification is right: a target-width site emits i64 on x64 either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Give ctest its -C Release, without which nothing runs This is a multi-config MSBuild generator, so a bare 'ctest -j 16' reports all 2765 tests as (Not Run) and exits with errors. That reads as a catastrophic regression and is nothing of the sort. Found by the Task 5 implementer; confirmed here by running both forms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Make the pointer->float cast unsigned and widen the GC descriptor op contract Review fallout from classifying the i64 sites, not from the calls themselves. The pointer->float cast now uses uitofp. While ptrtoint produced a fixed i64 the zero-extension made the signedness moot; now that the integer is only as wide as the pointer, any address at or above 0x80000000 - reachable in a /LARGEADDRESSAWARE i686 process or a large wasm32 memory - would have read back as a negative float. An address is unsigned, which is also what the ZExtOp two branches down already assumes. GCMakeDescriptorOp's result and GCNewExplicitlyTypedOp's operand were still constrained to I64 in the ODS. The generated build() takes the result type verbatim so construction would have worked, but verification would have rejected the i32 descriptor those ops now carry on a 32-bit target. They are the only two I64 operand/result constraints in the file. Three comments corrected: createI64ConstantOf's claimed an invariant the codebase does not hold (LowerToLLVM.cpp passes it a byte count), the promotion-ladder one called getIndexType() fixed-width when it is the one thing there that is not, and getTypeBitmapValueType() now records that it is the same GC_word contract and the one remaining width gap in that path, deliberately left for a later phase. x64 IR is still byte-identical for both baseline samples; suite 2765/2765. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Refuse --emit=jit for a target this process cannot run Verified before this change: with the release binary, both `--emit=jit -mm=none --no-default-lib -mtriple=i686-pc-windows-msvc` and the same with `-mtriple=wasm32-unknown-unknown` printed "Hello 32" and exited 0. tslang.exe is an x64 process; jit.cpp:634 overwrites the module's target triple with the host TargetMachine's, so it silently ran x64 code while the user asked for i386 or wasm. Added a guard as the first statement of runJit(), before any LLVM target initialization, that checks CompileOptions::targetInfo.supportsInProcessJit (true only when the requested target's arch and OS match the host's) and refuses with a message naming both the host triple and the requested one. Left to LLJIT, the same mismatch surfaces later as a relocation or "symbol not found" failure deep in the session that never mentions the triple as the cause. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Compare the optional-member sentinel at target width on both sides InterfaceSymbolRefOpLowering built its ptrtoint at llvmtch.getIntPtrType(0), which comes from the type converter's data layout - LLVM's default, 64-bit pointers, for every non-wasm target - while the -1 it compared against was built at compileOptions.sizeBits(). On i686 that was an i64 against an i32 and verification failed ("'llvm.icmp' op requires all operands to have the same type") for any optional interface member. Both sides now take the width from compileOptions. The ptrtoint has to be at 32 bits on i686: at i64 it would zero-extend the 0xFFFFFFFF sentinel and never compare equal. The constant is built directly rather than through createIConstantOf, whose unsigned value parameter would give 0xFFFFFFFF, not -1, at 64 bits. x64 IR is unchanged for both baseline samples and 00interface_optional.ts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Make the old-arch-list test cover what the list actually got wrong The comment said the old opts.cpp list had rotted because loongarch64 was on it and riscv32 appeared nowhere. Neither is a fault: the list named 64-bit arches and defaulted to 32, so loongarch64, riscv32 and armv7 all came out right. What it got wrong was aarch64_32 listed as 64-bit (already covered by Arm64_32IsAnIlp32Target) and 64-bit arches it did not list, which it gave 32: systemz, sparcv9, amdgcn, ve, and also spirv and riscv64be. PointerWidthIsRightForArchesTheOldListMissed now asserts 64 for s390x, sparcv9, amdgcn and ve, each a spelling llvm::Triple's parseArch maps to the intended arch, so it fails against the old list. Also marks test/check-datalayout.sh executable; it was committed 100644. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Bring the 32-bit spec and Phase 1 plan in line with what was built Spec: Phase 1 is marked implemented. Drops the parsed llvm::Triple on CompileOptions and indexBits, neither of which exists. Describes TypeHelper's optional CompileOptions pointer and getSizeType()'s report_fatal_error in place of the "wide but mechanical" parameter. Corrects the audit count to 18 call sites, records its result (13 target-width, 5 genuinely 64-bit) and the two latent bugs it found. Re-scopes the Phase 1 gate: the lowering's type converter still uses LLVM's default data layout for non-wasm targets, so 64-bit-shaped layout on i686 is not yet gone; deriving that layout from the module becomes Phase 2's first task, with its own x64 diff. Records the ABI-by-environment width disagreement as an open issue and the i686 for-await failures as a Phase 4 finding. Plan: same Triple and getSizeType() corrections, 20-entry arch list, the gate allows the one added target datalayout line, the 18-site list is inlined, and the baseline directory is marked local-only scratch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 25, 2026
#333 took LLVMSupport, LLVMDemangle and ntdll out of the CMake template's link libraries, but --cmake and --new still read the LLVM lib path into an LLVM_LIB_PATH substitution. No CMake template uses it, and the VSCode tasks and launch configurations passed it only as --llvm-lib-path, which tslang ignores. Drop the substitution and those arguments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Sep 25, 2026
) * Stop shipping LLVMSupport and LLVMDemangle in the release packages Since #333 no compiled program links an LLVM library: the linker never gets the --llvm-lib-path folder, only the default-lib build environment and the cmake/vscode templates do. Drop LLVMSupport.lib from the Windows zip and libLLVMSupport.a/libLLVMDemangle.a from the Linux tarballs, and from the memory-model measuring script's link line. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Stop passing the LLVM lib path into the project templates #333 took LLVMSupport, LLVMDemangle and ntdll out of the CMake template's link libraries, but --cmake and --new still read the LLVM lib path into an LLVM_LIB_PATH substitution. No CMake template uses it, and the VSCode tasks and launch configurations passed it only as --llvm-lib-path, which tslang ignores. Drop the substitution and those arguments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Compiled programs no longer link any LLVM library. Two things used to require it:
AsyncRuntimeCommon.incusedllvm::DefaultThreadPool. It now has its ownstd::threadpool that behaves the same way: one worker per hardware thread, started on demand, andwait()covers tasks queued by other tasks. The JIT's runtime shares this file.LLVMLoadLibraryPermanently/LLVMSearchForAddressOfSymbol. Those calls are renamed totslang_load_library_permanently/tslang_search_for_address_of_symbol:TypeScriptAsyncRuntime/DynamicRuntime.cppdefines them:LoadLibraryW/GetProcAddresson Windows,dlopen/dlsymon Linux. Like LLVM, it loads each library once and searches symbols in load order.TypeScriptRuntimeexports them under the new names (.defand the export table).The driver, test runner, generated CMake template and
docs/how/debug/debug-shared.batno longer link LLVMSupport, LLVMDemangle,ntdllor-ltinfo. The last two were only there for LLVMSupport.--llvm-lib-path/LLVM_LIB_PATHare still accepted but ignored, so CI, the default-lib build scripts and existing user scripts keep working.Test plan
llvm-nm:TypeScriptAsyncRuntime.liband the default lib (gc/rc/none) need no LLVM symbols.tslang --emit=exebuilds and runs async programs withLLVM_LIB_PATHunset.ntdll.lib.dlopenpath, and linking without-lLLVMDemangle/-ltinfo. Not run locally.Objects built by an older compiler still reference the
LLVM*names and need rebuilding. The default lib is rebuilt per compiler version.🤖 Generated with Claude Code