Flang OpenMP Report Plugin

This plugin parses Fortran files and creates a
YAML report with all the OpenMP constructs and
clauses seen in the file.

The following tests have been modified to be
compatible for testing the plugin, hence why
they are not reused from another directory:

- omp-atomic.f90
- omp-declarative-directive.f90
- omp-device-constructs.f90

The plugin outputs a single file in the same
directory as the source file in the following format:
`<source-file-name>.yaml`

Building the plugin:
`ninja flangOmpReport`

Running the plugin:
`./bin/flang-new -fc1 -load lib/flangOmpReport.so -plugin flang-omp-report -fopenmp <source_file.f90>`

Co-authored-by: Kiran Chandramohan <kiran.chandramohan@arm.com>
Co-authored-by: Stuart Ellis <stuart.ellis@arm.com>

Reviewed By: awarzynski, kiranchandramohan

Differential Revision: https://reviews.llvm.org/D109890
This commit is contained in:
Stuart Ellis 2021-09-28 22:17:27 +01:00 committed by Ivan Zhechev
parent bdde959533
commit 38c42d42eb
8 changed files with 666 additions and 0 deletions

View File

@ -12,3 +12,4 @@ target_link_libraries(external-hello-world
) )
add_subdirectory(PrintFlangFunctionNames) add_subdirectory(PrintFlangFunctionNames)
add_subdirectory(flang-omp-report-plugin)

View File

@ -0,0 +1,6 @@
add_llvm_library(
flangOmpReport
MODULE
flang-omp-report.cpp
flang-omp-report-visitor.cpp
)

View File

