Skip to content

Commit 5ba8579

Browse files
[Arm64EC] Preserve X9 for indirect calls. (#167782)
Arm64EC indirect calls use a function __os_arm64x_check_icall... this has one obvious return value, x11, which is the function to call. However, it actually returns one other important value: x9, which is the final destination for the emulator after the call. If the call is calling x64 code, x9 is used by the thunk. Previously, we didn't model this, and it mostly worked because the compiler usually doesn't modify x9 in the narrow window between the check, and the call. That said, it can happen in some cases; one reliable way is to do an indirect tail-call with stack protectors enabled. (You can also just get unlucky with register allocation, but it's harder to write a testcase for that.) This patch uses the cfguardtarget bundle to simplify the calling convention handling, for similar reasons that x64 uses it: modifying arbitrary calls is difficult without a separate marking. Fixes #167430.
1 parent efee326 commit 5ba8579

File tree

3 files changed

+78
-5
lines changed

3 files changed

+78
-5
lines changed

llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -662,12 +662,15 @@ Function *AArch64Arm64ECCallLowering::buildGuestExitThunk(Function *F) {
662662
Function *Thunk = buildExitThunk(F->getFunctionType(), F->getAttributes());
663663
CallInst *GuardCheck = B.CreateCall(
664664
GuardFnType, GuardCheckLoad, {F, Thunk});
665+
Value *GuardCheckDest = B.CreateExtractValue(GuardCheck, 0);
666+
Value *GuardFinalDest = B.CreateExtractValue(GuardCheck, 1);
665667

666668
// Ensure that the first argument is passed in the correct register.
667669
GuardCheck->setCallingConv(CallingConv::CFGuard_Check);
668670

669671
SmallVector<Value *> Args(llvm::make_pointer_range(GuestExit->args()));
670-
CallInst *Call = B.CreateCall(Arm64Ty, GuardCheck, Args);
672+
OperandBundleDef OB("cfguardtarget", GuardFinalDest);
673+
CallInst *Call = B.CreateCall(Arm64Ty, GuardCheckDest, Args, OB);
671674
Call->setTailCallKind(llvm::CallInst::TCK_MustTail);
672675

673676
if (Call->getType()->isVoidTy())
@@ -767,11 +770,21 @@ void AArch64Arm64ECCallLowering::lowerCall(CallBase *CB) {
767770
CallInst *GuardCheck =
768771
B.CreateCall(GuardFnType, GuardCheckLoad, {CalledOperand, Thunk},
769772
Bundles);
773+
Value *GuardCheckDest = B.CreateExtractValue(GuardCheck, 0);
774+
Value *GuardFinalDest = B.CreateExtractValue(GuardCheck, 1);
770775

771776
// Ensure that the first argument is passed in the correct register.
772777
GuardCheck->setCallingConv(CallingConv::CFGuard_Check);
773778

774-
CB->setCalledOperand(GuardCheck);
779+
// Update the call: set the callee, and add a bundle with the final
780+
// destination,
781+
CB->setCalledOperand(GuardCheckDest);
782+
OperandBundleDef OB("cfguardtarget", GuardFinalDest);
783+
auto *NewCall = CallBase::addOperandBundle(CB, LLVMContext::OB_cfguardtarget,
784+
OB, CB->getIterator());
785+
NewCall->copyMetadata(*CB);
786+
CB->replaceAllUsesWith(NewCall);
787+
CB->eraseFromParent();
775788
}
776789

777790
bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
@@ -789,7 +802,8 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
789802
I64Ty = Type::getInt64Ty(M->getContext());
790803
VoidTy = Type::getVoidTy(M->getContext());
791804

792-
GuardFnType = FunctionType::get(PtrTy, {PtrTy, PtrTy}, false);
805+
GuardFnType =
806+
FunctionType::get(StructType::get(PtrTy, PtrTy), {PtrTy, PtrTy}, false);
793807
DispatchFnType = FunctionType::get(PtrTy, {PtrTy, PtrTy, PtrTy}, false);
794808
GuardFnCFGlobal = M->getOrInsertGlobal("__os_arm64x_check_icall_cfg", PtrTy);
795809
GuardFnGlobal = M->getOrInsertGlobal("__os_arm64x_check_icall", PtrTy);

llvm/lib/Target/AArch64/AArch64CallingConvention.td

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,13 @@ def RetCC_AArch64_AAPCS : CallingConv<[
162162
]>;
163163

164164
let Entry = 1 in
165-
def CC_AArch64_Win64PCS : CallingConv<AArch64_Common>;
165+
def CC_AArch64_Win64PCS : CallingConv<!listconcat(
166+
[
167+
// 'CFGuardTarget' is used for Arm64EC; it passes its parameter in X9.
168+
CCIfCFGuardTarget<CCAssignToReg<[X9]>>
169+
],
170+
AArch64_Common)
171+
>;
166172

167173
// Vararg functions on windows pass floats in integer registers
168174
let Entry = 1 in
@@ -177,6 +183,9 @@ def CC_AArch64_Win64_VarArg : CallingConv<[
177183
// a stack layout compatible with the x64 calling convention.
178184
let Entry = 1 in
179185
def CC_AArch64_Arm64EC_VarArg : CallingConv<[
186+
// 'CFGuardTarget' is used for Arm64EC; it passes its parameter in X9.
187+
CCIfCFGuardTarget<CCAssignToReg<[X9]>>,
188+
180189
CCIfNest<CCAssignToReg<[X15]>>,
181190

182191
// Convert small floating-point values to integer.
@@ -345,7 +354,7 @@ def CC_AArch64_Arm64EC_CFGuard_Check : CallingConv<[
345354

346355
let Entry = 1 in
347356
def RetCC_AArch64_Arm64EC_CFGuard_Check : CallingConv<[
348-
CCIfType<[i64], CCAssignToReg<[X11]>>
357+
CCIfType<[i64], CCAssignToReg<[X11, X9]>>
349358
]>;
350359

351360

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
; RUN: llc -mtriple=arm64ec-pc-windows-msvc < %s | FileCheck %s
2+
3+
define void @simple(ptr %g) {
4+
; CHECK-LABEL: "#simple":
5+
; CHECK: str x30, [sp, #-16]!
6+
; CHECK-NEXT: .seh_save_reg_x x30, 16
7+
; CHECK-NEXT: .seh_endprologue
8+
; CHECK-NEXT: adrp x8, __os_arm64x_check_icall
9+
; CHECK-NEXT: adrp x10, $iexit_thunk$cdecl$v$v
10+
; CHECK-NEXT: add x10, x10, :lo12:$iexit_thunk$cdecl$v$v
11+
; CHECK-NEXT: ldr x8, [x8, :lo12:__os_arm64x_check_icall]
12+
; CHECK-NEXT: mov x11, x0
13+
; CHECK-NEXT: blr x8
14+
; CHECK-NEXT: blr x11
15+
; CHECK-NEXT: .seh_startepilogue
16+
; CHECK-NEXT: ldr x30, [sp], #16
17+
; CHECK-NEXT: .seh_save_reg_x x30, 16
18+
; CHECK-NEXT: .seh_endepilogue
19+
; CHECK-NEXT: ret
20+
21+
entry:
22+
call void %g()
23+
ret void
24+
}
25+
26+
; Make sure the check for the security cookie doesn't use x9.
27+
define void @stackguard(ptr %g) sspreq {
28+
; CHECK-LABEL: "#stackguard":
29+
; CHECK: adrp x8, __os_arm64x_check_icall
30+
; CHECK-NEXT: ldr x8, [x8, :lo12:__os_arm64x_check_icall]
31+
; CHECK-NEXT: blr x8
32+
; CHECK-NEXT: adrp x8, __security_cookie
33+
; CHECK-NEXT: ldr x10, [sp, #8]
34+
; CHECK-NEXT: ldr x8, [x8, :lo12:__security_cookie]
35+
; CHECK-NEXT: cmp x8, x10
36+
; CHECK-NEXT: b.ne .LBB1_2
37+
; CHECK-NEXT: // %bb.1:
38+
; CHECK-NEXT: fmov d0, #1.00000000
39+
; CHECK-NEXT: .seh_startepilogue
40+
; CHECK-NEXT: ldr x30, [sp, #16]
41+
; CHECK-NEXT: .seh_save_reg x30, 16
42+
; CHECK-NEXT: add sp, sp, #32
43+
; CHECK-NEXT: .seh_stackalloc 32
44+
; CHECK-NEXT: .seh_endepilogue
45+
; CHECK-NEXT: br x11
46+
47+
entry:
48+
%call = tail call double %g(double noundef 1.000000e+00)
49+
ret void
50+
}

0 commit comments

Comments
 (0)