[clang] [CIR] Split side_effect into memory effects, nounwind, and willreturn (PR #223890)

Adam Smith via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 16 18:01:34 PDT 2026


https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/223890

>From e5b75aa6c2930b65acc7724a82875dd14deb3c85 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 15 Sep 2026 17:30:59 -0700
Subject: [PATCH 1/2] [CIR] Split side_effect into memory effects, nounwind,
 and willreturn

side_effect was carrying three facts at once.  It described the memory a
callee may touch, and it was also the only thing lowering derived
nounwind and willreturn from, so any callee with a known memory effect
got both.  That is wrong for one that can throw, and willreturn had no
other representation in CIR at all.

const and pure now record the three separately, and the enum is replaced
by a structured #cir.memory_effects carrying the same six ModRef slots as
the LLVM dialect.  On an operation it prints compactly, as memory(none)
or memory(read, argmem: readwrite).  No lowered IR changes.

Assisted-by: Cursor / claude-opus-5
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  |  81 +++++---
 .../clang/CIR/Dialect/IR/CIRDialect.td        |   5 +-
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  18 +-
 .../clang/CIR/Interfaces/CIROpInterfaces.td   |   4 +-
 clang/lib/CIR/CodeGen/CIRGenCall.cpp          |  31 +--
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |   3 +-
 clang/lib/CIR/CodeGen/CIRGenModule.h          |   4 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 188 ++++++++++++++----
 .../Dialect/Transforms/CIRTransformUtils.cpp  |   9 +-
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 125 +++++-------
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.h   |   5 -
 clang/test/CIR/CodeGen/call.c                 |   4 +-
 clang/test/CIR/CodeGen/side-effect.cpp        |   8 +-
 clang/test/CIR/IR/call.cir                    |  32 ++-
 clang/test/CIR/IR/enum-attrs.cir              |  14 +-
 clang/test/CIR/IR/invalid-memory-effects.cir  |  50 +++++
 clang/test/CIR/Lowering/memory-effects.cir    |  39 ++++
 .../CIR/Transforms/flatten-preserve-attrs.cir |  38 +++-
 .../test/CIR/Transforms/idiom-recognizer.cpp  |   2 +-
 19 files changed, 455 insertions(+), 205 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-memory-effects.cir
 create mode 100644 clang/test/CIR/Lowering/memory-effects.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index f263cd30eb458..0e8d7177ee6b0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1953,39 +1953,74 @@ def CIR_BlockAddrDiffAttr
 }
 
 //===----------------------------------------------------------------------===//
-// Side Effect
+// Memory Effects
 //===----------------------------------------------------------------------===//
 
-def CIR_SideEffect : CIR_I32Enum<
-    "SideEffect", "allowed side effects of a function", [
-      I32EnumCase<"All", 0, "all">,
-      I32EnumCase<"Pure", 1, "pure">,
-      I32EnumCase<"Const", 2, "const">
+def CIR_ModRefInfo : CIR_I32Enum<
+    "ModRefInfo", "how a function accesses one class of memory", [
+      I32EnumCase<"NoModRef", 0, "none">,
+      I32EnumCase<"Ref", 1, "read">,
+      I32EnumCase<"Mod", 2, "write">,
+      I32EnumCase<"ModRef", 3, "readwrite">
 ]> {
   let description = [{
-    The side effect attribute specifies the possible side effects of a function
-    or the target of a call operation.  This is an enumeration attribute with
-    the following possible values:
-
-    - all: The function or callee can have any side effects. This is the default
-      if no side effects are explicitly listed.
-    - pure: The function or callee may read data from memory, but it cannot
-      write data to memory. This has the same effect as the GNU C/C++ attribute
-      `__attribute__((pure))`.
-    - const: The function or callee may not read or write data from memory. This
-      has the same effect as the GNU C/C++ attribute `__attribute__((const))`.
+    Whether a function may read and/or write one class of memory.  The
+    spellings match LLVM's, so `none` means neither read nor written and
+    `readwrite` means both may happen.
+  }];
+}
 
-    Examples:
+def CIR_MemoryEffectsAttr : CIR_Attr<"MemoryEffects", "memory_effects"> {
+  let summary = "What memory a function or callee may touch";
+  let description = [{
+    The memory a function or the target of a call may read and write, given
+    per class of memory so that a promise about one class does not have to be
+    a promise about all of them.
+
+    `other` covers every location not named by one of the remaining
+    parameters, so a function that only touches its pointer arguments has
+    `other = none` and `arg_mem = readwrite`.
+
+    An absent attribute means the effects are unknown, which is the
+    conservative default and what a function with no memory annotation gets.
+
+    On an operation the effects print as the access shared by every class not
+    named individually, followed by only the classes that differ from it.
+    The shared access is left out when it is `none` and some class does
+    differ, since an unnamed class reads back as `none`.
 
     ```
-    %2 = cir.call @add(%0, %1) : (!s32i, !s32i) -> !s32i
-    %2 = cir.call @add(%0, %1) : (!s32i, !s32i) -> !s32i side_effect(pure)
-    %2 = cir.call @add(%0, %1) : (!s32i, !s32i) -> !s32i side_effect(const)
+    cir.func private @a() -> !s32i memory(none)
+    cir.func private @b() -> !s32i memory(read)
+    cir.func private @c(!cir.ptr<!s32i>) memory(argmem: readwrite)
+    cir.func private @d(!cir.ptr<!s32i>) memory(read, argmem: readwrite)
+    %2 = cir.call @a() memory(none) : () -> !s32i
     ```
   }];
-}
 
-def CIR_SideEffectAttr : CIR_EnumAttr<CIR_SideEffect, "side_effect">;
+  let parameters = (ins "ModRefInfo":$other, "ModRefInfo":$arg_mem,
+                        "ModRefInfo":$inaccessible_mem,
+                        "ModRefInfo":$errno_mem, "ModRefInfo":$target_mem0,
+                        "ModRefInfo":$target_mem1);
+
+  let builders = [
+    AttrBuilder<(ins "ModRefInfo":$all), [{
+      return $_get($_ctxt, all, all, all, all, all, all);
+    }]>
+  ];
+
+  let extraClassDeclaration = [{
+    /// Whether every class of memory carries the same access as `other`.
+    bool isUniform() const {
+      return getArgMem() == getOther() && getInaccessibleMem() == getOther() &&
+             getErrnoMem() == getOther() && getTargetMem0() == getOther() &&
+             getTargetMem1() == getOther();
+    }
+  }];
+
+  let assemblyFormat = "`<` struct(params) `>`";
+  let canHaveIllegalCXXABIType = 0;
+}
 
 //===----------------------------------------------------------------------===//
 // StaticLocalGuardAttr
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index e323eff0b9aa6..463d158c08f8b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -46,7 +46,9 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getCalleeAttrName() { return "callee"; }
     static llvm::StringRef getNoThrowAttrName() { return "nothrow"; }
     static llvm::StringRef getNoReturnAttrName() { return "noreturn"; }
-    static llvm::StringRef getSideEffectAttrName() { return "side_effect"; }
+    static llvm::StringRef getMemoryEffectsAttrName() {
+      return "memory_effects";
+    }
     static llvm::StringRef getReturnsTwiceAttrName() { return "returns_twice"; }
     static llvm::StringRef getColdAttrName() { return "cold"; }
     static llvm::StringRef getHotAttrName() { return "hot"; }
@@ -54,6 +56,7 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getNoDuplicatesAttrName() { return "noduplicate"; }
     static llvm::StringRef getConvergentAttrName() { return "convergent"; }
     static llvm::StringRef getNoUnwindAttrName() { return "nounwind"; }
