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

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 26 08:29:06 PDT 2026


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

>From b3ec39dc4a1183418aa11469a093e4d6f6a2a757 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 25 Sep 2026 12:55:28 +0100
Subject: [PATCH] [IR] Verify oracle functions are only used by
 llvm.speculative.load.

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.
---
 llvm/docs/LangRef.md                          |  2 +
 llvm/lib/IR/Verifier.cpp                      | 18 ++++
 .../AArch64/speculative-load-intrinsic.ll     |  8 +-
 .../CodeGen/X86/speculative-load-intrinsic.ll |  8 +-
 llvm/test/Verifier/speculative-load.ll        | 94 +++++++++++++++++--
 5 files changed, 120 insertions(+), 10 deletions(-)

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..c9ce7e0cd5434 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,17 @@ 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)) {
+    Check(F.hasLocalLinkage(), "oracle function must have local linkage", &F);
+    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)
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..f077b861eb8e4 100644
--- a/llvm/test/Verifier/speculative-load.ll
+++ b/llvm/test/Verifier/speculative-load.ll
@@ -13,14 +13,33 @@ 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
+}
+; CHECK: oracle function must have local linkage
+; CHECK-NEXT: ptr @external_oracle
+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 +146,11 @@ define b128 @test_non_function_oracle(ptr %ptr, ptr %not_fn) {
   ret b128 %res
 }
 
+define b128 @test_oracle_external_linkage(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
+}



More information about the llvm-commits mailing list