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

>From 55cbb3266302fe96bebb006bb0d997f0c827320d Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Wed, 20 May 2026 14:50:17 -0700
Subject: [PATCH 1/3] [CIR] Inline trivial copy/move assignment at call sites

Classic CodeGen does not call implicit trivial copy/move assignment
operators for unions and other memcpy-equivalent types; it emits an
aggregate copy at the assignment site.  CIR was calling the implicit
operator=, whose body is only "return *this", so LLVM at -O3 inferred
readonly on the destination pointer and deleted the store.  That broke
kimwitu++ kc (-fclangir -O3) and any bison-style *++yyvsp = yylval path.

Mirror CGExprCXX: evaluate the RHS first for operator=, then
emitAggregateAssign for trivial copy/move when the record may not insert
extra padding.  Add emitAggregateAssign on CIRGenFunction.

Regression test trivial-union-assign.cpp (CIR/LLVM/OGCG at -O3).
Update cxx-special-member-attr, lvalue-nttp, and device-stub checks for
the new emission shape.
---
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp       | 46 +++++++++++++------
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  6 +++
 .../CIR/CodeGen/cxx-special-member-attr.cpp   |  8 ++--
 .../test/CIR/CodeGen/trivial-union-assign.cpp | 36 +++++++++++++++
 clang/test/CIR/CodeGenCUDA/device-stub.cu     |  2 +-
 clang/test/CIR/CodeGenCXX/lvalue-nttp.cpp     |  2 +-
 6 files changed, 79 insertions(+), 21 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/trivial-union-assign.cpp

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 1a565b077a2eb..94fca3f68aa40 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -169,24 +169,28 @@ RValue 
CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
     }
   }
 
-  // Note on trivial assignment
-  // --------------------------
-  // Classic codegen avoids generating the trivial copy/move assignment 
operator
-  // when it isn't necessary, choosing instead to just produce IR with an
-  // equivalent effect. We have chosen not to do that in CIR, instead emitting
-  // trivial copy/move assignment operators and allowing later transformations
-  // to optimize them away if appropriate.
+  bool trivialForCodegen =
+      md->isTrivial() || (md->isDefaulted() && md->getParent()->isUnion());
+  bool trivialAssignment =
+      trivialForCodegen &&
+      (md->isCopyAssignmentOperator() || md->isMoveAssignmentOperator()) &&
+      !md->getParent()->mayInsertExtraPadding();
 
   // C++17 demands that we evaluate the RHS of a (possibly-compound) assignment
   // operator before the LHS.
   CallArgList rtlArgStorage;
   CallArgList *rtlArgs = nullptr;
+  LValue trivialAssignmentRhs;
   if (auto *oce = dyn_cast<CXXOperatorCallExpr>(ce)) {
     if (oce->isAssignmentOp()) {
-      rtlArgs = &rtlArgStorage;
-      emitCallArgs(*rtlArgs, md->getType()->castAs<FunctionProtoType>(),
-                   drop_begin(ce->arguments(), 1), ce->getDirectCallee(),
-                   /*ParamsToSkip*/ 0);
+      if (trivialAssignment)
+        trivialAssignmentRhs = emitLValue(oce->getArg(1));
+      else {
+        rtlArgs = &rtlArgStorage;
+        emitCallArgs(*rtlArgs, md->getType()->castAs<FunctionProtoType>(),
+                     drop_begin(ce->arguments(), 1), ce->getDirectCallee(),
+                     /*ParamsToSkip*/ 0);
+      }
     }
   }
 
@@ -195,7 +199,8 @@ RValue 
CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
     LValueBaseInfo baseInfo;
     assert(!cir::MissingFeatures::opTBAA());
     Address thisValue = emitPointerWithAlignment(base, &baseInfo);
-    thisPtr = makeAddrLValue(thisValue, base->getType(), baseInfo);
+    thisPtr =
+        makeAddrLValue(thisValue, base->getType()->getPointeeType(), baseInfo);
   } else {
     thisPtr = emitLValue(base);
   }
@@ -206,9 +211,20 @@ RValue 
CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
     return RValue::get(nullptr);
   }
 
