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/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/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/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 +}