Repository navigation
Take pointer width from the target triple (32-bit support, phase 1) - #334
Merged
Merged
Conversation
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
…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>
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>
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>
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>
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>
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
The compiler now takes pointer width from the target triple instead of a hand-written list of 64-bit architectures. This is phase 1 of 32-bit support; the design for all four phases is in
docs/superpowers/specs/2026-09-20-32-bit-compilation-design.md.TargetInfo(include/TypeScript/TargetInfo.h) is built once from the triple inprepareOptions()and stored onCompileOptions. It holds the pointer width, fromTriple::getArchPointerBitWidth(), and three policy flags for later phases.sizeBitsis now an accessor over it.aarch64_32as 64-bit (ARM64_32 is ILP32), and it left out systemz, sparcv9, amdgcn and ve, which therefore got 32.TypeHelper::getSizeType()/getPointerIntType().TypeHelpertakes an optionalCompileOptions; only the sites that need a width pass it. Without it,getSizeType()callsreport_fatal_errorin every build configuration, not just Debug.getI64Type()audit. Each of the 18 call sites outside the Win32 RTTI helpers is now marked either target-width (13, changed) or 64-bit whatever the target (5, unchanged). The audit fixed two 32-bit bugs:-1sentinel for a missing optional interface member was built ati64but compared at a different width. At i686 that was invalid IR ('llvm.icmp' op requires all operands to have the same type), which broke 12 test files.sitofp, so a 32-bit address at or above0x80000000became negative. It is nowuitofp.target datalayoutis set when the module is generated, so--emit=llvmoutput names its layout, not only its triple. A triple with no registered target is now an error at this point instead of silently emitting IR without a layout.Behavior change
--emit=jit -mtriple=<a non-host target>used to run x64 code and exit 0, because the JIT replaced the module's triple with the host's. It now refuses:Nothing under
test/uses--emit=jitwith-mtriple.x64 output
Unchanged, apart from the added
target datalayoutline.Not in this PR
The lowering's
LLVMTypeConverteris still built with LLVM's default data layout for every non-wasm target, so at i686 struct layout, union sizing andgetIntPtrTypestill assume 8-byte pointers. Fixing that changes x64 output as well: the default layout alignsi64at 32 bits, x64's real one at 64. So it is left for the first task of phase 2, which blocks any native 32-bit test. The spec records this, together with the wasm32 layout hardcoded in the same place, which is also wrong.Test plan
ctest -j 16 -C Releasepasses 2765/2765 at the head commit.MLIRGenTests: 142/142, including the newTargetInfoTestcases (widths across triples, arches the old list got wrong, EH and JIT policy,TypeHelper::getSizeType).--emit=llvmoutput diffed against the pre-change compiler: only the addedtarget datalayoutline differs.test/check-datalayout.sh(run by hand, not wired into ctest): correct layout fori686-pc-windows-msvc,wasm32-unknown-unknownandx86_64-pc-windows-msvc; an unknown triple fails and names it.00interface_optional.tsnow compiles at i686 (icmp eq i32 …, -1).--emit=jitrefuses i686 and wasm32; still runs with no-mtripleand with-mtriple=x86_64-pc-windows-msvc.🤖 Generated with Claude Code