llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clangir

Author: TenHian

<details>
<summary>Changes</summary>

Because ABI calling convention lowering is deferred in CIR, we do not
use sret parameters for functions that return an aggregate.  Instead,
CIR emits a store through the `__retval` alloca and a reload from it.
For types with a deleted copy constructor, this store+load constitutes
an illegal bitwise copy.

Approach
--------

Detect whether the return type's copy constructor is deleted (recursing
into members and bases).  When it is, skip the `__retval` alloca entirely
and forward the call result as an SSA value directly to `cir.return`.

Key changes:

- `emitAndUpdateRetAlloca`: skip `__retval` for non-copyable types
- `emitReturnStmt`: for non-copyable types, emit the call with
  `ReturnValueSlot::forNoAggregateStore()` and return the SSA value
- `emitReturnOfRValue`: when `returnValue` is absent, forward the RValue
  directly without going through a memory slot
- `ReturnValueSlot`: add `noAggregateStore` flag to prevent `emitCall`
  from storing to memory when the result will be returned directly
- `CIRGenVTables.cpp`: route aggregate returns through
  `emitReturnOfRValue` regardless of slot validity
- NRVO guard: skip NRVO allocation when `__retval` is absent

Impact
------

| Aspect                    | Description |
| ------------------------- | ----------- |
| Core fix                  | Non-copyable types no longer go through 
`__retval` store/load |
| Fallthrough               | Explicit trap (O0) / unreachable (O1+) when no 
`__retval` exists |
| Virtual dispatch          | Previously crashed, now correctly returns SSA 
value |
| Copyable types            | Unchanged — still use `__retval` store/load |
| Ternary / branches        | Still has redundant store/load through `agg.tmp` 
(not `__retval`; does not affect ABI lowering) |
| Lambda invoker / thunk    | Redundant store/load through a function-local 
temp when `returnValue` is absent (pre-existing from PR #<!-- -->197572; can be 
addressed in a follow-up) |

Fixes https://github.com/llvm/llvm-project/issues/198602

---

Patch is 23.78 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/199205.diff


7 Files Affected:

- (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+5) 
- (modified) clang/lib/CIR/CodeGen/CIRGenCall.h (+8) 
- (modified) clang/lib/CIR/CodeGen/CIRGenDecl.cpp (+1-1) 
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+49-5) 
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+70-33) 
- (modified) clang/lib/CIR/CodeGen/CIRGenVTables.cpp (+7-8) 
- (added) clang/test/CIR/CodeGenCXX/uncopyable-return.cpp (+327) 


