[clang] [CIR] Widen memory effects for ABI-introduced argument memory (PR #221067)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 8 10:57:28 PDT 2026


================
@@ -1,43 +1,140 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+
+struct Big { long a, b, c, d; };
+struct Small { int x; };
+
 extern "C" {
 
+__attribute__((const)) Big const_sret();
+__attribute__((pure)) Big pure_sret();
+__attribute__((const)) int const_byval(Big b);
+__attribute__((pure)) int pure_byval(Big b);
+__attribute__((const)) Small const_small();
+__attribute__((const)) int const_ptr(const int *p);
+__attribute__((const)) int const_variadic_decl(int n, ...);
+__attribute__((const)) int const_byval2(int n, Big b);
+__attribute__((const)) int const_bigref(const Big &b);
+
 // 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} {
-// LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read)
-// LLVM: define{{.*}} @pure_func() #{{.*}} {
-// OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(read)
-// OGCG: define{{.*}} @pure_func() #{{.*}} {
 __attribute__((pure))
 int pure_func() { return 2;}
 
 // CIR: cir.func{{.*}}@const_func() -> !s32i side_effect(const) attributes {{{.*}}nothrow} {
-// LLVM: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none)
-// LLVM: define{{.*}} @const_func() #{{.*}} {
-// OGCG: Function Attrs: {{.*}}nounwind{{.*}}willreturn{{.*}}memory(none)
-// OGCG: define{{.*}} @const_func() #{{.*}} {
 __attribute__((const))
 int const_func() { return 1;}
 
+// Variadic definition: widened with no indirect slot in the signature.
+// CIR: cir.func{{.*}}@const_variadic(%arg0: !s32i {{.*}}, ...) -> !s32i side_effect(const)
+__attribute__((const))
+int const_variadic(int n, ...) { return n; }
+
+// A definition also gets llvm.noalias on the sret slot, so this is where
+// noalias, writable and the widened effect have to coexist.
+// CIR: cir.func{{.*}}@const_sret_def(%arg0: !cir.ptr<!rec_Big> {{{.*}}llvm.noalias{{.*}}llvm.sret = !rec_Big{{.*}}llvm.writable{{.*}}) side_effect(const)
+__attribute__((const))
+Big const_sret_def() { Big r{}; return r; }
+
 void use() {
-  // CIR: cir.call @pure_func() side_effect(pure) : () -> !s32i
-  // LLVM: call i32 @pure_func() #[[PURE_ATTR:.*]]
-  // OGCG: call i32 @pure_func() #[[PURE_ATTR:.*]]
+  // Unwidened at a call site: neither takes an indirect slot.
+  // CIR: cir.call @pure_func() side_effect(pure)
   pure_func();
-  // CIR: cir.call @const_func() side_effect(const) : () -> !s32i
-  // LLVM: call i32 @const_func() #[[CONST_ATTR:.*]]
-  // OGCG: call i32 @const_func() #[[CONST_ATTR:.*]]
+  // CIR: cir.call @const_func() side_effect(const)
   const_func();
+
+  // The pass has already given these calls their sret operand.
+  // CIR: cir.call @const_sret(%{{.+}}) side_effect(const)
+  const_sret();
+  // CIR: cir.call @pure_sret(%{{.+}}) side_effect(pure)
+  pure_sret();
+
+  Big b{};
+  int i = 0;
+  // CIR: cir.call @const_byval({{.*}}) side_effect(const)
+  const_byval(b);
+  // CIR: cir.call @pure_byval({{.*}}) side_effect(pure)
+  pure_byval(b);
+
+  // CIR: cir.call @const_small() side_effect(const)
+  const_small();
+  // A source-level pointer is not ABI-introduced memory, so the effect stays
+  // memory(none).
+  // CIR: cir.call @const_ptr({{.*}}) side_effect(const)
+  const_ptr(&i);
+
+  // A call site sees the arguments it passes, so it is not widened for being
+  // variadic.
+  // CIR: cir.call @const_variadic({{.*}}) side_effect(const)
+  const_variadic(1);
+  // CIR: cir.call @const_variadic_decl({{.*}}) side_effect(const)
+  const_variadic_decl(1);
+
+  // CIR: cir.call @const_byval2({{.*}}) side_effect(const) : (!s32i{{.*}}, !cir.ptr<!rec_Big> {{{.*}}llvm.byval{{.*}}}) -> !s32i
+  const_byval2(1, b);
+  // CIR: cir.call @const_sret_def(%{{.+}}) side_effect(const)
+  const_sret_def();
+
+  // A reference is a direct pointer slot that still carries align, so neither
+  // align nor a record pointee can stand in for an indirect slot.
+  // CIR: cir.call @const_bigref({{.*}}) side_effect(const)
+  const_bigref(b);
 }
 
-// LLVM: attributes #[[PURE_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(read) }
-// OGCG: attributes #[[PURE_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(read) }
-// LLVM: attributes #[[CONST_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(none) }
-// OGCG: attributes #[[CONST_ATTR]] = {{{.*}}nounwind{{.*}}willreturn{{.*}}memory(none) }
 }
 
+// The named functions appear in the same relative order in both emits, so
+// these checks are ordered.  The intrinsic declarations do not, so each
+// attribute group is captured off its own define or declare line instead of
+// matched adjacent to it.
+
+// Definitions.
+// LLVM: define{{.*}} i32 @pure_func() #[[READ_DEF:[0-9]+]] {
----------------
andykaylor wrote:

Why aren't these checks kept with the function they are checking?

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


More information about the cfe-commits mailing list