[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