-  if ((md->isTrivial() || (md->isDefaulted() && md->getParent()->isUnion())) &&
-      isa<CXXDestructorDecl>(md))
-    return RValue::get(nullptr);
+  if (trivialForCodegen) {
+    if (isa<CXXDestructorDecl>(md))
+      return RValue::get(nullptr);
+
+    if (trivialAssignment) {
+      LValue rhs = isa<CXXOperatorCallExpr>(ce) ? trivialAssignmentRhs
+                                                : emitLValue(*ce->arg_begin());
+      emitAggregateAssign(thisPtr, rhs, ce->getType());
+      return RValue::get(thisPtr.getPointer());
+    }
+
+    assert(md->getParent()->mayInsertExtraPadding() &&
+           "unknown trivial member function");
+  }
 
   // Compute the function type we're calling
   const CXXMethodDecl *calleeDecl =
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 9f2facd12f417..6053bedba81af 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1512,6 +1512,12 @@ class CIRGenFunction : public CIRGenTypeCache {
                          AggValueSlot::Overlap_t mayOverlap,
                          bool isVolatile = false);
 
+  /// Emit an aggregate assignment.
+  void emitAggregateAssign(LValue dest, LValue src, QualType eltTy) {
+    emitAggregateCopy(dest, src, eltTy, AggValueSlot::MayOverlap,
+                      hasVolatileMember(eltTy));
+  }
+
   /// Emit code to compute the specified expression which can have any type. 
The
   /// result is returned as an RValue struct. If this is an aggregate
   /// expression, the aggloc/agglocvolatile arguments indicate where the result
diff --git a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp 
b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
index bfca59db44fdf..3c529c9426bc2 100644
--- a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
+++ b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
@@ -30,10 +30,6 @@ struct Foo {
   ~Foo();
 };
 
