https://github.com/andykaylor updated 
https://github.com/llvm/llvm-project/pull/157713

>From 522ea40cad53aaf2b787a7b2f27fbc3c7927d873 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akay...@nvidia.com>
Date: Mon, 8 Sep 2025 12:40:06 -0700
Subject: [PATCH 1/2] [CIR] Add support for copy elision

This adds basic support for eliding copy constructors. In order to
make this possible, it also adds support for returning structures.
This support does not include setting an NRVO flag when the class
whose copy is being elided has a non-trivial destructor.
---
 clang/include/clang/CIR/MissingFeatures.h |  1 +
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp      | 33 ++++++++++++---
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp      | 12 +++++-
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp  |  6 ++-
 clang/lib/CIR/CodeGen/CIRGenFunction.h    | 12 ++++++
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp      | 15 +++++--
 clang/test/CIR/CodeGen/nrvo.cpp           | 51 +++++++++++++++++++++++
 7 files changed, 117 insertions(+), 13 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/nrvo.cpp

diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 1e64278d118b5..52d5f8a2ded2c 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -244,6 +244,7 @@ struct MissingFeatures {
   static bool moduleNameHash() { return false; }
   static bool msabi() { return false; }
   static bool needsGlobalCtorDtor() { return false; }
+  static bool nrvo() { return false; }
   static bool objCBlocks() { return false; }
   static bool objCGC() { return false; }
   static bool objCLifetime() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 7cc024fd5596c..7f8ffa209dacb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -31,6 +31,8 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
     cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: address space");
 
   mlir::Location loc = getLoc(d.getSourceRange());
+  bool nrvo =
+      getContext().getLangOpts().ElideConstructors && d.isNRVOVariable();
 
   CIRGenFunction::AutoVarEmission emission(d);
   emission.IsEscapingByRef = d.isEscapingByref();
@@ -44,16 +46,37 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
   if (ty->isVariablyModifiedType())
     cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: variably modified 
type");
 
+  assert(!cir::MissingFeatures::openMP());
+
   Address address = Address::invalid();
   if (!ty->isConstantSizeType())
     cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: non-constant size 
type");
 
   // A normal fixed sized variable becomes an alloca in the entry block,
-  mlir::Type allocaTy = convertTypeForMem(ty);
-  // Create the temp alloca and declare variable using it.
-  address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
-                             /*arraySize=*/nullptr, /*alloca=*/nullptr, ip);
-  declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()), alignment);
+  // unless:
+  // - it's an NRVO variable.
+  // - we are compiling OpenMP and it's an OpenMP local variable.
+  if (nrvo) {
+    // The named return value optimization: allocate this variable in the
+    // return slot, so that we can elide the copy when returning this
+    // variable (C++0x [class.copy]p34).
+    address = returnValue;
+
+    if (const auto *rd = ty->getAsRecordDecl()) {
+      if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
+          (cxxrd && !cxxrd->hasTrivialDestructor()) ||
+          rd->isNonTrivialToPrimitiveDestroy())
+        cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: set NRVO flag");
+    }
+  } else {
+    // A normal fixed sized variable becomes an alloca in the entry block,
+    mlir::Type allocaTy = convertTypeForMem(ty);
+    // Create the temp alloca and declare variable using it.
+    address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
+                               /*arraySize=*/nullptr, /*alloca=*/nullptr, ip);
+    declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()),
+            alignment);
+  }
 
   emission.Addr = address;
   setAddrOfLocalVar(&d, address);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index d8c7903a4888d..aab7e2745f30f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1986,8 +1986,16 @@ void CIRGenFunction::emitCXXConstructExpr(const 
CXXConstructExpr *e,
 
   // Elide the constructor if we're constructing from a temporary
   if (getLangOpts().ElideConstructors && e->isElidable()) {
-    cgm.errorNYI(e->getSourceRange(),
-                 "emitCXXConstructExpr: elidable constructor");
+    // FIXME: This only handles the simplest case, where the source object is
+    //        passed directly as the first argument to the constructor. This
+    //        should also handle stepping through implicit casts and conversion
+    //        sequences which involve two steps, with a conversion operator
+    //        follwed by a converting constructor.
+    const Expr *srcObj = e->getArg(0);
+    assert(srcObj->isTemporaryObject(getContext(), cd->getParent()));
+    assert(
+        getContext().hasSameUnqualifiedType(e->getType(), srcObj->getType()));
+    emitAggExpr(srcObj, dest);
     return;
   }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index ed1272ffe1a13..e2181b8222aa2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -206,8 +206,10 @@ bool CIRGenFunction::constantFoldsToSimpleInteger(const 
Expr *cond,
 void CIRGenFunction::emitAndUpdateRetAlloca(QualType type, mlir::Location loc,
                                             CharUnits alignment) {
   if (!type->isVoidType()) {
-    fnRetAlloca = emitAlloca("__retval", convertType(type), loc, alignment,
-                             /*insertIntoFnEntryBlock=*/false);
+    mlir::Value addr = emitAlloca("__retval", convertType(type), loc, 
alignment,
+                                  /*insertIntoFnEntryBlock=*/false);
+    fnRetAlloca = addr;
+    returnValue = Address(addr, alignment);
   }
 }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index de9e3541ed840..42f7f401555ca 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -66,6 +66,10 @@ class CIRGenFunction : public CIRGenTypeCache {
   /// The compiler-generated variable that holds the return value.
   std::optional<mlir::Value> fnRetAlloca;
 
+  /// The temporary alloca to hold the return value. This is
+  /// invalid iff the function has no return value.
+  Address returnValue = Address::invalid();
+
   /// Tracks function scope overall cleanup handling.
   EHScopeStack ehStack;
 
@@ -726,6 +730,14 @@ class CIRGenFunction : public CIRGenTypeCache {
                                                 const CXXRecordDecl *base,
                                                 bool baseIsVirtual);
 
+  /// Determine whether a return value slot may overlap some other object.
+  AggValueSlot::Overlap_t getOverlapForReturnValue() {
+    // FIXME: Assuming no overlap here breaks guaranteed copy elision for base
+    // class subobjects. These cases may need to be revisited depending on the
+    // resolution of the relevant core issue.
+    return AggValueSlot::DoesNotOverlap;
+  }
+
   /// Determine whether a base class initialization may overlap some other
   /// object.
   AggValueSlot::Overlap_t getOverlapForBaseInit(const CXXRecordDecl *rd,
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 12821c1dae0c1..f116efc202061 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -445,8 +445,8 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const 
ReturnStmt &s) {
 
   if (getContext().getLangOpts().ElideConstructors && s.getNRVOCandidate() &&
       s.getNRVOCandidate()->isNRVOVariable()) {
-    getCIRGenModule().errorNYI(s.getSourceRange(),
-                               "named return value optimization");
+    assert(!cir::MissingFeatures::openMP());
+    assert(!cir::MissingFeatures::nrvo());
   } else if (!rv) {
     // No return expression. Do nothing.
   } else if (rv->getType()->isVoidType()) {
@@ -471,9 +471,16 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const 
ReturnStmt &s) {
         builder.CIRBaseBuilderTy::createStore(loc, value, *fnRetAlloca);
       }
       break;
-    default:
+    case cir::TEK_Complex:
       getCIRGenModule().errorNYI(s.getSourceRange(),
-                                 "non-scalar function return type");
+                                 "complex function return type");
+      break;
+    case cir::TEK_Aggregate:
+      assert(!cir::MissingFeatures::aggValueSlotGC());
+      emitAggExpr(rv, AggValueSlot::forAddr(returnValue, Qualifiers(),
+                                            AggValueSlot::IsDestructed,
+                                            AggValueSlot::IsNotAliased,
+                                            getOverlapForReturnValue()));
       break;
     }
   }
diff --git a/clang/test/CIR/CodeGen/nrvo.cpp b/clang/test/CIR/CodeGen/nrvo.cpp
new file mode 100644
index 0000000000000..72c39d7878dc6
--- /dev/null
+++ b/clang/test/CIR/CodeGen/nrvo.cpp
@@ -0,0 +1,51 @@
+// 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 -fno-elide-constructors 
-fclangir -emit-cir %s -o %t-noelide.cir
+// RUN: FileCheck --input-file=%t-noelide.cir %s --check-prefix=CIR-NOELIDE
+// 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
+
+// There are no LLVM and OGCG tests with -fno-elide-constructors because the
+// lowering isn't of interest for this test. We just need to see that the
+// copy constructor is elided without -fno-elide-constructors but not with it.
+
+struct S {
+  S();
+  int a;
+  int b;
+};
+
+struct S f1() {
+  S s;
+  return s;
+}
+
+// CIR:      cir.func{{.*}} @_Z2f1v() -> !rec_S {
+// CIR-NEXT:   %[[RETVAL:.*]] = cir.alloca !rec_S, !cir.ptr<!rec_S>, 
["__retval", init]
+// CIR-NEXT:   cir.call @_ZN1SC1Ev(%[[RETVAL]]) : (!cir.ptr<!rec_S>) -> ()
+// CIR-NEXT:   %[[RET:.*]] = cir.load %[[RETVAL]] : !cir.ptr<!rec_S>, !rec_S
+// CIR-NEXT:   cir.return %[[RET]]
+
+// CIR-NOELIDE:      cir.func{{.*}} @_Z2f1v() -> !rec_S {
+// CIR-NOELIDE-NEXT:   %[[RETVAL:.*]] = cir.alloca !rec_S, !cir.ptr<!rec_S>, 
["__retval"]
+// CIR-NOELIDE-NEXT:   %[[S:.*]] = cir.alloca !rec_S, !cir.ptr<!rec_S>, ["s", 
init]
+// CIR-NOELIDE-NEXT:   cir.call @_ZN1SC1Ev(%[[S]]) : (!cir.ptr<!rec_S>) -> ()
+// CIR-NOELIDE-NEXT:   cir.call @_ZN1SC1EOS_(%[[RETVAL]], %[[S]]){{.*}} : 
(!cir.ptr<!rec_S>, !cir.ptr<!rec_S>) -> ()
+// CIR-NOELIDE-NEXT:   %[[RET:.*]] = cir.load %[[RETVAL]] : !cir.ptr<!rec_S>, 
!rec_S
+// CIR-NOELIDE-NEXT:   cir.return %[[RET]]
+
+// FIXME: Update this when calling convetnion lowering is implemented.
+// LLVM:      define{{.*}} %struct.S @_Z2f1v()
+// LLVM-NEXT:   %[[RETVAL:.*]] = alloca %struct.S
+// LLVM-NEXT:   call void @_ZN1SC1Ev(ptr %[[RETVAL]])
+// LLVM-NEXT:   %[[RET:.*]] = load %struct.S, ptr %[[RETVAL]]
+// LLVM-NEXT:   ret %struct.S %[[RET]]
+
+// OGCG:      define{{.*}} i64 @_Z2f1v()
+// OGCG-NEXT: entry:
+// OGCG-NEXT:   %[[RETVAL:.*]] = alloca %struct.S
+// OGCG-NEXT:   call void @_ZN1SC1Ev(ptr {{.*}} %[[RETVAL]])
+// OGCG-NEXT:   %[[RET:.*]] = load i64, ptr %[[RETVAL]]
+// OGCG-NEXT:   ret i64 %[[RET]]

>From 3e349a053131f4dc477706073d43e7242466d6f9 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akay...@nvidia.com>
Date: Tue, 9 Sep 2025 10:28:19 -0700
Subject: [PATCH 2/2] Address review feedback

---
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 7f8ffa209dacb..66cd67389c1c7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -62,7 +62,7 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
     // variable (C++0x [class.copy]p34).
     address = returnValue;
 
-    if (const auto *rd = ty->getAsRecordDecl()) {
+    if (const RecordDecl *rd = ty->getAsRecordDecl()) {
       if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd);
           (cxxrd && !cxxrd->hasTrivialDestructor()) ||
           rd->isNonTrivialToPrimitiveDestroy())

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to