[llvm] [IR] Add an "atomicity" operand bundle (PR #223298)

via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 13 21:06:20 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Arseniy Obolenskiy (aobolensk)

<details>
<summary>Changes</summary>

Record that a call implements an atomic memory access for cases where the atomicity cannot be recovered from the call itself

---

Patch is 20.21 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/223298.diff


15 Files Affected:

- (modified) llvm/docs/LangRef.md (+24) 
- (modified) llvm/include/llvm/IR/InstrTypes.h (+11) 
- (modified) llvm/include/llvm/IR/Instructions.h (+4) 
- (modified) llvm/include/llvm/IR/LLVMContext.h (+2-1) 
- (modified) llvm/include/llvm/Support/AtomicOrdering.h (+14) 
- (modified) llvm/lib/CodeGen/MIRParser/MIParser.cpp (+2-10) 
- (modified) llvm/lib/IR/Instruction.cpp (+16) 
- (modified) llvm/lib/IR/Instructions.cpp (+22-2) 
- (modified) llvm/lib/IR/LLVMContext.cpp (+2) 
- (modified) llvm/lib/IR/Verifier.cpp (+27-1) 
- (modified) llvm/lib/Transforms/IPO/Attributor.cpp (+7-6) 
- (modified) llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp (+7-6) 
- (modified) llvm/test/Bitcode/operand-bundles-bc-analyzer.ll (+1) 
- (added) llvm/test/Verifier/atomicity-bundle.ll (+39) 
- (modified) llvm/unittests/IR/InstructionsTest.cpp (+28) 


``````````diff
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 14caff88243c1..71d0ef9bbc006 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -3456,6 +3456,30 @@ value of its first argument instead of calling the specified function
 or intrinsic. This is achieved with `PATCHINST` relocations on the
 target instructions (see the AArch64 psABI for details).
 
+(atomicity)=
+
+#### Atomicity Operand Bundles
+
+An `"atomicity"` operand bundle records that a call implements an atomic
+memory access, for cases where the atomicity cannot be recovered from the
+call itself. At most one such bundle may be present, and it must have exactly
+two operands: a metadata string naming an atomic ordering and a metadata
+string naming a synchronization scope, where the empty string denotes system
+scope.
+
+```llvm
+call void @store_release(ptr %p, i32 %val)
+    [ "atomicity"(metadata !"release", metadata !"agent") ]
+```
+
+A call carrying the bundle reports `true` from `Instruction::isAtomic`, so
+generic transforms treat it like any other atomic operation. The bundle
+carries no memory effects of its own, so it does not make an
+otherwise-analyzable call opaque.
+
+A target lowers the bundle onto the selected instruction. Targets are
+expected to document the orderings and scope names they accept.
+
 (moduleasm)=
 
 ### Module-Level Inline Assembly
diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h
index 5f7df6a4eb6f8..f2d5c05b26371 100644
--- a/llvm/include/llvm/IR/InstrTypes.h
+++ b/llvm/include/llvm/IR/InstrTypes.h
@@ -1165,6 +1165,13 @@ using ConstOperandBundleDef = OperandBundleDefT<const Value *>;
 //===----------------------------------------------------------------------===//
 
 /// Base class for all callable instructions (InvokeInst and CallInst)
+/// The atomicity of a memory access implemented by a call, as recorded by an
+/// "atomicity" operand bundle.
+struct AtomicityBundleInfo {
+  AtomicOrdering Order;
+  SyncScope::ID SSID;
+};
+
 /// Holds everything related to calling a function.
 ///
 /// All call-like instructions are required to use a common operand layout:
@@ -2203,6 +2210,10 @@ class CallBase : public Instruction {
   /// may write to the heap.
   LLVM_ABI bool hasClobberingOperandBundles() const;
 
+  /// Return the atomicity recorded by the "atomicity" operand bundle, or
+  /// std::nullopt if there is no such bundle.
+  LLVM_ABI std::optional<AtomicityBundleInfo> getAtomicityBundleInfo() const;
+
   /// Return true if the bundle operand at index \p OpIdx has the
   /// attribute \p A.
   bool bundleOperandHasAttr(unsigned OpIdx,  Attribute::AttrKind A) const {
diff --git a/llvm/include/llvm/IR/Instructions.h b/llvm/include/llvm/IR/Instructions.h
index 00d689283479f..2477f56517b60 100644
--- a/llvm/include/llvm/IR/Instructions.h
+++ b/llvm/include/llvm/IR/Instructions.h
@@ -5334,6 +5334,8 @@ inline std::optional<SyncScope::ID> getAtomicSyncScopeID(const Instruction *I) {
     return AI->getSyncScopeID();
   if (auto *AI = dyn_cast<AtomicRMWInst>(I))
     return AI->getSyncScopeID();
+  if (auto *CB = dyn_cast<CallBase>(I))
+    return CB->getAtomicityBundleInfo()->SSID;
   llvm_unreachable("unhandled atomic operation");
 }
 
@@ -5351,6 +5353,8 @@ inline void setAtomicSyncScopeID(Instruction *I, SyncScope::ID SSID) {
   else if (auto *AI = dyn_cast<AtomicRMWInst>(I))
     AI->setSyncScopeID(SSID);
   else
+    // A call records its scope in an operand bundle, which cannot be mutated
+    // in place.
     llvm_unreachable("unhandled atomic operation");
 }
 
diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h
index ced9b9227538e..393d460c820dd 100644
--- a/llvm/include/llvm/IR/LLVMContext.h
+++ b/llvm/include/llvm/IR/LLVMContext.h
@@ -100,7 +100,8 @@ class LLVMContext {
     OB_kcfi,                   // "kcfi"
     OB_convergencectrl,        // "convergencectrl"
     OB_deactivation_symbol,    // "deactivation-symbol"
-    OB_LastBundleID = OB_deactivation_symbol
+    OB_atomicity,              // "atomicity"
+    OB_LastBundleID = OB_atomicity
   };
 
   /// getMDKindID - Return a unique non-zero ID for the specified metadata kind.
diff --git a/llvm/include/llvm/Support/AtomicOrdering.h b/llvm/include/llvm/Support/AtomicOrdering.h
index ad35cc3d4582f..68dd2f2b46233 100644
--- a/llvm/include/llvm/Support/AtomicOrdering.h
+++ b/llvm/include/llvm/Support/AtomicOrdering.h
@@ -17,7 +17,9 @@
 #ifndef LLVM_SUPPORT_ATOMICORDERING_H
 #define LLVM_SUPPORT_ATOMICORDERING_H
 
+#include "llvm/ADT/StringRef.h"
 #include <cstddef>
+#include <optional>
 
 namespace llvm {
 
@@ -86,6 +88,18 @@ inline const char *toIRString(AtomicOrdering ao) {
   return names[static_cast<size_t>(ao)];
 }
 
+/// Inverse of toIRString. "not_atomic" is rejected: it is the absence of an
+/// ordering, not an IR keyword. Returns std::nullopt if Str names none.
+inline std::optional<AtomicOrdering> parseAtomicOrdering(StringRef Str) {
+  for (size_t I = static_cast<size_t>(AtomicOrdering::Unordered);
+       I <= static_cast<size_t>(AtomicOrdering::SequentiallyConsistent); ++I) {
+    if (isValidAtomicOrdering(I) &&
+        Str == toIRString(static_cast<AtomicOrdering>(I)))
+      return static_cast<AtomicOrdering>(I);
+  }
+  return std::nullopt;
+}
+
 /// Returns true if ao is stronger than other as defined by the AtomicOrdering
 /// lattice, which is based on C++'s definition.
 inline bool isStrongerThan(AtomicOrdering AO, AtomicOrdering Other) {
diff --git a/llvm/lib/CodeGen/MIRParser/MIParser.cpp b/llvm/lib/CodeGen/MIRParser/MIParser.cpp
index 4b951785e9c41..f0546499b1221 100644
--- a/llvm/lib/CodeGen/MIRParser/MIParser.cpp
+++ b/llvm/lib/CodeGen/MIRParser/MIParser.cpp
@@ -3576,16 +3576,8 @@ bool MIParser::parseOptionalAtomicOrdering(AtomicOrdering &Order) {
   if (Token.isNot(MIToken::Identifier))
     return false;
 
-  Order = StringSwitch<AtomicOrdering>(Token.stringValue())
-              .Case("unordered", AtomicOrdering::Unordered)
-              .Case("monotonic", AtomicOrdering::Monotonic)
-              .Case("acquire", AtomicOrdering::Acquire)
-              .Case("release", AtomicOrdering::Release)
-              .Case("acq_rel", AtomicOrdering::AcquireRelease)
-              .Case("seq_cst", AtomicOrdering::SequentiallyConsistent)
-              .Default(AtomicOrdering::NotAtomic);
-
-  if (Order != AtomicOrdering::NotAtomic) {
+  if (auto ParsedOrder = parseAtomicOrdering(Token.stringValue())) {
+    Order = *ParsedOrder;
     lex();
     return false;
   }
diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index 60e4f353585db..48e58330125ae 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -1186,6 +1186,14 @@ bool Instruction::isAtomic() const {
     return cast<LoadInst>(this)->getOrdering() != AtomicOrdering::NotAtomic;
   case Instruction::Store:
     return cast<StoreInst>(this)->getOrdering() != AtomicOrdering::NotAtomic;
+  case Instruction::Call:
+  case Instruction::Invoke:
+  case Instruction::CallBr:
+    // A call implementing an atomic access records it in an "atomicity"
+    // operand bundle.
+    return cast<CallBase>(this)
+        ->getOperandBundle(LLVMContext::OB_atomicity)
+        .has_value();
   }
 }
 
@@ -1198,6 +1206,10 @@ bool Instruction::hasAtomicLoad() const {
   case Instruction::AtomicRMW:
   case Instruction::Load:
     return true;
+  case Instruction::Call:
+  case Instruction::Invoke:
+  case Instruction::CallBr:
+    return mayReadFromMemory();
   }
 }
 
@@ -1210,6 +1222,10 @@ bool Instruction::hasAtomicStore() const {
   case Instruction::AtomicRMW:
   case Instruction::Store:
     return true;
+  case Instruction::Call:
+  case Instruction::Invoke:
+  case Instruction::CallBr:
+    return mayWriteToMemory();
   }
 }
 
diff --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp
index 325850b0a880d..90113028ac21b 100644
--- a/llvm/lib/IR/Instructions.cpp
+++ b/llvm/lib/IR/Instructions.cpp
@@ -639,7 +639,8 @@ bool CallBase::hasReadingOperandBundles() const {
   return hasOperandBundlesOtherThan({LLVMContext::OB_ptrauth,
                                      LLVMContext::OB_kcfi,
                                      LLVMContext::OB_convergencectrl,
-                                     LLVMContext::OB_deactivation_symbol}) &&
+                                     LLVMContext::OB_deactivation_symbol,
+                                     LLVMContext::OB_atomicity}) &&
          getIntrinsicID() != Intrinsic::assume;
 }
 
@@ -648,10 +649,29 @@ bool CallBase::hasClobberingOperandBundles() const {
              {LLVMContext::OB_deopt, LLVMContext::OB_funclet,
               LLVMContext::OB_ptrauth, LLVMContext::OB_kcfi,
               LLVMContext::OB_convergencectrl,
-              LLVMContext::OB_deactivation_symbol}) &&
+              LLVMContext::OB_deactivation_symbol,
+              LLVMContext::OB_atomicity}) &&
          getIntrinsicID() != Intrinsic::assume;
 }
 