@ -0,0 +1,269 @@
//===-- examples/flang-omp-report-plugin/flang-omp-report-visitor.cpp -----===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "flang-omp-report-visitor.h"
namespace Fortran {
namespace parser {
bool operator<(const ClauseInfo &a, const ClauseInfo &b) {
return a.clause < b.clause;
}
bool operator==(const ClauseInfo &a, const ClauseInfo &b) {
return a.clause == b.clause && a.clauseDetails == b.clauseDetails;
}
bool operator!=(const ClauseInfo &a, const ClauseInfo &b) { return !(a == b); }
bool operator==(const LogRecord &a, const LogRecord &b) {
return a.file == b.file && a.line == b.line && a.construct == b.construct &&
a.clauses == b.clauses;
}
bool operator!=(const LogRecord &a, const LogRecord &b) { return !(a == b); }
std::string OpenMPCounterVisitor::normalize_construct_name(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::tolower(c); });
return s;
}
ClauseInfo OpenMPCounterVisitor::normalize_clause_name(const std::string &s) {
std::size_t start = s.find('(');
std::size_t end = s.find(')');
std::string clauseName;
if (start != std::string::npos && end != std::string::npos) {
clauseName = s.substr(0, start);
clauseDetails = s.substr(start + 1, end - start - 1);
} else {
clauseName = s;
}
std::transform(clauseName.begin(), clauseName.end(), clauseName.begin(),
[](unsigned char c) { return std::tolower(c); });
std::transform(clauseDetails.begin(), clauseDetails.end(),
clauseDetails.begin(), [](unsigned char c) { return std::tolower(c); });
return ClauseInfo{clauseName, clauseDetails};
}
SourcePosition OpenMPCounterVisitor::getLocation(const OmpWrapperType &w) {
if (auto *val = std::get_if<const OpenMPConstruct *>(&w)) {
const OpenMPConstruct *o{*val};
return getLocation(*o);
}
return getLocation(*std::get<const OpenMPDeclarativeConstruct *>(w));
}
SourcePosition OpenMPCounterVisitor::getLocation(
const OpenMPDeclarativeConstruct &c) {
return std::visit(
[&](const auto &o) -> SourcePosition {
return parsing->allCooked().GetSourcePositionRange(o.source)->first;
},
c.u);
}
SourcePosition OpenMPCounterVisitor::getLocation(const OpenMPConstruct &c) {
return std::visit(
Fortran::common::visitors{
[&](const OpenMPStandaloneConstruct &c) -> SourcePosition {
return parsing->allCooked().GetSourcePositionRange(c.source)->first;
},
// OpenMPSectionsConstruct, OpenMPLoopConstruct,
// OpenMPBlockConstruct, OpenMPCriticalConstruct Get the source from
// the directive field.
[&](const auto &c) -> SourcePosition {
const CharBlock &source{std::get<0>(c.t).source};
return (parsing->allCooked().GetSourcePositionRange(source))->first;
},
[&](const OpenMPAtomicConstruct &c) -> SourcePosition {
return std::visit(
[&](const auto &o) -> SourcePosition {
const CharBlock &source{std::get<Verbatim>(o.t).source};
return parsing->allCooked()
.GetSourcePositionRange(source)
->first;
},
c.u);
},
},
c.u);
}
std::string OpenMPCounterVisitor::getName(const OmpWrapperType &w) {
if (auto *val = std::get_if<const OpenMPConstruct *>(&w)) {
const OpenMPConstruct *o{*val};
return getName(*o);
}
return getName(*std::get<const OpenMPDeclarativeConstruct *>(w));
}
std::string OpenMPCounterVisitor::getName(const OpenMPDeclarativeConstruct &c) {
return std::visit(
[&](const auto &o) -> std::string {
const CharBlock &source{std::get<Verbatim>(o.t).source};
return normalize_construct_name(source.ToString());
},
c.u);
}
std::string OpenMPCounterVisitor::getName(const OpenMPConstruct &c) {
return std::visit(
Fortran::common::visitors{
[&](const OpenMPStandaloneConstruct &c) -> std::string {
return std::visit(
[&](const auto &c) {
// Get source from the directive or verbatim fields
const CharBlock &source{std::get<0>(c.t).source};
return normalize_construct_name(source.ToString());
},
c.u);
},
[&](const OpenMPExecutableAllocate &c) -> std::string {
const CharBlock &source{std::get<0>(c.t).source};
return normalize_construct_name(source.ToString());
},
[&](const OpenMPDeclarativeAllocate &c) -> std::string {
const CharBlock &source{std::get<0>(c.t).source};
return normalize_construct_name(source.ToString());
},
[&](const OpenMPAtomicConstruct &c) -> std::string {
return std::visit(
[&](const auto &c) {
// Get source from the verbatim fields
const CharBlock &source{std::get<Verbatim>(c.t).source};
return "atomic-" +
normalize_construct_name(source.ToString());
},
c.u);
},
// OpenMPSectionsConstruct, OpenMPLoopConstruct,
// OpenMPBlockConstruct, OpenMPCriticalConstruct Get the source from
// the directive field of the begin directive or from the verbatim
// field of the begin directive in Critical
[&](const auto &c) -> std::string {
const CharBlock &source{std::get<0>(std::get<0>(c.t).t).source};
return normalize_construct_name(source.ToString());
},
},
c.u);
}
bool OpenMPCounterVisitor::Pre(const OpenMPDeclarativeConstruct &c) {
OmpWrapperType *ow{new OmpWrapperType(&c)};
ompWrapperStack.push_back(ow);
return true;
}
bool OpenMPCounterVisitor::Pre(const OpenMPConstruct &c) {
OmpWrapperType *ow{new OmpWrapperType(&c)};
ompWrapperStack.push_back(ow);
return true;
}
bool OpenMPCounterVisitor::Pre(const OmpEndLoopDirective &c) { return true; }
bool OpenMPCounterVisitor::Pre(const DoConstruct &) {
loopLogRecordStack.push_back(curLoopLogRecord);
return true;
}
void OpenMPCounterVisitor::Post(const OpenMPDeclarativeConstruct &) {
PostConstructsCommon();
}
void OpenMPCounterVisitor::Post(const OpenMPConstruct &) {
PostConstructsCommon();
}
void OpenMPCounterVisitor::PostConstructsCommon() {
OmpWrapperType *curConstruct = ompWrapperStack.back();
std::sort(
clauseStrings[curConstruct].begin(), clauseStrings[curConstruct].end());
SourcePosition s{getLocation(*curConstruct)};
LogRecord r{s.file.path(), s.line, getName(*curConstruct),
clauseStrings[curConstruct]};
constructClauses.push_back(r);
// Keep track of loop log records if it can potentially have the
// nowait clause added on later.
if (const auto *oc = std::get_if<const OpenMPConstruct *>(curConstruct)) {
if (const auto *olc = std::get_if<OpenMPLoopConstruct>(&(*oc)->u)) {
const auto &beginLoopDir{
std::get<Fortran::parser::OmpBeginLoopDirective>(olc->t)};
const auto &beginDir{
std::get<Fortran::parser::OmpLoopDirective>(beginLoopDir.t)};
if (beginDir.v == llvm::omp::Directive::OMPD_do ||
beginDir.v == llvm::omp::Directive::OMPD_do_simd) {
curLoopLogRecord = &constructClauses.back();
}
}
}
auto it = clauseStrings.find(curConstruct);
clauseStrings.erase(it);
ompWrapperStack.pop_back();
delete curConstruct;
}
void OpenMPCounterVisitor::Post(const OmpEndLoopDirective &c) {}
void OpenMPCounterVisitor::Post(const OmpProcBindClause::Type &c) {
clauseDetails += "type=" + OmpProcBindClause::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpDefaultClause::Type &c) {
clauseDetails += "type=" + OmpDefaultClause::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(
const OmpDefaultmapClause::ImplicitBehavior &c) {
clauseDetails +=
"implicit_behavior=" + OmpDefaultmapClause::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(
const OmpDefaultmapClause::VariableCategory &c) {
clauseDetails +=
"variable_category=" + OmpDefaultmapClause::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpScheduleModifierType::ModType &c) {
clauseDetails += "modifier=" + OmpScheduleModifierType::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpLinearModifier::Type &c) {
clauseDetails += "modifier=" + OmpLinearModifier::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpDependenceType::Type &c) {
clauseDetails += "type=" + OmpDependenceType::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpMapType::Type &c) {
clauseDetails += "type=" + OmpMapType::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpScheduleClause::ScheduleType &c) {
clauseDetails += "type=" + OmpScheduleClause::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpIfClause::DirectiveNameModifier &c) {
clauseDetails += "name_modifier=" + OmpIfClause::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpCancelType::Type &c) {
clauseDetails += "type=" + OmpCancelType::EnumToString(c) + ";";
}
void OpenMPCounterVisitor::Post(const OmpClause &c) {
PostClauseCommon(normalize_clause_name(c.source.ToString()));
clauseDetails.clear();
}
void OpenMPCounterVisitor::PostClauseCommon(const ClauseInfo &ci) {
// The end loop construct (!$omp end do) can contain a nowait clause.
// The flang parser does not parse the end loop construct as part of
// the OpenMP construct for the loop construct. So the end loop is left
// hanging as a separate executable construct. If a nowait clause is seen in
// an end loop construct we have to find the associated loop construct and
// add nowait to its list of clauses. Note: This is not a bug in flang, the
// parse tree is corrected during semantic analysis.
if (ci.clause == "nowait") {
assert(curLoopLogRecord &&
"loop Construct should be visited before a nowait clause");
constructClauseCount[std::make_pair(
curLoopLogRecord->construct, ci.clause)]++;
curLoopLogRecord->clauses.push_back(ci);
} else {
assert(!ompWrapperStack.empty() &&
"Construct should be visited before clause");
constructClauseCount[std::make_pair(
getName(*ompWrapperStack.back()), ci.clause)]++;
clauseStrings[ompWrapperStack.back()].push_back(ci);
}
}
void OpenMPCounterVisitor::Post(const DoConstruct &) {
curLoopLogRecord = loopLogRecordStack.back();
loopLogRecordStack.pop_back();
}
} // namespace parser
} // namespace Fortran