``````````diff
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index f648eff375a77..c4f0c6ae49e57 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1346,6 +1346,11 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
     return getUndefRValue(retTy);
   switch (getEvaluationKind(retTy)) {
   case cir::TEK_Aggregate: {
+    if (returnValue.isNoAggregateStore()) {
+      mlir::ResultRange results = theCall->getOpResults();
+      assert(results.size() <= 1 && "multiple returns from a call");
+      return RValue::get(results[0]);
+    }
     Address destPtr = returnValue.getValue();
 
     if (!destPtr.isValid())
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.h 
b/clang/lib/CIR/CodeGen/CIRGenCall.h
index b30b4969ca45e..904310b89dfb9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.h
@@ -259,12 +259,20 @@ class CallArgList : public llvm::SmallVector<CallArg, 8> {
 /// whether the address is volatile or not.
 class ReturnValueSlot {
   Address addr = Address::invalid();
+  bool noAggregateStore = false;
 
 public:
   ReturnValueSlot() = default;
   ReturnValueSlot(Address addr) : addr(addr) {}
 
+  static ReturnValueSlot forNoAggregateStore() {
+    ReturnValueSlot slot;
+    slot.noAggregateStore = true;
+    return slot;
+  }
+
   bool isNull() const { return !addr.isValid(); }
+  bool isNoAggregateStore() const { return noAggregateStore; }
   Address getValue() const { return addr; }
 };
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 99f3aea03aae3..8f62df38f54cc 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -94,7 +94,7 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d,
     // unless:
     // - it's an NRVO variable.
     // - we are compiling OpenMP and it's an OpenMP local variable.
-    if (nrvo) {
+    if (nrvo && returnValue.isValid()) {
       // 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).
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 52e7a9d3de412..fde1d5164ec82 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -22,6 +22,7 @@
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/MissingFeatures.h"
 #include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/IR/FPEnv.h"
 
 #include <cassert>
@@ -215,10 +216,43 @@ bool CIRGenFunction::constantFoldsToSimpleInteger(const 
Expr *cond,
   resultInt = intValue;
   return true;
 }
+static bool hasDeletedCopyConstructor(
+    const clang::CXXRecordDecl *rd,
+    llvm::SmallPtrSet<const clang::CXXRecordDecl *, 8> &visited) {
+  if (!visited.insert(rd).second)
+    return false;
+  // Check user-declared copy ctors.
+  if (rd->hasUserDeclaredCopyConstructor()) {
+    for (const auto *ctor : rd->ctors())
+      if (ctor->isCopyConstructor() && ctor->isDeleted())
+        return true;
+  } else if (!rd->needsOverloadResolutionForCopyConstructor() &&
+             rd->defaultedCopyConstructorIsDeleted()) {
+    return true;
+  }
+  // Check fields and bases recursively.
+  for (const auto *field : rd->fields())
+    if (const auto *fieldRD = field->getType()->getAsCXXRecordDecl())
+      if (hasDeletedCopyConstructor(fieldRD, visited))
+        return true;
+  for (const auto &base : rd->bases())
+    if (const auto *baseRD = base.getType()->getAsCXXRecordDecl())
+      if (hasDeletedCopyConstructor(baseRD, visited))
+        return true;
+  return false;
+}
 
 void CIRGenFunction::emitAndUpdateRetAlloca(QualType type, mlir::Location loc,
                                             CharUnits alignment) {
   if (!type->isVoidType()) {
+    // Types with non-trivial or deleted copy constructors cannot be
+    // bitwise-copied into __retval.  The return value must flow as an
+    // SSA value directly.
+    if (const auto *rd = type->getAsCXXRecordDecl()) {
+      llvm::SmallPtrSet<const clang::CXXRecordDecl *, 8> visited;
+      if (hasDeletedCopyConstructor(rd, visited))
+        return;
+    }
     Address allocaAddr = Address::invalid();
     returnValue = createMemTemp(type, alignment, loc, "__retval", &allocaAddr);
     fnRetAlloca = allocaAddr.getPointer();
@@ -336,11 +370,21 @@ cir::ReturnOp 
CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
   assert(fn && "emitReturn from non-function");
 
   if (!fn.getFunctionType().hasVoidReturn()) {
-    // Load the value from `__retval` and return it via the `cir.return` op.
-    auto value = cir::LoadOp::create(
-        builder, loc, fn.getFunctionType().getReturnType(), *cgf.fnRetAlloca);
-    return cir::ReturnOp::create(builder, loc,
-                                 llvm::ArrayRef(value.getResult()));
+    if (cgf.fnRetAlloca) {
+      // Load the value from `__retval` and return it via the `cir.return` op.
+      auto value = cir::LoadOp::create(
+          builder, loc, fn.getFunctionType().getReturnType(), 
*cgf.fnRetAlloca);
+      return cir::ReturnOp::create(builder, loc,
+                                   llvm::ArrayRef(value.getResult()));
+    }
+    // Non-trivially-copyable return type with no __retval:
+    // falling off the end is UB; emit unreachable/trap.
+    if (cgf.cgm.getCodeGenOpts().OptimizationLevel == 0)
+      cir::TrapOp::create(builder, loc);
+    else
+      cir::UnreachableOp::create(builder, loc);
+    builder.clearInsertionPoint();
+    return cir::ReturnOp();
   }
   return cir::ReturnOp::create(builder, loc);
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp 
b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 4777d8e429e34..e98ff2dfe2572 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -598,6 +598,8 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const 
ReturnStmt &s) {
     rv = ewc->getSubExpr();
     createNewScope = true;
   }
+  mlir::Value directReturnValue = nullptr;
+  Address returnTemp = Address::invalid();
 
   auto handleReturnVal = [&]() {
     if (getContext().getLangOpts().ElideConstructors && s.getNRVOCandidate() &&
@@ -642,11 +644,33 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const 
ReturnStmt &s) {
                                   /*isInit=*/true);
         break;
       case cir::TEK_Aggregate:
-        assert(!cir::MissingFeatures::aggValueSlotGC());
-        emitAggExpr(rv, AggValueSlot::forAddr(returnValue, Qualifiers(),
-                                              AggValueSlot::IsDestructed,
-                                              AggValueSlot::IsNotAliased,
-                                              getOverlapForReturnValue()));
+        if (fnRetAlloca) {
+          assert(!cir::MissingFeatures::aggValueSlotGC());
+          emitAggExpr(rv, AggValueSlot::forAddr(returnValue, Qualifiers(),
+                                                AggValueSlot::IsDestructed,
+                                                AggValueSlot::IsNotAliased,
+                                                getOverlapForReturnValue()));
+        } else {
+          // Non-trivially-copyable aggregate: emit result directly
+          // without bitwise-copying through __retval.
+          // CXXBindTemporaryExpr wraps the call when the type has a
+          // non-trivial destructor. Unwrap it to find the inner CallExpr.
+          const Expr *unwrapped = rv->IgnoreParens();
+          if (auto *bte = dyn_cast<CXXBindTemporaryExpr>(unwrapped))
+            unwrapped = bte->getSubExpr();
+          if (const auto *ce = dyn_cast<CallExpr>(unwrapped)) {
+            RValue callRV =
+                emitCallExpr(const_cast<CallExpr *>(ce),
+                             ReturnValueSlot::forNoAggregateStore());
+            directReturnValue = callRV.getValue();
+          } else {
+            returnTemp = createMemTemp(rv->getType(), loc, "agg.tmp");
+            emitAggExpr(rv, AggValueSlot::forAddr(returnTemp, Qualifiers(),
+                                                  AggValueSlot::IsDestructed,
+                                                  AggValueSlot::IsNotAliased,
+                                                  getOverlapForReturnValue()));
+          }
+        }
         break;
       }
     }
@@ -666,15 +690,23 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const 
ReturnStmt &s) {
   // during the CFG flattening phase, we can just emit the return statement
   // directly.
   // TODO(cir): Eliminate this redundant load and the store above when we can.
-  if (fnRetAlloca) {
+  if (directReturnValue) {
+    cir::ReturnOp::create(builder, loc, {directReturnValue});
+  } else if (returnTemp.isValid()) {
+    // Load from the temp alloca and return. The load happens after
+    // forceCleanup to avoid SSA dominance issues with scope blocks.
+    auto loaded = builder.createLoad(loc, returnTemp);
+    cir::ReturnOp::create(builder, loc, {loaded});
+  } else if (fnRetAlloca) {
+
     // Load the value from `__retval` and return it via the `cir.return` op.
     cir::AllocaOp retAlloca =
         mlir::cast<cir::AllocaOp>(fnRetAlloca->getDefiningOp());
     auto value = cir::LoadOp::create(builder, loc, retAlloca.getAllocaType(),
                                      *fnRetAlloca);
-
     cir::ReturnOp::create(builder, loc, {value});
   } else {
+
     cir::ReturnOp::create(builder, loc);
   }
 
@@ -1251,33 +1283,38 @@ mlir::LogicalResult 
CIRGenFunction::emitSwitchStmt(const clang::SwitchStmt &s) {
 
 void CIRGenFunction::emitReturnOfRValue(mlir::Location loc, RValue rv,
                                         QualType ty) {
-  if (rv.isScalar()) {
-    builder.createStore(loc, rv.getValue(), returnValue);
-  } else if (rv.isAggregate()) {
-    Address rvAddr = rv.getAggregateAddress();
-    // If the aggregate is already in the return slot (e.g. a callee was
-    // invoked through a ReturnValueSlot bound to returnValue), the copy is
-    // a no-op.  Calling emitAggregateCopy here would also incorrectly
-    // require the type to have a trivial copy/move.
-    if (rvAddr.getPointer() != returnValue.getPointer()) {
-      LValue dest = makeAddrLValue(returnValue, ty);
-      LValue src = makeAddrLValue(rvAddr, ty);
-      emitAggregateCopy(dest, src, ty, getOverlapForReturnValue());
+  if (returnValue.isValid()) {
+    if (rv.isScalar()) {
+      builder.createStore(loc, rv.getValue(), returnValue);
+    } else if (rv.isAggregate()) {
+      Address rvAddr = rv.getAggregateAddress();
+      if (rvAddr.getPointer() != returnValue.getPointer()) {
+        LValue dest = makeAddrLValue(returnValue, ty);
+        LValue src = makeAddrLValue(rvAddr, ty);
+        emitAggregateCopy(dest, src, ty, getOverlapForReturnValue());
+      }
+    } else {
+      cgm.errorNYI(loc, "emitReturnOfRValue: complex return type");
     }
-  } else {
-    cgm.errorNYI(loc, "emitReturnOfRValue: complex return type");
   }
 
-  // Classic codegen emits a branch through any cleanups before continuing to
-  // a shared return block. Because CIR handles branching through cleanups
-  // during the CFG flattening phase, we can just emit the return statement
-  // directly.
-  // TODO(cir): Eliminate this redundant load and the store above when we can.
-  // Load the value from `__retval` and return it via the `cir.return` op.
-  cir::AllocaOp retAlloca =
-      mlir::cast<cir::AllocaOp>(fnRetAlloca->getDefiningOp());
-  auto value = cir::LoadOp::create(builder, loc, retAlloca.getAllocaType(),
-                                   *fnRetAlloca);
-
-  cir::ReturnOp::create(builder, loc, {value});
+  if (fnRetAlloca) {
+    // Load the value from `__retval` and return it via the `cir.return` op.
+    cir::AllocaOp retAlloca =
+        mlir::cast<cir::AllocaOp>(fnRetAlloca->getDefiningOp());
+    auto value = cir::LoadOp::create(builder, loc, retAlloca.getAllocaType(),
+                                     *fnRetAlloca);
+    cir::ReturnOp::create(builder, loc, {value});
+  } else {
+    // Non-trivially-copyable return type with no __retval:
+    // return the RValue directly.
+    if (rv.isScalar() && rv.getValue()) {
+      cir::ReturnOp::create(builder, loc, {rv.getValue()});
+    } else if (rv.isAggregate() && rv.getAggregateAddress().isValid()) {
+      auto loaded = builder.createLoad(loc, rv.getAggregateAddress());
+      cir::ReturnOp::create(builder, loc, {loaded});
+    } else {
+      cir::ReturnOp::create(builder, loc);
+    }
+  }
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp 
b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index 03d777cf7363a..4b81c337f7af8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -752,19 +752,18 @@ void 
CIRGenFunction::emitCallAndReturnForThunk(cir::FuncOp callee,
   else
     assert(!cir::MissingFeatures::opCallMustTail());
 
-  // Emit return.  For aggregate returns the call has already written the
-  // result through the slot bound to returnValue above; emit the
-  // corresponding load+return here rather than leaving the function to
-  // fall off the end and have LexicalScope::emitImplicitReturn drop a
-  // `cir.trap` / `cir.unreachable` in its place (which would silently
-  // discard the result we just stored).
+  // Route the return through emitReturnOfRValue rather than leaving the
+  // function to fall off the end, where LexicalScope::emitImplicitReturn
+  // would drop a `cir.trap` / `cir.unreachable` and silently discard the
+  // just-computed result.  When the return type has a deleted copy ctor,
+  // returnValue is absent and emitCall materializes a temporary for the
+  // call result; emitReturnOfRValue handles both cases.
   if (!resultType->isVoidType()) {
-    if (slot.isNull())
+    if (slot.isNull() && !hasAggregateEvaluationKind(resultType))
       cgm.getCXXABI().emitReturnFromThunk(*this, rv, resultType);
     else
       emitReturnOfRValue(loc, rv, resultType);
   }
-
   // Disable final ARC autorelease.
   assert(!cir::MissingFeatures::objCLifetime());
 
diff --git a/clang/test/CIR/CodeGenCXX/uncopyable-return.cpp 
b/clang/test/CIR/CodeGenCXX/uncopyable-return.cpp
new file mode 100644
index 0000000000000..77ec6230e8da5
--- /dev/null
+++ b/clang/test/CIR/CodeGenCXX/uncopyable-return.cpp
@@ -0,0 +1,327 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown -fclangir 
-emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown -emit-llvm %s -o 
%t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+//
+//===----------------------------------------------------------------------===//
+// Tests that CIRGen does not emit illegal copies for return values of
+// non-trivially-copyable struct types.
+//
+// Because ABI calling convention lowering is deferred in CIR, we do not use
+// sret parameters for functions that return a non-copyable struct. This is
+// acceptable as long as CIRGen does not introduce a store/load round-trip
+// through __retval, which would constitute an illegal copy for types whose
+// copy constructor is deleted.
+//
+// The expected CIR pattern for such functions is a direct forwarding of the
+// call result to cir.return, without any alloca/store/load indirection.
+//
+// Uses -std=c++17 for guaranteed copy elision (P0135), so that returning a
+// prvalue of a non-copyable type is well-formed without requiring an
+// accessible copy or move constructor.
+//===----------------------------------------------------------------------===//
+
+// --- Test 1: Forwarding return of a struct with deleted copy+move ctor ---
+// This is the core bug from issue #198602. C++17 guaranteed copy elision
+// ensures return foo() is well-formed. CIR must not introduce a store/load
+// round-trip through __retval.
+namespace deleted_copy_ctor {
+struct S {
+  S();
+  S(const S &) = delete;
+  S(S &&) = delete;
+  ~S();
+};
+
+S foo();
+S bar() { return foo(); }
+
+// CIR-LABEL: cir.func {{.*}} @_ZN17deleted_copy_ctor3barEv
+// CIR-NOT:     __retval
+// CIR:         %{{[0-9]+}} = cir.call @_ZN17deleted_copy_ctor3fooEv() : () -> 
!rec{{.*}}
+// CIR-NEXT:    cir.return %{{[0-9]+}} : !rec{{.*}}
+
+// LLVM-LABEL: define {{.*}} @_ZN17deleted_copy_ctor3barEv(
+
+// OGCG-LABEL: define {{.*}} void @_ZN17deleted_copy_ctor3barEv(
+// OGCG:         call void @_ZN17deleted_copy_ctor3fooEv(ptr
+// OGCG-NEXT:    ret void
+}
+
+// --- Test 2: Forwarding return with only copy deleted, move available ---
+// When only the copy constructor is deleted but a move constructor exists,
+// the return should still not go through __retval.
+namespace copy_deleted {
+struct S {
+  S();
+  S(const S &) = delete;
+  S(S &&);
+  ~S();
+};
+
+S foo();
+S bar() { return foo(); }
+
+// CIR-LABEL: cir.func {{.*}} @_ZN12copy_deleted3barEv
+// CIR-NOT:     __retval
+// CIR:         %{{[0-9]+}} = cir.call @_ZN12copy_deleted3fooEv() : () -> 
!rec{{.*}}
+// CIR-NEXT:    cir.return %{{[0-9]+}} : !rec{{.*}}
+
+// LLVM-LABEL: define {{.*}} @_ZN12copy_deleted3barEv(
+
+// OGCG-LABEL: define {{.*}} void @_ZN12copy_deleted3barEv(
+// OGCG:         call void @_ZN12copy_deleted3fooEv(ptr
+// OGCG-NEXT:    ret void
+}
+
+// --- Test 3: Forwarding return with copy deleted by a member ---
+namespace deleted_by_member {
+struct B {
+  B();
+  B(const B &) = delete;
+  B(B &&) = delete;
+  ~B();
+};
+struct A {
+  A();
+  B b;
+  ~A();
+};
+
+A foo();
+A bar() { return foo(); }
+
+// CIR-LABEL: cir.func {{.*}} @_ZN17deleted_by_member3barEv
+// CIR-NOT:     __retval
+// CIR:         %{{[0-9]+}} = cir.call @_ZN17deleted_by_member3fooEv() : () -> 
!rec{{.*}}
+// CIR-NEXT:    cir.return %{{[0-9]+}} : !rec{{.*}}
+
+// LLVM-LABEL: define {{.*}} @_ZN17deleted_by_member3barEv(
+
+// OGCG-LABEL: define {{.*}} void @_ZN17deleted_by_member3barEv(
+// OGCG:         call void @_ZN17deleted_by_member3fooEv(ptr
+// OGCG-NEXT:    ret void
+}
+
+// --- Test 4: Forwarding return with copy deleted by a base class ---
+namespace deleted_by_base {
+struct B {
+  B();
+  B(const B &) = delete;
+  B(B &&) = delete;
+  ~B();
+};
+struct A : B {
+  A();
+  ~A();
+};
+
+A foo();
+A bar() { return foo(); }
+
+// CIR-LABEL: cir.func {{.*}} @_ZN15deleted_by_base3barEv
+// CIR-NOT:     __retval
+// CIR:         %{{[0-9]+}} = cir.call @_ZN15deleted_by_base3fooEv() : () -> 
!rec{{.*}}
+// CIR-NEXT:    cir.return %{{[0-9]+}} : !rec{{.*}}
+
+// LLVM-LABEL: define {{.*}} @_ZN15deleted_by_base3barEv(
+
+// OGCG-LABEL: define {{.*}} void @_ZN15deleted_by_base3barEv(
+// OGCG:         call void @_ZN15deleted_by_base3fooEv(ptr
+// OGCG-NEXT:    ret void
+}
+
+// --- Test 5: Struct with reference member (trivial copy ctor, not deleted) 
---
+// A class with a reference member has a valid trivial copy constructor
+// (reference binding, not copying). The __retval store/load is acceptable.
+namespace implicitly_deleted {
+struct S {
+  S();
+  int &ref;
+  ~S();
+};
+S foo();
+S bar() { return foo(); }
+// CIR-LABEL: cir.func {{.*}} @_ZN18implicitly_deleted3barEv
+// CIR:         cir.alloca !rec{{.*}}, !cir.ptr<!rec{{.*}}, ["__retval"]
+// CIR:         cir.return
+// LLVM-LABEL: define {{.*}} @_ZN18implicitly_deleted3barEv(
+// OGCG-LABEL: define {{.*}} void @_ZN18implicitly_deleted3barEv(
+// OGCG:         call void @_ZN18implicitly_deleted3fooEv(ptr
+// OGCG-NEXT:    ret void
+}
+
+// --- Test 6: Trivially-copyable struct (control group) ---
+// For trivially-copyable types, the __retval store/load pattern is still
+// acceptable since no copy constructor semantics are violated. This test
+// verifies the fix does not regress existing behavior for trivial types.
+namespace trivially_copyable {
+struct S {
+  int x;
+  double y;
+};
+
+S make();
+S use() { return make(); }
+
+// CIR-LABEL: cir.func {{.*}} @_ZN18trivially_copyable3useEv
+// CIR:         cir.alloca !rec{{.*}}, !cir.ptr<!rec{{.*}}, ["__retval"]
+// CIR:         cir.store
+// CIR:         cir.load
+// CIR:         cir.return
+
+// LLVM-LABEL: define {{.*}} @_ZN18trivially_copyable3useEv(
+
+// OGCG-LABEL: define {{.*}} @_ZN18tr...
[truncated]

``````````

</details>


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

Reply via email to