[llvm] [IR] Verify oracle functions are only used by llvm.speculative.load. (PR #226669)

via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 02:35:04 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-aarch64

Author: Florian Hahn (fhahn)

<details>
<summary>Changes</summary>

Update speculative load verification rules to enforce that speculative load oracle functions are only used by speculative loads and require local linakge.

This simplifies dropping oracle functions during codegen.

---
Full diff: https://github.com/llvm/llvm-project/pull/226669.diff


5 Files Affected:

- (modified) llvm/docs/LangRef.md (+2) 
- (modified) llvm/lib/IR/Verifier.cpp (+20) 
- (modified) llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll (+7-1) 
- (modified) llvm/test/CodeGen/X86/speculative-load-intrinsic.ll (+7-1) 
- (modified) llvm/test/Verifier/speculative-load.ll (+86-8) 


``````````diff
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index ee7068b2125cc..30dda68b859f9 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -24239,6 +24239,8 @@ directly. In the **oracle form**, the third argument must be a direct
 reference to a non-variadic function returning `i64` that is `nounwind`,
 `nosync` and `willreturn` and may only read memory through its arguments;
 the remaining arguments are forwarded to it, and its return value is `N`.
+The oracle function must have local linkage, and it may only be used as the
+oracle argument of '`llvm.speculative.load`' calls.
 
 ##### Semantics:
 
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 2de006bfc032d..53cf3fe847c0e 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3202,6 +3202,13 @@ void Verifier::verifySiblingFuncletUnwinds() {
   }
 }
 
+/// Returns true if \p U is the oracle operand of an llvm.speculative.load.
+static bool isSpeculativeLoadOracleUse(const Use &U) {
+  auto *II = dyn_cast<IntrinsicInst>(U.getUser());
+  return II && II->getIntrinsicID() == Intrinsic::speculative_load &&
+         II->isArgOperand(&U) && II->getArgOperandNo(&U) == 2;
+}
+
 // visitFunction - Verify that a function is ok.
 //
 void Verifier::visitFunction(const Function &F) {
@@ -3494,6 +3501,16 @@ void Verifier::visitFunction(const Function &F) {
           PrintDecl);
   }
 
+  // A function used as the oracle of llvm.speculative.load may not be
+  // referenced in any other way.
+  if (isMaterialized && any_of(F.uses(), isSpeculativeLoadOracleUse)) {
+    for (const Use &U : F.uses())
+      Check(isSpeculativeLoadOracleUse(U),
+            "oracle function may only be used as the oracle operand of "
+            "llvm.speculative.load",
+            &F, U.getUser());
+  }
+
   auto *N = F.getSubprogram();
   HasDebugInfo = (N != nullptr);
   if (!HasDebugInfo)
@@ -7054,6 +7071,9 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
             "llvm.speculative.load third argument must be i64 or a direct "
             "reference to an oracle function",
             &Call);