View File

@ -0,0 +1,106 @@
//===-- examples/flang-omp-report-plugin/flang-omp-report-visitor.h -------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_FLANG_OMP_REPORT_VISITOR_H
#define FORTRAN_FLANG_OMP_REPORT_VISITOR_H
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Parser/parsing.h"
#include <deque>
#include <map>
#include <string>
#include <vector>
namespace Fortran {
namespace parser {
struct ClauseInfo {
std::string clause;
std::string clauseDetails;
ClauseInfo() {}
ClauseInfo(const std::string &c, const std::string &cd)
: clause{c}, clauseDetails{cd} {}
ClauseInfo(const std::pair<std::string, std::string> &p)
: clause{std::get<0>(p)}, clauseDetails{std::get<1>(p)} {}
};
bool operator<(const ClauseInfo &a, const ClauseInfo &b);
bool operator==(const ClauseInfo &a, const ClauseInfo &b);
bool operator!=(const ClauseInfo &a, const ClauseInfo &b);
struct LogRecord {
std::string file;
int line;
std::string construct;
std::vector<ClauseInfo> clauses;
};
bool operator==(const LogRecord &a, const LogRecord &b);
bool operator!=(const LogRecord &a, const LogRecord &b);
using OmpWrapperType =
std::variant<const OpenMPConstruct *, const OpenMPDeclarativeConstruct *>;
struct OpenMPCounterVisitor {
std::string normalize_construct_name(std::string s);
ClauseInfo normalize_clause_name(const std::string &s);
SourcePosition getLocation(const OmpWrapperType &w);
SourcePosition getLocation(const OpenMPDeclarativeConstruct &c);
SourcePosition getLocation(const OpenMPConstruct &c);
std::string getName(const OmpWrapperType &w);
std::string getName(const OpenMPDeclarativeConstruct &c);
std::string getName(const OpenMPConstruct &c);
template <typename A> bool Pre(const A &) { return true; }
template <typename A> void Post(const A &) {}
bool Pre(const OpenMPDeclarativeConstruct &c);
bool Pre(const OpenMPConstruct &c);
bool Pre(const OmpEndLoopDirective &c);
bool Pre(const DoConstruct &);
void Post(const OpenMPDeclarativeConstruct &);
void Post(const OpenMPConstruct &);
void PostConstructsCommon();
void Post(const OmpEndLoopDirective &c);
void Post(const OmpProcBindClause::Type &c);
void Post(const OmpDefaultClause::Type &c);
void Post(const OmpDefaultmapClause::ImplicitBehavior &c);
void Post(const OmpDefaultmapClause::VariableCategory &c);
void Post(const OmpScheduleModifierType::ModType &c);
void Post(const OmpLinearModifier::Type &c);
void Post(const OmpDependenceType::Type &c);
void Post(const OmpMapType::Type &c);
void Post(const OmpScheduleClause::ScheduleType &c);
void Post(const OmpIfClause::DirectiveNameModifier &c);
void Post(const OmpCancelType::Type &c);
void Post(const OmpClause &c);
void PostClauseCommon(const ClauseInfo &ci);
void Post(const DoConstruct &);
std::string clauseDetails{""};
std::map<std::pair<std::string, std::string>, int> constructClauseCount;
// curLoopLogRecord and loopLogRecordStack store
// pointers to this datastructure's entries. Hence a
// vector cannot be used since pointers are invalidated
// on resize. Next best option seems to be deque. Also a
// list cannot be used since YAML gen requires a
// datastructure which can be accessed through indices.
std::deque<LogRecord> constructClauses;
LogRecord *curLoopLogRecord{nullptr};
std::vector<LogRecord *> loopLogRecordStack;
std::vector<OmpWrapperType *> ompWrapperStack;
std::map<OmpWrapperType *, std::vector<ClauseInfo>> clauseStrings;
Parsing *parsing{nullptr};
};
} // namespace parser
} // namespace Fortran
#endif /* FORTRAN_FLANG_OMP_REPORT_VISITOR_H */

