Repository navigation
Throw and catch in 32-bit programs (32-bit support, phase 3) - #336
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ThrowInfo, CatchableType and CatchableTypeArray hold i32 cross-references. 64-bit MSVC stores image-base-relative RVAs there; 32-bit x86 stores the absolute address, which is also 4 bytes, so only the value changes. Both RTTI helpers (MLIR-level and LLVM-dialect) now build these through one ehReference() helper gated on targetInfo.usesImageBaseRelativeEH, and declare __ImageBase only when it is referenced. x64 output is unchanged. Adds tslang/test/check-x86-eh.sh, which checks the x86 and x64 IR and the x86 object's relocations (DIR32, not DIR32NB). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On i686-pc-windows-msvc, _CxxThrowException is __stdcall, so its symbol is __CxxThrowException@8 and the link failed on the undecorated name. A new module pass, CxxThrowCallingConvPass, sets x86_stdcallcc on the declaration and on every call and invoke of it. It runs after Win32ExceptionPass (which synthesizes rethrows and turns calls into invokes) and before the optimization pipeline, since a call whose convention differs from its callee's is UB that InstCombine turns into unreachable. Any non-callee use is a hard error. It is gated on targetInfo.stdcallDecoratesCxxThrow, so x64 output is unchanged. check-x86-eh.sh gains the decorated-symbol check at x86 and x64 and an IR check that no call site keeps the C convention, with and without --opt. mask-ir.sh lists 00typeof_union_narrowing as known-noisy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check-x86-eh.sh now links and runs x86 programs that throw. eh_order.ts prints a trace that pins the unwinding order: finally blocks run innermost-first, a throw from a catch reaches the outer try, a rethrow is caught, and execution continues after each. Eleven exception corpus files run beside it; they assert internally, so exit 0 is the check. Every case runs under gc, rc and none, checks the exe is I386, and has a 20 s limit. The rethrow in eh_order.ts uses a typed catch. An untyped catch variable gets a void* handler, which does not match a thrown int, at x64 as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec now describes the calling-convention pass and ehReference as built, records the Phase 4 findings (async exits 139 at i686, 64-bit EH name mangling, untyped catch misses thrown numbers) and the catch_value results. usesImageBaseRelativeEH follows pointer width, as clang's MicrosoftCXXABI does, so 32-bit ARM Windows is absolute too. check-x86-eh.sh requires timeout. 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
This is phase 3 of 32-bit support. 32-bit Windows programs (
-mtriple=i686-pc-windows-msvc) now throw and catch correctly under-mm=gc,-mm=rcand-mm=none. Before this PR, every x86 program that threw failed to link withunresolved external symbol __CxxThrowException.The plan is in
docs/superpowers/plans/2026-09-22-32-bit-phase-3-x86-eh.md. The spec's Phase 3 section now describes what was built.ThrowInfo,CatchableTypeandCatchableTypeArraykeep their i32 fields. On x64 those fields hold image-base-relative RVAs; on 32-bit x86 they hold the absolute address, which is also 4 bytes.ehReferencefunction where it used to repeat the ptrtoint/sub/trunc sequence three times. On x64 it emits exactly the old sequence. On x86 it emitsptrtoint … to i32, and__ImageBaseis not declared. The x86 object gets onlyIMAGE_REL_I386_DIR32relocations, with noDIR32NB.MLIRRTTIHelperVCWin32.h) emits these globals. The LLVM-dialect helper's table emitters (LLVMRTTIHelperVCWin32::setRTTIForTypeand what it calls) have no callers. They were changed only so that neither helper can compute RVAs on x86._CxxThrowExceptionis__stdcallon x86, so its symbol is__CxxThrowException@8.CxxThrowCallingConvPass, setsx86_stdcallccon the declaration and on every call and invoke of it.transform.cppruns it afterWin32ExceptionPassand before the optimization pipeline, and only whenstdcallDecoratesCxxThrowis set.Win32ExceptionPasssynthesizes, and itsToInvoke. A call whose convention differs from its callee's is undefined behaviour, which InstCombine turns intounreachable.llvm,bc,obj,asm,exeanddll.usesImageBaseRelativeEHis decided by pointer width. clang'sMicrosoftCXXABI::isImageRelative()decides it that way too, so 32-bit ARM Windows now gets absolute references as well. i686 and x64 are unchanged.No other compiler change was needed. With these two fixes, every exception corpus file ran correctly at x86.
x64 output
The x64 output is unchanged.
compare-ir.shagainst the pre-change compiler finds only the known nondeterministic files.00typeof_union_narrowingis now listed among them, because the base compiler alone gives two different outputs for it.Found, not fixed here
These are recorded in the spec's open issues.
catch (e)never catches a thrown number or string. This happens on x64 too and is not caused by this PR.throw 1andthrow "s"escape the catch, and the process exits 127. Output printed before the throw is lost, because it is never flushed.ThrowInfofollows the thrown value's static type, while an untyped catch variable gets avoid*handler.throw <any>xand class objects are caught.eh_order.tsusescatch (e: TypeOf<1>)for this reason.catch (e)is the usual TypeScript idiom.try. The cause is not EH.??_R0PEAD@8,@88). tslang's throw and catch share these names, so tslang programs are unaffected. A throw from an x86 tslang DLL is caught by a C++catch (int)in the host. Catching a pointer type thrown by MSVC-compiled x86 code would not match.00catch_valueand00catch_value_minimalexit 0 at i686 under gc, rc and none, in both release and debug.Test plan
Windows Release suite:
ctest -j 16 -C Releasepasses 2766/2766, includingMLIRGenTests. TheTargetInfotests add aarch64 and thumbv7 Windows cases.tslang/test/check-x86-eh.shpasses 53/53 in about 20 s. It checks:__CxxThrowException@8at x86 and_CxxThrowExceptionat x64;--opt;tslang/test/x86/eh_order.ts, whose exact finally/catch/rethrow trace is checked;Before the fixes, the IR and object checks failed with the old compiler. The script is run by hand, not in ctest; it needs the phase 2 x86 libraries, and it builds release only.
Checked by hand at i686:
using/dispose and01try_*files;eh_order.ts;--emit=dllboundary, caught by a C++catch (int);--emit=obj) without a compiler crash.check-x86-run.shpasses 6/6 andcheck-x86-libs.shpasses 11/11.Linux CI. Not run locally. The new pass is compiled on every platform but only added for i686 MSVC.
🤖 Generated with Claude Code