+      Check(OracleFn->hasLocalLinkage(),
+            "llvm.speculative.load oracle function must have local linkage",
+            &Call);
 
       // Make sure the called oracle matches the attributes of the intrinsic.
       Check(OracleFn->onlyReadsMemory() && OracleFn->onlyAccessesArgMemory() &&
diff --git a/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll b/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll
index ae90f2e3a7fd4..d2337e24a6681 100644
--- a/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll
+++ b/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll
@@ -77,7 +77,13 @@ define <vscale x 2 x double> @speculative_load_nxv2f64(ptr %ptr) {
 
 ; Oracle form tests
 
-declare i64 @oracle(ptr, i64) memory(argmem: read) nounwind nosync willreturn
+define internal i64 @oracle(ptr %p, i64 %n) memory(argmem: read) nounwind nosync willreturn {
+; CHECK-LABEL: oracle:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    mov x0, x1
+; CHECK-NEXT:    ret
+  ret i64 %n
+}
 
 define b128 @speculative_load_b128_oracle(ptr %ptr, i64 %n) {
 ; CHECK-LABEL: speculative_load_b128_oracle:
diff --git a/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll b/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll
index c3fc86dccebe1..40f629857aac3 100644
--- a/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll
+++ b/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll
@@ -67,7 +67,13 @@ define <2 x double> @speculative_load_v2f64(ptr %ptr) {
 }
 
 ; Oracle form tests
-declare i64 @oracle(ptr, i64) memory(argmem: read) nounwind nosync willreturn
+define internal i64 @oracle(ptr %p, i64 %n) memory(argmem: read) nounwind nosync willreturn {
+; CHECK-LABEL: oracle:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movq %rsi, %rax
+; CHECK-NEXT:    retq
+  ret i64 %n
+}
 
 define b128 @speculative_load_b128_oracle(ptr %ptr, i64 %n) {
 ; CHECK-LABEL: speculative_load_b128_oracle:
diff --git a/llvm/test/Verifier/speculative-load.ll b/llvm/test/Verifier/speculative-load.ll
index d2241ee09037e..0ca4d0ff988fd 100644
--- a/llvm/test/Verifier/speculative-load.ll
+++ b/llvm/test/Verifier/speculative-load.ll
@@ -13,14 +13,31 @@ declare [4 x i32] @llvm.speculative.load.a4i32.p0(ptr, i1, ...)
 declare <4 x b3> @llvm.speculative.load.v4b3.p0(ptr, i1, ...)
 declare <3 x ptr> @llvm.speculative.load.v3p0.p0(ptr, i1, ...)
 
-declare i32 @bad_oracle_ret(ptr, i64) memory(argmem: read) nounwind nosync willreturn
-declare i64 @good_oracle(ptr, i64) memory(argmem: read) nounwind nosync willreturn
-declare i64 @oracle_i32_param(i32) memory(argmem: read) nounwind nosync willreturn
-declare i64 @side_effecting_oracle(ptr, i64)
-declare i64 @throwing_oracle(ptr, i64) memory(argmem: read) nosync willreturn
-declare i64 @syncing_oracle(ptr, i64) memory(argmem: read) nounwind willreturn
-declare i64 @looping_oracle(ptr, i64) memory(argmem: read) nounwind nosync
-declare i64 @variadic_oracle(i64, ...) memory(argmem: read) nounwind nosync willreturn
+define internal i32 @bad_oracle_ret(ptr %p, i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i32 0
+}
+define internal i64 @good_oracle(ptr %p, i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+define internal i64 @oracle_i32_param(i32 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 0
+}
+define internal i64 @side_effecting_oracle(ptr %p, i64 %n) {
+  ret i64 %n
+}
+define internal i64 @throwing_oracle(ptr %p, i64 %n) memory(argmem: read) nosync willreturn {
+  ret i64 %n
+}
+define internal i64 @syncing_oracle(ptr %p, i64 %n) memory(argmem: read) nounwind willreturn {
+  ret i64 %n
+}
+define internal i64 @looping_oracle(ptr %p, i64 %n) memory(argmem: read) nounwind nosync {
+  ret i64 %n
+}
+define internal i64 @variadic_oracle(i64 %n, ...) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+declare i64 @external_oracle(ptr, i64) memory(argmem: read) nounwind nosync willreturn
 
 define i32 @test_non_byte_non_vector_int(ptr %ptr) {
 ; CHECK: llvm.speculative.load return type must be a byte type or a vector type
@@ -127,6 +144,13 @@ define b128 @test_non_function_oracle(ptr %ptr, ptr %not_fn) {
   ret b128 %res
 }
 
+define b128 @test_oracle_external_linkage(ptr %ptr, i64 %n) {
+; CHECK: llvm.speculative.load oracle function must have local linkage
+; CHECK-NEXT: call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @external_oracle, ptr %ptr, i64 %n)
+  %res = call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @external_oracle, ptr %ptr, i64 %n)
+  ret b128 %res
+}
+
 define b128 @test_oracle_side_effects(ptr %ptr, i64 %n) {
 ; CHECK: llvm.speculative.load oracle function must be nounwind, nosync and willreturn, must not have side effects and may only read memory through its arguments
 ; CHECK-NEXT: call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @side_effecting_oracle, ptr %ptr, i64 %n)
@@ -183,3 +207,57 @@ define <3 x ptr> @test_vector_of_pointers_size_not_pow2(ptr %ptr) {
   %res = call <3 x ptr> (ptr, i1, ...) @llvm.speculative.load.v3p0.p0(ptr %ptr, i1 false, i64 24)
   ret <3 x ptr> %res
 }
+
+; Oracle functions may only be used as the oracle operand of
+; llvm.speculative.load.
+
+ at llvm.used = appending global [1 x ptr] [ptr @oracle_in_used], section "llvm.metadata"
+ at alias = internal alias i64 (i64), ptr @oracle_aliased
+
+; CHECK: oracle function may only be used as the oracle operand of llvm.speculative.load
+; CHECK-NEXT: ptr @oracle_called
+; CHECK-NEXT: %r = call i64 @oracle_called(i64 %n)
+define internal i64 @oracle_called(i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+
+; CHECK: oracle function may only be used as the oracle operand of llvm.speculative.load
+; CHECK-NEXT: ptr @oracle_stored
+; CHECK-NEXT: store ptr @oracle_stored, ptr %ptr
+define internal i64 @oracle_stored(i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+
+; CHECK: oracle function may only be used as the oracle operand of llvm.speculative.load
+; CHECK-NEXT: ptr @oracle_in_used
+; CHECK-NEXT: [1 x ptr] [ptr @oracle_in_used]
+define internal i64 @oracle_in_used(i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+
+; CHECK: oracle function may only be used as the oracle operand of llvm.speculative.load
+; CHECK-NEXT: ptr @oracle_aliased
+; CHECK-NEXT: ptr @alias
+define internal i64 @oracle_aliased(i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+
+; Operand 2 of an intrinsic other than llvm.speculative.load.
+; CHECK: oracle function may only be used as the oracle operand of llvm.speculative.load
+; CHECK-NEXT: ptr @oracle_other_intrinsic
+; CHECK-NEXT: call void (i64, i32, ...) @llvm.experimental.stackmap(i64 0, i32 0, ptr @oracle_other_intrinsic)
+define internal i64 @oracle_other_intrinsic(i64 %n) memory(argmem: read) nounwind nosync willreturn {
+  ret i64 %n
+}
+
+define b128 @test_oracle_invalid_uses(ptr %ptr, i64 %n) {
+  %r = call i64 @oracle_called(i64 %n)
+  store ptr @oracle_stored, ptr %ptr
+  call void (i64, i32, ...) @llvm.experimental.stackmap(i64 0, i32 0, ptr @oracle_other_intrinsic)
+  %a = call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @oracle_called, i64 %n)
+  %c = call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @oracle_stored, i64 %n)
+  %d = call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @oracle_in_used, i64 %n)
+  %e = call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @oracle_aliased, i64 %n)
+  %f = call b128 (ptr, i1, ...) @llvm.speculative.load.b128.p0(ptr %ptr, i1 false, ptr @oracle_other_intrinsic, i64 %n)
+  ret b128 %f
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/226669


More information about the llvm-commits mailing list