View File

@ -0,0 +1,79 @@
//===-- examples/flang-omp-report-plugin/flang-omp-report.cpp -------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// This plugin parses a Fortran source file and generates a YAML
// report with all the OpenMP constructs and clauses and which
// line they're located on.
//
// The plugin may be invoked as:
// ./bin/flang-new -fc1 -load lib/flangOmpReport.so -plugin
// flang-omp-report -fopenmp -o - <source_file.f90>
//
//===----------------------------------------------------------------------===//
#include "flang-omp-report-visitor.h"
#include "flang/Frontend/CompilerInstance.h"
#include "flang/Frontend/FrontendActions.h"
#include "flang/Frontend/FrontendPluginRegistry.h"
#include "flang/Parser/dump-parse-tree.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/YAMLTraits.h"
using namespace Fortran::frontend;
using namespace Fortran::parser;
LLVM_YAML_IS_SEQUENCE_VECTOR(LogRecord)
LLVM_YAML_IS_SEQUENCE_VECTOR(ClauseInfo)
namespace llvm {
namespace yaml {
using llvm::yaml::IO;
using llvm::yaml::MappingTraits;
template <typename T>
struct SequenceTraits<std::deque<T>,
std::enable_if_t<CheckIsBool<SequenceElementTraits<T>::flow>::value>>
: SequenceTraitsImpl<std::deque<T>, SequenceElementTraits<T>::flow> {};
template <> struct MappingTraits<ClauseInfo> {
static void mapping(IO &io, ClauseInfo &info) {
io.mapRequired("clause", info.clause);
io.mapRequired("details", info.clauseDetails);
}
};
template <> struct MappingTraits<LogRecord> {
static void mapping(IO &io, LogRecord &info) {
io.mapRequired("file", info.file);
io.mapRequired("line", info.line);
io.mapRequired("construct", info.construct);
io.mapRequired("clauses", info.clauses);
}
};
} // namespace yaml
} // namespace llvm
class FlangOmpReport : public PluginParseTreeAction {
void ExecuteAction() override {
// Prepare the parse tree and the visitor
CompilerInstance &ci = this->instance();
Parsing &parsing = ci.parsing();
const Program &parseTree = *parsing.parseTree();
OpenMPCounterVisitor visitor;
visitor.parsing = &parsing;
// Walk the parse tree
Walk(parseTree, visitor);
// Dump the output
std::unique_ptr<llvm::raw_pwrite_stream> OS{ci.CreateDefaultOutputFile(
/*Binary=*/true, /*InFile=*/GetCurrentFileOrBufferName(),
/*Extension=*/".yaml")};
llvm::yaml::Output yout(*OS);
yout << visitor.constructClauses;
}
};
static FrontendPluginRegistry::Add<FlangOmpReport> X("flang-omp-report",
"Generate a YAML summary of OpenMP constructs and clauses");