+std::optional<AtomicityBundleInfo> CallBase::getAtomicityBundleInfo() const {
+  std::optional<OperandBundleUse> Bundle =
+      getOperandBundle(LLVMContext::OB_atomicity);
+  if (!Bundle)
+    return std::nullopt;
+
+  // The Verifier guarantees the shape of the bundle.
+  assert(Bundle->Inputs.size() == 2 && "malformed \"atomicity\" bundle");
+  auto GetString = [](const Value *V) {
+    return cast<MDString>(cast<MetadataAsValue>(V)->getMetadata())->getString();
+  };
+  std::optional<AtomicOrdering> Order =
+      parseAtomicOrdering(GetString(Bundle->Inputs[0]));
+  assert(Order && "malformed \"atomicity\" bundle ordering");
+  return AtomicityBundleInfo{*Order, getContext().getOrInsertSyncScopeID(
+                                         GetString(Bundle->Inputs[1]))};
+}
+
 MemoryEffects CallBase::getMemoryEffects() const {
   MemoryEffects ME = getAttributes().getMemoryEffects();
   if (auto *Fn = dyn_cast<Function>(getCalledOperand())) {
diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp
index 1fcdd3bc4c142..509ed1379290b 100644
--- a/llvm/lib/IR/LLVMContext.cpp
+++ b/llvm/lib/IR/LLVMContext.cpp
@@ -58,6 +58,8 @@ static StringRef knownBundleName(unsigned BundleTagID) {
     return "convergencectrl";
   case LLVMContext::OB_deactivation_symbol:
     return "deactivation-symbol";
+  case LLVMContext::OB_atomicity:
+    return "atomicity";
   default:
     llvm_unreachable("unknown bundle id");
   }
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 12ab6e9c34a5e..56ab864a12e8c 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -504,6 +504,8 @@ class Verifier : public InstVisitor<Verifier>, VerifierSupport {
   void verifyAttachedCallBundle(const CallBase &Call,
                                 const OperandBundleUse &BU);
 
+  void verifyAtomicityBundle(const CallBase &Call, const OperandBundleUse &BU);
+
   /// Verify the llvm.experimental.noalias.scope.decl declarations
   void verifyNoAliasScopeDecl();
 };
@@ -4173,7 +4175,7 @@ void Verifier::visitCallBase(CallBase &Call) {
        FoundGCTransitionBundle = false, FoundCFGuardTargetBundle = false,
        FoundPreallocatedBundle = false, FoundGCLiveBundle = false,
        FoundPtrauthBundle = false, FoundKCFIBundle = false,
-       FoundAttachedCallBundle = false;
+       FoundAttachedCallBundle = false, FoundAtomicityBundle = false;
   for (unsigned i = 0, e = Call.getNumOperandBundles(); i < e; ++i) {
     OperandBundleUse BU = Call.getOperandBundleAt(i);
     for (const Value *Input : BU.Inputs)
@@ -4239,6 +4241,11 @@ void Verifier::visitCallBase(CallBase &Call) {
             "Multiple \"clang.arc.attachedcall\" operand bundles", Call);
       FoundAttachedCallBundle = true;
       verifyAttachedCallBundle(Call, BU);
+    } else if (Tag == LLVMContext::OB_atomicity) {
+      Check(!FoundAtomicityBundle, "Multiple \"atomicity\" operand bundles",
+            Call);
+      FoundAtomicityBundle = true;
+      verifyAtomicityBundle(Call, BU);
     }
   }
 
@@ -7723,6 +7730,25 @@ void Verifier::verifyAttachedCallBundle(const CallBase &Call,
   }
 }
 
+void Verifier::verifyAtomicityBundle(const CallBase &Call,
+                                     const OperandBundleUse &BU) {
+  Check(BU.Inputs.size() == 2,
+        "Expected exactly two \"atomicity\" bundle operands", Call);
+
+  auto GetMDString = [](const Value *V) -> const MDString * {
+    const auto *MAV = dyn_cast<MetadataAsValue>(V);
+    return MAV ? dyn_cast<MDString>(MAV->getMetadata()) : nullptr;
+  };
+
+  const MDString *Ordering = GetMDString(BU.Inputs[0]);
+  Check(Ordering && parseAtomicOrdering(Ordering->getString()),
+        "\"atomicity\" ordering operand must be a metadata string naming an "
+        "atomic ordering",
+        Call);
+  Check(GetMDString(BU.Inputs[1]),
+        "\"atomicity\" syncscope operand must be a metadata string", Call);
+}
+
 void Verifier::verifyNoAliasScopeDecl() {
   if (NoAliasScopeDecls.empty())
     return;
diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp
index 338240b2f8087..01a35f49c50ce 100644
--- a/llvm/lib/Transforms/IPO/Attributor.cpp
+++ b/llvm/lib/Transforms/IPO/Attributor.cpp
@@ -1336,10 +1336,11 @@ SubsumingPositionIterator::SubsumingPositionIterator(const IRPosition &IRP) {
   IRPositions.emplace_back(IRP);
 
   // Helper to determine if operand bundles on a call site are benign or
-  // potentially problematic. We handle only llvm.assume for now.
+  // potentially problematic. We handle llvm.assume and "atomicity".
   auto CanIgnoreOperandBundles = [](const CallBase &CB) {
-    return (isa<IntrinsicInst>(CB) &&
-            cast<IntrinsicInst>(CB).getIntrinsicID() == Intrinsic ::assume);
+    return !CB.hasOperandBundlesOtherThan(LLVMContext::OB_atomicity) ||
+           (isa<IntrinsicInst>(CB) &&
+            cast<IntrinsicInst>(CB).getIntrinsicID() == Intrinsic::assume);
   };
 
   const auto *CB = dyn_cast<CallBase>(&IRP.getAnchorValue());
@@ -1356,7 +1357,7 @@ SubsumingPositionIterator::SubsumingPositionIterator(const IRPosition &IRP) {
     assert(CB && "Expected call site!");
     // TODO: We need to look at the operand bundles similar to the redirection
     //       in CallBase.
-    if (!CB->hasOperandBundles() || CanIgnoreOperandBundles(*CB))
+    if (CanIgnoreOperandBundles(*CB))
       if (auto *Callee = dyn_cast_if_present<Function>(CB->getCalledOperand()))
         IRPositions.emplace_back(IRPosition::function(*Callee));
     return;
@@ -1364,7 +1365,7 @@ SubsumingPositionIterator::SubsumingPositionIterator(const IRPosition &IRP) {
     assert(CB && "Expected call site!");
     // TODO: We need to look at the operand bundles similar to the redirection
     //       in CallBase.
-    if (!CB->hasOperandBundles() || CanIgnoreOperandBundles(*CB)) {
+    if (CanIgnoreOperandBundles(*CB)) {
       if (auto *Callee =
               dyn_cast_if_present<Function>(CB->getCalledOperand())) {
         IRPositions.emplace_back(IRPosition::returned(*Callee));
@@ -1385,7 +1386,7 @@ SubsumingPositionIterator::SubsumingPositionIterator(const IRPosition &IRP) {
     assert(CB && "Expected call site!");
     // TODO: We need to look at the operand bundles similar to the redirection
     //       in CallBase.
-    if (!CB->hasOperandBundles() || CanIgnoreOperandBundles(*CB)) {
+    if (CanIgnoreOperandBundles(*CB)) {
       auto *Callee = dyn_cast_if_present<Function>(CB->getCalledOperand());
       if (Callee) {
         if (Argument *Arg = IRP.getAssociatedArgument())
diff --git a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
index 880b0bda90d70..f0d518af5d3a2 100644
--- a/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/TailRecursionElimination.cpp
@@ -303,13 +303,14 @@ static bool markTails(Function &F, OptimizationRemarkEmitter *ORE,
         if (II->getIntrinsicID() == Intrinsic::stackrestore)
           continue;
 
-      // Special-case operand bundles "clang.arc.attachedcall", "ptrauth", and
-      // "kcfi".
+      // Special-case operand bundles "clang.arc.attachedcall", "ptrauth",
+      // "kcfi", and "atomicity".
       bool DisableForCold = shouldDisableTailCallsForCold(CI, &F, PSI, BFI);
-      bool IsNoTail = CI->isNoTailCall() || DisableForCold ||
-                      CI->hasOperandBundlesOtherThan(
-                          {LLVMContext::OB_clang_arc_attachedcall,
-                           LLVMContext::OB_ptrauth, LLVMContext::OB_kcfi});
+      bool IsNoTail =
+          CI->isNoTailCall() || DisableForCold ||
+          CI->hasOperandBundlesOtherThan(
+              {LLVMContext::OB_clang_arc_attachedcall, LLVMContext::OB_ptrauth,
+               LLVMContext::OB_kcfi, LLVMContext::OB_atomicity});
       if (!CI->isNoTailCall() && DisableForCold)
         ++NumTREPreventedCold;
 
diff --git a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll
index 2ef0e61dd126b..d54b90ba8bcf3 100644
--- a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll
+++ b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll
@@ -22,6 +22,7 @@
 ; CHECK-NEXT:    <OPERAND_BUNDLE_TAG
 ; CHECK-NEXT:    <OPERAND_BUNDLE_TAG
 ; CHECK-NEXT:    <OPERAND_BUNDLE_TAG
+; CHECK-NEXT:    <OPERAND_BUNDLE_TAG
 ; CHECK-NEXT:  </OPERAND_BUNDLE_TAGS_BLOCK
 
 ; CHECK:   <FUNCTION_BLOCK
diff --git a/llvm/test/Verifier/atomicity-bundle.ll b/llvm/test/Verifier/atomicity-bundle.ll
new file mode 100644
index 0000000000000..1f170f8ace7f6
--- /dev/null
+++ b/llvm/test/Verifier/atomicity-bundle.ll
@@ -0,0 +1,39 @@
+; RUN: not llvm-as < %s -o /dev/null 2>&1 | FileCheck %s
+
+declare void @f()
+
+; The bundle is not restricted to any particular callee.
+define void @ordinary_call() {
+  call void @f() [ "atomicity"(metadata !"release", metadata !"agent") ]
+  ret void
+}
+
+; CHECK: Multiple "atomicity" operand bundles
+define void @multiple() {
+  call void @f() [ "atomicity"(metadata !"release", metadata !"agent"), "atomicity"(metadata !"release", metadata !"agent") ]
+  ret void
+}
+
+; CHECK: Expected exactly two "atomicity" bundle operands
+define void @wrong_operand_count() {
+  call void @f() [ "atomicity"(metadata !"release") ]
+  ret void
+}
+
+; CHECK: "atomicity" ordering operand must be a metadata string naming an atomic ordering
+define void @not_an_ordering() {
+  call void @f() [ "atomicity"(metadata !"consume", metadata !"agent") ]
+  ret void
+}
+
+; CHECK: "atomicity" ordering operand must be a metadata string naming an atomic ordering
+define void @ordering_not_a_string() {
+  call void @f() [ "atomicity"(i32 0, metadata !"agent") ]
+  ret void
+}
+
+; CHECK: "atomicity" syncscope operand must be a metadata string
+define void @scope_not_a_string() {
+  call void @f() [ "atomicity"(metadata !"release", i32 0) ]
+  ret void
+}
diff --git a/llvm/unittests/IR/InstructionsTest.cpp b/llvm/unittests/IR/InstructionsTest.cpp
index 5f92f325f793f..f1faff246083c 100644
--- a/llvm/unittests/IR/InstructionsTest.cpp
+++ b/llvm/unittests/IR/InstructionsTest.cpp
@@ -2054,6 +2054,34 @@ TEST(InstructionsTest, AtomicSyncscope) {
   EXPECT_TRUE(LLVMIsAtomicSingleThread(CmpXchg));
 }
 
+TEST(InstructionsTest, AtomicityBundle) {
+  LLVMContext Ctx;
+
+  Module M("Mod", Ctx);
+  FunctionType *FT = FunctionType::get(Type::getVoidTy(Ctx), {}, false);
+  Function *F = Function::Create(FT, Function::ExternalLinkage, "Fun", M);
+  Function *Callee =
+      Function::Create(FT, Function::ExternalLinkage, "Callee", M);
+  BasicBlock *BB = BasicBlock::Create(Ctx, "Entry", F);
+  IRBuilder<> Builder(BB);
+
+  CallInst *Plain = Builder.CreateCall(Callee);
+  EXPECT_FALSE(Plain->isAtomic());
+  EXPECT_FALSE(Plain->getAtomicityBundleInfo().has_value());
+  EXPECT_EQ(getAtomicSyncScopeID(Plain), std::nullopt);
+
+  Value *Ops[] = {MetadataAsValue::get(Ctx, MDString::get(Ctx, "release")),
+                  MetadataAsValue::get(Ctx, MDString::get(Ctx, "agent"))};
+  CallInst *Atomic =
+      Builder.CreateCall(Callee, {}, {OperandBundleDef("atomicity", Ops)});
+  EXPECT_TRUE(Atomic->isAtomic());
+  EXPECT_EQ(Atomic->getAtomicityBundleInfo()->Order, AtomicOrdering::Release);
+  EXPECT_EQ(getAtomicSyncScopeID(Atomic), Ctx.getOrInsertSyncScopeID("agent"));
+  // The callee is not readnone, so the call both reads and writes memory.
+  EXPECT_TRUE(Atomic->hasAtomicLoad());
+  EXPECT_TRUE(A...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list