| //===-- CodeGenOpenMP.cpp -------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "flang/Optimizer/CodeGen/CodeGenOpenMP.h" | |
| #include "flang/Optimizer/Builder/FIRBuilder.h" | |
| #include "flang/Optimizer/CodeGen/CodeGen.h" | |
| #include "flang/Optimizer/Dialect/FIRType.h" | |
| #include "flang/Optimizer/Dialect/Support/FIRContext.h" | |
| #include "flang/Optimizer/Support/FatalError.h" | |
| #include "flang/Optimizer/Support/Utils.h" | |
| #include "mlir/Conversion/LLVMCommon/Pattern.h" | |
| #include "mlir/Dialect/LLVMIR/LLVMDialect.h" | |
| #include "mlir/Dialect/OpenMP/OpenMPDialect.h" | |
| #include "mlir/IR/PatternMatch.h" | |
| #include "mlir/Transforms/DialectConversion.h" | |
| using namespace fir; | |
| #define DEBUG_TYPE "flang-codegen-openmp" | |
| // fir::LLVMTypeConverter for converting to LLVM IR dialect types. | |
| #include "flang/Optimizer/CodeGen/TypeConverter.h" | |
| namespace { | |
| /// A pattern that converts the region arguments in a single-region OpenMP | |
| /// operation to the LLVM dialect. The body of the region is not modified and is | |
| /// expected to either be processed by the conversion infrastructure or already | |
| /// contain ops compatible with LLVM dialect types. | |
| template <typename OpType> | |
| class OpenMPFIROpConversion : public mlir::ConvertOpToLLVMPattern<OpType> { | |
| public: | |
| explicit OpenMPFIROpConversion(const fir::LLVMTypeConverter &lowering) | |
| : mlir::ConvertOpToLLVMPattern<OpType>(lowering) {} | |
| const fir::LLVMTypeConverter &lowerTy() const { | |
| return *static_cast<const fir::LLVMTypeConverter *>( | |
| this->getTypeConverter()); | |
| } | |
| }; | |
| // FIR Op specific conversion for MapInfoOp that overwrites the default OpenMP | |
| // Dialect lowering, this allows FIR specific lowering of types, required for | |
| // descriptors of allocatables currently. | |
| struct MapInfoOpConversion | |
| : public OpenMPFIROpConversion<mlir::omp::MapInfoOp> { | |
| using OpenMPFIROpConversion::OpenMPFIROpConversion; | |
| mlir::omp::MapBoundsOp | |
| createBoundsForCharString(mlir::ConversionPatternRewriter &rewriter, | |
| unsigned int len, mlir::Location loc) const { | |
| mlir::Type i64Ty = rewriter.getIntegerType(64); | |
| auto lBound = mlir::LLVM::ConstantOp::create(rewriter, loc, i64Ty, 0); | |
| auto uBoundAndExt = | |
| mlir::LLVM::ConstantOp::create(rewriter, loc, i64Ty, len - 1); | |
| auto stride = mlir::LLVM::ConstantOp::create(rewriter, loc, i64Ty, 1); | |
| auto baseLb = mlir::LLVM::ConstantOp::create(rewriter, loc, i64Ty, 1); | |
| auto mapBoundType = rewriter.getType<mlir::omp::MapBoundsType>(); | |
| return mlir::omp::MapBoundsOp::create(rewriter, loc, mapBoundType, lBound, | |
| uBoundAndExt, uBoundAndExt, stride, | |
| /*strideInBytes*/ false, baseLb); | |
| } | |
| llvm::LogicalResult | |
| matchAndRewrite(mlir::omp::MapInfoOp curOp, OpAdaptor adaptor, | |
| mlir::ConversionPatternRewriter &rewriter) const override { | |
| const mlir::TypeConverter *converter = getTypeConverter(); | |
| llvm::SmallVector<mlir::Type> resTypes; | |
| if (failed(converter->convertTypes(curOp->getResultTypes(), resTypes))) | |
| return mlir::failure(); | |
| auto newProperties = curOp.getProperties(); | |
| mlir::omp::MapBoundsOp mapBoundsOp; | |
| auto convertTypeAttr = [&](mlir::TypeAttr typeAttr) { | |
| mlir::Type newAttr; | |
| if (fir::isTypeWithDescriptor(typeAttr.getValue())) { | |
| newAttr = lowerTy().convertBoxTypeAsStruct( | |
| mlir::cast<fir::BaseBoxType>(typeAttr.getValue())); | |
| } else if (fir::isa_char_string(fir::unwrapSequenceType( | |
| fir::unwrapPassByRefType(typeAttr.getValue()))) && | |
| !characterWithDynamicLen( | |
| fir::unwrapPassByRefType(typeAttr.getValue()))) { | |
| // Characters with a LEN param are represented as strings | |
| // (array of characters), the lowering to LLVM dialect | |
| // doesn't generate bounds for these (and this is not | |
| // done at the initial lowering either) and there is | |
| // minor inconsistencies in the variable types we | |
| // create for the map without this step when converting | |
| // to the LLVM dialect. | |
| // | |
| // For example, given the types: | |
| // | |
| // 1) CHARACTER(LEN=16), dimension(:,:), allocatable :: char_arr | |
| // 2) CHARACTER(LEN=16), dimension(10,10) :: char_arr | |
| // | |
| // We get the FIR types (note for 1: we already peeled off the | |
| // dynamic extents from the type at this stage, but the conversion | |
| // to llvm dialect does that in any case, so the final result | |
| // is the same): | |
| // | |
| // 1) !fir.char<1,16> | |
| // 2) !fir.array<10x10x!fir.char<1,16>> | |
| // | |
| // Which are converted to the LLVM dialect types: | |
| // | |
| // 1) !llvm.array<16 x i8> | |
| // 2) llvm.array<10 x array<10 x array<16 x i8>> | |
| // | |
| // And in both cases, we are missing the innermost bounds for | |
| // the !fir.char<1,16> which is expanded into a 16 x i8 array | |
| // in the conversion to LLVM dialect. | |
| // | |
| // The problem with this is that we would like to treat these | |
| // cases identically and not have to create specialised | |
| // lowerings for either of these in the lowering to LLVM-IR | |
| // and treat them like any other array that passes through. | |
| // | |
| // To do so below, we generate an extra bound for the | |
| // innermost array (the char type/string) using the LEN | |
| // parameter of the character type. And we "canonicalize" | |
| // the type, stripping it down to the base element type, | |
| // which in this case is an i8. This effectively allows | |
| // the lowering to treat this as a 1-D array with multiple | |
| // bounds which it is capable of handling without any special | |
| // casing. | |
| // TODO: Handle dynamic LEN characters. | |
| if (auto ct = mlir::dyn_cast_or_null<fir::CharacterType>( | |
| fir::unwrapSequenceType(typeAttr.getValue()))) { | |
| newAttr = converter->convertType( | |
| fir::unwrapSequenceType(typeAttr.getValue())); | |
| if (auto type = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(newAttr)) | |
| newAttr = type.getElementType(); | |
| // We do not generate MapBoundsOps for the device pass, as | |
| // MapBoundsOps are not generated for the device pass, as | |
| // they're unused in the device lowering. | |
| auto offloadMod = | |
| llvm::dyn_cast_or_null<mlir::omp::OffloadModuleInterface>( | |
| *curOp->getParentOfType<mlir::ModuleOp>()); | |
| if (!offloadMod.getIsTargetDevice()) | |
| mapBoundsOp = createBoundsForCharString(rewriter, ct.getLen(), | |
| curOp.getLoc()); | |
| } else { | |
| newAttr = converter->convertType(typeAttr.getValue()); | |
| } | |
| } else { | |
| newAttr = converter->convertType(typeAttr.getValue()); | |
| } | |
| return mlir::TypeAttr::get(newAttr); | |
| }; | |
| newProperties.setVarPtrType(convertTypeAttr(curOp.getVarPtrTypeAttr())); | |
| if (mlir::TypeAttr varPtrPtrType = curOp.getVarPtrPtrTypeAttr()) | |
| newProperties.setVarPtrPtrType(convertTypeAttr(varPtrPtrType)); | |
| auto newOp = rewriter.replaceOpWithNewOp<mlir::omp::MapInfoOp>( | |
| curOp, resTypes, adaptor.getOperands(), newProperties, | |
| curOp->getDiscardableAttrDictionary().getValue()); | |
| if (mapBoundsOp) { | |
| rewriter.startOpModification(newOp); | |
| newOp.getBoundsMutable().append(mlir::ValueRange{mapBoundsOp}); | |
| rewriter.finalizeOpModification(newOp); | |
| } | |
| return mlir::success(); | |
| } | |
| }; | |
| // FIR op specific conversion for PrivateClauseOp that overwrites the default | |
| // OpenMP Dialect lowering, this allows FIR-aware lowering of types, required | |
| // for boxes because the OpenMP dialect conversion doesn't know anything about | |
| // FIR types. | |
| struct PrivateClauseOpConversion | |
| : public OpenMPFIROpConversion<mlir::omp::PrivateClauseOp> { | |
| using OpenMPFIROpConversion::OpenMPFIROpConversion; | |
| llvm::LogicalResult | |
| matchAndRewrite(mlir::omp::PrivateClauseOp curOp, OpAdaptor adaptor, | |
| mlir::ConversionPatternRewriter &rewriter) const override { | |
| const fir::LLVMTypeConverter &converter = lowerTy(); | |
| mlir::Type convertedAllocType; | |
| if (auto box = mlir::dyn_cast<fir::BaseBoxType>(curOp.getType())) { | |
| // In LLVM codegen fir.box<> == fir.ref<fir.box<>> == llvm.ptr | |
| // Here we really do want the actual structure | |
| if (box.isAssumedRank()) | |
| TODO(curOp->getLoc(), "Privatize an assumed rank array"); | |
| unsigned rank = 0; | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>( | |
| fir::unwrapRefType(box.getEleTy()))) | |
| rank = seqTy.getShape().size(); | |
| convertedAllocType = converter.convertBoxTypeAsStruct(box, rank); | |
| } else { | |
| convertedAllocType = converter.convertType(adaptor.getType()); | |
| } | |
| if (!convertedAllocType) | |
| return mlir::failure(); | |
| rewriter.startOpModification(curOp); | |
| curOp.setType(convertedAllocType); | |
| rewriter.finalizeOpModification(curOp); | |
| return mlir::success(); | |
| } | |
| }; | |
| // Convert FIR type to LLVM without turning fir.box<T> into memory | |
| // reference. | |
| static mlir::Type convertObjectType(const fir::LLVMTypeConverter &converter, | |
| mlir::Type firType) { | |
| if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(firType)) | |
| return converter.convertBoxTypeAsStruct(boxTy); | |
| return converter.convertType(firType); | |
| } | |
| // FIR Op specific conversion for TargetAllocMemOp | |
| struct TargetAllocMemOpConversion | |
| : public OpenMPFIROpConversion<mlir::omp::TargetAllocMemOp> { | |
| using OpenMPFIROpConversion::OpenMPFIROpConversion; | |
| llvm::LogicalResult | |
| matchAndRewrite(mlir::omp::TargetAllocMemOp allocmemOp, OpAdaptor adaptor, | |
| mlir::ConversionPatternRewriter &rewriter) const override { | |
| mlir::Type heapTy = allocmemOp.getAllocatedType(); | |
| mlir::Location loc = allocmemOp.getLoc(); | |
| auto ity = lowerTy().indexType(); | |
| mlir::Type dataTy = fir::unwrapRefType(heapTy); | |
| mlir::Type llvmObjectTy = convertObjectType(lowerTy(), dataTy); | |
| if (fir::isRecordWithTypeParameters(fir::unwrapSequenceType(dataTy))) | |
| TODO(loc, "omp.target_allocmem codegen of derived type with length " | |
| "parameters"); | |
| mlir::Value size = fir::computeElementDistance( | |
| loc, llvmObjectTy, ity, rewriter, lowerTy().getDataLayout()); | |
| if (auto scaleSize = fir::genAllocationScaleSize( | |
| loc, allocmemOp.getInType(), ity, rewriter)) | |
| size = mlir::LLVM::MulOp::create(rewriter, loc, ity, size, scaleSize); | |
| for (mlir::Value opnd : adaptor.getOperands().drop_front()) | |
| size = mlir::LLVM::MulOp::create( | |
| rewriter, loc, ity, size, | |
| integerCast(lowerTy(), loc, rewriter, ity, opnd)); | |
| auto mallocTyWidth = lowerTy().getIndexTypeBitwidth(); | |
| auto mallocTy = | |
| mlir::IntegerType::get(rewriter.getContext(), mallocTyWidth); | |
| if (mallocTyWidth != ity.getIntOrFloatBitWidth()) | |
| size = integerCast(lowerTy(), loc, rewriter, mallocTy, size); | |
| rewriter.modifyOpInPlace(allocmemOp, [&]() { | |
| allocmemOp.setInType(rewriter.getI8Type()); | |
| allocmemOp.getTypeparamsMutable().clear(); | |
| allocmemOp.getTypeparamsMutable().append(size); | |
| }); | |
| return mlir::success(); | |
| } | |
| }; | |
| struct DeclareMapperOpConversion | |
| : public OpenMPFIROpConversion<mlir::omp::DeclareMapperOp> { | |
| using OpenMPFIROpConversion::OpenMPFIROpConversion; | |
| llvm::LogicalResult | |
| matchAndRewrite(mlir::omp::DeclareMapperOp curOp, OpAdaptor adaptor, | |
| mlir::ConversionPatternRewriter &rewriter) const override { | |
| rewriter.startOpModification(curOp); | |
| curOp.setType(convertObjectType(lowerTy(), curOp.getType())); | |
| rewriter.finalizeOpModification(curOp); | |
| return mlir::success(); | |
| } | |
| }; | |
| } // namespace | |
| void fir::populateOpenMPFIRToLLVMConversionPatterns( | |
| const LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns) { | |
| patterns.add<MapInfoOpConversion>(converter); | |
| patterns.add<PrivateClauseOpConversion>(converter); | |
| patterns.add<TargetAllocMemOpConversion>(converter); | |
| patterns.add<DeclareMapperOpConversion>(converter); | |
| } |