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 @@ -2239,6 +2239,107 @@ converter.bindSymbol(sym, symThreadprivateExv); } +void handleDeclareTarget(Fortran::lower::AbstractConverter &converter, + Fortran::lower::pft::Evaluation &eval, + const Fortran::parser::OpenMPDeclareTargetConstruct + &declareTargetConstruct) { + llvm::SmallVector symbols; + auto findFuncAndVarSyms = [&](const Fortran::parser::OmpObjectList &objList) { + 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)) { + symbols.push_back(*name->symbol); + } + }, + [&](const Fortran::parser::Name &name) { + symbols.push_back(*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); + } else if (const auto *clauseList{ + Fortran::parser::Unwrap( + spec.u)}) { + if (clauseList->v.empty()) { + // Case: declare target, implicit capture of function + symbols.push_back(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); + } else if (const auto *linkClause{ + std::get_if( + &clause.u)}) { + // Case: declare target link(var1, var2)... + TODO(converter.getCurrentLocation(), + "the link clause is currently unsupported"); + } 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 : symbols) { + auto *op = mod.lookupSymbol(converter.mangleName(sym)); + + // TODO: Remove this cast and TODO assert when global data and link are + // supported + mlir::func::FuncOp fOp = mlir::dyn_cast(op); + if (!fOp) + TODO(converter.getCurrentLocation(), + "only subroutines and functions are currently supported"); + + 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 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 (mlir::omp::OpenMPDialect::isDeclareTarget(fOp)) { + if (mlir::omp::OpenMPDialect::getDeclareTargetDeviceType(fOp) != + newDeviceType) + mlir::omp::OpenMPDialect::setDeclareTarget( + op, mlir::omp::DeclareTargetDeviceType::any); + continue; + } + + mlir::omp::OpenMPDialect::setDeclareTarget(op, newDeviceType); + } +} + void Fortran::lower::genOpenMPDeclarativeConstruct( Fortran::lower::AbstractConverter &converter, Fortran::lower::pft::Evaluation &eval, @@ -2261,8 +2362,7 @@ }, [&](const Fortran::parser::OpenMPDeclareTargetConstruct &declareTargetConstruct) { - TODO(converter.getCurrentLocation(), - "OpenMPDeclareTargetConstruct"); + handleDeclareTarget(converter, eval, declareTargetConstruct); }, [&](const Fortran::parser::OpenMPRequiresConstruct &requiresConstruct) { 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,141 @@ +#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; +}; + +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/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.f90 b/flang/test/Lower/OpenMP/omp-declare-target.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Lower/OpenMP/omp-declare-target.f90 @@ -0,0 +1,262 @@ +!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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +SUBROUTINE SUBR_DEFAULT_ANY() +!$omp declare target +END + +! CHECK-LABEL: func.func @_QPsubr_default_extendedlist() +! CHECK-SAME: {{.*}}attributes {omp.declare_target = #omp{{.*}} +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{{.*}} +SUBROUTINE SUBR_EXIST_ON_BOTH() +END + +!! ----- + +! Check declare target inconjunction 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{{.*}} +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{{.*}} +FUNCTION TARGET_FUNCTION(TOGGLE) RESULT(I) +!$omp declare target + INTEGER :: I + LOGICAL :: TOGGLE + I = IMPLICITLY_CAPTURED(TOGGLE) +END FUNCTION TARGET_FUNCTION + +!! ----- + +! Check declare target inconjunction 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{{.*}} +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{{.*}} +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{{.*}} +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 inconjunction 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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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 inconjunction 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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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{{.*}} +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 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,191 @@ +! 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 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,10 @@ #include "mlir/Interfaces/ControlFlowInterfaces.h" #include "mlir/Interfaces/SideEffectInterfaces.h" +namespace mlir::omp { +enum class DeclareTargetDeviceType : 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/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 @@ -28,6 +28,13 @@ let cppNamespace = "::mlir::omp"; let dependentDialects = ["::mlir::LLVM::LLVMDialect"]; let useDefaultAttributePrinterParser = 1; + + let extraClassDeclaration = [{ + // Helper functions for assigning a DeclareTargetDeviceType Attribute to functions + static void setDeclareTarget(Operation *func, mlir::omp::DeclareTargetDeviceType deviceType); + static bool isDeclareTarget(Operation *func); + static mlir::omp::DeclareTargetDeviceType getDeclareTargetDeviceType(Operation *func); + }]; } // OmpCommon requires definition of OpenACC_Dialect. @@ -87,6 +94,27 @@ 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 `)`"; +} + //===----------------------------------------------------------------------===// // 2.6 parallel Construct //===----------------------------------------------------------------------===// 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 @@ -1441,6 +1441,30 @@ return success(); } +//===----------------------------------------------------------------------===// +// OpenMPDialect helper functions +//===----------------------------------------------------------------------===// + +void OpenMPDialect::setDeclareTarget( + Operation *func, mlir::omp::DeclareTargetDeviceType deviceType) { + func->setAttr("omp.declare_target", + mlir::omp::DeclareTargetDeviceTypeAttr::get(func->getContext(), + deviceType)); +} + +bool OpenMPDialect::isDeclareTarget(Operation *func) { + return func->hasAttr("omp.declare_target"); +} + +mlir::omp::DeclareTargetDeviceType +OpenMPDialect::getDeclareTargetDeviceType(Operation *func) { + if (mlir::Attribute declTar = func->getAttr("omp.declare_target")) { + if (declTar.isa()) + return declTar.cast().getValue(); + } + return {}; +} + #define GET_ATTRDEF_CLASSES #include "mlir/Dialect/OpenMP/OpenMPOpsAttributes.cpp.inc" 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,23 @@ // CHECK: module attributes {omp.flags = #omp.flags} { module attributes {omp.flags = #omp.flags} {} + +// ---- + +// CHECK-LABEL: func @omp_decl_tar_host +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp} { +func.func @omp_decl_tar_host() -> () attributes {omp.declare_target = #omp} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_nohost +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp} { +func.func @omp_decl_tar_nohost() -> () attributes {omp.declare_target = #omp} { + return +} + +// CHECK-LABEL: func @omp_decl_tar_any +// CHECK-SAME: {{.*}} attributes {omp.declare_target = #omp} { +func.func @omp_decl_tar_any() -> () attributes {omp.declare_target = #omp} { + return +}