Skip to content

Commit 36ed0ce

Browse files
committed
[OPENMP50]Add basic support for inscan reduction modifier.
Added basic support (parsing/sema checks) for the inscan modifier in the reduction clauses.
1 parent 08776de commit 36ed0ce

File tree

8 files changed

+220
-57
lines changed

8 files changed

+220
-57
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10101,6 +10101,19 @@ def err_omp_depobj_single_clause_expected : Error<
1010110101
"exactly one of 'depend', 'destroy', or 'update' clauses is expected">;
1010210102
def err_omp_scan_single_clause_expected : Error<
1010310103
"exactly one of 'inclusive' or 'exclusive' clauses is expected">;
10104+
def err_omp_inclusive_exclusive_not_reduction : Error<
10105+
"the list item must appear in 'reduction' clause with the 'inscan' modifier "
10106+
"of the parent directive">;
10107+
def err_omp_reduction_not_inclusive_exclusive : Error<
10108+
"the inscan reduction list item must appear as a list item in an 'inclusive' or"
10109+
" 'exclusive' clause on an inner 'omp scan' directive">;
10110+
def err_omp_wrong_inscan_reduction : Error<
10111+
"'inscan' modifier can be used only in 'omp for', 'omp simd', 'omp for simd',"
10112+
" 'omp parallel for', or 'omp parallel for simd' directive">;
10113+
def err_omp_inscan_reduction_expected : Error<
10114+
"expected 'reduction' clause with the 'inscan' modifier">;
10115+
def note_omp_previous_inscan_reduction : Note<
10116+
"'reduction' clause with 'inscan' modifier is used here">;
1010410117
def err_omp_expected_predefined_allocator : Error<
1010510118
"expected one of the predefined allocators for the variables with the static "
1010610119
"storage: 'omp_default_mem_alloc', 'omp_large_cap_mem_alloc', "

clang/include/clang/Basic/OpenMPKinds.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,7 @@ OPENMP_DEPOBJ_CLAUSE(update)
11121112

11131113
// Modifiers for 'reduction' clause.
11141114
OPENMP_REDUCTION_MODIFIER(default)
1115+
OPENMP_REDUCTION_MODIFIER(inscan)
11151116

11161117
#undef OPENMP_REDUCTION_MODIFIER
11171118
#undef OPENMP_SCAN_CLAUSE

