summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnatoly Trosinenko <atrosinenko@accesssoftek.com>2025-07-08 23:51:10 +0300
committerAnatoly Trosinenko <atrosinenko@accesssoftek.com>2025-07-22 14:01:43 +0300
commitbeee6727fcaf18520ef0f0c035d9d3532bb3a96f (patch)
treef38eec5c7145455ea9f8eb9603ea1900683bb7b0
parent37c2dda58d8f6e82113b92ddc2e499b3c5a50385 (diff)
[AArch64][PAC] Skip llvm.ptrauth.blend intrinsic in GVN PREusers/atrosinenko/pauth-gvn-blend-intrinsic
The instruction selector on AArch64 implements a best-effort heuristic to detect the discriminator being computed by llvm.ptrauth.blend intrinsic. If such pattern is detected, then address and immediate discriminator components are emitted as two separate operands of the corresponding pseudo instruction, which is not expanded until AsmPrinter. This helps enforcing the hard-coded immediate modifier even when the address part of the discriminator can be modified by an attacker, something along the lines mov x8, x20 movk x8, #1234, #48 pacda x0, x8 // ... bl callee mov x8, x20 // address in x20 can be modified movk x8, #1234, #48 // immediate modifier is enforced pacda x0, x8 instead of reloading a previously computed discriminator value from the stack (can be modified by an attacker under Pointer Authentication threat model) or keeping it in a callee-saved register (may be spilled to the stack in callee): movk x20, #1234, #48 pacda x0, x20 // ... bl callee pacda x0, x20 // the entire discriminator can be modified
-rw-r--r--llvm/lib/Transforms/Scalar/GVN.cpp7
-rw-r--r--llvm/test/CodeGen/AArch64/ptrauth-discriminator-components.ll53
2 files changed, 29 insertions, 31 deletions
diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index f6bf09d09433..d306342225b5 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -2993,6 +2993,13 @@ bool GVNPass::performScalarPRE(Instruction *CurInst) {
if (isa<GetElementPtrInst>(CurInst))
return false;
+ // Don't do PRE on ptrauth_blend intrinsic: on AArch64 the instruction
+ // selector wants to take its operands into account when selecting the user
+ // of the blended discriminator, so don't hide the blend behind PHI nodes.
+ if (auto *II = dyn_cast<IntrinsicInst>(CurInst))
+ if (II->getIntrinsicID() == Intrinsic::ptrauth_blend)
+ return false;
+
if (auto *CallB = dyn_cast<CallBase>(CurInst)) {
// We don't currently value number ANY inline asm calls.
if (CallB->isInlineAsm())
diff --git a/llvm/test/CodeGen/AArch64/ptrauth-discriminator-components.ll b/llvm/test/CodeGen/AArch64/ptrauth-discriminator-components.ll
index efa212380751..1987f58b27c7 100644
--- a/llvm/test/CodeGen/AArch64/ptrauth-discriminator-components.ll
+++ b/llvm/test/CodeGen/AArch64/ptrauth-discriminator-components.ll
@@ -24,11 +24,7 @@ define void @test_simple(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i64 %b)
; GVN-NEXT: [[ENTRY:.*:]]
; GVN-NEXT: [[STORAGE1_I:%.*]] = ptrtoint ptr [[STORAGE1]] to i64
; GVN-NEXT: [[STORAGE2_I:%.*]] = ptrtoint ptr [[STORAGE2]] to i64
-; GVN-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[ENTRY_EXIT_CRIT_EDGE:.*]]
-; GVN: [[ENTRY_EXIT_CRIT_EDGE]]:
-; GVN-NEXT: [[DOTPRE:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
-; GVN-NEXT: [[DOTPRE1:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
-; GVN-NEXT: br label %[[EXIT:.*]]
+; GVN-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[EXIT:.*]]
; GVN: [[IF_THEN]]:
; GVN-NEXT: [[DISCR1_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
; GVN-NEXT: [[DISCR2_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
@@ -38,10 +34,10 @@ define void @test_simple(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i64 %b)
; GVN-NEXT: store volatile i64 [[T2]], ptr [[STORAGE2]], align 8
; GVN-NEXT: br label %[[EXIT]]
; GVN: [[EXIT]]:
-; GVN-NEXT: [[DISCR2_EXIT_PRE_PHI:%.*]] = phi i64 [ [[DOTPRE1]], %[[ENTRY_EXIT_CRIT_EDGE]] ], [ [[DISCR2_THEN]], %[[IF_THEN]] ]
-; GVN-NEXT: [[DISCR1_EXIT_PRE_PHI:%.*]] = phi i64 [ [[DOTPRE]], %[[ENTRY_EXIT_CRIT_EDGE]] ], [ [[DISCR1_THEN]], %[[IF_THEN]] ]
-; GVN-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT_PRE_PHI]])
-; GVN-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT_PRE_PHI]])
+; GVN-NEXT: [[DISCR1_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
+; GVN-NEXT: [[DISCR2_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
+; GVN-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT]])
+; GVN-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT]])
; GVN-NEXT: store volatile i64 [[T3]], ptr [[STORAGE1]], align 8
; GVN-NEXT: store volatile i64 [[T4]], ptr [[STORAGE2]], align 8
; GVN-NEXT: ret void
@@ -51,18 +47,20 @@ define void @test_simple(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i64 %b)
; GVN-SCFG-NEXT: [[ENTRY:.*:]]
; GVN-SCFG-NEXT: [[STORAGE1_I:%.*]] = ptrtoint ptr [[STORAGE1]] to i64
; GVN-SCFG-NEXT: [[STORAGE2_I:%.*]] = ptrtoint ptr [[STORAGE2]] to i64
-; GVN-SCFG-NEXT: [[DISCR1_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
-; GVN-SCFG-NEXT: [[DISCR2_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
; GVN-SCFG-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[EXIT:.*]]
; GVN-SCFG: [[IF_THEN]]:
+; GVN-SCFG-NEXT: [[DISCR1_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
+; GVN-SCFG-NEXT: [[DISCR2_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
; GVN-SCFG-NEXT: [[T1:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[A]], i32 2, i64 [[DISCR1_THEN]])
; GVN-SCFG-NEXT: [[T2:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[A]], i32 2, i64 [[DISCR2_THEN]])
; GVN-SCFG-NEXT: store volatile i64 [[T1]], ptr [[STORAGE1]], align 8
; GVN-SCFG-NEXT: store volatile i64 [[T2]], ptr [[STORAGE2]], align 8
; GVN-SCFG-NEXT: br label %[[EXIT]]
; GVN-SCFG: [[EXIT]]:
-; GVN-SCFG-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_THEN]])
-; GVN-SCFG-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_THEN]])
+; GVN-SCFG-NEXT: [[DISCR1_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
+; GVN-SCFG-NEXT: [[DISCR2_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
+; GVN-SCFG-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT]])
+; GVN-SCFG-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT]])
; GVN-SCFG-NEXT: store volatile i64 [[T3]], ptr [[STORAGE1]], align 8
; GVN-SCFG-NEXT: store volatile i64 [[T4]], ptr [[STORAGE2]], align 8
; GVN-SCFG-NEXT: ret void
@@ -100,11 +98,7 @@ define void @test_interleaved(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i6
; GVN-NEXT: [[ENTRY:.*:]]
; GVN-NEXT: [[STORAGE1_I:%.*]] = ptrtoint ptr [[STORAGE1]] to i64
; GVN-NEXT: [[STORAGE2_I:%.*]] = ptrtoint ptr [[STORAGE2]] to i64
-; GVN-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[ENTRY_EXIT_CRIT_EDGE:.*]]
-; GVN: [[ENTRY_EXIT_CRIT_EDGE]]:
-; GVN-NEXT: [[DOTPRE:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
-; GVN-NEXT: [[DOTPRE1:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
-; GVN-NEXT: br label %[[EXIT:.*]]
+; GVN-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[EXIT:.*]]
; GVN: [[IF_THEN]]:
; GVN-NEXT: [[DISCR1_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
; GVN-NEXT: [[T1:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[A]], i32 2, i64 [[DISCR1_THEN]])
@@ -114,10 +108,10 @@ define void @test_interleaved(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i6
; GVN-NEXT: store volatile i64 [[T2]], ptr [[STORAGE2]], align 8
; GVN-NEXT: br label %[[EXIT]]
; GVN: [[EXIT]]:
-; GVN-NEXT: [[DISCR2_EXIT_PRE_PHI:%.*]] = phi i64 [ [[DOTPRE1]], %[[ENTRY_EXIT_CRIT_EDGE]] ], [ [[DISCR2_THEN]], %[[IF_THEN]] ]
-; GVN-NEXT: [[DISCR1_EXIT_PRE_PHI:%.*]] = phi i64 [ [[DOTPRE]], %[[ENTRY_EXIT_CRIT_EDGE]] ], [ [[DISCR1_THEN]], %[[IF_THEN]] ]
-; GVN-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT_PRE_PHI]])
-; GVN-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT_PRE_PHI]])
+; GVN-NEXT: [[DISCR1_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
+; GVN-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT]])
+; GVN-NEXT: [[DISCR2_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
+; GVN-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT]])
; GVN-NEXT: store volatile i64 [[T3]], ptr [[STORAGE1]], align 8
; GVN-NEXT: store volatile i64 [[T4]], ptr [[STORAGE2]], align 8
; GVN-NEXT: ret void
@@ -127,12 +121,9 @@ define void @test_interleaved(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i6
; GVN-SCFG-NEXT: [[ENTRY:.*:]]
; GVN-SCFG-NEXT: [[STORAGE1_I:%.*]] = ptrtoint ptr [[STORAGE1]] to i64
; GVN-SCFG-NEXT: [[STORAGE2_I:%.*]] = ptrtoint ptr [[STORAGE2]] to i64
-; GVN-SCFG-NEXT: [[DISCR1_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
-; GVN-SCFG-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[ENTRY_EXIT_CRIT_EDGE:.*]]
-; GVN-SCFG: [[ENTRY_EXIT_CRIT_EDGE]]:
-; GVN-SCFG-NEXT: [[DOTPRE1:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
-; GVN-SCFG-NEXT: br label %[[EXIT:.*]]
+; GVN-SCFG-NEXT: br i1 [[COND]], label %[[IF_THEN:.*]], label %[[EXIT:.*]]
; GVN-SCFG: [[IF_THEN]]:
+; GVN-SCFG-NEXT: [[DISCR1_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
; GVN-SCFG-NEXT: [[T1:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[A]], i32 2, i64 [[DISCR1_THEN]])
; GVN-SCFG-NEXT: [[DISCR2_THEN:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
; GVN-SCFG-NEXT: [[T2:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[A]], i32 2, i64 [[DISCR2_THEN]])
@@ -140,10 +131,10 @@ define void @test_interleaved(i1 %cond, ptr %storage1, ptr %storage2, i64 %a, i6
; GVN-SCFG-NEXT: store volatile i64 [[T2]], ptr [[STORAGE2]], align 8
; GVN-SCFG-NEXT: br label %[[EXIT]]
; GVN-SCFG: [[EXIT]]:
-; GVN-SCFG-NEXT: [[DISCR2_EXIT_PRE_PHI:%.*]] = phi i64 [ [[DOTPRE1]], %[[ENTRY_EXIT_CRIT_EDGE]] ], [ [[DISCR2_THEN]], %[[IF_THEN]] ]
-; GVN-SCFG-NEXT: [[DISCR1_EXIT_PRE_PHI:%.*]] = phi i64 [ [[DISCR1_THEN]], %[[ENTRY_EXIT_CRIT_EDGE]] ], [ [[DISCR1_THEN]], %[[IF_THEN]] ]
-; GVN-SCFG-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT_PRE_PHI]])
-; GVN-SCFG-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT_PRE_PHI]])
+; GVN-SCFG-NEXT: [[DISCR1_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE1_I]], i64 42)
+; GVN-SCFG-NEXT: [[T3:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR1_EXIT]])
+; GVN-SCFG-NEXT: [[DISCR2_EXIT:%.*]] = call i64 @llvm.ptrauth.blend(i64 [[STORAGE2_I]], i64 42)
+; GVN-SCFG-NEXT: [[T4:%.*]] = call i64 @llvm.ptrauth.sign(i64 [[B]], i32 2, i64 [[DISCR2_EXIT]])
; GVN-SCFG-NEXT: store volatile i64 [[T3]], ptr [[STORAGE1]], align 8
; GVN-SCFG-NEXT: store volatile i64 [[T4]], ptr [[STORAGE2]], align 8
; GVN-SCFG-NEXT: ret void