View File

@ -0,0 +1,59 @@
! Check the flang-omp-report plugin for omp-atomic.f90
! REQUIRES: plugins
! RUN: %flang_fc1 -load %llvmshlibdir/flangOmpReport.so -plugin flang-omp-report -fopenmp %s -o - | FileCheck %s
! Check OpenMP 2.13.6 atomic Construct
a = 1.0
!$omp parallel num_threads(4) shared(a)
!$omp atomic seq_cst, read
b = a
!$omp atomic seq_cst write
a = b
!$omp end atomic
!$omp atomic capture seq_cst
b = a
a = a + 1
!$omp end atomic
!$omp atomic
a = a + 1
!$omp end parallel
end
! CHECK:---
! CHECK-NEXT:- file: '{{[^"]*}}omp-atomic.f90'
! CHECK-NEXT: line: 11
! CHECK-NEXT: construct: atomic-read
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: seq_cst
! CHECK-NEXT: details: ''
! CHECK-NEXT:- file: '{{[^"]*}}omp-atomic.f90'
! CHECK-NEXT: line: 14
! CHECK-NEXT: construct: atomic-write
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: seq_cst
! CHECK-NEXT: details: ''
! CHECK-NEXT:- file: '{{[^"]*}}omp-atomic.f90'
! CHECK-NEXT: line: 18
! CHECK-NEXT: construct: atomic-capture
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: seq_cst
! CHECK-NEXT: details: ''
! CHECK-NEXT:- file: '{{[^"]*}}omp-atomic.f90'
! CHECK-NEXT: line: 23
! CHECK-NEXT: construct: atomic-atomic
! CHECK-NEXT: clauses: []
! CHECK-NEXT:- file: '{{[^"]*}}omp-atomic.f90'
! CHECK-NEXT: line: 10
! CHECK-NEXT: construct: parallel
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: num_threads
! CHECK-NEXT: details: '4'
! CHECK-NEXT: - clause: shared
! CHECK-NEXT: details: a
! CHECK-NEXT:...

View File

