[clang] [CIR] Inline trivial copy/move assignment at call sites (PR #198918)

via cfe-commits cfe-commits at lists.llvm.org
Mon Jun 22 13:47:14 PDT 2026


================
@@ -901,6 +901,36 @@ void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) {
   assert(!cir::MissingFeatures::incrementProfileCounter());
   assert(!cir::MissingFeatures::runCleanupsScope());
 
+  // For a memcpy-equivalent assignment operator, copy the whole object, then
+  // fall through to emit the trailing `return *this`.
+  if (assignOp->isMemcpyEquivalentSpecialMember(getContext())) {
+    CanQualType recordTy =
+        getContext().getCanonicalTagType(assignOp->getParent());
+    LValue dest = makeNaturalAlignAddrLValue(loadCXXThis(), recordTy);
+    mlir::Value srcPtr = builder.createLoad(getLoc(assignOp->getLocation()),
+                                            getAddrOfLocalVar(args.back()));
+    LValue src = makeNaturalAlignAddrLValue(srcPtr, recordTy);
+    emitAggregateAssign(dest, src, recordTy);
----------------
adams381 wrote:

This block is really doing two separate jobs, and only one of them needs the frontend.

For a trivially-copyable struct the AST already carries the work: per-field assignment expressions, plus a `__builtin_memcpy` call for array members. Dropping the whole-object copy and just emitting the body (which the non-memcpy path further down already does) is exactly the "reflect the AST and let optimization coalesce" behavior. The `assert(!MissingFeatures::assignMemcpyizer())` there is only the coalescing step; the per-field stores stand on their own and survive `-O3`. That case needs no frontend change.

The union is the one that can't go through that path. Its body is empty -- just `return *this` -- because Sema skips union fields at the FIXME in `SemaDeclCXX.cpp:15474` (copy) and `:15863` (move). There are no copies to leave, and a memcpyizer has nothing to coalesce on an empty body. That empty body is the original regression here: `*++yyvsp = yylval` in `kc`, where `YYSTYPE` is a union, whose no-op `operator=` LLVM deletes at `-O3`. Only a real AST node or this synthesis fills it.

So two ways forward, and I'd like your and @andykaylor's call before touching shared code (same scope question I raised on the other thread Jun 16):

1. Implement the FIXME -- form an AST node for the implied union memcpy in copy and move assignment. That fixes classic and CIR together and lets this PR drop the synthesis. It is a shared-AST change: it shifts classic union copy/move codegen, likely reaches constexpr handling, and updates existing CodeGen tests, and the FIXME has been open a long time, so I don't want to start it without a maintainer nod.
2. If that's more than this PR should carry, narrow it to the struct/array lowering and `errorNYI` the union body, then handle the union in its own PR once the AST question is settled.

Which do you prefer?


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


More information about the cfe-commits mailing list