Skip to content

Commit 08ac3b3

Browse files
authored
[win][aarch64] Place catch objects in the fixed object area (#147421)
Fixes #146973 When an object with alignment requirements is placed on the stack, this causes a stack realignment which causes AArch64 to use x19 to refer to objects on the stack as there may be a gap between local variables and the Stack Pointer. This causes issues with the MSVC C++ exception personality as the offset to the catch object recorded in the handler table no longer matches the object being used in the catch block itself. The fix for this is to place catch objects into the fixed object area.
1 parent c8048e7 commit 08ac3b3

File tree

5 files changed

+301
-20
lines changed

5 files changed

+301
-20
lines changed

llvm/lib/Target/AArch64/AArch64FrameLowering.cpp

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -435,19 +435,40 @@ AArch64FrameLowering::getStackIDForScalableVectors() const {
435435
static unsigned getFixedObjectSize(const MachineFunction &MF,
436436
const AArch64FunctionInfo *AFI, bool IsWin64,
437437
bool IsFunclet) {
438+
assert(AFI->getTailCallReservedStack() % 16 == 0 &&
439+
"Tail call reserved stack must be aligned to 16 bytes");
438440
if (!IsWin64 || IsFunclet) {
439441
return AFI->getTailCallReservedStack();
440442
} else {
441443
if (AFI->getTailCallReservedStack() != 0 &&
442444
!MF.getFunction().getAttributes().hasAttrSomewhere(
443445
Attribute::SwiftAsync))
444446
report_fatal_error("cannot generate ABI-changing tail call for Win64");
447+
unsigned FixedObjectSize = AFI->getTailCallReservedStack();
448+
445449
// Var args are stored here in the primary function.
446-
const unsigned VarArgsArea = AFI->getVarArgsGPRSize();
447-
// To support EH funclets we allocate an UnwindHelp object
448-
const unsigned UnwindHelpObject = (MF.hasEHFunclets() ? 8 : 0);
449-
return AFI->getTailCallReservedStack() +
450-
alignTo(VarArgsArea + UnwindHelpObject, 16);
450+
FixedObjectSize += AFI->getVarArgsGPRSize();
451+
452+
if (MF.hasEHFunclets()) {
453+
// Catch objects are stored here in the primary function.
454+
const MachineFrameInfo &MFI = MF.getFrameInfo();
455+
const WinEHFuncInfo &EHInfo = *MF.getWinEHFuncInfo();
456+
SmallSetVector<int, 8> CatchObjFrameIndices;
457+
for (const WinEHTryBlockMapEntry &TBME : EHInfo.TryBlockMap) {
458+
for (const WinEHHandlerType &H : TBME.HandlerArray) {
459+
int FrameIndex = H.CatchObj.FrameIndex;
460+
if ((FrameIndex != INT_MAX) &&
461+
CatchObjFrameIndices.insert(FrameIndex)) {
462+
FixedObjectSize = alignTo(FixedObjectSize,
463+
MFI.getObjectAlign(FrameIndex).value()) +
464+
MFI.getObjectSize(FrameIndex);
465+
}
466+
}
467+
}
468+
// To support EH funclets we allocate an UnwindHelp object
469+
FixedObjectSize += 8;
470+
}
471+
return alignTo(FixedObjectSize, 16);
451472
}
452473
}
453474

@@ -4662,30 +4683,47 @@ void AArch64FrameLowering::processFunctionBeforeFrameFinalized(
46624683
// anything.
46634684
if (!MF.hasEHFunclets())
46644685
return;
4665-
const TargetInstrInfo &TII = *MF.getSubtarget().getInstrInfo();
4666-
WinEHFuncInfo &EHInfo = *MF.getWinEHFuncInfo();
46674686

4668-
MachineBasicBlock &MBB = MF.front();
4669-
auto MBBI = MBB.begin();
4670-
while (MBBI != MBB.end() && MBBI->getFlag(MachineInstr::FrameSetup))
4671-
++MBBI;
4687+
// Win64 C++ EH needs to allocate space for the catch objects in the fixed
4688+
// object area right next to the UnwindHelp object.
4689+
WinEHFuncInfo &EHInfo = *MF.getWinEHFuncInfo();
4690+
int64_t CurrentOffset =
4691+
AFI->getVarArgsGPRSize() + AFI->getTailCallReservedStack();
4692+
for (WinEHTryBlockMapEntry &TBME : EHInfo.TryBlockMap) {
4693+
for (WinEHHandlerType &H : TBME.HandlerArray) {
4694+
int FrameIndex = H.CatchObj.FrameIndex;
4695+
if ((FrameIndex != INT_MAX) && MFI.getObjectOffset(FrameIndex) == 0) {
4696+
CurrentOffset =
4697+
alignTo(CurrentOffset, MFI.getObjectAlign(FrameIndex).value());
4698+
CurrentOffset += MFI.getObjectSize(FrameIndex);
4699+
MFI.setObjectOffset(FrameIndex, -CurrentOffset);
4700+
}
4701+
}
4702+
}
46724703

46734704
// Create an UnwindHelp object.
46744705
// The UnwindHelp object is allocated at the start of the fixed object area
4675-
int64_t FixedObject =
4676-
getFixedObjectSize(MF, AFI, /*IsWin64*/ true, /*IsFunclet*/ false);
4677-
int UnwindHelpFI = MFI.CreateFixedObject(/*Size*/ 8,
4678-
/*SPOffset*/ -FixedObject,
4706+
int64_t UnwindHelpOffset = alignTo(CurrentOffset + 8, Align(16));
4707+
assert(UnwindHelpOffset == getFixedObjectSize(MF, AFI, /*IsWin64*/ true,
4708+
/*IsFunclet*/ false) &&
4709+
"UnwindHelpOffset must be at the start of the fixed object area");
4710+
int UnwindHelpFI = MFI.CreateFixedObject(/*Size*/ 8, -UnwindHelpOffset,
46794711
/*IsImmutable=*/false);
46804712
EHInfo.UnwindHelpFrameIdx = UnwindHelpFI;
46814713

4714+
MachineBasicBlock &MBB = MF.front();
4715+
auto MBBI = MBB.begin();
4716+
while (MBBI != MBB.end() && MBBI->getFlag(MachineInstr::FrameSetup))
4717+
++MBBI;
4718+
46824719
// We need to store -2 into the UnwindHelp object at the start of the
46834720
// function.
46844721
DebugLoc DL;
46854722
RS->enterBasicBlockEnd(MBB);
46864723
RS->backward(MBBI);
46874724
Register DstReg = RS->FindUnusedReg(&AArch64::GPR64commonRegClass);
46884725
assert(DstReg && "There must be a free register after frame setup");
4726+
const TargetInstrInfo &TII = *MF.getSubtarget().getInstrInfo();
46894727
BuildMI(MBB, MBBI, DL, TII.get(AArch64::MOVi64imm), DstReg).addImm(-2);
46904728
BuildMI(MBB, MBBI, DL, TII.get(AArch64::STURXi))
46914729
.addReg(DstReg, getKillRegState(true))

llvm/lib/Target/AArch64/AArch64ISelLowering.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28556,9 +28556,7 @@ void AArch64TargetLowering::finalizeLowering(MachineFunction &MF) const {
2855628556
}
2855728557

2855828558
// Unlike X86, we let frame lowering assign offsets to all catch objects.
28559-
bool AArch64TargetLowering::needsFixedCatchObjects() const {
28560-
return false;
28561-
}
28559+
bool AArch64TargetLowering::needsFixedCatchObjects() const { return true; }
2856228560

2856328561
bool AArch64TargetLowering::shouldLocalize(
2856428562
const MachineInstr &MI, const TargetTransformInfo *TTI) const {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
; RUN: llc %s --mtriple=aarch64-pc-windows-msvc -o - | FileCheck %s
2+
3+
; Regression test for handling MSVC C++ exceptions when there's an aligned
4+
; object on the stack.
5+
6+
; Generated from this C++ code:
7+
; https://godbolt.org/z/cGzGfqq34
8+
; > clang --target=aarch64-pc-windows-msvc test.cpp
9+
; ```
10+
; // Large object: alignment seems to be important?
11+
; struct alignas(128) BigObj {
12+
; int value;
13+
; // Destructor so it's kept alive.
14+
; ~BigObj() { }
15+
; };
16+
;
17+
; // Exception type need to be large enough to not fit in a register.
18+
; struct Error {
19+
; int value;
20+
; int padding[3];
21+
; };
22+
;
23+
; int main() {
24+
; BigObj bo{};
25+
;
26+
; try {
27+
; throw Error { 42, {0, 0, 0} };
28+
; } catch (const Error& e) {
29+
; return e.value;
30+
; }
31+
; return 0;
32+
; }
33+
; ```
34+
35+
; CHECK-LABEL: main:
36+
; CHECK: sub x[[SPTMP:[0-9]+]], sp, #336
37+
; CHECK: and sp, x[[SPTMP]], #0xffffffffffffff80
38+
; CHECK: mov x[[FP:[0-9]+]], sp
39+
; CHECK: str wzr, [x[[FP]], #332]
40+
41+
; CHECK-LABEL: "?catch$3@?0?main@4HA":
42+
; CHECK: str w8, [x[[FP]], #332]
43+
; CHECK-NEXT: .seh_startepilogue
44+
; CHECK: ret
45+
46+
; CHECK-LABEL: $cppxdata$main:
47+
; CHECK: .word -16 // UnwindHelp
48+
; CHECK-LABEL: $handlerMap$0$main:
49+
; CHECK-NEXT: .word 8 // Adjectives
50+
; CHECK-NEXT: .word "??_R0?AUError@@@8"@IMGREL // Type
51+
; CHECK-NEXT: .word -8 // CatchObjOffset
52+
; CHECK-NEXT: .word "?catch$3@?0?main@4HA"@IMGREL // Handler
53+
54+
%rtti.TypeDescriptor11 = type { ptr, ptr, [12 x i8] }
55+
%eh.CatchableType = type { i32, i32, i32, i32, i32, i32, i32 }
56+
%eh.CatchableTypeArray.1 = type { i32, [1 x i32] }
57+
%eh.ThrowInfo = type { i32, i32, i32, i32 }
58+
%struct.BigObj = type { i32, [124 x i8] }
59+
%struct.Error = type { i32, [3 x i32] }
60+
61+
$"??1BigObj@@QEAA@XZ" = comdat any
62+
63+
$"??_R0?AUError@@@8" = comdat any
64+
65+
$"_CT??_R0?AUError@@@816" = comdat any
66+
67+
$"_CTA1?AUError@@" = comdat any
68+
69+
$"_TI1?AUError@@" = comdat any
70+
71+
@"??_7type_info@@6B@" = external constant ptr
72+
@"??_R0?AUError@@@8" = linkonce_odr global %rtti.TypeDescriptor11 { ptr @"??_7type_info@@6B@", ptr null, [12 x i8] c".?AUError@@\00" }, comdat
73+
@__ImageBase = external dso_local constant i8
74+
@"_CT??_R0?AUError@@@816" = linkonce_odr unnamed_addr constant %eh.CatchableType { i32 0, i32 trunc (i64 sub nuw nsw (i64 ptrtoint (ptr @"??_R0?AUError@@@8" to i64), i64 ptrtoint (ptr @__ImageBase to i64)) to i32), i32 0, i32 -1, i32 0, i32 16, i32 0 }, section ".xdata", comdat
75+
@"_CTA1?AUError@@" = linkonce_odr unnamed_addr constant %eh.CatchableTypeArray.1 { i32 1, [1 x i32] [i32 trunc (i64 sub nuw nsw (i64 ptrtoint (ptr @"_CT??_R0?AUError@@@816" to i64), i64 ptrtoint (ptr @__ImageBase to i64)) to i32)] }, section ".xdata", comdat
76+
@"_TI1?AUError@@" = linkonce_odr unnamed_addr constant %eh.ThrowInfo { i32 0, i32 0, i32 0, i32 trunc (i64 sub nuw nsw (i64 ptrtoint (ptr @"_CTA1?AUError@@" to i64), i64 ptrtoint (ptr @__ImageBase to i64)) to i32) }, section ".xdata", comdat
77+
78+
define dso_local noundef i32 @main() personality ptr @__CxxFrameHandler3 {
79+
entry:
80+
%retval = alloca i32, align 4
81+
%bo = alloca %struct.BigObj, align 128
82+
%tmp = alloca %struct.Error, align 4
83+
%e = alloca ptr, align 8
84+
%cleanup.dest.slot = alloca i32, align 4
85+
store i32 0, ptr %retval, align 4
86+
call void @llvm.memset.p0.i64(ptr align 128 %bo, i8 0, i64 128, i1 false)
87+
%value = getelementptr inbounds nuw %struct.BigObj, ptr %bo, i32 0, i32 0
88+
%value1 = getelementptr inbounds nuw %struct.Error, ptr %tmp, i32 0, i32 0
89+
store i32 42, ptr %value1, align 4
90+
%padding = getelementptr inbounds nuw %struct.Error, ptr %tmp, i32 0, i32 1
91+
store i32 0, ptr %padding, align 4
92+
%arrayinit.element = getelementptr inbounds i32, ptr %padding, i64 1
93+
store i32 0, ptr %arrayinit.element, align 4
94+
%arrayinit.element2 = getelementptr inbounds i32, ptr %padding, i64 2
95+
store i32 0, ptr %arrayinit.element2, align 4
96+
invoke void @_CxxThrowException(ptr %tmp, ptr @"_TI1?AUError@@") #3
97+
to label %unreachable unwind label %catch.dispatch
98+
99+
catch.dispatch:
100+
%0 = catchswitch within none [label %catch] unwind label %ehcleanup
101+
102+
catch:
103+
%1 = catchpad within %0 [ptr @"??_R0?AUError@@@8", i32 8, ptr %e]
104+
%2 = load ptr, ptr %e, align 8
105+
%value3 = getelementptr inbounds nuw %struct.Error, ptr %2, i32 0, i32 0
106+
%3 = load i32, ptr %value3, align 4
107+
store i32 %3, ptr %retval, align 4
108+
store i32 1, ptr %cleanup.dest.slot, align 4
109+
catchret from %1 to label %catchret.dest
110+
111+
catchret.dest:
112+
br label %cleanup
113+
114+
try.cont:
115+
store i32 0, ptr %retval, align 4
116+
store i32 1, ptr %cleanup.dest.slot, align 4
117+
br label %cleanup
118+
119+
cleanup:
120+
call void @"??1BigObj@@QEAA@XZ"(ptr noundef nonnull align 128 dereferenceable(4) %bo) #4
121+
%4 = load i32, ptr %retval, align 4
122+
ret i32 %4
123+
124+
ehcleanup:
125+
%5 = cleanuppad within none []
126+
call void @"??1BigObj@@QEAA@XZ"(ptr noundef nonnull align 128 dereferenceable(4) %bo) [ "funclet"(token %5) ]
127+
cleanupret from %5 unwind to caller
128+
129+
unreachable:
130+
unreachable
131+
}
132+
133+
declare void @llvm.memset.p0.i64(ptr writeonly captures(none), i8, i64, i1 immarg) #1
134+
135+
declare dso_local void @_CxxThrowException(ptr, ptr)
136+
137+
declare dso_local i32 @__CxxFrameHandler3(...)
138+
139+
define linkonce_odr dso_local void @"??1BigObj@@QEAA@XZ"(ptr noundef nonnull align 128 dereferenceable(4) %this) unnamed_addr comdat {
140+
entry:
141+
%this.addr = alloca ptr, align 8
142+
store ptr %this, ptr %this.addr, align 8
143+
%this1 = load ptr, ptr %this.addr, align 8
144+
ret void
145+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
; RUN: llc %s --mtriple=aarch64-pc-windows-msvc -o - | FileCheck %s
2+
3+
; Tests the fixed object layouts when two catchpads re-use the same stack
4+
; allocation for this catch objects.
5+
6+
; Generated from this C++ code, with modifications to the IR (see comments in
7+
; IR):
8+
; https://godbolt.org/z/9qv5Yn68j
9+
; > clang --target=aarch64-pc-windows-msvc test.cpp
10+
; ```
11+
; extern "C" void boom();
12+
; extern "C" int calls_boom();
13+
; {
14+
; try { boom(); }
15+
; catch (int& i) { return i; }
16+
; catch (long& l) { return l; }
17+
; return 0;
18+
; }
19+
; ```
20+
21+
; Only need 48 bytes on the stack, not 64.
22+
; CHECK-LABEL: calls_boom:
23+
; CHECK: sub sp, sp, #48
24+
; CHECK: .seh_stackalloc 48
25+
26+
; Both the catch blocks load from the same address.
27+
; CHECK-LABEL: "?catch$3@?0?calls_boom@4HA":
28+
; CHECK: ldr x8, [x29, #24]
29+
; CHECK-LABEL: "?catch$4@?0?calls_boom@4HA":
30+
; CHECK: ldr x8, [x29, #24]
31+
32+
; There's enough space for the UnwindHelp to be at -16 instead of -32
33+
; CHECK-LABEL: $cppxdata$calls_boom:
34+
; CHECK: .word -16 // UnwindHelp
35+
36+
; Both catches have the same object offset.
37+
; CHECK-LABEL: $handlerMap$0$calls_boom:
38+
; CHECK: .word -8 // CatchObjOffset
39+
; CHECK-NEXT: .word "?catch$3@?0?calls_boom@4HA"@IMGREL // Handler
40+
; CHECK: .word -8 // CatchObjOffset
41+
; CHECK-NEXT: .word "?catch$4@?0?calls_boom@4HA"@IMGREL // Handler
42+
43+
%rtti.TypeDescriptor2 = type { ptr, ptr, [3 x i8] }
44+
45+
$"??_R0H@8" = comdat any
46+
47+
$"??_R0J@8" = comdat any
48+
49+
@"??_7type_info@@6B@" = external constant ptr
50+
@"??_R0H@8" = linkonce_odr global %rtti.TypeDescriptor2 { ptr @"??_7type_info@@6B@", ptr null, [3 x i8] c".H\00" }, comdat
51+
@"??_R0J@8" = linkonce_odr global %rtti.TypeDescriptor2 { ptr @"??_7type_info@@6B@", ptr null, [3 x i8] c".J\00" }, comdat
52+
53+
define dso_local i32 @calls_boom() personality ptr @__CxxFrameHandler3 {
54+
entry:
55+
%retval = alloca i32, align 4
56+
; MODIFICATION: Remove unusued alloca
57+
; %l = alloca ptr, align 8
58+
%i = alloca ptr, align 8
59+
invoke void @boom()
60+
to label %invoke.cont unwind label %catch.dispatch
61+
62+
catch.dispatch:
63+
%0 = catchswitch within none [label %catch1, label %catch] unwind to caller
64+
65+
catch1:
66+
%1 = catchpad within %0 [ptr @"??_R0H@8", i32 8, ptr %i]
67+
%2 = load ptr, ptr %i, align 8
68+
%3 = load i32, ptr %2, align 4
69+
store i32 %3, ptr %retval, align 4
70+
catchret from %1 to label %catchret.dest2
71+
72+
catch:
73+
; MODIFICATION: Use %i instead of %l
74+
%4 = catchpad within %0 [ptr @"??_R0J@8", i32 8, ptr %i]
75+
%5 = load ptr, ptr %i, align 8
76+
%6 = load i32, ptr %5, align 4
77+
store i32 %6, ptr %retval, align 4
78+
catchret from %4 to label %catchret.dest
79+
80+
invoke.cont:
81+
br label %try.cont
82+
83+
catchret.dest:
84+
br label %return
85+
86+
catchret.dest2:
87+
br label %return
88+
89+
try.cont:
90+
store i32 0, ptr %retval, align 4
91+
br label %return
92+
93+
return:
94+
%7 = load i32, ptr %retval, align 4
95+
ret i32 %7
96+
}
97+
98+
declare dso_local void @boom() #1
99+
100+
declare dso_local i32 @__CxxFrameHandler3(...)

llvm/test/CodeGen/AArch64/wineh-try-catch.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
; CHECK: str x28, [sp, #24]
2121
; CHECK: stp x29, x30, [sp, #32]
2222
; CHECK: add x29, sp, #32
23-
; CHECK: sub sp, sp, #624
23+
; CHECK: sub sp, sp, #608
2424
; CHECK: mov x19, sp
2525
; CHECK: mov x0, #-2
2626
; CHECK: stur x0, [x29, #16]
@@ -51,7 +51,7 @@
5151
; CHECK: str x21, [sp, #16]
5252
; CHECK: str x28, [sp, #24]
5353
; CHECK: stp x29, x30, [sp, #32]
54-
; CHECK: add x20, x19, #12
54+
; CHECK: add x20, x19, #0
5555

5656
; Check that there are no further stack updates.
5757
; CHECK-NOT: sub sp, sp

0 commit comments

Comments
 (0)