@ -0,0 +1,43 @@
! Check the flang-omp-report plugin for omp-declarative-directive.f90
! REQUIRES: plugins
! RUN: %flang_fc1 -load %llvmshlibdir/flangOmpReport.so -plugin flang-omp-report -fopenmp %s -o - | FileCheck %s
! Check OpenMP declarative directives
! 2.8.2 declare-simd
subroutine declare_simd_1(a, b)
real(8), intent(inout) :: a, b
!$omp declare simd(declare_simd_1) aligned(a)
a = 3.14 + b
end subroutine declare_simd_1
! 2.10.6 declare-target
! 2.15.2 threadprivate
module m2
contains
subroutine foo
!$omp declare target
integer, parameter :: N=10000, M=1024
integer :: i
real :: Q(N, N), R(N,M), S(M,M)
end subroutine foo
end module m2
end
! CHECK:---
! CHECK-NEXT:- file: '{{[^"]*}}omp-declarative-directive.f90'
! CHECK-NEXT: line: 13
! CHECK-NEXT: construct: declare simd
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: aligned
! CHECK-NEXT: details: a
! CHECK-NEXT:- file: '{{[^"]*}}omp-declarative-directive.f90'
! CHECK-NEXT: line: 23
! CHECK-NEXT: construct: declare target
! CHECK-NEXT: clauses: []
! CHECK-NEXT:...

View File

@ -0,0 +1,103 @@
! Check flang-omp-report --femit-yaml for omp-device-constructs.f90
! REQUIRES: plugins
!RUN: %flang_fc1 -load %llvmshlibdir/flangOmpReport.so -plugin flang-omp-report -fopenmp %s -o - | FileCheck %s
! Check OpenMP clause validity for the following directives:
! 2.10 Device constructs
program main
real(8) :: arrayA(256), arrayB(256)
integer :: N
arrayA = 1.414
arrayB = 3.14
N = 256
!$omp target map(arrayA)
do i = 1, N
a = 3.14
enddo
!$omp end target
!$omp target device(0)
do i = 1, N
a = 3.14
enddo
!$omp end target
!$omp target defaultmap(tofrom:scalar)
do i = 1, N
a = 3.14
enddo
!$omp end target
!$omp teams num_teams(3) thread_limit(10) default(shared) private(i) shared(a)
do i = 1, N
a = 3.14
enddo
!$omp end teams
!$omp target map(tofrom:a)
do i = 1, N
a = 3.14
enddo
!$omp end target
!$omp target data device(0) map(to:a)
do i = 1, N
a = 3.14
enddo
!$omp end target data
end program main
! CHECK: ---
! CHECK-NEXT: - file: '{{[^"]*}}omp-device-constructs.f90'
! CHECK-NEXT: line: 18
! CHECK-NEXT: construct: target
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: map
! CHECK-NEXT: details: arraya
! CHECK-NEXT: - file: '{{[^"]*}}omp-device-constructs.f90'
! CHECK-NEXT: line: 24
! CHECK-NEXT: construct: target
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: device
! CHECK-NEXT: details: '0'
! CHECK-NEXT: - file: '{{[^"]*}}omp-device-constructs.f90'
! CHECK-NEXT: line: 30
! CHECK-NEXT: construct: target
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: defaultmap
! CHECK-NEXT: details: 'tofrom:scalar'
! CHECK-NEXT: - file: '{{[^"]*}}omp-device-constructs.f90'
! CHECK-NEXT: line: 36
! CHECK-NEXT: construct: teams
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: default
! CHECK-NEXT: details: shared
! CHECK-NEXT: - clause: num_teams
! CHECK-NEXT: details: '3'
! CHECK-NEXT: - clause: private
! CHECK-NEXT: details: i
! CHECK-NEXT: - clause: shared
! CHECK-NEXT: details: a
! CHECK-NEXT: - clause: thread_limit
! CHECK-NEXT: details: '10'
! CHECK-NEXT: - file: '{{[^"]*}}omp-device-constructs.f90'
! CHECK-NEXT: line: 42
! CHECK-NEXT: construct: target
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: map
! CHECK-NEXT: details: 'tofrom:a'
! CHECK-NEXT: - file: '{{[^"]*}}omp-device-constructs.f90'
! CHECK-NEXT: line: 48
! CHECK-NEXT: construct: target data
! CHECK-NEXT: clauses:
! CHECK-NEXT: - clause: device
! CHECK-NEXT: details: '0'
! CHECK-NEXT: - clause: map
! CHECK-NEXT: details: 'to:a'
! CHECK-NEXT: ...