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
