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 <[email protected]>
Date: Tue, 15 Sep 2026 17:30:59 -0700
Subject: [PATCH] [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

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to