Skip to content

Commit

Permalink
[Clang] [P2025] Analyze only potential scopes for NRVO
Browse files Browse the repository at this point in the history
Before the patch we calculated the NRVO candidate looking at the
variable's whole enclosing scope. The research in [P2025] shows that
looking at the variable's potential scope is better and covers more
cases where NRVO would be safe and desirable.

Many thanks to @Izaron for the original implementation.

Reviewed By: ChuanqiXu

Differential Revision: https://reviews.llvm.org/D119792
  • Loading branch information
rusyaev-roman authored and ChuanqiXu9 committed Jul 26, 2022
1 parent bf759e3 commit fec5ff2
Show file tree
Hide file tree
Showing 6 changed files with 631 additions and 712 deletions.
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,9 @@ C++ Language Changes in Clang
unsigned character literals. This fixes `Issue 54886 <https://github.com/llvm/llvm-project/issues/54886>`_.
- Stopped allowing constraints on non-template functions to be compliant with
dcl.decl.general p4.
- Improved ``copy elision`` optimization. It's possible to apply ``NRVO`` for an object if at the moment when
any return statement of this object is executed, the ``return slot`` won't be occupied by another object.


C++20 Feature Support
^^^^^^^^^^^^^^^^^^^^^
Expand Down
38 changes: 19 additions & 19 deletions clang/include/clang/Sema/Scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,19 @@ class Scope {
/// Used to determine if errors occurred in this scope.
DiagnosticErrorTrap ErrorTrap;

/// A lattice consisting of undefined, a single NRVO candidate variable in
/// this scope, or over-defined. The bit is true when over-defined.
llvm::PointerIntPair<VarDecl *, 1, bool> NRVO;
/// A single NRVO candidate variable in this scope.
/// There are three possible values:
/// 1) pointer to VarDecl that denotes NRVO candidate itself.
/// 2) nullptr value means that NRVO is not allowed in this scope
/// (e.g. return a function parameter).
/// 3) None value means that there is no NRVO candidate in this scope
/// (i.e. there are no return statements in this scope).
Optional<VarDecl *> NRVO;

/// Represents return slots for NRVO candidates in the current scope.
/// If a variable is present in this set, it means that a return slot is
/// available for this variable in the current scope.
llvm::SmallPtrSet<VarDecl *, 8> ReturnSlots;

void setFlags(Scope *Parent, unsigned F);

Expand Down Expand Up @@ -304,6 +314,10 @@ class Scope {
bool decl_empty() const { return DeclsInScope.empty(); }

void AddDecl(Decl *D) {
if (auto *VD = dyn_cast<VarDecl>(D))
if (!isa<ParmVarDecl>(VD))
ReturnSlots.insert(VD);

DeclsInScope.insert(D);
}

Expand Down Expand Up @@ -527,23 +541,9 @@ class Scope {
UsingDirectives.end());
}

void addNRVOCandidate(VarDecl *VD) {
if (NRVO.getInt())
return;
if (NRVO.getPointer() == nullptr) {
NRVO.setPointer(VD);
return;
}
if (NRVO.getPointer() != VD)
setNoNRVO();
}

void setNoNRVO() {
NRVO.setInt(true);
NRVO.setPointer(nullptr);
}
void updateNRVOCandidate(VarDecl *VD);

void mergeNRVOIntoParent();
void applyNRVO();

/// Init - This is used by the parser to implement scope caching.
void Init(Scope *parent, unsigned flags);
Expand Down
82 changes: 68 additions & 14 deletions clang/lib/Sema/Scope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void Scope::Init(Scope *parent, unsigned flags) {
UsingDirectives.clear();
Entity = nullptr;
ErrorTrap.reset();
NRVO.setPointerAndInt(nullptr, false);
NRVO = None;
}

bool Scope::containedInPrototypeScope() const {
Expand All @@ -118,19 +118,71 @@ void Scope::AddFlags(unsigned FlagsToSet) {
Flags |= FlagsToSet;
}

void Scope::mergeNRVOIntoParent() {
if (VarDecl *Candidate = NRVO.getPointer()) {
if (isDeclScope(Candidate))
Candidate->setNRVOVariable(true);
// The algorithm for updating NRVO candidate is as follows:
// 1. All previous candidates become invalid because a new NRVO candidate is
// obtained. Therefore, we need to clear return slots for other
// variables defined before the current return statement in the current
// scope and in outer scopes.
// 2. Store the new candidate if its return slot is available. Otherwise,
// there is no NRVO candidate so far.
void Scope::updateNRVOCandidate(VarDecl *VD) {
auto UpdateReturnSlotsInScopeForVD = [VD](Scope *S) -> bool {
bool IsReturnSlotFound = S->ReturnSlots.contains(VD);

// We found a candidate variable that can be put into a return slot.
// Clear the set, because other variables cannot occupy a return
// slot in the same scope.
S->ReturnSlots.clear();

if (IsReturnSlotFound)
S->ReturnSlots.insert(VD);

return IsReturnSlotFound;
};

bool CanBePutInReturnSlot = false;

for (auto *S = this; S; S = S->getParent()) {
CanBePutInReturnSlot |= UpdateReturnSlotsInScopeForVD(S);

if (S->getEntity())
break;
}

if (getEntity())
// Consider the variable as NRVO candidate if the return slot is available
// for it in the current scope, or if it can be available in outer scopes.
NRVO = CanBePutInReturnSlot ? VD : nullptr;
}

void Scope::applyNRVO() {
// There is no NRVO candidate in the current scope.
if (!NRVO.hasValue())
return;

if (NRVO.getInt())
getParent()->setNoNRVO();
else if (NRVO.getPointer())
getParent()->addNRVOCandidate(NRVO.getPointer());
if (*NRVO && isDeclScope(*NRVO))
NRVO.getValue()->setNRVOVariable(true);

// It's necessary to propagate NRVO candidate to the parent scope for cases
// when the parent scope doesn't contain a return statement.
// For example:
// X foo(bool b) {
// X x;
// if (b)
// return x;
// exit(0);
// }
// Also, we need to propagate nullptr value that means NRVO is not
// allowed in this scope.
// For example:
// X foo(bool b) {
// X x;
// if (b)
// return x;
// else
// return X(); // NRVO is not allowed
// }
if (!getEntity())
getParent()->NRVO = *NRVO;
}

LLVM_DUMP_METHOD void Scope::dump() const { dumpImpl(llvm::errs()); }
Expand Down Expand Up @@ -193,8 +245,10 @@ void Scope::dumpImpl(raw_ostream &OS) const {
if (const DeclContext *DC = getEntity())
OS << "Entity : (clang::DeclContext*)" << DC << '\n';

if (NRVO.getInt())
OS << "NRVO not allowed\n";
else if (NRVO.getPointer())
OS << "NRVO candidate : (clang::VarDecl*)" << NRVO.getPointer() << '\n';
if (!NRVO)
OS << "there is no NRVO candidate\n";
else if (*NRVO)
OS << "NRVO candidate : (clang::VarDecl*)" << *NRVO << '\n';
else
OS << "NRVO is not allowed\n";
}
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2092,7 +2092,7 @@ static void CheckPoppedLabel(LabelDecl *L, Sema &S) {
}

void Sema::ActOnPopScope(SourceLocation Loc, Scope *S) {
S->mergeNRVOIntoParent();
S->applyNRVO();

if (S->decl_empty()) return;
assert((S->getFlags() & (Scope::DeclScope | Scope::TemplateParamScope)) &&
Expand Down
10 changes: 4 additions & 6 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3898,12 +3898,10 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp,
if (R.isInvalid() || ExprEvalContexts.back().isDiscardedStatementContext())
return R;

if (VarDecl *VD =
const_cast<VarDecl*>(cast<ReturnStmt>(R.get())->getNRVOCandidate())) {
CurScope->addNRVOCandidate(VD);
} else {
CurScope->setNoNRVO();
}
VarDecl *VD =
const_cast<VarDecl *>(cast<ReturnStmt>(R.get())->getNRVOCandidate());

CurScope->updateNRVOCandidate(VD);

CheckJumpOutOfSEHFinally(*this, ReturnLoc, *CurScope->getFnParent());

Expand Down
Loading

0 comments on commit fec5ff2

Please sign in to comment.