+    static llvm::StringRef getWillReturnAttrName() { return "willreturn"; }
     static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
     static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
     static llvm::StringRef getGlobalDtorsAttrName() { return "cir.global_dtors"; }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index b366e781084a4..7051c3b2c2afa 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4296,7 +4296,7 @@ def CIR_FuncOp : CIR_Op<"func", [
     OptionalAttr<DictArrayAttr>:$arg_attrs,
     OptionalAttr<DictArrayAttr>:$res_attrs,
     OptionalAttr<FlatSymbolRefAttr>:$aliasee,
-    OptionalAttr<CIR_SideEffectAttr>:$side_effect,
+    OptionalAttr<CIR_MemoryEffectsAttr>:$memory_effects,
     OptionalAttr<FlatSymbolRefAttr>:$personality,
     CIR_OptionalPriorityAttr:$global_ctor_priority,
     CIR_OptionalPriorityAttr:$global_dtor_priority,
@@ -4598,9 +4598,11 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []>
   dag commonArgs = (ins OptionalAttr<FlatSymbolRefAttr>:$callee,
       Variadic<CIR_AnyType>:$args,
       UnitAttr:$nothrow,
+      UnitAttr:$nounwind,
+      UnitAttr:$willreturn,
       OptionalAttr<CIR_InlineKindAttr>:$inline_kind,
       UnitAttr:$musttail,
-      DefaultValuedAttr<CIR_SideEffectAttr, "SideEffect::All">:$side_effect,
+      OptionalAttr<CIR_MemoryEffectsAttr>:$memory_effects,
       OptionalAttr<DictArrayAttr>:$arg_attrs,
       OptionalAttr<DictArrayAttr>:$res_attrs
       );
@@ -4696,7 +4698,7 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
                "mlir::Block *":$normalDest,
                "mlir::Block *":$unwindDest,
                CArg<"mlir::ValueRange", "{}">:$callOperands,
-               CArg<"SideEffect", "SideEffect::All">:$sideEffect), [{
+               CArg<"cir::MemoryEffectsAttr", "{}">:$memoryEffects), [{
       $_state.addOperands(callOperands);
 
       if (callee)
@@ -4704,8 +4706,8 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
       if (resType && !isa<VoidType>(resType))
         $_state.addTypes(resType);
 
-      $_state.addAttribute("side_effect",
-        SideEffectAttr::get($_builder.getContext(), sideEffect));
+      if (memoryEffects)
+        $_state.addAttribute("memory_effects", memoryEffects);
 
       // Handle branches
       $_state.addSuccessors(normalDest);
@@ -4716,7 +4718,7 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
                "mlir::Block *":$normalDest,
                "mlir::Block *":$unwindDest,
                CArg<"mlir::ValueRange", "{}">:$callOperands,
-               CArg<"SideEffect", "SideEffect::All">:$sideEffect), [{
+               CArg<"cir::MemoryEffectsAttr", "{}">:$memoryEffects), [{
       ::llvm::SmallVector<mlir::Value, 4> finalCallOperands({ind_target});
       finalCallOperands.append(callOperands.begin(), callOperands.end());
       $_state.addOperands(finalCallOperands);
@@ -4724,8 +4726,8 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
       if (!fn_type.hasVoidReturn())
         $_state.addTypes(fn_type.getReturnType());
 
-      $_state.addAttribute("side_effect",
-        SideEffectAttr::get($_builder.getContext(), sideEffect));
+      if (memoryEffects)
+        $_state.addAttribute("memory_effects", memoryEffects);
 
       // Handle branches
       $_state.addSuccessors(normalDest);
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index c39a9c7818da7..a759e9c147b44 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -43,8 +43,8 @@ let cppNamespace = "::cir" in {
           "mlir::Value", "getIndirectCall", (ins)>,
       InterfaceMethod<"Return whether the callee is nothrow",
                       "bool", "getNothrow", (ins)>,
-      InterfaceMethod<"Return the side effects of the call operation",
-                      "cir::SideEffect", "getSideEffect", (ins)>,
+      InterfaceMethod<"Return the memory effects of the call operation",
+                      "cir::MemoryEffectsAttr", "getMemoryEffectsAttr", (ins)>,
       InterfaceMethod<[{"Set the inline-kind of a call operation"}],
                       "void", "setInlineKind", 
                       (ins "std::optional<cir::InlineKind>":$kind), [{}], 
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 2caf22d3beb88..304cf0e8bf3f6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -322,9 +322,8 @@ void CIRGenModule::constructAttributeList(
     CIRGenCalleeInfo calleeInfo, mlir::NamedAttrList &attrs,
     llvm::MutableArrayRef<mlir::NamedAttrList> argAttrs,
     mlir::NamedAttrList &retAttrs, cir::CallingConv &callingConv,
-    cir::SideEffect &sideEffect, bool attrOnCallSite, bool isThunk) {
+    bool attrOnCallSite, bool isThunk) {
   callingConv = info.getCallingConvention();
-  sideEffect = cir::SideEffect::All;
 
   auto addUnitAttr = [&](llvm::StringRef name) {
     attrs.set(name, mlir::UnitAttr::get(&getMLIRContext()));
@@ -390,19 +389,22 @@ void CIRGenModule::constructAttributeList(
 
     assert(!cir::MissingFeatures::opCallAttrs());
 
-    // 'const', 'pure' and 'noalias' attributed functions are also nounwind.
-    if (targetDecl->hasAttr<ConstAttr>()) {
-      // gcc specifies that 'const' functions have greater restrictions than
-      // 'pure' functions, so they also cannot have infinite loops.
-      sideEffect = cir::SideEffect::Const;
-    } else if (targetDecl->hasAttr<PureAttr>()) {
-      // gcc specifies that 'pure' functions cannot have infinite loops.
-      sideEffect = cir::SideEffect::Pure;
+    // 'const' and 'pure' imply more than memory effects: the callee also
+    // cannot unwind and cannot loop forever.  Each gets its own attribute,
+    // since none of the three can be derived from another.
+    std::optional<cir::ModRefInfo> access;
+    if (targetDecl->hasAttr<ConstAttr>())
+      access = cir::ModRefInfo::NoModRef;
+    else if (targetDecl->hasAttr<PureAttr>())
+      access = cir::ModRefInfo::Ref;
+
+    if (access) {
+      attrs.set(cir::CIRDialect::getMemoryEffectsAttrName(),
+                cir::MemoryEffectsAttr::get(&getMLIRContext(), *access));
+      addUnitAttr(cir::CIRDialect::getNoUnwindAttrName());
+      addUnitAttr(cir::CIRDialect::getWillReturnAttrName());
     }
 
-    attrs.set(cir::CIRDialect::getSideEffectAttrName(),
-              cir::SideEffectAttr::get(&getMLIRContext(), sideEffect));
-
     // TODO(cir): Add noalias to returns for malloc-like functions
     // (__attribute__((malloc)) / __declspec(restrict)).
 
@@ -1323,9 +1325,8 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
   assert(!cir::MissingFeatures::opCallCallConv());
   assert(!cir::MissingFeatures::opCallAttrs());
   cir::CallingConv callingConv;
-  cir::SideEffect sideEffect;
   cgm.constructAttributeList(funcName, funcInfo, callee.getAbstractInfo(),
-                             attrs, argAttrs, retAttrs, callingConv, sideEffect,
+                             attrs, argAttrs, retAttrs, callingConv,
                              /*attrOnCallSite=*/true, /*isThunk=*/false);
 
   auto resolvedFuncOpFromGlobal = [&](mlir::Operation *op) -> cir::FuncOp {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index bdc2707723283..bea0f05246efc 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3232,7 +3232,6 @@ void CIRGenModule::setCIRFunctionAttributes(GlobalDecl globalDecl,
                                             cir::FuncOp func, bool isThunk) {
   // TODO(cir): More logic of constructAttributeList is needed.
   cir::CallingConv callingConv;
-  cir::SideEffect sideEffect;
 
   // TODO(cir): The current list should be initialized with the extra function
   // attributes, but we don't have those yet.  For now, the PAL is initialized
@@ -3243,7 +3242,7 @@ void CIRGenModule::setCIRFunctionAttributes(GlobalDecl globalDecl,
   std::vector<mlir::NamedAttrList> argAttrs(info.arguments().size());
   mlir::NamedAttrList retAttrs{};
   constructAttributeList(func.getName(), info, globalDecl, pal, argAttrs,
-                         retAttrs, callingConv, sideEffect,
+                         retAttrs, callingConv,
                          /*attrOnCallSite=*/false, isThunk);
 
   for (mlir::NamedAttribute attr : pal)
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 51b9c420c94be..9fb74f221fafd 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -366,8 +366,6 @@ class CIRGenModule : public CIRGenTypeCache {
   /// contribute to the function attributes and calling convention.
   /// \param attrs [out] - On return, the attribute list to use.
   /// \param callingConv [out] - On return, the calling convention to use.
-  /// \param sideEffect [out] - On return, the side effect type of the
-  /// attributes.
   /// \param attrOnCallSite - Whether or not the attributes are on a call site.
   /// \param isThunk - Whether the function is a thunk.
   void constructAttributeList(
@@ -375,7 +373,7 @@ class CIRGenModule : public CIRGenTypeCache {
       CIRGenCalleeInfo calleeInfo, mlir::NamedAttrList &attrs,
       llvm::MutableArrayRef<mlir::NamedAttrList> argAttrs,
       mlir::NamedAttrList &retAttrs, cir::CallingConv &callingConv,
-      cir::SideEffect &sideEffect, bool attrOnCallSite, bool isThunk);
+      bool attrOnCallSite, bool isThunk);
   /// Helper function for constructAttributeList/others.  Builds a set of
   /// function attributes to add to a function based on language opts, codegen
   /// opts, and some small properties.
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 23426bed89f6a..043304bd72250 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -302,7 +302,7 @@ template <typename Ty> struct EnumTraits {};
 
 REGISTER_ENUM_TYPE(GlobalLinkageKind);
 REGISTER_ENUM_TYPE(VisibilityKind);
-REGISTER_ENUM_TYPE(SideEffect);
+REGISTER_ENUM_TYPE(ModRefInfo);
 REGISTER_ENUM_TYPE(CallingConv);
 } // namespace
 
@@ -335,6 +335,111 @@ static ParseResult parseCIRKeyword(AsmParser &parser, RetTy &result) {
   return success();
 }
 
+/// The memory classes that can be named individually, in the order they
+/// print.  Positional: parseMemoryEffects and printMemoryEffects both index
+/// by position, so a class added here needs an access added alongside it in
+/// both.  The spellings are LLVM's own, which underscores the target pair
+/// but not the rest.
+static constexpr llvm::StringLiteral memoryClassNames[] = {
+    "argmem", "inaccessiblemem", "errnomem", "target_mem0", "target_mem1"};
+
+/// Print memory effects in LLVM's compact form, except that the two target
+/// classes always print by name rather than collapsing to `target_mem`.  The
+/// access covering every class not named individually comes first, then only
+/// the classes differing from it.  That leading access is left out when it is
+/// `none` and some class does differ, since an unnamed class reads back as
+/// `none`.
+static void printMemoryEffects(mlir::AsmPrinter &printer,
+                               cir::MemoryEffectsAttr effects) {
+  cir::ModRefInfo other = effects.getOther();
+  printer << " memory(";
+  if (effects.isUniform()) {
+    printer << cir::stringifyModRefInfo(other) << ")";
+    return;
+  }
+
+  bool needComma = false;
+  if (other != cir::ModRefInfo::NoModRef) {
+    printer << cir::stringifyModRefInfo(other);
+    needComma = true;
+  }
+
+  cir::ModRefInfo classes[] = {
+      effects.getArgMem(), effects.getInaccessibleMem(), effects.getErrnoMem(),
+      effects.getTargetMem0(), effects.getTargetMem1()};
+  for (auto [name, info] : llvm::zip_equal(memoryClassNames, classes)) {
+    if (info == other)
+      continue;
+    if (needComma)
+      printer << ", ";
+    printer << name << ": " << cir::stringifyModRefInfo(info);
+    needComma = true;
+  }
+  printer << ")";
+}
+
+/// Parse the compact form printed by printMemoryEffects.  The opening
+/// `memory` keyword has already been consumed.
+static mlir::ParseResult parseMemoryEffects(mlir::AsmParser &parser,
+                                            cir::MemoryEffectsAttr &result) {
+  if (parser.parseLParen().failed())
+    return failure();
+
+  // A class left unnamed inherits `other`, which is `none` unless the spelling
+  // opens with a bare access.
+  cir::ModRefInfo other = cir::ModRefInfo::NoModRef;
+  std::array<std::optional<cir::ModRefInfo>, std::size(memoryClassNames)> named;
+
+  // Record one named class, with its name and colon already consumed.
+  auto parseClass = [&](llvm::SMLoc loc, llvm::StringRef name) -> ParseResult {
+    const auto *entry = llvm::find(memoryClassNames, name);
+    if (entry == std::end(memoryClassNames))
+      return parser.emitError(loc, "unknown memory class '") << name << "'";
+    llvm::SMLoc accessLoc = parser.getCurrentLocation();
+    cir::ModRefInfo info;
+    if (parseCIRKeyword<cir::ModRefInfo>(parser, info).failed())
+      return parser.emitError(accessLoc, "expected a memory access kind");
+    std::optional<cir::ModRefInfo> &slot =
+        named[entry - std::begin(memoryClassNames)];
+    if (slot)
+      return parser.emitError(loc, "duplicate memory class '") << name << "'";
+    slot = info;
+    return success();
+  };
+
+  llvm::SMLoc firstLoc = parser.getCurrentLocation();
+  llvm::StringRef first;
+  if (parser.parseKeyword(&first).failed())
+    return failure();
+
+  if (parser.parseOptionalColon().succeeded()) {
+    if (parseClass(firstLoc, first).failed())
+      return failure();
+  } else if (std::optional<cir::ModRefInfo> parsed =
+                 cir::symbolizeModRefInfo(first)) {
+    other = *parsed;
+  } else {
+    return parser.emitError(firstLoc, "expected a memory access kind or class");
+  }
+
+  while (parser.parseOptionalComma().succeeded()) {
+    llvm::SMLoc loc = parser.getCurrentLocation();
+    llvm::StringRef name;
+    if (parser.parseKeyword(&name).failed() || parser.parseColon().failed() ||
+        parseClass(loc, name).failed())
+      return failure();
+  }
+
+  if (parser.parseRParen().failed())
+    return failure();
+
+  result = cir::MemoryEffectsAttr::get(
+      parser.getContext(), other, named[0].value_or(other),
+      named[1].value_or(other), named[2].value_or(other),
+      named[3].value_or(other), named[4].value_or(other));
+  return success();
+}
+
 // Check if a region's termination omission is valid and, if so, creates and
 // inserts the omitted terminator into the region.
 static LogicalResult ensureRegionTerm(OpAsmParser &parser, Region &region,
@@ -1350,16 +1455,19 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser,
     result.addAttribute(CIRDialect::getNoThrowAttrName(),
                         mlir::UnitAttr::get(parser.getContext()));
 
-  if (parser.parseOptionalKeyword("side_effect").succeeded()) {
-    if (parser.parseLParen().failed())
-      return failure();
-    cir::SideEffect sideEffect;
-    if (parseCIRKeyword<cir::SideEffect>(parser, sideEffect).failed())
-      return failure();
-    if (parser.parseRParen().failed())
+  if (parser.parseOptionalKeyword("nounwind").succeeded())
+    result.addAttribute(CIRDialect::getNoUnwindAttrName(),
+                        mlir::UnitAttr::get(parser.getContext()));
+
+  if (parser.parseOptionalKeyword("willreturn").succeeded())
+    result.addAttribute(CIRDialect::getWillReturnAttrName(),
+                        mlir::UnitAttr::get(parser.getContext()));
+
+  if (parser.parseOptionalKeyword("memory").succeeded()) {
+    cir::MemoryEffectsAttr effects;
+    if (parseMemoryEffects(parser, effects).failed())
       return failure();
-    auto attr = cir::SideEffectAttr::get(parser.getContext(), sideEffect);
-    result.addAttribute(CIRDialect::getSideEffectAttrName(), attr);
+    result.addAttribute(CIRDialect::getMemoryEffectsAttrName(), effects);
   }
 
   if (parser.parseOptionalAttrDict(result.attributes))
@@ -1417,12 +1525,14 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser,
   return mlir::success();
 }
 
-static void
-printCallCommon(mlir::Operation *op, mlir::FlatSymbolRefAttr calleeSym,
-                mlir::Value indirectCallee, mlir::OpAsmPrinter &printer,
-                bool isNothrow, cir::SideEffect sideEffect, ArrayAttr argAttrs,
-                ArrayAttr resAttrs, mlir::Block *normalDest = nullptr,
-                mlir::Block *unwindDest = nullptr) {
+static void printCallCommon(mlir::Operation *op,
+                            mlir::FlatSymbolRefAttr calleeSym,
+                            mlir::Value indirectCallee,
+                            mlir::OpAsmPrinter &printer, bool isNothrow,
+                            cir::MemoryEffectsAttr memoryEffects,
+                            ArrayAttr argAttrs, ArrayAttr resAttrs,
+                            mlir::Block *normalDest = nullptr,
+                            mlir::Block *unwindDest = nullptr) {
   printer << ' ';
 
   auto callLikeOp = mlir::cast<cir::CIRCallOpInterface>(op);
@@ -1454,17 +1564,22 @@ printCallCommon(mlir::Operation *op, mlir::FlatSymbolRefAttr calleeSym,
   if (isNothrow)
     printer << " nothrow";
 
-  if (sideEffect != cir::SideEffect::All) {
-    printer << " side_effect(";
-    printer << stringifySideEffect(sideEffect);
-    printer << ")";
-  }
+  if (op->hasAttr(CIRDialect::getNoUnwindAttrName()))
+    printer << " nounwind";
+
+  if (op->hasAttr(CIRDialect::getWillReturnAttrName()))
+    printer << " willreturn";
+
+  if (memoryEffects)
+    printMemoryEffects(printer, memoryEffects);
 
   llvm::SmallVector<::llvm::StringRef> elidedAttrs = {
       CIRDialect::getCalleeAttrName(),
       CIRDialect::getMustTailAttrName(),
       CIRDialect::getNoThrowAttrName(),
-      CIRDialect::getSideEffectAttrName(),
+      CIRDialect::getNoUnwindAttrName(),
+      CIRDialect::getWillReturnAttrName(),
+      CIRDialect::getMemoryEffectsAttrName(),
       CIRDialect::getOperandSegmentSizesAttrName(),
       llvm::StringRef("res_attrs"),
       llvm::StringRef("arg_attrs")};
@@ -1497,9 +1612,9 @@ mlir::ParseResult cir::CallOp::parse(mlir::OpAsmParser &parser,
 
 void cir::CallOp::print(mlir::OpAsmPrinter &p) {
   mlir::Value indirectCallee = isIndirect() ? getIndirectCall() : nullptr;
-  cir::SideEffect sideEffect = getSideEffect();
+  cir::MemoryEffectsAttr memoryEffects = getMemoryEffectsAttr();
   printCallCommon(*this, getCalleeAttr(), indirectCallee, p, getNothrow(),
-                  sideEffect, getArgAttrsAttr(), getResAttrsAttr());
+                  memoryEffects, getArgAttrsAttr(), getResAttrsAttr());
 }
 
 static LogicalResult
@@ -1613,9 +1728,9 @@ mlir::ParseResult cir::TryCallOp::parse(mlir::OpAsmParser &parser,
 
 void cir::TryCallOp::print(::mlir::OpAsmPrinter &p) {
   mlir::Value indirectCallee = isIndirect() ? getIndirectCall() : nullptr;
-  cir::SideEffect sideEffect = getSideEffect();
+  cir::MemoryEffectsAttr memoryEffects = getMemoryEffectsAttr();
   printCallCommon(*this, getCalleeAttr(), indirectCallee, p, getNothrow(),
-                  sideEffect, getArgAttrsAttr(), getResAttrsAttr(),
+                  memoryEffects, getArgAttrsAttr(), getResAttrsAttr(),
                   getNormalDest(), getUnwindDest());
 }
 
@@ -2792,16 +2907,11 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
       }).failed())
     return failure();
 
-  if (parser.parseOptionalKeyword("side_effect").succeeded()) {
-    cir::SideEffect sideEffect;
-
-    if (parser.parseLParen().failed() ||
-        parseCIRKeyword<cir::SideEffect>(parser, sideEffect).failed() ||
-        parser.parseRParen().failed())
+  if (parser.parseOptionalKeyword("memory").succeeded()) {
+    cir::MemoryEffectsAttr effects;
+    if (parseMemoryEffects(parser, effects).failed())
       return failure();
-
-    auto attr = cir::SideEffectAttr::get(parser.getContext(), sideEffect);
-    state.addAttribute(CIRDialect::getSideEffectAttrName(), attr);
+    state.addAttribute(CIRDialect::getMemoryEffectsAttrName(), effects);
   }
 
   // Parse optional annotations attribute (an ArrayAttr of AnnotationAttr).
@@ -2988,12 +3098,8 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
       p << "(" << globalDtorPriority.value() << ")";
   }
 
-  if (std::optional<cir::SideEffect> sideEffect = getSideEffect();
-      sideEffect && *sideEffect != cir::SideEffect::All) {
-    p << " side_effect(";
-    p << stringifySideEffect(*sideEffect);
-    p << ")";
-  }
+  if (cir::MemoryEffectsAttr memoryEffects = getMemoryEffectsAttr())
+    printMemoryEffects(p, memoryEffects);
 
   if (mlir::ArrayAttr annotations = getAnnotationsAttr()) {
     p << ' ';
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
index 1653e673859dd..095740e72ea83 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
@@ -79,6 +79,8 @@ mlir::Block *cir::replaceCallWithTryCall(cir::CallOp callOp,
 
   // Copy all attributes from the original call except those already set by
   // TryCallOp::create or that are operation-specific and should not be copied.
+  // nounwind is copied even though this site has an unwind edge, because it
+  // describes the const or pure callee rather than the edge.
   llvm::StringRef excludedAttrs[] = {
       cir::CIRDialect::getCalleeAttrName(), // Set by create()
       cir::CIRDialect::getOperandSegmentSizesAttrName(),
@@ -86,13 +88,6 @@ mlir::Block *cir::replaceCallWithTryCall(cir::CallOp callOp,
   for (mlir::NamedAttribute attr : callOp->getAttrs()) {
     if (llvm::is_contained(excludedAttrs, attr.getName()))
       continue;
-    assert(!llvm::is_contained(
-               {
-                   cir::CIRDialect::getNoThrowAttrName(),
-                   cir::CIRDialect::getNoUnwindAttrName(),
-               },
-               attr.getName()) &&
-           "unexpected attribute on converted call");
     tryCallOp->setAttr(attr.getName(), attr.getValue());
   }
 
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index f4ada8eb04c1c..eefacc41fd1a7 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -430,45 +430,46 @@ mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp,
   return value;
 }
 
-void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow,
-                              cir::SideEffect sideEffect,
-                              mlir::LLVM::MemoryEffectsAttr &memoryEffect,
-                              bool &noUnwind, bool &willReturn,
-                              bool &noReturn) {
-  using mlir::LLVM::ModRefInfo;
-
-  switch (sideEffect) {
-  case cir::SideEffect::All:
-    memoryEffect = {};
-    noUnwind = isNothrow;
-    willReturn = false;
-    break;
-
-  case cir::SideEffect::Pure:
-    memoryEffect = mlir::LLVM::MemoryEffectsAttr::get(
-        callOp->getContext(), /*other=*/ModRefInfo::Ref,
-        /*argMem=*/ModRefInfo::Ref,
-        /*inaccessibleMem=*/ModRefInfo::Ref,
-        /*errnoMem=*/ModRefInfo::Ref,
-        /*targetMem0=*/ModRefInfo::Ref,
-        /*targetMem1=*/ModRefInfo::Ref);
-    noUnwind = true;
-    willReturn = true;
-    break;
-
-  case cir::SideEffect::Const:
-    memoryEffect = mlir::LLVM::MemoryEffectsAttr::get(
-        callOp->getContext(), /*other=*/ModRefInfo::NoModRef,
-        /*argMem=*/ModRefInfo::NoModRef,
-        /*inaccessibleMem=*/ModRefInfo::NoModRef,
-        /*errnoMem=*/ModRefInfo::NoModRef,
-        /*targetMem0=*/ModRefInfo::NoModRef,
-        /*targetMem1=*/ModRefInfo::NoModRef);
-    noUnwind = true;
-    willReturn = true;
-    break;
-  }
+static mlir::LLVM::ModRefInfo convertModRefInfo(cir::ModRefInfo info) {
+  switch (info) {
+  case cir::ModRefInfo::NoModRef:
+    return mlir::LLVM::ModRefInfo::NoModRef;
+  case cir::ModRefInfo::Ref:
+    return mlir::LLVM::ModRefInfo::Ref;
+  case cir::ModRefInfo::Mod:
+    return mlir::LLVM::ModRefInfo::Mod;
+  case cir::ModRefInfo::ModRef:
+    return mlir::LLVM::ModRefInfo::ModRef;
+  }
+  llvm_unreachable("unhandled cir::ModRefInfo");
+}
+
+/// A null input stays null, which is how both dialects spell unknown effects.
+static mlir::LLVM::MemoryEffectsAttr
+convertMemoryEffects(mlir::MLIRContext *ctx, cir::MemoryEffectsAttr effects) {
+  if (!effects)
+    return {};
 
+  return mlir::LLVM::MemoryEffectsAttr::get(
+      ctx, convertModRefInfo(effects.getOther()),
+      convertModRefInfo(effects.getArgMem()),
+      convertModRefInfo(effects.getInaccessibleMem()),
+      convertModRefInfo(effects.getErrnoMem()),
+      convertModRefInfo(effects.getTargetMem0()),
+      convertModRefInfo(effects.getTargetMem1()));
+}
+
+static void convertCallEffects(mlir::Operation *callOp, bool isNothrow,
+                               cir::MemoryEffectsAttr effects,
+                               mlir::LLVM::MemoryEffectsAttr &memoryEffect,
+                               bool &noUnwind, bool &willReturn,
+                               bool &noReturn) {
+  memoryEffect = convertMemoryEffects(callOp->getContext(), effects);
+  // CIR keeps two separate cannot-unwind facts, nothrow for a callee declared
+  // not to throw and nounwind for a const or pure callee.  LLVM has only
+  // nounwind, so either one sets it.
+  noUnwind = isNothrow || callOp->hasAttr(CIRDialect::getNoUnwindAttrName());
+  willReturn = callOp->hasAttr(CIRDialect::getWillReturnAttrName());
   noReturn = callOp->hasAttr(CIRDialect::getNoReturnAttrName());
 }
 
@@ -2160,9 +2161,10 @@ lowerCallAttributes(cir::CIRCallOpInterface op,
                     SmallVectorImpl<mlir::NamedAttribute> &result) {
   for (mlir::NamedAttribute attr : op->getAttrs()) {
     if (attr.getName() == CIRDialect::getCalleeAttrName() ||
-        attr.getName() == CIRDialect::getSideEffectAttrName() ||
+        attr.getName() == CIRDialect::getMemoryEffectsAttrName() ||
         attr.getName() == CIRDialect::getNoThrowAttrName() ||
         attr.getName() == CIRDialect::getNoUnwindAttrName() ||
+        attr.getName() == CIRDialect::getWillReturnAttrName() ||
         attr.getName() == CIRDialect::getNoReturnAttrName() ||
         attr.getName() == op.getInlineKindAttrName() ||
         attr.getName() == CIRDialect::getMustTailAttrName())
@@ -2205,8 +2207,8 @@ static mlir::LogicalResult rewriteCallOrInvoke(
   bool noUnwind = false;
   bool willReturn = false;
   bool noReturn = false;
-  convertSideEffectForCall(op, call.getNothrow(), call.getSideEffect(),
-                           memoryEffects, noUnwind, willReturn, noReturn);
+  convertCallEffects(op, call.getNothrow(), call.getMemoryEffectsAttr(),
+                     memoryEffects, noUnwind, willReturn, noReturn);
 
   SmallVector<mlir::NamedAttribute, 4> attributes;
   if (mlir::failed(
@@ -2694,8 +2696,10 @@ static bool shouldDropFuncAttribute(cir::FuncOp func, mlir::NamedAttribute attr,
          attr.getName() == func.getCallingConvAttrName() ||
          attr.getName() == func.getDsoLocalAttrName() ||
          attr.getName() == func.getInlineKindAttrName() ||
-         attr.getName() == func.getSideEffectAttrName() ||
+         attr.getName() == func.getMemoryEffectsAttrName() ||
          attr.getName() == CIRDialect::getNoReturnAttrName() ||
+         attr.getName() == CIRDialect::getNoUnwindAttrName() ||
+         attr.getName() == CIRDialect::getWillReturnAttrName() ||
          attr.getName() == CIRDialect::getStrictFPAttrName() ||
          attr.getName() == func.getAnnotationsAttrName();
 }
@@ -2813,36 +2817,13 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite(
 
   assert(!cir::MissingFeatures::opFuncMultipleReturnVals());
 
-  if (std::optional<cir::SideEffect> sideEffectKind = op.getSideEffect()) {
-    switch (*sideEffectKind) {
-    case cir::SideEffect::All:
-      break;
-    case cir::SideEffect::Pure:
-      fn.setMemoryEffectsAttr(mlir::LLVM::MemoryEffectsAttr::get(
-          fn.getContext(),
-          /*other=*/mlir::LLVM::ModRefInfo::Ref,
-          /*argMem=*/mlir::LLVM::ModRefInfo::Ref,
-          /*inaccessibleMem=*/mlir::LLVM::ModRefInfo::Ref,
-          /*errnoMem=*/mlir::LLVM::ModRefInfo::Ref,
-          /*targetMem0=*/mlir::LLVM::ModRefInfo::Ref,
-          /*targetMem1=*/mlir::LLVM::ModRefInfo::Ref));
-      fn.setNoUnwind(true);
-      fn.setWillReturn(true);
-      break;
-    case cir::SideEffect::Const:
-      fn.setMemoryEffectsAttr(mlir::LLVM::MemoryEffectsAttr::get(
-          fn.getContext(),
-          /*other=*/mlir::LLVM::ModRefInfo::NoModRef,
-          /*argMem=*/mlir::LLVM::ModRefInfo::NoModRef,
-          /*inaccessibleMem=*/mlir::LLVM::ModRefInfo::NoModRef,
-          /*errnoMem=*/mlir::LLVM::ModRefInfo::NoModRef,
-          /*targetMem0=*/mlir::LLVM::ModRefInfo::NoModRef,
-          /*targetMem1=*/mlir::LLVM::ModRefInfo::NoModRef));
-      fn.setNoUnwind(true);
-      fn.setWillReturn(true);
-      break;
-    }
-  }
+  if (cir::MemoryEffectsAttr effects = op.getMemoryEffectsAttr())
+    fn.setMemoryEffectsAttr(convertMemoryEffects(fn.getContext(), effects));
+
+  if (op->hasAttr(CIRDialect::getNoUnwindAttrName()))
+    fn.setNoUnwind(true);
+  if (op->hasAttr(CIRDialect::getWillReturnAttrName()))
+    fn.setWillReturn(true);
 
   if (op->hasAttr(CIRDialect::getNoReturnAttrName()))
     fn.setNoreturn(true);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index 146b31b907fcc..56941b2aa51e7 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -35,11 +35,6 @@ mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp, mlir::Attribute attr,
 
 mlir::LLVM::Linkage convertLinkage(cir::GlobalLinkageKind linkage);
 
-void convertSideEffectForCall(mlir::Operation *callOp, bool isNothrow,
-                              cir::SideEffect sideEffect,
-                              mlir::LLVM::MemoryEffectsAttr &memoryEffect,
-                              bool &noUnwind, bool &willReturn, bool &noReturn);
-
 struct LLVMBlockAddressInfo {
   // Get the next tag index
   uint32_t getTagIndex() { return blockTagOpIndex++; }
diff --git a/clang/test/CIR/CodeGen/call.c b/clang/test/CIR/CodeGen/call.c
index ebc811b7a2aa4..2653ba9845718 100644
--- a/clang/test/CIR/CodeGen/call.c
+++ b/clang/test/CIR/CodeGen/call.c
@@ -150,9 +150,9 @@ int f12(void) {
 
 // CIR-LABEL: cir.func{{.*}} @f12() -> !s32i{{.*}} {
 // CIR:         %[[A:.+]] = cir.const #cir.int<1> : !s32i
-// CIR-NEXT:    %{{.+}} = cir.call @f10(%[[A]]) side_effect(pure) : (!s32i {llvm.noundef}) -> !s32i
+// CIR-NEXT:    %{{.+}} = cir.call @f10(%[[A]]) nounwind willreturn memory(read) : (!s32i {llvm.noundef}) -> !s32i
 // CIR-NEXT:    %[[B:.+]] = cir.const #cir.int<2> : !s32i
-// CIR-NEXT:    %{{.+}} = cir.call @f11(%[[B]]) side_effect(const) : (!s32i {llvm.noundef}) -> !s32i
+// CIR-NEXT:    %{{.+}} = cir.call @f11(%[[B]]) nounwind willreturn memory(none) : (!s32i {llvm.noundef}) -> !s32i
 
 // LLVM-LABEL: define{{.*}} i32 @f12(){{.*}}
 // LLVM:         %{{.+}} = call i32 @f10(i32 noundef 1) #[[ATTR0:.+]]
diff --git a/clang/test/CIR/CodeGen/side-effect.cpp b/clang/test/CIR/CodeGen/side-effect.cpp
index a1e0fbbeb0e2c..d5193430f5afe 100644
--- a/clang/test/CIR/CodeGen/side-effect.cpp
+++ b/clang/test/CIR/CodeGen/side-effect.cpp
@@ -8,7 +8,7 @@ extern "C" {
 
 // FIXME: We should figure out how to better print this on functions in the
 // future.
-// CIR: cir.func{{.*}}@pure_func() -> !s32i side_effect(pure) attributes {{{.*}}nothrow} {
+// CIR: cir.func{{.*}}@pure_func() -> !s32i memory(read) attributes {{{.*}}nothrow, nounwind, willreturn} {
 // LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read)
 // LLVM: define{{.*}} @pure_func() #{{.*}} {
 // OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read)
@@ -16,7 +16,7 @@ extern "C" {
 __attribute__((pure))
 int pure_func() { return 2;}
 
-// CIR: cir.func{{.*}}@const_func() -> !s32i side_effect(const) attributes {{{.*}}nothrow} {
+// CIR: cir.func{{.*}}@const_func() -> !s32i memory(none) attributes {{{.*}}nothrow, nounwind, willreturn} {
 // LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none)
 // LLVM: define{{.*}} @const_func() #{{.*}} {
 // OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none)
@@ -25,11 +25,11 @@ __attribute__((const))
 int const_func() { return 1;}
 
 void use() {
-  // CIR: cir.call @pure_func() side_effect(pure) : () -> !s32i
+  // CIR: cir.call @pure_func() nounwind willreturn memory(read) : () -> !s32i
   // LLVM: call i32 @pure_func() #[[PURE_ATTR:.*]]
   // OGCG: call i32 @pure_func() #[[PURE_ATTR:.*]]
   pure_func();
-  // CIR: cir.call @const_func() side_effect(const) : () -> !s32i
+  // CIR: cir.call @const_func() nounwind willreturn memory(none) : () -> !s32i
   // LLVM: call i32 @const_func() #[[CONST_ATTR:.*]]
   // OGCG: call i32 @const_func() #[[CONST_ATTR:.*]]
   const_func();
diff --git a/clang/test/CIR/IR/call.cir b/clang/test/CIR/IR/call.cir
index 59f28be36846f..0c843b78023d1 100644
--- a/clang/test/CIR/IR/call.cir
+++ b/clang/test/CIR/IR/call.cir
@@ -8,18 +8,42 @@ cir.func private @f1()
 
 cir.func @f2() {
   cir.call @f1() : () -> ()
-  cir.call @f1() side_effect(pure) : () -> ()
-  cir.call @f1() side_effect(const) : () -> ()
+  cir.call @f1() memory(read) : () -> ()
+  cir.call @f1() memory(none) : () -> ()
+  cir.call @f1() nounwind : () -> ()
+  cir.call @f1() willreturn : () -> ()
+  cir.call @f1() nothrow nounwind willreturn memory(none) : () -> ()
+  cir.call @f1() memory(write) : () -> ()
+  cir.call @f1() memory(readwrite) : () -> ()
+  cir.call @f1() memory(argmem: readwrite) : () -> ()
+  cir.call @f1() memory(read, argmem: readwrite) : () -> ()
+  cir.call @f1() memory(inaccessiblemem: write, errnomem: read) : () -> ()
+  cir.call @f1() memory(argmem: read, inaccessiblemem: write) : () -> ()
+  cir.call @f1() memory(readwrite, target_mem0: read, target_mem1: none) : () -> ()
   cir.return
 }
 
 // CHECK:      cir.func{{.*}} @f2() {
 // CHECK-NEXT:   cir.call @f1() : () -> ()
-// CHECK-NEXT:   cir.call @f1() side_effect(pure) : () -> ()
-// CHECK-NEXT:   cir.call @f1() side_effect(const) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(read) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(none) : () -> ()
+// CHECK-NEXT:   cir.call @f1() nounwind : () -> ()
+// CHECK-NEXT:   cir.call @f1() willreturn : () -> ()
+// CHECK-NEXT:   cir.call @f1() nothrow nounwind willreturn memory(none) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(write) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(readwrite) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(argmem: readwrite) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(read, argmem: readwrite) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(inaccessiblemem: write, errnomem: read) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(argmem: read, inaccessiblemem: write) : () -> ()
+// CHECK-NEXT:   cir.call @f1() memory(readwrite, target_mem0: read, target_mem1: none) : () -> ()
 // CHECK-NEXT:   cir.return
 // CHECK-NEXT: }
 
+// cir.func parses memory effects separately from cir.call.
+// CHECK: cir.func private @f8() memory(read, argmem: readwrite)
+cir.func private @f8() memory(read, argmem: readwrite)
+
 cir.func private @f3() -> !s32i
 
 cir.func @f4() -> !s32i {
diff --git a/clang/test/CIR/IR/enum-attrs.cir b/clang/test/CIR/IR/enum-attrs.cir
index e3979fcd138f4..2dddf4c3c6724 100644
--- a/clang/test/CIR/IR/enum-attrs.cir
+++ b/clang/test/CIR/IR/enum-attrs.cir
@@ -120,11 +120,15 @@ cir.func @calling_conv_attr() {
                           #cir.calling_conv<amdgpu_kernel>]}
 }
 
-// CHECK-LABEL: cir.func @side_effect_attr() {
-cir.func @side_effect_attr() {
-  // CHECK: cir.return {cir.test = [#cir.side_effect<all>, #cir.side_effect<pure>, #cir.side_effect<const>]}
-  cir.return {cir.test = [#cir.side_effect<all>, #cir.side_effect<pure>,
-                          #cir.side_effect<const>]}
+// CHECK-LABEL: cir.func @memory_effects_attr() {
+cir.func @memory_effects_attr() {
+  // CHECK: cir.return {cir.test = [#cir.memory_effects<other = none, arg_mem = none, inaccessible_mem = none, errno_mem = none, target_mem0 = none, target_mem1 = none>, #cir.memory_effects<other = read, arg_mem = readwrite, inaccessible_mem = write, errno_mem = read, target_mem0 = read, target_mem1 = read>]}
+  cir.return {cir.test = [#cir.memory_effects<other = none, arg_mem = none,
+                            inaccessible_mem = none, errno_mem = none,
+                            target_mem0 = none, target_mem1 = none>,
+                          #cir.memory_effects<other = read, arg_mem = readwrite,
+                            inaccessible_mem = write, errno_mem = read,
+                            target_mem0 = read, target_mem1 = read>]}
 }
 
 // A bit enum, so a value can name several flags.
diff --git a/clang/test/CIR/IR/invalid-memory-effects.cir b/clang/test/CIR/IR/invalid-memory-effects.cir
new file mode 100644
index 0000000000000..a1db96e254a5d
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-memory-effects.cir
@@ -0,0 +1,50 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+cir.func private @f1()
+cir.func @bare_class_name() {
+  // expected-error @below {{expected a memory access kind or class}}
+  cir.call @f1() memory(argmem) : () -> ()
+  cir.return
+}
+
+// -----
+
+cir.func private @f1()
+cir.func @bad_named_access() {
+  // expected-error @below {{expected a memory access kind}}
+  cir.call @f1() memory(argmem: bogus) : () -> ()
+  cir.return
+}
+
+// -----
+
+cir.func private @f1()
+cir.func @duplicate_class() {
+  // expected-error @below {{duplicate memory class 'argmem'}}
+  cir.call @f1() memory(argmem: read, argmem: write) : () -> ()
+  cir.return
+}
+
+// -----
+
+cir.func private @f1()
+cir.func @unknown_class() {
+  // expected-error @below {{unknown memory class 'stackmem'}}
+  cir.call @f1() memory(stackmem: read) : () -> ()
+  cir.return
+}
+
+// -----
+
+cir.func private @f1()
+cir.func @unknown_class_after_shared_access() {
+  // expected-error @below {{unknown memory class 'stackmem'}}
+  cir.call @f1() memory(read, stackmem: read) : () -> ()
+  cir.return
+}
+
+// -----
+
+// cir.func parses memory effects separately from cir.call.
+// expected-error @below {{expected a memory access kind or class}}
+cir.func private @on_a_func() memory(nonsense)
diff --git a/clang/test/CIR/Lowering/memory-effects.cir b/clang/test/CIR/Lowering/memory-effects.cir
new file mode 100644
index 0000000000000..db954c2aa82f2
--- /dev/null
+++ b/clang/test/CIR/Lowering/memory-effects.cir
@@ -0,0 +1,39 @@
+// RUN: cir-translate %s -cir-to-llvmir --target x86_64-unknown-linux-gnu -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+
+!s32i = !cir.int<s, 32>
+
+module {
+  cir.func private @reads() -> !s32i memory(read)
+  cir.func private @reads_nounwind() -> !s32i memory(read)
+      attributes {nounwind, willreturn}
+  cir.func private @writes_args(!cir.ptr<!s32i>) memory(argmem: readwrite)
+  cir.func private @only_nounwind() -> !s32i attributes {nounwind}
+  cir.func private @only_willreturn() -> !s32i attributes {willreturn}
+
+  cir.func @caller(%arg0: !cir.ptr<!s32i>) {
+    %0 = cir.call @reads() memory(read) : () -> !s32i
+    %1 = cir.call @reads_nounwind() nounwind willreturn memory(read) : () -> !s32i
+    cir.call @writes_args(%arg0) memory(argmem: readwrite) : (!cir.ptr<!s32i>) -> ()
+    cir.return
+  }
+}
+
+// Memory effects alone must not imply nounwind or willreturn, so the group for
+// @reads has to be exactly memory(read).
+// LLVM: declare i32 @reads() #[[READS:[0-9]+]]
+// LLVM: declare i32 @reads_nounwind() #[[BOTH:[0-9]+]]
+// LLVM: declare void @writes_args(ptr) #[[ARGMEM:[0-9]+]]
+// LLVM: declare i32 @only_nounwind() #[[NUW:[0-9]+]]
+// LLVM: declare i32 @only_willreturn() #[[WR:[0-9]+]]
+// An absent attribute must stay unknown rather than lowering as memory(none),
+// so @caller's define carries no attribute group.
+// LLVM: define void @caller(ptr %{{.+}}) {
+// LLVM: call i32 @reads() #[[READS]]
+// LLVM: call i32 @reads_nounwind() #[[BOTH]]
+// LLVM: call void @writes_args(ptr %{{.+}}) #[[ARGMEM]]
+// LLVM-DAG: attributes #[[READS]] = { memory(read) }
+// LLVM-DAG: attributes #[[BOTH]] = { nounwind willreturn memory(read) }
+// LLVM-DAG: attributes #[[ARGMEM]] = { memory(argmem: readwrite) }
+// LLVM-DAG: attributes #[[NUW]] = { nounwind }
+// LLVM-DAG: attributes #[[WR]] = { willreturn }
diff --git a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
index c3333386ce15c..d0fb57c65a64e 100644
--- a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
+++ b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
@@ -43,12 +43,30 @@ cir.func @test_preserve_res_attrs() {
 // CHECK-LABEL: cir.func @test_preserve_res_attrs()
 // CHECK:         cir.try_call @returnsPtr() ^{{.*}}, ^{{.*}} : () -> (!cir.ptr<!s32i> {llvm.nonnull})
 
-// Test that the side_effect attribute on a cir.call is preserved on the
-// resulting cir.try_call after flattening.
-cir.func @test_preserve_side_effect() {
+// Test that the memory effects on a cir.call are preserved on the resulting
+// cir.try_call after flattening.
+cir.func @test_preserve_memory_effects() {
+  cir.scope {
+    cir.try {
+      %0 = cir.call @pureFunc() memory(read) : () -> !s32i
+      cir.yield
+    } catch all (%eh_token : !cir.eh_token) {
+      %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
+      cir.end_catch %catch_token : !cir.catch_token
+      cir.yield
+    }
+  }
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_preserve_memory_effects()
+// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} memory(read) : () -> !s32i
+
+// Test that nounwind and willreturn are preserved on the cir.try_call.
+cir.func @test_preserve_nounwind_willreturn() {
   cir.scope {
     cir.try {
-      %0 = cir.call @pureFunc() side_effect(pure) : () -> !s32i
+      %0 = cir.call @pureFunc() nounwind willreturn memory(read) : () -> !s32i
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -59,8 +77,8 @@ cir.func @test_preserve_side_effect() {
   cir.return
 }
 
-// CHECK-LABEL: cir.func @test_preserve_side_effect()
-// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} side_effect(pure) : () -> !s32i
+// CHECK-LABEL: cir.func @test_preserve_nounwind_willreturn()
+// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} nounwind willreturn memory(read) : () -> !s32i
 
 // Test that argument attributes on an indirect cir.call are preserved on the
 // resulting indirect cir.try_call after flattening.
@@ -87,7 +105,7 @@ cir.func @test_preserve_indirect_call_attrs(
 cir.func @test_preserve_all_attrs(%arg0 : !cir.ptr<!s32i>) {
   cir.scope {
     cir.try {
-      %0 = cir.call @generalFunc(%arg0) side_effect(const) : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
+      %0 = cir.call @generalFunc(%arg0) memory(none) : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -99,7 +117,7 @@ cir.func @test_preserve_all_attrs(%arg0 : !cir.ptr<!s32i>) {
 }
 
 // CHECK-LABEL: cir.func @test_preserve_all_attrs(%arg0: !cir.ptr<!s32i>)
-// CHECK:         cir.try_call @generalFunc(%arg0) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} side_effect(const) : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
+// CHECK:         cir.try_call @generalFunc(%arg0) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} memory(none) : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
 
 // Test that all attribute types are preserved with indirect calls.
 cir.func @test_preserve_all_attrs_indirect(
@@ -107,7 +125,7 @@ cir.func @test_preserve_all_attrs_indirect(
       %obj : !cir.ptr<!rec_SomeClass>) {
   cir.scope {
     cir.try {
-      %0 = cir.call %fptr(%obj) side_effect(const) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
+      %0 = cir.call %fptr(%obj) memory(none) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -121,7 +139,7 @@ cir.func @test_preserve_all_attrs_indirect(
 // CHECK-LABEL: cir.func @test_preserve_all_attrs_indirect
 // CHECK:             %[[FN_PTR:.*]]: !cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>,
 // CHECK-SAME:        %[[OBJ:.*]]: !cir.ptr<!rec_SomeClass>
-// CHECK:         cir.try_call %[[FN_PTR]](%[[OBJ]]) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} side_effect(const) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
+// CHECK:         cir.try_call %[[FN_PTR]](%[[OBJ]]) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} memory(none) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
 
 cir.func private @generalFunc(!cir.ptr<!s32i>) -> !cir.ptr<!s32i>
 cir.func private @takesPtr(!cir.ptr<!s32i>)
diff --git a/clang/test/CIR/Transforms/idiom-recognizer.cpp b/clang/test/CIR/Transforms/idiom-recognizer.cpp
index c314aeae56fdc..4cef0a7658094 100644
--- a/clang/test/CIR/Transforms/idiom-recognizer.cpp
+++ b/clang/test/CIR/Transforms/idiom-recognizer.cpp
@@ -43,7 +43,7 @@ char *test_find(char *first, char *last, const char &value) {
 // FINAL: %[[LAST:.*]] = cir.load{{.*}} %[[LAST_ADDR]] :
 // FINAL: %[[VALUE:.*]] = cir.load{{.*}} %[[VALUE_ADDR]] :
 // FINAL: cir.call @_ZSt4findIPccET_S1_S1_RKT0_(%[[FIRST]], %[[LAST]], %[[VALUE]])
-// FINAL-SAME: nothrow side_effect(pure)
+// FINAL-SAME: nothrow nounwind willreturn memory(read)
 // FINAL-SAME: {llvm.noundef}
 // FINAL-SAME: -> (!cir.ptr<!s8i> {llvm.noundef})
 // FINAL-NOT: cir.call @_ZSt4find

>From c3f4c582f350751d5a8e4d90b58f63f78d056756 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 16 Sep 2026 17:58:54 -0700
Subject: [PATCH 2/2] [CIR] Use the LLVM dialect's memory effects attribute

#cir.memory_effects copied #llvm.memory_effects slot for slot, and the
compact printer copied the memory( block of Attribute::getAsString,
diverging from it on target_mem in both directions.  Reusing the LLVM
attribute drops both, along with the ModRefInfo enum and the lowering
conversion.

Assisted-by: Cursor / claude-opus-5
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  |  70 --------
 .../include/clang/CIR/Dialect/IR/CIRDialect.h |   1 +
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |   9 +-
 .../clang/CIR/Dialect/IR/CMakeLists.txt       |  14 +-
 .../clang/CIR/Interfaces/CIROpInterfaces.h    |   1 +
 .../clang/CIR/Interfaces/CIROpInterfaces.td   |   7 +-
 clang/include/clang/CIR/MissingFeatures.h     |   1 +
 clang/lib/CIR/CodeGen/CIRGenCall.cpp          |  19 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 167 ++++--------------
 clang/lib/CIR/Dialect/IR/CMakeLists.txt       |   1 +
 .../lib/CIR/Dialect/Transforms/CMakeLists.txt |   1 +
 .../CIR/Dialect/Transforms/CXXABILowering.cpp |   6 +
 clang/lib/CIR/Interfaces/CMakeLists.txt       |   1 +
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  38 +---
 clang/test/CIR/CodeGen/call.c                 |   4 +-
 clang/test/CIR/CodeGen/side-effect.cpp        |   8 +-
 clang/test/CIR/IR/call.cir                    |  38 ++--
 clang/test/CIR/IR/enum-attrs.cir              |  11 --
 .../test/CIR/IR/invalid-func-effect-attrs.cir |  21 +++
 clang/test/CIR/IR/invalid-memory-effects.cir  |  50 ------
 clang/test/CIR/Lowering/memory-effects.cir    |  23 ++-
 .../CIR/Transforms/flatten-preserve-attrs.cir |  16 +-
 .../test/CIR/Transforms/idiom-recognizer.cpp  |   2 +-
 23 files changed, 143 insertions(+), 366 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-func-effect-attrs.cir
 delete mode 100644 clang/test/CIR/IR/invalid-memory-effects.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index c2750840f5fd0..a4e0850ea3b5a 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1985,76 +1985,6 @@ def CIR_BlockAddrDiffAttr
   let canHaveIllegalCXXABIType = 0;
 }
 
-//===----------------------------------------------------------------------===//
-// Memory Effects
-//===----------------------------------------------------------------------===//
-
-def CIR_ModRefInfo : CIR_I32Enum<
-    "ModRefInfo", "how a function accesses one class of memory", [
-      I32EnumCase<"NoModRef", 0, "none">,
-      I32EnumCase<"Ref", 1, "read">,
-      I32EnumCase<"Mod", 2, "write">,
-      I32EnumCase<"ModRef", 3, "readwrite">
-]> {
-  let description = [{
-    Whether a function may read and/or write one class of memory.  The
-    spellings match LLVM's, so `none` means neither read nor written and
-    `readwrite` means both may happen.
-  }];
-}
-
-def CIR_MemoryEffectsAttr : CIR_Attr<"MemoryEffects", "memory_effects"> {
-  let summary = "What memory a function or callee may touch";
-  let description = [{
-    The memory a function or the target of a call may read and write, given
-    per class of memory so that a promise about one class does not have to be
-    a promise about all of them.
-
-    `other` covers every location not named by one of the remaining
-    parameters, so a function that only touches its pointer arguments has
-    `other = none` and `arg_mem = readwrite`.
-
-    An absent attribute means the effects are unknown, which is the
-    conservative default and what a function with no memory annotation gets.
-
-    On an operation the effects print as the access shared by every class not
-    named individually, followed by only the classes that differ from it.
-    The shared access is left out when it is `none` and some class does
-    differ, since an unnamed class reads back as `none`.
-
-    ```
-    cir.func private @a() -> !s32i memory(none)
-    cir.func private @b() -> !s32i memory(read)
-    cir.func private @c(!cir.ptr<!s32i>) memory(argmem: readwrite)
-    cir.func private @d(!cir.ptr<!s32i>) memory(read, argmem: readwrite)
-    %2 = cir.call @a() memory(none) : () -> !s32i
-    ```
-  }];
-
-  let parameters = (ins "ModRefInfo":$other, "ModRefInfo":$arg_mem,
-                        "ModRefInfo":$inaccessible_mem,
-                        "ModRefInfo":$errno_mem, "ModRefInfo":$target_mem0,
-                        "ModRefInfo":$target_mem1);
-
-  let builders = [
-    AttrBuilder<(ins "ModRefInfo":$all), [{
-      return $_get($_ctxt, all, all, all, all, all, all);
-    }]>
-  ];
-
-  let extraClassDeclaration = [{
-    /// Whether every class of memory carries the same access as `other`.
-    bool isUniform() const {
-      return getArgMem() == getOther() && getInaccessibleMem() == getOther() &&
-             getErrnoMem() == getOther() && getTargetMem0() == getOther() &&
-             getTargetMem1() == getOther();
-    }
-  }];
-
-  let assemblyFormat = "`<` struct(params) `>`";
-  let canHaveIllegalCXXABIType = 0;
-}
-
 //===----------------------------------------------------------------------===//
 // StaticLocalGuardAttr
 //===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index d9018e93628b6..b5a556042c278 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -13,6 +13,7 @@
 #ifndef CLANG_CIR_DIALECT_IR_CIRDIALECT_H
 #define CLANG_CIR_DIALECT_IR_CIRDIALECT_H
 
+#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/BuiltinTypes.h"
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 10e23e742bbbc..c9a08f959bdb2 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -22,6 +22,7 @@ include "clang/CIR/Dialect/IR/CIRAttrConstraints.td"
 include "clang/CIR/Interfaces/CIROpInterfaces.td"
 include "clang/CIR/Interfaces/CIRLoopOpInterface.td"
 
+include "mlir/Dialect/LLVMIR/LLVMAttrDefs.td"
 include "mlir/IR/BuiltinAttributeInterfaces.td"
 include "mlir/IR/EnumAttr.td"
 include "mlir/IR/SymbolInterfaces.td"
@@ -4296,7 +4297,7 @@ def CIR_FuncOp : CIR_Op<"func", [
     OptionalAttr<DictArrayAttr>:$arg_attrs,
     OptionalAttr<DictArrayAttr>:$res_attrs,
     OptionalAttr<FlatSymbolRefAttr>:$aliasee,
-    OptionalAttr<CIR_MemoryEffectsAttr>:$memory_effects,
+    OptionalAttr<LLVM_MemoryEffectsAttr>:$memory_effects,
     OptionalAttr<FlatSymbolRefAttr>:$personality,
     CIR_OptionalPriorityAttr:$global_ctor_priority,
     CIR_OptionalPriorityAttr:$global_dtor_priority,
@@ -4602,7 +4603,7 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []>
       UnitAttr:$willreturn,
       OptionalAttr<CIR_InlineKindAttr>:$inline_kind,
       UnitAttr:$musttail,
-      OptionalAttr<CIR_MemoryEffectsAttr>:$memory_effects,
+      OptionalAttr<LLVM_MemoryEffectsAttr>:$memory_effects,
       OptionalAttr<DictArrayAttr>:$arg_attrs,
       OptionalAttr<DictArrayAttr>:$res_attrs
       );
@@ -4698,7 +4699,7 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
                "mlir::Block *":$normalDest,
                "mlir::Block *":$unwindDest,
                CArg<"mlir::ValueRange", "{}">:$callOperands,
-               CArg<"cir::MemoryEffectsAttr", "{}">:$memoryEffects), [{
+               CArg<"mlir::LLVM::MemoryEffectsAttr", "{}">:$memoryEffects), [{
       $_state.addOperands(callOperands);
 
       if (callee)
@@ -4718,7 +4719,7 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
                "mlir::Block *":$normalDest,
                "mlir::Block *":$unwindDest,
                CArg<"mlir::ValueRange", "{}">:$callOperands,
-               CArg<"cir::MemoryEffectsAttr", "{}">:$memoryEffects), [{
+               CArg<"mlir::LLVM::MemoryEffectsAttr", "{}">:$memoryEffects), [{
       ::llvm::SmallVector<mlir::Value, 4> finalCallOperands({ind_target});
       finalCallOperands.append(callOperands.begin(), callOperands.end());
       $_state.addOperands(finalCallOperands);
diff --git a/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt b/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt
index 8add2f04afaf7..30b0a77decf9e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt
+++ b/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt
@@ -7,20 +7,20 @@
 set(LLVM_TARGET_DEFINITIONS CIROps.td)
 mlir_tablegen(CIROps.h.inc -gen-op-decls)
 mlir_tablegen(CIROps.cpp.inc -gen-op-defs)
-mlir_tablegen(CIROpsTypes.h.inc -gen-typedef-decls)
-mlir_tablegen(CIROpsTypes.cpp.inc -gen-typedef-defs)
-mlir_tablegen(CIROpsDialect.h.inc -gen-dialect-decls)
-mlir_tablegen(CIROpsDialect.cpp.inc -gen-dialect-defs)
+mlir_tablegen(CIROpsTypes.h.inc -gen-typedef-decls -typedefs-dialect=cir)
+mlir_tablegen(CIROpsTypes.cpp.inc -gen-typedef-defs -typedefs-dialect=cir)
+mlir_tablegen(CIROpsDialect.h.inc -gen-dialect-decls -dialect=cir)
+mlir_tablegen(CIROpsDialect.cpp.inc -gen-dialect-defs -dialect=cir)
 add_public_tablegen_target(MLIRCIROpsIncGen)
 add_dependencies(mlir-headers MLIRCIROpsIncGen)
 
 # Equivalent to add_mlir_doc
-add_clang_mlir_doc(CIROps CIRDialect ./ -gen-dialect-doc)
+add_clang_mlir_doc(CIROps CIRDialect ./ -gen-dialect-doc -dialect=cir)
 
 mlir_tablegen(CIROpsEnums.h.inc -gen-enum-decls)
 mlir_tablegen(CIROpsEnums.cpp.inc -gen-enum-defs)
-mlir_tablegen(CIROpsAttributes.h.inc -gen-attrdef-decls)
-mlir_tablegen(CIROpsAttributes.cpp.inc -gen-attrdef-defs)
+mlir_tablegen(CIROpsAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=cir)
+mlir_tablegen(CIROpsAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=cir)
 add_public_tablegen_target(MLIRCIREnumsGen)
 
 clang_tablegen(CIRLowering.inc -gen-cir-lowering
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h
index 830b64f488feb..2e613c88317c6 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h
@@ -13,6 +13,7 @@
 #ifndef CLANG_CIR_INTERFACES_CIR_OP_H
 #define CLANG_CIR_INTERFACES_CIR_OP_H
 
+#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
 #include "mlir/IR/Attributes.h"
 #include "mlir/IR/Operation.h"
 #include "mlir/IR/Value.h"
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index a759e9c147b44..18c4fe0ab53f5 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -43,8 +43,11 @@ let cppNamespace = "::cir" in {
           "mlir::Value", "getIndirectCall", (ins)>,
       InterfaceMethod<"Return whether the callee is nothrow",
                       "bool", "getNothrow", (ins)>,
-      InterfaceMethod<"Return the memory effects of the call operation",
-                      "cir::MemoryEffectsAttr", "getMemoryEffectsAttr", (ins)>,
+      InterfaceMethod<
+          "Return the memory effects of the call operation, or null when it "
+          "declares none.  Null means the effects are unknown, not that the "
+          "callee touches no memory",
+          "mlir::LLVM::MemoryEffectsAttr", "getMemoryEffectsAttr", (ins)>,
       InterfaceMethod<[{"Set the inline-kind of a call operation"}],
                       "void", "setInlineKind", 
                       (ins "std::optional<cir::InlineKind>":$kind), [{}], 
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 16d97871ec7b6..19e005aba2c8c 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -93,6 +93,7 @@ struct MissingFeatures {
   static bool opCallThunkTailHint() { return false; }
   static bool opCallInAlloca() { return false; }
   static bool opCallAttrs() { return false; }
+  static bool opCallInvokeAttrs() { return false; }
   static bool opCallSurroundingTry() { return false; }
   static bool opCallObjCMethod() { return false; }
   static bool opCallExtParameterInfo() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 30e46129c5c13..e7a08b39fcc4c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -389,19 +389,24 @@ void CIRGenModule::constructAttributeList(
 
     assert(!cir::MissingFeatures::opCallAttrs());
 
-    // 'const' and 'pure' imply more than memory effects: the callee also
-    // cannot unwind and cannot loop forever.  Each gets its own attribute,
-    // since none of the three can be derived from another.
-    std::optional<cir::ModRefInfo> access;
+    std::optional<mlir::LLVM::ModRefInfo> access;
     if (targetDecl->hasAttr<ConstAttr>())
-      access = cir::ModRefInfo::NoModRef;
+      access = mlir::LLVM::ModRefInfo::NoModRef;
     else if (targetDecl->hasAttr<PureAttr>())
-      access = cir::ModRefInfo::Ref;
+      access = mlir::LLVM::ModRefInfo::Ref;
 
     if (access) {
+      // 'const' and 'pure' describe the callee as a whole, so every class of
+      // memory carries the same access.
       attrs.set(cir::CIRDialect::getMemoryEffectsAttrName(),
-                cir::MemoryEffectsAttr::get(&getMLIRContext(), *access));
+                mlir::LLVM::MemoryEffectsAttr::get(
+                    &getMLIRContext(), /*other=*/*access, /*argMem=*/*access,
+                    /*inaccessibleMem=*/*access, /*errnoMem=*/*access,
+                    /*targetMem0=*/*access, /*targetMem1=*/*access));
+      // 'const' and 'pure' attributed functions are also nounwind.
       addUnitAttr(cir::CIRDialect::getNoUnwindAttrName());
+      // gcc specifies that 'const' and 'pure' functions cannot have infinite
+      // loops.
       addUnitAttr(cir::CIRDialect::getWillReturnAttrName());
     }
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index c76f0a4f31faa..6ace68521fb4d 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -302,7 +302,6 @@ template <typename Ty> struct EnumTraits {};
 
 REGISTER_ENUM_TYPE(GlobalLinkageKind);
 REGISTER_ENUM_TYPE(VisibilityKind);
-REGISTER_ENUM_TYPE(ModRefInfo);
 REGISTER_ENUM_TYPE(CallingConv);
 } // namespace
 
@@ -335,111 +334,6 @@ static ParseResult parseCIRKeyword(AsmParser &parser, RetTy &result) {
   return success();
 }
 
-/// The memory classes that can be named individually, in the order they
-/// print.  Positional: parseMemoryEffects and printMemoryEffects both index
-/// by position, so a class added here needs an access added alongside it in
-/// both.  The spellings are LLVM's own, which underscores the target pair
-/// but not the rest.
-static constexpr llvm::StringLiteral memoryClassNames[] = {
-    "argmem", "inaccessiblemem", "errnomem", "target_mem0", "target_mem1"};
-
-/// Print memory effects in LLVM's compact form, except that the two target
-/// classes always print by name rather than collapsing to `target_mem`.  The
-/// access covering every class not named individually comes first, then only
-/// the classes differing from it.  That leading access is left out when it is
-/// `none` and some class does differ, since an unnamed class reads back as
-/// `none`.
-static void printMemoryEffects(mlir::AsmPrinter &printer,
-                               cir::MemoryEffectsAttr effects) {
-  cir::ModRefInfo other = effects.getOther();
-  printer << " memory(";
-  if (effects.isUniform()) {
-    printer << cir::stringifyModRefInfo(other) << ")";
-    return;
-  }
-
-  bool needComma = false;
-  if (other != cir::ModRefInfo::NoModRef) {
-    printer << cir::stringifyModRefInfo(other);
-    needComma = true;
-  }
-
-  cir::ModRefInfo classes[] = {
-      effects.getArgMem(), effects.getInaccessibleMem(), effects.getErrnoMem(),
-      effects.getTargetMem0(), effects.getTargetMem1()};
-  for (auto [name, info] : llvm::zip_equal(memoryClassNames, classes)) {
-    if (info == other)
-      continue;
-    if (needComma)
-      printer << ", ";
-    printer << name << ": " << cir::stringifyModRefInfo(info);
-    needComma = true;
-  }
-  printer << ")";
-}
-
-/// Parse the compact form printed by printMemoryEffects.  The opening
-/// `memory` keyword has already been consumed.
-static mlir::ParseResult parseMemoryEffects(mlir::AsmParser &parser,
-                                            cir::MemoryEffectsAttr &result) {
-  if (parser.parseLParen().failed())
-    return failure();
-
-  // A class left unnamed inherits `other`, which is `none` unless the spelling
-  // opens with a bare access.
-  cir::ModRefInfo other = cir::ModRefInfo::NoModRef;
-  std::array<std::optional<cir::ModRefInfo>, std::size(memoryClassNames)> named;
-
-  // Record one named class, with its name and colon already consumed.
-  auto parseClass = [&](llvm::SMLoc loc, llvm::StringRef name) -> ParseResult {
-    const auto *entry = llvm::find(memoryClassNames, name);
-    if (entry == std::end(memoryClassNames))
-      return parser.emitError(loc, "unknown memory class '") << name << "'";
-    llvm::SMLoc accessLoc = parser.getCurrentLocation();
-    cir::ModRefInfo info;
-    if (parseCIRKeyword<cir::ModRefInfo>(parser, info).failed())
-      return parser.emitError(accessLoc, "expected a memory access kind");
-    std::optional<cir::ModRefInfo> &slot =
-        named[entry - std::begin(memoryClassNames)];
-    if (slot)
-      return parser.emitError(loc, "duplicate memory class '") << name << "'";
-    slot = info;
-    return success();
-  };
-
-  llvm::SMLoc firstLoc = parser.getCurrentLocation();
-  llvm::StringRef first;
-  if (parser.parseKeyword(&first).failed())
-    return failure();
-
-  if (parser.parseOptionalColon().succeeded()) {
-    if (parseClass(firstLoc, first).failed())
-      return failure();
-  } else if (std::optional<cir::ModRefInfo> parsed =
-                 cir::symbolizeModRefInfo(first)) {
-    other = *parsed;
-  } else {
-    return parser.emitError(firstLoc, "expected a memory access kind or class");
-  }
-
-  while (parser.parseOptionalComma().succeeded()) {
-    llvm::SMLoc loc = parser.getCurrentLocation();
-    llvm::StringRef name;
-    if (parser.parseKeyword(&name).failed() || parser.parseColon().failed() ||
-        parseClass(loc, name).failed())
-      return failure();
-  }
-
-  if (parser.parseRParen().failed())
-    return failure();
-
-  result = cir::MemoryEffectsAttr::get(
-      parser.getContext(), other, named[0].value_or(other),
-      named[1].value_or(other), named[2].value_or(other),
-      named[3].value_or(other), named[4].value_or(other));
-  return success();
-}
-
 // Check if a region's termination omission is valid and, if so, creates and
 // inserts the omitted terminator into the region.
 static LogicalResult ensureRegionTerm(OpAsmParser &parser, Region &region,
@@ -1463,13 +1357,6 @@ static mlir::ParseResult parseCallCommon(mlir::OpAsmParser &parser,
     result.addAttribute(CIRDialect::getWillReturnAttrName(),
                         mlir::UnitAttr::get(parser.getContext()));
 
-  if (parser.parseOptionalKeyword("memory").succeeded()) {
-    cir::MemoryEffectsAttr effects;
-    if (parseMemoryEffects(parser, effects).failed())
-      return failure();
-    result.addAttribute(CIRDialect::getMemoryEffectsAttrName(), effects);
-  }
-
   if (parser.parseOptionalAttrDict(result.attributes))
     return ::mlir::failure();
 
@@ -1529,7 +1416,6 @@ static void printCallCommon(mlir::Operation *op,
                             mlir::FlatSymbolRefAttr calleeSym,
                             mlir::Value indirectCallee,
                             mlir::OpAsmPrinter &printer, bool isNothrow,
-                            cir::MemoryEffectsAttr memoryEffects,
                             ArrayAttr argAttrs, ArrayAttr resAttrs,
                             mlir::Block *normalDest = nullptr,
                             mlir::Block *unwindDest = nullptr) {
@@ -1570,16 +1456,12 @@ static void printCallCommon(mlir::Operation *op,
   if (op->hasAttr(CIRDialect::getWillReturnAttrName()))
     printer << " willreturn";
 
-  if (memoryEffects)
-    printMemoryEffects(printer, memoryEffects);
-
   llvm::SmallVector<::llvm::StringRef> elidedAttrs = {
       CIRDialect::getCalleeAttrName(),
       CIRDialect::getMustTailAttrName(),
       CIRDialect::getNoThrowAttrName(),
       CIRDialect::getNoUnwindAttrName(),
       CIRDialect::getWillReturnAttrName(),
-      CIRDialect::getMemoryEffectsAttrName(),
       CIRDialect::getOperandSegmentSizesAttrName(),
       llvm::StringRef("res_attrs"),
       llvm::StringRef("arg_attrs")};
@@ -1612,9 +1494,8 @@ mlir::ParseResult cir::CallOp::parse(mlir::OpAsmParser &parser,
 
 void cir::CallOp::print(mlir::OpAsmPrinter &p) {
   mlir::Value indirectCallee = isIndirect() ? getIndirectCall() : nullptr;
-  cir::MemoryEffectsAttr memoryEffects = getMemoryEffectsAttr();
   printCallCommon(*this, getCalleeAttr(), indirectCallee, p, getNothrow(),
-                  memoryEffects, getArgAttrsAttr(), getResAttrsAttr());
+                  getArgAttrsAttr(), getResAttrsAttr());
 }
 
 static LogicalResult
@@ -1728,10 +1609,9 @@ mlir::ParseResult cir::TryCallOp::parse(mlir::OpAsmParser &parser,
 
 void cir::TryCallOp::print(::mlir::OpAsmPrinter &p) {
   mlir::Value indirectCallee = isIndirect() ? getIndirectCall() : nullptr;
-  cir::MemoryEffectsAttr memoryEffects = getMemoryEffectsAttr();
   printCallCommon(*this, getCalleeAttr(), indirectCallee, p, getNothrow(),
-                  memoryEffects, getArgAttrsAttr(), getResAttrsAttr(),
-                  getNormalDest(), getUnwindDest());
+                  getArgAttrsAttr(), getResAttrsAttr(), getNormalDest(),
+                  getUnwindDest());
 }
 
 //===----------------------------------------------------------------------===//
@@ -2907,13 +2787,6 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
       }).failed())
     return failure();
 
-  if (parser.parseOptionalKeyword("memory").succeeded()) {
-    cir::MemoryEffectsAttr effects;
-    if (parseMemoryEffects(parser, effects).failed())
-      return failure();
-    state.addAttribute(CIRDialect::getMemoryEffectsAttrName(), effects);
-  }
-
   // Parse optional annotations attribute (an ArrayAttr of AnnotationAttr).
   mlir::StringAttr annotationsNameAttr = getAnnotationsAttrName(state.name);
   mlir::ArrayAttr annotationsAttr;
@@ -2926,13 +2799,35 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
   if (parser.parseOptionalAttrDictWithKeyword(parsedAttrs))
     return failure();
 
+  // memory_effects is the only declared attribute with no dedicated syntax, so
+  // it is the only one the explicit list may carry.
   for (StringRef disallowed : cir::FuncOp::getAttributeNames()) {
+    if (disallowed == CIRDialect::getMemoryEffectsAttrName())
+      continue;
     if (parsedAttrs.get(disallowed))
       return parser.emitError(loc, "attribute '")
              << disallowed
              << "' should not be specified in the explicit attribute list";
   }
 
+  // Being a declared attribute, memory_effects is stored as a property, so a
+  // value of the wrong kind would be dropped without a diagnostic.
+  if (mlir::Attribute effects =
+          parsedAttrs.get(CIRDialect::getMemoryEffectsAttrName()))
+    if (!mlir::isa<mlir::LLVM::MemoryEffectsAttr>(effects))
+      return parser.emitError(loc, "attribute '")
+             << CIRDialect::getMemoryEffectsAttrName()
+             << "' must be a #llvm.memory_effects attribute";
+
+  // These two are discardable on a function, so nothing else checks that they
+  // carry the unit value the passes testing for their presence assume.
+  for (llvm::StringRef name :
+       {CIRDialect::getNoUnwindAttrName(), CIRDialect::getWillReturnAttrName()})
+    if (mlir::Attribute flag = parsedAttrs.get(name))
+      if (!mlir::isa<mlir::UnitAttr>(flag))
+        return parser.emitError(loc, "attribute '")
+               << name << "' must be a unit attribute";
+
   state.attributes.append(parsedAttrs);
 
   // Parse the optional function body.
@@ -3098,16 +2993,18 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
       p << "(" << globalDtorPriority.value() << ")";
   }
 
-  if (cir::MemoryEffectsAttr memoryEffects = getMemoryEffectsAttr())
-    printMemoryEffects(p, memoryEffects);
-
   if (mlir::ArrayAttr annotations = getAnnotationsAttr()) {
     p << ' ';
     p.printAttribute(annotations);
   }
 
-  function_interface_impl::printFunctionAttributes(
-      p, *this, cir::FuncOp::getAttributeNames());
+  // Every declared attribute is printed by the syntax above, except
+  // memory_effects, which has none and so must reach the dictionary.
+  llvm::SmallVector<llvm::StringRef> elidedAttrs;
+  for (llvm::StringRef name : cir::FuncOp::getAttributeNames())
+    if (name != CIRDialect::getMemoryEffectsAttrName())
+      elidedAttrs.push_back(name);
+  function_interface_impl::printFunctionAttributes(p, *this, elidedAttrs);
 
   // Print the body if this is not an external function.
   Region &body = getOperation()->getRegion(0);
diff --git a/clang/lib/CIR/Dialect/IR/CMakeLists.txt b/clang/lib/CIR/Dialect/IR/CMakeLists.txt
index 0274005a28858..d89d7ee2c91b8 100644
--- a/clang/lib/CIR/Dialect/IR/CMakeLists.txt
+++ b/clang/lib/CIR/Dialect/IR/CMakeLists.txt
@@ -16,6 +16,7 @@ add_clang_library(MLIRCIR
   LINK_LIBS PUBLIC
   MLIRIR
   MLIRCIRInterfaces
+  MLIRLLVMDialect
   MLIRDLTIDialect
   MLIRDataLayoutInterfaces
   MLIRFuncDialect
diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
index 82078bf2e8f73..dc9638d53bdec 100644
--- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
+++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
@@ -27,6 +27,7 @@ add_clang_library(MLIRCIRTransforms
   MLIRAnalysis
   MLIRDLTIDialect
   MLIRIR
+  MLIRLLVMDialect
   MLIRPass
   MLIRTransformUtils
 
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index b9c3cba8be954..1698781a64bdd 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -66,6 +66,12 @@ bool isCXXABIAttributeLegal(const mlir::TypeConverter &tc,
           >(attr))
     return true;
 
+  // Memory effects come from the LLVM dialect and hold nothing but ModRefInfo
+  // enums, so no CIR type can reach them.  Attributes declared in CIR get this
+  // through canHaveIllegalCXXABIType, which is not available here.
+  if (isa<mlir::LLVM::MemoryEffectsAttr>(attr))
+    return true;
+
   // Data Member and method are ALWAYS illegal.
   if (isa<cir::DataMemberAttr, cir::DataMemberOffsetAttr, cir::MethodAttr>(
           attr))
diff --git a/clang/lib/CIR/Interfaces/CMakeLists.txt b/clang/lib/CIR/Interfaces/CMakeLists.txt
index 43a35d2516e83..21b7118bffe8b 100644
--- a/clang/lib/CIR/Interfaces/CMakeLists.txt
+++ b/clang/lib/CIR/Interfaces/CMakeLists.txt
@@ -26,6 +26,7 @@ add_clang_library(MLIRCIRInterfaces
 
   LINK_LIBS
   MLIRIR
+  MLIRLLVMDialect
   MLIRPtrMemorySpaceInterfaces
   MLIRSupport
  )
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 530c882ee682c..893758666b87a 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -430,41 +430,12 @@ mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp,
   return value;
 }
 
-static mlir::LLVM::ModRefInfo convertModRefInfo(cir::ModRefInfo info) {
-  switch (info) {
-  case cir::ModRefInfo::NoModRef:
-    return mlir::LLVM::ModRefInfo::NoModRef;
-  case cir::ModRefInfo::Ref:
-    return mlir::LLVM::ModRefInfo::Ref;
-  case cir::ModRefInfo::Mod:
-    return mlir::LLVM::ModRefInfo::Mod;
-  case cir::ModRefInfo::ModRef:
-    return mlir::LLVM::ModRefInfo::ModRef;
-  }
-  llvm_unreachable("unhandled cir::ModRefInfo");
-}
-
-/// A null input stays null, which is how both dialects spell unknown effects.
-static mlir::LLVM::MemoryEffectsAttr
-convertMemoryEffects(mlir::MLIRContext *ctx, cir::MemoryEffectsAttr effects) {
-  if (!effects)
-    return {};
-
-  return mlir::LLVM::MemoryEffectsAttr::get(
-      ctx, convertModRefInfo(effects.getOther()),
-      convertModRefInfo(effects.getArgMem()),
-      convertModRefInfo(effects.getInaccessibleMem()),
-      convertModRefInfo(effects.getErrnoMem()),
-      convertModRefInfo(effects.getTargetMem0()),
-      convertModRefInfo(effects.getTargetMem1()));
-}
-
 static void convertCallEffects(mlir::Operation *callOp, bool isNothrow,
-                               cir::MemoryEffectsAttr effects,
+                               mlir::LLVM::MemoryEffectsAttr effects,
                                mlir::LLVM::MemoryEffectsAttr &memoryEffect,
                                bool &noUnwind, bool &willReturn,
                                bool &noReturn) {
-  memoryEffect = convertMemoryEffects(callOp->getContext(), effects);
+  memoryEffect = effects;
   // CIR keeps two separate cannot-unwind facts, nothrow for a callee declared
   // not to throw and nounwind for a const or pure callee.  LLVM has only
   // nounwind, so either one sets it.
@@ -2269,6 +2240,7 @@ static mlir::LogicalResult rewriteCallOrInvoke(
   assert(!cir::MissingFeatures::opCallCallConv());
 
   if (landingPadBlock) {
+    assert(!cir::MissingFeatures::opCallInvokeAttrs());
     auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::InvokeOp>(
         op, llvmFnTy, calleeAttr, callOperands, continueBlock,
         mlir::ValueRange{}, landingPadBlock, mlir::ValueRange{});
@@ -2814,8 +2786,8 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite(
 
   assert(!cir::MissingFeatures::opFuncMultipleReturnVals());
 
-  if (cir::MemoryEffectsAttr effects = op.getMemoryEffectsAttr())
-    fn.setMemoryEffectsAttr(convertMemoryEffects(fn.getContext(), effects));
+  if (mlir::LLVM::MemoryEffectsAttr effects = op.getMemoryEffectsAttr())
+    fn.setMemoryEffectsAttr(effects);
 
   if (op->hasAttr(CIRDialect::getNoUnwindAttrName()))
     fn.setNoUnwind(true);
diff --git a/clang/test/CIR/CodeGen/call.c b/clang/test/CIR/CodeGen/call.c
index 2653ba9845718..65bb9dc64b2bb 100644
--- a/clang/test/CIR/CodeGen/call.c
+++ b/clang/test/CIR/CodeGen/call.c
@@ -150,9 +150,9 @@ int f12(void) {
 
 // CIR-LABEL: cir.func{{.*}} @f12() -> !s32i{{.*}} {
 // CIR:         %[[A:.+]] = cir.const #cir.int<1> : !s32i
-// CIR-NEXT:    %{{.+}} = cir.call @f10(%[[A]]) nounwind willreturn memory(read) : (!s32i {llvm.noundef}) -> !s32i
+// CIR-NEXT:    %{{.+}} = cir.call @f10(%[[A]]) nounwind willreturn {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : (!s32i {llvm.noundef}) -> !s32i
 // CIR-NEXT:    %[[B:.+]] = cir.const #cir.int<2> : !s32i
-// CIR-NEXT:    %{{.+}} = cir.call @f11(%[[B]]) nounwind willreturn memory(none) : (!s32i {llvm.noundef}) -> !s32i
+// CIR-NEXT:    %{{.+}} = cir.call @f11(%[[B]]) nounwind willreturn {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : (!s32i {llvm.noundef}) -> !s32i
 
 // LLVM-LABEL: define{{.*}} i32 @f12(){{.*}}
 // LLVM:         %{{.+}} = call i32 @f10(i32 noundef 1) #[[ATTR0:.+]]
diff --git a/clang/test/CIR/CodeGen/side-effect.cpp b/clang/test/CIR/CodeGen/side-effect.cpp
index d5193430f5afe..5383a93a023a7 100644
--- a/clang/test/CIR/CodeGen/side-effect.cpp
+++ b/clang/test/CIR/CodeGen/side-effect.cpp
@@ -8,7 +8,7 @@ extern "C" {
 
 // FIXME: We should figure out how to better print this on functions in the
 // future.
-// CIR: cir.func{{.*}}@pure_func() -> !s32i memory(read) attributes {{{.*}}nothrow, nounwind, willreturn} {
+// CIR: cir.func{{.*}}@pure_func() -> !s32i attributes {{{.*}}nothrow, nounwind, willreturn, memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} {
 // LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read)
 // LLVM: define{{.*}} @pure_func() #{{.*}} {
 // OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read)
@@ -16,7 +16,7 @@ extern "C" {
 __attribute__((pure))
 int pure_func() { return 2;}
 
-// CIR: cir.func{{.*}}@const_func() -> !s32i memory(none) attributes {{{.*}}nothrow, nounwind, willreturn} {
+// CIR: cir.func{{.*}}@const_func() -> !s32i attributes {{{.*}}nothrow, nounwind, willreturn, memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} {
 // LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none)
 // LLVM: define{{.*}} @const_func() #{{.*}} {
 // OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none)
@@ -25,11 +25,11 @@ __attribute__((const))
 int const_func() { return 1;}
 
 void use() {
-  // CIR: cir.call @pure_func() nounwind willreturn memory(read) : () -> !s32i
+  // CIR: cir.call @pure_func() nounwind willreturn {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
   // LLVM: call i32 @pure_func() #[[PURE_ATTR:.*]]
   // OGCG: call i32 @pure_func() #[[PURE_ATTR:.*]]
   pure_func();
-  // CIR: cir.call @const_func() nounwind willreturn memory(none) : () -> !s32i
+  // CIR: cir.call @const_func() nounwind willreturn {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : () -> !s32i
   // LLVM: call i32 @const_func() #[[CONST_ATTR:.*]]
   // OGCG: call i32 @const_func() #[[CONST_ATTR:.*]]
   const_func();
diff --git a/clang/test/CIR/IR/call.cir b/clang/test/CIR/IR/call.cir
index 0c843b78023d1..a2de23114e6d0 100644
--- a/clang/test/CIR/IR/call.cir
+++ b/clang/test/CIR/IR/call.cir
@@ -8,41 +8,33 @@ cir.func private @f1()
 
 cir.func @f2() {
   cir.call @f1() : () -> ()
-  cir.call @f1() memory(read) : () -> ()
-  cir.call @f1() memory(none) : () -> ()
   cir.call @f1() nounwind : () -> ()
   cir.call @f1() willreturn : () -> ()
-  cir.call @f1() nothrow nounwind willreturn memory(none) : () -> ()
-  cir.call @f1() memory(write) : () -> ()
-  cir.call @f1() memory(readwrite) : () -> ()
-  cir.call @f1() memory(argmem: readwrite) : () -> ()
-  cir.call @f1() memory(read, argmem: readwrite) : () -> ()
-  cir.call @f1() memory(inaccessiblemem: write, errnomem: read) : () -> ()
-  cir.call @f1() memory(argmem: read, inaccessiblemem: write) : () -> ()
-  cir.call @f1() memory(readwrite, target_mem0: read, target_mem1: none) : () -> ()
+  cir.call @f1() {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> ()
+  cir.call @f1() nothrow nounwind willreturn {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : () -> ()
+  cir.call @f1() {memory_effects = #llvm.memory_effects<other = read, argMem = readwrite, inaccessibleMem = write, errnoMem = read, targetMem0 = read, targetMem1 = none>} : () -> ()
   cir.return
 }
 
 // CHECK:      cir.func{{.*}} @f2() {
 // CHECK-NEXT:   cir.call @f1() : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(read) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(none) : () -> ()
 // CHECK-NEXT:   cir.call @f1() nounwind : () -> ()
 // CHECK-NEXT:   cir.call @f1() willreturn : () -> ()
-// CHECK-NEXT:   cir.call @f1() nothrow nounwind willreturn memory(none) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(write) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(readwrite) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(argmem: readwrite) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(read, argmem: readwrite) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(inaccessiblemem: write, errnomem: read) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(argmem: read, inaccessiblemem: write) : () -> ()
-// CHECK-NEXT:   cir.call @f1() memory(readwrite, target_mem0: read, target_mem1: none) : () -> ()
+// CHECK-NEXT:   cir.call @f1() {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> ()
+// CHECK-NEXT:   cir.call @f1() nothrow nounwind willreturn {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : () -> ()
+// CHECK-NEXT:   cir.call @f1() {memory_effects = #llvm.memory_effects<other = read, argMem = readwrite, inaccessibleMem = write, errnoMem = read, targetMem0 = read, targetMem1 = none>} : () -> ()
 // CHECK-NEXT:   cir.return
 // CHECK-NEXT: }
 
-// cir.func parses memory effects separately from cir.call.
-// CHECK: cir.func private @f8() memory(read, argmem: readwrite)
-cir.func private @f8() memory(read, argmem: readwrite)
+// CHECK: cir.func private @f8() attributes {memory_effects = #llvm.memory_effects<other = read, argMem = readwrite, inaccessibleMem = write, errnoMem = read, targetMem0 = read, targetMem1 = none>}
+cir.func private @f8() attributes {memory_effects = #llvm.memory_effects<other = read, argMem = readwrite, inaccessibleMem = write, errnoMem = read, targetMem0 = read, targetMem1 = none>}
+
+// CHECK:      cir.func{{.*}} @f9() attributes {nothrow, nounwind, willreturn, memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} {
+// CHECK-NEXT:   cir.return
+// CHECK-NEXT: }
+cir.func @f9() attributes {nothrow, nounwind, willreturn, memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} {
+  cir.return
+}
 
 cir.func private @f3() -> !s32i
 
diff --git a/clang/test/CIR/IR/enum-attrs.cir b/clang/test/CIR/IR/enum-attrs.cir
index 2dddf4c3c6724..6455564cab793 100644
--- a/clang/test/CIR/IR/enum-attrs.cir
+++ b/clang/test/CIR/IR/enum-attrs.cir
@@ -120,17 +120,6 @@ cir.func @calling_conv_attr() {
                           #cir.calling_conv<amdgpu_kernel>]}
 }
 
-// CHECK-LABEL: cir.func @memory_effects_attr() {
-cir.func @memory_effects_attr() {
-  // CHECK: cir.return {cir.test = [#cir.memory_effects<other = none, arg_mem = none, inaccessible_mem = none, errno_mem = none, target_mem0 = none, target_mem1 = none>, #cir.memory_effects<other = read, arg_mem = readwrite, inaccessible_mem = write, errno_mem = read, target_mem0 = read, target_mem1 = read>]}
-  cir.return {cir.test = [#cir.memory_effects<other = none, arg_mem = none,
-                            inaccessible_mem = none, errno_mem = none,
-                            target_mem0 = none, target_mem1 = none>,
-                          #cir.memory_effects<other = read, arg_mem = readwrite,
-                            inaccessible_mem = write, errno_mem = read,
-                            target_mem0 = read, target_mem1 = read>]}
-}
-
 // A bit enum, so a value can name several flags.
 
 // CHECK-LABEL: cir.func @fp_class_attr() {
diff --git a/clang/test/CIR/IR/invalid-func-effect-attrs.cir b/clang/test/CIR/IR/invalid-func-effect-attrs.cir
new file mode 100644
index 0000000000000..d0245f341061a
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-func-effect-attrs.cir
@@ -0,0 +1,21 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+// expected-error @below {{custom op 'cir.func' attribute 'memory_effects' must be a #llvm.memory_effects attribute}}
+cir.func private @wrong_memory_effects_kind() attributes {memory_effects = 42 : i32}
+
+// -----
+
+// expected-error @below {{custom op 'cir.func' attribute 'memory_effects' must be a #llvm.memory_effects attribute}}
+cir.func @wrong_memory_effects_kind_with_body() attributes {memory_effects = "readwrite"} {
+  cir.return
+}
+
+// -----
+
+// expected-error @below {{custom op 'cir.func' attribute 'nounwind' must be a unit attribute}}
+cir.func private @wrong_nounwind_kind() attributes {nounwind = 42 : i32}
+
+// -----
+
+// expected-error @below {{custom op 'cir.func' attribute 'willreturn' must be a unit attribute}}
+cir.func private @wrong_willreturn_kind() attributes {willreturn = "yes"}
diff --git a/clang/test/CIR/IR/invalid-memory-effects.cir b/clang/test/CIR/IR/invalid-memory-effects.cir
deleted file mode 100644
index a1db96e254a5d..0000000000000
--- a/clang/test/CIR/IR/invalid-memory-effects.cir
+++ /dev/null
@@ -1,50 +0,0 @@
-// RUN: cir-opt %s -verify-diagnostics -split-input-file
-
-cir.func private @f1()
-cir.func @bare_class_name() {
-  // expected-error @below {{expected a memory access kind or class}}
-  cir.call @f1() memory(argmem) : () -> ()
-  cir.return
-}
-
-// -----
-
-cir.func private @f1()
-cir.func @bad_named_access() {
-  // expected-error @below {{expected a memory access kind}}
-  cir.call @f1() memory(argmem: bogus) : () -> ()
-  cir.return
-}
-
-// -----
-
-cir.func private @f1()
-cir.func @duplicate_class() {
-  // expected-error @below {{duplicate memory class 'argmem'}}
-  cir.call @f1() memory(argmem: read, argmem: write) : () -> ()
-  cir.return
-}
-
-// -----
-
-cir.func private @f1()
-cir.func @unknown_class() {
-  // expected-error @below {{unknown memory class 'stackmem'}}
-  cir.call @f1() memory(stackmem: read) : () -> ()
-  cir.return
-}
-
-// -----
-
-cir.func private @f1()
-cir.func @unknown_class_after_shared_access() {
-  // expected-error @below {{unknown memory class 'stackmem'}}
-  cir.call @f1() memory(read, stackmem: read) : () -> ()
-  cir.return
-}
-
-// -----
-
-// cir.func parses memory effects separately from cir.call.
-// expected-error @below {{expected a memory access kind or class}}
-cir.func private @on_a_func() memory(nonsense)
diff --git a/clang/test/CIR/Lowering/memory-effects.cir b/clang/test/CIR/Lowering/memory-effects.cir
index db954c2aa82f2..b7a0759bcf954 100644
--- a/clang/test/CIR/Lowering/memory-effects.cir
+++ b/clang/test/CIR/Lowering/memory-effects.cir
@@ -4,23 +4,24 @@
 !s32i = !cir.int<s, 32>
 
 module {
-  cir.func private @reads() -> !s32i memory(read)
-  cir.func private @reads_nounwind() -> !s32i memory(read)
-      attributes {nounwind, willreturn}
-  cir.func private @writes_args(!cir.ptr<!s32i>) memory(argmem: readwrite)
+  cir.func private @reads() -> !s32i attributes {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>}
+  cir.func private @reads_nounwind() -> !s32i
+      attributes {nounwind, willreturn, memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>}
+  cir.func private @writes_args(!cir.ptr<!s32i>)
+      attributes {memory_effects = #llvm.memory_effects<other = none, argMem = readwrite, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>}
   cir.func private @only_nounwind() -> !s32i attributes {nounwind}
   cir.func private @only_willreturn() -> !s32i attributes {willreturn}
 
   cir.func @caller(%arg0: !cir.ptr<!s32i>) {
-    %0 = cir.call @reads() memory(read) : () -> !s32i
-    %1 = cir.call @reads_nounwind() nounwind willreturn memory(read) : () -> !s32i
-    cir.call @writes_args(%arg0) memory(argmem: readwrite) : (!cir.ptr<!s32i>) -> ()
+    %0 = cir.call @reads() {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
+    %1 = cir.call @reads_nounwind() nounwind willreturn {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
+    cir.call @writes_args(%arg0) {memory_effects = #llvm.memory_effects<other = none, argMem = readwrite, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : (!cir.ptr<!s32i>) -> ()
+    %2 = cir.call @only_nounwind() nounwind : () -> !s32i
+    %3 = cir.call @only_willreturn() willreturn : () -> !s32i
     cir.return
   }
 }
 
-// Memory effects alone must not imply nounwind or willreturn, so the group for
-// @reads has to be exactly memory(read).
 // LLVM: declare i32 @reads() #[[READS:[0-9]+]]
 // LLVM: declare i32 @reads_nounwind() #[[BOTH:[0-9]+]]
 // LLVM: declare void @writes_args(ptr) #[[ARGMEM:[0-9]+]]
@@ -32,6 +33,10 @@ module {
 // LLVM: call i32 @reads() #[[READS]]
 // LLVM: call i32 @reads_nounwind() #[[BOTH]]
 // LLVM: call void @writes_args(ptr %{{.+}}) #[[ARGMEM]]
+// Each flag has to survive on its own, without any memory effects.
+// LLVM: call i32 @only_nounwind() #[[NUW]]
+// LLVM: call i32 @only_willreturn() #[[WR]]
+// Memory effects alone must not imply nounwind or willreturn.
 // LLVM-DAG: attributes #[[READS]] = { memory(read) }
 // LLVM-DAG: attributes #[[BOTH]] = { nounwind willreturn memory(read) }
 // LLVM-DAG: attributes #[[ARGMEM]] = { memory(argmem: readwrite) }
diff --git a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
index d0fb57c65a64e..835232b19a33a 100644
--- a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
+++ b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
@@ -48,7 +48,7 @@ cir.func @test_preserve_res_attrs() {
 cir.func @test_preserve_memory_effects() {
   cir.scope {
     cir.try {
-      %0 = cir.call @pureFunc() memory(read) : () -> !s32i
+      %0 = cir.call @pureFunc() {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -60,13 +60,13 @@ cir.func @test_preserve_memory_effects() {
 }
 
 // CHECK-LABEL: cir.func @test_preserve_memory_effects()
-// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} memory(read) : () -> !s32i
+// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
 
 // Test that nounwind and willreturn are preserved on the cir.try_call.
 cir.func @test_preserve_nounwind_willreturn() {
   cir.scope {
     cir.try {
-      %0 = cir.call @pureFunc() nounwind willreturn memory(read) : () -> !s32i
+      %0 = cir.call @pureFunc() nounwind willreturn {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -78,7 +78,7 @@ cir.func @test_preserve_nounwind_willreturn() {
 }
 
 // CHECK-LABEL: cir.func @test_preserve_nounwind_willreturn()
-// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} nounwind willreturn memory(read) : () -> !s32i
+// CHECK:         cir.try_call @pureFunc() ^{{.*}}, ^{{.*}} nounwind willreturn {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>} : () -> !s32i
 
 // Test that argument attributes on an indirect cir.call are preserved on the
 // resulting indirect cir.try_call after flattening.
@@ -105,7 +105,7 @@ cir.func @test_preserve_indirect_call_attrs(
 cir.func @test_preserve_all_attrs(%arg0 : !cir.ptr<!s32i>) {
   cir.scope {
     cir.try {
-      %0 = cir.call @generalFunc(%arg0) memory(none) : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
+      %0 = cir.call @generalFunc(%arg0) {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -117,7 +117,7 @@ cir.func @test_preserve_all_attrs(%arg0 : !cir.ptr<!s32i>) {
 }
 
 // CHECK-LABEL: cir.func @test_preserve_all_attrs(%arg0: !cir.ptr<!s32i>)
-// CHECK:         cir.try_call @generalFunc(%arg0) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} memory(none) : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
+// CHECK:         cir.try_call @generalFunc(%arg0) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : (!cir.ptr<!s32i> {llvm.nonnull, llvm.noundef}) -> (!cir.ptr<!s32i> {llvm.nonnull})
 
 // Test that all attribute types are preserved with indirect calls.
 cir.func @test_preserve_all_attrs_indirect(
@@ -125,7 +125,7 @@ cir.func @test_preserve_all_attrs_indirect(
       %obj : !cir.ptr<!rec_SomeClass>) {
   cir.scope {
     cir.try {
-      %0 = cir.call %fptr(%obj) memory(none) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
+      %0 = cir.call %fptr(%obj) {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
       cir.yield
     } catch all (%eh_token : !cir.eh_token) {
       %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.void>)
@@ -139,7 +139,7 @@ cir.func @test_preserve_all_attrs_indirect(
 // CHECK-LABEL: cir.func @test_preserve_all_attrs_indirect
 // CHECK:             %[[FN_PTR:.*]]: !cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>,
 // CHECK-SAME:        %[[OBJ:.*]]: !cir.ptr<!rec_SomeClass>
-// CHECK:         cir.try_call %[[FN_PTR]](%[[OBJ]]) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} memory(none) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
+// CHECK:         cir.try_call %[[FN_PTR]](%[[OBJ]]) ^{{bb[0-9]+}}, ^{{bb[0-9]+}} {memory_effects = #llvm.memory_effects<other = none, argMem = none, inaccessibleMem = none, errnoMem = none, targetMem0 = none, targetMem1 = none>} : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> !s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
 
 cir.func private @generalFunc(!cir.ptr<!s32i>) -> !cir.ptr<!s32i>
 cir.func private @takesPtr(!cir.ptr<!s32i>)
diff --git a/clang/test/CIR/Transforms/idiom-recognizer.cpp b/clang/test/CIR/Transforms/idiom-recognizer.cpp
index 4cef0a7658094..bd7174889547d 100644
--- a/clang/test/CIR/Transforms/idiom-recognizer.cpp
+++ b/clang/test/CIR/Transforms/idiom-recognizer.cpp
@@ -43,7 +43,7 @@ char *test_find(char *first, char *last, const char &value) {
 // FINAL: %[[LAST:.*]] = cir.load{{.*}} %[[LAST_ADDR]] :
 // FINAL: %[[VALUE:.*]] = cir.load{{.*}} %[[VALUE_ADDR]] :
 // FINAL: cir.call @_ZSt4findIPccET_S1_S1_RKT0_(%[[FIRST]], %[[LAST]], %[[VALUE]])
-// FINAL-SAME: nothrow nounwind willreturn memory(read)
+// FINAL-SAME: nothrow nounwind willreturn {memory_effects = #llvm.memory_effects<other = read, argMem = read, inaccessibleMem = read, errnoMem = read, targetMem0 = read, targetMem1 = read>}
 // FINAL-SAME: {llvm.noundef}
 // FINAL-SAME: -> (!cir.ptr<!s8i> {llvm.noundef})
 // FINAL-NOT: cir.call @_ZSt4find



More information about the cfe-commits mailing list