[llvm] [CodeGen] Discard oracle functions during ISel translation (PR #188017)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Sat Sep 26 08:30:17 PDT 2026
https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/188017
>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 1/2] [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
+}
>From 32ce3faff8770786fde5c1396f06ca7c07cff66a Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 19 Mar 2026 17:11:17 +0000
Subject: [PATCH 2/2] [CodeGen] Discard oracle functions during ISel
translation.
Update IRTranslator.cpp and SelectionDAGISel.cpp to set the linkage of
oracle functions to available_externally, so no code is generated for
them.
Depends on verifier strengthening
https://github.com/llvm/llvm-project/pull/226669 (included in PR)
---
llvm/include/llvm/IR/IntrinsicInst.h | 7 +++++++
llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp | 7 +++++++
.../lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp | 7 +++++++
llvm/lib/IR/IntrinsicInst.cpp | 11 +++++++++++
llvm/lib/IR/Verifier.cpp | 7 -------
.../GlobalISel/speculative-load-intrinsic.ll | 15 ++++++++++++++-
.../CodeGen/AArch64/speculative-load-intrinsic.ll | 6 +-----
.../CodeGen/X86/speculative-load-intrinsic.ll | 6 +-----
8 files changed, 48 insertions(+), 18 deletions(-)
diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h
index 7d0bb92114e84..80c16f29dbae0 100644
--- a/llvm/include/llvm/IR/IntrinsicInst.h
+++ b/llvm/include/llvm/IR/IntrinsicInst.h
@@ -1832,6 +1832,13 @@ class StructuredGEPInst : public IntrinsicInst {
}
};
+/// Returns true if \p U is the oracle operand of an llvm.speculative.load.
+LLVM_ABI bool isSpeculativeLoadOracleUse(const Use &U);
+
+/// Returns true if \p F is used, and only used, as the oracle operand of
+/// llvm.speculative.load. No code needs to be emitted for such functions.
+LLVM_ABI bool isSpeculativeLoadOracle(const Function &F);
+
} // end namespace llvm
#endif // LLVM_IR_INTRINSICINST_H
diff --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index cbad39bf4016c..d0fe5e6671a6d 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -5115,6 +5115,13 @@ bool IRTranslatorImpl::runOnMachineFunction(
const LibcallLoweringInfo *LibcallInfo, SSPLayoutInfo *StackProtectorInfo) {
MF = &CurMF;
const Function &F = MF->getFunction();
+
+ // Make sure oracle functions are deleted.
+ if (isSpeculativeLoadOracle(F)) {
+ MF->getFunction().setLinkage(GlobalValue::AvailableExternallyLinkage);
+ return false;
+ }
+
ORE = std::make_unique<OptimizationRemarkEmitter>(&F);
CLI = MF->getSubtarget().getCallLowering();
SPInfo = StackProtectorInfo;
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 97061800ddba3..fb464c8b2095a 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -376,6 +376,13 @@ bool SelectionDAGISelLegacy::runOnMachineFunction(MachineFunction &MF) {
if (MF.getProperties().hasSelected())
return false;
+ // Make sure oracle functions are deleted.
+ Function &F = MF.getFunction();
+ if (isSpeculativeLoadOracle(F)) {
+ F.setLinkage(GlobalValue::AvailableExternallyLinkage);
+ return false;
+ }
+
// Do some sanity-checking on the command-line options.
if (EnableFastISelAbort && !Selector->TM.Options.EnableFastISel)
reportFatalUsageError("-fast-isel-abort > 0 requires -fast-isel");
diff --git a/llvm/lib/IR/IntrinsicInst.cpp b/llvm/lib/IR/IntrinsicInst.cpp
index 684aaf1a8f2d3..5d93b41a9397e 100644
--- a/llvm/lib/IR/IntrinsicInst.cpp
+++ b/llvm/lib/IR/IntrinsicInst.cpp
@@ -807,3 +807,14 @@ ConvergenceControlInst::CreateLoop(BasicBlock &BB,
auto *Call = CallInst::Create(Fn, {}, {OB}, "", BB.getFirstInsertionPt());
return cast<ConvergenceControlInst>(Call);
}
+
+bool llvm::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;
+}
+
+bool llvm::isSpeculativeLoadOracle(const Function &F) {
+ return F.hasLocalLinkage() && !F.use_empty() &&
+ all_of(F.uses(), isSpeculativeLoadOracleUse);
+}
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index c9ce7e0cd5434..c152bfe1547f3 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3202,13 +3202,6 @@ 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) {
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/speculative-load-intrinsic.ll b/llvm/test/CodeGen/AArch64/GlobalISel/speculative-load-intrinsic.ll
index 86bd8f49629de..1b601683abffc 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/speculative-load-intrinsic.ll
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/speculative-load-intrinsic.ll
@@ -1,6 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
; RUN: llc -mtriple=aarch64-unknown-linux-gnu -mattr=+sve -global-isel \
-; RUN: -global-isel-abort=1 -aarch64-enable-gisel-sve=1 < %s | FileCheck %s
+; RUN: -global-isel-abort=1 -aarch64-enable-gisel-sve=1 < %s | FileCheck %s --implicit-check-not='{{^}}oracle:'
define <4 x i32> @speculative_load_v4i32(ptr %ptr) {
; CHECK-LABEL: speculative_load_v4i32:
@@ -39,3 +39,16 @@ define <vscale x 4 x i32> @plain_load_nxv4i32(ptr %ptr) {
%r = load <vscale x 4 x i32>, ptr %ptr, align 16
ret <vscale x 4 x i32> %r
}
+
+define internal i64 @oracle(ptr %p, i64 %n) memory(argmem: read) nounwind nosync willreturn {
+ ret i64 %n
+}
+
+define <4 x i32> @speculative_load_v4i32_oracle(ptr %ptr, i64 %n) {
+; CHECK-LABEL: speculative_load_v4i32_oracle:
+; CHECK: // %bb.0:
+; CHECK-NEXT: ldr q0, [x0]
+; CHECK-NEXT: ret
+ %r = call <4 x i32> (ptr, i1, ...) @llvm.speculative.load.v4i32.p0(ptr align 16 %ptr, i1 false, ptr @oracle, ptr %ptr, i64 %n)
+ ret <4 x i32> %r
+}
diff --git a/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll b/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll
index d2337e24a6681..9a111336110a4 100644
--- a/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll
+++ b/llvm/test/CodeGen/AArch64/speculative-load-intrinsic.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: llc -mtriple=aarch64-unknown-linux-gnu -mattr=+sve < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu -mattr=+sve < %s | FileCheck %s --implicit-check-not='{{^}}oracle:'
; Test that @llvm.speculative.load is lowered to a regular load
; in SelectionDAG for fixed vectors, scalable vectors, and bytes.
@@ -78,10 +78,6 @@ define <vscale x 2 x double> @speculative_load_nxv2f64(ptr %ptr) {
; Oracle form tests
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
}
diff --git a/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll b/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll
index 40f629857aac3..e4a7d52e45f96 100644
--- a/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll
+++ b/llvm/test/CodeGen/X86/speculative-load-intrinsic.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: llc -mtriple=x86_64-unknown-linux-gnu -mattr=+avx2 < %s | FileCheck %s
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu -mattr=+avx2 < %s | FileCheck %s --implicit-check-not='{{^}}oracle:'
; Test that @llvm.speculative.load is lowered to a regular load
; in SelectionDAG for fixed vectors and bytes.
@@ -68,10 +68,6 @@ define <2 x double> @speculative_load_v2f64(ptr %ptr) {
; Oracle form tests
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
}
More information about the llvm-commits
mailing list