clang/lib/Sema/SemaOpenMP.cpp

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,15 @@ class DSAStackTy {
6262
struct DSAVarData {
6363
OpenMPDirectiveKind DKind = OMPD_unknown;
6464
OpenMPClauseKind CKind = OMPC_unknown;
65+
unsigned Modifier = 0;
6566
const Expr *RefExpr = nullptr;
6667
DeclRefExpr *PrivateCopy = nullptr;
6768
SourceLocation ImplicitDSALoc;
6869
DSAVarData() = default;
6970
DSAVarData(OpenMPDirectiveKind DKind, OpenMPClauseKind CKind,
7071
const Expr *RefExpr, DeclRefExpr *PrivateCopy,
71-
SourceLocation ImplicitDSALoc)
72-
: DKind(DKind), CKind(CKind), RefExpr(RefExpr),
72+
SourceLocation ImplicitDSALoc, unsigned Modifier)
73+
: DKind(DKind), CKind(CKind), Modifier(Modifier), RefExpr(RefExpr),
7374
PrivateCopy(PrivateCopy), ImplicitDSALoc(ImplicitDSALoc) {}
7475
};
7576
using OperatorOffsetTy =
@@ -80,6 +81,7 @@ class DSAStackTy {
8081
private:
8182
struct DSAInfo {
8283
OpenMPClauseKind Attributes = OMPC_unknown;
84+
unsigned Modifier = 0;
8385
/// Pointer to a reference expression and a flag which shows that the
8486
/// variable is marked as lastprivate(true) or not (false).
8587
llvm::PointerIntPair<const Expr *, 1, bool> RefExpr;
@@ -164,6 +166,8 @@ class DSAStackTy {
164166
/// List of globals marked as declare target link in this target region
165167
/// (isOpenMPTargetExecutionDirective(Directive) == true).
166168
llvm::SmallVector<DeclRefExpr *, 4> DeclareTargetLinkVarDecls;
169+
/// List of decls used in inclusive/exclusive clauses of the scan directive.
170+
llvm::DenseSet<CanonicalDeclPtr<Decl>> UsedInScanDirective;
167171
SharingMapTy(OpenMPDirectiveKind DKind, DeclarationNameInfo Name,
168172
Scope *CurScope, SourceLocation Loc)
169173
: Directive(DKind), DirectiveName(Name), CurScope(CurScope),
@@ -469,9 +473,20 @@ class DSAStackTy {
469473
/// parent directive.
470474
const ValueDecl *getParentLoopControlVariable(unsigned I) const;
471475

476+
/// Marks the specified decl \p D as used in scan directive.
477+
void markDeclAsUsedInScanDirective(ValueDecl *D) {
478+
if (SharingMapTy *Stack = getSecondOnStackOrNull())
479+
Stack->UsedInScanDirective.insert(D);
480+
}
481+
482+
/// Checks if the specified declaration was used in the inner scan directive.
483+
bool isUsedInScanDirective(ValueDecl *D) const {
484+
return getTopOfStack().UsedInScanDirective.count(D) > 0;
485+
}
486+
472487
/// Adds explicit data sharing attribute to the specified declaration.
473488
void addDSA(const ValueDecl *D, const Expr *E, OpenMPClauseKind A,
474-
DeclRefExpr *PrivateCopy = nullptr);
489+
DeclRefExpr *PrivateCopy = nullptr, unsigned Modifier = 0);
475490

476491
/// Adds additional information for the reduction items with the reduction id
477492
/// represented as an operator.
@@ -1079,6 +1094,7 @@ DSAStackTy::DSAVarData DSAStackTy::getDSA(const_iterator &Iter,
10791094
DVar.PrivateCopy = Data.PrivateCopy;
10801095
DVar.CKind = Data.Attributes;
10811096
DVar.ImplicitDSALoc = Iter->DefaultAttrLoc;
1097+
DVar.Modifier = Data.Modifier;
10821098
return DVar;
10831099
}
10841100

@@ -1226,19 +1242,21 @@ const ValueDecl *DSAStackTy::getParentLoopControlVariable(unsigned I) const {
12261242
}
12271243

12281244
void DSAStackTy::addDSA(const ValueDecl *D, const Expr *E, OpenMPClauseKind A,
1229-
DeclRefExpr *PrivateCopy) {
1245+
DeclRefExpr *PrivateCopy, unsigned Modifier) {
12301246
D = getCanonicalDecl(D);
12311247
if (A == OMPC_threadprivate) {
12321248
DSAInfo &Data = Threadprivates[D];
12331249
Data.Attributes = A;
12341250
Data.RefExpr.setPointer(E);
12351251
Data.PrivateCopy = nullptr;
1252+
Data.Modifier = Modifier;
12361253
} else {
12371254
DSAInfo &Data = getTopOfStack().SharingMap[D];
12381255
assert(Data.Attributes == OMPC_unknown || (A == Data.Attributes) ||
12391256
(A == OMPC_firstprivate && Data.Attributes == OMPC_lastprivate) ||
12401257
(A == OMPC_lastprivate && Data.Attributes == OMPC_firstprivate) ||
12411258
(isLoopControlVariable(D).first && A == OMPC_private));
1259+
Data.Modifier = Modifier;
12421260
if (A == OMPC_lastprivate && Data.Attributes == OMPC_firstprivate) {
12431261
Data.RefExpr.setInt(/*IntVal=*/true);
12441262
return;
@@ -1250,6 +1268,7 @@ void DSAStackTy::addDSA(const ValueDecl *D, const Expr *E, OpenMPClauseKind A,
12501268
Data.PrivateCopy = PrivateCopy;
12511269
if (PrivateCopy) {
12521270
DSAInfo &Data = getTopOfStack().SharingMap[PrivateCopy->getDecl()];
1271+
Data.Modifier = Modifier;
12531272
Data.Attributes = A;
12541273
Data.RefExpr.setPointerAndInt(PrivateCopy, IsLastprivate);
12551274
Data.PrivateCopy = nullptr;
@@ -1355,7 +1374,7 @@ const DSAStackTy::DSAVarData DSAStackTy::getTopMostTaskgroupReductionData(
13551374
"set.");
13561375
TaskgroupDescriptor = I->TaskgroupReductionRef;
13571376
return DSAVarData(OMPD_taskgroup, OMPC_reduction, Data.RefExpr.getPointer(),
1358-
Data.PrivateCopy, I->DefaultAttrLoc);
1377+
Data.PrivateCopy, I->DefaultAttrLoc, /*Modifier=*/0);
13591378
}
13601379
return DSAVarData();
13611380
}
@@ -1380,7 +1399,7 @@ const DSAStackTy::DSAVarData DSAStackTy::getTopMostTaskgroupReductionData(
13801399
"set.");
13811400
TaskgroupDescriptor = I->TaskgroupReductionRef;
13821401
return DSAVarData(OMPD_taskgroup, OMPC_reduction, Data.RefExpr.getPointer(),
1383-
Data.PrivateCopy, I->DefaultAttrLoc);
1402+
Data.PrivateCopy, I->DefaultAttrLoc, /*Modifier=*/0);
13841403
}
13851404
return DSAVarData();
13861405
}
@@ -1455,6 +1474,7 @@ const DSAStackTy::DSAVarData DSAStackTy::getTopDSA(ValueDecl *D,
14551474
if (TI != Threadprivates.end()) {
14561475
DVar.RefExpr = TI->getSecond().RefExpr.getPointer();
14571476
DVar.CKind = OMPC_threadprivate;
1477+
DVar.Modifier = TI->getSecond().Modifier;
14581478
return DVar;
14591479
}
14601480
if (VD && VD->hasAttr<OMPThreadPrivateDeclAttr>()) {
@@ -1546,6 +1566,7 @@ const DSAStackTy::DSAVarData DSAStackTy::getTopDSA(ValueDecl *D,
15461566
DVar.CKind = Data.Attributes;
15471567
DVar.ImplicitDSALoc = I->DefaultAttrLoc;
15481568
DVar.DKind = I->Directive;
1569+
DVar.Modifier = Data.Modifier;
15491570
return DVar;
15501571
}
15511572

@@ -1592,6 +1613,7 @@ const DSAStackTy::DSAVarData DSAStackTy::getTopDSA(ValueDecl *D,
15921613
DVar.CKind = Data.Attributes;
15931614
DVar.ImplicitDSALoc = I->DefaultAttrLoc;
15941615
DVar.DKind = I->Directive;
1616+
DVar.Modifier = Data.Modifier;
15951617
}
15961618

15971619
return DVar;
@@ -2315,11 +2337,64 @@ void Sema::EndOpenMPClause() {
23152337
DSAStack->setClauseParsingMode(/*K=*/OMPC_unknown);
23162338
}
23172339

2318-
static void checkAllocateClauses(Sema &S, DSAStackTy *Stack,
2319-
ArrayRef<OMPClause *> Clauses);
23202340
static std::pair<ValueDecl *, bool>
23212341
getPrivateItem(Sema &S, Expr *&RefExpr, SourceLocation &ELoc,
23222342
SourceRange &ERange, bool AllowArraySection = false);
2343+
2344+
/// Check consistency of the reduction clauses.
2345+
static void checkReductionClauses(Sema &S, DSAStackTy *Stack,
2346+
ArrayRef<OMPClause *> Clauses) {
2347+
bool InscanFound = false;
2348+
SourceLocation InscanLoc;
2349+
// OpenMP 5.0, 2.19.5.4 reduction Clause, Restrictions.
2350+
// A reduction clause without the inscan reduction-modifier may not appear on
2351+
// a construct on which a reduction clause with the inscan reduction-modifier
2352+
// appears.
2353+
for (OMPClause *C : Clauses) {
2354+
if (C->getClauseKind() != OMPC_reduction)
2355+
continue;
2356+
auto *RC = cast<OMPReductionClause>(C);
2357+
if (RC->getModifier() == OMPC_REDUCTION_inscan) {
2358+
InscanFound = true;
2359+
InscanLoc = RC->getModifierLoc();
2360+
break;
2361+
}
2362+
}
2363+
if (InscanFound) {
2364+
for (OMPClause *C : Clauses) {
2365+
if (C->getClauseKind() != OMPC_reduction)
2366+
continue;
2367+
auto *RC = cast<OMPReductionClause>(C);
2368+
if (RC->getModifier() != OMPC_REDUCTION_inscan) {
2369+
S.Diag(RC->getModifier() == OMPC_REDUCTION_unknown
2370+
? RC->getBeginLoc()
2371+
: RC->getModifierLoc(),
2372+
diag::err_omp_inscan_reduction_expected);
2373+
S.Diag(InscanLoc, diag::note_omp_previous_inscan_reduction);
2374+
continue;
2375+
}
2376+
for (Expr *Ref : RC->varlists()) {
2377+
assert(Ref && "NULL expr in OpenMP nontemporal clause.");
2378+
SourceLocation ELoc;
2379+
SourceRange ERange;
2380+
Expr *SimpleRefExpr = Ref;
2381+
auto Res = getPrivateItem(S, SimpleRefExpr, ELoc, ERange,
2382+
/*AllowArraySection=*/true);
2383+
ValueDecl *D = Res.first;
2384+
if (!D)
2385+
continue;
2386+
if (!Stack->isUsedInScanDirective(getCanonicalDecl(D))) {
2387+
S.Diag(Ref->getExprLoc(),
2388+
diag::err_omp_reduction_not_inclusive_exclusive)
2389+
<< Ref->getSourceRange();
2390+
}
2391+
}
2392+
}
2393+
}
2394+
}
2395+
2396+
static void checkAllocateClauses(Sema &S, DSAStackTy *Stack,
2397+
ArrayRef<OMPClause *> Clauses);
23232398
static DeclRefExpr *buildCapture(Sema &S, ValueDecl *D, Expr *CaptureExpr,
23242399
bool WithInit);
23252400

@@ -2396,6 +2471,7 @@ void Sema::EndOpenMPDSABlock(Stmt *CurDirective) {
23962471
// Check allocate clauses.
23972472
if (!CurContext->isDependentContext())
23982473
checkAllocateClauses(*this, DSAStack, D->clauses());
2474+
checkReductionClauses(*this, DSAStack, D->clauses());
23992475
}
24002476

24012477
DSAStack->pop();
@@ -14111,9 +14187,11 @@ struct ReductionData {
1411114187
SmallVector<Decl *, 4> ExprCaptures;
1411214188
/// List of postupdate expressions.
1411314189
SmallVector<Expr *, 4> ExprPostUpdates;
14190+
/// Reduction modifier.
14191+
unsigned RedModifier = 0;
1411414192
ReductionData() = delete;
1411514193
/// Reserves required memory for the reduction data.
14116-
ReductionData(unsigned Size) {
14194+
ReductionData(unsigned Size, unsigned Modifier = 0) : RedModifier(Modifier) {
1411714195
Vars.reserve(Size);
1411814196
Privates.reserve(Size);
1411914197
LHSs.reserve(Size);
@@ -14831,7 +14909,8 @@ static bool actOnOMPReductionKindClause(
1483114909
}
1483214910
// All reduction items are still marked as reduction (to do not increase
1483314911
// code base size).
14834-
Stack->addDSA(D, RefExpr->IgnoreParens(), OMPC_reduction, Ref);
14912+
Stack->addDSA(D, RefExpr->IgnoreParens(), OMPC_reduction, Ref,
14913+
RD.RedModifier);
1483514914
if (CurrDir == OMPD_taskgroup) {
1483614915
if (DeclareReductionRef.isUsable())
1483714916
Stack->addTaskgroupReductionData(D, ReductionIdRange,
@@ -14858,8 +14937,22 @@ OMPClause *Sema::ActOnOpenMPReductionClause(
1485814937
<< getOpenMPClauseName(OMPC_reduction);
1485914938
return nullptr;
1486014939
}
14940+
// OpenMP 5.0, 2.19.5.4 reduction Clause, Restrictions
14941+
// A reduction clause with the inscan reduction-modifier may only appear on a
14942+
// worksharing-loop construct, a worksharing-loop SIMD construct, a simd
14943+
// construct, a parallel worksharing-loop construct or a parallel
14944+
// worksharing-loop SIMD construct.
14945+
if (Modifier == OMPC_REDUCTION_inscan &&
14946+
(DSAStack->getCurrentDirective() != OMPD_for &&
14947+
DSAStack->getCurrentDirective() != OMPD_for_simd &&
14948+
DSAStack->getCurrentDirective() != OMPD_simd &&
14949+
DSAStack->getCurrentDirective() != OMPD_parallel_for &&
14950+
DSAStack->getCurrentDirective() != OMPD_parallel_for_simd)) {
14951+
Diag(ModifierLoc, diag::err_omp_wrong_inscan_reduction);
14952+
return nullptr;
14953+
}
1486114954

14862-
ReductionData RD(VarList.size());
14955+
ReductionData RD(VarList.size(), Modifier);
1486314956
if (actOnOMPReductionKindClause(*this, DSAStack, OMPC_reduction, VarList,
1486414957
StartLoc, LParenLoc, ColonLoc, EndLoc,
1486514958
ReductionIdScopeSpec, ReductionId,
@@ -18161,6 +18254,19 @@ OMPClause *Sema::ActOnOpenMPInclusiveClause(ArrayRef<Expr *> VarList,
1816118254
if (!D)
1816218255
continue;
1816318256

18257+
const DSAStackTy::DSAVarData DVar =
18258+
DSAStack->getTopDSA(D, /*FromParent=*/true);
18259+
// OpenMP 5.0, 2.9.6, scan Directive, Restrictions.
18260+
// A list item that appears in the inclusive or exclusive clause must appear
18261+
// in a reduction clause with the inscan modifier on the enclosing
18262+
// worksharing-loop, worksharing-loop SIMD, or simd construct.
18263+
if (DVar.CKind != OMPC_reduction ||
18264+
DVar.Modifier != OMPC_REDUCTION_inscan)
18265+
Diag(ELoc, diag::err_omp_inclusive_exclusive_not_reduction)
18266+
<< RefExpr->getSourceRange();
18267+
18268+
if (DSAStack->getParentDirective() != OMPD_unknown)
18269+
DSAStack->markDeclAsUsedInScanDirective(D);
1816418270
Vars.push_back(RefExpr);
1816518271
}
1816618272

@@ -18189,6 +18295,19 @@ OMPClause *Sema::ActOnOpenMPExclusiveClause(ArrayRef<Expr *> VarList,
1818918295
if (!D)
1819018296
continue;
1819118297

18298+
const DSAStackTy::DSAVarData DVar =
18299+
DSAStack->getTopDSA(D, /*FromParent=*/true);
18300+
// OpenMP 5.0, 2.9.6, scan Directive, Restrictions.
18301+
// A list item that appears in the inclusive or exclusive clause must appear
18302+
// in a reduction clause with the inscan modifier on the enclosing
18303+
// worksharing-loop, worksharing-loop SIMD, or simd construct.
18304+
if (DVar.CKind != OMPC_reduction ||
18305+
DVar.Modifier != OMPC_REDUCTION_inscan)
18306+
Diag(ELoc, diag::err_omp_inclusive_exclusive_not_reduction)
18307+
<< RefExpr->getSourceRange();
18308+
18309+
if (DSAStack->getParentDirective() != OMPD_unknown)
18310+
DSAStack->markDeclAsUsedInScanDirective(D);
1819218311
Vars.push_back(RefExpr);
1819318312
}
1819418313

clang/test/OpenMP/nesting_of_regions.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2892,7 +2892,7 @@ void foo() {
28922892
}
28932893
#pragma omp parallel for simd
28942894
for (int i = 0; i < 10; ++i) {
2895-
#pragma omp scan // omp45-error {{OpenMP constructs may not be nested inside a simd region}} omp50-error {{exactly one of 'inclusive' or 'exclusive' clauses is expected}}
2895+
#pragma omp scan // omp45-error {{OpenMP constructs may not be nested inside a simd region}} omp50-error {{exactly one of 'inclusive' or 'exclusive' clauses is expected}}
28962896
bar();
28972897
}
28982898
#pragma omp parallel for simd

clang/test/OpenMP/parallel_for_reduction_messages.cpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
// RUN: %clang_cc1 -verify -fopenmp -ferror-limit 150 -o - %s -Wuninitialized
2-
// RUN: %clang_cc1 -verify -fopenmp -std=c++98 -ferror-limit 150 -o - %s -Wuninitialized
3-
// RUN: %clang_cc1 -verify -fopenmp -std=c++11 -ferror-limit 150 -o - %s -Wuninitialized
1+
// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=50 -ferror-limit 150 -o - %s -Wuninitialized
2+
// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=50 -std=c++98 -ferror-limit 150 -o - %s -Wuninitialized
3+
// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=50 -std=c++11 -ferror-limit 150 -o - %s -Wuninitialized
44

5-
// RUN: %clang_cc1 -verify -fopenmp-simd -ferror-limit 150 -o - %s -Wuninitialized
6-
// RUN: %clang_cc1 -verify -fopenmp-simd -std=c++98 -ferror-limit 150 -o - %s -Wuninitialized
7-
// RUN: %clang_cc1 -verify -fopenmp-simd -std=c++11 -ferror-limit 150 -o - %s -Wuninitialized
5+
// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=50 -ferror-limit 150 -o - %s -Wuninitialized
6+
// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=50 -std=c++98 -ferror-limit 150 -o - %s -Wuninitialized
7+
// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=50 -std=c++11 -ferror-limit 150 -o - %s -Wuninitialized
88

99
extern int omp_default_mem_alloc;
1010
void xxx(int argc) {
@@ -78,6 +78,14 @@ class S5 {
7878
#pragma omp for reduction(+:a) // expected-error {{reduction variable must be shared}}
7979
for (int i = 0; i < 10; ++i)
8080
::foo();
81+
#pragma omp parallel for reduction(inscan, +:a)
82+
for (int i = 0; i < 10; ++i) {
83+
#pragma omp scan inclusive(a)
84+
}
85+
#pragma omp parallel for reduction(inscan, +:a)
86+
for (int i = 0; i < 10; ++i) {
87+
#pragma omp scan exclusive(a)
88+
}
8189
}
8290
};
8391
class S6 { // expected-note 3 {{candidate function (the implicit copy assignment operator) not viable: no known conversion from 'int' to 'const S6' for 1st argument}}
@@ -333,5 +341,21 @@ int main(int argc, char **argv) {
333341
for (int i = 0; i < 10; ++i)
334342
m++;
335343

344+
#pragma omp parallel for reduction(inscan, + : m) reduction(*: fl) reduction(default, &&: j) // expected-error 2 {{expected 'reduction' clause with the 'inscan' modifier}} expected-note 2 {{'reduction' clause with 'inscan' modifier is used here}}
345+
for (int i = 0; i < 10; ++i) {
346+
#pragma omp scan exclusive(m)
347+
m++;
348+
}
349+
#pragma omp parallel for reduction(inscan, + : m, fl, j) // expected-error 2 {{the inscan reduction list item must appear as a list item in an 'inclusive' or 'exclusive' clause on an inner 'omp scan' directive}}
350+
for (int i = 0; i < 10; ++i) {
351+
#pragma omp scan exclusive(m)
352+
m++;
353+
}
354+
#pragma omp parallel for reduction(inscan, + : m, fl, j) // expected-error 2 {{the inscan reduction list item must appear as a list item in an 'inclusive' or 'exclusive' clause on an inner 'omp scan' directive}}
355+
for (int i = 0; i < 10; ++i) {
356+
#pragma omp scan inclusive(m)
357+
m++;
358+
}
359+
336360
return tmain(argc) + tmain(fl); // expected-note {{in instantiation of function template specialization 'tmain<int>' requested here}} expected-note {{in instantiation of function template specialization 'tmain<float>' requested here}}
337361
}

0 commit comments

Comments
 (0)