[clang] [CIR] Inline trivial copy/move assignment at call sites (PR #198918)
via cfe-commits
cfe-commits at lists.llvm.org
Wed May 27 15:49:04 PDT 2026
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 <adams at nvidia.com>
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 <adams at nvidia.com>
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 <adams at nvidia.com>
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());
More information about the cfe-commits
mailing list