Skip to content

Commit b61e6c6

Browse files
Throw and catch in 32-bit programs (32-bit support, phase 3) (#336)
* Add the Phase 3 plan for 32-bit support (x86 exception handling) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Make EH cross-references absolute on 32-bit x86 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> * Make _CxxThrowException stdcall on 32-bit x86 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> * Run exception programs as 32-bit exes under every model 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> * Bring the spec up to date with Phase 3; decide EH RVAs by pointer width 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> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent adbd38b commit b61e6c6

14 files changed

Lines changed: 846 additions & 103 deletions

File tree

‎docs/superpowers/plans/2026-09-22-32-bit-phase-3-x86-eh.md‎

Lines changed: 415 additions & 0 deletions
Large diffs are not rendered by default.

‎docs/superpowers/specs/2026-09-20-32-bit-compilation-design.md‎

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Design for compiling TypeScript to 32-bit targets, with `i686-pc-windows-msvc`
44
as the proof target and width-correctness that generalizes to any 32-bit triple.
55

6-
Status: approved design. Phase 1 implemented; phases 2-4 not.
6+
Status: approved design. Phases 1-3 implemented; phase 4 not.
77

88
## Why now
99

@@ -202,24 +202,46 @@ real layout under `-mm=gc`, `rc` and `none`.
202202

203203
Two changes, both narrow because of the finding above.
204204

205-
1. `_CxxThrowException` is `__stdcall` on x86. The block already present but
206-
commented out at `lib/TypeScriptExceptionPass/Win32ExceptionPass.cpp:696`
207-
sets `CallingConv::X86_StdCall`, which makes LLVM emit
208-
`__CxxThrowException@8`. Gated on `stdcallDecoratesCxxThrow`.
209-
2. The image-base subtraction in `LLVMRTTIHelperVCWin32.h` (three sites) and
210-
the corresponding logic in `MLIRRTTIHelperVCWin32.h` is gated on
211-
`usesImageBaseRelativeEH`. On x86 the field takes the `ptrtoint` result
212-
directly. The `ptrtoint` and `sub` widths come from `TargetInfo` rather than
213-
being hardcoded `i64`.
205+
1. `_CxxThrowException` is `__stdcall` on x86, so its symbol is
206+
`__CxxThrowException@8`. **As built:** a module pass,
207+
`CxxThrowCallingConvPass` (`lib/TypeScriptExceptionPass`), sets
208+
`CallingConv::X86_StdCall` on the declaration and on every call and invoke
209+
of it. `transform.cpp` adds it after `Win32ExceptionPass` and before the
210+
optimization pipeline, gated on `stdcallDecoratesCxxThrow`. It is one pass
211+
rather than a fix at each declaration because three places create calls:
212+
the MLIR lowering (`ThrowLogic.h`), the rethrow `Win32ExceptionPass`
213+
synthesizes, and its `ToInvoke`. A call whose convention differs from its
214+
callee's is undefined behaviour, which InstCombine turns into `unreachable`.
215+
Any use other than as a direct callee is a hard error. The commented-out
216+
block that used to sit in `Win32ExceptionPass::getThrowFn` is gone.
217+
2. The cross-references in `ThrowInfo`, `CatchableType` and
218+
`CatchableTypeArray` are built by one `ehReference` function in each Win32
219+
RTTI helper. When `usesImageBaseRelativeEH` holds it emits
220+
`trunc(ptrtoint - ptrtoint __ImageBase)` at `sizeBits()` width, which is
221+
exactly the old x64 output; otherwise it emits `ptrtoint` to i32, and
222+
`__ImageBase` is not declared. Only the MLIR-level helper
223+
(`MLIRRTTIHelperVCWin32.h`) emits these globals; the LLVM-dialect helper's
224+
table emitters (`LLVMRTTIHelperVCWin32::setRTTIForType` and what it calls)
225+
have no callers, and were changed only so neither helper can compute RVAs
226+
on x86. `usesImageBaseRelativeEH` is decided by pointer width, as clang's
227+
`MicrosoftCXXABI::isImageRelative()` does, so 32-bit ARM Windows is absolute
228+
as well.
214229

215230
**Testing.** Tests assert that control flow reaches the catch, and on
216231
unwinding order. They do not assert on the catch variable's contents: reading a
217232
catch variable's value is a known-unreliable area independent of this work, and
218233
a test written on it would fail for reasons that have nothing to do with 32-bit
219234
support.
220235

236+
`tslang/test/check-x86-eh.sh` (hand-run, needs the phase 2 x86 libraries,
237+
release only) checks the IR and object form of both changes, then links and
238+
runs `tslang/test/x86/eh_order.ts` (an exact unwinding trace) and eleven
239+
exception corpus files as 32-bit exes under gc, rc and none. Debug x86 builds,
240+
the `using`/dispose corpus files and a throw crossing an x86 `--emit=dll`
241+
boundary into a C++ `catch (int)` were verified by hand.
242+
221243
**Gate.** throw/catch, nested catch, try/finally and rethrow behave correctly
222-
as 32-bit binaries.
244+
as 32-bit binaries. Met: no compiler change beyond the two above was needed.
223245

224246
## Phase 4 — Default library and the suite
225247

@@ -272,6 +294,24 @@ confusion:
272294
(`AsyncToLLVM.cpp:1026`) builds `LowerToLLVMOptions(ctx)` with LLVM's default
273295
layout and a 64-bit index, so its types disagree with the i686 type converter's.
274296

297+
- **Async programs crash at i686 (phase 4).** Found in phase 3: every async
298+
program, including `00async_await` and one with no `try`, exits 139 as a
299+
32-bit exe. Not EH-related.
300+
- **x86 EH type names use the 64-bit mangling (phase 4, interop only).** The
301+
hardcoded names in `LLVMRTTIHelperVCWin32Const.h` (`??_R0PEAD@8`,
302+
`_CT??_R0PEAD@88`) carry the `__ptr64` qualifier and 64-bit sizes. tslang's
303+
throw and catch share them, so tslang programs are unaffected, and a C++
304+
`catch (int)` across a DLL boundary works; catching a *pointer* type thrown
305+
by MSVC-compiled x86 code would not match.
306+
- **`00catch_value*` at i686.** Both exit 0 under gc, rc and none, release and
307+
debug, as of phase 3.
308+
- **Untyped `catch (e)` does not catch a thrown number (not 32-bit; x64 too).**
309+
The `ThrowInfo` follows the thrown value's static type
310+
(`MLIRGenStatements.cpp` ~1049-1067) while an untyped catch variable gets a
311+
`void*` handler (`??_R0PEAX@8`), so `throw 1` / `throw "s"` escape it and the
312+
process exits 127 with its earlier output unflushed. `throw <any>x` and class
313+
objects are caught. Pre-existing; `eh_order.ts` uses `catch (e: TypeOf<1>)`.
314+
275315
## Out of scope
276316

277317
- `-m32` shorthand. `-mtriple=i686-pc-windows-msvc` already works and is the

‎tslang/include/TypeScript/LowerToLLVM/LLVMRTTIHelperVCWin32.h‎

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,13 @@ class LLVMRTTIHelperVCWin32
278278

279279
LogicalResult imageBase(mlir::Location loc)
280280
{
281+
if (!compileOptions.targetInfo.usesImageBaseRelativeEH)
282+
{
283+
// absolute EH references (32-bit x86) never reference __ImageBase, and an unused
284+
// external declaration would still pull in the linker-defined symbol
285+
return success();
286+
}
287+
281288
auto name = imageBaseRef;
282289
if (parentModule.lookupSymbol<LLVM::GlobalOp>(name))
283290
{
@@ -288,6 +295,30 @@ class LLVMRTTIHelperVCWin32
288295
return success();
289296
}
290297

298+
// A cross-reference inside ThrowInfo, CatchableType or CatchableTypeArray: an i32 field.
299+
// 64-bit MSVC stores an image-base-relative RVA there, 32-bit x86 the absolute address, which
300+
// is also 4 bytes - so the struct layouts coincide and only the value differs.
301+
mlir::Value ehReference(mlir::Location loc, mlir::Value ptrValue)
302+
{
303+
if (!compileOptions.targetInfo.usesImageBaseRelativeEH)
304+
{
305+
if (compileOptions.sizeBits() != 32)
306+
{
307+
llvm::report_fatal_error("absolute EH references need 32-bit pointers");
308+
}
309+
310+
return rewriter.create<LLVM::PtrToIntOp>(loc, th.getI32Type(), ptrValue);
311+
}
312+
313+
auto intPtrTy = rewriter.getIntegerType(compileOptions.sizeBits());
314+
auto ptrInt = rewriter.create<LLVM::PtrToIntOp>(loc, intPtrTy, ptrValue);
315+
auto imageBasePtr =
316+
rewriter.create<LLVM::AddressOfOp>(loc, th.getPtrType(), FlatSymbolRefAttr::get(rewriter.getContext(), imageBaseRef));
317+
auto imageBaseInt = rewriter.create<LLVM::PtrToIntOp>(loc, intPtrTy, imageBasePtr);
318+
auto rva = rewriter.create<LLVM::SubOp>(loc, intPtrTy, ptrInt, imageBaseInt);
319+
return rewriter.create<LLVM::TruncOp>(loc, th.getI32Type(), rva);
320+
}
321+
291322
LogicalResult catchableTypes(mlir::Location loc)
292323
{
293324
for (auto type : types)
@@ -328,19 +359,7 @@ class LLVMRTTIHelperVCWin32
328359
auto rttiTypeDescriptor2PtrValue =
329360
rewriter.create<LLVM::AddressOfOp>(loc, LLVM::LLVMPointerType::get(rewriter.getContext()),
330361
FlatSymbolRefAttr::get(rewriter.getContext(), typeInfoRefName));
331-
auto rttiTypeDescriptor2IntValue = rewriter.create<LLVM::PtrToIntOp>(loc, th.getI64Type(), rttiTypeDescriptor2PtrValue);
332-
333-
auto imageBasePtrValue =
334-
rewriter.create<LLVM::AddressOfOp>(loc, th.getPtrType(), FlatSymbolRefAttr::get(rewriter.getContext(), imageBaseRef));
335-
auto imageBaseIntValue = rewriter.create<LLVM::PtrToIntOp>(loc, th.getI64Type(), imageBasePtrValue);
336-
337-
// sub
338-
auto subResValue = rewriter.create<LLVM::SubOp>(loc, th.getI64Type(), rttiTypeDescriptor2IntValue, imageBaseIntValue);
339-
340-
// trunc
341-
auto subRes32Value = rewriter.create<LLVM::TruncOp>(loc, th.getI32Type(), subResValue);
342-
343-
auto itemValue2 = subRes32Value;
362+
auto itemValue2 = ehReference(loc, rttiTypeDescriptor2PtrValue);
344363
ch.setStructValue(loc, structVal, itemValue2, 1);
345364

346365
auto itemValue3 = rewriter.create<LLVM::ConstantOp>(loc, th.getI32Type(), rewriter.getI32IntegerAttr(0));
@@ -371,19 +390,7 @@ class LLVMRTTIHelperVCWin32
371390
{
372391
auto rttiCatchableTypePtrValue = rewriter.create<LLVM::AddressOfOp>(
373392
loc, LLVM::LLVMPointerType::get(rewriter.getContext()), FlatSymbolRefAttr::get(rewriter.getContext(), catchableTypeRefName));
374-
auto rttiCatchableTypeIntValue = rewriter.create<LLVM::PtrToIntOp>(loc, th.getI64Type(), rttiCatchableTypePtrValue);
375-
376-
auto imageBasePtrValue =
377-
rewriter.create<LLVM::AddressOfOp>(loc, th.getPtrType(), FlatSymbolRefAttr::get(rewriter.getContext(), imageBaseRef));
378-
auto imageBaseIntValue = rewriter.create<LLVM::PtrToIntOp>(loc, th.getI64Type(), imageBasePtrValue);
379-
380-
// sub
381-
auto subResValue = rewriter.create<LLVM::SubOp>(loc, th.getI64Type(), rttiCatchableTypeIntValue, imageBaseIntValue);
382-
383-
// trunc
384-
auto subRes32Value = rewriter.create<LLVM::TruncOp>(loc, th.getI32Type(), subResValue);
385-
386-
return subRes32Value;
393+
return ehReference(loc, rttiCatchableTypePtrValue);
387394
}
388395

389396
LogicalResult catchableArrayType(mlir::Location loc)
@@ -459,18 +466,8 @@ class LLVMRTTIHelperVCWin32
459466
// value 3
460467
auto rttiCatchableArrayTypePtrValue = rewriter.create<LLVM::AddressOfOp>(
461468
loc, LLVM::LLVMPointerType::get(rewriter.getContext()), FlatSymbolRefAttr::get(rewriter.getContext(), catchableTypeInfoArrayRef));
462-
auto rttiCatchableArrayTypeIntValue = rewriter.create<LLVM::PtrToIntOp>(loc, th.getI64Type(), rttiCatchableArrayTypePtrValue);
463-
464-
auto imageBasePtrValue =
465-
rewriter.create<LLVM::AddressOfOp>(loc, th.getPtrType(), FlatSymbolRefAttr::get(rewriter.getContext(), imageBaseRef));
466-
auto imageBaseIntValue = rewriter.create<LLVM::PtrToIntOp>(loc, th.getI64Type(), imageBasePtrValue);
467-
468-
// sub
469-
auto subResValue = rewriter.create<LLVM::SubOp>(loc, th.getI64Type(), rttiCatchableArrayTypeIntValue, imageBaseIntValue);
470-
471-
// trunc
472-
auto subRes32Value = rewriter.create<LLVM::TruncOp>(loc, th.getI32Type(), subResValue);
473-
ch.setStructValue(loc, structValue, subRes32Value, 3);
469+
auto rttiCatchableArrayTypeRefValue = ehReference(loc, rttiCatchableArrayTypePtrValue);
470+
ch.setStructValue(loc, structValue, rttiCatchableArrayTypeRefValue, 3);
474471

475472
rewriter.create<LLVM::ReturnOp>(loc, ValueRange{structValue});
476473

‎tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCWin32.h‎

Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,13 @@ class MLIRRTTIHelperVCWin32
398398

399399
mlir::LogicalResult imageBase(mlir::Location loc)
400400
{
401+
if (!compileOptions.targetInfo.usesImageBaseRelativeEH)
402+
{
403+
// absolute EH references (32-bit x86) never reference __ImageBase, and an unused
404+
// external declaration would still pull in the linker-defined symbol
405+
return mlir::success();
406+
}
407+
401408
auto name = windows::imageBaseRef;
402409
if (parentModule.lookupSymbol<mlir_ts::GlobalOp>(name))
403410
{
@@ -408,6 +415,31 @@ class MLIRRTTIHelperVCWin32
408415
return mlir::success();
409416
}
410417

418+
// A cross-reference inside ThrowInfo, CatchableType or CatchableTypeArray: an i32 field.
419+
// 64-bit MSVC stores an image-base-relative RVA there, 32-bit x86 the absolute address, which
420+
// is also 4 bytes - so the struct layouts coincide and only the value differs.
421+
mlir::Value ehReference(mlir::Location loc, mlir::Value ptrValue)
422+
{
423+
if (!compileOptions.targetInfo.usesImageBaseRelativeEH)
424+
{
425+
if (compileOptions.sizeBits() != 32)
426+
{
427+
llvm::report_fatal_error("absolute EH references need 32-bit pointers");
428+
}
429+
430+
return rewriter.create<mlir_ts::CastOp>(loc, mth.getI32Type(), ptrValue);
431+
}
432+
433+
auto intPtrTy = mlir::IntegerType::get(rewriter.getContext(), compileOptions.sizeBits());
434+
auto ptrInt = rewriter.create<mlir_ts::CastOp>(loc, intPtrTy, ptrValue);
435+
auto imageBasePtr = rewriter.create<mlir_ts::ConstantOp>(loc, mth.getOpaqueType(),
436+
mlir::FlatSymbolRefAttr::get(rewriter.getContext(), windows::imageBaseRef));
437+
auto imageBaseInt = rewriter.create<mlir_ts::CastOp>(loc, intPtrTy, imageBasePtr);
438+
auto rva = rewriter.create<mlir_ts::ArithmeticBinaryOp>(
439+
loc, intPtrTy, rewriter.getI32IntegerAttr(static_cast<int32_t>(SyntaxKind::MinusToken)), ptrInt, imageBaseInt);
440+
return rewriter.create<mlir_ts::CastOp>(loc, mth.getI32Type(), rva);
441+
}
442+
411443
mlir::LogicalResult catchableTypes(mlir::Location loc)
412444
{
413445
for (auto type : types)
@@ -445,21 +477,7 @@ class MLIRRTTIHelperVCWin32
445477
auto rttiTypeDescriptor2PtrValue =
446478
rewriter.create<mlir_ts::ConstantOp>(loc, getRttiTypeDescriptor2PtrTy(StringRef(typeName).size()),
447479
mlir::FlatSymbolRefAttr::get(rewriter.getContext(), typeInfoRefName));
448-
auto rttiTypeDescriptor2IntValue = rewriter.create<mlir_ts::CastOp>(loc, mth.getI64Type(), rttiTypeDescriptor2PtrValue);
449-
450-
auto imageBasePtrValue = rewriter.create<mlir_ts::ConstantOp>(
451-
loc, mth.getOpaqueType(), mlir::FlatSymbolRefAttr::get(rewriter.getContext(), windows::imageBaseRef));
452-
auto imageBaseIntValue = rewriter.create<mlir_ts::CastOp>(loc, mth.getI64Type(), imageBasePtrValue);
453-
454-
// sub
455-
auto subResValue = rewriter.create<mlir_ts::ArithmeticBinaryOp>(
456-
loc, mth.getI64Type(), rewriter.getI32IntegerAttr(static_cast<int32_t>(SyntaxKind::MinusToken)),
457-
rttiTypeDescriptor2IntValue, imageBaseIntValue);
458-
459-
// trunc
460-
auto subRes32Value = rewriter.create<mlir_ts::CastOp>(loc, mth.getI32Type(), subResValue);
461-
462-
auto itemValue2 = subRes32Value;
480+
auto itemValue2 = ehReference(loc, rttiTypeDescriptor2PtrValue);
463481
setStructValue(loc, structVal, itemValue2, 1);
464482

465483
auto itemValue3 = rewriter.create<mlir_ts::ConstantOp>(loc, mth.getI32Type(), rewriter.getI32IntegerAttr(0));
@@ -490,21 +508,7 @@ class MLIRRTTIHelperVCWin32
490508
{
491509
auto rttiCatchableTypePtrValue = rewriter.create<mlir_ts::ConstantOp>(
492510
loc, getCatchableTypePtrTy(), mlir::FlatSymbolRefAttr::get(rewriter.getContext(), catchableTypeRefName));
493-
auto rttiCatchableTypeIntValue = rewriter.create<mlir_ts::CastOp>(loc, mth.getI64Type(), rttiCatchableTypePtrValue);
494-
495-
auto imageBasePtrValue = rewriter.create<mlir_ts::ConstantOp>(loc, mth.getOpaqueType(),
496-
mlir::FlatSymbolRefAttr::get(rewriter.getContext(), windows::imageBaseRef));
497-
auto imageBaseIntValue = rewriter.create<mlir_ts::CastOp>(loc, mth.getI64Type(), imageBasePtrValue);
498-
499-
// sub
500-
auto subResValue = rewriter.create<mlir_ts::ArithmeticBinaryOp>(
501-
loc, mth.getI64Type(), rewriter.getI32IntegerAttr(static_cast<int32_t>(SyntaxKind::MinusToken)), rttiCatchableTypeIntValue,
502-
imageBaseIntValue);
503-
504-
// trunc
505-
auto subRes32Value = rewriter.create<mlir_ts::CastOp>(loc, mth.getI32Type(), subResValue);
506-
507-
return subRes32Value;
511+
return ehReference(loc, rttiCatchableTypePtrValue);
508512
}
509513

510514
mlir::LogicalResult catchableArrayType(mlir::Location loc)
@@ -602,20 +606,8 @@ class MLIRRTTIHelperVCWin32
602606
// value 3
603607
auto rttiCatchableArrayTypePtrValue = rewriter.create<mlir_ts::ConstantOp>(
604608
loc, getCatchableArrayTypePtrTy(arraySize), mlir::FlatSymbolRefAttr::get(rewriter.getContext(), catchableTypeInfoArrayRef));
605-
auto rttiCatchableArrayTypeIntValue = rewriter.create<mlir_ts::CastOp>(loc, mth.getI64Type(), rttiCatchableArrayTypePtrValue);
606-
607-
auto imageBasePtrValue = rewriter.create<mlir_ts::ConstantOp>(loc, mth.getOpaqueType(),
608-
mlir::FlatSymbolRefAttr::get(rewriter.getContext(), windows::imageBaseRef));
609-
auto imageBaseIntValue = rewriter.create<mlir_ts::CastOp>(loc, mth.getI64Type(), imageBasePtrValue);
610-
611-
// sub
612-
auto subResValue = rewriter.create<mlir_ts::ArithmeticBinaryOp>(
613-
loc, mth.getI64Type(), rewriter.getI32IntegerAttr(static_cast<int32_t>(SyntaxKind::MinusToken)), rttiCatchableArrayTypeIntValue,
614-
imageBaseIntValue);
615-
616-
// trunc
617-
auto subRes32Value = rewriter.create<mlir_ts::CastOp>(loc, mth.getI32Type(), subResValue);
618-
setStructValue(loc, structValue, subRes32Value, 3);
609+
auto rttiCatchableArrayTypeRefValue = ehReference(loc, rttiCatchableArrayTypePtrValue);
610+
setStructValue(loc, structValue, rttiCatchableArrayTypeRefValue, 3);
619611

620612
rewriter.create<mlir_ts::GlobalResultOp>(loc, mlir::ValueRange{structValue});
621613

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef CXXTHROWCALLINGCONVPASS__H
2+
#define CXXTHROWCALLINGCONVPASS__H
3+
4+
#include "llvm/Pass.h"
5+
#include "llvm/IR/PassManager.h"
6+
7+
namespace ts
8+
{
9+
// _CxxThrowException is __stdcall on 32-bit x86 MSVC; its symbol is then __CxxThrowException@8.
10+
// Sets that convention on the declaration and on every call and invoke of it. A call whose
11+
// convention differs from its callee's is undefined behaviour, which InstCombine turns into
12+
// `unreachable`, so this runs before the optimization pipeline.
13+
class CxxThrowCallingConvPass : public llvm::PassInfoMixin<CxxThrowCallingConvPass>
14+
{
15+
public:
16+
llvm::PreservedAnalyses run(llvm::Module &M, llvm::ModuleAnalysisManager &AM);
17+
18+
static bool isRequired()
19+
{
20+
return true;
21+
}
22+
};
23+
}
24+
25+
#endif // CXXTHROWCALLINGCONVPASS__H

‎tslang/include/TypeScript/TargetInfo.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ struct TargetInfo
4444
const bool msvc = target.isKnownWindowsMSVCEnvironment();
4545
const bool x86_32 = target.getArch() == llvm::Triple::x86;
4646

47-
info.usesImageBaseRelativeEH = msvc && !x86_32;
47+
// By pointer width, as clang's MicrosoftCXXABI::isImageRelative() decides it: 32-bit ARM
48+
// Windows stores absolute pointers too, not just x86.
49+
info.usesImageBaseRelativeEH = msvc && info.pointerBits == 64;
4850
info.stdcallDecoratesCxxThrow = msvc && x86_32;
4951
info.supportsInProcessJit =
5052
target.getArch() == host.getArch() && target.getOS() == host.getOS();

‎tslang/lib/TypeScriptExceptionPass/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ set(LLVM_LINK_COMPONENTS
77
add_llvm_library(TypeScriptExceptionPass
88
Win32ExceptionPass.cpp
99
LandingPadFixPass.cpp
10+
CxxThrowCallingConvPass.cpp
1011

1112
ADDITIONAL_HEADER_DIRS
1213

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "TypeScript/Pass/CxxThrowCallingConvPass.h"
2+
3+
#include "llvm/IR/InstrTypes.h"
4+
#include "llvm/IR/Module.h"
5+
#include "llvm/Support/ErrorHandling.h"
6+
7+
using namespace llvm;
8+
9+
#define DEBUG_TYPE "pass"
10+
11+
namespace ts
12+
{
13+
llvm::PreservedAnalyses CxxThrowCallingConvPass::run(llvm::Module &M, llvm::ModuleAnalysisManager &AM)
14+
{
15+
auto *throwFn = M.getFunction("_CxxThrowException");
16+
if (!throwFn)
17+
{
18+
return llvm::PreservedAnalyses::all();
19+
}
20+
21+
throwFn->setCallingConv(CallingConv::X86_StdCall);
22+
for (auto *user : throwFn->users())
23+
{
24+
auto *call = dyn_cast<CallBase>(user);
25+
if (!call || call->getCalledOperand() != throwFn)
26+
{
27+
// Only direct calls and invokes are expected. Any other use (the function taken as
28+
// a value) could reach an indirect call this pass cannot give the convention to.
29+
report_fatal_error("_CxxThrowException is used other than as a direct callee; "
30+
"its x86 stdcall convention cannot be applied to that use");
31+
}
32+
33+
call->setCallingConv(CallingConv::X86_StdCall);
34+
}
35+
36+
return llvm::PreservedAnalyses::none();
37+
}
38+
}

0 commit comments

Comments
 (0)