diff --git a/flang/include/flang/Optimizer/Dialect/FIRDialect.h b/flang/include/flang/Optimizer/Dialect/FIRDialect.h --- a/flang/include/flang/Optimizer/Dialect/FIRDialect.h +++ b/flang/include/flang/Optimizer/Dialect/FIRDialect.h @@ -47,6 +47,9 @@ void registerAttributes(); // Register the Types of this dialect. void registerTypes(); + // Register external interfaces on operations of + // this dialect. + void registerOpInterfaces(); }; /// The FIR codegen dialect is a dialect containing a small set of transient diff --git a/flang/lib/Lower/OpenMP.cpp b/flang/lib/Lower/OpenMP.cpp --- a/flang/lib/Lower/OpenMP.cpp +++ b/flang/lib/Lower/OpenMP.cpp @@ -2469,6 +2469,112 @@ converter.bindSymbol(sym, symThreadprivateExv); } +void handleDeclareTarget(Fortran::lower::AbstractConverter &converter, + Fortran::lower::pft::Evaluation &eval, + const Fortran::parser::OpenMPDeclareTargetConstruct + &declareTargetConstruct) { + llvm::SmallVector, + 0> + symbolAndClause; + auto findFuncAndVarSyms = [&](const Fortran::parser::OmpObjectList &objList, + mlir::omp::DeclareTargetCaptureClause clause) { + for (const auto &ompObject : objList.v) { + Fortran::common::visit( + Fortran::common::visitors{ + [&](const Fortran::parser::Designator &designator) { + if (const Fortran::parser::Name *name = + getDesignatorNameIfDataRef(designator)) { + symbolAndClause.push_back( + std::make_pair(clause, *name->symbol)); + } + }, + [&](const Fortran::parser::Name &name) { + symbolAndClause.push_back(std::make_pair(clause, *name.symbol)); + }}, + ompObject.u); + } + }; + + const auto &spec{std::get( + declareTargetConstruct.t)}; + auto mod = converter.getFirOpBuilder().getModule(); + + // The default capture type + auto deviceType = Fortran::parser::OmpDeviceTypeClause::Type::Any; + if (const auto *objectList{ + Fortran::parser::Unwrap(spec.u)}) { + // Case: declare target(func, var1, var2) + findFuncAndVarSyms(*objectList, mlir::omp::DeclareTargetCaptureClause::to); + } else if (const auto *clauseList{ + Fortran::parser::Unwrap( + spec.u)}) { + if (clauseList->v.empty()) { + // Case: declare target, implicit capture of function + symbolAndClause.push_back( + std::make_pair(mlir::omp::DeclareTargetCaptureClause::to, + eval.getOwningProcedure()->getSubprogramSymbol())); + } + + for (const auto &clause : clauseList->v) { + if (const auto *toClause{ + std::get_if(&clause.u)}) { + // Case: declare target to(func, var1, var2)... + findFuncAndVarSyms(toClause->v, + mlir::omp::DeclareTargetCaptureClause::to); + } else if (const auto *linkClause{ + std::get_if( + &clause.u)}) { + // Case: declare target link(var1, var2)... + findFuncAndVarSyms(linkClause->v, + mlir::omp::DeclareTargetCaptureClause::link); + } else if (const auto *deviceClause{ + std::get_if( + &clause.u)}) { + // Case: declare target ... device_type(any | host | nohost) + deviceType = deviceClause->v.v; + } + } + } + + for (auto sym : symbolAndClause) { + auto *op = mod.lookupSymbol(converter.mangleName(std::get<1>(sym))); + + auto declareTargetOp = dyn_cast(op); + if (!declareTargetOp) + fir::emitFatalError( + converter.getCurrentLocation(), + "Attempt to apply declare target on unsupproted operation"); + + mlir::omp::DeclareTargetDeviceType newDeviceType; + switch (deviceType) { + case Fortran::parser::OmpDeviceTypeClause::Type::Nohost: + newDeviceType = mlir::omp::DeclareTargetDeviceType::nohost; + break; + case Fortran::parser::OmpDeviceTypeClause::Type::Host: + newDeviceType = mlir::omp::DeclareTargetDeviceType::host; + break; + case Fortran::parser::OmpDeviceTypeClause::Type::Any: + newDeviceType = mlir::omp::DeclareTargetDeviceType::any; + break; + } + + // The function or global already has a declare target applied to it, + // very likely through implicit capture (usage in another declare + // target function/subroutine). It should be marked as any if it has + // been assigned both host and nohost, else we skip, as there is no + // change + if (declareTargetOp.isDeclareTarget()) { + if (declareTargetOp.getDeclareTargetDeviceType() != newDeviceType) + declareTargetOp.setDeclareTarget( + mlir::omp::DeclareTargetDeviceType::any, std::get<0>(sym)); + continue; + } + + declareTargetOp.setDeclareTarget(newDeviceType, std::get<0>(sym)); + } +} + void Fortran::lower::genOpenMPDeclarativeConstruct( Fortran::lower::AbstractConverter &converter, Fortran::lower::pft::Evaluation &eval, @@ -2491,8 +2597,7 @@ }, [&](const Fortran::parser::OpenMPDeclareTargetConstruct &declareTargetConstruct) { - TODO(converter.getCurrentLocation(), - "OpenMPDeclareTargetConstruct"); + handleDeclareTarget(converter, eval, declareTargetConstruct); }, [&](const Fortran::parser::OpenMPRequiresConstruct &requiresConstruct) { diff --git a/flang/lib/Optimizer/Dialect/FIRDialect.cpp b/flang/lib/Optimizer/Dialect/FIRDialect.cpp --- a/flang/lib/Optimizer/Dialect/FIRDialect.cpp +++ b/flang/lib/Optimizer/Dialect/FIRDialect.cpp @@ -64,6 +64,7 @@ #define GET_OP_LIST #include "flang/Optimizer/Dialect/FIROps.cpp.inc" >(); + registerOpInterfaces(); addInterfaces(); } diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp --- a/flang/lib/Optimizer/Dialect/FIROps.cpp +++ b/flang/lib/Optimizer/Dialect/FIROps.cpp @@ -12,6 +12,7 @@ #include "flang/Optimizer/Dialect/FIROps.h" #include "flang/Optimizer/Dialect/FIRAttr.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" #include "flang/Optimizer/Dialect/FIROpsSupport.h" #include "flang/Optimizer/Dialect/FIRType.h" #include "flang/Optimizer/Dialect/Support/FIRContext.h" @@ -19,6 +20,7 @@ #include "flang/Optimizer/Support/Utils.h" #include "mlir/Dialect/CommonFolders.h" #include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/OpenMP/OpenMPDialect.h" #include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinOps.h" @@ -3758,6 +3760,17 @@ return fortranVar.verifyDeclareLikeOpImpl(getMemref()); } +//===----------------------------------------------------------------------===// +// FIROpsDialect +//===----------------------------------------------------------------------===// + +void fir::FIROpsDialect::registerOpInterfaces() { + // Attach default declare target interfaces to operations which can be marked + // as declare target. + fir::GlobalOp::attachInterface< + mlir::omp::DeclareTargetDefaultModel>(*getContext()); +} + // Tablegen operators #define GET_OP_CLASSES diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt --- a/flang/lib/Semantics/CMakeLists.txt +++ b/flang/lib/Semantics/CMakeLists.txt @@ -28,6 +28,7 @@ data-to-inits.cpp definable.cpp expression.cpp + finalize-omp.cpp mod-file.cpp pointer-assignment.cpp program-tree.cpp diff --git a/flang/lib/Semantics/finalize-omp.h b/flang/lib/Semantics/finalize-omp.h new file mode 100644 --- /dev/null +++ b/flang/lib/Semantics/finalize-omp.h @@ -0,0 +1,21 @@ +//===-- lib/Semantics/finalize-omp.h ------------------------*- C++ -*-===// +// +// 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_SEMANTICS_FINALIZE_OMP_H_ +#define FORTRAN_SEMANTICS_FINALIZE_OMP_H_ + +namespace Fortran::parser { +struct Program; +} // namespace Fortran::parser + +namespace Fortran::semantics { +class SemanticsContext; +bool FinalizeOMP(SemanticsContext &context, parser::Program &program); +} // namespace Fortran::semantics + +#endif // FORTRAN_SEMANTICS_FINALIZE_OMP_H_ diff --git a/flang/lib/Semantics/finalize-omp.cpp b/flang/lib/Semantics/finalize-omp.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/Semantics/finalize-omp.cpp @@ -0,0 +1,158 @@ +#include "finalize-omp.h" +#include "flang/Parser/parse-tree-visitor.h" +#include "flang/Semantics/tools.h" + +#include +#include +#include + +namespace Fortran::semantics { + +using namespace parser::literals; + +class GatherCallRefs { +public: + GatherCallRefs() {} + + // Default action for a parse tree node is to visit children. + template bool Pre(T &) { return true; } + template void Post(T &) {} + + void Post(parser::Call &call) { + if (std::holds_alternative(std::get<0>(call.t).u)) + callNames.push_back(std::get(std::get<0>(call.t).u)); + } + + std::list callNames; +}; + +// This pass works by passing over the modules PFT and collecting all of the +// ProgramUnits(subroutines / functions) as it passes through these nodes. +// When it meets a declare target node, it reads through the extended-list of +// the declare target directive's clause finding functions (the current function +// should be referred to in this declare target if a list is specified), if no +// list is specified the function the declare target is in, is considered the +// only function in the declare target list. These functions are then processed, +// with the algorithm looking for calls in these functions that are not included +// in the declare target but are implicitly declare target as they are invoked +// by a declare target region. These functions are searched for in the list of +// ProgramUnits (check if it exists, and allows access to go through it's own +// callees, to further find implicit functions) before being added to the +// original declare target extended-list (either by extended it to add a list, +// or just appending to the original list), now becoming directly declare target +// and can be further lowered as declare target. Care is taken not to add +// duplicate members to these lists, this also helps avoid infinite recursion +// when encountering a recursive function. +class ImplicitDeclareTargetCapture { +public: + template bool Pre(T &) { return true; } + template void Post(T &) {} + ImplicitDeclareTargetCapture(SemanticsContext &context) + : messages_{context.messages()} {} + + // Related to rewriting declare target specifiers to + // contain functions nested within the primary declare + // target function. + void Post(parser::OpenMPDeclareTargetConstruct &x) { + auto &spec{std::get(x.t)}; + if (parser::OmpObjectList * + objectList{parser::Unwrap(spec.u)}) { + markDeclTarForEachProgramInList(programUnits_, *objectList); + } else if (auto *clauseList{ + parser::Unwrap(spec.u)}) { + for (auto &clause : clauseList->v) { + if (auto *toClause{std::get_if(&clause.u)}) { + markDeclTarForEachProgramInList(programUnits_, toClause->v); + } else if (auto *linkClause{ + std::get_if(&clause.u)}) { + markDeclTarForEachProgramInList(programUnits_, linkClause->v); + } + } + + // The default "declare target" inside of a function case, we must + // create and generate an to extended-list, containing at minimum the + // current function + if (clauseList->v.empty()) { + if (auto *name = getNameFromProgramUnit(*currentProgramUnit_)) { + std::list list; + list.push_back(parser::OmpObject{ + parser::Designator{parser::DataRef{std::move(*name)}}}); + auto objList = parser::OmpObjectList{std::move(list)}; + markDeclTarForEachProgramInList(programUnits_, objList); + clauseList->v.push_back(parser::OmpClause::To{std::move(objList)}); + } + } + } + } + + bool Pre(parser::ProgramUnit &x) { + currentProgramUnit_ = &x; + if (auto *name = getNameFromProgramUnit(x)) + programUnits_[name->ToString()] = &x; + return true; + } + + parser::Name *getNameFromProgramUnit(parser::ProgramUnit &x) { + if (auto *func{parser::Unwrap(x.u)}) { + parser::FunctionStmt &Stmt = std::get<0>(func->t).statement; + return &std::get(Stmt.t); + } else if (auto *subr{parser::Unwrap(x.u)}) { + parser::SubroutineStmt &Stmt = std::get<0>(subr->t).statement; + return &std::get(Stmt.t); + } + return nullptr; + } + + void markDeclTarForEachProgramInList( + std::map programUnits, + parser::OmpObjectList &objList) { + auto existsInList = [](parser::OmpObjectList &objList, parser::Name name) { + for (auto &ompObject : objList.v) + if (auto *objName{parser::Unwrap(ompObject)}) + if (objName->ToString() == name.ToString()) + return true; + return false; + }; + + GatherCallRefs gatherer{}; + for (auto &ompObject : objList.v) { + if (auto *name{parser::Unwrap(ompObject)}) { + auto programUnit = programUnits.find(name->ToString()); + // something other than a subroutine or function, skip it + if (programUnit == programUnits.end()) + continue; + + parser::Walk(*programUnit->second, gatherer); + + // Currently using the Function Name rather than the CallRef name, + // unsure if these are interchangeable. However, ideally functions + // and subroutines should probably be parser::PorcedureDesignator's + // rather than parser::Designator's, but regular designators seem + // to be all that is utilised in the PFT definition for OmpObjects. + for (auto v : gatherer.callNames) { + if (!existsInList(objList, v)) { + objList.v.push_back(parser::OmpObject{parser::Designator{ + parser::DataRef{std::move(*getNameFromProgramUnit( + *programUnits.find(v.ToString())->second))}}}); + } + } + + gatherer.callNames.clear(); + } + } + } + +private: + std::map programUnits_; + parser::ProgramUnit *currentProgramUnit_ = nullptr; + + parser::Messages &messages_; +}; + +bool FinalizeOMP(SemanticsContext &context, parser::Program &program) { + ImplicitDeclareTargetCapture impCap{context}; + Walk(program, impCap); + return !context.AnyFatalError(); +} + +} // namespace Fortran::semantics diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp --- a/flang/lib/Semantics/semantics.cpp +++ b/flang/lib/Semantics/semantics.cpp @@ -31,6 +31,7 @@ #include "check-select-type.h" #include "check-stop.h" #include "compute-offsets.h" +#include "finalize-omp.h" #include "mod-file.h" #include "resolve-labels.h" #include "resolve-names.h" @@ -170,6 +171,7 @@ ResolveNames(context, program, context.globalScope()); RewriteParseTree(context, program); ComputeOffsets(context, context.globalScope()); + FinalizeOMP(context, program); CheckDeclarations(context); StatementSemanticsPass1{context}.Walk(program); StatementSemanticsPass2 pass2{context}; diff --git a/flang/test/Fir/omp-declare-target-data.fir b/flang/test/Fir/omp-declare-target-data.fir new file mode 100644 --- /dev/null +++ b/flang/test/Fir/omp-declare-target-data.fir @@ -0,0 +1,78 @@ +// RUN: fir-opt --fir-to-llvm-ir %s | FileCheck %s + +module attributes {omp.is_device = #omp.isdevice} { + + // CHECK: llvm.mlir.global external @_QMtest_0Earray_1d(dense<[1, 2, 3]> : tensor<3xi32>) {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : !llvm.array<3 x i32> + fir.global @_QMtest_0Earray_1d(dense<[1, 2, 3]> : tensor<3xi32>) {omp.declare_target = #omp.declaretarget} : !fir.array<3xi32> + + // CHECK: llvm.mlir.global external @_QMtest_0Earray_2d() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : !llvm.array<2 x array<2 x i32>> + fir.global @_QMtest_0Earray_2d {omp.declare_target = #omp.declaretarget} : !fir.array<2x2xi32> { + %0 = fir.undefined !fir.array<2x2xi32> + %c1_i32 = arith.constant 1 : i32 + %1 = fir.insert_value %0, %c1_i32, [0 : index, 0 : index] : (!fir.array<2x2xi32>, i32) -> !fir.array<2x2xi32> + %c2_i32 = arith.constant 2 : i32 + %2 = fir.insert_value %1, %c2_i32, [1 : index, 0 : index] : (!fir.array<2x2xi32>, i32) -> !fir.array<2x2xi32> + %c3_i32 = arith.constant 3 : i32 + %3 = fir.insert_value %2, %c3_i32, [0 : index, 1 : index] : (!fir.array<2x2xi32>, i32) -> !fir.array<2x2xi32> + %c4_i32 = arith.constant 4 : i32 + %4 = fir.insert_value %3, %c4_i32, [1 : index, 1 : index] : (!fir.array<2x2xi32>, i32) -> !fir.array<2x2xi32> + %c2 = arith.constant 2 : index + %c2_0 = arith.constant 2 : index + fir.has_value %4 : !fir.array<2x2xi32> + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_extended_link_1() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : f32 + fir.global @_QMtest_0Edata_extended_link_1 {omp.declare_target = #omp.declaretarget} : f32 { + %cst = arith.constant 2.000000e+00 : f32 + fir.has_value %cst : f32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_extended_link_2() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : f32 + fir.global @_QMtest_0Edata_extended_link_2 {omp.declare_target = #omp.declaretarget} : f32 { + %cst = arith.constant 3.000000e+00 : f32 + fir.has_value %cst : f32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_extended_to_1() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : f32 + fir.global @_QMtest_0Edata_extended_to_1 {omp.declare_target = #omp.declaretarget} : f32 { + %cst = arith.constant 2.000000e+00 : f32 + fir.has_value %cst : f32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_extended_to_2() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : f32 { + fir.global @_QMtest_0Edata_extended_to_2 {omp.declare_target = #omp.declaretarget} : f32 { + %cst = arith.constant 3.000000e+00 : f32 + fir.has_value %cst : f32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_int() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : i32 + fir.global @_QMtest_0Edata_int {omp.declare_target = #omp.declaretarget} : i32 { + %c10_i32 = arith.constant 10 : i32 + fir.has_value %c10_i32 : i32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_int_clauseless() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : i32 + fir.global @_QMtest_0Edata_int_clauseless {omp.declare_target = #omp.declaretarget} : i32 { + %c1_i32 = arith.constant 1 : i32 + fir.has_value %c1_i32 : i32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Edata_int_to() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : i32 + fir.global @_QMtest_0Edata_int_to {omp.declare_target = #omp.declaretarget} : i32 { + %c5_i32 = arith.constant 5 : i32 + fir.has_value %c5_i32 : i32 + } + + // CHECK: llvm.mlir.global external @_QMtest_0Ept1() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> { + fir.global @_QMtest_0Ept1 {omp.declare_target = #omp.declaretarget} : !fir.box> { + %0 = fir.zero_bits !fir.ptr + %1 = fir.embox %0 : (!fir.ptr) -> !fir.box> + fir.has_value %1 : !fir.box> + } + + // CHECK: llvm.mlir.global external @_QMtest_0Ept2_tar() {{{.*}}omp.declare_target = #omp.declaretarget{{.*}}} : i32 + fir.global @_QMtest_0Ept2_tar {omp.declare_target = #omp.declaretarget} target : i32 { + %c5_i32 = arith.constant 5 : i32 + fir.has_value %c5_i32 : i32 + } +} diff --git a/flang/test/Lower/OpenMP/Todo/omp-declare-target.f90 b/flang/test/Lower/OpenMP/Todo/omp-declare-target.f90 deleted file mode 100644 --- a/flang/test/Lower/OpenMP/Todo/omp-declare-target.f90 +++ /dev/null @@ -1,12 +0,0 @@ -! This test checks lowering of OpenMP declare target Directive. - -// RUN: not flang-new -fc1 -emit-fir -fopenmp %s 2>&1 | FileCheck %s - -module mod1 -contains - subroutine sub() - integer :: x, y - // CHECK: not yet implemented: OpenMPDeclareTargetConstruct - !$omp declare target - end -end module diff --git a/flang/test/Lower/OpenMP/omp-declare-target-data.f90 b/flang/test/Lower/OpenMP/omp-declare-target-data.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Lower/OpenMP/omp-declare-target-data.f90 @@ -0,0 +1,52 @@ +!RUN: %flang_fc1 -emit-fir -fopenmp %s -o - | FileCheck %s +!RUN: %flang_fc1 -emit-fir -fopenmp -fopenmp-is-device %s -o - | FileCheck %s + +module test_0 + implicit none + +!CHECK-DAG: fir.global @_QMtest_0Edata_int {omp.declare_target = #omp.declaretarget} : i32 +INTEGER :: data_int = 10 +!$omp declare target link(data_int) + +!CHECK-DAG: fir.global @_QMtest_0Earray_1d(dense<[1, 2, 3]> : tensor<3xi32>) {omp.declare_target = #omp.declaretarget} : !fir.array<3xi32> +INTEGER :: array_1d(3) = (/1,2,3/) +!$omp declare target link(array_1d) + +!CHECK-DAG: fir.global @_QMtest_0Earray_2d {omp.declare_target = #omp.declaretarget} : !fir.array<2x2xi32> +INTEGER :: array_2d(2,2) = reshape((/1,2,3,4/), (/2,2/)) +!$omp declare target link(array_2d) + +!CHECK-DAG: fir.global @_QMtest_0Ept1 {omp.declare_target = #omp.declaretarget} : !fir.box> +INTEGER, POINTER :: pt1 +!$omp declare target link(pt1) + +!CHECK-DAG: fir.global @_QMtest_0Ept2_tar {omp.declare_target = #omp.declaretarget} target : i32 +INTEGER, TARGET :: pt2_tar = 5 +!$omp declare target link(pt2_tar) + +!CHECK-DAG: fir.global @_QMtest_0Ept2 {omp.declare_target = #omp.declaretarget} : !fir.box> +INTEGER, POINTER :: pt2 => pt2_tar +!$omp declare target link(pt2) + +!CHECK-DAG: fir.global @_QMtest_0Edata_int_to {omp.declare_target = #omp.declaretarget} : i32 +INTEGER :: data_int_to = 5 +!$omp declare target to(data_int_to) + +!CHECK-DAG: fir.global @_QMtest_0Edata_int_clauseless {omp.declare_target = #omp.declaretarget} : i32 +INTEGER :: data_int_clauseless = 1 +!$omp declare target(data_int_clauseless) + +!CHECK-DAG: fir.global @_QMtest_0Edata_extended_to_1 {omp.declare_target = #omp.declaretarget} : f32 +!CHECK-DAG: fir.global @_QMtest_0Edata_extended_to_2 {omp.declare_target = #omp.declaretarget} : f32 +REAL :: data_extended_to_1 = 2 +REAL :: data_extended_to_2 = 3 +!$omp declare target to(data_extended_to_1, data_extended_to_2) + +!CHECK-DAG: fir.global @_QMtest_0Edata_extended_link_1 {omp.declare_target = #omp.declaretarget} : f32 +!CHECK-DAG: fir.global @_QMtest_0Edata_extended_link_2 {omp.declare_target = #omp.declaretarget} : f32 +REAL :: data_extended_link_1 = 2 +REAL :: data_extended_link_2 = 3 +!$omp declare target link(data_extended_link_1, data_extended_link_2) + +contains +end module test_0 diff --git a/flang/test/Lower/OpenMP/omp-declare-target-func-and-subr.f90 b/flang/test/Lower/OpenMP/omp-declare-target-func-and-subr.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Lower/OpenMP/omp-declare-target-func-and-subr.f90 @@ -0,0 +1,297 @@ +!RUN: %flang_fc1 -emit-fir -fopenmp %s -o - | FileCheck %s + +! Check specification valid forms of declare target with functions +! utilising device_type and to clauses as well as the default +! zero clause declare target + +! CHECK-LABEL: func.func @_QPfunc_t_device() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION FUNC_T_DEVICE() RESULT(I) +!$omp declare target to(FUNC_T_DEVICE) device_type(nohost) + INTEGER :: I + I = 1 +END FUNCTION FUNC_T_DEVICE + +! CHECK-LABEL: func.func @_QPfunc_t_host() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION FUNC_T_HOST() RESULT(I) +!$omp declare target to(FUNC_T_HOST) device_type(host) + INTEGER :: I + I = 1 +END FUNCTION FUNC_T_HOST + +! CHECK-LABEL: func.func @_QPfunc_t_any() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION FUNC_T_ANY() RESULT(I) +!$omp declare target to(FUNC_T_ANY) device_type(any) + INTEGER :: I + I = 1 +END FUNCTION FUNC_T_ANY + +! CHECK-LABEL: func.func @_QPfunc_default_t_any() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION FUNC_DEFAULT_T_ANY() RESULT(I) +!$omp declare target to(FUNC_DEFAULT_T_ANY) + INTEGER :: I + I = 1 +END FUNCTION FUNC_DEFAULT_T_ANY + +! CHECK-LABEL: func.func @_QPfunc_default_any() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION FUNC_DEFAULT_ANY() RESULT(I) +!$omp declare target + INTEGER :: I + I = 1 +END FUNCTION FUNC_DEFAULT_ANY + +! CHECK-LABEL: func.func @_QPfunc_default_extendedlist() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION FUNC_DEFAULT_EXTENDEDLIST() RESULT(I) +!$omp declare target(FUNC_DEFAULT_EXTENDEDLIST) + INTEGER :: I + I = 1 +END FUNCTION FUNC_DEFAULT_EXTENDEDLIST + +! CHECK-LABEL: func.func @_QPexist_on_both() +! CHECK-NOT: {{.*}}attributes {omp.declare_target = #omp.declaretarget<({{.*}})>{{.*}} +FUNCTION EXIST_ON_BOTH() RESULT(I) + INTEGER :: I + I = 1 +END FUNCTION EXIST_ON_BOTH + +!! ----- + +! Check specification valid forms of declare target with subroutines +! utilising device_type and to clauses as well as the default +! zero clause declare target + +! CHECK-LABEL: func.func @_QPsubr_t_device() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_T_DEVICE() +!$omp declare target to(SUBR_T_DEVICE) device_type(nohost) +END + +! CHECK-LABEL: func.func @_QPsubr_t_host() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_T_HOST() +!$omp declare target to(SUBR_T_HOST) device_type(host) +END + +! CHECK-LABEL: func.func @_QPsubr_t_any() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_T_ANY() +!$omp declare target to(SUBR_T_ANY) device_type(any) +END + +! CHECK-LABEL: func.func @_QPsubr_default_t_any() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_DEFAULT_T_ANY() +!$omp declare target to(SUBR_DEFAULT_T_ANY) +END + +! CHECK-LABEL: func.func @_QPsubr_default_any() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_DEFAULT_ANY() +!$omp declare target +END + +! CHECK-LABEL: func.func @_QPsubr_default_extendedlist() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_DEFAULT_EXTENDEDLIST() +!$omp declare target(SUBR_DEFAULT_EXTENDEDLIST) +END + +! CHECK-LABEL: func.func @_QPsubr_exist_on_both() +! CHECK-NOT: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +SUBROUTINE SUBR_EXIST_ON_BOTH() +END + +!! ----- + +! Check declare target in conjunction with implicitly +! invoked functions, this tests the declare target +! implicit capture pass within Flang. Functions +! invoked within an explicitly declare target function +! are marked as declare target with the callers +! device_type clause + +! CHECK-LABEL: func.func @_QPimplicitly_captured +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED(TOGGLE) RESULT(K) + INTEGER :: I, J, K + LOGICAL :: TOGGLE + I = 10 + J = 5 + IF (TOGGLE) THEN + K = I + ELSE + K = J + END IF +END FUNCTION IMPLICITLY_CAPTURED + + +! CHECK-LABEL: func.func @_QPtarget_function +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION(TOGGLE) RESULT(I) +!$omp declare target + INTEGER :: I + LOGICAL :: TOGGLE + I = IMPLICITLY_CAPTURED(TOGGLE) +END FUNCTION TARGET_FUNCTION + +!! ----- + +! Check declare target in conjunction with implicitly +! invoked functions, this tests the declare target +! implicit capture pass within Flang. Functions +! invoked within an explicitly declare target function +! are marked as declare target with the callers +! device_type clause, however, if they are found with +! distinct device_type clauses i.e. host and nohost, +! then they should be marked as any + +! CHECK-LABEL: func.func @_QPimplicitly_captured_twice +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_TWICE() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_TWICE + +! CHECK-LABEL: func.func @_QPtarget_function_twice_host +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION_TWICE_HOST() RESULT(I) +!$omp declare target to(TARGET_FUNCTION_TWICE_HOST) device_type(host) + INTEGER :: I + I = IMPLICITLY_CAPTURED_TWICE() +END FUNCTION TARGET_FUNCTION_TWICE_HOST + +! CHECK-LABEL: func.func @_QPtarget_function_twice_device +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION_TWICE_DEVICE() RESULT(I) +!$omp declare target to(TARGET_FUNCTION_TWICE_DEVICE) device_type(nohost) + INTEGER :: I + I = IMPLICITLY_CAPTURED_TWICE() +END FUNCTION TARGET_FUNCTION_TWICE_DEVICE + +!! ----- + +! Check declare target in conjunction with implicitly +! invoked functions, this tests the declare target +! implicit capture pass within Flang. A slightly more +! complex test checking functions are marked implicitly +! appropriately. + +! CHECK-LABEL: func.func @_QPimplicitly_captured_nest +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_NEST() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_NEST + +! CHECK-LABEL: func.func @_QPimplicitly_captured_one +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_ONE() RESULT(K) + K = IMPLICITLY_CAPTURED_NEST() +END FUNCTION IMPLICITLY_CAPTURED_ONE + +! CHECK-LABEL: func.func @_QPimplicitly_captured_two +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_TWO() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_TWO + +! CHECK-LABEL: func.func @_QPtarget_function_test +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION_TEST() RESULT(J) +!$omp declare target to(TARGET_FUNCTION_TEST) device_type(nohost) + INTEGER :: I, J + I = IMPLICITLY_CAPTURED_ONE() + J = IMPLICITLY_CAPTURED_TWO() + I +END FUNCTION TARGET_FUNCTION_TEST + +!! ----- + +! Check declare target in conjunction with implicitly +! invoked functions, this tests the declare target +! implicit capture pass within Flang. A slightly more +! complex test checking functions are marked implicitly +! appropriately. + +! CHECK-LABEL: func.func @_QPimplicitly_captured_nest_twice +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_NEST_TWICE() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_NEST_TWICE + +! CHECK-LABEL: func.func @_QPimplicitly_captured_one_twice +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_ONE_TWICE() RESULT(K) + K = IMPLICITLY_CAPTURED_NEST_TWICE() +END FUNCTION IMPLICITLY_CAPTURED_ONE_TWICE + +! CHECK-LABEL: func.func @_QPimplicitly_captured_two_twice +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION IMPLICITLY_CAPTURED_TWO_TWICE() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_TWO_TWICE + +! CHECK-LABEL: func.func @_QPtarget_function_test_device +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION_TEST_DEVICE() RESULT(J) + !$omp declare target to(TARGET_FUNCTION_TEST_DEVICE) device_type(nohost) + INTEGER :: I, J + I = IMPLICITLY_CAPTURED_ONE_TWICE() + J = IMPLICITLY_CAPTURED_TWO_TWICE() + I +END FUNCTION TARGET_FUNCTION_TEST_DEVICE + +! CHECK-LABEL: func.func @_QPtarget_function_test_host +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION_TEST_HOST() RESULT(J) + !$omp declare target to(TARGET_FUNCTION_TEST_HOST) device_type(host) + INTEGER :: I, J + I = IMPLICITLY_CAPTURED_ONE_TWICE() + J = IMPLICITLY_CAPTURED_TWO_TWICE() + I +END FUNCTION TARGET_FUNCTION_TEST_HOST + +!! ----- + +! CHECK-LABEL: func.func @_QPimplicitly_captured_recursive +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +RECURSIVE FUNCTION IMPLICITLY_CAPTURED_RECURSIVE(INCREMENT) RESULT(K) + INTEGER :: INCREMENT, K + IF (INCREMENT == 10) THEN + K = INCREMENT + ELSE + K = IMPLICITLY_CAPTURED_RECURSIVE(INCREMENT + 1) + END IF +END FUNCTION IMPLICITLY_CAPTURED_RECURSIVE + +! CHECK-LABEL: func.func @_QPtarget_function_recurse +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +FUNCTION TARGET_FUNCTION_RECURSE() RESULT(I) +!$omp declare target(TARGET_FUNCTION_RECURSE) + INTEGER :: I + I = IMPLICITLY_CAPTURED_RECURSIVE(0) +END FUNCTION TARGET_FUNCTION_RECURSE + +!! ----- + +! CHECK-LABEL: func.func @_QPrecursive_declare_target +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp.declaretarget{{.*}} +RECURSIVE FUNCTION RECURSIVE_DECLARE_TARGET(INCREMENT) RESULT(K) +!$omp declare target to(RECURSIVE_DECLARE_TARGET) device_type(nohost) + INTEGER :: INCREMENT, K + IF (INCREMENT == 10) THEN + K = INCREMENT + ELSE + K = RECURSIVE_DECLARE_TARGET(INCREMENT + 1) + END IF +END FUNCTION RECURSIVE_DECLARE_TARGET diff --git a/flang/test/Semantics/OpenMP/declare-target-implicit-capture-rewrite.f90 b/flang/test/Semantics/OpenMP/declare-target-implicit-capture-rewrite.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Semantics/OpenMP/declare-target-implicit-capture-rewrite.f90 @@ -0,0 +1,229 @@ +! RUN: %flang_fc1 -fopenmp -fdebug-dump-parse-tree %s 2>&1 | FileCheck %s +! +! Ensure that functions and subroutines referenced within +! declare target functions are themselves made declare target +! as specified by more recent iterations of the OpenMP +! specification. This is done through a semantic pass which +! appends the implicitly captured functions to the original +! declare target declaration rather than generating and +! inserting new ones within the captured functions. +! +! For example a declare target inside of a function named 'ORIGINAL', +! would initially be empty, after the pass, the declare target +! would be expanded to declare target to(ORIGINAL). If +! there is a function named 'CAPTURED' called within 'ORIGINAL' +! the declare target inside of 'ORIGINAL' would be further +! expanded to declare target to(ORIGINAL, CAPTURED) + +FUNCTION IMPLICITLY_CAPTURED_NEST_TWICE() RESULT(I) + INTEGER :: I + I = 10 +END FUNCTION IMPLICITLY_CAPTURED_NEST_TWICE + +FUNCTION IMPLICITLY_CAPTURED_ONE_TWICE() RESULT(K) + K = IMPLICITLY_CAPTURED_NEST_TWICE() +END FUNCTION IMPLICITLY_CAPTURED_ONE_TWICE + +FUNCTION IMPLICITLY_CAPTURED_TWO_TWICE() RESULT(Y) + INTEGER :: Y + Y = 5 +END FUNCTION IMPLICITLY_CAPTURED_TWO_TWICE + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'target_function_test_device' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_one_twice' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_two_twice' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_nest_twice' +! CHECK: OmpClause -> DeviceType -> OmpDeviceTypeClause -> Type = Nohost +FUNCTION TARGET_FUNCTION_TEST_DEVICE() RESULT(J) +!$omp declare target to(TARGET_FUNCTION_TEST_DEVICE) device_type(nohost) + INTEGER :: I, J + I = IMPLICITLY_CAPTURED_ONE_TWICE() + J = IMPLICITLY_CAPTURED_TWO_TWICE() + I +END FUNCTION TARGET_FUNCTION_TEST_DEVICE + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'target_function_test_host' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_one_twice' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_two_twice' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_nest_twice' +! CHECK: OmpClause -> DeviceType -> OmpDeviceTypeClause -> Type = Host +FUNCTION TARGET_FUNCTION_TEST_HOST() RESULT(J) +!$omp declare target to(TARGET_FUNCTION_TEST_HOST) device_type(host) + INTEGER :: I, J + I = IMPLICITLY_CAPTURED_ONE_TWICE() + J = IMPLICITLY_CAPTURED_TWO_TWICE() + I +END FUNCTION TARGET_FUNCTION_TEST_HOST + +!! ----- + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'func_t_device' +! CHECK: OmpClause -> DeviceType -> OmpDeviceTypeClause -> Type = Nohost +FUNCTION FUNC_T_DEVICE() RESULT(I) +!$omp declare target to(FUNC_T_DEVICE) device_type(nohost) + INTEGER :: I + I = 1 +END FUNCTION FUNC_T_DEVICE + +!! ----- + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'subr_t_any' +! CHECK: OmpClause -> DeviceType -> OmpDeviceTypeClause -> Type = Any +SUBROUTINE SUBR_T_ANY() +!$omp declare target to(SUBR_T_ANY) device_type(any) +END + +!! ----- + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithList -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'subr_default_extendedlist' +SUBROUTINE SUBR_DEFAULT_EXTENDEDLIST() +!$omp declare target(SUBR_DEFAULT_EXTENDEDLIST) +END + +!! ----- + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'subr_unspecified' +SUBROUTINE SUBR_UNSPECIFIED() +!$omp declare target +END + +!! ----- + +FUNCTION UNSPECIFIED_CAPTURE() RESULT(K) + REAL :: K + K = 1 +END FUNCTION UNSPECIFIED_CAPTURE + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'subr_unspecified_capture' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'unspecified_capture' +SUBROUTINE SUBR_UNSPECIFIED_CAPTURE() +!$omp declare target + REAL :: I + I = UNSPECIFIED_CAPTURE() +END + +!! ----- + +FUNCTION IMPLICITLY_CAPTURED_NEST() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_NEST + +FUNCTION IMPLICITLY_CAPTURED_ONE() RESULT(K) + K = IMPLICITLY_CAPTURED_NEST() +END FUNCTION IMPLICITLY_CAPTURED_ONE + +FUNCTION IMPLICITLY_CAPTURED_TWO() RESULT(K) + INTEGER :: I + I = 10 + K = I +END FUNCTION IMPLICITLY_CAPTURED_TWO + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'target_function_test' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_one' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_two' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_nest' +! CHECK: OmpClause -> DeviceType -> OmpDeviceTypeClause -> Type = Nohost +FUNCTION TARGET_FUNCTION_TEST() RESULT(J) +!$omp declare target to(TARGET_FUNCTION_TEST) device_type(nohost) + INTEGER :: I, J + I = IMPLICITLY_CAPTURED_ONE() + J = IMPLICITLY_CAPTURED_TWO() + I +END FUNCTION TARGET_FUNCTION_TEST + +!! ----- + +FUNCTION NO_DECLARE_TARGET() RESULT(K) + implicit none + REAL :: I, K + I = 10.0 + K = I +END FUNCTION NO_DECLARE_TARGET + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'declare_target_two' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'no_declare_target' +FUNCTION DECLARE_TARGET_TWO() RESULT(J) +!$omp declare target to(DECLARE_TARGET_TWO) + implicit none + REAL :: I, J + I = NO_DECLARE_TARGET() + J = I +END FUNCTION DECLARE_TARGET_TWO + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'declare_target_one' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'declare_target_two' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'no_declare_target' +FUNCTION DECLARE_TARGET_ONE() RESULT(I) +!$omp declare target to(DECLARE_TARGET_ONE) + implicit none + REAL :: K, I + I = DECLARE_TARGET_TWO() + K = I +END FUNCTION DECLARE_TARGET_ONE + +!! ----- + +RECURSIVE FUNCTION IMPLICITLY_CAPTURED_RECURSIVE(INCREMENT) RESULT(K) + INTEGER :: INCREMENT, K + IF (INCREMENT == 10) THEN + K = INCREMENT + ELSE + K = IMPLICITLY_CAPTURED_RECURSIVE(INCREMENT + 1) + END IF +END FUNCTION IMPLICITLY_CAPTURED_RECURSIVE + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithList -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'target_function_recurse' +! CHECK: OmpObject -> Designator -> DataRef -> Name = 'implicitly_captured_recursive' +FUNCTION TARGET_FUNCTION_RECURSE() RESULT(I) +!$omp declare target(TARGET_FUNCTION_RECURSE) + INTEGER :: I + I = IMPLICITLY_CAPTURED_RECURSIVE(0) +END FUNCTION TARGET_FUNCTION_RECURSE + +!! ----- + +! CHECK: SpecificationPart +! CHECK: OpenMPDeclarativeConstruct -> OpenMPDeclareTargetConstruct +! CHECK: Verbatim +! CHECK: OmpDeclareTargetSpecifier -> OmpDeclareTargetWithClause -> OmpClauseList -> OmpClause -> To -> OmpObjectList -> OmpObject -> Designator -> DataRef -> Name = 'recursive_declare_target' +RECURSIVE FUNCTION RECURSIVE_DECLARE_TARGET(INCREMENT) RESULT(K) +!$omp declare target to(RECURSIVE_DECLARE_TARGET) device_type(nohost) + INTEGER :: INCREMENT, K + IF (INCREMENT == 10) THEN + K = INCREMENT + ELSE + K = RECURSIVE_DECLARE_TARGET(INCREMENT + 1) + END IF +END FUNCTION RECURSIVE_DECLARE_TARGET diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h --- a/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h +++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPDialect.h @@ -21,6 +21,11 @@ #include "mlir/Interfaces/ControlFlowInterfaces.h" #include "mlir/Interfaces/SideEffectInterfaces.h" +namespace mlir::omp { +enum class DeclareTargetDeviceType : uint32_t; +enum class DeclareTargetCaptureClause : uint32_t; +} // namespace mlir::omp + #include "mlir/Dialect/OpenMP/OpenMPOpsDialect.h.inc" #include "mlir/Dialect/OpenMP/OpenMPOpsEnums.h.inc" #include "mlir/Dialect/OpenMP/OpenMPTypeInterfaces.h.inc" diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPInterfaces.h b/mlir/include/mlir/Dialect/OpenMP/OpenMPInterfaces.h --- a/mlir/include/mlir/Dialect/OpenMP/OpenMPInterfaces.h +++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPInterfaces.h @@ -30,6 +30,12 @@ struct OffloadModuleDefaultModel : public OffloadModuleInterface::ExternalModel {}; + +template +struct DeclareTargetDefaultModel + : public DeclareTargetInterface::ExternalModel, + T> {}; + } // namespace mlir::omp #endif // MLIR_DIALECT_OPENMP_OPENMPINTERFACES_H_ diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td --- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td +++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td @@ -26,7 +26,7 @@ def OpenMP_Dialect : Dialect { let name = "omp"; let cppNamespace = "::mlir::omp"; - let dependentDialects = ["::mlir::LLVM::LLVMDialect"]; + let dependentDialects = ["::mlir::LLVM::LLVMDialect, ::mlir::func::FuncDialect"]; let useDefaultAttributePrinterParser = 1; } @@ -87,6 +87,52 @@ def OpenMP_PointerLikeType : TypeAlias; +//===----------------------------------------------------------------------===// +// 2.12.7 Declare Target Directive +//===----------------------------------------------------------------------===// + +def DeviceTypeAny : I32EnumAttrCase<"any", 0>; +def DeviceTypeHost : I32EnumAttrCase<"host", 1>; +def DeviceTypeNoHost : I32EnumAttrCase<"nohost", 2>; + +def DeclareTargetDeviceType : I32EnumAttr< + "DeclareTargetDeviceType", + "device_type clause", + [DeviceTypeAny, DeviceTypeHost, DeviceTypeNoHost]> { + let genSpecializedAttr = 0; + let cppNamespace = "::mlir::omp"; +} + +def DeclareTargetDeviceTypeAttr : EnumAttr { + let assemblyFormat = "`(` $value `)`"; +} + +def CaptureClauseLink : I32EnumAttrCase<"to", 0>; +def CaptureClauseTo : I32EnumAttrCase<"link", 1>; + +def DeclareTargetCaptureClause : I32EnumAttr< + "DeclareTargetCaptureClause", + "capture clause", + [CaptureClauseLink, CaptureClauseTo]> { + let genSpecializedAttr = 0; + let cppNamespace = "::mlir::omp"; +} + +def DeclareTargetCaptureClauseAttr : EnumAttr { + let assemblyFormat = "`(` $value `)`"; +} + +def DeclareTargetAttr : OpenMP_Attr<"DeclareTarget", "declaretarget"> { + let parameters = (ins + OptionalParameter<"DeclareTargetDeviceTypeAttr">:$device_type, + OptionalParameter<"DeclareTargetCaptureClauseAttr">:$capture_clause + ); + + let assemblyFormat = "`<` struct(params) `>`"; +} + //===----------------------------------------------------------------------===// // 2.6 parallel Construct //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td --- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td +++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td @@ -47,6 +47,74 @@ ]; } +def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> { + let description = [{ + OpenMP operations that support declare target have this interface. + For example, FuncOp's and llvm.GlobalOp/fir.GlobalOp's. This + interface allows simple manipulation and introspection of the + declare target attribute that can be applied to these operations. + }]; + + let cppNamespace = "::mlir::omp"; + + let methods = [ + InterfaceMethod< + /*description=*/[{ + Set the declare target attribute on the current operation with the + specified attribute arguments. + }], + /*retTy=*/"void", + /*methodName=*/"setDeclareTarget", + (ins "mlir::omp::DeclareTargetDeviceType":$deviceType, + "mlir::omp::DeclareTargetCaptureClause":$captureClause), [{}], [{ + $_op->setAttr("omp.declare_target", + mlir::omp::DeclareTargetAttr::get( + $_op->getContext(), + mlir::omp::DeclareTargetDeviceTypeAttr::get( + $_op->getContext(), deviceType), + mlir::omp::DeclareTargetCaptureClauseAttr::get( + $_op->getContext(), captureClause))); + }]>, + InterfaceMethod< + /*description=*/[{ + Checks if the declare target attribute has been applied and exists on the + current operation. Returns true if it exists on it, otherwise returns + false. + }], + /*retTy=*/"bool", + /*methodName=*/"isDeclareTarget", + (ins), [{}], [{ + return $_op->hasAttr("omp.declare_target"); + }]>, + InterfaceMethod< + /*description=*/[{ + Returns the DeclareTargetDeviceType segemnt of the DeclareTarget attribute if it + exists on the current operation. Otherwise it returns null. + }], + /*retTy=*/"mlir::omp::DeclareTargetDeviceType", + /*methodName=*/"getDeclareTargetDeviceType", + (ins), [{}], [{ + if (mlir::Attribute dTar = $_op->getAttr("omp.declare_target")) + if (auto dAttr = dTar.dyn_cast_or_null()) + return dAttr.getDeviceType().getValue(); + return {}; + }]>, + InterfaceMethod< + /*description=*/[{ + Returns the DeclareTargetCaptureClause segemnt of the DeclareTarget attribute if it + exists on the current operation. Otherwise it returns null. + }], + /*retTy=*/"mlir::omp::DeclareTargetCaptureClause", + /*methodName=*/"getDeclareTargetCaptureClause", + (ins), [{}], [{ + if (mlir::Attribute dTar = $_op->getAttr("omp.declare_target")) + if (auto dAttr = dTar.dyn_cast_or_null()) + return dAttr.getCaptureClause().getValue(); + return {}; + }]> + ]; +} + def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> { let description = [{ Operations that represent a module for offloading (host or device) diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp --- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp +++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "mlir/Dialect/OpenMP/OpenMPDialect.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMTypes.h" #include "mlir/IR/Attributes.h" #include "mlir/IR/DialectImplementation.h" @@ -71,8 +72,23 @@ MemRefType::attachInterface>(*getContext()); LLVM::LLVMPointerType::attachInterface< PointerLikeModel>(*getContext()); + + // Attach default offload module interface to module op to access + // offload functionality through mlir::ModuleOp::attachInterface( *getContext()); + + // Attach default declare target interfaces to operations which can be marked + // as declare target (Global Operations and Functions/Subroutines in dialects + // that Fortran (or other languages that lower to MLIR) translates too + mlir::LLVM::GlobalOp::attachInterface< + mlir::omp::DeclareTargetDefaultModel>( + *getContext()); + mlir::LLVM::LLVMFuncOp::attachInterface< + mlir::omp::DeclareTargetDefaultModel>( + *getContext()); + mlir::func::FuncOp::attachInterface< + mlir::omp::DeclareTargetDefaultModel>(*getContext()); } //===----------------------------------------------------------------------===// diff --git a/mlir/test/Dialect/OpenMP/attr.mlir b/mlir/test/Dialect/OpenMP/attr.mlir --- a/mlir/test/Dialect/OpenMP/attr.mlir +++ b/mlir/test/Dialect/OpenMP/attr.mlir @@ -29,3 +29,83 @@ // CHECK: module attributes {omp.flags = #omp.flags} { module attributes {omp.flags = #omp.flags} {} + +// ---- + +// CHECK-LABEL: func @omp_decl_tar_host_to +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp.declaretarget} { +func.func @omp_decl_tar_host_to() -> () attributes {omp.declare_target = #omp.declaretarget} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_host_link +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp.declaretarget} { +func.func @omp_decl_tar_host_link() -> () attributes {omp.declare_target = #omp.declaretarget} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_nohost_to +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp.declaretarget} { +func.func @omp_decl_tar_nohost_to() -> () attributes {omp.declare_target = #omp.declaretarget} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_nohost_link +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp.declaretarget} { +func.func @omp_decl_tar_nohost_link() -> () attributes {omp.declare_target = #omp.declaretarget} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_any_to +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp.declaretarget} { +func.func @omp_decl_tar_any_to() -> () attributes {omp.declare_target = #omp.declaretarget} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_any_link +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp.declaretarget} { +func.func @omp_decl_tar_any_link() -> () attributes {omp.declare_target = #omp.declaretarget} { + return +} + +// CHECK-LABEL: global external @omp_decl_tar_data_host_to +// CHECK-SAME: {{.*}} {{{.*}}omp.declare_target = #omp.declaretarget} +llvm.mlir.global external @omp_decl_tar_data_host_to() {omp.declare_target = #omp.declaretarget} : i32 { + %0 = llvm.mlir.constant(1 : i32) : i32 + llvm.return %0 : i32 +} + +// CHECK-LABEL: global external @omp_decl_tar_data_host_link +// CHECK-SAME: {{.*}} {{{.*}}omp.declare_target = #omp.declaretarget} +llvm.mlir.global external @omp_decl_tar_data_host_link() {omp.declare_target = #omp.declaretarget} : i32 { + %0 = llvm.mlir.constant(1 : i32) : i32 + llvm.return %0 : i32 +} + +// CHECK-LABEL: global external @omp_decl_tar_data_nohost_to +// CHECK-SAME: {{.*}} {{{.*}}omp.declare_target = #omp.declaretarget} +llvm.mlir.global external @omp_decl_tar_data_nohost_to() {omp.declare_target = #omp.declaretarget} : i32 { + %0 = llvm.mlir.constant(1 : i32) : i32 + llvm.return %0 : i32 +} + +// CHECK-LABEL: global external @omp_decl_tar_data_nohost_link +// CHECK-SAME: {{.*}} {{{.*}}omp.declare_target = #omp.declaretarget} +llvm.mlir.global external @omp_decl_tar_data_nohost_link() {omp.declare_target = #omp.declaretarget} : i32 { + %0 = llvm.mlir.constant(1 : i32) : i32 + llvm.return %0 : i32 +} + +// CHECK-LABEL: global external @omp_decl_tar_data_any_to +// CHECK-SAME: {{.*}} {{{.*}}omp.declare_target = #omp.declaretarget} +llvm.mlir.global external @omp_decl_tar_data_any_to() {omp.declare_target = #omp.declaretarget} : i32 { + %0 = llvm.mlir.constant(1 : i32) : i32 + llvm.return %0 : i32 +} + +// CHECK-LABEL: global external @omp_decl_tar_data_any_link +// CHECK-SAME: {{.*}} {{{.*}}omp.declare_target = #omp.declaretarget} +llvm.mlir.global external @omp_decl_tar_data_any_link() {omp.declare_target = #omp.declaretarget} : i32 { + %0 = llvm.mlir.constant(1 : i32) : i32 + llvm.return %0 : i32 +}