[llvm] [AMDGPU] Replace relaxed-buffer-oob-mode feature with module flag (PR #160922)
Piotr Sobczak via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 13 06:08:22 PDT 2026
https://github.com/piotrAMD updated https://github.com/llvm/llvm-project/pull/160922
>From 59a853360c32552ad4b233cc698806a01f17b383 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Fri, 26 Sep 2025 17:58:13 +0200
Subject: [PATCH 1/7] [AMDGPU][Draft] OOB mode - module flag
Draft of a solution based on a module flag to replace the subtarget
feature with module flag.
---
llvm/lib/Target/AMDGPU/AMDGPU.td | 6 ------
llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp | 10 ++++++++++
llvm/lib/Target/AMDGPU/GCNSubtarget.h | 15 +++++++++++++--
3 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/AMDGPU.td b/llvm/lib/Target/AMDGPU/AMDGPU.td
index eaa1870f4be28..d158a10c01738 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPU.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPU.td
@@ -128,12 +128,6 @@ def FeatureUnalignedDSAccess : SubtargetFeature<"unaligned-ds-access",
"Hardware supports unaligned local and region loads and stores"
>;
-def FeatureRelaxedBufferOOBMode : SubtargetFeature<"relaxed-buffer-oob-mode",
- "RelaxedBufferOOBMode",
- "true",
- "Disable strict out-of-bounds buffer guarantees. An OOB access may potentially cause an adjacent access to be treated as if it were also OOB"
->;
-
def FeatureApertureRegs : SubtargetFeature<"aperture-regs",
"HasApertureRegs",
"true",
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index 92a587b5771b6..fdfc2f9a079f7 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -1095,6 +1095,15 @@ bool AMDGPUTargetMachine::splitModule(
return true;
}
+static unsigned getOOBModeFromModule(const Module *M) {
+ unsigned Mode = 0;
+ if (M)
+ if (Metadata *MD = M->getModuleFlag("amdgpu.oob.mode"))
+ if (auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(MD))
+ Mode = CI->getZExtValue();
+ return Mode;
+}
+
//===----------------------------------------------------------------------===//
// GCN Target Machine (SI+)
//===----------------------------------------------------------------------===//
@@ -1125,6 +1134,7 @@ GCNTargetMachine::getSubtargetImpl(const Function &F) const {
}
I->setScalarizeGlobalBehavior(ScalarizeGlobal);
+ I->setOOBMode(getOOBModeFromModule(F.getParent()));
return I.get();
}
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index a54d6651c25c1..cf50a0c6d088b 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -78,7 +78,6 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
bool BackOffBarrier = false;
bool UnalignedScratchAccess = false;
bool UnalignedAccessMode = false;
- bool RelaxedBufferOOBMode = false;
bool HasApertureRegs = false;
bool SupportsXNACK = false;
bool KernargPreload = false;
@@ -291,6 +290,17 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
// Dummy feature to use for assembler in tablegen.
bool FeatureDisable = false;
+ // Module flag features.
+
+ // Out-Of-Bounds mode flags.
+ // Setting a bit enables a relaxed mode that disables strict OOB guarantees;
+ // an out-of-bounds access may cause a neighboring in-bounds access to be
+ // treated as OOB.
+ // If bit is set, enable relaxed mode. 0 in a bit keeps the corresponding check strict.
+ // OOBMode{0} - untyped buffers (buffer_load)
+ // OOBMode{1} - typed buffers (tbuffer_load)
+ unsigned OOBMode = 0;
+
private:
SIInstrInfo InstrInfo;
SITargetLowering TLInfo;
@@ -646,7 +656,8 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
return UnalignedAccessMode;
}
- bool hasRelaxedBufferOOBMode() const { return RelaxedBufferOOBMode; }
+ bool hasRelaxedBufferOOBMode() const { return OOBMode == 1; // TODO: Use named const/enum.}
+ void setOOBMode(unsigned val) { OOBMode = val; }
bool hasApertureRegs() const {
return HasApertureRegs;
>From 5394c17828838b46dcb3ba3563cd47f84ae40e81 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Mon, 9 Mar 2026 13:56:30 +0100
Subject: [PATCH 2/7] Address review comments
---
llvm/docs/AMDGPUUsage.rst | 55 +++++++++++++++
llvm/lib/IR/Verifier.cpp | 14 ++++
llvm/lib/Target/AMDGPU/AMDGPU.td | 5 --
.../lib/Target/AMDGPU/AMDGPUTargetMachine.cpp | 22 +++---
llvm/lib/Target/AMDGPU/GCNSubtarget.h | 35 ++++++----
llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 13 ++--
llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll | 4 +-
.../AMDGPU/merge-vectors.ll | 8 ++-
.../AMDGPU/unaligned-buffer.ll | 15 +++--
.../Verifier/AMDGPU/module-flag-oob-mode.ll | 67 +++++++++++++++++++
10 files changed, 195 insertions(+), 43 deletions(-)
create mode 100644 llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
diff --git a/llvm/docs/AMDGPUUsage.rst b/llvm/docs/AMDGPUUsage.rst
index 036b4461ec06d..6dae7c661a747 100644
--- a/llvm/docs/AMDGPUUsage.rst
+++ b/llvm/docs/AMDGPUUsage.rst
@@ -812,6 +812,61 @@ For example:
=============== ============================ ==================================================
+.. _amdgpu-module-flags:
+
+Module Flags
+------------
+
+AMDGPU-specific behaviour can be controlled via LLVM module flags (see
+`Module Flags Metadata
+<https://llvm.org/docs/LangRef.html#module-flags-metadata>`_ in the language
+reference). These flags are set by frontends and are
+consumed by the AMDGPU backend during code generation.
+
+.. table:: AMDGPU Module Flags
+ :name: amdgpu-module-flags-table
+
+ =================== =========== ===== ================================================
+ Flag Name Type Merge Description
+ Behaviour
+ =================== =========== ===== ================================================
+ ``amdgpu.oob.mode`` ``i32`` Min Bitmask controlling relaxation of out-of-bounds
+ (OOB) buffer access semantics. When a bit is
+ **cleared** (strict mode, the default), the
+ backend ensures that misaligned buffer accesses
+ that straddle an OOB boundary are not merged,
+ preserving correct per-byte robustness guarantees
+ (e.g. required by Vulkan ``robustBufferAccess2``).
+ When a bit is **set** (relaxed mode), the backend
+ may merge such accesses for performance, which
+ is safe for workloads that do not
+ require strict OOB byte-level isolation.
+
+ Bits:
+
+ * ``0x1`` — relax OOB handling for **untyped**
+ buffer instructions (``buffer_load`` /
+ ``buffer_store``).
+ * ``0x2`` — relax OOB handling for **typed**
+ buffer instructions (``tbuffer_load`` /
+ ``tbuffer_store``).
+
+ The ``Min`` merge behaviour means that when
+ modules are linked, the strictest (smallest)
+ value wins: a strict module (value ``0``) linked
+ with a relaxed module always produces strict
+ semantics.
+
+ .. note::
+
+ Frontends that require misaligned-access
+ merging for performance must set this flag
+ (bits ``0x3``). Frontends that require strict
+ per-byte OOB guarantees (e.g. Vulkan
+ ``robustBufferAccess2``) should leave the
+ flag absent or set to ``0``.
+ =================== =========== ===== ================================================
+
.. _amdgpu-target-id:
Target ID
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 3784ee00811f8..3b5b462c5bed2 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2070,6 +2070,20 @@ Verifier::visitModuleFlag(const MDNode *Op,
"SemanticInterposition metadata requires constant integer argument");
}
+ if (ID->getString() == "amdgpu.oob.mode") {
+ Check(MFB == Module::Min,
+ "'amdgpu.oob.mode' module flag must use 'min' merge behaviour");
+ ConstantInt *Value =
+ mdconst::dyn_extract_or_null<ConstantInt>(Op->getOperand(2));
+ Check(Value,
+ "'amdgpu.oob.mode' module flag must have a constant integer value");
+ if (Value) {
+ constexpr uint32_t KnownBits = 0x3; // UntypedBuffer | TypedBuffer
+ Check((Value->getZExtValue() & ~KnownBits) == 0,
+ "'amdgpu.oob.mode' module flag has unknown bits set");
+ }
+ }
+
if (ID->getString() == "CG Profile") {
for (const MDOperand &MDO : cast<MDNode>(Op->getOperand(2))->operands())
visitModuleFlagCGProfileEntry(MDO);
diff --git a/llvm/lib/Target/AMDGPU/AMDGPU.td b/llvm/lib/Target/AMDGPU/AMDGPU.td
index ac561327a79d4..6ad730d51405a 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPU.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPU.td
@@ -155,11 +155,6 @@ defm UnalignedDSAccess : AMDGPUSubtargetFeature<"unaligned-ds-access",
"Hardware supports unaligned local and region loads and stores"
>;
-defm RelaxedBufferOOBMode : AMDGPUSubtargetFeature<"relaxed-buffer-oob-mode",
- "Disable strict out-of-bounds buffer guarantees. An OOB access may potentially"
- "cause an adjacent access to be treated as if it were also OOB"
->;
-
defm DX10ClampAndIEEEMode : AMDGPUSubtargetFeature<"dx10-clamp-and-ieee-mode",
"Target has DX10_CLAMP and IEEE_MODE kernel descriptor bits"
>;
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index 16fc06ed27c2a..ae12c5c0c7775 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -90,6 +90,7 @@
#include "llvm/CodeGen/RegAllocRegistry.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/IntrinsicsAMDGPU.h"
+#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/InitializePasses.h"
@@ -1188,15 +1189,6 @@ bool AMDGPUTargetMachine::splitModule(
return true;
}
-static unsigned getOOBModeFromModule(const Module *M) {
- unsigned Mode = 0;
- if (M)
- if (Metadata *MD = M->getModuleFlag("amdgpu.oob.mode"))
- if (auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(MD))
- Mode = CI->getZExtValue();
- return Mode;
-}
-
//===----------------------------------------------------------------------===//
// GCN Target Machine (SI+)
//===----------------------------------------------------------------------===//
@@ -1209,13 +1201,23 @@ GCNTargetMachine::GCNTargetMachine(const Target &T, const Triple &TT,
CodeGenOptLevel OL, bool JIT)
: AMDGPUTargetMachine(T, TT, CPU, FS, Options, RM, CM, OL) {}
+/// Returns the value of the "amdgpu.oob.mode" module flag, or 0 if absent.
+/// See AMDGPUOOBMode for the bit definitions.
+static unsigned getOOBModeFromModule(const Module &M) {
+ const auto *Flag =
+ mdconst::dyn_extract_or_null<ConstantInt>(M.getModuleFlag("amdgpu.oob.mode"));
+ return Flag ? static_cast<unsigned>(Flag->getZExtValue()) : 0u;
+}
+
const TargetSubtargetInfo *
GCNTargetMachine::getSubtargetImpl(const Function &F) const {
StringRef GPU = getGPUName(F);
StringRef FS = getFeatureString(F);
+ unsigned OOBMode = getOOBModeFromModule(*F.getParent());
SmallString<128> SubtargetKey(GPU);
SubtargetKey.append(FS);
+ SubtargetKey.append((",oob=" + Twine(OOBMode)).str());
auto &I = SubtargetMap[SubtargetKey];
if (!I) {
@@ -1224,10 +1226,10 @@ GCNTargetMachine::getSubtargetImpl(const Function &F) const {
// function that reside in TargetOptions.
resetTargetOptions(F);
I = std::make_unique<GCNSubtarget>(TargetTriple, GPU, FS, *this);
+ I->setOOBMode(OOBMode);
}
I->setScalarizeGlobalBehavior(ScalarizeGlobal);
- I->setOOBMode(getOOBModeFromModule(F.getParent()));
return I.get();
}
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index fda5867c0f928..609c7af8582ab 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -30,6 +30,22 @@ namespace llvm {
class GCNTargetMachine;
+/// Bit flags for the "amdgpu.oob.mode" LLVM module flag.
+/// These control per-module relaxation of out-of-bounds (OOB) buffer access
+/// semantics. Using Module::Min merge behaviour, a stricter module always
+/// overrides a more relaxed one at link time.
+namespace AMDGPUOOBMode {
+enum : unsigned {
+ /// Relax OOB handling for untyped buffer instructions (buffer_load /
+ /// buffer_store). When set, the backend may merge misaligned accesses across
+ /// an OOB boundary, which would be incorrect under strict Vulkan robustness.
+ UntypedBuffer = 0x1,
+ /// Relax OOB handling for typed buffer instructions (tbuffer_load /
+ /// tbuffer_store).
+ TypedBuffer = 0x2,
+};
+} // namespace AMDGPUOOBMode
+
class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
public AMDGPUSubtarget {
public:
@@ -70,6 +86,7 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
bool DynamicVGPR = false;
bool DynamicVGPRBlockSize32 = false;
bool ScalarizeGlobal = false;
+ unsigned OOBMode = 0;
/// The maximum number of instructions that may be placed within an S_CLAUSE,
/// which is one greater than the maximum argument to S_CLAUSE. A value of 0
@@ -80,17 +97,6 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
bool ATTRIBUTE = DEFAULT;
#include "AMDGPUGenSubtargetInfo.inc"
- // Module flag features.
-
- // Out-Of-Bounds mode flags.
- // Setting a bit enables a relaxed mode that disables strict OOB guarantees;
- // an out-of-bounds access may cause a neighboring in-bounds access to be
- // treated as OOB.
- // If bit is set, enable relaxed mode. 0 in a bit keeps the corresponding check strict.
- // OOBMode{0} - untyped buffers (buffer_load)
- // OOBMode{1} - typed buffers (tbuffer_load)
- unsigned OOBMode = 0;
-
private:
SIInstrInfo InstrInfo;
SITargetLowering TLInfo;
@@ -327,10 +333,13 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
bool isXNACKEnabled() const { return TargetID.isXnackOnOrAny(); }
- bool hasRelaxedBufferOOBMode() const { return OOBMode == 1; // TODO: Use named const/enum.}
- void setOOBMode(unsigned val) { OOBMode = val; }
bool isTgSplitEnabled() const { return EnableTgSplit; }
+ bool hasRelaxedBufferOOBMode() const {
+ return OOBMode & AMDGPUOOBMode::UntypedBuffer;
+ }
+ void setOOBMode(unsigned Val) { OOBMode = Val; }
+
bool isCuModeEnabled() const { return EnableCuMode; }
bool isPreciseMemoryEnabled() const { return EnablePreciseMemory; }
diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index 1ee43ab8d8172..1e27afe1589cc 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -2205,12 +2205,13 @@ bool SITargetLowering::allowsMisalignedMemoryAccessesImpl(
Subtarget->hasUnalignedBufferAccessEnabled();
}
- // Ensure robust out-of-bounds guarantees for buffer accesses are met if
- // RelaxedBufferOOBMode is disabled. Normally hardware will ensure proper
- // out-of-bounds behavior, but in the edge case where an access starts
- // out-of-bounds and then enter in-bounds, the entire access would be treated
- // as out-of-bounds. Prevent misaligned memory accesses by requiring the
- // natural alignment of buffer accesses.
+ // Ensure robust out-of-bounds guarantees for buffer accesses are met when the
+ // "amdgpu.oob.mode" module flag has not enabled relaxed untyped-buffer OOB
+ // semantics. Normally hardware will ensure proper out-of-bounds behavior, but
+ // in the edge case where an access starts out-of-bounds and then enters
+ // in-bounds, the entire access would be treated as out-of-bounds. Prevent
+ // misaligned memory accesses by requiring the natural alignment of buffer
+ // accesses.
if (AddrSpace == AMDGPUAS::BUFFER_FAT_POINTER ||
AddrSpace == AMDGPUAS::BUFFER_RESOURCE ||
AddrSpace == AMDGPUAS::BUFFER_STRIDED_POINTER) {
diff --git a/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll b/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
index b518d5e738031..8954ea4e9fd10 100644
--- a/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
+++ b/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
@@ -2,7 +2,7 @@
; RUN: llc -global-isel=0 -mtriple=amdgcn -mcpu=gfx1100 < %s | FileCheck -check-prefix=SDAG %s
; RUN: llc -global-isel=1 -new-reg-bank-select -mtriple=amdgcn -mcpu=gfx1100 < %s | FileCheck -check-prefix=GISEL %s
-; Check that in strict OOB mode for buffers (relaxed-buffer-oob-mode attribute not set) the underaligned loads and stores get split.
+; Check that in strict OOB mode for buffers ("amdgpu.oob.mode" module flag not set) the underaligned loads and stores get split.
; FIXME: The loads/stores do not get split (extend amdgpu-lower-buffer-fat-pointers?).
define amdgpu_ps void @split_underaligned_load(ptr addrspace(7) inreg %p, ptr addrspace(7) inreg %p2) #0 {
@@ -52,7 +52,7 @@ entry:
ret void
}
-; Check that in strict OOB mode for buffers (relaxed-buffer-oob-mode attribute not set) the naturally aligned loads and stores do not get split.
+; Check that in strict OOB mode for buffers ("amdgpu.oob.mode" module flag not set) the naturally aligned loads and stores do not get split.
define amdgpu_ps void @do_not_split_aligned_load(ptr addrspace(7) inreg %p, ptr addrspace(7) inreg %p2) #0 {
; CHECK-LABEL: do_not_split_aligned_load:
diff --git a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
index d6b51039d5b44..6d201b0e96db7 100644
--- a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
+++ b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
@@ -1,6 +1,10 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -mattr=+relaxed-buffer-oob-mode -S -o - %s | FileCheck --check-prefixes=CHECK,CHECK-OOB-RELAXED %s
-; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %s | FileCheck --check-prefixes=CHECK,CHECK-OOB-STRICT %s
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %s \
+; RUN: | FileCheck --check-prefixes=CHECK,CHECK-OOB-STRICT %s
+; RUN: cp %s %t.relaxed.ll
+; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.oob.mode", i32 1}\n' >> %t.relaxed.ll
+; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %t.relaxed.ll \
+; RUN: | FileCheck --check-prefixes=CHECK,CHECK-OOB-RELAXED %s
define amdgpu_kernel void @merge_v2i32_v2i32(ptr addrspace(1) nocapture %a, ptr addrspace(1) nocapture readonly %b) #0 {
; CHECK-LABEL: define amdgpu_kernel void @merge_v2i32_v2i32(
diff --git a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
index d590a4a403fb7..08f7375beef09 100644
--- a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
+++ b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
@@ -1,8 +1,12 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -S -o - %s | FileCheck --check-prefix=OOB-STRICT %s
-; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -mattr=+relaxed-buffer-oob-mode -S -o - %s | FileCheck --check-prefixes=OOB-RELAXED %s
+; RUN: cp %s %t.relaxed.ll
+; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.oob.mode", i32 1}\n' >> %t.relaxed.ll
+; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -S -o - %t.relaxed.ll \
+; RUN: | FileCheck --check-prefixes=OOB-RELAXED %s
-; The test checks that relaxed-buffer-oob-mode allows merging loads even if the target load is not naturally aligned.
+; The test checks that the "amdgpu.oob.mode" module flag (UntypedBuffer bit)
+; allows merging loads even if the target load is not naturally aligned.
define amdgpu_kernel void @merge_align_4(ptr addrspace(7) captures(none) %p) #0 {
;
@@ -20,7 +24,7 @@ define amdgpu_kernel void @merge_align_4(ptr addrspace(7) captures(none) %p) #0
; OOB-STRICT-NEXT: ret void
;
; OOB-RELAXED-LABEL: define amdgpu_kernel void @merge_align_4(
-; OOB-RELAXED-SAME: ptr addrspace(7) captures(none) [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+; OOB-RELAXED-SAME: ptr addrspace(7) captures(none) [[P:%.*]]) {
; OOB-RELAXED-NEXT: [[ENTRY:.*:]]
; OOB-RELAXED-NEXT: [[GEP_M8:%.*]] = getelementptr i8, ptr addrspace(7) [[P]], i32 -8
; OOB-RELAXED-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr addrspace(7) [[GEP_M8]], align 4
@@ -42,7 +46,8 @@ entry:
ret void
}
-; The test checks that strict OOB mode (relaxed-buffer-oob-mode not set) allows merging loads if the target load is naturally aligned.
+; The test checks that strict OOB mode ("amdgpu.oob.mode" absent or 0) allows
+; merging loads when the target load is naturally aligned.
define amdgpu_kernel void @merge_align_16(ptr addrspace(7) captures(none) %p) #0 {
; OOB-STRICT-LABEL: define amdgpu_kernel void @merge_align_16(
@@ -57,7 +62,7 @@ define amdgpu_kernel void @merge_align_16(ptr addrspace(7) captures(none) %p) #0
; OOB-STRICT-NEXT: ret void
;
; OOB-RELAXED-LABEL: define amdgpu_kernel void @merge_align_16(
-; OOB-RELAXED-SAME: ptr addrspace(7) captures(none) [[P:%.*]]) #[[ATTR0]] {
+; OOB-RELAXED-SAME: ptr addrspace(7) captures(none) [[P:%.*]]) {
; OOB-RELAXED-NEXT: [[ENTRY:.*:]]
; OOB-RELAXED-NEXT: [[GEP_M8:%.*]] = getelementptr i8, ptr addrspace(7) [[P]], i32 -8
; OOB-RELAXED-NEXT: [[TMP0:%.*]] = load <4 x i32>, ptr addrspace(7) [[GEP_M8]], align 16
diff --git a/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll b/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
new file mode 100644
index 0000000000000..86bff5c5b3220
--- /dev/null
+++ b/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
@@ -0,0 +1,67 @@
+; Tests for IR verifier enforcement of the "amdgpu.oob.mode" module flag.
+; The flag must use Module::Min (i32 8) merge behaviour, carry a constant
+; integer value, and have no bits set outside the currently defined mask (0x3).
+
+; RUN: split-file %s %t
+
+; --- Negative: wrong merge behaviour (Override=4 instead of Min=8) ---
+; RUN: not llvm-as %t/wrong-behavior.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=WRONG-BEHAVIOR
+
+; --- Negative: non-integer value ---
+; RUN: not llvm-as %t/non-integer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=NON-INTEGER
+
+; --- Negative: unknown bits set ---
+; RUN: not llvm-as %t/unknown-bits.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=UNKNOWN-BITS
+
+; --- Positive: absent flag (no error expected) ---
+; RUN: llvm-as %t/absent.ll --disable-output 2>&1 | count 0
+
+; --- Positive: valid relaxed value 0x1 ---
+; RUN: llvm-as %t/valid-0x1.ll --disable-output 2>&1 | count 0
+
+; --- Positive: valid relaxed value 0x3 ---
+; RUN: llvm-as %t/valid-0x3.ll --disable-output 2>&1 | count 0
+
+; --- Positive: explicit strict value 0x0 ---
+; RUN: llvm-as %t/valid-0x0.ll --disable-output 2>&1 | count 0
+
+; WRONG-BEHAVIOR: 'amdgpu.oob.mode' module flag must use 'min' merge behaviour
+; NON-INTEGER: invalid value for 'min' module flag (expected constant non-negative integer)
+; UNKNOWN-BITS: 'amdgpu.oob.mode' module flag has unknown bits set
+
+;--- wrong-behavior.ll
+; Override (i32 4) is not Min (i32 8).
+!0 = !{i32 4, !"amdgpu.oob.mode", i32 1}
+!llvm.module.flags = !{!0}
+
+;--- non-integer.ll
+; Min behaviour but float value instead of integer.
+!0 = !{i32 8, !"amdgpu.oob.mode", float 1.0}
+!llvm.module.flags = !{!0}
+
+;--- unknown-bits.ll
+; Bit 2 (0x4) is not defined in AMDGPUOOBMode.
+!0 = !{i32 8, !"amdgpu.oob.mode", i32 4}
+!llvm.module.flags = !{!0}
+
+;--- absent.ll
+; No "amdgpu.oob.mode" flag at all -- should be accepted.
+define void @f() { ret void }
+
+;--- valid-0x1.ll
+; UntypedBuffer bit only.
+!0 = !{i32 8, !"amdgpu.oob.mode", i32 1}
+!llvm.module.flags = !{!0}
+
+;--- valid-0x3.ll
+; Both UntypedBuffer and TypedBuffer bits.
+!0 = !{i32 8, !"amdgpu.oob.mode", i32 3}
+!llvm.module.flags = !{!0}
+
+;--- valid-0x0.ll
+; Explicit strict mode.
+!0 = !{i32 8, !"amdgpu.oob.mode", i32 0}
+!llvm.module.flags = !{!0}
>From 6138d0ed6311d9433801e4ee0d9d7b4a9266ccb1 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Mon, 9 Mar 2026 14:09:13 +0100
Subject: [PATCH 3/7] Fix formatter
---
llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index ae12c5c0c7775..3c03fa8f227d4 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -1204,8 +1204,8 @@ GCNTargetMachine::GCNTargetMachine(const Target &T, const Triple &TT,
/// Returns the value of the "amdgpu.oob.mode" module flag, or 0 if absent.
/// See AMDGPUOOBMode for the bit definitions.
static unsigned getOOBModeFromModule(const Module &M) {
- const auto *Flag =
- mdconst::dyn_extract_or_null<ConstantInt>(M.getModuleFlag("amdgpu.oob.mode"));
+ const auto *Flag = mdconst::dyn_extract_or_null<ConstantInt>(
+ M.getModuleFlag("amdgpu.oob.mode"));
return Flag ? static_cast<unsigned>(Flag->getZExtValue()) : 0u;
}
>From 8ee63d1d610b00cbf888dcfc114fe4ee01a90754 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Mon, 9 Mar 2026 14:16:29 +0100
Subject: [PATCH 4/7] Fix doc
---
llvm/docs/AMDGPUUsage.rst | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/docs/AMDGPUUsage.rst b/llvm/docs/AMDGPUUsage.rst
index 6dae7c661a747..1c43ac5a4340a 100644
--- a/llvm/docs/AMDGPUUsage.rst
+++ b/llvm/docs/AMDGPUUsage.rst
@@ -828,7 +828,6 @@ consumed by the AMDGPU backend during code generation.
=================== =========== ===== ================================================
Flag Name Type Merge Description
- Behaviour
=================== =========== ===== ================================================
``amdgpu.oob.mode`` ``i32`` Min Bitmask controlling relaxation of out-of-bounds
(OOB) buffer access semantics. When a bit is
>From 12a1e1a23bf3e149c2a38afc06ba1485823384e6 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Thu, 12 Mar 2026 14:16:53 +0100
Subject: [PATCH 5/7] Split amdgpu.oob.mode into 2 separate module flags
---
llvm/docs/AMDGPUUsage.rst | 73 +++++------
llvm/lib/IR/Verifier.cpp | 15 ++-
.../lib/Target/AMDGPU/AMDGPUTargetMachine.cpp | 31 +++--
llvm/lib/Target/AMDGPU/GCNSubtarget.h | 30 ++---
llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 12 +-
llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll | 4 +-
.../AMDGPU/merge-vectors.ll | 2 +-
.../AMDGPU/unaligned-buffer.ll | 8 +-
.../Verifier/AMDGPU/module-flag-oob-mode.ll | 123 ++++++++++++------
9 files changed, 172 insertions(+), 126 deletions(-)
diff --git a/llvm/docs/AMDGPUUsage.rst b/llvm/docs/AMDGPUUsage.rst
index 1c43ac5a4340a..8843d201ea22c 100644
--- a/llvm/docs/AMDGPUUsage.rst
+++ b/llvm/docs/AMDGPUUsage.rst
@@ -823,48 +823,41 @@ AMDGPU-specific behaviour can be controlled via LLVM module flags (see
reference). These flags are set by frontends and are
consumed by the AMDGPU backend during code generation.
-.. table:: AMDGPU Module Flags
+.. list-table:: AMDGPU Module Flags
:name: amdgpu-module-flags-table
+ :header-rows: 1
+
+ * - Flag Name
+ - Type
+ - Merge
+ - Description
+ * - ``amdgpu.buffer.oob.relaxed``
+ - ``i32``
+ - Min
+ - Controls relaxation of out-of-bounds (OOB) semantics for untyped
+ buffer instructions (``buffer_load`` / ``buffer_store``).
+
+ - ``0`` (or absent): **strict** (default). The backend preserves
+ per-byte OOB guarantees by preventing merging of misaligned buffer
+ accesses that could straddle an OOB boundary (e.g. as required by
+ Vulkan ``robustBufferAccess2``).
+ - ``1``: **relaxed**. The backend may merge such accesses for
+ performance.
+
+ ``Min`` merge means the strictest (smallest) value wins at link time
+ when both modules define the flag.
+ * - ``amdgpu.tbuffer.oob.relaxed``
+ - ``i32``
+ - Min
+ - Same as above, but for typed buffer instructions (``tbuffer_load`` /
+ ``tbuffer_store``).
- =================== =========== ===== ================================================
- Flag Name Type Merge Description
- =================== =========== ===== ================================================
- ``amdgpu.oob.mode`` ``i32`` Min Bitmask controlling relaxation of out-of-bounds
- (OOB) buffer access semantics. When a bit is
- **cleared** (strict mode, the default), the
- backend ensures that misaligned buffer accesses
- that straddle an OOB boundary are not merged,
- preserving correct per-byte robustness guarantees
- (e.g. required by Vulkan ``robustBufferAccess2``).
- When a bit is **set** (relaxed mode), the backend
- may merge such accesses for performance, which
- is safe for workloads that do not
- require strict OOB byte-level isolation.
-
- Bits:
-
- * ``0x1`` — relax OOB handling for **untyped**
- buffer instructions (``buffer_load`` /
- ``buffer_store``).
- * ``0x2`` — relax OOB handling for **typed**
- buffer instructions (``tbuffer_load`` /
- ``tbuffer_store``).
-
- The ``Min`` merge behaviour means that when
- modules are linked, the strictest (smallest)
- value wins: a strict module (value ``0``) linked
- with a relaxed module always produces strict
- semantics.
-
- .. note::
-
- Frontends that require misaligned-access
- merging for performance must set this flag
- (bits ``0x3``). Frontends that require strict
- per-byte OOB guarantees (e.g. Vulkan
- ``robustBufferAccess2``) should leave the
- flag absent or set to ``0``.
- =================== =========== ===== ================================================
+.. note::
+
+ Frontends that require misaligned-access merging for performance should
+ set both flags to ``1``. Frontends that require strict per-byte OOB
+ guarantees (e.g. Vulkan ``robustBufferAccess2``) should leave the flags
+ absent or set to ``0``.
.. _amdgpu-target-id:
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 3b5b462c5bed2..32bdd968e604e 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2070,17 +2070,18 @@ Verifier::visitModuleFlag(const MDNode *Op,
"SemanticInterposition metadata requires constant integer argument");
}
- if (ID->getString() == "amdgpu.oob.mode") {
+ if (ID->getString() == "amdgpu.buffer.oob.relaxed" ||
+ ID->getString() == "amdgpu.tbuffer.oob.relaxed") {
Check(MFB == Module::Min,
- "'amdgpu.oob.mode' module flag must use 'min' merge behaviour");
+ "'" + ID->getString() +
+ "' module flag must use 'min' merge behaviour");
ConstantInt *Value =
mdconst::dyn_extract_or_null<ConstantInt>(Op->getOperand(2));
- Check(Value,
- "'amdgpu.oob.mode' module flag must have a constant integer value");
+ Check(Value, "'" + ID->getString() +
+ "' module flag must have a constant integer value");
if (Value) {
- constexpr uint32_t KnownBits = 0x3; // UntypedBuffer | TypedBuffer
- Check((Value->getZExtValue() & ~KnownBits) == 0,
- "'amdgpu.oob.mode' module flag has unknown bits set");
+ Check(Value->getZExtValue() <= 1,
+ "'" + ID->getString() + "' module flag must be 0 or 1");
}
}
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index 3c03fa8f227d4..d288290855b08 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -1201,12 +1201,19 @@ GCNTargetMachine::GCNTargetMachine(const Target &T, const Triple &TT,
CodeGenOptLevel OL, bool JIT)
: AMDGPUTargetMachine(T, TT, CPU, FS, Options, RM, CM, OL) {}
-/// Returns the value of the "amdgpu.oob.mode" module flag, or 0 if absent.
-/// See AMDGPUOOBMode for the bit definitions.
-static unsigned getOOBModeFromModule(const Module &M) {
- const auto *Flag = mdconst::dyn_extract_or_null<ConstantInt>(
- M.getModuleFlag("amdgpu.oob.mode"));
- return Flag ? static_cast<unsigned>(Flag->getZExtValue()) : 0u;
+enum class OOBFlagValue {
+ Strict = 0,
+ Relaxed = 1,
+};
+
+/// Returns the OOB relaxation mode encoded by a module flag.
+/// An absent flag defaults to Strict.
+static OOBFlagValue getOOBRelaxedFlag(const Module &M, StringRef FlagName) {
+ const auto *Flag =
+ mdconst::dyn_extract_or_null<ConstantInt>(M.getModuleFlag(FlagName));
+ if (!Flag)
+ return OOBFlagValue::Strict;
+ return static_cast<OOBFlagValue>(Flag->getZExtValue());
}
const TargetSubtargetInfo *
@@ -1214,10 +1221,15 @@ GCNTargetMachine::getSubtargetImpl(const Function &F) const {
StringRef GPU = getGPUName(F);
StringRef FS = getFeatureString(F);
- unsigned OOBMode = getOOBModeFromModule(*F.getParent());
+ const Module &M = *F.getParent();
+ OOBFlagValue BufOOB = getOOBRelaxedFlag(M, AMDGPUOOBMode::BufferFlag);
+ OOBFlagValue TBufOOB = getOOBRelaxedFlag(M, AMDGPUOOBMode::TBufferFlag);
+ bool BufRelaxed = BufOOB == OOBFlagValue::Relaxed;
+ bool TBufRelaxed = TBufOOB == OOBFlagValue::Relaxed;
SmallString<128> SubtargetKey(GPU);
SubtargetKey.append(FS);
- SubtargetKey.append((",oob=" + Twine(OOBMode)).str());
+ SubtargetKey.append(BufRelaxed ? ",buf-oob=1" : ",buf-oob=0");
+ SubtargetKey.append(TBufRelaxed ? ",tbuf-oob=1" : ",tbuf-oob=0");
auto &I = SubtargetMap[SubtargetKey];
if (!I) {
@@ -1226,7 +1238,8 @@ GCNTargetMachine::getSubtargetImpl(const Function &F) const {
// function that reside in TargetOptions.
resetTargetOptions(F);
I = std::make_unique<GCNSubtarget>(TargetTriple, GPU, FS, *this);
- I->setOOBMode(OOBMode);
+ I->setBufferOOBRelaxed(BufRelaxed);
+ I->setTBufferOOBRelaxed(TBufRelaxed);
}
I->setScalarizeGlobalBehavior(ScalarizeGlobal);
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index 609c7af8582ab..8f7520935281f 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -30,20 +30,13 @@ namespace llvm {
class GCNTargetMachine;
-/// Bit flags for the "amdgpu.oob.mode" LLVM module flag.
-/// These control per-module relaxation of out-of-bounds (OOB) buffer access
-/// semantics. Using Module::Min merge behaviour, a stricter module always
-/// overrides a more relaxed one at link time.
+/// Module flag names controlling relaxation of out-of-bounds (OOB) buffer
+/// access semantics. Each flag is an i32 with Module::Min merge behaviour:
+/// 0 = strict, 1 = relaxed. In a single module, an absent flag is treated as
+/// strict by default.
namespace AMDGPUOOBMode {
-enum : unsigned {
- /// Relax OOB handling for untyped buffer instructions (buffer_load /
- /// buffer_store). When set, the backend may merge misaligned accesses across
- /// an OOB boundary, which would be incorrect under strict Vulkan robustness.
- UntypedBuffer = 0x1,
- /// Relax OOB handling for typed buffer instructions (tbuffer_load /
- /// tbuffer_store).
- TypedBuffer = 0x2,
-};
+inline constexpr StringLiteral BufferFlag("amdgpu.buffer.oob.relaxed");
+inline constexpr StringLiteral TBufferFlag("amdgpu.tbuffer.oob.relaxed");
} // namespace AMDGPUOOBMode
class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
@@ -86,7 +79,8 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
bool DynamicVGPR = false;
bool DynamicVGPRBlockSize32 = false;
bool ScalarizeGlobal = false;
- unsigned OOBMode = 0;
+ bool BufferOOBRelaxed = false;
+ bool TBufferOOBRelaxed = false;
/// The maximum number of instructions that may be placed within an S_CLAUSE,
/// which is one greater than the maximum argument to S_CLAUSE. A value of 0
@@ -335,10 +329,10 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
bool isTgSplitEnabled() const { return EnableTgSplit; }
- bool hasRelaxedBufferOOBMode() const {
- return OOBMode & AMDGPUOOBMode::UntypedBuffer;
- }
- void setOOBMode(unsigned Val) { OOBMode = Val; }
+ bool hasRelaxedBufferOOBMode() const { return BufferOOBRelaxed; }
+ bool hasRelaxedTBufferOOBMode() const { return TBufferOOBRelaxed; }
+ void setBufferOOBRelaxed(bool V) { BufferOOBRelaxed = V; }
+ void setTBufferOOBRelaxed(bool V) { TBufferOOBRelaxed = V; }
bool isCuModeEnabled() const { return EnableCuMode; }
diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index 1e27afe1589cc..6b54c46bc63a4 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -2206,12 +2206,12 @@ bool SITargetLowering::allowsMisalignedMemoryAccessesImpl(
}
// Ensure robust out-of-bounds guarantees for buffer accesses are met when the
- // "amdgpu.oob.mode" module flag has not enabled relaxed untyped-buffer OOB
- // semantics. Normally hardware will ensure proper out-of-bounds behavior, but
- // in the edge case where an access starts out-of-bounds and then enters
- // in-bounds, the entire access would be treated as out-of-bounds. Prevent
- // misaligned memory accesses by requiring the natural alignment of buffer
- // accesses.
+ // "amdgpu.buffer.oob.relaxed" module flag has not enabled relaxed
+ // untyped-buffer OOB semantics. Normally hardware will ensure proper
+ // out-of-bounds behavior, but in the edge case where an access starts
+ // out-of-bounds and then enters in-bounds, the entire access would be treated
+ // as out-of-bounds. Prevent misaligned memory accesses by requiring the
+ // natural alignment of buffer accesses.
if (AddrSpace == AMDGPUAS::BUFFER_FAT_POINTER ||
AddrSpace == AMDGPUAS::BUFFER_RESOURCE ||
AddrSpace == AMDGPUAS::BUFFER_STRIDED_POINTER) {
diff --git a/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll b/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
index 8954ea4e9fd10..720f978f11788 100644
--- a/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
+++ b/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
@@ -2,7 +2,7 @@
; RUN: llc -global-isel=0 -mtriple=amdgcn -mcpu=gfx1100 < %s | FileCheck -check-prefix=SDAG %s
; RUN: llc -global-isel=1 -new-reg-bank-select -mtriple=amdgcn -mcpu=gfx1100 < %s | FileCheck -check-prefix=GISEL %s
-; Check that in strict OOB mode for buffers ("amdgpu.oob.mode" module flag not set) the underaligned loads and stores get split.
+; Check that in strict OOB mode for buffers ("amdgpu.buffer.oob.relaxed" module flag not set) the underaligned loads and stores get split.
; FIXME: The loads/stores do not get split (extend amdgpu-lower-buffer-fat-pointers?).
define amdgpu_ps void @split_underaligned_load(ptr addrspace(7) inreg %p, ptr addrspace(7) inreg %p2) #0 {
@@ -52,7 +52,7 @@ entry:
ret void
}
-; Check that in strict OOB mode for buffers ("amdgpu.oob.mode" module flag not set) the naturally aligned loads and stores do not get split.
+; Check that in strict OOB mode for buffers ("amdgpu.buffer.oob.relaxed" module flag not set) the naturally aligned loads and stores do not get split.
define amdgpu_ps void @do_not_split_aligned_load(ptr addrspace(7) inreg %p, ptr addrspace(7) inreg %p2) #0 {
; CHECK-LABEL: do_not_split_aligned_load:
diff --git a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
index 6d201b0e96db7..59760bfebd4f1 100644
--- a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
+++ b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
@@ -2,7 +2,7 @@
; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %s \
; RUN: | FileCheck --check-prefixes=CHECK,CHECK-OOB-STRICT %s
; RUN: cp %s %t.relaxed.ll
-; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.oob.mode", i32 1}\n' >> %t.relaxed.ll
+; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}\n' >> %t.relaxed.ll
; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %t.relaxed.ll \
; RUN: | FileCheck --check-prefixes=CHECK,CHECK-OOB-RELAXED %s
diff --git a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
index 08f7375beef09..25773e16049f8 100644
--- a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
+++ b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
@@ -1,11 +1,11 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -S -o - %s | FileCheck --check-prefix=OOB-STRICT %s
; RUN: cp %s %t.relaxed.ll
-; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.oob.mode", i32 1}\n' >> %t.relaxed.ll
+; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}\n' >> %t.relaxed.ll
; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -S -o - %t.relaxed.ll \
; RUN: | FileCheck --check-prefixes=OOB-RELAXED %s
-; The test checks that the "amdgpu.oob.mode" module flag (UntypedBuffer bit)
+; The test checks that the "amdgpu.buffer.oob.relaxed" module flag
; allows merging loads even if the target load is not naturally aligned.
define amdgpu_kernel void @merge_align_4(ptr addrspace(7) captures(none) %p) #0 {
@@ -46,8 +46,8 @@ entry:
ret void
}
-; The test checks that strict OOB mode ("amdgpu.oob.mode" absent or 0) allows
-; merging loads when the target load is naturally aligned.
+; The test checks that strict OOB mode ("amdgpu.buffer.oob.relaxed" absent or 0)
+; allows merging loads when the target load is naturally aligned.
define amdgpu_kernel void @merge_align_16(ptr addrspace(7) captures(none) %p) #0 {
; OOB-STRICT-LABEL: define amdgpu_kernel void @merge_align_16(
diff --git a/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll b/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
index 86bff5c5b3220..863a8ade0a2b0 100644
--- a/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
+++ b/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
@@ -1,67 +1,112 @@
-; Tests for IR verifier enforcement of the "amdgpu.oob.mode" module flag.
-; The flag must use Module::Min (i32 8) merge behaviour, carry a constant
-; integer value, and have no bits set outside the currently defined mask (0x3).
+; Tests for IR verifier enforcement of the "amdgpu.buffer.oob.relaxed" and
+; "amdgpu.tbuffer.oob.relaxed" module flags. Each flag must use Module::Min
+; (i32 8) merge behaviour, carry a constant integer value, and be 0 or 1.
; RUN: split-file %s %t
; --- Negative: wrong merge behaviour (Override=4 instead of Min=8) ---
-; RUN: not llvm-as %t/wrong-behavior.ll --disable-output 2>&1 \
-; RUN: | FileCheck %s --check-prefix=WRONG-BEHAVIOR
+; RUN: not llvm-as %t/wrong-behavior-buffer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=WRONG-BUF
+; RUN: not llvm-as %t/wrong-behavior-tbuffer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=WRONG-TBUF
; --- Negative: non-integer value ---
-; RUN: not llvm-as %t/non-integer.ll --disable-output 2>&1 \
-; RUN: | FileCheck %s --check-prefix=NON-INTEGER
+; RUN: not llvm-as %t/non-integer-buffer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=NON-INT-BUF
+; RUN: not llvm-as %t/non-integer-tbuffer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=NON-INT-TBUF
+
+; --- Negative: value out of range (2 is not 0 or 1) ---
+; RUN: not llvm-as %t/out-of-range-buffer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=RANGE-BUF
+; RUN: not llvm-as %t/out-of-range-tbuffer.ll --disable-output 2>&1 \
+; RUN: | FileCheck %s --check-prefix=RANGE-TBUF
+
+; --- Positive: absent flags (no error expected) ---
+; RUN: llvm-as %t/absent.ll --disable-output 2>&1 | count 0
-; --- Negative: unknown bits set ---
-; RUN: not llvm-as %t/unknown-bits.ll --disable-output 2>&1 \
-; RUN: | FileCheck %s --check-prefix=UNKNOWN-BITS
+; --- Positive: valid relaxed value 1 for buffer ---
+; RUN: llvm-as %t/valid-buffer-1.ll --disable-output 2>&1 | count 0
-; --- Positive: absent flag (no error expected) ---
-; RUN: llvm-as %t/absent.ll --disable-output 2>&1 | count 0
+; --- Positive: valid relaxed value 1 for tbuffer ---
+; RUN: llvm-as %t/valid-tbuffer-1.ll --disable-output 2>&1 | count 0
+
+; --- Positive: both flags set to 1 ---
+; RUN: llvm-as %t/valid-both-1.ll --disable-output 2>&1 | count 0
-; --- Positive: valid relaxed value 0x1 ---
-; RUN: llvm-as %t/valid-0x1.ll --disable-output 2>&1 | count 0
+; --- Positive: explicit strict value 0 ---
+; RUN: llvm-as %t/valid-both-0.ll --disable-output 2>&1 | count 0
-; --- Positive: valid relaxed value 0x3 ---
-; RUN: llvm-as %t/valid-0x3.ll --disable-output 2>&1 | count 0
+; --- Linker BUG: absent + relaxed(1) currently preserves relaxed(1) ---
+; NOTE: This documents current IRMover behavior. For Module::Min, absent should
+; ideally behave as 0 (strict), but early-return paths in IRMover bypass the
+; absent->0 fixup.
+; RUN: llvm-link %t/absent.ll %t/valid-buffer-1.ll -S -o - 2>&1 \
+; RUN: | FileCheck %s --check-prefix=BUG-LINK-ABSENT-PASSTHRU
-; --- Positive: explicit strict value 0x0 ---
-; RUN: llvm-as %t/valid-0x0.ll --disable-output 2>&1 | count 0
+; --- Linker: strict(0) + relaxed(1) -> strict(0) via Min ---
+; RUN: llvm-link %t/valid-both-0.ll %t/valid-buffer-1.ll -S -o - 2>&1 \
+; RUN: | FileCheck %s --check-prefix=LINK-STRICT
-; WRONG-BEHAVIOR: 'amdgpu.oob.mode' module flag must use 'min' merge behaviour
-; NON-INTEGER: invalid value for 'min' module flag (expected constant non-negative integer)
-; UNKNOWN-BITS: 'amdgpu.oob.mode' module flag has unknown bits set
+; WRONG-BUF: 'amdgpu.buffer.oob.relaxed' module flag must use 'min' merge behaviour
+; WRONG-TBUF: 'amdgpu.tbuffer.oob.relaxed' module flag must use 'min' merge behaviour
+; NON-INT-BUF: invalid value for 'min' module flag (expected constant non-negative integer)
+; NON-INT-TBUF: invalid value for 'min' module flag (expected constant non-negative integer)
+; RANGE-BUF: 'amdgpu.buffer.oob.relaxed' module flag must be 0 or 1
+; RANGE-TBUF: 'amdgpu.tbuffer.oob.relaxed' module flag must be 0 or 1
-;--- wrong-behavior.ll
+; BUG-LINK-ABSENT-PASSTHRU: !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}
+; LINK-STRICT: !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 0}
+
+;--- wrong-behavior-buffer.ll
; Override (i32 4) is not Min (i32 8).
-!0 = !{i32 4, !"amdgpu.oob.mode", i32 1}
+!0 = !{i32 4, !"amdgpu.buffer.oob.relaxed", i32 1}
+!llvm.module.flags = !{!0}
+
+;--- wrong-behavior-tbuffer.ll
+!0 = !{i32 4, !"amdgpu.tbuffer.oob.relaxed", i32 1}
!llvm.module.flags = !{!0}
-;--- non-integer.ll
+;--- non-integer-buffer.ll
; Min behaviour but float value instead of integer.
-!0 = !{i32 8, !"amdgpu.oob.mode", float 1.0}
+!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", float 1.0}
!llvm.module.flags = !{!0}
-;--- unknown-bits.ll
-; Bit 2 (0x4) is not defined in AMDGPUOOBMode.
-!0 = !{i32 8, !"amdgpu.oob.mode", i32 4}
+;--- non-integer-tbuffer.ll
+!0 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", float 1.0}
+!llvm.module.flags = !{!0}
+
+;--- out-of-range-buffer.ll
+; Value 2 is not a valid boolean (must be 0 or 1).
+!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 2}
+!llvm.module.flags = !{!0}
+
+;--- out-of-range-tbuffer.ll
+!0 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 2}
!llvm.module.flags = !{!0}
;--- absent.ll
-; No "amdgpu.oob.mode" flag at all -- should be accepted.
+; No OOB flags at all - should be accepted.
define void @f() { ret void }
-;--- valid-0x1.ll
-; UntypedBuffer bit only.
-!0 = !{i32 8, !"amdgpu.oob.mode", i32 1}
+;--- valid-buffer-1.ll
+; Relaxed untyped buffer OOB.
+!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}
!llvm.module.flags = !{!0}
-;--- valid-0x3.ll
-; Both UntypedBuffer and TypedBuffer bits.
-!0 = !{i32 8, !"amdgpu.oob.mode", i32 3}
+;--- valid-tbuffer-1.ll
+; Relaxed typed buffer OOB.
+!0 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 1}
!llvm.module.flags = !{!0}
-;--- valid-0x0.ll
-; Explicit strict mode.
-!0 = !{i32 8, !"amdgpu.oob.mode", i32 0}
-!llvm.module.flags = !{!0}
+;--- valid-both-1.ll
+; Both flags relaxed.
+!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}
+!1 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 1}
+!llvm.module.flags = !{!0, !1}
+
+;--- valid-both-0.ll
+; Both flags explicitly strict.
+!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 0}
+!1 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 0}
+!llvm.module.flags = !{!0, !1}
>From 7a53579c978a93b115ab01df950fc4bb75d85735 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Wed, 25 Mar 2026 13:42:52 +0100
Subject: [PATCH 6/7] Only extend subtarget key for non-default OOB flag values
---
llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index d288290855b08..b961671adf68e 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -1228,8 +1228,10 @@ GCNTargetMachine::getSubtargetImpl(const Function &F) const {
bool TBufRelaxed = TBufOOB == OOBFlagValue::Relaxed;
SmallString<128> SubtargetKey(GPU);
SubtargetKey.append(FS);
- SubtargetKey.append(BufRelaxed ? ",buf-oob=1" : ",buf-oob=0");
- SubtargetKey.append(TBufRelaxed ? ",tbuf-oob=1" : ",tbuf-oob=0");
+ if (BufRelaxed)
+ SubtargetKey.append(",buf-oob=1");
+ if (TBufRelaxed)
+ SubtargetKey.append(",tbuf-oob=1");
auto &I = SubtargetMap[SubtargetKey];
if (!I) {
>From 406fbf5c7dac443ca0e06173376c94fe569edc28 Mon Sep 17 00:00:00 2001
From: Piotr Sobczak <piotr.sobczak at amd.com>
Date: Mon, 13 Apr 2026 14:42:50 +0200
Subject: [PATCH 7/7] Switch OOB module flags to tri-state
---
llvm/docs/AMDGPUUsage.rst | 40 +++++----
llvm/lib/IR/Verifier.cpp | 12 +--
.../lib/Target/AMDGPU/AMDGPUTargetMachine.cpp | 15 ++--
llvm/lib/Target/AMDGPU/GCNSubtarget.h | 13 +--
llvm/lib/Target/AMDGPU/SIISelLowering.cpp | 6 +-
llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll | 4 +-
.../AMDGPU/merge-vectors.ll | 2 +-
.../AMDGPU/unaligned-buffer.ll | 6 +-
.../Verifier/AMDGPU/module-flag-oob-mode.ll | 88 ++++++++-----------
9 files changed, 88 insertions(+), 98 deletions(-)
diff --git a/llvm/docs/AMDGPUUsage.rst b/llvm/docs/AMDGPUUsage.rst
index 8843d201ea22c..19b2c04e94f8a 100644
--- a/llvm/docs/AMDGPUUsage.rst
+++ b/llvm/docs/AMDGPUUsage.rst
@@ -831,33 +831,35 @@ consumed by the AMDGPU backend during code generation.
- Type
- Merge
- Description
- * - ``amdgpu.buffer.oob.relaxed``
+ * - ``amdgpu.buffer.oob.mode``
- ``i32``
- - Min
- - Controls relaxation of out-of-bounds (OOB) semantics for untyped
- buffer instructions (``buffer_load`` / ``buffer_store``).
-
- - ``0`` (or absent): **strict** (default). The backend preserves
- per-byte OOB guarantees by preventing merging of misaligned buffer
- accesses that could straddle an OOB boundary (e.g. as required by
- Vulkan ``robustBufferAccess2``).
- - ``1``: **relaxed**. The backend may merge such accesses for
- performance.
-
- ``Min`` merge means the strictest (smallest) value wins at link time
- when both modules define the flag.
- * - ``amdgpu.tbuffer.oob.relaxed``
+ - Max
+ - Controls out-of-bounds semantics for untyped buffer
+ instructions (``buffer_load`` / ``buffer_store``).
+
+ - ``0`` (or absent): **any**. The module does not care about OOB
+ semantics. The backend may treat this the same as
+ **strict**, but this behavior is not guaranteed.
+ - ``1``: **relaxed**. The backend may merge misaligned buffer
+ accesses for performance, even if that changes OOB behaviour.
+ - ``2``: **strict**. The backend preserves per-byte OOB guarantees
+ by preventing merging of misaligned buffer accesses that could
+ straddle an OOB boundary (e.g. as required by Vulkan
+ ``robustBufferAccess2``).
+
+ * - ``amdgpu.tbuffer.oob.mode``
- ``i32``
- - Min
+ - Max
- Same as above, but for typed buffer instructions (``tbuffer_load`` /
``tbuffer_store``).
.. note::
Frontends that require misaligned-access merging for performance should
- set both flags to ``1``. Frontends that require strict per-byte OOB
- guarantees (e.g. Vulkan ``robustBufferAccess2``) should leave the flags
- absent or set to ``0``.
+ set both flags to ``1`` (relaxed). Frontends that require strict
+ per-byte OOB guarantees should set the flags to ``2`` (strict) as needed.
+ Modules that do not use buffer operations or are indifferent to OOB semantics
+ (e.g. device libraries) should leave the flags absent.
.. _amdgpu-target-id:
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 32bdd968e604e..dad454079e560 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2070,18 +2070,18 @@ Verifier::visitModuleFlag(const MDNode *Op,
"SemanticInterposition metadata requires constant integer argument");
}
- if (ID->getString() == "amdgpu.buffer.oob.relaxed" ||
- ID->getString() == "amdgpu.tbuffer.oob.relaxed") {
- Check(MFB == Module::Min,
+ if (ID->getString() == "amdgpu.buffer.oob.mode" ||
+ ID->getString() == "amdgpu.tbuffer.oob.mode") {
+ Check(MFB == Module::Max,
"'" + ID->getString() +
- "' module flag must use 'min' merge behaviour");
+ "' module flag must use 'max' merge behaviour");
ConstantInt *Value =
mdconst::dyn_extract_or_null<ConstantInt>(Op->getOperand(2));
Check(Value, "'" + ID->getString() +
"' module flag must have a constant integer value");
if (Value) {
- Check(Value->getZExtValue() <= 1,
- "'" + ID->getString() + "' module flag must be 0 or 1");
+ Check(Value->getZExtValue() <= 2,
+ "'" + ID->getString() + "' module flag must be 0, 1, or 2");
}
}
diff --git a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
index b961671adf68e..bbce1d2cf06b6 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
+++ b/llvm/lib/Target/AMDGPU/AMDGPUTargetMachine.cpp
@@ -1202,17 +1202,18 @@ GCNTargetMachine::GCNTargetMachine(const Target &T, const Triple &TT,
: AMDGPUTargetMachine(T, TT, CPU, FS, Options, RM, CM, OL) {}
enum class OOBFlagValue {
- Strict = 0,
+ Any = 0,
Relaxed = 1,
+ Strict = 2,
};
-/// Returns the OOB relaxation mode encoded by a module flag.
-/// An absent flag defaults to Strict.
-static OOBFlagValue getOOBRelaxedFlag(const Module &M, StringRef FlagName) {
+/// Returns the OOB mode encoded by a module flag.
+/// An absent flag defaults to Any.
+static OOBFlagValue getOOBFlagValue(const Module &M, StringRef FlagName) {
const auto *Flag =
mdconst::dyn_extract_or_null<ConstantInt>(M.getModuleFlag(FlagName));
if (!Flag)
- return OOBFlagValue::Strict;
+ return OOBFlagValue::Any;
return static_cast<OOBFlagValue>(Flag->getZExtValue());
}
@@ -1222,8 +1223,8 @@ GCNTargetMachine::getSubtargetImpl(const Function &F) const {
StringRef FS = getFeatureString(F);
const Module &M = *F.getParent();
- OOBFlagValue BufOOB = getOOBRelaxedFlag(M, AMDGPUOOBMode::BufferFlag);
- OOBFlagValue TBufOOB = getOOBRelaxedFlag(M, AMDGPUOOBMode::TBufferFlag);
+ OOBFlagValue BufOOB = getOOBFlagValue(M, AMDGPUOOBMode::BufferFlag);
+ OOBFlagValue TBufOOB = getOOBFlagValue(M, AMDGPUOOBMode::TBufferFlag);
bool BufRelaxed = BufOOB == OOBFlagValue::Relaxed;
bool TBufRelaxed = TBufOOB == OOBFlagValue::Relaxed;
SmallString<128> SubtargetKey(GPU);
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index 8f7520935281f..d6198c316e160 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -30,13 +30,14 @@ namespace llvm {
class GCNTargetMachine;
-/// Module flag names controlling relaxation of out-of-bounds (OOB) buffer
-/// access semantics. Each flag is an i32 with Module::Min merge behaviour:
-/// 0 = strict, 1 = relaxed. In a single module, an absent flag is treated as
-/// strict by default.
+/// Module flag names controlling out-of-bounds buffer access semantics.
+/// Each flag is an i32 with Module::Max merge behaviour and tri-state values:
+/// 0 = any (absent/default - backend currently treats as strict)
+/// 1 = relaxed
+/// 2 = strict
namespace AMDGPUOOBMode {
-inline constexpr StringLiteral BufferFlag("amdgpu.buffer.oob.relaxed");
-inline constexpr StringLiteral TBufferFlag("amdgpu.tbuffer.oob.relaxed");
+inline constexpr StringLiteral BufferFlag("amdgpu.buffer.oob.mode");
+inline constexpr StringLiteral TBufferFlag("amdgpu.tbuffer.oob.mode");
} // namespace AMDGPUOOBMode
class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
diff --git a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
index 6b54c46bc63a4..2a90a8daa685f 100644
--- a/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
+++ b/llvm/lib/Target/AMDGPU/SIISelLowering.cpp
@@ -2206,11 +2206,11 @@ bool SITargetLowering::allowsMisalignedMemoryAccessesImpl(
}
// Ensure robust out-of-bounds guarantees for buffer accesses are met when the
- // "amdgpu.buffer.oob.relaxed" module flag has not enabled relaxed
- // untyped-buffer OOB semantics. Normally hardware will ensure proper
+ // "amdgpu.buffer.oob.mode" module flag has not enabled relaxed untyped-buffer
+ // OOB semantics. Normally hardware will ensure proper
// out-of-bounds behavior, but in the edge case where an access starts
// out-of-bounds and then enters in-bounds, the entire access would be treated
- // as out-of-bounds. Prevent misaligned memory accesses by requiring the
+ // as out-of-bounds. Prevent misaligned memory accesses by requiring the
// natural alignment of buffer accesses.
if (AddrSpace == AMDGPUAS::BUFFER_FAT_POINTER ||
AddrSpace == AMDGPUAS::BUFFER_RESOURCE ||
diff --git a/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll b/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
index 720f978f11788..36415c80660d6 100644
--- a/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
+++ b/llvm/test/CodeGen/AMDGPU/unaligned-buffer.ll
@@ -2,7 +2,7 @@
; RUN: llc -global-isel=0 -mtriple=amdgcn -mcpu=gfx1100 < %s | FileCheck -check-prefix=SDAG %s
; RUN: llc -global-isel=1 -new-reg-bank-select -mtriple=amdgcn -mcpu=gfx1100 < %s | FileCheck -check-prefix=GISEL %s
-; Check that in strict OOB mode for buffers ("amdgpu.buffer.oob.relaxed" module flag not set) the underaligned loads and stores get split.
+; Check that in strict OOB mode for buffers ("amdgpu.buffer.oob.mode" module flag not set) the underaligned loads and stores get split.
; FIXME: The loads/stores do not get split (extend amdgpu-lower-buffer-fat-pointers?).
define amdgpu_ps void @split_underaligned_load(ptr addrspace(7) inreg %p, ptr addrspace(7) inreg %p2) #0 {
@@ -52,7 +52,7 @@ entry:
ret void
}
-; Check that in strict OOB mode for buffers ("amdgpu.buffer.oob.relaxed" module flag not set) the naturally aligned loads and stores do not get split.
+; Check that in strict OOB mode for buffers ("amdgpu.buffer.oob.mode" module flag not set) the naturally aligned loads and stores do not get split.
define amdgpu_ps void @do_not_split_aligned_load(ptr addrspace(7) inreg %p, ptr addrspace(7) inreg %p2) #0 {
; CHECK-LABEL: do_not_split_aligned_load:
diff --git a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
index 59760bfebd4f1..f2099cee2ed69 100644
--- a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
+++ b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/merge-vectors.ll
@@ -2,7 +2,7 @@
; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %s \
; RUN: | FileCheck --check-prefixes=CHECK,CHECK-OOB-STRICT %s
; RUN: cp %s %t.relaxed.ll
-; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}\n' >> %t.relaxed.ll
+; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 7, !"amdgpu.buffer.oob.mode", i32 1}\n' >> %t.relaxed.ll
; RUN: opt -mtriple=amdgcn-amd-amdhsa -passes=load-store-vectorizer -S -o - %t.relaxed.ll \
; RUN: | FileCheck --check-prefixes=CHECK,CHECK-OOB-RELAXED %s
diff --git a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
index 25773e16049f8..74a247b702caf 100644
--- a/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
+++ b/llvm/test/Transforms/LoadStoreVectorizer/AMDGPU/unaligned-buffer.ll
@@ -1,11 +1,11 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -S -o - %s | FileCheck --check-prefix=OOB-STRICT %s
; RUN: cp %s %t.relaxed.ll
-; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}\n' >> %t.relaxed.ll
+; RUN: printf '\n!llvm.module.flags = !{!0}\n!0 = !{i32 7, !"amdgpu.buffer.oob.mode", i32 1}\n' >> %t.relaxed.ll
; RUN: opt -mtriple=amdgcn--amdpal -passes=load-store-vectorizer -S -o - %t.relaxed.ll \
; RUN: | FileCheck --check-prefixes=OOB-RELAXED %s
-; The test checks that the "amdgpu.buffer.oob.relaxed" module flag
+; The test checks that the "amdgpu.buffer.oob.mode" module flag
; allows merging loads even if the target load is not naturally aligned.
define amdgpu_kernel void @merge_align_4(ptr addrspace(7) captures(none) %p) #0 {
@@ -46,7 +46,7 @@ entry:
ret void
}
-; The test checks that strict OOB mode ("amdgpu.buffer.oob.relaxed" absent or 0)
+; The test checks that strict OOB mode ("amdgpu.buffer.oob.mode" absent or 0)
; allows merging loads when the target load is naturally aligned.
define amdgpu_kernel void @merge_align_16(ptr addrspace(7) captures(none) %p) #0 {
diff --git a/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll b/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
index 863a8ade0a2b0..f471d8e2f9bfe 100644
--- a/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
+++ b/llvm/test/Verifier/AMDGPU/module-flag-oob-mode.ll
@@ -1,10 +1,10 @@
-; Tests for IR verifier enforcement of the "amdgpu.buffer.oob.relaxed" and
-; "amdgpu.tbuffer.oob.relaxed" module flags. Each flag must use Module::Min
-; (i32 8) merge behaviour, carry a constant integer value, and be 0 or 1.
+; Tests for IR verifier enforcement of the "amdgpu.buffer.oob.mode" and
+; "amdgpu.tbuffer.oob.mode" module flags. Each flag must use Module::Max
+; (i32 7) merge behaviour, carry a constant integer value, and be 0, 1, or 2.
; RUN: split-file %s %t
-; --- Negative: wrong merge behaviour (Override=4 instead of Min=8) ---
+; --- Negative: wrong merge behaviour (Override=4 instead of Max=7) ---
; RUN: not llvm-as %t/wrong-behavior-buffer.ll --disable-output 2>&1 \
; RUN: | FileCheck %s --check-prefix=WRONG-BUF
; RUN: not llvm-as %t/wrong-behavior-tbuffer.ll --disable-output 2>&1 \
@@ -16,7 +16,7 @@
; RUN: not llvm-as %t/non-integer-tbuffer.ll --disable-output 2>&1 \
; RUN: | FileCheck %s --check-prefix=NON-INT-TBUF
-; --- Negative: value out of range (2 is not 0 or 1) ---
+; --- Negative: value out of range (3 is not 0, 1, or 2) ---
; RUN: not llvm-as %t/out-of-range-buffer.ll --disable-output 2>&1 \
; RUN: | FileCheck %s --check-prefix=RANGE-BUF
; RUN: not llvm-as %t/out-of-range-tbuffer.ll --disable-output 2>&1 \
@@ -25,88 +25,74 @@
; --- Positive: absent flags (no error expected) ---
; RUN: llvm-as %t/absent.ll --disable-output 2>&1 | count 0
+; --- Positive: valid any value 0 ---
+; RUN: llvm-as %t/valid-both-0.ll --disable-output 2>&1 | count 0
+
; --- Positive: valid relaxed value 1 for buffer ---
; RUN: llvm-as %t/valid-buffer-1.ll --disable-output 2>&1 | count 0
; --- Positive: valid relaxed value 1 for tbuffer ---
; RUN: llvm-as %t/valid-tbuffer-1.ll --disable-output 2>&1 | count 0
-; --- Positive: both flags set to 1 ---
-; RUN: llvm-as %t/valid-both-1.ll --disable-output 2>&1 | count 0
-
-; --- Positive: explicit strict value 0 ---
-; RUN: llvm-as %t/valid-both-0.ll --disable-output 2>&1 | count 0
-
-; --- Linker BUG: absent + relaxed(1) currently preserves relaxed(1) ---
-; NOTE: This documents current IRMover behavior. For Module::Min, absent should
-; ideally behave as 0 (strict), but early-return paths in IRMover bypass the
-; absent->0 fixup.
-; RUN: llvm-link %t/absent.ll %t/valid-buffer-1.ll -S -o - 2>&1 \
-; RUN: | FileCheck %s --check-prefix=BUG-LINK-ABSENT-PASSTHRU
-
-; --- Linker: strict(0) + relaxed(1) -> strict(0) via Min ---
-; RUN: llvm-link %t/valid-both-0.ll %t/valid-buffer-1.ll -S -o - 2>&1 \
-; RUN: | FileCheck %s --check-prefix=LINK-STRICT
-
-; WRONG-BUF: 'amdgpu.buffer.oob.relaxed' module flag must use 'min' merge behaviour
-; WRONG-TBUF: 'amdgpu.tbuffer.oob.relaxed' module flag must use 'min' merge behaviour
-; NON-INT-BUF: invalid value for 'min' module flag (expected constant non-negative integer)
-; NON-INT-TBUF: invalid value for 'min' module flag (expected constant non-negative integer)
-; RANGE-BUF: 'amdgpu.buffer.oob.relaxed' module flag must be 0 or 1
-; RANGE-TBUF: 'amdgpu.tbuffer.oob.relaxed' module flag must be 0 or 1
+; --- Positive: valid strict value 2 ---
+; RUN: llvm-as %t/valid-both-2.ll --disable-output 2>&1 | count 0
-; BUG-LINK-ABSENT-PASSTHRU: !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}
-; LINK-STRICT: !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 0}
+; WRONG-BUF: 'amdgpu.buffer.oob.mode' module flag must use 'max' merge behaviour
+; WRONG-TBUF: 'amdgpu.tbuffer.oob.mode' module flag must use 'max' merge behaviour
+; NON-INT-BUF: invalid value for 'max' module flag (expected constant integer)
+; NON-INT-TBUF: invalid value for 'max' module flag (expected constant integer)
+; RANGE-BUF: 'amdgpu.buffer.oob.mode' module flag must be 0, 1, or 2
+; RANGE-TBUF: 'amdgpu.tbuffer.oob.mode' module flag must be 0, 1, or 2
;--- wrong-behavior-buffer.ll
-; Override (i32 4) is not Min (i32 8).
-!0 = !{i32 4, !"amdgpu.buffer.oob.relaxed", i32 1}
+; Override (i32 4) is not Max (i32 7).
+!0 = !{i32 4, !"amdgpu.buffer.oob.mode", i32 1}
!llvm.module.flags = !{!0}
;--- wrong-behavior-tbuffer.ll
-!0 = !{i32 4, !"amdgpu.tbuffer.oob.relaxed", i32 1}
+!0 = !{i32 4, !"amdgpu.tbuffer.oob.mode", i32 1}
!llvm.module.flags = !{!0}
;--- non-integer-buffer.ll
-; Min behaviour but float value instead of integer.
-!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", float 1.0}
+; Max behaviour but float value instead of integer.
+!0 = !{i32 7, !"amdgpu.buffer.oob.mode", float 1.0}
!llvm.module.flags = !{!0}
;--- non-integer-tbuffer.ll
-!0 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", float 1.0}
+!0 = !{i32 7, !"amdgpu.tbuffer.oob.mode", float 1.0}
!llvm.module.flags = !{!0}
;--- out-of-range-buffer.ll
-; Value 2 is not a valid boolean (must be 0 or 1).
-!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 2}
+; Value 3 is out of range (must be 0, 1, or 2).
+!0 = !{i32 7, !"amdgpu.buffer.oob.mode", i32 3}
!llvm.module.flags = !{!0}
;--- out-of-range-tbuffer.ll
-!0 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 2}
+!0 = !{i32 7, !"amdgpu.tbuffer.oob.mode", i32 3}
!llvm.module.flags = !{!0}
;--- absent.ll
; No OOB flags at all - should be accepted.
define void @f() { ret void }
+;--- valid-both-0.ll
+; Both flags explicitly any.
+!0 = !{i32 7, !"amdgpu.buffer.oob.mode", i32 0}
+!1 = !{i32 7, !"amdgpu.tbuffer.oob.mode", i32 0}
+!llvm.module.flags = !{!0, !1}
+
;--- valid-buffer-1.ll
; Relaxed untyped buffer OOB.
-!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}
+!0 = !{i32 7, !"amdgpu.buffer.oob.mode", i32 1}
!llvm.module.flags = !{!0}
;--- valid-tbuffer-1.ll
; Relaxed typed buffer OOB.
-!0 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 1}
+!0 = !{i32 7, !"amdgpu.tbuffer.oob.mode", i32 1}
!llvm.module.flags = !{!0}
-;--- valid-both-1.ll
-; Both flags relaxed.
-!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 1}
-!1 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 1}
-!llvm.module.flags = !{!0, !1}
-
-;--- valid-both-0.ll
-; Both flags explicitly strict.
-!0 = !{i32 8, !"amdgpu.buffer.oob.relaxed", i32 0}
-!1 = !{i32 8, !"amdgpu.tbuffer.oob.relaxed", i32 0}
+;--- valid-both-2.ll
+; Both flags strict.
+!0 = !{i32 7, !"amdgpu.buffer.oob.mode", i32 2}
+!1 = !{i32 7, !"amdgpu.tbuffer.oob.mode", i32 2}
!llvm.module.flags = !{!0, !1}
More information about the llvm-commits
mailing list