-// Trivial copy/move assignment operator definitions appear at module level.
-// CIR: @_ZN4FlubaSERKS_(%arg0: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Flub>{{.*}}) special_member<#cir.cxx_assign<!rec_Flub, copy, 
trivial true>>
-// CIR: @_ZN4FlubaSEOS_(%arg0: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Flub>{{.*}}) special_member<#cir.cxx_assign<!rec_Flub, move, 
trivial true>>
-
 void trivial_func() {
   Flub f1{};
 
@@ -45,7 +41,11 @@ void trivial_func() {
   // CIR: cir.copy {{.*}} : !cir.ptr<!rec_Flub>
 
   f2 = f1;
+  // CIR: cir.copy {{.*}} : !cir.ptr<!rec_Flub>
+  // CIR-NOT: cir.call{{.*}}@_ZN4FlubaSERKS_
   f1 = static_cast<Flub&&>(f3);
+  // CIR: cir.copy {{.*}} : !cir.ptr<!rec_Flub>
+  // CIR-NOT: cir.call{{.*}}@_ZN4FlubaSEOS_
 }
 
 void non_trivial_func() {
diff --git a/clang/test/CIR/CodeGen/trivial-union-assign.cpp 
b/clang/test/CIR/CodeGen/trivial-union-assign.cpp
new file mode 100644
index 0000000000000..1d02a4634806e
--- /dev/null
+++ b/clang/test/CIR/CodeGen/trivial-union-assign.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -fclangir -emit-cir %s 
-o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -fclangir -emit-llvm 
%s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+union YYSTYPE {
+  void *yt_casestring;
+  void *yt_ID;
+};
+
+extern YYSTYPE yylval;
+
+static int consume(YYSTYPE v) { return v.yt_casestring != nullptr; }
+
+int test_shift(YYSTYPE *yyvsp) {
+  yylval.yt_casestring = reinterpret_cast<void *>(0x42);
+  *++yyvsp = yylval;
+  return consume(yyvsp[0]);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z10test_shiftP7YYSTYPE
+// CIR-NOT: cir.call{{.*}}@_ZN7YYSTYPEaSERKS_
+// CIR: cir.copy
+
+// LLVM-LABEL: define{{.*}} @_Z10test_shiftP7YYSTYPE(
+// LLVM: store{{.*}}@yylval
+// LLVM: store i64 66
+// LLVM-NOT: readonly
+// LLVM: ret i32 1
+
+// OGCG-LABEL: define{{.*}} @_Z10test_shiftP7YYSTYPE(
+// OGCG: store{{.*}}@yylval
+// OGCG: store i64 66
+// OGCG: ret i32 1
diff --git a/clang/test/CIR/CodeGenCUDA/device-stub.cu 
b/clang/test/CIR/CodeGenCUDA/device-stub.cu
index ca5e185add5fc..3ab60009b6045 100644
--- a/clang/test/CIR/CodeGenCUDA/device-stub.cu
+++ b/clang/test/CIR/CodeGenCUDA/device-stub.cu
@@ -255,7 +255,7 @@ __device__ _BitInt(36) c;
 // HIP-CIR-SAME: #cir.int<1> : !s32i,
 // HIP-CIR-SAME: #cir.global_view<@__hip_fatbin_str> : !cir.ptr<!void>,
 // HIP-CIR-SAME: #cir.ptr<null> : !cir.ptr<!void>
-// HIP-CIR-SAME: }> : !rec_anon_struct {section = ".hipFatBinSegment"}
+// HIP-CIR-SAME: }> : !rec_anon_struct{{.*}} {section = ".hipFatBinSegment"}
 
 // HIP-CIR: cir.global "private" internal @__hip_gpubin_handle = 
#cir.ptr<null> : !cir.ptr<!cir.ptr<!void>>
 // HIP-CIR: cir.func private @__hipRegisterFatBinary(!cir.ptr<!void>) -> 
!cir.ptr<!cir.ptr<!void>>
diff --git a/clang/test/CIR/CodeGenCXX/lvalue-nttp.cpp 
b/clang/test/CIR/CodeGenCXX/lvalue-nttp.cpp
index 961430a1a3fb9..7261f61d462a3 100644
--- a/clang/test/CIR/CodeGenCXX/lvalue-nttp.cpp
+++ b/clang/test/CIR/CodeGenCXX/lvalue-nttp.cpp
@@ -34,7 +34,7 @@ template void templ<s>();
 // CIR-NEXT: %[[ONE:.*]] = cir.const #cir.int<1>
 // CIR-NEXT: cir.call @_ZN6StructC1Ei(%[[TEMP]], %[[ONE]])
 // CIR-NEXT: %[[GLOB:.*]] = cir.get_global @s : !cir.ptr<!rec_Struct>
-// CIR-NEXT: cir.call @_ZN6StructaSEOS_(%[[GLOB]], %[[TEMP]])
+// CIR-NEXT: cir.return
 
 // LLVM: define{{.*}}@_Z5templITnRDaL_Z1sEEvv()
 // LLVM: %[[TEMP:.*]] = alloca %struct.Struct

>From f82b7274301e7337d052a4c2c0ade705440fca6e Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Thu, 21 May 2026 11:06:22 -0700
Subject: [PATCH 2/3] [CIR] Fold trivial-union-assign test into
 cxx-special-member-attr

Move the -O3 yacc-stack repro into cxx-special-member-attr.cpp via
split-file and drop trivial-union-assign.cpp per review.  Use one LLVM
check prefix for CIR-lowered and classic output.
---
 .../CIR/CodeGen/cxx-special-member-attr.cpp   | 42 ++++++++++++++++++-
 .../test/CIR/CodeGen/trivial-union-assign.cpp | 36 ----------------
 2 files changed, 40 insertions(+), 38 deletions(-)
 delete mode 100644 clang/test/CIR/CodeGen/trivial-union-assign.cpp

diff --git a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp 
b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
index 3c529c9426bc2..b92e7b5eb6549 100644
--- a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
+++ b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
@@ -1,5 +1,9 @@
-// RUN: %clang_cc1 -std=c++11 -triple aarch64-none-linux-android21 -fclangir 
-emit-cir %s -o %t.cir
-// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: split-file %s %t
+
+//--- special_member_attr.cpp
+
+// RUN: %clang_cc1 -std=c++11 -triple aarch64-none-linux-android21 -fclangir 
-emit-cir %t/special_member_attr.cpp -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir 
%t/special_member_attr.cpp
 
 struct Flub {
   int a = 123;
@@ -65,3 +69,37 @@ void non_trivial_func() {
   // CIR: @_ZN3FooaSEOS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Foo>{{.*}}) special_member<#cir.cxx_assign<!rec_Foo, move>>
   // CIR: @_ZN3FooD1Ev(!cir.ptr<!rec_Foo> {{.*}}) 
special_member<#cir.cxx_dtor<!rec_Foo>>
 }
+
+//--- trivial_union_assign.cpp
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -fclangir -emit-cir 
%t/trivial_union_assign.cpp -o %t-union.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t-union.cir 
%t/trivial_union_assign.cpp
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -fclangir -emit-llvm 
%t/trivial_union_assign.cpp -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll 
%t/trivial_union_assign.cpp
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -emit-llvm 
%t/trivial_union_assign.cpp -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll 
%t/trivial_union_assign.cpp
+
+union YYSTYPE {
+  void *yt_casestring;
+  void *yt_ID;
+};
+
+extern YYSTYPE yylval;
+
+static int consume(YYSTYPE v) { return v.yt_casestring != nullptr; }
+
+int test_shift(YYSTYPE *yyvsp) {
+  yylval.yt_casestring = reinterpret_cast<void *>(0x42);
+  *++yyvsp = yylval;
+  return consume(yyvsp[0]);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z10test_shiftP7YYSTYPE
+// CIR-NOT: cir.call{{.*}}@_ZN7YYSTYPEaSERKS_
+// CIR: cir.copy
+
+// LLVM-LABEL: define{{.*}} @_Z10test_shiftP7YYSTYPE(
+// LLVM: store{{.*}}@yylval
+// LLVM: store i64 66
+// LLVM-NOT: readonly
+// LLVM: ret i32 1
diff --git a/clang/test/CIR/CodeGen/trivial-union-assign.cpp 
b/clang/test/CIR/CodeGen/trivial-union-assign.cpp
deleted file mode 100644
index 1d02a4634806e..0000000000000
--- a/clang/test/CIR/CodeGen/trivial-union-assign.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -fclangir -emit-cir %s 
-o %t.cir
-// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -fclangir -emit-llvm 
%s -o %t-cir.ll
-// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O3 -emit-llvm %s -o %t.ll
-// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
-
-union YYSTYPE {
-  void *yt_casestring;
-  void *yt_ID;
-};
-
-extern YYSTYPE yylval;
-
-static int consume(YYSTYPE v) { return v.yt_casestring != nullptr; }
-
-int test_shift(YYSTYPE *yyvsp) {
-  yylval.yt_casestring = reinterpret_cast<void *>(0x42);
-  *++yyvsp = yylval;
-  return consume(yyvsp[0]);
-}
-
-// CIR-LABEL: cir.func{{.*}} @_Z10test_shiftP7YYSTYPE
-// CIR-NOT: cir.call{{.*}}@_ZN7YYSTYPEaSERKS_
-// CIR: cir.copy
-
-// LLVM-LABEL: define{{.*}} @_Z10test_shiftP7YYSTYPE(
-// LLVM: store{{.*}}@yylval
-// LLVM: store i64 66
-// LLVM-NOT: readonly
-// LLVM: ret i32 1
-
-// OGCG-LABEL: define{{.*}} @_Z10test_shiftP7YYSTYPE(
-// OGCG: store{{.*}}@yylval
-// OGCG: store i64 66
-// OGCG: ret i32 1

>From bbcf200da0629b97c54d5aa3de6cfa6bbd6b79d9 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Wed, 27 May 2026 15:48:37 -0700
Subject: [PATCH 3/3] [CIR] NFC: comment on trivialAssignment inlining path

Mirror the rationale from classic CodeGen's
EmitCXXMemberOrOperatorMemberCallExpr: we inline trivial copy/move
assignment rather than calling the operator to preserve TBAA information
from the RHS.  emitLValue must be used here instead of emitting call
arguments for the same reason.
---
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 94fca3f68aa40..93526c7791170 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -216,6 +216,11 @@ RValue 
CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
       return RValue::get(nullptr);
 
     if (trivialAssignment) {
+      // We don't like to generate the trivial copy/move assignment operator
+      // when it isn't necessary; just produce the proper effect here.  It's
+      // important that we use the result of emitLValue here rather than
+      // emitting call arguments, in order to preserve TBAA information from
+      // the RHS.
       LValue rhs = isa<CXXOperatorCallExpr>(ce) ? trivialAssignmentRhs
                                                 : emitLValue(*ce->arg_begin());
       emitAggregateAssign(thisPtr, rhs, ce->getType());

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

Reply via email to