[clang] [CIR] Skip __retval for return types with deleted copy constructors (PR #199205)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 22 05:16:03 PDT 2026
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
More information about the cfe-commits
mailing list