Skip to content

[clang] Define convergence in C++ languages such as HIP, CUDA, OpenCL #136280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
795 changes: 795 additions & 0 deletions clang/docs/ThreadConvergence.rst

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions clang/docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
.. |ReleaseNotesTitle| replace:: {in_progress_title} Release Notes
"""

# -- General options for output ------------------------------------------------

numfig = True

# -- Options for HTML output ---------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
Expand Down
1 change: 1 addition & 0 deletions clang/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Using Clang as a Compiler
OpenMPSupport
SYCLSupport
HIPSupport
ThreadConvergence
HLSL/HLSLDocs
ThinLTO
APINotes
Expand Down
14 changes: 12 additions & 2 deletions clang/include/clang/AST/ParentMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#ifndef LLVM_CLANG_AST_PARENTMAP_H
#define LLVM_CLANG_AST_PARENTMAP_H

#include <utility>

namespace clang {
class Stmt;
class Expr;
Expand All @@ -23,17 +25,21 @@ class ParentMap {
ParentMap(Stmt* ASTRoot);
~ParentMap();

using ValueT = std::pair<Stmt *, unsigned>;

/// Adds and/or updates the parent/child-relations of the complete
/// stmt tree of S. All children of S including indirect descendants are
/// visited and updated or inserted but not the parents of S.
void addStmt(Stmt* S);
void addStmt(Stmt *S, unsigned Depth);

/// Manually sets the parent of \p S to \p Parent.
///
/// If \p S is already in the map, this method will update the mapping.
void setParent(const Stmt *S, const Stmt *Parent);

ValueT lookup(Stmt *) const;
Stmt *getParent(Stmt*) const;
unsigned getParentDepth(Stmt *) const;
Stmt *getParentIgnoreParens(Stmt *) const;
Stmt *getParentIgnoreParenCasts(Stmt *) const;
Stmt *getParentIgnoreParenImpCasts(Stmt *) const;
Expand All @@ -43,6 +49,10 @@ class ParentMap {
return getParent(const_cast<Stmt*>(S));
}

unsigned getParentDepth(const Stmt *S) const {
return getParentDepth(const_cast<Stmt *>(S));
}

const Stmt *getParentIgnoreParens(const Stmt *S) const {
return getParentIgnoreParens(const_cast<Stmt*>(S));
}
Expand All @@ -60,5 +70,5 @@ class ParentMap {
}
};

} // end clang namespace
} // end namespace clang
#endif
25 changes: 25 additions & 0 deletions clang/include/clang/Analysis/Analyses/ConvergenceCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Analyse implicit convergence in the CFG.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_CONVERGENCECHECK_H
#define LLVM_CLANG_ANALYSIS_ANALYSES_CONVERGENCECHECK_H

namespace clang {
class AnalysisDeclContext;
class Sema;
class Stmt;

void analyzeForConvergence(Sema &S, AnalysisDeclContext &AC);

} // end namespace clang

#endif // LLVM_CLANG_ANALYSIS_ANALYSES_CONVERGENCECHECK_H
2 changes: 2 additions & 0 deletions clang/include/clang/Analysis/CFG.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ class CFGStmt : public CFGElement {
return static_cast<const Stmt *>(Data1.getPointer());
}

Stmt *getStmt() { return static_cast<Stmt *>(Data1.getPointer()); }

private:
friend class CFGElement;

Expand Down
16 changes: 7 additions & 9 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -1675,15 +1675,13 @@ of the condition.
def ConvergentDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``convergent`` attribute can be placed on a function declaration. It is
translated into the LLVM ``convergent`` attribute, which indicates that the call
instructions of a function with this attribute cannot be made control-dependent
on any additional values.

This attribute is different from ``noduplicate`` because it allows duplicating
function calls if it can be proved that the duplicated function calls are
not made control-dependent on any additional values, e.g., unrolling a loop
executed by all work items.
The ``convergent`` attribute can be placed on a function declaration to indicate
that every call to this function should be treated as a
:ref:`convergent operation<convergent-operation>`.

This attribute is different from ``noduplicate``. In general, ``convergent``
calls can be duplicated if each copy retains the threads that are converged at
each execution of the original call.

Sample usage:

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,9 @@ def HIPOpenMPOffloading: DiagGroup<"hip-omp-target-directives">;
// Warning about multiple GPUs are detected.
def MultiGPU: DiagGroup<"multi-gpu">;

// A warning group for thread convergence.
def Convergence : DiagGroup<"convergence">;

// Warnings which cause linking of the runtime libraries like
// libc and the CRT to be skipped.
def AVRRtlibLinkingQuirks : DiagGroup<"avr-rtlib-linking-quirks">;
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -6510,6 +6510,10 @@ def warn_unused_label : Warning<"unused label %0">,
def err_continue_from_cond_var_init : Error<
"cannot jump from this continue statement to the loop increment; "
"jump bypasses initialization of loop condition variable">;
def note_goto_affects_convergence : Note<
"jump from this goto statement affects convergence">;
def note_switch_case_affects_convergence : Note<
"jump to this case statement affects convergence of loop">;
def err_goto_into_protected_scope : Error<
"cannot jump from this goto statement to its label">;
def ext_goto_into_protected_scope : ExtWarn<
Expand Down Expand Up @@ -10513,6 +10517,12 @@ def warn_loop_ctrl_binds_to_inner : Warning<
def err_omp_bind_required_on_loop : Error<
"expected 'bind' clause for 'loop' construct without an enclosing OpenMP "
"construct">;
def warn_loop_side_entry_affects_convergence : Warning<
"jump enters an iteration statement; convergence is implementation-defined">,
InGroup<Convergence>, DefaultIgnore;
def warn_cycle_created_by_goto_affects_convergence : Warning<
"convergence is implementation-defined due to a backwards goto">,
InGroup<Convergence>, DefaultIgnore;
def err_omp_loop_reduction_clause : Error<
"'reduction' clause not allowed with '#pragma omp loop bind(teams)'">;
def warn_break_binds_to_switch : Warning<
Expand Down
65 changes: 35 additions & 30 deletions clang/lib/AST/ParentMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@
#include "clang/AST/ParentMap.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtObjC.h"
#include "llvm/ADT/DenseMap.h"

using namespace clang;

typedef llvm::DenseMap<Stmt*, Stmt*> MapTy;
typedef llvm::DenseMap<Stmt *, ParentMap::ValueT> MapTy;

enum OpaqueValueMode {
OV_Transparent,
OV_Opaque
};

static void BuildParentMap(MapTy& M, Stmt* S,
static void BuildParentMap(MapTy &M, Stmt *S, unsigned ParentDepth,
OpaqueValueMode OVMode = OV_Transparent) {
if (!S)
return;
Expand All @@ -35,41 +36,41 @@ static void BuildParentMap(MapTy& M, Stmt* S,
PseudoObjectExpr *POE = cast<PseudoObjectExpr>(S);
Expr *SF = POE->getSyntacticForm();

auto [Iter, Inserted] = M.try_emplace(SF, S);
auto [Iter, Inserted] = M.try_emplace(SF, S, ParentDepth);
if (!Inserted) {
// Nothing more to do in opaque mode if we are updating an existing map.
if (OVMode == OV_Opaque)
break;
// Update the entry in transparent mode, and clear existing state.
Iter->second = S;
Iter->second = {S, ParentDepth};
for (Stmt *SubStmt : S->children())
M.erase(SubStmt);
}
BuildParentMap(M, SF, OV_Transparent);
BuildParentMap(M, SF, ParentDepth + 1, OV_Transparent);

for (PseudoObjectExpr::semantics_iterator I = POE->semantics_begin(),
E = POE->semantics_end();
I != E; ++I) {
M[*I] = S;
BuildParentMap(M, *I, OV_Opaque);
M[*I] = {S, ParentDepth + 1};
BuildParentMap(M, *I, ParentDepth + 1, OV_Opaque);
}
break;
}
case Stmt::BinaryConditionalOperatorClass: {
assert(OVMode == OV_Transparent && "Should not appear alongside OVEs");
BinaryConditionalOperator *BCO = cast<BinaryConditionalOperator>(S);

M[BCO->getCommon()] = S;
BuildParentMap(M, BCO->getCommon(), OV_Transparent);
M[BCO->getCommon()] = {S, ParentDepth + 1};
BuildParentMap(M, BCO->getCommon(), ParentDepth + 1, OV_Transparent);

M[BCO->getCond()] = S;
BuildParentMap(M, BCO->getCond(), OV_Opaque);
M[BCO->getCond()] = {S, ParentDepth + 1};
BuildParentMap(M, BCO->getCond(), ParentDepth + 1, OV_Opaque);

M[BCO->getTrueExpr()] = S;
BuildParentMap(M, BCO->getTrueExpr(), OV_Opaque);
M[BCO->getTrueExpr()] = {S, ParentDepth + 1};
BuildParentMap(M, BCO->getTrueExpr(), ParentDepth + 1, OV_Opaque);

M[BCO->getFalseExpr()] = S;
BuildParentMap(M, BCO->getFalseExpr(), OV_Transparent);
M[BCO->getFalseExpr()] = {S, ParentDepth + 1};
BuildParentMap(M, BCO->getFalseExpr(), ParentDepth + 1, OV_Transparent);

break;
}
Expand All @@ -81,33 +82,33 @@ static void BuildParentMap(MapTy& M, Stmt* S,
// parent, then not reassign that when traversing the semantic expressions.
OpaqueValueExpr *OVE = cast<OpaqueValueExpr>(S);
Expr *SrcExpr = OVE->getSourceExpr();
auto [Iter, Inserted] = M.try_emplace(SrcExpr, S);
auto [Iter, Inserted] = M.try_emplace(SrcExpr, S, ParentDepth);
// Force update in transparent mode.
if (!Inserted && OVMode == OV_Transparent) {
Iter->second = S;
Iter->second = {S, ParentDepth};
Inserted = true;
}
if (Inserted)
BuildParentMap(M, SrcExpr, OV_Transparent);
BuildParentMap(M, SrcExpr, ParentDepth + 1, OV_Transparent);
break;
}
case Stmt::CapturedStmtClass:
for (Stmt *SubStmt : S->children()) {
if (SubStmt) {
M[SubStmt] = S;
BuildParentMap(M, SubStmt, OVMode);
M[SubStmt] = {S, ParentDepth + 1};
BuildParentMap(M, SubStmt, ParentDepth + 1, OVMode);
}
}
if (Stmt *SubStmt = cast<CapturedStmt>(S)->getCapturedStmt()) {
M[SubStmt] = S;
BuildParentMap(M, SubStmt, OVMode);
M[SubStmt] = {S, ParentDepth + 1};
BuildParentMap(M, SubStmt, ParentDepth + 1, OVMode);
}
break;
default:
for (Stmt *SubStmt : S->children()) {
if (SubStmt) {
M[SubStmt] = S;
BuildParentMap(M, SubStmt, OVMode);
M[SubStmt] = {S, ParentDepth + 1};
BuildParentMap(M, SubStmt, ParentDepth + 1, OVMode);
}
}
break;
Expand All @@ -117,7 +118,7 @@ static void BuildParentMap(MapTy& M, Stmt* S,
ParentMap::ParentMap(Stmt *S) : Impl(nullptr) {
if (S) {
MapTy *M = new MapTy();
BuildParentMap(*M, S);
BuildParentMap(*M, S, 0);
Impl = M;
}
}
Expand All @@ -126,24 +127,29 @@ ParentMap::~ParentMap() {
delete (MapTy*) Impl;
}

void ParentMap::addStmt(Stmt* S) {
void ParentMap::addStmt(Stmt *S, unsigned Depth) {
if (S) {
BuildParentMap(*(MapTy*) Impl, S);
BuildParentMap(*(MapTy *)Impl, S, Depth);
}
}

void ParentMap::setParent(const Stmt *S, const Stmt *Parent) {
assert(S);
assert(Parent);
MapTy *M = reinterpret_cast<MapTy *>(Impl);
M->insert(std::make_pair(const_cast<Stmt *>(S), const_cast<Stmt *>(Parent)));
M->try_emplace(const_cast<Stmt *>(S), const_cast<Stmt *>(Parent),
getParentDepth(Parent) + 1);
}

Stmt* ParentMap::getParent(Stmt* S) const {
ParentMap::ValueT ParentMap::lookup(Stmt *S) const {
MapTy* M = (MapTy*) Impl;
return M->lookup(S);
}

Stmt *ParentMap::getParent(Stmt *S) const { return lookup(S).first; }

unsigned ParentMap::getParentDepth(Stmt *S) const { return lookup(S).second; }

Stmt *ParentMap::getParentIgnoreParens(Stmt *S) const {
do {
S = getParent(S);
Expand Down Expand Up @@ -221,4 +227,3 @@ bool ParentMap::isConsumedExpr(Expr* E) const {
return true;
}
}

2 changes: 1 addition & 1 deletion clang/lib/Analysis/AnalysisDeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ ParentMap &AnalysisDeclContext::getParentMap() {
PM.reset(new ParentMap(getBody()));
if (const auto *C = dyn_cast<CXXConstructorDecl>(getDecl())) {
for (const auto *I : C->inits()) {
PM->addStmt(I->getInit());
PM->addStmt(I->getInit(), 0);
}
}
if (builtCFG)
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_clang_library(clangAnalysis
Dominators.cpp
ExprMutationAnalyzer.cpp
FixitUtil.cpp
ConvergenceCheck.cpp
IntervalPartition.cpp
IssueHash.cpp
LiveVariables.cpp
Expand Down
Loading
Loading