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..66cd67389c1c7 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 RecordDecl *rd = ty->getAsRecordDecl()) { + if (const auto *cxxrd = dyn_cast(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 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, ["__retval", init] +// CIR-NEXT: cir.call @_ZN1SC1Ev(%[[RETVAL]]) : (!cir.ptr) -> () +// CIR-NEXT: %[[RET:.*]] = cir.load %[[RETVAL]] : !cir.ptr, !rec_S +// CIR-NEXT: cir.return %[[RET]] + +// CIR-NOELIDE: cir.func{{.*}} @_Z2f1v() -> !rec_S { +// CIR-NOELIDE-NEXT: %[[RETVAL:.*]] = cir.alloca !rec_S, !cir.ptr, ["__retval"] +// CIR-NOELIDE-NEXT: %[[S:.*]] = cir.alloca !rec_S, !cir.ptr, ["s", init] +// CIR-NOELIDE-NEXT: cir.call @_ZN1SC1Ev(%[[S]]) : (!cir.ptr) -> () +// CIR-NOELIDE-NEXT: cir.call @_ZN1SC1EOS_(%[[RETVAL]], %[[S]]){{.*}} : (!cir.ptr, !cir.ptr) -> () +// CIR-NOELIDE-NEXT: %[[RET:.*]] = cir.load %[[RETVAL]] : !cir.ptr, !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]]