| //===-- FIROps.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/Dialect/FIROps.h" | |
| #include "flang/Optimizer/Dialect/CUDAKernelOpInterface.h" | |
| #include "flang/Optimizer/Dialect/FIRAttr.h" | |
| #include "flang/Optimizer/Dialect/FIRBoxUtils.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" | |
| #include "flang/Optimizer/Dialect/Support/KindMapping.h" | |
| #include "flang/Optimizer/Support/Utils.h" | |
| #include "mlir/Dialect/Func/IR/FuncOps.h" | |
| #include "mlir/Dialect/OpenACC/OpenACC.h" | |
| #include "mlir/Dialect/OpenACC/OpenACCUtils.h" | |
| #include "mlir/Dialect/OpenMP/OpenMPDialect.h" | |
| #include "mlir/IR/Attributes.h" | |
| #include "mlir/IR/BuiltinAttributes.h" | |
| #include "mlir/IR/BuiltinOps.h" | |
| #include "mlir/IR/Diagnostics.h" | |
| #include "mlir/IR/Dominance.h" | |
| #include "mlir/IR/Matchers.h" | |
| #include "mlir/IR/OpDefinition.h" | |
| #include "mlir/IR/PatternMatch.h" | |
| #include "mlir/IR/TypeRange.h" | |
| #include "mlir/Interfaces/DataLayoutInterfaces.h" | |
| #include "llvm/ADT/DenseMap.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/TypeSwitch.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include <type_traits> | |
| namespace { | |
| #include "flang/Optimizer/Dialect/CanonicalizationPatterns.inc" | |
| } // namespace | |
| static llvm::cl::opt<bool> clUseStrictVolatileVerification( | |
| "strict-fir-volatile-verifier", llvm::cl::init(false), | |
| llvm::cl::desc( | |
| "use stricter verifier for FIR operations with volatile types")); | |
| bool fir::useStrictVolatileVerification() { | |
| return clUseStrictVolatileVerification; | |
| } | |
| static void propagateAttributes(mlir::Operation *fromOp, | |
| mlir::Operation *toOp) { | |
| if (!fromOp || !toOp) | |
| return; | |
| for (mlir::NamedAttribute attr : | |
| fromOp->getDiscardableAttrDictionary().getValue()) { | |
| if (attr.getName().getValue().starts_with( | |
| mlir::acc::OpenACCDialect::getDialectNamespace())) | |
| toOp->setDiscardableAttr(attr.getName(), attr.getValue()); | |
| } | |
| } | |
| static mlir::ParseResult | |
| parseOptionalProperties(mlir::OpAsmParser &parser, | |
| mlir::Attribute &parsedProperties) { | |
| if (mlir::succeeded(parser.parseOptionalLess()) && | |
| (parser.parseAttribute(parsedProperties) || parser.parseGreater())) | |
| return mlir::failure(); | |
| if (parsedProperties && !mlir::isa<mlir::DictionaryAttr>(parsedProperties)) | |
| return parser.emitError(parser.getNameLoc(), | |
| "expected properties dictionary"); | |
| return mlir::success(); | |
| } | |
| template <typename Op> | |
| static mlir::ParseResult setParsedProperties(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result, | |
| mlir::Attribute parsedProperties) { | |
| mlir::NamedAttrList properties( | |
| parsedProperties ? mlir::cast<mlir::DictionaryAttr>(parsedProperties) | |
| : parser.getBuilder().getDictionaryAttr({})); | |
| for (llvm::StringRef name : Op::getAttributeNames()) | |
| if (mlir::Attribute attr = result.attributes.erase(name)) | |
| properties.set(name, attr); | |
| mlir::DictionaryAttr propertyDictionary = | |
| properties.getDictionary(result.getContext()); | |
| auto emitError = [&]() { | |
| return mlir::emitError(result.location, "invalid properties ") | |
| << propertyDictionary << " for op " << result.name.getStringRef() | |
| << ": "; | |
| }; | |
| return Op::setPropertiesFromParsedAttr( | |
| result.getOrAddProperties<typename Op::Properties>(), propertyDictionary, | |
| emitError); | |
| } | |
| /// Return true if a sequence type is of some incomplete size or a record type | |
| /// is malformed or contains an incomplete sequence type. An incomplete sequence | |
| /// type is one with more unknown extents in the type than have been provided | |
| /// via `dynamicExtents`. Sequence types with an unknown rank are incomplete by | |
| /// definition. | |
| static bool verifyInType(mlir::Type inType, | |
| llvm::SmallVectorImpl<llvm::StringRef> &visited, | |
| unsigned dynamicExtents = 0) { | |
| if (auto st = mlir::dyn_cast<fir::SequenceType>(inType)) { | |
| auto shape = st.getShape(); | |
| if (shape.size() == 0) | |
| return true; | |
| for (std::size_t i = 0, end = shape.size(); i < end; ++i) { | |
| if (shape[i] != fir::SequenceType::getUnknownExtent()) | |
| continue; | |
| if (dynamicExtents-- == 0) | |
| return true; | |
| } | |
| } else if (auto rt = mlir::dyn_cast<fir::RecordType>(inType)) { | |
| // don't recurse if we're already visiting this one | |
| if (llvm::is_contained(visited, rt.getName())) | |
| return false; | |
| // keep track of record types currently being visited | |
| visited.push_back(rt.getName()); | |
| for (auto &field : rt.getTypeList()) | |
| if (verifyInType(field.second, visited)) | |
| return true; | |
| visited.pop_back(); | |
| } | |
| return false; | |
| } | |
| static bool verifyTypeParamCount(mlir::Type inType, unsigned numParams) { | |
| auto ty = fir::unwrapSequenceType(inType); | |
| if (numParams > 0) { | |
| if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) | |
| return numParams != recTy.getNumLenParams(); | |
| if (auto chrTy = mlir::dyn_cast<fir::CharacterType>(ty)) | |
| return !(numParams == 1 && chrTy.hasDynamicLen()); | |
| return true; | |
| } | |
| if (auto chrTy = mlir::dyn_cast<fir::CharacterType>(ty)) | |
| return !chrTy.hasConstantLen(); | |
| return false; | |
| } | |
| /// Parser shared by Alloca and Allocmem | |
| /// operation ::= %res = (`fir.alloca` | `fir.allocmem`) $in_type | |
| /// ( `(` $typeparams `)` )? ( `,` $shape )? | |
| /// attr-dict-without-keyword | |
| template <typename OP, typename FN> | |
| static mlir::ParseResult parseAllocatableOp(FN wrapResultType, | |
| mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::Type intype; | |
| if (parser.parseType(intype)) | |
| return mlir::failure(); | |
| auto &builder = parser.getBuilder(); | |
| result.addAttribute("in_type", mlir::TypeAttr::get(intype)); | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> operands; | |
| llvm::SmallVector<mlir::Type> typeVec; | |
| bool hasOperands = false; | |
| std::int32_t typeparamsSize = 0; | |
| if (!parser.parseOptionalLParen()) { | |
| // parse the LEN params of the derived type. (<params> : <types>) | |
| if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::None) || | |
| parser.parseColonTypeList(typeVec) || parser.parseRParen()) | |
| return mlir::failure(); | |
| typeparamsSize = operands.size(); | |
| hasOperands = true; | |
| } | |
| std::int32_t shapeSize = 0; | |
| if (!parser.parseOptionalComma()) { | |
| // parse size to scale by, vector of n dimensions of type index | |
| if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::None)) | |
| return mlir::failure(); | |
| shapeSize = operands.size() - typeparamsSize; | |
| auto idxTy = builder.getIndexType(); | |
| for (std::int32_t i = typeparamsSize, end = operands.size(); i != end; ++i) | |
| typeVec.push_back(idxTy); | |
| hasOperands = true; | |
| } | |
| if (hasOperands && | |
| parser.resolveOperands(operands, typeVec, parser.getNameLoc(), | |
| result.operands)) | |
| return mlir::failure(); | |
| mlir::Type restype = wrapResultType(intype); | |
| if (!restype) { | |
| parser.emitError(parser.getNameLoc(), "invalid allocate type: ") << intype; | |
| return mlir::failure(); | |
| } | |
| result.addAttribute("operandSegmentSizes", builder.getDenseI32ArrayAttr( | |
| {typeparamsSize, shapeSize})); | |
| mlir::Attribute parsedProperties; | |
| if (parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(result.attributes) || | |
| parser.addTypeToList(restype, result.types) || | |
| setParsedProperties<OP>(parser, result, parsedProperties)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| template <typename OP> | |
| static void printAllocatableOp(mlir::OpAsmPrinter &p, OP &op) { | |
| p << ' ' << op.getInType(); | |
| if (!op.getTypeparams().empty()) { | |
| p << '(' << op.getTypeparams() << " : " << op.getTypeparams().getTypes() | |
| << ')'; | |
| } | |
| // print the shape of the allocation (if any); all must be index type | |
| for (auto sh : op.getShape()) { | |
| p << ", "; | |
| p.printOperand(sh); | |
| } | |
| OP::printProperties(op.getContext(), p, op.getProperties(), | |
| {"in_type", "operandSegmentSizes"}); | |
| p.printOptionalAttrDict(op->getDiscardableAttrDictionary()); | |
| } | |
| bool fir::mayBeAbsentBox(mlir::Value val) { | |
| assert(mlir::isa<fir::BaseBoxType>(val.getType()) && "expected box argument"); | |
| while (val) { | |
| mlir::Operation *defOp = val.getDefiningOp(); | |
| if (!defOp) { | |
| // TODO: we need a better way to identify definitely | |
| // present boxes to allow more speculation. | |
| // Maybe we should rely on [hl]fir.declare's inside | |
| // acc.compute_region or use more generic interface | |
| // (such as RegionBranchOpInterface) to map the block | |
| // arguments to the operands (though, the meaning | |
| // of operands/block-arguments of acc.compute_region | |
| // is tricky). | |
| if (mlir::Value accOperand = mlir::acc::getACCOperandForBlockArg(val)) { | |
| val = accOperand; | |
| continue; | |
| } | |
| return true; | |
| } | |
| if (auto varIface = mlir::dyn_cast<fir::FortranVariableOpInterface>(defOp)) | |
| return varIface.isOptional(); | |
| // Check for fir.embox and fir.rebox before checking for | |
| // FortranObjectViewOpInterface, which they support. | |
| // A box created by fir.embox/fir.rebox/fir.rebox_assumed_rank is only | |
| // potentially absent when the operation was explicitly tagged with the | |
| // `optional` attribute. | |
| if (auto reboxOp = mlir::dyn_cast<fir::ReboxOp>(defOp)) | |
| return reboxOp.getOptional(); | |
| if (auto emboxOp = mlir::dyn_cast<fir::EmboxOp>(defOp)) | |
| return emboxOp.getOptional(); | |
| if (auto reboxAROp = mlir::dyn_cast<fir::ReboxAssumedRankOp>(defOp)) | |
| return reboxAROp.getOptional(); | |
| if (mlir::isa<fir::LoadOp>(defOp)) | |
| return false; | |
| if (mlir::isa<ACC_DATA_ENTRY_AND_INIT_OPS>(defOp)) { | |
| val = mlir::acc::getVar(defOp); | |
| continue; | |
| } | |
| if (auto viewIface = | |
| mlir::dyn_cast<fir::FortranObjectViewOpInterface>(defOp)) { | |
| val = viewIface.getViewSource(mlir::cast<mlir::OpResult>(val)); | |
| continue; | |
| } | |
| break; | |
| } | |
| return true; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // AllocaOp | |
| //===----------------------------------------------------------------------===// | |
| /// Create a legal memory reference as return type | |
| static mlir::Type wrapAllocaResultType(mlir::Type intype) { | |
| // FIR semantics: memory references to memory references are disallowed | |
| if (mlir::isa<fir::ReferenceType>(intype)) | |
| return {}; | |
| return fir::ReferenceType::get(intype); | |
| } | |
| llvm::SmallVector<mlir::MemorySlot> fir::AllocaOp::getPromotableSlots() { | |
| // TODO: support promotion of dynamic allocas | |
| if (isDynamic()) | |
| return {}; | |
| return {mlir::MemorySlot{getResult(), getAllocatedType()}}; | |
| } | |
| mlir::Value fir::AllocaOp::getDefaultValue(const mlir::MemorySlot &slot, | |
| mlir::OpBuilder &builder) { | |
| return fir::UndefOp::create(builder, getLoc(), slot.elemType); | |
| } | |
| void fir::AllocaOp::handleBlockArgument(const mlir::MemorySlot &slot, | |
| mlir::BlockArgument argument, | |
| mlir::OpBuilder &builder) { | |
| // When there is a fir.declare, fir.debug_value must be emitted at each value | |
| // change and at each beginning of a block where the reaching value is | |
| // propagated as a block argument. | |
| // TODO: in order to get proper inter-dialect mem2reg, the | |
| // PromotableOpInterface should be provided with a | |
| // requiresInsertedBlockArguments similar to requiresReplacedValues so that | |
| // fir::DeclareOp can be the one dictating that this needs to happen instead | |
| // of the allocation. There are other challenges to inter dialect mem2reg to | |
| // solve first, like having a common concept for going through converts and | |
| // no-ops like fir.declare (i.e., to replace the FIR specific | |
| // isSlotOrDeclaredSlot). | |
| for (mlir::Operation *user : getOperation()->getUsers()) | |
| if (auto declareOp = mlir::dyn_cast<fir::DeclareOp>(user)) { | |
| // Inlined dummy scopes may not dominate block-argument debug values. | |
| if (declareOp.getDummyScope()) | |
| continue; | |
| fir::DeclareValueOp::create( | |
| builder, declareOp.getLoc(), argument, declareOp.getDummyScope(), | |
| declareOp.getUniqNameAttr(), declareOp.getFortranAttrsAttr(), | |
| declareOp.getDataAttrAttr(), declareOp.getDummyArgNoAttr()); | |
| } | |
| } | |
| std::optional<mlir::PromotableAllocationOpInterface> | |
| fir::AllocaOp::handlePromotionComplete(const mlir::MemorySlot &slot, | |
| mlir::Value defaultValue, | |
| mlir::OpBuilder &builder) { | |
| if (defaultValue && defaultValue.use_empty()) { | |
| assert(mlir::isa<fir::UndefOp>(defaultValue.getDefiningOp()) && | |
| "Expected undef op to be the default value"); | |
| defaultValue.getDefiningOp()->erase(); | |
| } | |
| this->erase(); | |
| return std::nullopt; | |
| } | |
| mlir::Type fir::AllocaOp::getAllocatedType() { | |
| return mlir::cast<fir::ReferenceType>(getType()).getEleTy(); | |
| } | |
| mlir::Type fir::AllocaOp::getRefTy(mlir::Type ty) { | |
| return fir::ReferenceType::get(ty); | |
| } | |
| void fir::AllocaOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| llvm::StringRef uniqName, mlir::ValueRange typeparams, | |
| mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| auto nameAttr = builder.getStringAttr(uniqName); | |
| build(builder, result, wrapAllocaResultType(inType), inType, nameAttr, {}, | |
| /*pinned=*/false, typeparams, shape); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocaOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| llvm::StringRef uniqName, bool pinned, | |
| mlir::ValueRange typeparams, mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| auto nameAttr = builder.getStringAttr(uniqName); | |
| build(builder, result, wrapAllocaResultType(inType), inType, nameAttr, {}, | |
| pinned, typeparams, shape); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocaOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| llvm::StringRef uniqName, llvm::StringRef bindcName, | |
| mlir::ValueRange typeparams, mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| auto nameAttr = | |
| uniqName.empty() ? mlir::StringAttr{} : builder.getStringAttr(uniqName); | |
| auto bindcAttr = | |
| bindcName.empty() ? mlir::StringAttr{} : builder.getStringAttr(bindcName); | |
| build(builder, result, wrapAllocaResultType(inType), inType, nameAttr, | |
| bindcAttr, /*pinned=*/false, typeparams, shape); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocaOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| llvm::StringRef uniqName, llvm::StringRef bindcName, | |
| bool pinned, mlir::ValueRange typeparams, | |
| mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| auto nameAttr = | |
| uniqName.empty() ? mlir::StringAttr{} : builder.getStringAttr(uniqName); | |
| auto bindcAttr = | |
| bindcName.empty() ? mlir::StringAttr{} : builder.getStringAttr(bindcName); | |
| build(builder, result, wrapAllocaResultType(inType), inType, nameAttr, | |
| bindcAttr, pinned, typeparams, shape); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocaOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| mlir::ValueRange typeparams, mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| build(builder, result, wrapAllocaResultType(inType), inType, {}, {}, | |
| /*pinned=*/false, typeparams, shape); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocaOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| bool pinned, mlir::ValueRange typeparams, | |
| mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| build(builder, result, wrapAllocaResultType(inType), inType, {}, {}, pinned, | |
| typeparams, shape); | |
| result.addAttributes(attributes); | |
| } | |
| mlir::ParseResult fir::AllocaOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseAllocatableOp<fir::AllocaOp>(wrapAllocaResultType, parser, | |
| result); | |
| } | |
| void fir::AllocaOp::print(mlir::OpAsmPrinter &p) { | |
| printAllocatableOp(p, *this); | |
| } | |
| llvm::LogicalResult fir::AllocaOp::verify() { | |
| if (mlir::isa_and_nonnull<mlir::ModuleOp>((*this)->getParentOp())) | |
| return emitOpError("must not be defined at module scope"); | |
| llvm::SmallVector<llvm::StringRef> visited; | |
| if (verifyInType(getInType(), visited, numShapeOperands())) | |
| return emitOpError("invalid type for allocation"); | |
| if (verifyTypeParamCount(getInType(), numLenParams())) | |
| return emitOpError("LEN params do not correspond to type"); | |
| mlir::Type outType = getType(); | |
| if (!mlir::isa<fir::ReferenceType>(outType)) | |
| return emitOpError("must be a !fir.ref type"); | |
| return mlir::success(); | |
| } | |
| bool fir::AllocaOp::ownsNestedAlloca(mlir::Operation *op) { | |
| return op->hasTrait<mlir::OpTrait::IsIsolatedFromAbove>() || | |
| op->hasTrait<mlir::OpTrait::AutomaticAllocationScope>() || | |
| mlir::isa<mlir::LoopLikeOpInterface>(*op); | |
| } | |
| mlir::Region *fir::AllocaOp::getOwnerRegion() { | |
| mlir::Operation *currentOp = getOperation(); | |
| while (mlir::Operation *parentOp = currentOp->getParentOp()) { | |
| // If the operation was not registered, inquiries about its traits will be | |
| // incorrect and it is not possible to reason about the operation. This | |
| // should not happen in a normal Fortran compilation flow, but be foolproof. | |
| if (!parentOp->isRegistered()) | |
| return nullptr; | |
| if (fir::AllocaOp::ownsNestedAlloca(parentOp)) | |
| return currentOp->getParentRegion(); | |
| currentOp = parentOp; | |
| } | |
| return nullptr; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // AllocMemOp | |
| //===----------------------------------------------------------------------===// | |
| /// Create a legal heap reference as return type | |
| static mlir::Type wrapAllocMemResultType(mlir::Type intype) { | |
| // Fortran semantics: C852 an entity cannot be both ALLOCATABLE and POINTER | |
| // 8.5.3 note 1 prohibits ALLOCATABLE procedures as well | |
| // FIR semantics: one may not allocate a memory reference value | |
| if (mlir::isa<fir::ReferenceType, fir::HeapType, fir::PointerType, | |
| mlir::FunctionType>(intype)) | |
| return {}; | |
| return fir::HeapType::get(intype); | |
| } | |
| mlir::Type fir::AllocMemOp::getAllocatedType() { | |
| return mlir::cast<fir::HeapType>(getType()).getEleTy(); | |
| } | |
| mlir::Type fir::AllocMemOp::getRefTy(mlir::Type ty) { | |
| return fir::HeapType::get(ty); | |
| } | |
| void fir::AllocMemOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| llvm::StringRef uniqName, | |
| mlir::ValueRange typeparams, mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| auto nameAttr = builder.getStringAttr(uniqName); | |
| build(builder, result, wrapAllocMemResultType(inType), inType, nameAttr, {}, | |
| typeparams, shape, /*alignment=*/{}); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocMemOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| llvm::StringRef uniqName, llvm::StringRef bindcName, | |
| mlir::ValueRange typeparams, mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| auto nameAttr = builder.getStringAttr(uniqName); | |
| auto bindcAttr = builder.getStringAttr(bindcName); | |
| build(builder, result, wrapAllocMemResultType(inType), inType, nameAttr, | |
| bindcAttr, typeparams, shape, /*alignment=*/{}); | |
| result.addAttributes(attributes); | |
| } | |
| void fir::AllocMemOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Type inType, | |
| mlir::ValueRange typeparams, mlir::ValueRange shape, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| build(builder, result, wrapAllocMemResultType(inType), inType, {}, {}, | |
| typeparams, shape, /*alignment=*/{}); | |
| result.addAttributes(attributes); | |
| } | |
| mlir::ParseResult fir::AllocMemOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseAllocatableOp<fir::AllocMemOp>(wrapAllocMemResultType, parser, | |
| result); | |
| } | |
| void fir::AllocMemOp::print(mlir::OpAsmPrinter &p) { | |
| printAllocatableOp(p, *this); | |
| } | |
| llvm::LogicalResult fir::AllocMemOp::verify() { | |
| llvm::SmallVector<llvm::StringRef> visited; | |
| if (verifyInType(getInType(), visited, numShapeOperands())) | |
| return emitOpError("invalid type for allocation"); | |
| if (verifyTypeParamCount(getInType(), numLenParams())) | |
| return emitOpError("LEN params do not correspond to type"); | |
| mlir::Type outType = getType(); | |
| if (!mlir::dyn_cast<fir::HeapType>(outType)) | |
| return emitOpError("must be a !fir.heap type"); | |
| if (fir::isa_unknown_size_box(fir::dyn_cast_ptrEleTy(outType))) | |
| return emitOpError("cannot allocate !fir.box of unknown rank or type"); | |
| if (std::optional<std::int64_t> alignment = getAlignment()) { | |
| if (*alignment <= 0 || (*alignment & (*alignment - 1)) != 0) | |
| return emitOpError("alignment must be a positive power of two"); | |
| } | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ArrayCoorOp | |
| //===----------------------------------------------------------------------===// | |
| // CHARACTERs and derived types with LEN PARAMETERs are dependent types that | |
| // require runtime values to fully define the type of an object. | |
| static bool validTypeParams(mlir::Type dynTy, mlir::ValueRange typeParams, | |
| bool allowParamsForBox = false) { | |
| dynTy = fir::unwrapAllRefAndSeqType(dynTy); | |
| if (mlir::isa<fir::BaseBoxType>(dynTy)) { | |
| // A box value will contain type parameter values itself. | |
| if (!allowParamsForBox) | |
| return typeParams.size() == 0; | |
| // A boxed value may have no length parameters, when the lengths | |
| // are assumed. If dynamic lengths are used, then proceed | |
| // to the verification below. | |
| if (typeParams.size() == 0) | |
| return true; | |
| dynTy = fir::getFortranElementType(dynTy); | |
| } | |
| // Derived type must have all type parameters satisfied. | |
| if (auto recTy = mlir::dyn_cast<fir::RecordType>(dynTy)) | |
| return typeParams.size() == recTy.getNumLenParams(); | |
| // Characters with non-constant LEN must have a type parameter value. | |
| if (auto charTy = mlir::dyn_cast<fir::CharacterType>(dynTy)) | |
| if (charTy.hasDynamicLen()) | |
| return typeParams.size() == 1; | |
| // Otherwise, any type parameters are invalid. | |
| return typeParams.size() == 0; | |
| } | |
| llvm::LogicalResult fir::ArrayCoorOp::verify() { | |
| auto eleTy = fir::dyn_cast_ptrOrBoxEleTy(getMemref().getType()); | |
| auto arrTy = mlir::dyn_cast<fir::SequenceType>(eleTy); | |
| if (!arrTy) | |
| return emitOpError("must be a reference to an array"); | |
| auto arrDim = arrTy.getDimension(); | |
| if (auto shapeOp = getShape()) { | |
| auto shapeTy = shapeOp.getType(); | |
| unsigned shapeTyRank = 0; | |
| if (auto s = mlir::dyn_cast<fir::ShapeType>(shapeTy)) { | |
| shapeTyRank = s.getRank(); | |
| } else if (auto ss = mlir::dyn_cast<fir::ShapeShiftType>(shapeTy)) { | |
| shapeTyRank = ss.getRank(); | |
| } else { | |
| auto s = mlir::cast<fir::ShiftType>(shapeTy); | |
| shapeTyRank = s.getRank(); | |
| // TODO: it looks like PreCGRewrite and CodeGen can support | |
| // fir.shift with plain array reference, so we may consider | |
| // removing this check. | |
| if (!mlir::isa<fir::BaseBoxType>(getMemref().getType())) | |
| return emitOpError("shift can only be provided with fir.box memref"); | |
| } | |
| if (arrDim && arrDim != shapeTyRank) | |
| return emitOpError("rank of dimension mismatched"); | |
| // TODO: support slicing with changing the number of dimensions, | |
| // e.g. when array_coor represents an element access to array(:,1,:) | |
| // slice: the shape is 3D and the number of indices is 2 in this case. | |
| if (shapeTyRank != getIndices().size()) | |
| return emitOpError("number of indices do not match dim rank"); | |
| } | |
| if (auto sliceOp = getSlice()) { | |
| if (auto sl = mlir::dyn_cast_or_null<fir::SliceOp>(sliceOp.getDefiningOp())) | |
| if (!sl.getSubstr().empty()) | |
| return emitOpError("array_coor cannot take a slice with substring"); | |
| if (auto sliceTy = mlir::dyn_cast<fir::SliceType>(sliceOp.getType())) | |
| if (sliceTy.getRank() != arrDim) | |
| return emitOpError("rank of dimension in slice mismatched"); | |
| } | |
| if (!validTypeParams(getMemref().getType(), getTypeparams())) | |
| return emitOpError("invalid type parameters"); | |
| return mlir::success(); | |
| } | |
| // Helper shared by array_coor canonicalization patterns. Returns true if | |
| // folding `producers` into `consumer` would cross an ACC data-clause or CUDA | |
| // kernel boundary that the lowering pipeline relies on: | |
| // | |
| // - ACC: when `consumer` lives inside an ACC compute or data construct and | |
| // any producer's result is referenced by an ACC data-clause op, the | |
| // producer must remain (the data legalization pipeline expects it to be | |
| // the copyin var / kernel argument). | |
| // - CUF: every producer must share the same enclosing CUDA kernel as the | |
| // consumer; otherwise the kernel would end up capturing a host-side | |
| // descriptor directly, causing illegal device dereferences at runtime. | |
| static bool arrayCoorFoldCrossesACCOrCUDABoundary( | |
| mlir::Operation *consumer, llvm::ArrayRef<mlir::Operation *> producers) { | |
| if (consumer->getParentOfType<ACC_COMPUTE_AND_DATA_CONSTRUCT_OPS>() && | |
| llvm::any_of(producers, [](mlir::Operation *p) { | |
| return llvm::any_of(p->getUsers(), [](mlir::Operation *u) { | |
| return mlir::isa<ACC_DATA_ENTRY_OPS>(u); | |
| }); | |
| })) | |
| return true; | |
| auto consumerKernel = consumer->getParentOfType<fir::CUDAKernelOpInterface>(); | |
| for (mlir::Operation *p : producers) | |
| if (p->getParentOfType<fir::CUDAKernelOpInterface>() != consumerKernel) | |
| return true; | |
| return false; | |
| } | |
| // Pull in fir.embox and fir.rebox into fir.array_coor when possible. | |
| struct SimplifyArrayCoorOp : public mlir::OpRewritePattern<fir::ArrayCoorOp> { | |
| using mlir::OpRewritePattern<fir::ArrayCoorOp>::OpRewritePattern; | |
| llvm::LogicalResult | |
| matchAndRewrite(fir::ArrayCoorOp op, | |
| mlir::PatternRewriter &rewriter) const override { | |
| mlir::Value memref = op.getMemref(); | |
| // Look through fir.box_addr: if the array_coor's memref is a ref-typed | |
| // value produced by fir.box_addr, treat the underlying box as the | |
| // defining producer for the embox/rebox lookup below. This exposes a | |
| // sliced embox that is otherwise hidden by the box_addr indirection, | |
| // which is necessary for later lowering (FIRToMemRef) to see the | |
| // full-rank access pattern. | |
| mlir::Operation *defOp = memref.getDefiningOp(); | |
| if (!mlir::isa<fir::BaseBoxType>(memref.getType())) { | |
| auto boxAddrOp = mlir::dyn_cast_or_null<fir::BoxAddrOp>(defOp); | |
| if (!boxAddrOp) | |
| return mlir::failure(); | |
| // fir.box_addr may carry an explicit result type that does not match the | |
| // boxed value type (a merged cast). Only peel when types agree so we do | |
| // not reinterpret array_coor index semantics from the lied ref type. | |
| mlir::Type boxEleTy = | |
| fir::dyn_cast_ptrOrBoxEleTy(boxAddrOp.getVal().getType()); | |
| mlir::Type refEleTy = fir::dyn_cast_ptrOrBoxEleTy(boxAddrOp.getType()); | |
| if (boxEleTy && (!refEleTy || boxEleTy != refEleTy)) | |
| return mlir::failure(); | |
| defOp = boxAddrOp.getVal().getDefiningOp(); | |
| } | |
| mlir::Value boxedMemref, boxedShape, boxedSlice; | |
| if (auto emboxOp = mlir::dyn_cast_or_null<fir::EmboxOp>(defOp)) { | |
| boxedMemref = emboxOp.getMemref(); | |
| boxedShape = emboxOp.getShape(); | |
| boxedSlice = emboxOp.getSlice(); | |
| // If any of operands, that are not currently supported for migration | |
| // to ArrayCoorOp, is present, don't rewrite. | |
| if (!emboxOp.getTypeparams().empty() || emboxOp.getSourceBox() || | |
| emboxOp.getAccessMap()) | |
| return mlir::failure(); | |
| } else if (auto reboxOp = mlir::dyn_cast_or_null<fir::ReboxOp>(defOp)) { | |
| // Skip when folding the rebox into the array_coor would cross an ACC | |
| // data-clause or CUDA kernel boundary that the lowering pipeline | |
| // relies on (data legalization expects the rebox to remain as the | |
| // copyin var / kernel argument; folding across a CUDA kernel boundary | |
| // would leave the kernel capturing a host-side descriptor directly). | |
| if (arrayCoorFoldCrossesACCOrCUDABoundary(op, {reboxOp})) | |
| return mlir::failure(); | |
| boxedMemref = reboxOp.getBox(); | |
| boxedShape = reboxOp.getShape(); | |
| // Avoid pulling in rebox that performs reshaping. | |
| // There is no way to represent box reshaping with array_coor. | |
| if (boxedShape && !mlir::isa<fir::ShiftType>(boxedShape.getType())) | |
| return mlir::failure(); | |
| boxedSlice = reboxOp.getSlice(); | |
| } else { | |
| return mlir::failure(); | |
| } | |
| bool boxedShapeIsShift = | |
| boxedShape && mlir::isa<fir::ShiftType>(boxedShape.getType()); | |
| bool boxedShapeIsShape = | |
| boxedShape && mlir::isa<fir::ShapeType>(boxedShape.getType()); | |
| bool boxedShapeIsShapeShift = | |
| boxedShape && mlir::isa<fir::ShapeShiftType>(boxedShape.getType()); | |
| // Compute the rank of the original underlying memref and the rank of | |
| // the view the current array_coor operates on. | |
| unsigned origBoxRank; | |
| if (mlir::isa<fir::BaseBoxType>(boxedMemref.getType())) | |
| origBoxRank = fir::getBoxRank(boxedMemref.getType()); | |
| else if (auto arrTy = mlir::dyn_cast<fir::SequenceType>( | |
| fir::unwrapRefType(boxedMemref.getType()))) | |
| origBoxRank = arrTy.getDimension(); | |
| else | |
| return mlir::failure(); | |
| unsigned opRank; | |
| if (mlir::isa<fir::BaseBoxType>(memref.getType())) | |
| opRank = fir::getBoxRank(memref.getType()); | |
| else if (auto arrTy = mlir::dyn_cast<fir::SequenceType>( | |
| fir::unwrapRefType(memref.getType()))) | |
| opRank = arrTy.getDimension(); | |
| else | |
| return mlir::failure(); | |
| if (opRank > origBoxRank) | |
| return mlir::failure(); | |
| // Slices with substring are not supported by array_coor. | |
| if (boxedSlice) | |
| if (auto sliceOp = | |
| mlir::dyn_cast_or_null<fir::SliceOp>(boxedSlice.getDefiningOp())) | |
| if (!sliceOp.getSubstr().empty()) | |
| return mlir::failure(); | |
| // Rank-reducing case: the underlying memref has more dimensions than the | |
| // array_coor view because boxedSlice contains scalar triples (whose upper | |
| // bound is fir.undefined). Rebuild the array_coor on the original memref | |
| // with origBoxRank indices, filling scalar dims from the slice's lower | |
| // bounds and the remaining range dims from the existing array_coor | |
| // indices. | |
| if (opRank < origBoxRank) { | |
| auto sliceOp = mlir::dyn_cast_or_null<fir::SliceOp>( | |
| boxedSlice ? boxedSlice.getDefiningOp() : nullptr); | |
| if (!sliceOp) | |
| return mlir::failure(); | |
| // A component-path slice (substr handled above already) is not | |
| // representable in the new array_coor's indices. | |
| if (!sliceOp.getFields().empty()) | |
| return mlir::failure(); | |
| // Combining the array_coor's own slice with the boxedSlice when the | |
| // ranks differ is out of scope. | |
| if (op.getSlice()) | |
| return mlir::failure(); | |
| if (!boxedShape) { | |
| // fir.rebox with a rank-reducing slice often has no shape operand. | |
| // Synthesize fir.shape from box_dims extents. Do not synthesize | |
| // fir.shift: the descriptor produced by fir.rebox have default | |
| // lower bounds. | |
| if (!mlir::isa<fir::BaseBoxType>(boxedMemref.getType())) | |
| return mlir::failure(); | |
| boxedShape = getShapeFromBoxDims(boxedMemref, origBoxRank, op.getLoc(), | |
| op, rewriter); | |
| if (!boxedShape) | |
| return mlir::failure(); | |
| } | |
| // Avoid emitting a plain ref array_coor whose shape is a ShiftType: | |
| // the verifier rejects this (shift can only pair with fir.box memref). | |
| if (!mlir::isa<fir::BaseBoxType>(boxedMemref.getType()) && | |
| mlir::isa<fir::ShiftType>(boxedShape.getType())) | |
| return mlir::failure(); | |
| auto triples = sliceOp.getTriples(); | |
| if (triples.size() != 3 * origBoxRank) | |
| return mlir::failure(); | |
| IndicesVectorTy newIndices; | |
| newIndices.reserve(origBoxRank); | |
| llvm::SmallVector<mlir::Value> lowerBounds; | |
| if (auto shiftOp = mlir::dyn_cast_or_null<fir::ShiftOp>( | |
| boxedShape.getDefiningOp())) { | |
| for (mlir::Value lb : shiftOp.getOrigins()) | |
| lowerBounds.push_back(lb); | |
| } else if (auto shapeShiftOp = mlir::dyn_cast_or_null<fir::ShapeShiftOp>( | |
| boxedShape.getDefiningOp())) { | |
| for (mlir::Value lb : shapeShiftOp.getOrigins()) | |
| lowerBounds.push_back(lb); | |
| } | |
| if (!lowerBounds.empty() && lowerBounds.size() != origBoxRank) | |
| return mlir::failure(); | |
| mlir::Type idxTy = rewriter.getIndexType(); | |
| mlir::Value one = | |
| mlir::arith::ConstantIndexOp::create(rewriter, op.getLoc(), 1); | |
| auto nsw = mlir::arith::IntegerOverflowFlags::nsw; | |
| auto opIndices = op.getIndices(); | |
| unsigned opIdxPos = 0; | |
| for (unsigned i = 0; i < origBoxRank; ++i) { | |
| mlir::Value upper = triples[3 * i + 1]; | |
| bool isScalar = | |
| mlir::isa_and_nonnull<fir::UndefOp>(upper.getDefiningOp()); | |
| if (isScalar) { | |
| // fir.array_coor's indices are typed as AnyCoordinateType, so any | |
| // signless integer (or index) is accepted directly without an | |
| // explicit fir.convert to index. | |
| newIndices.push_back(triples[3 * i]); | |
| } else { | |
| if (opIdxPos >= opIndices.size()) | |
| return mlir::failure(); | |
| mlir::Value idx = opIndices[opIdxPos++]; | |
| if (!lowerBounds.empty()) { | |
| mlir::Value lb = lowerBounds[i]; | |
| auto constLb = fir::getIntIfConstant(lb); | |
| if (!(constLb && constLb->isOne())) { | |
| mlir::Location loc = op.getLoc(); | |
| mlir::Value extLb = | |
| fir::ConvertOp::create(rewriter, loc, idxTy, lb); | |
| mlir::Value extIdx = | |
| fir::ConvertOp::create(rewriter, loc, idxTy, idx); | |
| mlir::Value add = mlir::arith::AddIOp::create(rewriter, loc, | |
| extIdx, extLb, nsw); | |
| idx = mlir::arith::SubIOp::create(rewriter, loc, add, one, nsw); | |
| } | |
| } | |
| newIndices.push_back(idx); | |
| } | |
| } | |
| if (opIdxPos != opIndices.size()) | |
| return mlir::failure(); | |
| rewriter.replaceOpWithNewOp<fir::ArrayCoorOp>( | |
| op, op.getType(), boxedMemref, boxedShape, boxedSlice, newIndices, | |
| typeparamsForCanonicalizedMemref(boxedMemref, op.getTypeparams())); | |
| return mlir::success(); | |
| } | |
| // Rank-preserving case from here on. | |
| assert(opRank == origBoxRank && "expected rank-preserving case"); | |
| // If embox/rebox and array_coor have conflicting shapes or slices, | |
| // do nothing. | |
| if (op.getShape() && boxedShape && boxedShape != op.getShape()) | |
| return mlir::failure(); | |
| if (op.getSlice() && boxedSlice && boxedSlice != op.getSlice()) | |
| return mlir::failure(); | |
| std::optional<IndicesVectorTy> shiftedIndices; | |
| // The embox/rebox and array_coor either have compatible | |
| // shape/slice at this point or shape/slice is null | |
| // in one of them but not in the other. | |
| // The compatibility means they are equal or both null. | |
| if (!op.getShape()) { | |
| if (boxedShape) { | |
| if (op.getSlice()) { | |
| if (!boxedSlice) { | |
| if (boxedShapeIsShift) { | |
| // %0 = fir.rebox %arg(%shift) | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // Both the slice indices and %idx are 1-based, so the rebox | |
| // may be pulled in as: | |
| // %1 = fir.array_coor %arg [%slice] %idx | |
| boxedShape = nullptr; | |
| } else if (boxedShapeIsShape) { | |
| // %0 = fir.embox %arg(%shape) | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| } else if (boxedShapeIsShapeShift) { | |
| // %0 = fir.embox %arg(%shapeshift) | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // Pull in as: | |
| // %shape = fir.shape <extents from the %shapeshift> | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| boxedShape = getShapeFromShapeShift(boxedShape, rewriter); | |
| if (!boxedShape) | |
| return mlir::failure(); | |
| } else { | |
| return mlir::failure(); | |
| } | |
| } else { | |
| if (boxedShapeIsShift) { | |
| // %0 = fir.rebox %arg(%shift) [%slice] | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // This FIR may only be valid if the shape specifies | |
| // that all lower bounds are 1s and the slice's start indices | |
| // and strides are all 1s. | |
| // We could pull in the rebox as: | |
| // %1 = fir.array_coor %arg [%slice] %idx | |
| // Do not do anything for the time being. | |
| return mlir::failure(); | |
| } else if (boxedShapeIsShape) { | |
| // %0 = fir.embox %arg(%shape) [%slice] | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // This FIR may only be valid if the slice's start indices | |
| // and strides are all 1s. | |
| // We could pull in the embox as: | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| return mlir::failure(); | |
| } else if (boxedShapeIsShapeShift) { | |
| // %0 = fir.embox %arg(%shapeshift) [%slice] | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // This FIR may only be valid if the shape specifies | |
| // that all lower bounds are 1s and the slice's start indices | |
| // and strides are all 1s. | |
| // We could pull in the embox as: | |
| // %shape = fir.shape <extents from the %shapeshift> | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| return mlir::failure(); | |
| } else { | |
| return mlir::failure(); | |
| } | |
| } | |
| } else { // !op.getSlice() | |
| if (!boxedSlice) { | |
| if (boxedShapeIsShift) { | |
| // %0 = fir.rebox %arg(%shift) | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg %idx | |
| boxedShape = nullptr; | |
| } else if (boxedShapeIsShape) { | |
| // %0 = fir.embox %arg(%shape) | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) %idx | |
| } else if (boxedShapeIsShapeShift) { | |
| // %0 = fir.embox %arg(%shapeshift) | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %shape = fir.shape <extents from the %shapeshift> | |
| // %1 = fir.array_coor %arg(%shape) %idx | |
| boxedShape = getShapeFromShapeShift(boxedShape, rewriter); | |
| if (!boxedShape) | |
| return mlir::failure(); | |
| } else { | |
| return mlir::failure(); | |
| } | |
| } else { | |
| if (boxedShapeIsShift) { | |
| // %0 = fir.embox %arg(%shift) [%slice] | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %tmp = arith.addi %idx, %shift.origin | |
| // %idx_shifted = arith.subi %tmp, 1 | |
| // %1 = fir.array_coor %arg(%shift) %[slice] %idx_shifted | |
| shiftedIndices = | |
| getShiftedIndices(boxedShape, op.getIndices(), rewriter); | |
| if (!shiftedIndices) | |
| return mlir::failure(); | |
| } else if (boxedShapeIsShape) { | |
| // %0 = fir.embox %arg(%shape) [%slice] | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) %[slice] %idx | |
| } else if (boxedShapeIsShapeShift) { | |
| // %0 = fir.embox %arg(%shapeshift) [%slice] | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %tmp = arith.addi %idx, %shapeshift.lb | |
| // %idx_shifted = arith.subi %tmp, 1 | |
| // %1 = fir.array_coor %arg(%shapeshift) %[slice] %idx_shifted | |
| shiftedIndices = | |
| getShiftedIndices(boxedShape, op.getIndices(), rewriter); | |
| if (!shiftedIndices) | |
| return mlir::failure(); | |
| } else { | |
| return mlir::failure(); | |
| } | |
| } | |
| } | |
| } else { // !boxedShape | |
| if (op.getSlice()) { | |
| if (!boxedSlice) { | |
| // %0 = fir.rebox %arg | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg [%slice] %idx | |
| } else { | |
| // %0 = fir.rebox %arg [%slice] | |
| // %1 = fir.array_coor %0 [%slice] %idx | |
| // This is a valid FIR iff the slice's lower bounds | |
| // and strides are all 1s. | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg [%slice] %idx | |
| } | |
| } else { // !op.getSlice() | |
| if (!boxedSlice) { | |
| // %0 = fir.rebox %arg | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg %idx | |
| } else { | |
| // %0 = fir.rebox %arg [%slice] | |
| // %1 = fir.array_coor %0 %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg [%slice] %idx | |
| } | |
| } | |
| } | |
| } else { // op.getShape() | |
| if (boxedShape) { | |
| // Check if pulling in non-default shape is correct. | |
| if (op.getSlice()) { | |
| if (!boxedSlice) { | |
| // %0 = fir.embox %arg(%shape) | |
| // %1 = fir.array_coor %0(%shape) [%slice] %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| } else { | |
| // %0 = fir.embox %arg(%shape) [%slice] | |
| // %1 = fir.array_coor %0(%shape) [%slice] %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| } | |
| } else { // !op.getSlice() | |
| if (!boxedSlice) { | |
| // %0 = fir.embox %arg(%shape) | |
| // %1 = fir.array_coor %0(%shape) %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) %idx | |
| } else { | |
| // %0 = fir.embox %arg(%shape) [%slice] | |
| // %1 = fir.array_coor %0(%shape) %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| } | |
| } | |
| } else { // !boxedShape | |
| if (op.getSlice()) { | |
| if (!boxedSlice) { | |
| // %0 = fir.rebox %arg | |
| // %1 = fir.array_coor %0(%shape) [%slice] %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) [%slice] %idx | |
| } else { | |
| // %0 = fir.rebox %arg [%slice] | |
| // %1 = fir.array_coor %0(%shape) [%slice] %idx | |
| return mlir::failure(); | |
| } | |
| } else { // !op.getSlice() | |
| if (!boxedSlice) { | |
| // %0 = fir.rebox %arg | |
| // %1 = fir.array_coor %0(%shape) %idx | |
| // Pull in as: | |
| // %1 = fir.array_coor %arg(%shape) %idx | |
| } else { | |
| // %0 = fir.rebox %arg [%slice] | |
| // %1 = fir.array_coor %0(%shape) %idx | |
| // Cannot pull in without adjusting the slice indices. | |
| return mlir::failure(); | |
| } | |
| } | |
| } | |
| } | |
| // TODO: temporarily avoid producing array_coor with the shape shift | |
| // and plain array reference (it seems to be a limitation of | |
| // ArrayCoorOp verifier). | |
| if (!mlir::isa<fir::BaseBoxType>(boxedMemref.getType())) { | |
| if (boxedShape) { | |
| if (mlir::isa<fir::ShiftType>(boxedShape.getType())) | |
| return mlir::failure(); | |
| } else if (op.getShape() && | |
| mlir::isa<fir::ShiftType>(op.getShape().getType())) { | |
| return mlir::failure(); | |
| } | |
| } | |
| rewriter.modifyOpInPlace(op, [&]() { | |
| op.getMemrefMutable().assign(boxedMemref); | |
| if (boxedShape) | |
| op.getShapeMutable().assign(boxedShape); | |
| if (boxedSlice) | |
| op.getSliceMutable().assign(boxedSlice); | |
| if (shiftedIndices) | |
| op.getIndicesMutable().assign(*shiftedIndices); | |
| op.getTypeparamsMutable().assign( | |
| typeparamsForCanonicalizedMemref(boxedMemref, op.getTypeparams())); | |
| }); | |
| return mlir::success(); | |
| } | |
| private: | |
| using IndicesVectorTy = std::vector<mlir::Value>; | |
| // array_coor on a fir.box carries length/type info in the descriptor; | |
| // explicit typeparams are only valid for plain ref memrefs. | |
| static mlir::ValueRange | |
| typeparamsForCanonicalizedMemref(mlir::Value memref, | |
| mlir::ValueRange typeparams) { | |
| if (mlir::isa<fir::BaseBoxType>(memref.getType())) | |
| return mlir::ValueRange{}; | |
| return typeparams; | |
| } | |
| // Build fir.shape<rank> from fir.box_dims extents of box. | |
| static mlir::Value getShapeFromBoxDims(mlir::Value box, unsigned rank, | |
| mlir::Location loc, | |
| mlir::Operation *insertBefore, | |
| mlir::PatternRewriter &rewriter) { | |
| auto boxType = mlir::dyn_cast<fir::BaseBoxType>(box.getType()); | |
| if (!boxType || boxType.isAssumedRank() || fir::getBoxRank(boxType) != rank) | |
| return nullptr; | |
| mlir::OpBuilder::InsertionGuard guard(rewriter); | |
| rewriter.setInsertionPoint(insertBefore); | |
| llvm::SmallVector<mlir::Value> extents; | |
| fir::genDimInfoFromBox(rewriter, loc, box, /*lbounds=*/nullptr, &extents, | |
| /*strides=*/nullptr); | |
| return fir::ShapeOp::create(rewriter, loc, extents); | |
| } | |
| // If v is a shape_shift operation: | |
| // fir.shape_shift %l1, %e1, %l2, %e2, ... | |
| // create: | |
| // fir.shape %e1, %e2, ... | |
| static mlir::Value getShapeFromShapeShift(mlir::Value v, | |
| mlir::PatternRewriter &rewriter) { | |
| auto shapeShiftOp = | |
| mlir::dyn_cast_or_null<fir::ShapeShiftOp>(v.getDefiningOp()); | |
| if (!shapeShiftOp) | |
| return nullptr; | |
| mlir::OpBuilder::InsertionGuard guard(rewriter); | |
| rewriter.setInsertionPoint(shapeShiftOp); | |
| return fir::ShapeOp::create(rewriter, shapeShiftOp.getLoc(), | |
| shapeShiftOp.getExtents()); | |
| } | |
| static std::optional<IndicesVectorTy> | |
| getShiftedIndices(mlir::Value v, mlir::ValueRange indices, | |
| mlir::PatternRewriter &rewriter) { | |
| auto insertAdjustments = [&](mlir::Operation *op, mlir::ValueRange lbs) { | |
| // Compute the shifted indices using the extended type. | |
| // Note that this can probably result in less efficient | |
| // MLIR and further LLVM IR due to the extra conversions. | |
| mlir::OpBuilder::InsertPoint savedIP = rewriter.saveInsertionPoint(); | |
| rewriter.setInsertionPoint(op); | |
| mlir::Location loc = op->getLoc(); | |
| mlir::Type idxTy = rewriter.getIndexType(); | |
| mlir::Value one = mlir::arith::ConstantOp::create( | |
| rewriter, loc, idxTy, rewriter.getIndexAttr(1)); | |
| rewriter.restoreInsertionPoint(savedIP); | |
| auto nsw = mlir::arith::IntegerOverflowFlags::nsw; | |
| IndicesVectorTy shiftedIndices; | |
| for (auto [lb, idx] : llvm::zip(lbs, indices)) { | |
| mlir::Value extLb = fir::ConvertOp::create(rewriter, loc, idxTy, lb); | |
| mlir::Value extIdx = fir::ConvertOp::create(rewriter, loc, idxTy, idx); | |
| mlir::Value add = | |
| mlir::arith::AddIOp::create(rewriter, loc, extIdx, extLb, nsw); | |
| mlir::Value sub = | |
| mlir::arith::SubIOp::create(rewriter, loc, add, one, nsw); | |
| shiftedIndices.push_back(sub); | |
| } | |
| return shiftedIndices; | |
| }; | |
| if (auto shiftOp = | |
| mlir::dyn_cast_or_null<fir::ShiftOp>(v.getDefiningOp())) { | |
| return insertAdjustments(shiftOp.getOperation(), shiftOp.getOrigins()); | |
| } else if (auto shapeShiftOp = mlir::dyn_cast_or_null<fir::ShapeShiftOp>( | |
| v.getDefiningOp())) { | |
| return insertAdjustments(shapeShiftOp.getOperation(), | |
| shapeShiftOp.getOrigins()); | |
| } | |
| return std::nullopt; | |
| } | |
| }; | |
| // Pull a producer fir.array_coor (via a fir.convert that reinterprets a | |
| // scalar element address as an M-D array ref) into the consumer fir.array_coor. | |
| // Mirrors the slice-designator lowering pattern emitted by ConvertHLFIRtoFIR | |
| // for Fortran array sections like `dx(1:3)` or `dx(1:3, 4)`. | |
| // | |
| // Pattern: | |
| // %outer = fir.array_coor %base(%base_shape) %ox_0, ..., %ox_{N-1} | |
| // : (BaseTy, ShapeTy, index, ...) -> !fir.ref<EleTy> | |
| // %view = fir.convert %outer | |
| // : !fir.ref<EleTy> -> !fir.ref<!fir.array<...x EleTy>> (M-D) | |
| // %inner = fir.array_coor %view(%inner_shape) %ix_0, ..., %ix_{M-1} | |
| // : (!fir.ref<!fir.array<...>>, !fir.shape<M>, index, ...) | |
| // -> !fir.ref<EleTy> | |
| // | |
| // In Fortran column-major storage, the M leading dimensions of the original | |
| // array are exactly the dimensions of the contiguous sub-array; the trailing | |
| // (N-M) outer indices stay fixed. So %inner is rewritten as: | |
| // %combined_i = arith.addi %ox_i, arith.subi(%ix_i, 1) for i in 0..M-1 | |
| // %inner = fir.array_coor %base(%base_shape) | |
| // %combined_0, ..., %combined_{M-1}, | |
| // %ox_M, ..., %ox_{N-1} | |
| // : (BaseTy, ShapeTy, index, ...) -> !fir.ref<EleTy> | |
| // | |
| // Restrictions (kept minimal): | |
| // - No slice on outer or inner. | |
| // - Inner shape must be a plain fir.shape (default lb=1); shift/shape_shift | |
| // on the inner are not handled. | |
| // - Outer's result type must be a scalar ref (not an array ref). | |
| // - Convert source type must equal outer's result type. | |
| // - Convert result type's element type must equal outer's pointee. | |
| // - Inner rank M <= outer rank N. | |
| // - Neither op carries typeparams. | |
| // - Mirrors SimplifyArrayCoorOp's ACC/CUF guards. | |
| struct MergeArrayCoorOnConvert | |
| : public mlir::OpRewritePattern<fir::ArrayCoorOp> { | |
| using mlir::OpRewritePattern<fir::ArrayCoorOp>::OpRewritePattern; | |
| llvm::LogicalResult | |
| matchAndRewrite(fir::ArrayCoorOp inner, | |
| mlir::PatternRewriter &rewriter) const override { | |
| if (inner.getSlice() || !inner.getTypeparams().empty()) | |
| return mlir::failure(); | |
| auto innerShapeOp = mlir::dyn_cast_or_null<fir::ShapeOp>( | |
| inner.getShape() ? inner.getShape().getDefiningOp() : nullptr); | |
| if (!innerShapeOp) | |
| return mlir::failure(); | |
| auto convertOp = inner.getMemref().getDefiningOp<fir::ConvertOp>(); | |
| if (!convertOp) | |
| return mlir::failure(); | |
| auto outer = convertOp.getValue().getDefiningOp<fir::ArrayCoorOp>(); | |
| if (!outer) | |
| return mlir::failure(); | |
| if (outer.getSlice() || !outer.getTypeparams().empty()) | |
| return mlir::failure(); | |
| mlir::Type outerResTy = outer.getType(); | |
| mlir::Type outerEleTy = fir::dyn_cast_ptrEleTy(outerResTy); | |
| if (!outerEleTy || mlir::isa<fir::SequenceType>(outerEleTy)) | |
| return mlir::failure(); | |
| if (convertOp.getValue().getType() != outerResTy) | |
| return mlir::failure(); | |
| mlir::Type convResTy = convertOp.getType(); | |
| mlir::Type convResEleTy = fir::dyn_cast_ptrEleTy(convResTy); | |
| auto convResSeqTy = mlir::dyn_cast_or_null<fir::SequenceType>(convResEleTy); | |
| if (!convResSeqTy || convResSeqTy.getEleTy() != outerEleTy) | |
| return mlir::failure(); | |
| unsigned innerRank = inner.getIndices().size(); | |
| unsigned outerRank = outer.getIndices().size(); | |
| if (innerRank == 0 || innerRank > outerRank) | |
| return mlir::failure(); | |
| if (innerShapeOp.getExtents().size() != innerRank) | |
| return mlir::failure(); | |
| // Skip when folding the convert+outer chain into the inner array_coor | |
| // would cross an ACC data-clause or CUDA kernel boundary. | |
| if (arrayCoorFoldCrossesACCOrCUDABoundary(inner, {convertOp, outer})) | |
| return mlir::failure(); | |
| mlir::Location loc = inner.getLoc(); | |
| mlir::Type idxTy = rewriter.getIndexType(); | |
| mlir::Value one = mlir::arith::ConstantIndexOp::create(rewriter, loc, 1); | |
| auto nsw = mlir::arith::IntegerOverflowFlags::nsw; | |
| auto toIndex = [&](mlir::Value v) -> mlir::Value { | |
| if (v.getType() == idxTy) | |
| return v; | |
| return fir::ConvertOp::create(rewriter, loc, idxTy, v); | |
| }; | |
| llvm::SmallVector<mlir::Value> combinedIndices; | |
| combinedIndices.reserve(outerRank); | |
| for (unsigned i = 0; i < innerRank; ++i) { | |
| mlir::Value outerIdx = toIndex(outer.getIndices()[i]); | |
| mlir::Value innerIdx = toIndex(inner.getIndices()[i]); | |
| mlir::Value innerMinusOne = | |
| mlir::arith::SubIOp::create(rewriter, loc, innerIdx, one, nsw); | |
| mlir::Value combined = mlir::arith::AddIOp::create( | |
| rewriter, loc, outerIdx, innerMinusOne, nsw); | |
| combinedIndices.push_back(combined); | |
| } | |
| for (unsigned i = innerRank; i < outerRank; ++i) | |
| combinedIndices.push_back(outer.getIndices()[i]); | |
| rewriter.replaceOpWithNewOp<fir::ArrayCoorOp>( | |
| inner, inner.getType(), outer.getMemref(), outer.getShape(), | |
| /*slice=*/mlir::Value{}, combinedIndices, outer.getTypeparams()); | |
| return mlir::success(); | |
| } | |
| }; | |
| void fir::ArrayCoorOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &patterns, mlir::MLIRContext *context) { | |
| // TODO: !fir.shape<1> operand may be removed from array_coor always. | |
| patterns.add<SimplifyArrayCoorOp, MergeArrayCoorOnConvert>(context); | |
| } | |
| std::optional<std::int64_t> fir::ArrayCoorOp::getViewOffset(mlir::OpResult) { | |
| // TODO: we can try to compute the constant offset. | |
| return std::nullopt; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxAddrOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::BoxAddrOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Value val) { | |
| mlir::Type type = | |
| llvm::TypeSwitch<mlir::Type, mlir::Type>(val.getType()) | |
| .Case([&](fir::BaseBoxType ty) -> mlir::Type { | |
| mlir::Type eleTy = ty.getEleTy(); | |
| if (fir::isa_ref_type(eleTy)) | |
| return eleTy; | |
| return fir::ReferenceType::get(eleTy); | |
| }) | |
| .Case([&](fir::BoxCharType ty) -> mlir::Type { | |
| return fir::ReferenceType::get(ty.getEleTy()); | |
| }) | |
| .Case([&](fir::BoxProcType ty) { return ty.getEleTy(); }) | |
| .Default([&](const auto &) { return mlir::Type{}; }); | |
| assert(type && "bad val type"); | |
| build(builder, result, type, val); | |
| } | |
| mlir::OpFoldResult fir::BoxAddrOp::fold(FoldAdaptor adaptor) { | |
| if (auto *v = getVal().getDefiningOp()) { | |
| if (auto box = mlir::dyn_cast<fir::EmboxOp>(v)) { | |
| // Fold only if not sliced | |
| if (!box.getSlice() && box.getMemref().getType() == getType()) { | |
| propagateAttributes(getOperation(), box.getMemref().getDefiningOp()); | |
| return box.getMemref(); | |
| } | |
| } | |
| // fir.create_box always describes the whole array (no slice), so the | |
| // base address is exactly its memref operand. The verifier guarantees the | |
| // result box preserves the memref storage kind, so the types match. | |
| if (auto box = mlir::dyn_cast<fir::CreateBoxOp>(v)) { | |
| if (box.getMemref().getType() == getType()) { | |
| propagateAttributes(getOperation(), box.getMemref().getDefiningOp()); | |
| return box.getMemref(); | |
| } | |
| } | |
| if (auto box = mlir::dyn_cast<fir::EmboxCharOp>(v)) | |
| if (box.getMemref().getType() == getType()) | |
| return box.getMemref(); | |
| } | |
| return {}; | |
| } | |
| std::optional<std::int64_t> fir::BoxAddrOp::getViewOffset(mlir::OpResult) { | |
| // fir.box_addr just returns the base address stored inside a box, | |
| // so the direct accesses through the base address and through the box | |
| // are not offsetted. | |
| return 0; | |
| } | |
| mlir::Speculation::Speculatability fir::BoxAddrOp::getSpeculatability() { | |
| // Do not speculate fir.box_addr with BoxProcType and BoxCharType | |
| // inputs. | |
| if (!mlir::isa<fir::BaseBoxType>(getVal().getType())) | |
| return mlir::Speculation::NotSpeculatable; | |
| return mayBeAbsentBox(getVal()) ? mlir::Speculation::NotSpeculatable | |
| : mlir::Speculation::Speculatable; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxCharLenOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::OpFoldResult fir::BoxCharLenOp::fold(FoldAdaptor adaptor) { | |
| if (auto v = getVal().getDefiningOp()) { | |
| if (auto box = mlir::dyn_cast<fir::EmboxCharOp>(v)) | |
| return box.getLen(); | |
| } | |
| return {}; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxEleSizeOp | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// Canonicalize fir.box_elesize when the element type is statically known. | |
| struct FoldBoxEleSize : public mlir::OpRewritePattern<fir::BoxEleSizeOp> { | |
| using mlir::OpRewritePattern<fir::BoxEleSizeOp>::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::BoxEleSizeOp op, | |
| mlir::PatternRewriter &rewriter) const override { | |
| auto boxTy = mlir::cast<fir::BaseBoxType>(op.getVal().getType()); | |
| // ClassType is polymorphic — the actual runtime type may differ, size | |
| // not statically known. | |
| if (mlir::isa<fir::ClassType>(boxTy)) | |
| return mlir::failure(); | |
| // unwrapInnerType peels through any ptr/heap/array wrapper inside the box | |
| // to reach the scalar element type. | |
| mlir::Type eleTy = boxTy.unwrapInnerType(); | |
| // NoneType is the element type for TYPE(*) assumed-type boxes — no static | |
| // element type. | |
| if (mlir::isa<mlir::NoneType>(eleTy)) | |
| return mlir::failure(); | |
| // Bail out for types whose size is only known at runtime as a string whose | |
| // length is unknown or a record type with fields with a dynamic size | |
| if (fir::hasDynamicSize(eleTy)) | |
| return mlir::failure(); | |
| mlir::DataLayout dl = mlir::DataLayout::closest(op); | |
| fir::KindMapping kindMap = fir::getKindMapping(op.getOperation()); | |
| auto sizeAndAlign = | |
| fir::getTypeSizeAndAlignment(op.getLoc(), eleTy, dl, kindMap); | |
| if (!sizeAndAlign) | |
| return mlir::failure(); | |
| // The descriptor stores the byte stride between elements, which is the | |
| // allocation size including tail padding. getTypeSizeAndAlignment already | |
| // rounds up to alignment for RecordType, so llvm::alignTo here is a no-op | |
| // for struct types; it is kept for scalar types (e.g. x86_fp80) where the | |
| // rounding is still needed, matching fir::computeElementDistance. | |
| auto [size, alignment] = *sizeAndAlign; | |
| std::int64_t distance = llvm::alignTo(size, alignment); | |
| mlir::Type resultTy = op.getType(); | |
| rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>( | |
| op, resultTy, rewriter.getIntegerAttr(resultTy, distance)); | |
| return mlir::success(); | |
| } | |
| }; | |
| } // namespace | |
| void fir::BoxEleSizeOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &results, mlir::MLIRContext *context) { | |
| results.insert<FoldBoxEleSize>(context); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxDimsOp | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// fir.box_dims of a descriptor built by fir.create_box with a statically known | |
| /// dimension forwards the lower bound, extent, and byte stride operands that | |
| /// built that dimension. These are plain SSA operands of the create_box, so | |
| /// replacing the box_dims results with them is a value forwarding; the | |
| /// create_box dominates the box_dims, so the operands are already available. | |
| struct SimplifyBoxDimsFromCreateBox | |
| : public mlir::OpRewritePattern<fir::BoxDimsOp> { | |
| using mlir::OpRewritePattern<fir::BoxDimsOp>::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::BoxDimsOp op, | |
| mlir::PatternRewriter &rewriter) const override { | |
| auto createBox = op.getVal().getDefiningOp<fir::CreateBoxOp>(); | |
| if (!createBox) | |
| return mlir::failure(); | |
| std::optional<llvm::APInt> constantDim = fir::getIntIfConstant(op.getDim()); | |
| std::optional<std::int64_t> dim = | |
| constantDim ? constantDim->trySExtValue() : std::nullopt; | |
| if (!dim || *dim < 0 || *dim >= createBox.getRank()) | |
| return mlir::failure(); | |
| // The create_box operands are AnyIntegerType and may differ from the | |
| // index-typed box_dims results, so convert them. | |
| mlir::Location loc = op.getLoc(); | |
| rewriter.replaceOp(op, | |
| {rewriter.createOrFold<fir::ConvertOp>( | |
| loc, op.getType(0), createBox.getLbounds()[*dim]), | |
| rewriter.createOrFold<fir::ConvertOp>( | |
| loc, op.getType(1), createBox.getExtents()[*dim]), | |
| rewriter.createOrFold<fir::ConvertOp>( | |
| loc, op.getType(2), createBox.getStrides()[*dim])}); | |
| return mlir::success(); | |
| } | |
| }; | |
| } // namespace | |
| void fir::BoxDimsOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &results, mlir::MLIRContext *context) { | |
| results.insert<SimplifyBoxDimsFromCreateBox>(context); | |
| } | |
| /// Get the result types packed in a tuple tuple | |
| mlir::Type fir::BoxDimsOp::getTupleType() { | |
| // note: triple, but 4 is nearest power of 2 | |
| llvm::SmallVector<mlir::Type> triple{ | |
| getResult(0).getType(), getResult(1).getType(), getResult(2).getType()}; | |
| return mlir::TupleType::get(getContext(), triple); | |
| } | |
| mlir::Speculation::Speculatability fir::BoxDimsOp::getSpeculatability() { | |
| return mayBeAbsentBox(getVal()) ? mlir::Speculation::NotSpeculatable | |
| : mlir::Speculation::Speculatable; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxRankOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::BoxRankOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| mlir::OpOperand &inputBox = getBoxMutable(); | |
| if (fir::isBoxAddress(inputBox.get().getType())) | |
| effects.emplace_back(mlir::MemoryEffects::Read::get(), &inputBox, | |
| mlir::SideEffects::DefaultResource::get()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CallOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::FunctionType fir::CallOp::getFunctionType() { | |
| return mlir::FunctionType::get(getContext(), getOperandTypes(), | |
| getResultTypes()); | |
| } | |
| void fir::CallOp::print(mlir::OpAsmPrinter &p) { | |
| bool isDirect = getCallee().has_value(); | |
| p << ' '; | |
| if (isDirect) | |
| p << *getCallee(); | |
| else | |
| p << getOperand(0); | |
| p << '(' << (*this)->getOperands().drop_front(isDirect ? 0 : 1) << ')'; | |
| // Print `proc_attrs<...>`, if present. | |
| fir::FortranProcedureFlagsEnumAttr procAttrs = getProcedureAttrsAttr(); | |
| if (procAttrs && | |
| procAttrs.getValue() != fir::FortranProcedureFlagsEnum::none) { | |
| p << ' ' << fir::FortranProcedureFlagsEnumAttr::getMnemonic(); | |
| p.printStrippedAttrOrType(procAttrs); | |
| } | |
| // Print 'fastmath<...>' (if it has non-default value) before | |
| // any other attributes. | |
| mlir::arith::FastMathFlagsAttr fmfAttr = getFastmathAttr(); | |
| if (fmfAttr.getValue() != mlir::arith::FastMathFlags::none) { | |
| p << ' ' << mlir::arith::FastMathFlagsAttr::getMnemonic(); | |
| p.printStrippedAttrOrType(fmfAttr); | |
| } | |
| fir::CallOp::printProperties(getContext(), p, getProperties(), | |
| {fir::CallOp::getCalleeAttrNameStr(), | |
| getFastmathAttrName(), | |
| getProcedureAttrsAttrName(), | |
| getArgAttrsAttrName(), getResAttrsAttrName()}); | |
| p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary()); | |
| p << " : "; | |
| mlir::call_interface_impl::printFunctionSignature( | |
| p, getArgs().drop_front(isDirect ? 0 : 1).getTypes(), getArgAttrsAttr(), | |
| /*isVariadic=*/false, getResultTypes(), getResAttrsAttr()); | |
| } | |
| mlir::ParseResult fir::CallOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> operands; | |
| if (parser.parseOperandList(operands)) | |
| return mlir::failure(); | |
| mlir::NamedAttrList attrs; | |
| mlir::SymbolRefAttr funcAttr; | |
| bool isDirect = operands.empty(); | |
| if (isDirect) | |
| if (parser.parseAttribute(funcAttr, fir::CallOp::getCalleeAttrNameStr(), | |
| attrs)) | |
| return mlir::failure(); | |
| if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::Paren)) | |
| return mlir::failure(); | |
| // Parse `proc_attrs<...>`, if present. | |
| fir::FortranProcedureFlagsEnumAttr procAttr; | |
| if (mlir::succeeded(parser.parseOptionalKeyword( | |
| fir::FortranProcedureFlagsEnumAttr::getMnemonic()))) | |
| if (parser.parseCustomAttributeWithFallback( | |
| procAttr, mlir::Type{}, getProcedureAttrsAttrName(result.name), | |
| attrs)) | |
| return mlir::failure(); | |
| // Parse 'fastmath<...>', if present. | |
| mlir::arith::FastMathFlagsAttr fmfAttr; | |
| llvm::StringRef fmfAttrName = getFastmathAttrName(result.name); | |
| if (mlir::succeeded(parser.parseOptionalKeyword(fmfAttrName))) | |
| if (parser.parseCustomAttributeWithFallback(fmfAttr, mlir::Type{}, | |
| fmfAttrName, attrs)) | |
| return mlir::failure(); | |
| mlir::Attribute parsedProperties; | |
| if (parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(attrs) || parser.parseColon()) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Type> argTypes; | |
| llvm::SmallVector<mlir::Type> resTypes; | |
| llvm::SmallVector<mlir::DictionaryAttr> argAttrs; | |
| llvm::SmallVector<mlir::DictionaryAttr> resultAttrs; | |
| if (mlir::call_interface_impl::parseFunctionSignature( | |
| parser, argTypes, argAttrs, resTypes, resultAttrs)) | |
| return parser.emitError(parser.getNameLoc(), "expected function type"); | |
| mlir::FunctionType funcType = | |
| mlir::FunctionType::get(parser.getContext(), argTypes, resTypes); | |
| if (isDirect) { | |
| if (parser.resolveOperands(operands, funcType.getInputs(), | |
| parser.getNameLoc(), result.operands)) | |
| return mlir::failure(); | |
| } else { | |
| auto funcArgs = | |
| llvm::ArrayRef<mlir::OpAsmParser::UnresolvedOperand>(operands) | |
| .drop_front(); | |
| if (parser.resolveOperand(operands[0], funcType, result.operands) || | |
| parser.resolveOperands(funcArgs, funcType.getInputs(), | |
| parser.getNameLoc(), result.operands)) | |
| return mlir::failure(); | |
| } | |
| result.attributes = attrs; | |
| mlir::call_interface_impl::addArgAndResultAttrs( | |
| parser.getBuilder(), result, argAttrs, resultAttrs, | |
| getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name)); | |
| result.addTypes(funcType.getResults()); | |
| return setParsedProperties<fir::CallOp>(parser, result, parsedProperties); | |
| } | |
| void fir::CallOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::func::FuncOp callee, mlir::ValueRange operands) { | |
| result.addOperands(operands); | |
| result.addAttribute(getCalleeAttrNameStr(), mlir::SymbolRefAttr::get(callee)); | |
| result.addTypes(callee.getFunctionType().getResults()); | |
| } | |
| void fir::CallOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::SymbolRefAttr callee, | |
| llvm::ArrayRef<mlir::Type> results, | |
| mlir::ValueRange operands) { | |
| result.addOperands(operands); | |
| if (callee) | |
| result.addAttribute(getCalleeAttrNameStr(), callee); | |
| result.addTypes(results); | |
| } | |
| void fir::CallOp::setCalleeFromCallable(mlir::CallInterfaceCallable callee) { | |
| if (auto symbolRef = llvm::dyn_cast<mlir::SymbolRefAttr>(callee)) { | |
| // Handling a direct call. | |
| bool wasIndirect = llvm::isa<mlir::Value>(getCallableForCallee()); | |
| (*this)->setAttr(getCalleeAttrName(), symbolRef); | |
| // If it was indirect before, the operand list and associated attributes | |
| // needs to be fixed up. | |
| if (wasIndirect) { | |
| assert(getNumOperands() > 0 && "indirect call must have callee operand"); | |
| (*this)->eraseOperand(0); | |
| // Fix arg_attrs to remove the first (callee) operand if needed. | |
| if (auto argAttrs = getArgAttrsAttr()) { | |
| // Since we already removed the first operand, check that number | |
| // of attributes is one more than number of operands. | |
| assert(argAttrs.size() == getNumOperands() + 1 && | |
| "arg_attrs must be one-per-operand"); | |
| llvm::SmallVector<mlir::Attribute> newAttrs(argAttrs.begin() + 1, | |
| argAttrs.end()); | |
| if (newAttrs.empty()) | |
| (*this)->removeAttr(getArgAttrsAttrName()); | |
| else | |
| (*this)->setAttr(getArgAttrsAttrName(), | |
| mlir::ArrayAttr::get(getContext(), newAttrs)); | |
| } | |
| } | |
| return; | |
| } | |
| // The provided callee makes this an indirect call now. | |
| bool wasIndirect = llvm::isa<mlir::Value>(getCallableForCallee()); | |
| (*this)->removeAttr(getCalleeAttrNameStr()); | |
| mlir::Value calleeVal = llvm::cast<mlir::Value>(callee); | |
| if (wasIndirect) { | |
| setOperand(0, calleeVal); | |
| } else { | |
| (*this)->insertOperands(0, calleeVal); | |
| // Make arg_attrs consistent in size with operands by adding an empty dict | |
| // for the callee. | |
| if (auto argAttrs = getArgAttrsAttr()) { | |
| assert(argAttrs.size() == getNumOperands() - 1 && | |
| "arg_attrs must be one-per-operand"); | |
| llvm::SmallVector<mlir::Attribute> newAttrs; | |
| newAttrs.reserve(1 + argAttrs.size()); | |
| newAttrs.push_back(mlir::DictionaryAttr::get(getContext(), {})); | |
| newAttrs.append(argAttrs.begin(), argAttrs.end()); | |
| (*this)->setAttr(getArgAttrsAttrName(), | |
| mlir::ArrayAttr::get(getContext(), newAttrs)); | |
| } | |
| } | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CharConvertOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::CharConvertOp::verify() { | |
| auto unwrap = [&](mlir::Type t) { | |
| t = fir::unwrapSequenceType(fir::dyn_cast_ptrEleTy(t)); | |
| return mlir::dyn_cast<fir::CharacterType>(t); | |
| }; | |
| auto inTy = unwrap(getFrom().getType()); | |
| auto outTy = unwrap(getTo().getType()); | |
| if (!(inTy && outTy)) | |
| return emitOpError("not a reference to a character"); | |
| if (inTy.getFKind() == outTy.getFKind()) | |
| return emitOpError("buffers must have different KIND values"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CmpOp | |
| //===----------------------------------------------------------------------===// | |
| template <typename OPTY> | |
| static void printCmpOp(mlir::OpAsmPrinter &p, OPTY op) { | |
| p << ' '; | |
| auto predSym = mlir::arith::symbolizeCmpFPredicate( | |
| op->template getAttrOfType<mlir::IntegerAttr>( | |
| OPTY::getPredicateAttrName()) | |
| .getInt()); | |
| assert(predSym.has_value() && "invalid symbol value for predicate"); | |
| p << '"' << mlir::arith::stringifyCmpFPredicate(predSym.value()) << '"' | |
| << ", "; | |
| p.printOperand(op.getLhs()); | |
| p << ", "; | |
| p.printOperand(op.getRhs()); | |
| OPTY::printProperties(op.getContext(), p, op.getProperties()); | |
| p.printOptionalAttrDict(op->getDiscardableAttrDictionary(), | |
| /*elidedAttrs=*/{OPTY::getPredicateAttrName()}); | |
| p << " : " << op.getLhs().getType(); | |
| } | |
| template <typename OPTY> | |
| static mlir::ParseResult parseCmpOp(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> ops; | |
| mlir::NamedAttrList attrs; | |
| mlir::Attribute predicateNameAttr; | |
| mlir::Attribute parsedProperties; | |
| mlir::Type type; | |
| if (parser.parseAttribute(predicateNameAttr, OPTY::getPredicateAttrName(), | |
| attrs) || | |
| parser.parseComma() || parser.parseOperandList(ops, 2) || | |
| parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(attrs) || parser.parseColonType(type) || | |
| parser.resolveOperands(ops, type, result.operands)) | |
| return mlir::failure(); | |
| if (!mlir::isa<mlir::StringAttr>(predicateNameAttr)) | |
| return parser.emitError(parser.getNameLoc(), | |
| "expected string comparison predicate attribute"); | |
| // Rewrite string attribute to an enum value. | |
| llvm::StringRef predicateName = | |
| mlir::cast<mlir::StringAttr>(predicateNameAttr).getValue(); | |
| auto predicate = fir::CmpcOp::getPredicateByName(predicateName); | |
| auto builder = parser.getBuilder(); | |
| mlir::Type i1Type = builder.getI1Type(); | |
| attrs.set(OPTY::getPredicateAttrName(), | |
| builder.getI64IntegerAttr(static_cast<std::int64_t>(predicate))); | |
| result.attributes = attrs; | |
| result.addTypes({i1Type}); | |
| return setParsedProperties<OPTY>(parser, result, parsedProperties); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BitcastOp | |
| //===----------------------------------------------------------------------===// | |
| static bool isBitcastCompatibleType(mlir::Type ty) { | |
| return mlir::isa<mlir::IntegerType, mlir::FloatType, fir::LogicalType>(ty) || | |
| (mlir::isa<fir::CharacterType>(ty) && | |
| mlir::cast<fir::CharacterType>(ty).getLen() == | |
| fir::CharacterType::singleton()); | |
| } | |
| static std::optional<unsigned> getBitcastBitSize(mlir::Type ty) { | |
| if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty)) | |
| return intTy.getWidth(); | |
| if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(ty)) | |
| return floatTy.getWidth(); | |
| // Bit size of fir.logical and fir.char depends on the kind map which is not | |
| // available in the verifier without an expensive lookup. | |
| return std::nullopt; | |
| } | |
| llvm::LogicalResult fir::BitcastOp::verify() { | |
| mlir::Type inType = getValue().getType(); | |
| mlir::Type outType = getType(); | |
| if (!isBitcastCompatibleType(inType)) | |
| return emitOpError("input type is not bitcast compatible: ") << inType; | |
| if (!isBitcastCompatibleType(outType)) | |
| return emitOpError("output type is not bitcast compatible: ") << outType; | |
| auto inBits = getBitcastBitSize(inType); | |
| auto outBits = getBitcastBitSize(outType); | |
| if (inBits && outBits && *inBits != *outBits) | |
| return emitOpError("bit size mismatch: input has ") | |
| << *inBits << " bits but output has " << *outBits << " bits"; | |
| return mlir::success(); | |
| } | |
| mlir::OpFoldResult fir::BitcastOp::fold(FoldAdaptor adaptor) { | |
| if (getValue().getType() == getType()) | |
| return getValue(); | |
| if (auto inner = getValue().getDefiningOp<fir::BitcastOp>()) { | |
| if (inner.getValue().getType() == getType()) | |
| return inner.getValue(); | |
| getValueMutable().assign(inner.getValue()); | |
| return getResult(); | |
| } | |
| return {}; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CmpcOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::buildCmpCOp(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::arith::CmpFPredicate predicate, mlir::Value lhs, | |
| mlir::Value rhs) { | |
| result.addOperands({lhs, rhs}); | |
| result.types.push_back(builder.getI1Type()); | |
| result.addAttribute( | |
| fir::CmpcOp::getPredicateAttrName(), | |
| builder.getI64IntegerAttr(static_cast<std::int64_t>(predicate))); | |
| } | |
| mlir::arith::CmpFPredicate | |
| fir::CmpcOp::getPredicateByName(llvm::StringRef name) { | |
| auto pred = mlir::arith::symbolizeCmpFPredicate(name); | |
| assert(pred.has_value() && "invalid predicate name"); | |
| return pred.value(); | |
| } | |
| void fir::CmpcOp::print(mlir::OpAsmPrinter &p) { printCmpOp(p, *this); } | |
| mlir::ParseResult fir::CmpcOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseCmpOp<fir::CmpcOp>(parser, result); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // VolatileCastOp | |
| //===----------------------------------------------------------------------===// | |
| static bool typesMatchExceptForVolatility(mlir::Type fromType, | |
| mlir::Type toType) { | |
| // If we can change only the volatility and get identical types, then we | |
| // match. | |
| if (fir::updateTypeWithVolatility(fromType, fir::isa_volatile_type(toType)) == | |
| toType) | |
| return true; | |
| // Otherwise, recurse on the element types if the base classes are the same. | |
| const bool match = | |
| llvm::TypeSwitch<mlir::Type, bool>(fromType) | |
| .Case<fir::BoxType, fir::ReferenceType, fir::ClassType>( | |
| [&](auto type) { | |
| using TYPE = decltype(type); | |
| // If we are not the same base class, then we don't match. | |
| auto castedToType = mlir::dyn_cast<TYPE>(toType); | |
| if (!castedToType) | |
| return false; | |
| // If we are the same base class, we match if the element types | |
| // match. | |
| return typesMatchExceptForVolatility(type.getEleTy(), | |
| castedToType.getEleTy()); | |
| }) | |
| .Default([](mlir::Type) { return false; }); | |
| return match; | |
| } | |
| llvm::LogicalResult fir::VolatileCastOp::verify() { | |
| mlir::Type fromType = getValue().getType(); | |
| mlir::Type toType = getType(); | |
| if (!typesMatchExceptForVolatility(fromType, toType)) | |
| return emitOpError("types must be identical except for volatility ") | |
| << fromType << " / " << toType; | |
| return mlir::success(); | |
| } | |
| mlir::OpFoldResult fir::VolatileCastOp::fold(FoldAdaptor adaptor) { | |
| if (getValue().getType() == getType()) | |
| return getValue(); | |
| return {}; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ConvertOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::ConvertOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &results, mlir::MLIRContext *context) { | |
| results.insert<ConvertConvertOptPattern, ConvertAscendingIndexOptPattern, | |
| ConvertDescendingIndexOptPattern, RedundantConvertOptPattern, | |
| CombineConvertOptPattern, CombineConvertTruncOptPattern, | |
| ForwardConstantConvertPattern, ChainedPointerConvertsPattern>( | |
| context); | |
| } | |
| static bool isI1(mlir::Type ty) { | |
| if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty)) | |
| return intTy.getWidth() == 1; | |
| return false; | |
| } | |
| mlir::OpFoldResult fir::ConvertOp::fold(FoldAdaptor adaptor) { | |
| if (getValue().getType() == getType()) | |
| return getValue(); | |
| if (matchPattern(getValue(), mlir::m_Op<fir::ConvertOp>())) { | |
| auto inner = mlir::cast<fir::ConvertOp>(getValue().getDefiningOp()); | |
| // (convert (convert 'a : logical -> i1) : i1 -> logical) ==> forward 'a | |
| if (auto toTy = mlir::dyn_cast<fir::LogicalType>(getType())) | |
| if (auto fromTy = | |
| mlir::dyn_cast<fir::LogicalType>(inner.getValue().getType())) | |
| if (mlir::isa<mlir::IntegerType>(inner.getType()) && (toTy == fromTy)) | |
| return inner.getValue(); | |
| // (convert (convert 'a : i1 -> logical) : logical -> i1) ==> forward 'a | |
| if (auto toTy = mlir::dyn_cast<mlir::IntegerType>(getType())) | |
| if (auto fromTy = | |
| mlir::dyn_cast<mlir::IntegerType>(inner.getValue().getType())) | |
| if (mlir::isa<fir::LogicalType>(inner.getType()) && (toTy == fromTy) && | |
| (fromTy.getWidth() == 1)) | |
| return inner.getValue(); | |
| } | |
| // (convert (bitcast 'cst : int -> logical) : logical -> i1) ==> `'cst != 0` | |
| if (isI1(getType()) && | |
| matchPattern(getValue(), mlir::m_Op<fir::BitcastOp>())) { | |
| auto bitcast = mlir::cast<fir::BitcastOp>(getValue().getDefiningOp()); | |
| if (auto cst = fir::getIntIfConstant(bitcast.getValue())) | |
| return mlir::IntegerAttr::get(getType(), cst->isZero() ? 0 : 1); | |
| } | |
| // (convert 'cst : iA -> iB) ==> the constant in iB, converted by the same | |
| // rules as a non-constant operand: truncate when narrowing, zero-extend an | |
| // i1 or unsigned source, sign-extend otherwise. The result must be signless | |
| // to materialize as an arith.constant. | |
| if (auto fromTy = mlir::dyn_cast<mlir::IntegerType>(getValue().getType())) | |
| if (auto toTy = mlir::dyn_cast<mlir::IntegerType>(getType())) | |
| if (toTy.isSignless()) | |
| if (auto cst = fir::getIntIfConstant(getValue())) { | |
| unsigned toBits = toTy.getWidth(); | |
| bool signExtend = toBits > fromTy.getWidth() && | |
| fromTy.getWidth() != 1 && | |
| !fromTy.isUnsignedInteger(); | |
| return mlir::IntegerAttr::get(toTy, signExtend | |
| ? cst->sextOrTrunc(toBits) | |
| : cst->zextOrTrunc(toBits)); | |
| } | |
| return {}; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Logical binary operations fold helpers | |
| //===----------------------------------------------------------------------===// | |
| /// If \p value is a fir.convert of an arith.constant i1, return the i1 value. | |
| static std::optional<bool> getLogicalConstant(mlir::Value value) { | |
| mlir::Value maybeConvertInput = value; | |
| if (auto convertOp = value.getDefiningOp<fir::ConvertOp>()) | |
| maybeConvertInput = convertOp.getValue(); | |
| if (auto cst = fir::getIntIfConstant(maybeConvertInput)) | |
| return !cst->isZero(); | |
| return std::nullopt; | |
| } | |
| /// Try to fold a logical binary operation where both operands are constants. | |
| /// For integer result types, return an IntegerAttr directly (materialized by | |
| /// the dialect's materializeConstant hook). For fir.logical result types we | |
| /// cannot create new operations in a folder, so return one of the existing | |
| /// constant operands if its truth value matches the intended result. | |
| static mlir::OpFoldResult | |
| foldLogicalBinaryWithConstants(bool result, mlir::Type resTy, bool lhsCst, | |
| mlir::Value lhs, bool rhsCst, mlir::Value rhs) { | |
| if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(resTy)) | |
| return mlir::IntegerAttr::get(intTy, result ? 1 : 0); | |
| if (lhsCst == result) | |
| return lhs; | |
| if (rhsCst == result) | |
| return rhs; | |
| return {}; | |
| } | |
| mlir::OpFoldResult fir::LogicalAndOp::fold(FoldAdaptor adaptor) { | |
| auto lhsCst = getLogicalConstant(getLhs()); | |
| auto rhsCst = getLogicalConstant(getRhs()); | |
| if (lhsCst && rhsCst) | |
| return foldLogicalBinaryWithConstants(*lhsCst && *rhsCst, getType(), | |
| *lhsCst, getLhs(), *rhsCst, getRhs()); | |
| // logical_and(x, true) -> x | |
| if (rhsCst && *rhsCst) | |
| return getLhs(); | |
| if (lhsCst && *lhsCst) | |
| return getRhs(); | |
| return {}; | |
| } | |
| mlir::OpFoldResult fir::LogicalOrOp::fold(FoldAdaptor adaptor) { | |
| auto lhsCst = getLogicalConstant(getLhs()); | |
| auto rhsCst = getLogicalConstant(getRhs()); | |
| if (lhsCst && rhsCst) | |
| return foldLogicalBinaryWithConstants(*lhsCst || *rhsCst, getType(), | |
| *lhsCst, getLhs(), *rhsCst, getRhs()); | |
| // logical_or(x, false) -> x | |
| if (rhsCst && !*rhsCst) | |
| return getLhs(); | |
| if (lhsCst && !*lhsCst) | |
| return getRhs(); | |
| return {}; | |
| } | |
| mlir::OpFoldResult fir::EqvOp::fold(FoldAdaptor adaptor) { | |
| auto lhsCst = getLogicalConstant(getLhs()); | |
| auto rhsCst = getLogicalConstant(getRhs()); | |
| if (lhsCst && rhsCst) | |
| return foldLogicalBinaryWithConstants(*lhsCst == *rhsCst, getType(), | |
| *lhsCst, getLhs(), *rhsCst, getRhs()); | |
| // eqv(x, true) -> x | |
| if (rhsCst && *rhsCst) | |
| return getLhs(); | |
| if (lhsCst && *lhsCst) | |
| return getRhs(); | |
| return {}; | |
| } | |
| mlir::OpFoldResult fir::NeqvOp::fold(FoldAdaptor adaptor) { | |
| auto lhsCst = getLogicalConstant(getLhs()); | |
| auto rhsCst = getLogicalConstant(getRhs()); | |
| if (lhsCst && rhsCst) | |
| return foldLogicalBinaryWithConstants(*lhsCst != *rhsCst, getType(), | |
| *lhsCst, getLhs(), *rhsCst, getRhs()); | |
| // neqv(x, false) -> x | |
| if (rhsCst && !*rhsCst) | |
| return getLhs(); | |
| if (lhsCst && !*lhsCst) | |
| return getRhs(); | |
| return {}; | |
| } | |
| bool fir::ConvertOp::isInteger(mlir::Type ty) { | |
| return mlir::isa<mlir::IntegerType, mlir::IndexType, fir::IntegerType>(ty); | |
| } | |
| bool fir::ConvertOp::isIntegerCompatible(mlir::Type ty) { | |
| return isInteger(ty) || mlir::isa<fir::LogicalType>(ty); | |
| } | |
| bool fir::ConvertOp::isFloatCompatible(mlir::Type ty) { | |
| return mlir::isa<mlir::FloatType>(ty); | |
| } | |
| bool fir::ConvertOp::isPointerCompatible(mlir::Type ty) { | |
| return mlir::isa<fir::ReferenceType, fir::PointerType, fir::HeapType, | |
| fir::LLVMPointerType, mlir::MemRefType, mlir::FunctionType, | |
| fir::TypeDescType, mlir::LLVM::LLVMPointerType>(ty); | |
| } | |
| static std::optional<mlir::Type> getVectorElementType(mlir::Type ty) { | |
| mlir::Type elemTy; | |
| if (mlir::isa<fir::VectorType>(ty)) | |
| elemTy = mlir::dyn_cast<fir::VectorType>(ty).getElementType(); | |
| else if (mlir::isa<mlir::VectorType>(ty)) | |
| elemTy = mlir::dyn_cast<mlir::VectorType>(ty).getElementType(); | |
| else | |
| return std::nullopt; | |
| // e.g. fir.vector<4:ui32> => mlir.vector<4xi32> | |
| // e.g. mlir.vector<4xui32> => mlir.vector<4xi32> | |
| if (elemTy.isUnsignedInteger()) { | |
| elemTy = mlir::IntegerType::get( | |
| ty.getContext(), mlir::dyn_cast<mlir::IntegerType>(elemTy).getWidth()); | |
| } | |
| return elemTy; | |
| } | |
| static std::optional<uint64_t> getVectorLen(mlir::Type ty) { | |
| if (mlir::isa<fir::VectorType>(ty)) | |
| return mlir::dyn_cast<fir::VectorType>(ty).getLen(); | |
| else if (mlir::isa<mlir::VectorType>(ty)) { | |
| // fir.vector only supports 1-D vector | |
| if (!(mlir::dyn_cast<mlir::VectorType>(ty).isScalable())) | |
| return mlir::dyn_cast<mlir::VectorType>(ty).getShape()[0]; | |
| } | |
| return std::nullopt; | |
| } | |
| bool fir::ConvertOp::areVectorsCompatible(mlir::Type inTy, mlir::Type outTy) { | |
| if (!(mlir::isa<fir::VectorType>(inTy) && | |
| mlir::isa<mlir::VectorType>(outTy)) && | |
| !(mlir::isa<mlir::VectorType>(inTy) && mlir::isa<fir::VectorType>(outTy))) | |
| return false; | |
| // Only support integer, unsigned and real vector | |
| // Both vectors must have the same element type | |
| std::optional<mlir::Type> inElemTy = getVectorElementType(inTy); | |
| std::optional<mlir::Type> outElemTy = getVectorElementType(outTy); | |
| if (!inElemTy.has_value() || !outElemTy.has_value() || | |
| inElemTy.value() != outElemTy.value()) | |
| return false; | |
| // Both vectors must have the same number of elements | |
| std::optional<uint64_t> inLen = getVectorLen(inTy); | |
| std::optional<uint64_t> outLen = getVectorLen(outTy); | |
| if (!inLen.has_value() || !outLen.has_value() || | |
| inLen.value() != outLen.value()) | |
| return false; | |
| return true; | |
| } | |
| static bool areRecordsCompatible(mlir::Type inTy, mlir::Type outTy) { | |
| // Both records must have the same field types. | |
| // Trust frontend semantics for in-depth checks, such as if both records | |
| // have the BIND(C) attribute. | |
| auto inRecTy = mlir::dyn_cast<fir::RecordType>(inTy); | |
| auto outRecTy = mlir::dyn_cast<fir::RecordType>(outTy); | |
| return inRecTy && outRecTy && inRecTy.getTypeList() == outRecTy.getTypeList(); | |
| } | |
| bool fir::ConvertOp::canBeConverted(mlir::Type inType, mlir::Type outType) { | |
| if (inType == outType) | |
| return true; | |
| return (isPointerCompatible(inType) && isPointerCompatible(outType)) || | |
| (isIntegerCompatible(inType) && isIntegerCompatible(outType)) || | |
| (isInteger(inType) && isFloatCompatible(outType)) || | |
| (isFloatCompatible(inType) && isInteger(outType)) || | |
| (isFloatCompatible(inType) && isFloatCompatible(outType)) || | |
| (isInteger(inType) && isPointerCompatible(outType)) || | |
| (isPointerCompatible(inType) && isInteger(outType)) || | |
| (mlir::isa<fir::BoxType>(inType) && | |
| mlir::isa<fir::BoxType>(outType)) || | |
| (mlir::isa<fir::BoxProcType>(inType) && | |
| mlir::isa<fir::BoxProcType>(outType)) || | |
| (fir::isa_complex(inType) && fir::isa_complex(outType)) || | |
| (fir::isBoxedRecordType(inType) && fir::isPolymorphicType(outType)) || | |
| (fir::isPolymorphicType(inType) && fir::isPolymorphicType(outType)) || | |
| (fir::isPolymorphicType(inType) && mlir::isa<BoxType>(outType)) || | |
| areVectorsCompatible(inType, outType) || | |
| areRecordsCompatible(inType, outType); | |
| } | |
| // In general, ptrtoint-like conversions are allowed to lose volatility | |
| // information because they are either: | |
| // | |
| // 1. passing an entity to an external function and there's nothing we can do | |
| // about volatility after that happens, or | |
| // 2. for code generation, at which point we represent volatility with | |
| // attributes on the LLVM instructions and intrinsics. | |
| // | |
| // For all other cases, volatility ought to match exactly. | |
| static mlir::LogicalResult verifyVolatility(mlir::Type inType, | |
| mlir::Type outType) { | |
| const bool toLLVMPointer = mlir::isa<mlir::LLVM::LLVMPointerType>(outType); | |
| const bool toInteger = fir::isa_integer(outType); | |
| // When converting references to classes or allocatables into boxes for | |
| // runtime arguments, we cast away all the volatility information and pass a | |
| // box<none>. This is allowed. | |
| const bool isBoxNoneLike = [&]() { | |
| if (fir::isBoxNone(outType)) | |
| return true; | |
| if (auto referenceType = mlir::dyn_cast<fir::ReferenceType>(outType)) { | |
| if (fir::isBoxNone(referenceType.getElementType())) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }(); | |
| const bool isPtrToIntLike = toLLVMPointer || toInteger || isBoxNoneLike; | |
| if (isPtrToIntLike) { | |
| return mlir::success(); | |
| } | |
| // In all other cases, we need to check for an exact volatility match. | |
| return mlir::success(fir::isa_volatile_type(inType) == | |
| fir::isa_volatile_type(outType)); | |
| } | |
| llvm::LogicalResult fir::ConvertOp::verify() { | |
| mlir::Type inType = getValue().getType(); | |
| mlir::Type outType = getType(); | |
| if (fir::useStrictVolatileVerification()) { | |
| if (failed(verifyVolatility(inType, outType))) { | |
| return emitOpError("this conversion does not preserve volatility: ") | |
| << inType << " / " << outType; | |
| } | |
| } | |
| if (canBeConverted(inType, outType)) | |
| return mlir::success(); | |
| return emitOpError("invalid type conversion") | |
| << getValue().getType() << " / " << getType(); | |
| } | |
| /// Check whether a slot of this pointee may be exposed as an alias. Promoting a | |
| /// floating-point slot lets its value be folded or, when nothing reads it, | |
| /// deleted, either of which loses an IEEE exception that the running program | |
| /// can observe through ieee_get_flag. Hence, restrict to Integer and Logical | |
| /// types. | |
| static bool isAliasableSlotPointee(mlir::Type pointee) { | |
| return mlir::isa<mlir::IntegerType, fir::LogicalType>(pointee); | |
| } | |
| /// Pointee of a reference to a simple scalar, or null. Both fir.ref and rank-0 | |
| /// memref qualify, since the storage of a scalar is cast between those forms. | |
| static mlir::Type getScalarSlotPointeeType(mlir::Type type) { | |
| mlir::Type eleTy; | |
| if (auto refTy = mlir::dyn_cast<fir::ReferenceType>(type)) { | |
| eleTy = refTy.getEleTy(); | |
| } else if (auto memrefTy = mlir::dyn_cast<mlir::MemRefType>(type)) { | |
| // A rank, layout or memory space lets the cast reinterpret the storage. | |
| if (memrefTy.getRank() != 0 || !memrefTy.getLayout().isIdentity() || | |
| memrefTy.getMemorySpace()) | |
| return {}; | |
| eleTy = memrefTy.getElementType(); | |
| } else { | |
| return {}; | |
| } | |
| if (!isAliasableSlotPointee(eleTy)) | |
| return {}; | |
| return eleTy; | |
| } | |
| /// Returns the source that 'value' is a view of at offset zero, or null. A | |
| /// displaced view, such as an array element, addresses a different location and | |
| /// so is a different slot. | |
| static mlir::Value getAliasedSlotPointer(mlir::Value value) { | |
| auto result = mlir::dyn_cast<mlir::OpResult>(value); | |
| if (!result) | |
| return {}; | |
| auto view = | |
| mlir::dyn_cast<fir::FortranObjectViewOpInterface>(result.getOwner()); | |
| if (!view) | |
| return {}; | |
| std::optional<std::int64_t> offset = view.getViewOffset(result); | |
| if (!offset || *offset != 0) | |
| return {}; | |
| return view.getViewSource(result); | |
| } | |
| /// Checks whether a write to the slot reached through `pointer` dominates every | |
| /// read of it. Otherwise promotion replaces a read with the allocation's | |
| /// default value, which is fir.undefined for fir.alloca but poison for a memref | |
| /// allocation, so promoting through a view would change what reading an | |
| /// uninitialized variable does. | |
| /// | |
| /// TODO: this is only needed because memref::AllocaOp::getDefaultValue returns | |
| /// ub.poison, whereas fir::AllocaOp and LLVM::AllocaOp both return undef, as | |
| /// LLVM's own mem2reg does. Once those align semantically, a read that no | |
| /// write dominates promotes the same way it already does for fir.alloca and | |
| /// this check, along with the dominance query it needs, can go away. | |
| static bool everyReadIsDominatedByAWrite(mlir::Value pointer, | |
| mlir::Type pointee) { | |
| // Start at the root of the chain, to see writes through a sibling alias. | |
| while (mlir::Value aliased = getAliasedSlotPointer(pointer)) { | |
| if (getScalarSlotPointeeType(aliased.getType()) != pointee) | |
| break; | |
| pointer = aliased; | |
| } | |
| llvm::SmallVector<mlir::Operation *> reads; | |
| llvm::SmallVector<mlir::Operation *> writes; | |
| llvm::SmallVector<mlir::Value> worklist{pointer}; | |
| llvm::SmallPtrSet<mlir::Value, 8> visited{pointer}; | |
| while (!worklist.empty()) { | |
| mlir::Value slotPointer = worklist.pop_back_val(); | |
| for (mlir::OpOperand &use : slotPointer.getUses()) { | |
| mlir::Operation *user = use.getOwner(); | |
| // A view of the pointer is another handle on the same storage, so its | |
| // own uses have to be walked as well. | |
| for (mlir::OpResult result : user->getOpResults()) { | |
| if (getAliasedSlotPointer(result) != slotPointer) | |
| continue; | |
| // A different pointee is another slot, which blocks promotion itself. | |
| if (getScalarSlotPointeeType(result.getType()) == pointee && | |
| visited.insert(result).second) | |
| worklist.push_back(result); | |
| } | |
| // A user that is not a promotable access blocks promotion itself. | |
| if (auto memOp = mlir::dyn_cast<mlir::PromotableMemOpInterface>(user)) { | |
| mlir::MemorySlot slot{use.get(), pointee}; | |
| if (memOp.storesTo(slot)) | |
| writes.push_back(user); | |
| else if (memOp.loadsFrom(slot)) | |
| reads.push_back(user); | |
| } | |
| } | |
| } | |
| mlir::DominanceInfo dominance; | |
| return llvm::all_of(reads, [&](mlir::Operation *read) { | |
| return llvm::any_of(writes, [&](mlir::Operation *write) { | |
| return dominance.properlyDominates(write, read); | |
| }); | |
| }); | |
| } | |
| /// The pointee a cast carries through unchanged, or null if the cast is not a | |
| /// transparent alias of the slot. | |
| static mlir::Type getPointeePreservedByCast(fir::ConvertOp op) { | |
| mlir::Type fromTy = op.getValue().getType(); | |
| mlir::Type toTy = op.getType(); | |
| // Volatility may be added or dropped, and such accesses must be kept. | |
| if (fir::isa_volatile_type(fromTy) || fir::isa_volatile_type(toTy)) | |
| return {}; | |
| // A scalar slot on both sides also excludes fir.llvm_ptr and llvm.ptr, which | |
| // carry an address space. | |
| mlir::Type from = getScalarSlotPointeeType(fromTy); | |
| mlir::Type to = getScalarSlotPointeeType(toTy); | |
| if (!from || from != to) | |
| return {}; | |
| // A read that no write dominates must keep its access. | |
| if (!everyReadIsDominatedByAWrite(op.getValue(), to)) | |
| return {}; | |
| return to; | |
| } | |
| void fir::ConvertOp::getPromotableSlotAliases( | |
| mlir::OpOperand &aliasedSlotPointerOperand, | |
| const mlir::MemorySlot &parentSlot, | |
| llvm::SmallVectorImpl<mlir::MemorySlot> &newMemorySlots) { | |
| // The result aliases the slot exactly, so value projections are unchanged. | |
| if (mlir::Type pointee = getPointeePreservedByCast(*this)) | |
| newMemorySlots.push_back({getResult(), pointee}); | |
| } | |
| bool fir::ConvertOp::canUsesBeRemoved( | |
| const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses, | |
| const mlir::DataLayout &dataLayout) { | |
| if (!getPointeePreservedByCast(*this)) | |
| return false; | |
| for (mlir::OpOperand &use : getResult().getUses()) | |
| newBlockingUses.push_back(&use); | |
| return true; | |
| } | |
| mlir::DeletionKind fir::ConvertOp::removeBlockingUses( | |
| const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::OpBuilder &builder) { | |
| return mlir::DeletionKind::Delete; | |
| } | |
| mlir::Speculation::Speculatability fir::ConvertOp::getSpeculatability() { | |
| // fir.convert is speculatable, in general. The only concern may be | |
| // converting from or/and to floating point types, which may trigger | |
| // some FP exceptions. Disallow speculating such converts for the time being. | |
| // Also disallow speculation for converts to/from non-FIR types, except | |
| // for some builtin types. | |
| auto canSpeculateType = [](mlir::Type ty) { | |
| if (fir::isa_fir_type(ty) || fir::isa_integer(ty) || | |
| mlir::isa<mlir::MemRefType>(ty)) | |
| return true; | |
| return false; | |
| }; | |
| return (canSpeculateType(getValue().getType()) && canSpeculateType(getType())) | |
| ? mlir::Speculation::Speculatable | |
| : mlir::Speculation::NotSpeculatable; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CoordinateOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::CoordinateOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| mlir::Type resultType, mlir::Value ref, | |
| mlir::ValueRange coor) { | |
| llvm::SmallVector<int32_t> fieldIndices; | |
| llvm::SmallVector<mlir::Value> dynamicIndices; | |
| bool anyField = false; | |
| for (mlir::Value index : coor) { | |
| if (auto field = index.getDefiningOp<fir::FieldIndexOp>()) { | |
| auto recTy = mlir::cast<fir::RecordType>(field.getOnType()); | |
| fieldIndices.push_back(recTy.getFieldIndex(field.getFieldId())); | |
| anyField = true; | |
| } else { | |
| fieldIndices.push_back(fir::CoordinateOp::kDynamicIndex); | |
| dynamicIndices.push_back(index); | |
| } | |
| } | |
| auto typeAttr = mlir::TypeAttr::get(ref.getType()); | |
| if (anyField) { | |
| build(builder, result, resultType, ref, dynamicIndices, typeAttr, | |
| builder.getDenseI32ArrayAttr(fieldIndices)); | |
| } else { | |
| build(builder, result, resultType, ref, dynamicIndices, typeAttr, nullptr); | |
| } | |
| } | |
| void fir::CoordinateOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| mlir::Type resultType, mlir::Value ref, | |
| llvm::ArrayRef<fir::IntOrValue> coor) { | |
| llvm::SmallVector<int32_t> fieldIndices; | |
| llvm::SmallVector<mlir::Value> dynamicIndices; | |
| bool anyField = false; | |
| for (fir::IntOrValue index : coor) { | |
| llvm::TypeSwitch<fir::IntOrValue>(index) | |
| .Case([&](mlir::IntegerAttr intAttr) { | |
| fieldIndices.push_back(intAttr.getInt()); | |
| anyField = true; | |
| }) | |
| .Case([&](mlir::Value value) { | |
| dynamicIndices.push_back(value); | |
| fieldIndices.push_back(fir::CoordinateOp::kDynamicIndex); | |
| }); | |
| } | |
| auto typeAttr = mlir::TypeAttr::get(ref.getType()); | |
| if (anyField) { | |
| build(builder, result, resultType, ref, dynamicIndices, typeAttr, | |
| builder.getDenseI32ArrayAttr(fieldIndices)); | |
| } else { | |
| build(builder, result, resultType, ref, dynamicIndices, typeAttr, nullptr); | |
| } | |
| } | |
| void fir::CoordinateOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' ' << getRef(); | |
| if (!getFieldIndicesAttr()) { | |
| p << ", " << getCoor(); | |
| } else { | |
| mlir::Type eleTy = fir::getFortranElementType(getRef().getType()); | |
| for (auto index : getIndices()) { | |
| p << ", "; | |
| llvm::TypeSwitch<fir::IntOrValue>(index) | |
| .Case([&](mlir::IntegerAttr intAttr) { | |
| if (auto recordType = llvm::dyn_cast<fir::RecordType>(eleTy)) { | |
| int fieldId = intAttr.getInt(); | |
| if (fieldId < static_cast<int>(recordType.getNumFields())) { | |
| auto nameAndType = recordType.getTypeList()[fieldId]; | |
| p << std::get<std::string>(nameAndType); | |
| eleTy = fir::getFortranElementType( | |
| std::get<mlir::Type>(nameAndType)); | |
| return; | |
| } | |
| } | |
| // Invalid index, still print it so that invalid IR can be | |
| // investigated. | |
| p << intAttr; | |
| }) | |
| .Case([&](mlir::Value value) { p << value; }); | |
| } | |
| } | |
| p.printOptionalAttrDict( | |
| (*this)->getDiscardableAttrDictionary().getValue(), | |
| /*elideAttrs=*/{getBaseTypeAttrName(), getFieldIndicesAttrName()}); | |
| p << " : "; | |
| p.printFunctionalType(getOperandTypes(), (*this)->getResultTypes()); | |
| } | |
| mlir::ParseResult fir::CoordinateOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::OpAsmParser::UnresolvedOperand memref; | |
| if (parser.parseOperand(memref) || parser.parseComma()) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> coorOperands; | |
| llvm::SmallVector<std::pair<llvm::StringRef, int>> fieldNames; | |
| llvm::SmallVector<int32_t> fieldIndices; | |
| while (true) { | |
| llvm::StringRef fieldName; | |
| if (mlir::succeeded(parser.parseOptionalKeyword(&fieldName))) { | |
| fieldNames.push_back({fieldName, static_cast<int>(fieldIndices.size())}); | |
| // Actual value will be computed later when base type has been parsed. | |
| fieldIndices.push_back(0); | |
| } else { | |
| mlir::OpAsmParser::UnresolvedOperand index; | |
| if (parser.parseOperand(index)) | |
| return mlir::failure(); | |
| fieldIndices.push_back(fir::CoordinateOp::kDynamicIndex); | |
| coorOperands.push_back(index); | |
| } | |
| if (mlir::failed(parser.parseOptionalComma())) | |
| break; | |
| } | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> allOperands; | |
| allOperands.push_back(memref); | |
| allOperands.append(coorOperands.begin(), coorOperands.end()); | |
| mlir::FunctionType funcTy; | |
| auto loc = parser.getCurrentLocation(); | |
| if (parser.parseOptionalAttrDict(result.attributes) || | |
| parser.parseColonType(funcTy) || | |
| parser.resolveOperands(allOperands, funcTy.getInputs(), loc, | |
| result.operands) || | |
| parser.addTypesToList(funcTy.getResults(), result.types)) | |
| return mlir::failure(); | |
| result.addAttribute(getBaseTypeAttrName(result.name), | |
| mlir::TypeAttr::get(funcTy.getInput(0))); | |
| if (!fieldNames.empty()) { | |
| mlir::Type eleTy = fir::getFortranElementType(funcTy.getInput(0)); | |
| for (auto [fieldName, operandPosition] : fieldNames) { | |
| auto recTy = llvm::dyn_cast<fir::RecordType>(eleTy); | |
| if (!recTy) | |
| return parser.emitError( | |
| loc, "base must be a derived type when field name appears"); | |
| unsigned fieldNum = recTy.getFieldIndex(fieldName); | |
| if (fieldNum > recTy.getNumFields()) | |
| return parser.emitError(loc) | |
| << "field '" << fieldName | |
| << "' is not a component or subcomponent of the base type"; | |
| fieldIndices[operandPosition] = fieldNum; | |
| eleTy = fir::getFortranElementType( | |
| std::get<mlir::Type>(recTy.getTypeList()[fieldNum])); | |
| } | |
| result.addAttribute(getFieldIndicesAttrName(result.name), | |
| parser.getBuilder().getDenseI32ArrayAttr(fieldIndices)); | |
| } | |
| return mlir::success(); | |
| } | |
| llvm::LogicalResult fir::CoordinateOp::verify() { | |
| const mlir::Type refTy = getRef().getType(); | |
| if (fir::isa_ref_type(refTy)) { | |
| auto eleTy = fir::dyn_cast_ptrEleTy(refTy); | |
| if (auto arrTy = mlir::dyn_cast<fir::SequenceType>(eleTy)) { | |
| if (arrTy.hasUnknownShape()) | |
| return emitOpError("cannot find coordinate in unknown shape"); | |
| if (arrTy.getConstantRows() < arrTy.getDimension() - 1) | |
| return emitOpError("cannot find coordinate with unknown extents"); | |
| } | |
| if (!(fir::isa_aggregate(eleTy) || fir::isa_complex(eleTy) || | |
| fir::isa_char_string(eleTy))) | |
| return emitOpError("cannot apply to this element type"); | |
| } | |
| auto eleTy = fir::dyn_cast_ptrOrBoxEleTy(refTy); | |
| unsigned dimension = 0; | |
| const unsigned numCoors = getCoor().size(); | |
| for (auto coorOperand : llvm::enumerate(getCoor())) { | |
| auto co = coorOperand.value(); | |
| if (dimension == 0 && mlir::isa<fir::SequenceType>(eleTy)) { | |
| dimension = mlir::cast<fir::SequenceType>(eleTy).getDimension(); | |
| if (dimension == 0) | |
| return emitOpError("cannot apply to array of unknown rank"); | |
| } | |
| if (auto *defOp = co.getDefiningOp()) { | |
| if (auto index = mlir::dyn_cast<fir::LenParamIndexOp>(defOp)) { | |
| // Recovering a LEN type parameter only makes sense from a boxed | |
| // value. For a bare reference, the LEN type parameters must be | |
| // passed as additional arguments to `index`. | |
| if (mlir::isa<fir::BoxType>(refTy)) { | |
| if (coorOperand.index() != numCoors - 1) | |
| return emitOpError("len_param_index must be last argument"); | |
| if (getNumOperands() != 2) | |
| return emitOpError("too many operands for len_param_index case"); | |
| } | |
| if (eleTy != index.getOnType()) | |
| return emitOpError( | |
| "len_param_index type not compatible with reference type"); | |
| return mlir::success(); | |
| } else if (auto index = mlir::dyn_cast<fir::FieldIndexOp>(defOp)) { | |
| if (eleTy != index.getOnType()) | |
| return emitOpError( | |
| "field_index type not compatible with reference type"); | |
| if (auto recTy = mlir::dyn_cast<fir::RecordType>(eleTy)) { | |
| eleTy = recTy.getType(index.getFieldName()); | |
| continue; | |
| } | |
| return emitOpError("field_index not applied to !fir.type"); | |
| } | |
| } | |
| if (dimension) { | |
| if (--dimension == 0) | |
| eleTy = mlir::cast<fir::SequenceType>(eleTy).getElementType(); | |
| } else { | |
| if (auto t = mlir::dyn_cast<mlir::TupleType>(eleTy)) { | |
| // FIXME: Generally, we don't know which field of the tuple is being | |
| // referred to unless the operand is a constant. Just assume everything | |
| // is good in the tuple case for now. | |
| return mlir::success(); | |
| } else if (auto t = mlir::dyn_cast<fir::RecordType>(eleTy)) { | |
| // FIXME: This is the same as the tuple case. | |
| return mlir::success(); | |
| } else if (auto t = mlir::dyn_cast<mlir::ComplexType>(eleTy)) { | |
| eleTy = t.getElementType(); | |
| } else if (auto t = mlir::dyn_cast<fir::CharacterType>(eleTy)) { | |
| if (t.getLen() == fir::CharacterType::singleton()) | |
| return emitOpError("cannot apply to character singleton"); | |
| eleTy = fir::CharacterType::getSingleton(t.getContext(), t.getFKind()); | |
| if (fir::unwrapRefType(getType()) != eleTy) | |
| return emitOpError("character type mismatch"); | |
| } else { | |
| return emitOpError("invalid parameters (too many)"); | |
| } | |
| } | |
| } | |
| return mlir::success(); | |
| } | |
| fir::CoordinateIndicesAdaptor fir::CoordinateOp::getIndices() { | |
| return CoordinateIndicesAdaptor(getFieldIndicesAttr(), getCoor()); | |
| } | |
| std::optional<std::int64_t> fir::CoordinateOp::getViewOffset(mlir::OpResult) { | |
| // TODO: we can try to compute the constant offset. | |
| return std::nullopt; | |
| } | |
| mlir::Speculation::Speculatability fir::CoordinateOp::getSpeculatability() { | |
| const mlir::Type refTy = getRef().getType(); | |
| if (fir::isa_ref_type(refTy)) | |
| return mlir::Speculation::Speculatable; | |
| return mayBeAbsentBox(getRef()) ? mlir::Speculation::NotSpeculatable | |
| : mlir::Speculation::Speculatable; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DispatchOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::DispatchOp::verify() { | |
| // Check that pass_arg_pos is in range of actual operands. pass_arg_pos is | |
| // unsigned so check for less than zero is not needed. | |
| if (getPassArgPos() && *getPassArgPos() > (getArgOperands().size() - 1)) | |
| return emitOpError( | |
| "pass_arg_pos must be smaller than the number of operands"); | |
| // Operand pointed by pass_arg_pos must have polymorphic type. | |
| if (getPassArgPos() && | |
| !fir::isPolymorphicType(getArgOperands()[*getPassArgPos()].getType())) | |
| return emitOpError("pass_arg_pos must be a polymorphic operand"); | |
| return mlir::success(); | |
| } | |
| mlir::FunctionType fir::DispatchOp::getFunctionType() { | |
| return mlir::FunctionType::get(getContext(), getOperandTypes(), | |
| getResultTypes()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // TypeInfoOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::TypeInfoOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, fir::RecordType type, | |
| fir::RecordType parentType, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| result.addRegion(); | |
| result.addRegion(); | |
| result.addAttribute(getSymNameAttrName(result.name), | |
| builder.getStringAttr(type.getName())); | |
| result.addAttribute(getTypeAttrName(result.name), mlir::TypeAttr::get(type)); | |
| if (parentType) | |
| result.addAttribute(getParentTypeAttrName(result.name), | |
| mlir::TypeAttr::get(parentType)); | |
| result.addAttributes(attrs); | |
| } | |
| llvm::LogicalResult fir::TypeInfoOp::verify() { | |
| if (!getDispatchTable().empty()) | |
| for (auto &op : getDispatchTable().front().without_terminator()) | |
| if (!mlir::isa<fir::DTEntryOp>(op)) | |
| return op.emitOpError("dispatch table must contain dt_entry"); | |
| if (!mlir::isa<fir::RecordType>(getType())) | |
| return emitOpError("type must be a fir.type"); | |
| if (getParentType() && !mlir::isa<fir::RecordType>(*getParentType())) | |
| return emitOpError("parent_type must be a fir.type"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // EmboxOp | |
| //===----------------------------------------------------------------------===// | |
| // Conversions from reference types to box types must preserve volatility. | |
| static llvm::LogicalResult | |
| verifyEmboxOpVolatilityInvariants(mlir::Type memrefType, | |
| mlir::Type resultType) { | |
| if (!fir::useStrictVolatileVerification()) | |
| return mlir::success(); | |
| mlir::Type boxElementType = | |
| llvm::TypeSwitch<mlir::Type, mlir::Type>(resultType) | |
| .Case<fir::BoxType, fir::ClassType>( | |
| [&](auto type) { return type.getEleTy(); }) | |
| .Default([&](mlir::Type type) { return type; }); | |
| // If the embox is simply wrapping a non-volatile type into a volatile box, | |
| // we're not losing any volatility information. | |
| if (boxElementType == memrefType) { | |
| return mlir::success(); | |
| } | |
| // Otherwise, the volatility of the input and result must match. | |
| const bool volatilityMatches = | |
| fir::isa_volatile_type(memrefType) == fir::isa_volatile_type(resultType); | |
| return mlir::success(volatilityMatches); | |
| } | |
| llvm::LogicalResult fir::EmboxOp::verify() { | |
| auto eleTy = fir::dyn_cast_ptrEleTy(getMemref().getType()); | |
| bool isArray = false; | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy)) { | |
| eleTy = seqTy.getEleTy(); | |
| isArray = true; | |
| } | |
| if (hasLenParams()) { | |
| auto lenPs = numLenParams(); | |
| if (auto rt = mlir::dyn_cast<fir::RecordType>(eleTy)) { | |
| if (lenPs != rt.getNumLenParams()) | |
| return emitOpError("number of LEN params does not correspond" | |
| " to the !fir.type type"); | |
| } else if (auto strTy = mlir::dyn_cast<fir::CharacterType>(eleTy)) { | |
| if (strTy.getLen() != fir::CharacterType::unknownLen()) | |
| return emitOpError("CHARACTER already has static LEN"); | |
| } else { | |
| return emitOpError("LEN parameters require CHARACTER or derived type"); | |
| } | |
| for (auto lp : getTypeparams()) | |
| if (!fir::isa_integer(lp.getType())) | |
| return emitOpError("LEN parameters must be integral type"); | |
| } | |
| if (getShape() && !isArray) | |
| return emitOpError("shape must not be provided for a scalar"); | |
| if (getSlice() && !isArray) | |
| return emitOpError("slice must not be provided for a scalar"); | |
| if (getSourceBox() && !mlir::isa<fir::ClassType>(getResult().getType())) | |
| return emitOpError("source_box must be used with fir.class result type"); | |
| if (failed(verifyEmboxOpVolatilityInvariants(getMemref().getType(), | |
| getResult().getType()))) | |
| return emitOpError( | |
| "cannot convert between volatile and non-volatile types:") | |
| << " " << getMemref().getType() << " " << getResult().getType(); | |
| return mlir::success(); | |
| } | |
| /// Returns true if \p extent matches the extent of the \p box's | |
| /// dimension \p dim. | |
| static bool isBoxExtent(mlir::Value box, std::int64_t dim, mlir::Value extent) { | |
| if (auto op = extent.getDefiningOp<fir::BoxDimsOp>()) | |
| if (op.getVal() == box && op.getExtent() == extent) | |
| if (auto dimOperand = fir::getIntIfConstant(op.getDim())) | |
| return *dimOperand == dim; | |
| return false; | |
| } | |
| /// Returns true if \p lb matches the lower bound of the \p box's | |
| /// dimension \p dim. If \p mayHaveNonDefaultLowerBounds is false, | |
| /// then \p lb may be an integer constant 1. | |
| static bool isBoxLb(mlir::Value box, std::int64_t dim, mlir::Value lb, | |
| bool mayHaveNonDefaultLowerBounds = true) { | |
| if (auto op = lb.getDefiningOp<fir::BoxDimsOp>()) { | |
| if (op.getVal() == box && op.getLowerBound() == lb) | |
| if (auto dimOperand = fir::getIntIfConstant(op.getDim())) | |
| return *dimOperand == dim; | |
| } else if (!mayHaveNonDefaultLowerBounds) { | |
| if (auto constantLb = fir::getIntIfConstant(lb)) | |
| return constantLb->isOne(); | |
| } | |
| return false; | |
| } | |
| /// Returns true if \p ub matches the upper bound of the \p box's | |
| /// dimension \p dim. If \p mayHaveNonDefaultLowerBounds is false, | |
| /// then the dimension's lower bound may be an integer constant 1. | |
| /// Note that the upper bound is usually a result of computation | |
| /// involving the lower bound and the extent, and the function | |
| /// tries its best to recognize the computation pattern. | |
| /// The conservative result 'false' does not necessarily mean | |
| /// that \p ub is not an actual upper bound value. | |
| static bool isBoxUb(mlir::Value box, std::int64_t dim, mlir::Value ub, | |
| bool mayHaveNonDefaultLowerBounds = true) { | |
| if (auto sub1 = ub.getDefiningOp<mlir::arith::SubIOp>()) { | |
| auto one = fir::getIntIfConstant(sub1.getOperand(1)); | |
| if (!one || !one->isOne()) | |
| return false; | |
| if (auto add = sub1.getOperand(0).getDefiningOp<mlir::arith::AddIOp>()) | |
| if ((isBoxLb(box, dim, add.getOperand(0)) && | |
| isBoxExtent(box, dim, add.getOperand(1))) || | |
| (isBoxLb(box, dim, add.getOperand(1)) && | |
| isBoxExtent(box, dim, add.getOperand(0)))) | |
| return true; | |
| } else if (!mayHaveNonDefaultLowerBounds) { | |
| return isBoxExtent(box, dim, ub); | |
| } | |
| return false; | |
| } | |
| /// Checks if the given \p sliceOp specifies a contiguous | |
| /// array slice. If \p checkWhole is true, then the check | |
| /// is done for all dimensions, otherwise, only for the innermost | |
| /// dimension. | |
| /// The simplest way to prove that this is an contiguous slice | |
| /// is to check whether the slice stride(s) is 1. | |
| /// For more complex cases, extra information must be provided | |
| /// by the caller: | |
| /// * \p origBox - if not null, then the source array is represented | |
| /// with this !fir.box value. The box is used to recognize | |
| /// the full dimension slices, which are specified by the triplets | |
| /// computed from the dimensions' lower bounds and extents. | |
| /// * \p mayHaveNonDefaultLowerBounds may be set to false to indicate | |
| /// that the source entity has default lower bounds, so the full | |
| /// dimension slices computations may use 1 for the lower bound. | |
| static bool isContiguousArraySlice(fir::SliceOp sliceOp, bool checkWhole = true, | |
| mlir::Value origBox = nullptr, | |
| bool mayHaveNonDefaultLowerBounds = true) { | |
| if (sliceOp.getFields().empty() && sliceOp.getSubstr().empty()) { | |
| // TODO: generalize code for the triples analysis with | |
| // hlfir::designatePreservesContinuity, especially when | |
| // recognition of the whole dimension slices is added. | |
| auto triples = sliceOp.getTriples(); | |
| assert((triples.size() % 3) == 0 && "invalid triples size"); | |
| // A slice with step=1 in the innermost dimension preserves | |
| // the continuity of the array in the innermost dimension. | |
| // If checkWhole is false, then check only the innermost slice triples. | |
| std::size_t checkUpTo = checkWhole ? triples.size() : 3; | |
| checkUpTo = std::min(checkUpTo, triples.size()); | |
| for (std::size_t i = 0; i < checkUpTo; i += 3) { | |
| if (triples[i] != triples[i + 1]) { | |
| // This is a section of the dimension. Only allow it | |
| // to be the first triple, if the source of the slice | |
| // is a boxed array. If it is a raw pointer, then | |
| // the result will still be contiguous, as long as | |
| // the strides are all ones. | |
| // When origBox is not null, we must prove that the triple | |
| // covers the whole dimension and the stride is one, | |
| // before claiming contiguity for this dimension. | |
| if (i != 0 && origBox) { | |
| std::int64_t dim = i / 3; | |
| if (!isBoxLb(origBox, dim, triples[i], | |
| mayHaveNonDefaultLowerBounds) || | |
| !isBoxUb(origBox, dim, triples[i + 1], | |
| mayHaveNonDefaultLowerBounds)) | |
| return false; | |
| } | |
| auto constantStep = fir::getIntIfConstant(triples[i + 2]); | |
| if (!constantStep || !constantStep->isOne()) | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool fir::isContiguousEmbox(fir::EmboxOp embox, bool checkWhole) { | |
| auto sliceArg = embox.getSlice(); | |
| if (!sliceArg) | |
| return true; | |
| if (auto sliceOp = | |
| mlir::dyn_cast_or_null<fir::SliceOp>(sliceArg.getDefiningOp())) | |
| return isContiguousArraySlice(sliceOp, checkWhole); | |
| return false; | |
| } | |
| std::optional<std::int64_t> fir::EmboxOp::getViewOffset(mlir::OpResult) { | |
| // The address offset is zero, unless there is a slice. | |
| // TODO: we can handle slices that leave the base address untouched. | |
| if (!getSlice()) | |
| return 0; | |
| return std::nullopt; | |
| } | |
| mlir::Speculation::Speculatability fir::EmboxOp::getSpeculatability() { | |
| // The operation is always safe to evaluate if it has the "optional" | |
| // attribute, otherwise it is not safe to evaluate if the input may | |
| // be absent. | |
| if (getOptional()) | |
| return mlir::Speculation::Speculatable; | |
| return (getSourceBox() && mayBeAbsentBox(getSourceBox())) | |
| ? mlir::Speculation::NotSpeculatable | |
| : mlir::Speculation::Speculatable; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CreateBoxOp | |
| //===----------------------------------------------------------------------===// | |
| unsigned fir::CreateBoxOp::getRank() { | |
| auto boxTy = mlir::cast<fir::BaseBoxType>(getResult().getType()); | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>( | |
| fir::unwrapRefType(boxTy.getEleTy()))) | |
| return seqTy.getDimension(); | |
| return 0; | |
| } | |
| llvm::LogicalResult fir::CreateBoxOp::verify() { | |
| auto memEleTy = fir::dyn_cast_ptrEleTy(getMemref().getType()); | |
| auto seqTy = mlir::dyn_cast_or_null<fir::SequenceType>(memEleTy); | |
| if (!seqTy) | |
| return emitOpError("memref must be a reference to an array"); | |
| auto boxTy = mlir::cast<fir::BaseBoxType>(getResult().getType()); | |
| auto boxSeqTy = | |
| mlir::dyn_cast<fir::SequenceType>(fir::unwrapRefType(boxTy.getEleTy())); | |
| if (!boxSeqTy) | |
| return emitOpError("result box must describe an array"); | |
| // The result box must preserve the memref's storage kind so that the | |
| // descriptor's CFI attribute field is set correctly and the value matches a | |
| // box loaded from the corresponding !fir.ref<!fir.box<...>>: | |
| // !fir.heap<...> -> !fir.box<!fir.heap<...>> (allocatable) | |
| // !fir.ptr<...> -> !fir.box<!fir.ptr<...>> (pointer) | |
| // !fir.ref<...> -> !fir.box<...> (plain) | |
| mlir::Type memrefTy = getMemref().getType(); | |
| mlir::Type boxEleTy = boxTy.getEleTy(); | |
| if (mlir::isa<fir::HeapType>(memrefTy) != | |
| mlir::isa<fir::HeapType>(boxEleTy) || | |
| mlir::isa<fir::PointerType>(memrefTy) != | |
| mlir::isa<fir::PointerType>(boxEleTy)) | |
| return emitOpError( | |
| "result box element storage kind must match the memref storage kind"); | |
| unsigned rank = boxSeqTy.getDimension(); | |
| if (seqTy.getDimension() != rank) | |
| return emitOpError("memref rank does not match result box rank"); | |
| if (getLbounds().size() != rank || getExtents().size() != rank || | |
| getStrides().size() != rank) | |
| return emitOpError("expected ") | |
| << rank << " values for each of lbounds, extents, and strides"; | |
| mlir::Type eleTy = boxSeqTy.getEleTy(); | |
| if (fir::isPolymorphicType(boxTy)) | |
| return emitOpError("result box element type must not be polymorphic"); | |
| if (fir::characterWithDynamicLen(eleTy) || | |
| fir::isRecordWithTypeParameters(eleTy)) | |
| return emitOpError("result box element type must be statically sized"); | |
| if (failed(verifyEmboxOpVolatilityInvariants(getMemref().getType(), | |
| getResult().getType()))) | |
| return emitOpError( | |
| "cannot convert between volatile and non-volatile types:") | |
| << " " << getMemref().getType() << " " << getResult().getType(); | |
| return mlir::success(); | |
| } | |
| std::optional<std::int64_t> fir::CreateBoxOp::getViewOffset(mlir::OpResult) { | |
| // The base pointer is the view source and the descriptor addresses relative | |
| // to it, so the view offset onto the source is zero. | |
| return 0; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // EmboxCharOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::EmboxCharOp::verify() { | |
| auto eleTy = fir::dyn_cast_ptrEleTy(getMemref().getType()); | |
| if (!mlir::dyn_cast_or_null<fir::CharacterType>(eleTy)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // EmboxProcOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::EmboxProcOp::verify() { | |
| // host bindings (optional) must be a reference to a tuple | |
| if (auto h = getHost()) { | |
| if (auto r = mlir::dyn_cast<fir::ReferenceType>(h.getType())) | |
| if (mlir::isa<mlir::TupleType>(r.getEleTy())) | |
| return mlir::success(); | |
| return mlir::failure(); | |
| } | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // TypeDescOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::TypeDescOp::build(mlir::OpBuilder &, mlir::OperationState &result, | |
| mlir::TypeAttr inty) { | |
| result.addAttribute("in_type", inty); | |
| result.addTypes(TypeDescType::get(inty.getValue())); | |
| } | |
| mlir::ParseResult fir::TypeDescOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::Type intype; | |
| if (parser.parseType(intype)) | |
| return mlir::failure(); | |
| result.addAttribute("in_type", mlir::TypeAttr::get(intype)); | |
| mlir::Type restype = fir::TypeDescType::get(intype); | |
| if (parser.addTypeToList(restype, result.types)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::TypeDescOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' ' << getOperation()->getAttr("in_type"); | |
| p.printOptionalAttrDict( | |
| getOperation()->getDiscardableAttrDictionary().getValue()); | |
| } | |
| llvm::LogicalResult fir::TypeDescOp::verify() { | |
| mlir::Type resultTy = getType(); | |
| if (auto tdesc = mlir::dyn_cast<fir::TypeDescType>(resultTy)) { | |
| if (tdesc.getOfTy() != getInType()) | |
| return emitOpError("wrapped type mismatched"); | |
| return mlir::success(); | |
| } | |
| return emitOpError("must be !fir.tdesc type"); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // GlobalOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::Type fir::GlobalOp::resultType() { | |
| return wrapAllocaResultType(getType()); | |
| } | |
| mlir::ParseResult fir::GlobalOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| (void)mlir::impl::parseOptionalVisibilityKeyword(parser, result.attributes); | |
| // Parse the optional linkage | |
| llvm::StringRef linkage; | |
| auto &builder = parser.getBuilder(); | |
| if (mlir::succeeded(parser.parseOptionalKeyword(&linkage))) { | |
| if (fir::GlobalOp::verifyValidLinkage(linkage)) | |
| return parser.emitError(parser.getCurrentLocation()) | |
| << "invalid linkage '" << linkage << "'"; | |
| auto linkEnum = fir::symbolizeLinkageEnum(linkage); | |
| result.addAttribute(fir::GlobalOp::getLinkageAttrName(result.name), | |
| fir::LinkageAttr::get(builder.getContext(), *linkEnum)); | |
| } | |
| // Parse the name as a symbol reference attribute. | |
| mlir::SymbolRefAttr nameAttr; | |
| if (parser.parseAttribute(nameAttr, | |
| fir::GlobalOp::getSymrefAttrName(result.name), | |
| result.attributes)) | |
| return mlir::failure(); | |
| result.addAttribute(getSymNameAttrName(result.name), | |
| nameAttr.getRootReference()); | |
| bool simpleInitializer = false; | |
| if (mlir::succeeded(parser.parseOptionalLParen())) { | |
| mlir::Attribute attr; | |
| if (parser.parseAttribute(attr, getInitValAttrName(result.name), | |
| result.attributes) || | |
| parser.parseRParen()) | |
| return mlir::failure(); | |
| simpleInitializer = true; | |
| } | |
| mlir::Attribute parsedProperties; | |
| if (parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(result.attributes)) | |
| return mlir::failure(); | |
| if (succeeded( | |
| parser.parseOptionalKeyword(getConstantAttrName(result.name)))) { | |
| // if "constant" keyword then mark this as a constant, not a variable | |
| result.addAttribute(getConstantAttrName(result.name), | |
| builder.getUnitAttr()); | |
| } | |
| if (succeeded(parser.parseOptionalKeyword(getTargetAttrName(result.name)))) | |
| result.addAttribute(getTargetAttrName(result.name), builder.getUnitAttr()); | |
| mlir::Type globalType; | |
| if (parser.parseColonType(globalType)) | |
| return mlir::failure(); | |
| result.addAttribute(fir::GlobalOp::getTypeAttrName(result.name), | |
| mlir::TypeAttr::get(globalType)); | |
| if (simpleInitializer) { | |
| result.addRegion(); | |
| } else { | |
| // Parse the optional initializer body. | |
| auto parseResult = | |
| parser.parseOptionalRegion(*result.addRegion(), /*arguments=*/{}); | |
| if (parseResult.has_value() && mlir::failed(*parseResult)) | |
| return mlir::failure(); | |
| } | |
| return setParsedProperties<fir::GlobalOp>(parser, result, parsedProperties); | |
| } | |
| void fir::GlobalOp::print(mlir::OpAsmPrinter &p) { | |
| if (mlir::StringAttr visibility = getSymVisibilityAttr()) | |
| p << ' ' << visibility.getValue(); | |
| if (std::optional<fir::LinkageEnum> link = getLinkage()) | |
| p << ' ' << fir::stringifyLinkageEnum(*link); | |
| p << ' '; | |
| p.printAttributeWithoutType(getSymrefAttr()); | |
| if (auto val = getValueOrNull()) | |
| p << '(' << val << ')'; | |
| // Print all other attributes that are not pretty printed here. | |
| fir::GlobalOp::printProperties( | |
| getContext(), p, getProperties(), | |
| /*elidedProps=*/ | |
| {getSymNameAttrName(), getSymrefAttrName(), getTypeAttrName(), | |
| getConstantAttrName(), getTargetAttrName(), getLinkageAttrName(), | |
| getInitValAttrName(), getSymVisibilityAttrName()}); | |
| p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary()); | |
| if (getOperation()->getAttr(getConstantAttrName())) | |
| p << " " << getConstantAttrName().strref(); | |
| if (getOperation()->getAttr(getTargetAttrName())) | |
| p << " " << getTargetAttrName().strref(); | |
| p << " : "; | |
| p.printType(getType()); | |
| if (hasInitializationBody()) { | |
| p << ' '; | |
| p.printRegion(getOperation()->getRegion(0), | |
| /*printEntryBlockArgs=*/false, | |
| /*printBlockTerminators=*/true); | |
| } | |
| } | |
| void fir::GlobalOp::appendInitialValue(mlir::Operation *op) { | |
| getBlock().getOperations().push_back(op); | |
| } | |
| void fir::GlobalOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, llvm::StringRef name, | |
| bool isConstant, bool isTarget, mlir::Type type, | |
| mlir::Attribute initialVal, fir::LinkageAttr linkage, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| result.addRegion(); | |
| result.addAttribute(getTypeAttrName(result.name), mlir::TypeAttr::get(type)); | |
| result.addAttribute(getSymNameAttrName(result.name), | |
| builder.getStringAttr(name)); | |
| result.addAttribute(getSymrefAttrName(result.name), | |
| mlir::SymbolRefAttr::get(builder.getContext(), name)); | |
| if (isConstant) | |
| result.addAttribute(getConstantAttrName(result.name), | |
| builder.getUnitAttr()); | |
| if (isTarget) | |
| result.addAttribute(getTargetAttrName(result.name), builder.getUnitAttr()); | |
| if (initialVal) | |
| result.addAttribute(getInitValAttrName(result.name), initialVal); | |
| if (linkage) | |
| result.addAttribute(getLinkageAttrName(result.name), linkage); | |
| result.attributes.append(attrs.begin(), attrs.end()); | |
| } | |
| void fir::GlobalOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, llvm::StringRef name, | |
| mlir::Type type, mlir::Attribute initialVal, | |
| fir::LinkageAttr linkage, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| build(builder, result, name, /*isConstant=*/false, /*isTarget=*/false, type, | |
| {}, linkage, attrs); | |
| } | |
| void fir::GlobalOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, llvm::StringRef name, | |
| bool isConstant, bool isTarget, mlir::Type type, | |
| fir::LinkageAttr linkage, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| build(builder, result, name, isConstant, isTarget, type, {}, linkage, attrs); | |
| } | |
| void fir::GlobalOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, llvm::StringRef name, | |
| mlir::Type type, fir::LinkageAttr linkage, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| build(builder, result, name, /*isConstant=*/false, /*isTarget=*/false, type, | |
| {}, linkage, attrs); | |
| } | |
| void fir::GlobalOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, llvm::StringRef name, | |
| bool isConstant, bool isTarget, mlir::Type type, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| build(builder, result, name, isConstant, isTarget, type, fir::LinkageAttr{}, | |
| attrs); | |
| } | |
| void fir::GlobalOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, llvm::StringRef name, | |
| mlir::Type type, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs) { | |
| build(builder, result, name, /*isConstant=*/false, /*isTarget=*/false, type, | |
| attrs); | |
| } | |
| mlir::ParseResult fir::GlobalOp::verifyValidLinkage(llvm::StringRef linkage) { | |
| // Supporting only a subset of the LLVM linkage types for now. | |
| return mlir::success(fir::symbolizeLinkageEnum(linkage).has_value()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // GlobalLenOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::ParseResult fir::GlobalLenOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| llvm::StringRef fieldName; | |
| if (failed(parser.parseOptionalKeyword(&fieldName))) { | |
| mlir::StringAttr fieldAttr; | |
| if (parser.parseAttribute(fieldAttr, | |
| fir::GlobalLenOp::getLenParamAttrName(), | |
| result.attributes)) | |
| return mlir::failure(); | |
| } else { | |
| result.addAttribute(fir::GlobalLenOp::getLenParamAttrName(), | |
| parser.getBuilder().getStringAttr(fieldName)); | |
| } | |
| mlir::IntegerAttr constant; | |
| if (parser.parseComma() || | |
| parser.parseAttribute(constant, fir::GlobalLenOp::getIntAttrName(), | |
| result.attributes)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::GlobalLenOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' ' << getOperation()->getAttr(fir::GlobalLenOp::getLenParamAttrName()) | |
| << ", " << getOperation()->getAttr(fir::GlobalLenOp::getIntAttrName()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // FieldIndexOp | |
| //===----------------------------------------------------------------------===// | |
| template <typename TY> | |
| mlir::ParseResult parseFieldLikeOp(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| llvm::StringRef fieldName; | |
| auto &builder = parser.getBuilder(); | |
| mlir::Type recty; | |
| if (parser.parseOptionalKeyword(&fieldName) || parser.parseComma() || | |
| parser.parseType(recty)) | |
| return mlir::failure(); | |
| result.addAttribute(fir::FieldIndexOp::getFieldAttrName(), | |
| builder.getStringAttr(fieldName)); | |
| if (!mlir::dyn_cast<fir::RecordType>(recty)) | |
| return mlir::failure(); | |
| result.addAttribute(fir::FieldIndexOp::getTypeAttrName(), | |
| mlir::TypeAttr::get(recty)); | |
| if (!parser.parseOptionalLParen()) { | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> operands; | |
| llvm::SmallVector<mlir::Type> types; | |
| auto loc = parser.getNameLoc(); | |
| if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::None) || | |
| parser.parseColonTypeList(types) || parser.parseRParen() || | |
| parser.resolveOperands(operands, types, loc, result.operands)) | |
| return mlir::failure(); | |
| } | |
| mlir::Type fieldType = TY::get(builder.getContext()); | |
| if (parser.addTypeToList(fieldType, result.types)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| mlir::ParseResult fir::FieldIndexOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseFieldLikeOp<fir::FieldType>(parser, result); | |
| } | |
| template <typename OP> | |
| void printFieldLikeOp(mlir::OpAsmPrinter &p, OP &op) { | |
| p << ' ' | |
| << op.getOperation() | |
| ->template getAttrOfType<mlir::StringAttr>( | |
| fir::FieldIndexOp::getFieldAttrName()) | |
| .getValue() | |
| << ", " << op.getOperation()->getAttr(fir::FieldIndexOp::getTypeAttrName()); | |
| if (op.getNumOperands()) { | |
| p << '('; | |
| p.printOperands(op.getTypeparams()); | |
| auto sep = ") : "; | |
| for (auto op : op.getTypeparams()) { | |
| p << sep; | |
| if (op) | |
| p.printType(op.getType()); | |
| else | |
| p << "()"; | |
| sep = ", "; | |
| } | |
| } | |
| } | |
| void fir::FieldIndexOp::print(mlir::OpAsmPrinter &p) { | |
| printFieldLikeOp(p, *this); | |
| } | |
| void fir::FieldIndexOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| llvm::StringRef fieldName, mlir::Type recTy, | |
| mlir::ValueRange operands) { | |
| result.addAttribute(getFieldAttrName(), builder.getStringAttr(fieldName)); | |
| result.addAttribute(getTypeAttrName(), mlir::TypeAttr::get(recTy)); | |
| result.addOperands(operands); | |
| } | |
| llvm::SmallVector<mlir::Attribute> fir::FieldIndexOp::getAttributes() { | |
| llvm::SmallVector<mlir::Attribute> attrs; | |
| attrs.push_back(getFieldIdAttr()); | |
| attrs.push_back(getOnTypeAttr()); | |
| return attrs; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // InsertOnRangeOp | |
| //===----------------------------------------------------------------------===// | |
| static mlir::ParseResult | |
| parseCustomRangeSubscript(mlir::OpAsmParser &parser, | |
| mlir::DenseIntElementsAttr &coord) { | |
| llvm::SmallVector<std::int64_t> lbounds; | |
| llvm::SmallVector<std::int64_t> ubounds; | |
| if (parser.parseKeyword("from") || | |
| parser.parseCommaSeparatedList( | |
| mlir::AsmParser::Delimiter::Paren, | |
| [&] { return parser.parseInteger(lbounds.emplace_back(0)); }) || | |
| parser.parseKeyword("to") || | |
| parser.parseCommaSeparatedList(mlir::AsmParser::Delimiter::Paren, [&] { | |
| return parser.parseInteger(ubounds.emplace_back(0)); | |
| })) | |
| return mlir::failure(); | |
| llvm::SmallVector<std::int64_t> zippedBounds; | |
| for (auto zip : llvm::zip(lbounds, ubounds)) { | |
| zippedBounds.push_back(std::get<0>(zip)); | |
| zippedBounds.push_back(std::get<1>(zip)); | |
| } | |
| coord = mlir::Builder(parser.getContext()).getIndexTensorAttr(zippedBounds); | |
| return mlir::success(); | |
| } | |
| static void printCustomRangeSubscript(mlir::OpAsmPrinter &printer, | |
| fir::InsertOnRangeOp op, | |
| mlir::DenseIntElementsAttr coord) { | |
| printer << "from ("; | |
| auto enumerate = llvm::enumerate(coord.getValues<std::int64_t>()); | |
| // Even entries are the lower bounds. | |
| llvm::interleaveComma( | |
| make_filter_range( | |
| enumerate, | |
| [](auto indexed_value) { return indexed_value.index() % 2 == 0; }), | |
| printer, [&](auto indexed_value) { printer << indexed_value.value(); }); | |
| printer << ") to ("; | |
| // Odd entries are the upper bounds. | |
| llvm::interleaveComma( | |
| make_filter_range( | |
| enumerate, | |
| [](auto indexed_value) { return indexed_value.index() % 2 != 0; }), | |
| printer, [&](auto indexed_value) { printer << indexed_value.value(); }); | |
| printer << ")"; | |
| } | |
| /// Range bounds must be nonnegative, and the range must not be empty. | |
| llvm::LogicalResult fir::InsertOnRangeOp::verify() { | |
| if (fir::hasDynamicSize(getSeq().getType())) | |
| return emitOpError("must have constant shape and size"); | |
| mlir::DenseIntElementsAttr coorAttr = getCoor(); | |
| if (coorAttr.size() < 2 || coorAttr.size() % 2 != 0) | |
| return emitOpError("has uneven number of values in ranges"); | |
| bool rangeIsKnownToBeNonempty = false; | |
| for (auto i = coorAttr.getValues<std::int64_t>().end(), | |
| b = coorAttr.getValues<std::int64_t>().begin(); | |
| i != b;) { | |
| int64_t ub = (*--i); | |
| int64_t lb = (*--i); | |
| if (lb < 0 || ub < 0) | |
| return emitOpError("negative range bound"); | |
| if (rangeIsKnownToBeNonempty) | |
| continue; | |
| if (lb > ub) | |
| return emitOpError("empty range"); | |
| rangeIsKnownToBeNonempty = lb < ub; | |
| } | |
| return mlir::success(); | |
| } | |
| bool fir::InsertOnRangeOp::isFullRange() { | |
| auto extents = getType().getShape(); | |
| mlir::DenseIntElementsAttr indexes = getCoor(); | |
| if (indexes.size() / 2 != static_cast<int64_t>(extents.size())) | |
| return false; | |
| auto cur_index = indexes.value_begin<int64_t>(); | |
| for (unsigned i = 0; i < indexes.size(); i += 2) { | |
| if (*(cur_index++) != 0) | |
| return false; | |
| if (*(cur_index++) != extents[i / 2] - 1) | |
| return false; | |
| } | |
| return true; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // InsertValueOp | |
| //===----------------------------------------------------------------------===// | |
| static bool checkIsIntegerConstant(mlir::Attribute attr, std::int64_t conVal) { | |
| if (auto iattr = mlir::dyn_cast<mlir::IntegerAttr>(attr)) | |
| return iattr.getInt() == conVal; | |
| return false; | |
| } | |
| static bool isZero(mlir::Attribute a) { return checkIsIntegerConstant(a, 0); } | |
| static bool isOne(mlir::Attribute a) { return checkIsIntegerConstant(a, 1); } | |
| // Undo some complex patterns created in the front-end and turn them back into | |
| // complex ops. | |
| template <typename FltOp, typename CpxOp> | |
| struct UndoComplexPattern : public mlir::RewritePattern { | |
| UndoComplexPattern(mlir::MLIRContext *ctx) | |
| : mlir::RewritePattern("fir.insert_value", 2, ctx) {} | |
| llvm::LogicalResult | |
| matchAndRewrite(mlir::Operation *op, | |
| mlir::PatternRewriter &rewriter) const override { | |
| auto insval = mlir::dyn_cast_or_null<fir::InsertValueOp>(op); | |
| if (!insval || !mlir::isa<mlir::ComplexType>(insval.getType())) | |
| return mlir::failure(); | |
| auto insval2 = mlir::dyn_cast_or_null<fir::InsertValueOp>( | |
| insval.getAdt().getDefiningOp()); | |
| if (!insval2) | |
| return mlir::failure(); | |
| auto binf = mlir::dyn_cast_or_null<FltOp>(insval.getVal().getDefiningOp()); | |
| auto binf2 = | |
| mlir::dyn_cast_or_null<FltOp>(insval2.getVal().getDefiningOp()); | |
| if (!binf || !binf2 || insval.getCoor().size() != 1 || | |
| !isOne(insval.getCoor()[0]) || insval2.getCoor().size() != 1 || | |
| !isZero(insval2.getCoor()[0])) | |
| return mlir::failure(); | |
| auto eai = mlir::dyn_cast_or_null<fir::ExtractValueOp>( | |
| binf.getLhs().getDefiningOp()); | |
| auto ebi = mlir::dyn_cast_or_null<fir::ExtractValueOp>( | |
| binf.getRhs().getDefiningOp()); | |
| auto ear = mlir::dyn_cast_or_null<fir::ExtractValueOp>( | |
| binf2.getLhs().getDefiningOp()); | |
| auto ebr = mlir::dyn_cast_or_null<fir::ExtractValueOp>( | |
| binf2.getRhs().getDefiningOp()); | |
| if (!eai || !ebi || !ear || !ebr || ear.getAdt() != eai.getAdt() || | |
| ebr.getAdt() != ebi.getAdt() || eai.getCoor().size() != 1 || | |
| !isOne(eai.getCoor()[0]) || ebi.getCoor().size() != 1 || | |
| !isOne(ebi.getCoor()[0]) || ear.getCoor().size() != 1 || | |
| !isZero(ear.getCoor()[0]) || ebr.getCoor().size() != 1 || | |
| !isZero(ebr.getCoor()[0])) | |
| return mlir::failure(); | |
| rewriter.replaceOpWithNewOp<CpxOp>(op, ear.getAdt(), ebr.getAdt()); | |
| return mlir::success(); | |
| } | |
| }; | |
| void fir::InsertValueOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &results, mlir::MLIRContext *context) { | |
| results.insert<UndoComplexPattern<mlir::arith::AddFOp, fir::AddcOp>, | |
| UndoComplexPattern<mlir::arith::SubFOp, fir::SubcOp>>(context); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // IterWhileOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::IterWhileOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Value lb, | |
| mlir::Value ub, mlir::Value step, | |
| mlir::Value iterate, bool finalCountValue, | |
| mlir::ValueRange iterArgs, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| result.addOperands({lb, ub, step, iterate}); | |
| if (finalCountValue) { | |
| result.addTypes(builder.getIndexType()); | |
| result.addAttribute(getFinalValueAttrNameStr(), builder.getUnitAttr()); | |
| } | |
| result.addTypes(iterate.getType()); | |
| result.addOperands(iterArgs); | |
| for (auto v : iterArgs) | |
| result.addTypes(v.getType()); | |
| mlir::Region *bodyRegion = result.addRegion(); | |
| bodyRegion->push_back(new mlir::Block{}); | |
| bodyRegion->front().addArgument(builder.getIndexType(), result.location); | |
| bodyRegion->front().addArgument(iterate.getType(), result.location); | |
| bodyRegion->front().addArguments( | |
| iterArgs.getTypes(), | |
| llvm::SmallVector<mlir::Location>(iterArgs.size(), result.location)); | |
| result.addAttributes(attributes); | |
| } | |
| mlir::ParseResult fir::IterWhileOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| auto &builder = parser.getBuilder(); | |
| mlir::OpAsmParser::Argument inductionVariable, iterateVar; | |
| mlir::OpAsmParser::UnresolvedOperand lb, ub, step, iterateInput; | |
| if (parser.parseLParen() || parser.parseArgument(inductionVariable) || | |
| parser.parseEqual()) | |
| return mlir::failure(); | |
| // Parse loop bounds. | |
| auto indexType = builder.getIndexType(); | |
| auto i1Type = builder.getIntegerType(1); | |
| if (parser.parseOperand(lb) || | |
| parser.resolveOperand(lb, indexType, result.operands) || | |
| parser.parseKeyword("to") || parser.parseOperand(ub) || | |
| parser.resolveOperand(ub, indexType, result.operands) || | |
| parser.parseKeyword("step") || parser.parseOperand(step) || | |
| parser.parseRParen() || | |
| parser.resolveOperand(step, indexType, result.operands) || | |
| parser.parseKeyword("and") || parser.parseLParen() || | |
| parser.parseArgument(iterateVar) || parser.parseEqual() || | |
| parser.parseOperand(iterateInput) || parser.parseRParen() || | |
| parser.resolveOperand(iterateInput, i1Type, result.operands)) | |
| return mlir::failure(); | |
| // Parse the initial iteration arguments. | |
| auto prependCount = false; | |
| // Induction variable. | |
| llvm::SmallVector<mlir::OpAsmParser::Argument> regionArgs; | |
| regionArgs.push_back(inductionVariable); | |
| regionArgs.push_back(iterateVar); | |
| if (succeeded(parser.parseOptionalKeyword("iter_args"))) { | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> operands; | |
| llvm::SmallVector<mlir::Type> regionTypes; | |
| // Parse assignment list and results type list. | |
| if (parser.parseAssignmentList(regionArgs, operands) || | |
| parser.parseArrowTypeList(regionTypes)) | |
| return mlir::failure(); | |
| if (regionTypes.size() == operands.size() + 2) | |
| prependCount = true; | |
| llvm::ArrayRef<mlir::Type> resTypes = regionTypes; | |
| resTypes = prependCount ? resTypes.drop_front(2) : resTypes; | |
| // Resolve input operands. | |
| for (auto operandType : llvm::zip(operands, resTypes)) | |
| if (parser.resolveOperand(std::get<0>(operandType), | |
| std::get<1>(operandType), result.operands)) | |
| return mlir::failure(); | |
| if (prependCount) { | |
| result.addTypes(regionTypes); | |
| } else { | |
| result.addTypes(i1Type); | |
| result.addTypes(resTypes); | |
| } | |
| } else if (succeeded(parser.parseOptionalArrow())) { | |
| llvm::SmallVector<mlir::Type> typeList; | |
| if (parser.parseLParen() || parser.parseTypeList(typeList) || | |
| parser.parseRParen()) | |
| return mlir::failure(); | |
| // Type list must be "(index, i1)". | |
| if (typeList.size() != 2 || !mlir::isa<mlir::IndexType>(typeList[0]) || | |
| !typeList[1].isSignlessInteger(1)) | |
| return mlir::failure(); | |
| result.addTypes(typeList); | |
| prependCount = true; | |
| } else { | |
| result.addTypes(i1Type); | |
| } | |
| if (parser.parseOptionalAttrDictWithKeyword(result.attributes)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Type> argTypes; | |
| // Induction variable (hidden) | |
| if (prependCount) | |
| result.addAttribute(IterWhileOp::getFinalValueAttrNameStr(), | |
| builder.getUnitAttr()); | |
| else | |
| argTypes.push_back(indexType); | |
| // Loop carried variables (including iterate) | |
| argTypes.append(result.types.begin(), result.types.end()); | |
| // Parse the body region. | |
| auto *body = result.addRegion(); | |
| if (regionArgs.size() != argTypes.size()) | |
| return parser.emitError( | |
| parser.getNameLoc(), | |
| "mismatch in number of loop-carried values and defined values"); | |
| for (size_t i = 0, e = regionArgs.size(); i != e; ++i) | |
| regionArgs[i].type = argTypes[i]; | |
| if (parser.parseRegion(*body, regionArgs)) | |
| return mlir::failure(); | |
| fir::IterWhileOp::ensureTerminator(*body, builder, result.location); | |
| return mlir::success(); | |
| } | |
| llvm::LogicalResult fir::IterWhileOp::verify() { | |
| // Check that the body defines as single block argument for the induction | |
| // variable. | |
| auto *body = getBody(); | |
| if (!body->getArgument(1).getType().isInteger(1)) | |
| return emitOpError( | |
| "expected body second argument to be an index argument for " | |
| "the induction variable"); | |
| if (!body->getArgument(0).getType().isIndex()) | |
| return emitOpError( | |
| "expected body first argument to be an index argument for " | |
| "the induction variable"); | |
| auto opNumResults = getNumResults(); | |
| if (getFinalValue()) { | |
| // Result type must be "(index, i1, ...)". | |
| if (!mlir::isa<mlir::IndexType>(getResult(0).getType())) | |
| return emitOpError("result #0 expected to be index"); | |
| if (!getResult(1).getType().isSignlessInteger(1)) | |
| return emitOpError("result #1 expected to be i1"); | |
| opNumResults--; | |
| } else { | |
| // iterate_while always returns the early exit induction value. | |
| // Result type must be "(i1, ...)" | |
| if (!getResult(0).getType().isSignlessInteger(1)) | |
| return emitOpError("result #0 expected to be i1"); | |
| } | |
| if (opNumResults == 0) | |
| return mlir::failure(); | |
| if (getNumIterOperands() != opNumResults) | |
| return emitOpError( | |
| "mismatch in number of loop-carried values and defined values"); | |
| if (getNumRegionIterArgs() != opNumResults) | |
| return emitOpError( | |
| "mismatch in number of basic block args and defined values"); | |
| auto iterOperands = getIterOperands(); | |
| auto iterArgs = getRegionIterArgs(); | |
| auto opResults = getFinalValue() ? getResults().drop_front() : getResults(); | |
| unsigned i = 0u; | |
| for (auto e : llvm::zip(iterOperands, iterArgs, opResults)) { | |
| if (std::get<0>(e).getType() != std::get<2>(e).getType()) | |
| return emitOpError() << "types mismatch between " << i | |
| << "th iter operand and defined value"; | |
| if (std::get<1>(e).getType() != std::get<2>(e).getType()) | |
| return emitOpError() << "types mismatch between " << i | |
| << "th iter region arg and defined value"; | |
| i++; | |
| } | |
| return mlir::success(); | |
| } | |
| void fir::IterWhileOp::print(mlir::OpAsmPrinter &p) { | |
| p << " (" << getInductionVar() << " = " << getLowerBound() << " to " | |
| << getUpperBound() << " step " << getStep() << ") and ("; | |
| assert(hasIterOperands()); | |
| auto regionArgs = getRegionIterArgs(); | |
| auto operands = getIterOperands(); | |
| p << regionArgs.front() << " = " << *operands.begin() << ")"; | |
| if (regionArgs.size() > 1) { | |
| p << " iter_args("; | |
| llvm::interleaveComma( | |
| llvm::zip(regionArgs.drop_front(), operands.drop_front()), p, | |
| [&](auto it) { p << std::get<0>(it) << " = " << std::get<1>(it); }); | |
| p << ") -> ("; | |
| llvm::interleaveComma( | |
| llvm::drop_begin(getResultTypes(), getFinalValue() ? 0 : 1), p); | |
| p << ")"; | |
| } else if (getFinalValue()) { | |
| p << " -> (" << getResultTypes() << ')'; | |
| } | |
| p.printOptionalAttrDictWithKeyword( | |
| (*this)->getDiscardableAttrDictionary().getValue()); | |
| p << ' '; | |
| p.printRegion(getRegion(), /*printEntryBlockArgs=*/false, | |
| /*printBlockTerminators=*/true); | |
| } | |
| llvm::SmallVector<mlir::Region *> fir::IterWhileOp::getLoopRegions() { | |
| return {&getRegion()}; | |
| } | |
| mlir::BlockArgument fir::IterWhileOp::iterArgToBlockArg(mlir::Value iterArg) { | |
| for (auto i : llvm::enumerate(getInitArgs())) | |
| if (iterArg == i.value()) | |
| return getRegion().front().getArgument(i.index() + 1); | |
| return {}; | |
| } | |
| void fir::IterWhileOp::resultToSourceOps( | |
| llvm::SmallVectorImpl<mlir::Value> &results, unsigned resultNum) { | |
| auto oper = getFinalValue() ? resultNum + 1 : resultNum; | |
| auto *term = getRegion().front().getTerminator(); | |
| if (oper < term->getNumOperands()) | |
| results.push_back(term->getOperand(oper)); | |
| } | |
| mlir::Value fir::IterWhileOp::blockArgToSourceOp(unsigned blockArgNum) { | |
| if (blockArgNum > 0 && blockArgNum <= getInitArgs().size()) | |
| return getInitArgs()[blockArgNum - 1]; | |
| return {}; | |
| } | |
| std::optional<llvm::MutableArrayRef<mlir::OpOperand>> | |
| fir::IterWhileOp::getYieldedValuesMutable() { | |
| auto *term = getRegion().front().getTerminator(); | |
| return getFinalValue() ? term->getOpOperands().drop_front() | |
| : term->getOpOperands(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // LenParamIndexOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::ParseResult fir::LenParamIndexOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseFieldLikeOp<fir::LenType>(parser, result); | |
| } | |
| void fir::LenParamIndexOp::print(mlir::OpAsmPrinter &p) { | |
| printFieldLikeOp(p, *this); | |
| } | |
| void fir::LenParamIndexOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| llvm::StringRef fieldName, mlir::Type recTy, | |
| mlir::ValueRange operands) { | |
| result.addAttribute(getFieldAttrName(), builder.getStringAttr(fieldName)); | |
| result.addAttribute(getTypeAttrName(), mlir::TypeAttr::get(recTy)); | |
| result.addOperands(operands); | |
| } | |
| llvm::SmallVector<mlir::Attribute> fir::LenParamIndexOp::getAttributes() { | |
| llvm::SmallVector<mlir::Attribute> attrs; | |
| attrs.push_back(getFieldIdAttr()); | |
| attrs.push_back(getOnTypeAttr()); | |
| return attrs; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // LoadOp | |
| //===----------------------------------------------------------------------===// | |
| static bool isSlotOrDeclaredSlot(mlir::Value val, | |
| const mlir::MemorySlot &slot) { | |
| if (val == slot.ptr) | |
| return true; | |
| if (auto declareOp = val.getDefiningOp<fir::DeclareOp>()) | |
| return declareOp.getMemref() == slot.ptr; | |
| return false; | |
| } | |
| bool fir::LoadOp::loadsFrom(const mlir::MemorySlot &slot) { | |
| return isSlotOrDeclaredSlot(getMemref(), slot); | |
| } | |
| bool fir::LoadOp::storesTo(const mlir::MemorySlot &slot) { return false; } | |
| mlir::Value fir::LoadOp::getStored(const mlir::MemorySlot &slot, | |
| mlir::OpBuilder &builder, | |
| mlir::Value reachingDef, | |
| const mlir::DataLayout &dataLayout) { | |
| return mlir::Value(); | |
| } | |
| bool fir::LoadOp::canUsesBeRemoved( | |
| const mlir::MemorySlot &slot, | |
| const SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses, | |
| const mlir::DataLayout &dataLayout) { | |
| if (blockingUses.size() != 1) | |
| return false; | |
| mlir::Value blockingUse = (*blockingUses.begin())->get(); | |
| return isSlotOrDeclaredSlot(blockingUse, slot) && getMemref() == blockingUse; | |
| } | |
| mlir::DeletionKind fir::LoadOp::removeBlockingUses( | |
| const mlir::MemorySlot &slot, | |
| const SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::OpBuilder &builder, mlir::Value reachingDefinition, | |
| const mlir::DataLayout &dataLayout) { | |
| getResult().replaceAllUsesWith(reachingDefinition); | |
| return mlir::DeletionKind::Delete; | |
| } | |
| void fir::LoadOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::Value refVal) { | |
| if (!refVal) { | |
| mlir::emitError(result.location, "LoadOp has null argument"); | |
| return; | |
| } | |
| auto eleTy = fir::dyn_cast_ptrEleTy(refVal.getType()); | |
| if (!eleTy) { | |
| mlir::emitError(result.location, "not a memory reference type"); | |
| return; | |
| } | |
| build(builder, result, eleTy, refVal); | |
| } | |
| void fir::LoadOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::Type resTy, mlir::Value refVal) { | |
| if (!refVal) { | |
| mlir::emitError(result.location, "LoadOp has null argument"); | |
| return; | |
| } | |
| result.addOperands(refVal); | |
| result.addTypes(resTy); | |
| } | |
| mlir::ParseResult fir::LoadOp::getElementOf(mlir::Type &ele, mlir::Type ref) { | |
| if ((ele = fir::dyn_cast_ptrEleTy(ref))) | |
| return mlir::success(); | |
| return mlir::failure(); | |
| } | |
| mlir::ParseResult fir::LoadOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::Attribute parsedProperties; | |
| mlir::Type type; | |
| mlir::OpAsmParser::UnresolvedOperand oper; | |
| if (parser.parseOperand(oper) || | |
| parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(result.attributes) || | |
| parser.parseColonType(type) || | |
| parser.resolveOperand(oper, type, result.operands)) | |
| return mlir::failure(); | |
| mlir::Type eleTy; | |
| if (fir::LoadOp::getElementOf(eleTy, type) || | |
| parser.addTypeToList(eleTy, result.types) || | |
| setParsedProperties<fir::LoadOp>(parser, result, parsedProperties)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::LoadOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' '; | |
| p.printOperand(getMemref()); | |
| fir::LoadOp::printProperties(getContext(), p, getProperties()); | |
| p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary()); | |
| p << " : " << getMemref().getType(); | |
| } | |
| void fir::LoadOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| effects.emplace_back(mlir::MemoryEffects::Read::get(), &getMemrefMutable(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| addVolatileMemoryEffects({getMemref().getType()}, effects); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DoLoopOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::DoLoopOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Value lb, | |
| mlir::Value ub, mlir::Value step, bool unordered, | |
| bool finalCountValue, mlir::ValueRange iterArgs, | |
| mlir::ValueRange reduceOperands, | |
| llvm::ArrayRef<mlir::Attribute> reduceAttrs, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| result.addOperands({lb, ub, step}); | |
| result.addOperands(reduceOperands); | |
| result.addOperands(iterArgs); | |
| result.addAttribute(getOperandSegmentSizeAttr(), | |
| builder.getDenseI32ArrayAttr( | |
| {1, 1, 1, static_cast<int32_t>(reduceOperands.size()), | |
| static_cast<int32_t>(iterArgs.size())})); | |
| if (finalCountValue) { | |
| result.addTypes(lb.getType()); | |
| result.addAttribute(getFinalValueAttrName(result.name), | |
| builder.getUnitAttr()); | |
| } | |
| for (auto v : iterArgs) | |
| result.addTypes(v.getType()); | |
| mlir::Region *bodyRegion = result.addRegion(); | |
| bodyRegion->push_back(new mlir::Block{}); | |
| if (iterArgs.empty() && !finalCountValue) | |
| fir::DoLoopOp::ensureTerminator(*bodyRegion, builder, result.location); | |
| bodyRegion->front().addArgument(lb.getType(), result.location); | |
| bodyRegion->front().addArguments( | |
| iterArgs.getTypes(), | |
| llvm::SmallVector<mlir::Location>(iterArgs.size(), result.location)); | |
| if (unordered) | |
| result.addAttribute(getUnorderedAttrName(result.name), | |
| builder.getUnitAttr()); | |
| if (!reduceAttrs.empty()) | |
| result.addAttribute(getReduceAttrsAttrName(result.name), | |
| builder.getArrayAttr(reduceAttrs)); | |
| result.addAttributes(attributes); | |
| } | |
| mlir::ParseResult fir::DoLoopOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| auto &builder = parser.getBuilder(); | |
| mlir::OpAsmParser::Argument inductionVariable; | |
| mlir::OpAsmParser::UnresolvedOperand lb, ub, step; | |
| // Parse the induction variable followed by '='. | |
| if (parser.parseArgument(inductionVariable) || parser.parseEqual()) | |
| return mlir::failure(); | |
| // Parse loop bounds. | |
| if (parser.parseOperand(lb) || parser.parseKeyword("to") || | |
| parser.parseOperand(ub) || parser.parseKeyword("step") || | |
| parser.parseOperand(step)) | |
| return mlir::failure(); | |
| if (mlir::succeeded(parser.parseOptionalKeyword("unordered"))) | |
| result.addAttribute("unordered", builder.getUnitAttr()); | |
| // Parse the reduction arguments. | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> reduceOperands; | |
| llvm::SmallVector<mlir::Type> reduceArgTypes; | |
| if (succeeded(parser.parseOptionalKeyword("reduce"))) { | |
| // Parse reduction attributes and variables. | |
| llvm::SmallVector<ReduceAttr> attributes; | |
| if (failed(parser.parseCommaSeparatedList( | |
| mlir::AsmParser::Delimiter::Paren, [&]() { | |
| if (parser.parseAttribute(attributes.emplace_back()) || | |
| parser.parseArrow() || | |
| parser.parseOperand(reduceOperands.emplace_back()) || | |
| parser.parseColonType(reduceArgTypes.emplace_back())) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| }))) | |
| return mlir::failure(); | |
| // Resolve input operands. | |
| for (auto operand_type : llvm::zip(reduceOperands, reduceArgTypes)) | |
| if (parser.resolveOperand(std::get<0>(operand_type), | |
| std::get<1>(operand_type), result.operands)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Attribute> arrayAttr(attributes.begin(), | |
| attributes.end()); | |
| result.addAttribute(getReduceAttrsAttrName(result.name), | |
| builder.getArrayAttr(arrayAttr)); | |
| } | |
| // Parse the optional initial iteration arguments. | |
| llvm::SmallVector<mlir::OpAsmParser::Argument> regionArgs; | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> iterOperands; | |
| llvm::SmallVector<mlir::Type> argTypes; | |
| bool prependCount = false; | |
| regionArgs.push_back(inductionVariable); | |
| if (succeeded(parser.parseOptionalKeyword("iter_args"))) { | |
| // Parse assignment list and results type list. | |
| if (parser.parseAssignmentList(regionArgs, iterOperands) || | |
| parser.parseArrowTypeList(result.types)) | |
| return mlir::failure(); | |
| if (result.types.size() == iterOperands.size() + 1) | |
| prependCount = true; | |
| // Resolve input operands. | |
| llvm::ArrayRef<mlir::Type> resTypes = result.types; | |
| for (auto operand_type : llvm::zip( | |
| iterOperands, prependCount ? resTypes.drop_front() : resTypes)) | |
| if (parser.resolveOperand(std::get<0>(operand_type), | |
| std::get<1>(operand_type), result.operands)) | |
| return mlir::failure(); | |
| } else if (succeeded(parser.parseOptionalArrow())) { | |
| mlir::Type finalValueType; | |
| if (parser.parseType(finalValueType)) | |
| return mlir::failure(); | |
| result.types.push_back(finalValueType); | |
| prependCount = true; | |
| } | |
| // Set the operandSegmentSizes attribute | |
| result.addAttribute(getOperandSegmentSizeAttr(), | |
| builder.getDenseI32ArrayAttr( | |
| {1, 1, 1, static_cast<int32_t>(reduceOperands.size()), | |
| static_cast<int32_t>(iterOperands.size())})); | |
| mlir::Type controlType = builder.getIndexType(); | |
| if (succeeded(parser.parseOptionalColon()) && parser.parseType(controlType)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Value> controlOperands; | |
| if (parser.resolveOperand(lb, controlType, controlOperands) || | |
| parser.resolveOperand(ub, controlType, controlOperands) || | |
| parser.resolveOperand(step, controlType, controlOperands)) | |
| return mlir::failure(); | |
| result.operands.insert(result.operands.begin(), controlOperands.begin(), | |
| controlOperands.end()); | |
| mlir::Attribute parsedProperties; | |
| if (parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDictWithKeyword(result.attributes)) | |
| return mlir::failure(); | |
| // Induction variable. | |
| if (prependCount) | |
| result.addAttribute(DoLoopOp::getFinalValueAttrName(result.name), | |
| builder.getUnitAttr()); | |
| argTypes.push_back(controlType); | |
| // Loop carried variables | |
| mlir::TypeRange iterArgTypes = result.types; | |
| if (prependCount) | |
| iterArgTypes = iterArgTypes.drop_front(); | |
| argTypes.append(iterArgTypes.begin(), iterArgTypes.end()); | |
| // Parse the body region. | |
| auto *body = result.addRegion(); | |
| if (regionArgs.size() != argTypes.size()) | |
| return parser.emitError( | |
| parser.getNameLoc(), | |
| "mismatch in number of loop-carried values and defined values"); | |
| for (size_t i = 0, e = regionArgs.size(); i != e; ++i) | |
| regionArgs[i].type = argTypes[i]; | |
| if (parser.parseRegion(*body, regionArgs)) | |
| return mlir::failure(); | |
| DoLoopOp::ensureTerminator(*body, builder, result.location); | |
| return setParsedProperties<fir::DoLoopOp>(parser, result, parsedProperties); | |
| } | |
| fir::DoLoopOp fir::getForInductionVarOwner(mlir::Value val) { | |
| auto ivArg = mlir::dyn_cast<mlir::BlockArgument>(val); | |
| if (!ivArg) | |
| return {}; | |
| assert(ivArg.getOwner() && "unlinked block argument"); | |
| auto *containingInst = ivArg.getOwner()->getParentOp(); | |
| return mlir::dyn_cast_or_null<fir::DoLoopOp>(containingInst); | |
| } | |
| // Lifted from loop.loop | |
| llvm::LogicalResult fir::DoLoopOp::verify() { | |
| // Check that the body defines as single block argument for the induction | |
| // variable. | |
| auto *body = getBody(); | |
| if (body->getArgument(0).getType() != getLowerBound().getType()) | |
| return emitOpError("expected induction variable to have the same type as " | |
| "the loop control"); | |
| auto opNumResults = getNumResults(); | |
| if (opNumResults == 0) | |
| return mlir::success(); | |
| if (getFinalValue()) { | |
| if (getUnordered()) | |
| return emitOpError("unordered loop has no final value"); | |
| if (getResult(0).getType() != getLowerBound().getType()) | |
| return emitOpError("expected final value to have the same type as the " | |
| "loop control"); | |
| opNumResults--; | |
| } | |
| if (getNumIterOperands() != opNumResults) | |
| return emitOpError( | |
| "mismatch in number of loop-carried values and defined values"); | |
| if (getNumRegionIterArgs() != opNumResults) | |
| return emitOpError( | |
| "mismatch in number of basic block args and defined values"); | |
| auto iterOperands = getIterOperands(); | |
| auto iterArgs = getRegionIterArgs(); | |
| auto opResults = getFinalValue() ? getResults().drop_front() : getResults(); | |
| unsigned i = 0u; | |
| for (auto e : llvm::zip(iterOperands, iterArgs, opResults)) { | |
| if (std::get<0>(e).getType() != std::get<2>(e).getType()) | |
| return emitOpError() << "types mismatch between " << i | |
| << "th iter operand and defined value"; | |
| if (std::get<1>(e).getType() != std::get<2>(e).getType()) | |
| return emitOpError() << "types mismatch between " << i | |
| << "th iter region arg and defined value"; | |
| i++; | |
| } | |
| auto reduceAttrs = getReduceAttrsAttr(); | |
| if (getNumReduceOperands() != (reduceAttrs ? reduceAttrs.size() : 0)) | |
| return emitOpError( | |
| "mismatch in number of reduction variables and reduction attributes"); | |
| return mlir::success(); | |
| } | |
| void fir::DoLoopOp::print(mlir::OpAsmPrinter &p) { | |
| bool printBlockTerminators = false; | |
| p << ' ' << getInductionVar() << " = " << getLowerBound() << " to " | |
| << getUpperBound() << " step " << getStep(); | |
| if (getUnordered()) | |
| p << " unordered"; | |
| if (hasReduceOperands()) { | |
| p << " reduce("; | |
| auto attrs = getReduceAttrsAttr(); | |
| auto operands = getReduceOperands(); | |
| llvm::interleaveComma(llvm::zip(attrs, operands), p, [&](auto it) { | |
| p << std::get<0>(it) << " -> " << std::get<1>(it) << " : " | |
| << std::get<1>(it).getType(); | |
| }); | |
| p << ')'; | |
| printBlockTerminators = true; | |
| } | |
| if (hasIterOperands()) { | |
| p << " iter_args("; | |
| auto regionArgs = getRegionIterArgs(); | |
| auto operands = getIterOperands(); | |
| llvm::interleaveComma(llvm::zip(regionArgs, operands), p, [&](auto it) { | |
| p << std::get<0>(it) << " = " << std::get<1>(it); | |
| }); | |
| p << ") -> (" << getResultTypes() << ')'; | |
| printBlockTerminators = true; | |
| } else if (getFinalValue()) { | |
| p << " -> " << getResultTypes(); | |
| printBlockTerminators = true; | |
| } | |
| if (!getInductionVar().getType().isIndex()) | |
| p << " : " << getInductionVar().getType(); | |
| fir::DoLoopOp::printProperties( | |
| getContext(), p, getProperties(), | |
| {"unordered", "finalValue", "reduceAttrs", "operandSegmentSizes"}); | |
| p.printOptionalAttrDictWithKeyword( | |
| (*this)->getDiscardableAttrDictionary().getValue()); | |
| p << ' '; | |
| p.printRegion(getRegion(), /*printEntryBlockArgs=*/false, | |
| printBlockTerminators); | |
| } | |
| llvm::SmallVector<mlir::Region *> fir::DoLoopOp::getLoopRegions() { | |
| return {&getRegion()}; | |
| } | |
| /// Translate a value passed as an iter_arg to the corresponding block | |
| /// argument in the body of the loop. | |
| mlir::BlockArgument fir::DoLoopOp::iterArgToBlockArg(mlir::Value iterArg) { | |
| for (auto i : llvm::enumerate(getInitArgs())) | |
| if (iterArg == i.value()) | |
| return getRegion().front().getArgument(i.index() + 1); | |
| return {}; | |
| } | |
| /// Translate the result vector (by index number) to the corresponding value | |
| /// to the `fir.result` Op. | |
| void fir::DoLoopOp::resultToSourceOps( | |
| llvm::SmallVectorImpl<mlir::Value> &results, unsigned resultNum) { | |
| auto oper = getFinalValue() ? resultNum + 1 : resultNum; | |
| auto *term = getRegion().front().getTerminator(); | |
| if (oper < term->getNumOperands()) | |
| results.push_back(term->getOperand(oper)); | |
| } | |
| /// Translate the block argument (by index number) to the corresponding value | |
| /// passed as an iter_arg to the parent DoLoopOp. | |
| mlir::Value fir::DoLoopOp::blockArgToSourceOp(unsigned blockArgNum) { | |
| if (blockArgNum > 0 && blockArgNum <= getInitArgs().size()) | |
| return getInitArgs()[blockArgNum - 1]; | |
| return {}; | |
| } | |
| std::optional<llvm::MutableArrayRef<mlir::OpOperand>> | |
| fir::DoLoopOp::getYieldedValuesMutable() { | |
| auto *term = getRegion().front().getTerminator(); | |
| return getFinalValue() ? term->getOpOperands().drop_front() | |
| : term->getOpOperands(); | |
| } | |
| void fir::DoLoopOp::getSuccessorRegions( | |
| mlir::RegionBranchPoint point, | |
| llvm::SmallVectorImpl<mlir::RegionSuccessor> ®ions) { | |
| // Entry (parent → body): loop executes ≥1 iterations, or is skipped (0-trip). | |
| // Loop-back (body → body): another iteration follows. | |
| regions.push_back(mlir::RegionSuccessor(&getRegion())); | |
| // Exit (body → parent): last iteration completes. | |
| regions.push_back(mlir::RegionSuccessor(getOperation())); | |
| } | |
| // Note: when finalValue is set, result[0] tracks the IV's final value. | |
| mlir::OperandRange | |
| fir::DoLoopOp::getEntrySuccessorOperands(mlir::RegionSuccessor successor) { | |
| // Initial iter-arg values, forwarded into the body or (zero-trip) directly | |
| // to the parent's iter results. | |
| return getInitArgs(); | |
| } | |
| mlir::ValueRange | |
| fir::DoLoopOp::getSuccessorInputs(mlir::RegionSuccessor successor) { | |
| // Returns the receiving slots for values flowing into `successor`. | |
| // | |
| // For a loop with finalValue and one iter arg: | |
| // %r:2 = fir.do_loop ... iter_args(%acc = %init) -> (index, i32) | |
| // | |
| // body successor → receiving slots = [%acc] (regionIterArgs) | |
| // parent successor → receiving slots = [%r#1] (results, drop %r#0) | |
| // | |
| // %r#0 (finalValue IV) is excluded: it has no symmetric iter-arg slot in | |
| // the body, so it cannot participate in the 4-edge count check. | |
| if (successor.isOperation()) | |
| return getResults().drop_front(getFinalValue() ? 1 : 0); | |
| return getRegionIterArgs(); | |
| } | |
| mlir::MutableOperandRange | |
| fir::ResultOp::getMutableSuccessorOperands(mlir::RegionSuccessor successor) { | |
| // called on fir.result to determine | |
| // which of the operands is sending to the specific successor | |
| // | |
| // Without finalValue (N iter args, N results): | |
| // fir.result %a0, %a1 : T0, T1 → send all N ops to body/%acc or results | |
| // | |
| // With finalValue (N iter args, N+1 results): | |
| // fir.result %iv_next, %a0 : index, T0 | |
| // ops[0]: IV final value — only meaningful on the last exit, | |
| // has no iter-arg slot in the body; excluded here. | |
| // ops[1..]: iter-carried values — sent on both loop-back and | |
| // exit. | |
| // | |
| // For fir.if / fir.iter_while the parent cast fails; all ops are returned. | |
| if (auto doLoop = | |
| mlir::dyn_cast<fir::DoLoopOp>(getOperation()->getParentOp())) | |
| if (doLoop.getFinalValue() && getNumOperands() > 0) | |
| return mlir::MutableOperandRange(getOperation(), /*start=*/1, | |
| getNumOperands() - 1); | |
| return mlir::MutableOperandRange(getOperation()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DTEntryOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::ParseResult fir::DTEntryOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| llvm::StringRef methodName; | |
| // allow `methodName` or `"methodName"` | |
| if (failed(parser.parseOptionalKeyword(&methodName))) { | |
| mlir::StringAttr methodAttr; | |
| if (parser.parseAttribute(methodAttr, getMethodAttrName(result.name), | |
| result.attributes)) | |
| return mlir::failure(); | |
| } else { | |
| result.addAttribute(getMethodAttrName(result.name), | |
| parser.getBuilder().getStringAttr(methodName)); | |
| } | |
| mlir::SymbolRefAttr calleeAttr; | |
| if (parser.parseComma() || | |
| parser.parseAttribute(calleeAttr, fir::DTEntryOp::getProcAttrNameStr(), | |
| result.attributes)) | |
| return mlir::failure(); | |
| // Optional "deferred" keyword. | |
| if (succeeded(parser.parseOptionalKeyword("deferred"))) { | |
| result.addAttribute(fir::DTEntryOp::getDeferredAttrNameStr(), | |
| parser.getBuilder().getUnitAttr()); | |
| } | |
| return mlir::success(); | |
| } | |
| void fir::DTEntryOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' ' << getMethodAttr() << ", " << getProcAttr(); | |
| if ((*this)->getAttr(fir::DTEntryOp::getDeferredAttrNameStr())) | |
| p << " deferred"; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ReboxOp | |
| //===----------------------------------------------------------------------===// | |
| /// Get the scalar type related to a fir.box type. | |
| /// Example: return f32 for !fir.box<!fir.heap<!fir.array<?x?xf32>>. | |
| static mlir::Type getBoxScalarEleTy(mlir::Type boxTy) { | |
| auto eleTy = fir::dyn_cast_ptrOrBoxEleTy(boxTy); | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy)) | |
| return seqTy.getEleTy(); | |
| return eleTy; | |
| } | |
| /// Test if \p t1 and \p t2 are compatible character types (if they can | |
| /// represent the same type at runtime). | |
| static bool areCompatibleCharacterTypes(mlir::Type t1, mlir::Type t2) { | |
| auto c1 = mlir::dyn_cast<fir::CharacterType>(t1); | |
| auto c2 = mlir::dyn_cast<fir::CharacterType>(t2); | |
| if (!c1 || !c2) | |
| return false; | |
| if (c1.hasDynamicLen() || c2.hasDynamicLen()) | |
| return true; | |
| return c1.getLen() == c2.getLen(); | |
| } | |
| llvm::LogicalResult fir::ReboxOp::verify() { | |
| auto inputBoxTy = getBox().getType(); | |
| if (fir::isa_unknown_size_box(inputBoxTy)) | |
| return emitOpError("box operand must not have unknown rank or type"); | |
| auto outBoxTy = getType(); | |
| if (fir::isa_unknown_size_box(outBoxTy)) | |
| return emitOpError("result type must not have unknown rank or type"); | |
| auto inputRank = fir::getBoxRank(inputBoxTy); | |
| auto inputEleTy = getBoxScalarEleTy(inputBoxTy); | |
| auto outRank = fir::getBoxRank(outBoxTy); | |
| auto outEleTy = getBoxScalarEleTy(outBoxTy); | |
| if (auto sliceVal = getSlice()) { | |
| // Slicing case | |
| if (mlir::cast<fir::SliceType>(sliceVal.getType()).getRank() != inputRank) | |
| return emitOpError("slice operand rank must match box operand rank"); | |
| if (auto shapeVal = getShape()) { | |
| if (auto shiftTy = mlir::dyn_cast<fir::ShiftType>(shapeVal.getType())) { | |
| if (shiftTy.getRank() != inputRank) | |
| return emitOpError("shape operand and input box ranks must match " | |
| "when there is a slice"); | |
| } else { | |
| return emitOpError("shape operand must absent or be a fir.shift " | |
| "when there is a slice"); | |
| } | |
| } | |
| if (auto sliceOp = sliceVal.getDefiningOp()) { | |
| auto slicedRank = mlir::cast<fir::SliceOp>(sliceOp).getOutRank(); | |
| if (slicedRank != outRank) | |
| return emitOpError("result type rank and rank after applying slice " | |
| "operand must match"); | |
| } | |
| } else { | |
| // Reshaping case | |
| unsigned shapeRank = inputRank; | |
| if (auto shapeVal = getShape()) { | |
| auto ty = shapeVal.getType(); | |
| if (auto shapeTy = mlir::dyn_cast<fir::ShapeType>(ty)) { | |
| shapeRank = shapeTy.getRank(); | |
| } else if (auto shapeShiftTy = mlir::dyn_cast<fir::ShapeShiftType>(ty)) { | |
| shapeRank = shapeShiftTy.getRank(); | |
| } else { | |
| auto shiftTy = mlir::cast<fir::ShiftType>(ty); | |
| shapeRank = shiftTy.getRank(); | |
| if (shapeRank != inputRank) | |
| return emitOpError("shape operand and input box ranks must match " | |
| "when the shape is a fir.shift"); | |
| } | |
| } | |
| if (shapeRank != outRank) | |
| return emitOpError("result type and shape operand ranks must match"); | |
| } | |
| if (inputEleTy != outEleTy) { | |
| // TODO: check that outBoxTy is a parent type of inputBoxTy for derived | |
| // types. | |
| // Character input and output types with constant length may be different if | |
| // there is a substring in the slice, otherwise, they must match. If any of | |
| // the types is a character with dynamic length, the other type can be any | |
| // character type. | |
| const bool typeCanMismatch = | |
| mlir::isa<fir::RecordType>(inputEleTy) || | |
| mlir::isa<mlir::NoneType>(outEleTy) || | |
| (mlir::isa<mlir::NoneType>(inputEleTy) && | |
| mlir::isa<fir::RecordType>(outEleTy)) || | |
| (getSlice() && mlir::isa<fir::CharacterType>(inputEleTy)) || | |
| (getSlice() && fir::isa_complex(inputEleTy) && | |
| mlir::isa<mlir::FloatType>(outEleTy)) || | |
| areCompatibleCharacterTypes(inputEleTy, outEleTy); | |
| if (!typeCanMismatch) | |
| return emitOpError( | |
| "op input and output element types must match for intrinsic types"); | |
| } | |
| return mlir::success(); | |
| } | |
| std::optional<std::int64_t> fir::ReboxOp::getViewOffset(mlir::OpResult) { | |
| // The address offset is zero, unless there is a slice. | |
| // TODO: we can handle slices that leave the base address untouched. | |
| if (!getSlice()) | |
| return 0; | |
| return std::nullopt; | |
| } | |
| mlir::Speculation::Speculatability fir::ReboxOp::getSpeculatability() { | |
| // The operation is always safe to evaluate if it has the "optional" | |
| // attribute, otherwise it is not safe to evaluate if the input may | |
| // be absent. | |
| if (getOptional()) | |
| return mlir::Speculation::Speculatable; | |
| return mayBeAbsentBox(getBox()) ? mlir::Speculation::NotSpeculatable | |
| : mlir::Speculation::Speculatable; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ReboxAssumedRankOp | |
| //===----------------------------------------------------------------------===// | |
| static bool areCompatibleAssumedRankElementType(mlir::Type inputEleTy, | |
| mlir::Type outEleTy) { | |
| if (inputEleTy == outEleTy) | |
| return true; | |
| // Output is unlimited polymorphic -> output dynamic type is the same as input | |
| // type. | |
| if (mlir::isa<mlir::NoneType>(outEleTy)) | |
| return true; | |
| // Output/Input are derived types. Assuming input extends output type, output | |
| // dynamic type is the output static type, unless output is polymorphic. | |
| if (mlir::isa<fir::RecordType>(inputEleTy) && | |
| mlir::isa<fir::RecordType>(outEleTy)) | |
| return true; | |
| if (areCompatibleCharacterTypes(inputEleTy, outEleTy)) | |
| return true; | |
| return false; | |
| } | |
| llvm::LogicalResult fir::ReboxAssumedRankOp::verify() { | |
| mlir::Type inputType = getBox().getType(); | |
| if (!mlir::isa<fir::BaseBoxType>(inputType) && !fir::isBoxAddress(inputType)) | |
| return emitOpError("input must be a box or box address"); | |
| mlir::Type inputEleTy = | |
| mlir::cast<fir::BaseBoxType>(fir::unwrapRefType(inputType)) | |
| .unwrapInnerType(); | |
| mlir::Type outEleTy = | |
| mlir::cast<fir::BaseBoxType>(getType()).unwrapInnerType(); | |
| if (!areCompatibleAssumedRankElementType(inputEleTy, outEleTy)) | |
| return emitOpError("input and output element types are incompatible"); | |
| return mlir::success(); | |
| } | |
| void fir::ReboxAssumedRankOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| mlir::OpOperand &inputBox = getBoxMutable(); | |
| if (fir::isBoxAddress(inputBox.get().getType())) | |
| effects.emplace_back(mlir::MemoryEffects::Read::get(), &inputBox, | |
| mlir::SideEffects::DefaultResource::get()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ResultOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::ResultOp::verify() { | |
| auto *parentOp = (*this)->getParentOp(); | |
| auto results = parentOp->getResults(); | |
| auto operands = (*this)->getOperands(); | |
| if (parentOp->getNumResults() != getNumOperands()) | |
| return emitOpError() << "parent of result must have same arity"; | |
| for (auto e : llvm::zip(results, operands)) | |
| if (std::get<0>(e).getType() != std::get<1>(e).getType()) | |
| return emitOpError() << "types mismatch between result op and its parent"; | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SaveResultOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::SaveResultOp::verify() { | |
| auto resultType = getValue().getType(); | |
| if (resultType != fir::dyn_cast_ptrEleTy(getMemref().getType())) | |
| return emitOpError("value type must match memory reference type"); | |
| if (fir::isa_unknown_size_box(resultType)) | |
| return emitOpError("cannot save !fir.box of unknown rank or type"); | |
| if (mlir::isa<fir::BoxType>(resultType)) { | |
| if (getShape() || !getTypeparams().empty()) | |
| return emitOpError( | |
| "must not have shape or length operands if the value is a fir.box"); | |
| return mlir::success(); | |
| } | |
| // fir.record or fir.array case. | |
| unsigned shapeTyRank = 0; | |
| if (auto shapeVal = getShape()) { | |
| auto shapeTy = shapeVal.getType(); | |
| if (auto s = mlir::dyn_cast<fir::ShapeType>(shapeTy)) | |
| shapeTyRank = s.getRank(); | |
| else | |
| shapeTyRank = mlir::cast<fir::ShapeShiftType>(shapeTy).getRank(); | |
| } | |
| auto eleTy = resultType; | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(resultType)) { | |
| if (seqTy.getDimension() != shapeTyRank) | |
| return emitOpError( | |
| "shape operand must be provided and have the value rank " | |
| "when the value is a fir.array"); | |
| eleTy = seqTy.getEleTy(); | |
| } else { | |
| if (shapeTyRank != 0) | |
| return emitOpError( | |
| "shape operand should only be provided if the value is a fir.array"); | |
| } | |
| if (auto recTy = mlir::dyn_cast<fir::RecordType>(eleTy)) { | |
| if (recTy.getNumLenParams() != getTypeparams().size()) | |
| return emitOpError( | |
| "length parameters number must match with the value type " | |
| "length parameters"); | |
| } else if (auto charTy = mlir::dyn_cast<fir::CharacterType>(eleTy)) { | |
| if (getTypeparams().size() > 1) | |
| return emitOpError( | |
| "no more than one length parameter must be provided for " | |
| "character value"); | |
| } else { | |
| if (!getTypeparams().empty()) | |
| return emitOpError( | |
| "length parameters must not be provided for this value type"); | |
| } | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // IntegralSwitchTerminator | |
| //===----------------------------------------------------------------------===// | |
| static constexpr llvm::StringRef getCompareOffsetAttr() { | |
| return "compare_operand_offsets"; | |
| } | |
| static constexpr llvm::StringRef getTargetOffsetAttr() { | |
| return "target_operand_offsets"; | |
| } | |
| template <typename OpT> | |
| static llvm::LogicalResult verifyIntegralSwitchTerminator(OpT op) { | |
| if (!mlir::isa<mlir::IntegerType, mlir::IndexType, fir::IntegerType>( | |
| op.getSelector().getType())) | |
| return op.emitOpError("must be an integer"); | |
| auto cases = | |
| op->template getAttrOfType<mlir::ArrayAttr>(op.getCasesAttr()).getValue(); | |
| auto count = op.getNumDest(); | |
| if (count == 0) | |
| return op.emitOpError("must have at least one successor"); | |
| if (op.getNumConditions() != count) | |
| return op.emitOpError("number of cases and targets don't match"); | |
| if (op.targetOffsetSize() != count) | |
| return op.emitOpError("incorrect number of successor operand groups"); | |
| for (decltype(count) i = 0; i != count; ++i) { | |
| if (!mlir::isa<mlir::IntegerAttr, mlir::UnitAttr>(cases[i])) | |
| return op.emitOpError("invalid case alternative"); | |
| } | |
| return mlir::success(); | |
| } | |
| static mlir::ParseResult parseIntegralSwitchTerminator( | |
| mlir::OpAsmParser &parser, mlir::OperationState &result, | |
| llvm::StringRef casesAttr, llvm::StringRef operandSegmentAttr) { | |
| mlir::OpAsmParser::UnresolvedOperand selector; | |
| mlir::Type type; | |
| if (fir::parseSelector(parser, result, selector, type)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Attribute> ivalues; | |
| llvm::SmallVector<mlir::Block *> dests; | |
| llvm::SmallVector<llvm::SmallVector<mlir::Value>> destArgs; | |
| while (true) { | |
| mlir::Attribute ivalue; // Integer or Unit | |
| mlir::Block *dest; | |
| llvm::SmallVector<mlir::Value> destArg; | |
| mlir::NamedAttrList temp; | |
| if (parser.parseAttribute(ivalue, "i", temp) || parser.parseComma() || | |
| parser.parseSuccessorAndUseList(dest, destArg)) | |
| return mlir::failure(); | |
| ivalues.push_back(ivalue); | |
| dests.push_back(dest); | |
| destArgs.push_back(destArg); | |
| if (!parser.parseOptionalRSquare()) | |
| break; | |
| if (parser.parseComma()) | |
| return mlir::failure(); | |
| } | |
| auto &bld = parser.getBuilder(); | |
| result.addAttribute(casesAttr, bld.getArrayAttr(ivalues)); | |
| llvm::SmallVector<int32_t> argOffs; | |
| int32_t sumArgs = 0; | |
| const auto count = dests.size(); | |
| for (std::remove_const_t<decltype(count)> i = 0; i != count; ++i) { | |
| result.addSuccessors(dests[i]); | |
| result.addOperands(destArgs[i]); | |
| auto argSize = destArgs[i].size(); | |
| argOffs.push_back(argSize); | |
| sumArgs += argSize; | |
| } | |
| result.addAttribute(operandSegmentAttr, | |
| bld.getDenseI32ArrayAttr({1, 0, sumArgs})); | |
| result.addAttribute(getTargetOffsetAttr(), bld.getDenseI32ArrayAttr(argOffs)); | |
| return mlir::success(); | |
| } | |
| template <typename OpT> | |
| static void printIntegralSwitchTerminator(OpT op, mlir::OpAsmPrinter &p) { | |
| p << ' '; | |
| p.printOperand(op.getSelector()); | |
| p << " : " << op.getSelector().getType() << " ["; | |
| auto cases = | |
| op->template getAttrOfType<mlir::ArrayAttr>(op.getCasesAttr()).getValue(); | |
| auto count = op.getNumConditions(); | |
| for (decltype(count) i = 0; i != count; ++i) { | |
| if (i) | |
| p << ", "; | |
| auto &attr = cases[i]; | |
| if (auto intAttr = mlir::dyn_cast_or_null<mlir::IntegerAttr>(attr)) | |
| p << intAttr.getValue(); | |
| else | |
| p.printAttribute(attr); | |
| p << ", "; | |
| op.printSuccessorAtIndex(p, i); | |
| } | |
| p << ']'; | |
| p.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue(), | |
| {op.getCasesAttr(), getCompareOffsetAttr(), | |
| getTargetOffsetAttr(), | |
| op.getOperandSegmentSizeAttr()}); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SelectOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::SelectOp::verify() { | |
| return verifyIntegralSwitchTerminator(*this); | |
| } | |
| mlir::ParseResult fir::SelectOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseIntegralSwitchTerminator(parser, result, getCasesAttr(), | |
| getOperandSegmentSizeAttr()); | |
| } | |
| void fir::SelectOp::print(mlir::OpAsmPrinter &p) { | |
| printIntegralSwitchTerminator(*this, p); | |
| } | |
| template <typename A, typename... AdditionalArgs> | |
| static A getSubOperands(unsigned pos, A allArgs, mlir::DenseI32ArrayAttr ranges, | |
| AdditionalArgs &&...additionalArgs) { | |
| unsigned start = 0; | |
| for (unsigned i = 0; i < pos; ++i) | |
| start += ranges[i]; | |
| return allArgs.slice(start, ranges[pos], | |
| std::forward<AdditionalArgs>(additionalArgs)...); | |
| } | |
| static mlir::MutableOperandRange | |
| getMutableSuccessorOperands(unsigned pos, mlir::MutableOperandRange operands, | |
| llvm::StringRef offsetAttr) { | |
| mlir::Operation *owner = operands.getOwner(); | |
| mlir::NamedAttribute targetOffsetAttr = | |
| *owner->getAttrDictionary().getNamed(offsetAttr); | |
| return getSubOperands( | |
| pos, operands, | |
| mlir::cast<mlir::DenseI32ArrayAttr>(targetOffsetAttr.getValue()), | |
| mlir::MutableOperandRange::OperandSegment(pos, targetOffsetAttr)); | |
| } | |
| std::optional<mlir::OperandRange> fir::SelectOp::getCompareOperands(unsigned) { | |
| return {}; | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectOp::getCompareOperands(llvm::ArrayRef<mlir::Value>, unsigned) { | |
| return {}; | |
| } | |
| mlir::SuccessorOperands fir::SelectOp::getSuccessorOperands(unsigned oper) { | |
| return mlir::SuccessorOperands(::getMutableSuccessorOperands( | |
| oper, getTargetArgsMutable(), getTargetOffsetAttr())); | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectOp::getSuccessorOperands(llvm::ArrayRef<mlir::Value> operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| std::optional<mlir::ValueRange> | |
| fir::SelectOp::getSuccessorOperands(mlir::ValueRange operands, unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| unsigned fir::SelectOp::targetOffsetSize() { | |
| return (*this) | |
| ->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()) | |
| .size(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SelectCaseOp | |
| //===----------------------------------------------------------------------===// | |
| std::optional<mlir::OperandRange> | |
| fir::SelectCaseOp::getCompareOperands(unsigned cond) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getCompareOffsetAttr()); | |
| return {getSubOperands(cond, getCompareArgs(), a)}; | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectCaseOp::getCompareOperands(llvm::ArrayRef<mlir::Value> operands, | |
| unsigned cond) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getCompareOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(cond, getSubOperands(1, operands, segments), a)}; | |
| } | |
| std::optional<mlir::ValueRange> | |
| fir::SelectCaseOp::getCompareOperands(mlir::ValueRange operands, | |
| unsigned cond) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getCompareOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(cond, getSubOperands(1, operands, segments), a)}; | |
| } | |
| mlir::SuccessorOperands fir::SelectCaseOp::getSuccessorOperands(unsigned oper) { | |
| return mlir::SuccessorOperands(::getMutableSuccessorOperands( | |
| oper, getTargetArgsMutable(), getTargetOffsetAttr())); | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectCaseOp::getSuccessorOperands(llvm::ArrayRef<mlir::Value> operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| std::optional<mlir::ValueRange> | |
| fir::SelectCaseOp::getSuccessorOperands(mlir::ValueRange operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| // parser for fir.select_case Op | |
| mlir::ParseResult fir::SelectCaseOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::OpAsmParser::UnresolvedOperand selector; | |
| mlir::Type type; | |
| if (fir::parseSelector(parser, result, selector, type)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Attribute> attrs; | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> opers; | |
| llvm::SmallVector<mlir::Block *> dests; | |
| llvm::SmallVector<llvm::SmallVector<mlir::Value>> destArgs; | |
| llvm::SmallVector<std::int32_t> argOffs; | |
| std::int32_t offSize = 0; | |
| while (true) { | |
| mlir::Attribute attr; | |
| mlir::Block *dest; | |
| llvm::SmallVector<mlir::Value> destArg; | |
| mlir::NamedAttrList temp; | |
| if (parser.parseAttribute(attr, "a", temp) || isValidCaseAttr(attr) || | |
| parser.parseComma()) | |
| return mlir::failure(); | |
| attrs.push_back(attr); | |
| if (mlir::dyn_cast_or_null<mlir::UnitAttr>(attr)) { | |
| argOffs.push_back(0); | |
| } else if (mlir::dyn_cast_or_null<fir::ClosedIntervalAttr>(attr)) { | |
| mlir::OpAsmParser::UnresolvedOperand oper1; | |
| mlir::OpAsmParser::UnresolvedOperand oper2; | |
| if (parser.parseOperand(oper1) || parser.parseComma() || | |
| parser.parseOperand(oper2) || parser.parseComma()) | |
| return mlir::failure(); | |
| opers.push_back(oper1); | |
| opers.push_back(oper2); | |
| argOffs.push_back(2); | |
| offSize += 2; | |
| } else { | |
| mlir::OpAsmParser::UnresolvedOperand oper; | |
| if (parser.parseOperand(oper) || parser.parseComma()) | |
| return mlir::failure(); | |
| opers.push_back(oper); | |
| argOffs.push_back(1); | |
| ++offSize; | |
| } | |
| if (parser.parseSuccessorAndUseList(dest, destArg)) | |
| return mlir::failure(); | |
| dests.push_back(dest); | |
| destArgs.push_back(destArg); | |
| if (mlir::succeeded(parser.parseOptionalRSquare())) | |
| break; | |
| if (parser.parseComma()) | |
| return mlir::failure(); | |
| } | |
| result.addAttribute(fir::SelectCaseOp::getCasesAttr(), | |
| parser.getBuilder().getArrayAttr(attrs)); | |
| if (parser.resolveOperands(opers, type, result.operands)) | |
| return mlir::failure(); | |
| llvm::SmallVector<int32_t> targOffs; | |
| int32_t toffSize = 0; | |
| const auto count = dests.size(); | |
| for (std::remove_const_t<decltype(count)> i = 0; i != count; ++i) { | |
| result.addSuccessors(dests[i]); | |
| result.addOperands(destArgs[i]); | |
| auto argSize = destArgs[i].size(); | |
| targOffs.push_back(argSize); | |
| toffSize += argSize; | |
| } | |
| auto &bld = parser.getBuilder(); | |
| result.addAttribute(fir::SelectCaseOp::getOperandSegmentSizeAttr(), | |
| bld.getDenseI32ArrayAttr({1, offSize, toffSize})); | |
| result.addAttribute(getCompareOffsetAttr(), | |
| bld.getDenseI32ArrayAttr(argOffs)); | |
| result.addAttribute(getTargetOffsetAttr(), | |
| bld.getDenseI32ArrayAttr(targOffs)); | |
| return mlir::success(); | |
| } | |
| void fir::SelectCaseOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' '; | |
| p.printOperand(getSelector()); | |
| p << " : " << getSelector().getType() << " ["; | |
| auto cases = | |
| getOperation()->getAttrOfType<mlir::ArrayAttr>(getCasesAttr()).getValue(); | |
| auto count = getNumConditions(); | |
| for (decltype(count) i = 0; i != count; ++i) { | |
| if (i) | |
| p << ", "; | |
| p << cases[i] << ", "; | |
| if (!mlir::isa<mlir::UnitAttr>(cases[i])) { | |
| auto caseArgs = *getCompareOperands(i); | |
| p.printOperand(*caseArgs.begin()); | |
| p << ", "; | |
| if (mlir::isa<fir::ClosedIntervalAttr>(cases[i])) { | |
| p.printOperand(*(++caseArgs.begin())); | |
| p << ", "; | |
| } | |
| } | |
| printSuccessorAtIndex(p, i); | |
| } | |
| p << ']'; | |
| p.printOptionalAttrDict( | |
| getOperation()->getDiscardableAttrDictionary().getValue(), | |
| {getCasesAttr(), getCompareOffsetAttr(), getTargetOffsetAttr(), | |
| getOperandSegmentSizeAttr()}); | |
| } | |
| unsigned fir::SelectCaseOp::compareOffsetSize() { | |
| return (*this) | |
| ->getAttrOfType<mlir::DenseI32ArrayAttr>(getCompareOffsetAttr()) | |
| .size(); | |
| } | |
| unsigned fir::SelectCaseOp::targetOffsetSize() { | |
| return (*this) | |
| ->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()) | |
| .size(); | |
| } | |
| void fir::SelectCaseOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| mlir::Value selector, | |
| llvm::ArrayRef<mlir::Attribute> compareAttrs, | |
| llvm::ArrayRef<mlir::ValueRange> cmpOperands, | |
| llvm::ArrayRef<mlir::Block *> destinations, | |
| llvm::ArrayRef<mlir::ValueRange> destOperands, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| result.addOperands(selector); | |
| result.addAttribute(getCasesAttr(), builder.getArrayAttr(compareAttrs)); | |
| llvm::SmallVector<int32_t> operOffs; | |
| int32_t operSize = 0; | |
| for (auto attr : compareAttrs) { | |
| if (mlir::isa<fir::ClosedIntervalAttr>(attr)) { | |
| operOffs.push_back(2); | |
| operSize += 2; | |
| } else if (mlir::isa<mlir::UnitAttr>(attr)) { | |
| operOffs.push_back(0); | |
| } else { | |
| operOffs.push_back(1); | |
| ++operSize; | |
| } | |
| } | |
| for (auto ops : cmpOperands) | |
| result.addOperands(ops); | |
| result.addAttribute(getCompareOffsetAttr(), | |
| builder.getDenseI32ArrayAttr(operOffs)); | |
| const auto count = destinations.size(); | |
| for (auto d : destinations) | |
| result.addSuccessors(d); | |
| const auto opCount = destOperands.size(); | |
| llvm::SmallVector<std::int32_t> argOffs; | |
| std::int32_t sumArgs = 0; | |
| for (std::remove_const_t<decltype(count)> i = 0; i != count; ++i) { | |
| if (i < opCount) { | |
| result.addOperands(destOperands[i]); | |
| const auto argSz = destOperands[i].size(); | |
| argOffs.push_back(argSz); | |
| sumArgs += argSz; | |
| } else { | |
| argOffs.push_back(0); | |
| } | |
| } | |
| result.addAttribute(getOperandSegmentSizeAttr(), | |
| builder.getDenseI32ArrayAttr({1, operSize, sumArgs})); | |
| result.addAttribute(getTargetOffsetAttr(), | |
| builder.getDenseI32ArrayAttr(argOffs)); | |
| result.addAttributes(attributes); | |
| } | |
| /// This builder has a slightly simplified interface in that the list of | |
| /// operands need not be partitioned by the builder. Instead the operands are | |
| /// partitioned here, before being passed to the default builder. This | |
| /// partitioning is unchecked, so can go awry on bad input. | |
| void fir::SelectCaseOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| mlir::Value selector, | |
| llvm::ArrayRef<mlir::Attribute> compareAttrs, | |
| llvm::ArrayRef<mlir::Value> cmpOpList, | |
| llvm::ArrayRef<mlir::Block *> destinations, | |
| llvm::ArrayRef<mlir::ValueRange> destOperands, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| llvm::SmallVector<mlir::ValueRange> cmpOpers; | |
| auto iter = cmpOpList.begin(); | |
| for (auto &attr : compareAttrs) { | |
| if (mlir::isa<fir::ClosedIntervalAttr>(attr)) { | |
| cmpOpers.push_back(mlir::ValueRange({iter, iter + 2})); | |
| iter += 2; | |
| } else if (mlir::isa<mlir::UnitAttr>(attr)) { | |
| cmpOpers.push_back(mlir::ValueRange{}); | |
| } else { | |
| cmpOpers.push_back(mlir::ValueRange({iter, iter + 1})); | |
| ++iter; | |
| } | |
| } | |
| build(builder, result, selector, compareAttrs, cmpOpers, destinations, | |
| destOperands, attributes); | |
| } | |
| llvm::LogicalResult fir::SelectCaseOp::verify() { | |
| if (!mlir::isa<mlir::IntegerType, mlir::IndexType, fir::IntegerType, | |
| fir::LogicalType, fir::CharacterType>(getSelector().getType())) | |
| return emitOpError("must be an integer, character, or logical"); | |
| auto cases = | |
| getOperation()->getAttrOfType<mlir::ArrayAttr>(getCasesAttr()).getValue(); | |
| auto count = getNumDest(); | |
| if (count == 0) | |
| return emitOpError("must have at least one successor"); | |
| if (getNumConditions() != count) | |
| return emitOpError("number of conditions and successors don't match"); | |
| if (compareOffsetSize() != count) | |
| return emitOpError("incorrect number of compare operand groups"); | |
| if (targetOffsetSize() != count) | |
| return emitOpError("incorrect number of successor operand groups"); | |
| for (decltype(count) i = 0; i != count; ++i) { | |
| auto &attr = cases[i]; | |
| if (!(mlir::isa<fir::PointIntervalAttr>(attr) || | |
| mlir::isa<fir::LowerBoundAttr>(attr) || | |
| mlir::isa<fir::UpperBoundAttr>(attr) || | |
| mlir::isa<fir::ClosedIntervalAttr>(attr) || | |
| mlir::isa<mlir::UnitAttr>(attr))) | |
| return emitOpError("incorrect select case attribute type"); | |
| } | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SelectRankOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::SelectRankOp::verify() { | |
| return verifyIntegralSwitchTerminator(*this); | |
| } | |
| mlir::ParseResult fir::SelectRankOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| return parseIntegralSwitchTerminator(parser, result, getCasesAttr(), | |
| getOperandSegmentSizeAttr()); | |
| } | |
| void fir::SelectRankOp::print(mlir::OpAsmPrinter &p) { | |
| printIntegralSwitchTerminator(*this, p); | |
| } | |
| std::optional<mlir::OperandRange> | |
| fir::SelectRankOp::getCompareOperands(unsigned) { | |
| return {}; | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectRankOp::getCompareOperands(llvm::ArrayRef<mlir::Value>, unsigned) { | |
| return {}; | |
| } | |
| mlir::SuccessorOperands fir::SelectRankOp::getSuccessorOperands(unsigned oper) { | |
| return mlir::SuccessorOperands(::getMutableSuccessorOperands( | |
| oper, getTargetArgsMutable(), getTargetOffsetAttr())); | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectRankOp::getSuccessorOperands(llvm::ArrayRef<mlir::Value> operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| std::optional<mlir::ValueRange> | |
| fir::SelectRankOp::getSuccessorOperands(mlir::ValueRange operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| unsigned fir::SelectRankOp::targetOffsetSize() { | |
| return (*this) | |
| ->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()) | |
| .size(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SelectTypeOp | |
| //===----------------------------------------------------------------------===// | |
| std::optional<mlir::OperandRange> | |
| fir::SelectTypeOp::getCompareOperands(unsigned) { | |
| return {}; | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectTypeOp::getCompareOperands(llvm::ArrayRef<mlir::Value>, unsigned) { | |
| return {}; | |
| } | |
| mlir::SuccessorOperands fir::SelectTypeOp::getSuccessorOperands(unsigned oper) { | |
| return mlir::SuccessorOperands(::getMutableSuccessorOperands( | |
| oper, getTargetArgsMutable(), getTargetOffsetAttr())); | |
| } | |
| std::optional<llvm::ArrayRef<mlir::Value>> | |
| fir::SelectTypeOp::getSuccessorOperands(llvm::ArrayRef<mlir::Value> operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| std::optional<mlir::ValueRange> | |
| fir::SelectTypeOp::getSuccessorOperands(mlir::ValueRange operands, | |
| unsigned oper) { | |
| auto a = | |
| (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()); | |
| auto segments = (*this)->getAttrOfType<mlir::DenseI32ArrayAttr>( | |
| getOperandSegmentSizeAttr()); | |
| return {getSubOperands(oper, getSubOperands(2, operands, segments), a)}; | |
| } | |
| mlir::ParseResult fir::SelectTypeOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::OpAsmParser::UnresolvedOperand selector; | |
| mlir::Type type; | |
| if (fir::parseSelector(parser, result, selector, type)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Attribute> attrs; | |
| llvm::SmallVector<mlir::Block *> dests; | |
| llvm::SmallVector<llvm::SmallVector<mlir::Value>> destArgs; | |
| while (true) { | |
| mlir::Attribute attr; | |
| mlir::Block *dest; | |
| llvm::SmallVector<mlir::Value> destArg; | |
| mlir::NamedAttrList temp; | |
| if (parser.parseAttribute(attr, "a", temp) || parser.parseComma() || | |
| parser.parseSuccessorAndUseList(dest, destArg)) | |
| return mlir::failure(); | |
| attrs.push_back(attr); | |
| dests.push_back(dest); | |
| destArgs.push_back(destArg); | |
| if (mlir::succeeded(parser.parseOptionalRSquare())) | |
| break; | |
| if (parser.parseComma()) | |
| return mlir::failure(); | |
| } | |
| auto &bld = parser.getBuilder(); | |
| result.addAttribute(fir::SelectTypeOp::getCasesAttr(), | |
| bld.getArrayAttr(attrs)); | |
| llvm::SmallVector<int32_t> argOffs; | |
| int32_t offSize = 0; | |
| const auto count = dests.size(); | |
| for (std::remove_const_t<decltype(count)> i = 0; i != count; ++i) { | |
| result.addSuccessors(dests[i]); | |
| result.addOperands(destArgs[i]); | |
| auto argSize = destArgs[i].size(); | |
| argOffs.push_back(argSize); | |
| offSize += argSize; | |
| } | |
| result.addAttribute(fir::SelectTypeOp::getOperandSegmentSizeAttr(), | |
| bld.getDenseI32ArrayAttr({1, 0, offSize})); | |
| result.addAttribute(getTargetOffsetAttr(), bld.getDenseI32ArrayAttr(argOffs)); | |
| return mlir::success(); | |
| } | |
| unsigned fir::SelectTypeOp::targetOffsetSize() { | |
| return (*this) | |
| ->getAttrOfType<mlir::DenseI32ArrayAttr>(getTargetOffsetAttr()) | |
| .size(); | |
| } | |
| void fir::SelectTypeOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' '; | |
| p.printOperand(getSelector()); | |
| p << " : " << getSelector().getType() << " ["; | |
| auto cases = | |
| getOperation()->getAttrOfType<mlir::ArrayAttr>(getCasesAttr()).getValue(); | |
| auto count = getNumConditions(); | |
| for (decltype(count) i = 0; i != count; ++i) { | |
| if (i) | |
| p << ", "; | |
| p << cases[i] << ", "; | |
| printSuccessorAtIndex(p, i); | |
| } | |
| p << ']'; | |
| p.printOptionalAttrDict( | |
| getOperation()->getDiscardableAttrDictionary().getValue(), | |
| {getCasesAttr(), getCompareOffsetAttr(), getTargetOffsetAttr(), | |
| fir::SelectTypeOp::getOperandSegmentSizeAttr()}); | |
| } | |
| llvm::LogicalResult fir::SelectTypeOp::verify() { | |
| if (!mlir::isa<fir::BaseBoxType>(getSelector().getType())) | |
| return emitOpError("must be a fir.class or fir.box type"); | |
| if (auto boxType = mlir::dyn_cast<fir::BoxType>(getSelector().getType())) | |
| if (!mlir::isa<mlir::NoneType>(boxType.getEleTy())) | |
| return emitOpError("selector must be polymorphic"); | |
| auto typeGuardAttr = getCases(); | |
| for (unsigned idx = 0; idx < typeGuardAttr.size(); ++idx) | |
| if (mlir::isa<mlir::UnitAttr>(typeGuardAttr[idx]) && | |
| idx != typeGuardAttr.size() - 1) | |
| return emitOpError("default must be the last attribute"); | |
| auto count = getNumDest(); | |
| if (count == 0) | |
| return emitOpError("must have at least one successor"); | |
| if (getNumConditions() != count) | |
| return emitOpError("number of conditions and successors don't match"); | |
| if (targetOffsetSize() != count) | |
| return emitOpError("incorrect number of successor operand groups"); | |
| for (unsigned i = 0; i != count; ++i) { | |
| if (!mlir::isa<fir::ExactTypeAttr, fir::SubclassAttr, mlir::UnitAttr>( | |
| typeGuardAttr[i])) | |
| return emitOpError("invalid type-case alternative"); | |
| } | |
| return mlir::success(); | |
| } | |
| void fir::SelectTypeOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| mlir::Value selector, | |
| llvm::ArrayRef<mlir::Attribute> typeOperands, | |
| llvm::ArrayRef<mlir::Block *> destinations, | |
| llvm::ArrayRef<mlir::ValueRange> destOperands, | |
| llvm::ArrayRef<mlir::NamedAttribute> attributes) { | |
| result.addOperands(selector); | |
| result.addAttribute(getCasesAttr(), builder.getArrayAttr(typeOperands)); | |
| const auto count = destinations.size(); | |
| for (mlir::Block *dest : destinations) | |
| result.addSuccessors(dest); | |
| const auto opCount = destOperands.size(); | |
| llvm::SmallVector<int32_t> argOffs; | |
| int32_t sumArgs = 0; | |
| for (std::remove_const_t<decltype(count)> i = 0; i != count; ++i) { | |
| if (i < opCount) { | |
| result.addOperands(destOperands[i]); | |
| const auto argSz = destOperands[i].size(); | |
| argOffs.push_back(argSz); | |
| sumArgs += argSz; | |
| } else { | |
| argOffs.push_back(0); | |
| } | |
| } | |
| result.addAttribute(getOperandSegmentSizeAttr(), | |
| builder.getDenseI32ArrayAttr({1, 0, sumArgs})); | |
| result.addAttribute(getTargetOffsetAttr(), | |
| builder.getDenseI32ArrayAttr(argOffs)); | |
| result.addAttributes(attributes); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShapeOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::ShapeOp::verify() { | |
| auto size = getExtents().size(); | |
| auto shapeTy = mlir::dyn_cast<fir::ShapeType>(getType()); | |
| assert(shapeTy && "must be a shape type"); | |
| if (shapeTy.getRank() != size) | |
| return emitOpError("shape type rank mismatch"); | |
| return mlir::success(); | |
| } | |
| void fir::ShapeOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::ValueRange extents) { | |
| auto type = fir::ShapeType::get(builder.getContext(), extents.size()); | |
| build(builder, result, type, extents); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShapeExtentsOp | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct FoldShapeExtentsOfShape | |
| : public mlir::OpRewritePattern<fir::ShapeExtentsOp> { | |
| using OpRewritePattern::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::ShapeExtentsOp op, | |
| mlir::PatternRewriter &rewriter) const override { | |
| mlir::Value shape = op.getShape(); | |
| mlir::ValueRange extents; | |
| if (auto shapeOp = shape.getDefiningOp<fir::ShapeOp>()) | |
| extents = shapeOp.getExtents(); | |
| else if (auto ssOp = shape.getDefiningOp<fir::ShapeShiftOp>()) | |
| extents = ssOp.getExtents(); | |
| else | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Value> results; | |
| for (auto [extent, resultType] : llvm::zip(extents, op.getResultTypes())) { | |
| if (extent.getType() == resultType) { | |
| results.push_back(extent); | |
| } else if (fir::ConvertOp::canBeConverted(extent.getType(), resultType)) { | |
| results.push_back( | |
| fir::ConvertOp::create(rewriter, op.getLoc(), resultType, extent)); | |
| } else { | |
| return mlir::failure(); | |
| } | |
| } | |
| rewriter.replaceOp(op, results); | |
| return mlir::success(); | |
| } | |
| }; | |
| } // namespace | |
| llvm::LogicalResult fir::ShapeExtentsOp::verify() { | |
| mlir::Type ty = getShape().getType(); | |
| unsigned rank; | |
| if (auto shapeTy = mlir::dyn_cast<fir::ShapeType>(ty)) | |
| rank = shapeTy.getRank(); | |
| else if (auto ssTy = mlir::dyn_cast<fir::ShapeShiftType>(ty)) | |
| rank = ssTy.getRank(); | |
| else | |
| return emitOpError("operand must be !fir.shape or !fir.shapeshift"); | |
| if (getNumResults() != rank) | |
| return emitOpError("number of results must match shape rank"); | |
| return mlir::success(); | |
| } | |
| void fir::ShapeExtentsOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| mlir::Value shape) { | |
| mlir::Type ty = shape.getType(); | |
| unsigned rank; | |
| if (auto shapeTy = mlir::dyn_cast<fir::ShapeType>(ty)) | |
| rank = shapeTy.getRank(); | |
| else | |
| rank = mlir::cast<fir::ShapeShiftType>(ty).getRank(); | |
| mlir::Type indexTy = builder.getIndexType(); | |
| llvm::SmallVector<mlir::Type> resultTypes(rank, indexTy); | |
| result.addTypes(resultTypes); | |
| result.addOperands(shape); | |
| } | |
| void fir::ShapeExtentsOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &patterns, mlir::MLIRContext *context) { | |
| patterns.add<FoldShapeExtentsOfShape>(context); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShapeShiftOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::ShapeShiftOp::verify() { | |
| auto size = getPairs().size(); | |
| if (size < 2 || size > 16 * 2) | |
| return emitOpError("incorrect number of args"); | |
| if (size % 2 != 0) | |
| return emitOpError("requires a multiple of 2 args"); | |
| auto shapeTy = mlir::dyn_cast<fir::ShapeShiftType>(getType()); | |
| assert(shapeTy && "must be a shape shift type"); | |
| if (shapeTy.getRank() * 2 != size) | |
| return emitOpError("shape type rank mismatch"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShiftOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::ShiftOp::verify() { | |
| auto size = getOrigins().size(); | |
| auto shiftTy = mlir::dyn_cast<fir::ShiftType>(getType()); | |
| assert(shiftTy && "must be a shift type"); | |
| if (shiftTy.getRank() != size) | |
| return emitOpError("shift type rank mismatch"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // SliceOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::SliceOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::ValueRange trips, mlir::ValueRange path, | |
| mlir::ValueRange substr) { | |
| const auto rank = trips.size() / 3; | |
| auto sliceTy = fir::SliceType::get(builder.getContext(), rank); | |
| build(builder, result, sliceTy, trips, path, substr); | |
| } | |
| /// Return the output rank of a slice op. The output rank must be between 1 and | |
| /// the rank of the array being sliced (inclusive). | |
| unsigned fir::SliceOp::getOutputRank(mlir::ValueRange triples) { | |
| unsigned rank = 0; | |
| if (!triples.empty()) { | |
| for (unsigned i = 1, end = triples.size(); i < end; i += 3) { | |
| auto *op = triples[i].getDefiningOp(); | |
| if (!mlir::isa_and_nonnull<fir::UndefOp>(op)) | |
| ++rank; | |
| } | |
| assert(rank > 0); | |
| } | |
| return rank; | |
| } | |
| llvm::LogicalResult fir::SliceOp::verify() { | |
| auto size = getTriples().size(); | |
| if (size < 3 || size > 16 * 3) | |
| return emitOpError("incorrect number of args for triple"); | |
| if (size % 3 != 0) | |
| return emitOpError("requires a multiple of 3 args"); | |
| auto sliceTy = mlir::dyn_cast<fir::SliceType>(getType()); | |
| assert(sliceTy && "must be a slice type"); | |
| if (sliceTy.getRank() * 3 != size) | |
| return emitOpError("slice type rank mismatch"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // StoreOp | |
| //===----------------------------------------------------------------------===// | |
| bool fir::StoreOp::loadsFrom(const mlir::MemorySlot &slot) { return false; } | |
| bool fir::StoreOp::storesTo(const mlir::MemorySlot &slot) { | |
| return isSlotOrDeclaredSlot(getMemref(), slot); | |
| } | |
| mlir::Value fir::StoreOp::getStored(const mlir::MemorySlot &slot, | |
| mlir::OpBuilder &builder, | |
| mlir::Value reachingDef, | |
| const mlir::DataLayout &dataLayout) { | |
| return getValue(); | |
| } | |
| bool fir::StoreOp::canUsesBeRemoved( | |
| const mlir::MemorySlot &slot, | |
| const SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses, | |
| const mlir::DataLayout &dataLayout) { | |
| if (blockingUses.size() != 1) | |
| return false; | |
| mlir::Value blockingUse = (*blockingUses.begin())->get(); | |
| return isSlotOrDeclaredSlot(blockingUse, slot) && | |
| getMemref() == blockingUse && getValue() != blockingUse; | |
| } | |
| mlir::DeletionKind fir::StoreOp::removeBlockingUses( | |
| const mlir::MemorySlot &slot, | |
| const SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::OpBuilder &builder, mlir::Value reachingDefinition, | |
| const mlir::DataLayout &dataLayout) { | |
| return mlir::DeletionKind::Delete; | |
| } | |
| mlir::Type fir::StoreOp::elementType(mlir::Type refType) { | |
| return fir::dyn_cast_ptrEleTy(refType); | |
| } | |
| mlir::ParseResult fir::StoreOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::Attribute parsedProperties; | |
| mlir::Type type; | |
| mlir::OpAsmParser::UnresolvedOperand oper; | |
| mlir::OpAsmParser::UnresolvedOperand store; | |
| if (parser.parseOperand(oper) || parser.parseKeyword("to") || | |
| parser.parseOperand(store) || | |
| parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(result.attributes) || | |
| parser.parseColonType(type) || | |
| parser.resolveOperand(oper, fir::StoreOp::elementType(type), | |
| result.operands) || | |
| parser.resolveOperand(store, type, result.operands) || | |
| setParsedProperties<fir::StoreOp>(parser, result, parsedProperties)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::StoreOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' '; | |
| p.printOperand(getValue()); | |
| p << " to "; | |
| p.printOperand(getMemref()); | |
| fir::StoreOp::printProperties(getContext(), p, getProperties()); | |
| p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary()); | |
| p << " : " << getMemref().getType(); | |
| } | |
| llvm::LogicalResult fir::StoreOp::verify() { | |
| if (getValue().getType() != fir::dyn_cast_ptrEleTy(getMemref().getType())) | |
| return emitOpError("store value type must match memory reference type"); | |
| return mlir::success(); | |
| } | |
| void fir::StoreOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::Value value, mlir::Value memref) { | |
| build(builder, result, value, memref, {}, {}, {}); | |
| } | |
| void fir::StoreOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| effects.emplace_back(mlir::MemoryEffects::Write::get(), &getMemrefMutable(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| addVolatileMemoryEffects({getMemref().getType()}, effects); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // PrefetchOp | |
| //===----------------------------------------------------------------------===// | |
| mlir::ParseResult fir::PrefetchOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| mlir::OpAsmParser::UnresolvedOperand memref; | |
| if (parser.parseOperand(memref)) | |
| return mlir::failure(); | |
| if (mlir::succeeded(parser.parseLBrace())) { | |
| llvm::StringRef kw; | |
| if (parser.parseKeyword(&kw)) | |
| return mlir::failure(); | |
| if (kw == "read") | |
| result.addAttribute("rw", parser.getBuilder().getBoolAttr(false)); | |
| else if (kw == "write") | |
| result.addAttribute("rw", parser.getBuilder().getUnitAttr()); | |
| else | |
| return parser.emitError(parser.getCurrentLocation(), | |
| "Expected either read or write keyword"); | |
| if (parser.parseComma()) | |
| return mlir::failure(); | |
| if (parser.parseKeyword(&kw)) | |
| return mlir::failure(); | |
| if (kw == "instruction") { | |
| result.addAttribute("cacheType", parser.getBuilder().getBoolAttr(false)); | |
| } else if (kw == "data") { | |
| result.addAttribute("cacheType", parser.getBuilder().getUnitAttr()); | |
| } else | |
| return parser.emitError(parser.getCurrentLocation(), | |
| "Expected either intruction or data keyword"); | |
| if (parser.parseComma()) | |
| return mlir::failure(); | |
| if (mlir::succeeded(parser.parseKeyword("localityHint"))) { | |
| if (parser.parseEqual()) | |
| return mlir::failure(); | |
| mlir::Attribute intAttr; | |
| if (parser.parseAttribute(intAttr)) | |
| return mlir::failure(); | |
| result.addAttribute("localityHint", intAttr); | |
| } | |
| if (parser.parseRBrace()) | |
| return mlir::failure(); | |
| } | |
| mlir::Type type; | |
| if (parser.parseColonType(type)) | |
| return mlir::failure(); | |
| if (parser.resolveOperand(memref, type, result.operands)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::PrefetchOp::print(mlir::OpAsmPrinter &p) { | |
| p << " "; | |
| p.printOperand(getMemref()); | |
| p << " {"; | |
| if (getRw()) | |
| p << "write"; | |
| else | |
| p << "read"; | |
| p << ", "; | |
| if (getCacheType()) | |
| p << "data"; | |
| else | |
| p << "instruction"; | |
| p << ", localityHint = "; | |
| p << getLocalityHint(); | |
| p << " : " << getLocalityHintAttr().getType(); | |
| p << "} : " << getMemref().getType(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // CopyOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::CopyOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::Value source, mlir::Value destination, | |
| bool noOverlap) { | |
| mlir::UnitAttr noOverlapAttr = | |
| noOverlap ? builder.getUnitAttr() : mlir::UnitAttr{}; | |
| build(builder, result, source, destination, noOverlapAttr); | |
| } | |
| llvm::LogicalResult fir::CopyOp::verify() { | |
| mlir::Type sourceType = fir::unwrapRefType(getSource().getType()); | |
| mlir::Type destinationType = fir::unwrapRefType(getDestination().getType()); | |
| if (sourceType != destinationType) | |
| return emitOpError("source and destination must have the same value type"); | |
| return mlir::success(); | |
| } | |
| void fir::CopyOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| effects.emplace_back(mlir::MemoryEffects::Read::get(), &getSourceMutable(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| effects.emplace_back(mlir::MemoryEffects::Write::get(), | |
| &getDestinationMutable(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| addVolatileMemoryEffects({getDestination().getType(), getSource().getType()}, | |
| effects); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // StringLitOp | |
| //===----------------------------------------------------------------------===// | |
| inline fir::CharacterType::KindTy stringLitOpGetKind(fir::StringLitOp op) { | |
| auto eleTy = mlir::cast<fir::SequenceType>(op.getType()).getElementType(); | |
| return mlir::cast<fir::CharacterType>(eleTy).getFKind(); | |
| } | |
| bool fir::StringLitOp::isWideValue() { return stringLitOpGetKind(*this) != 1; } | |
| static mlir::NamedAttribute | |
| mkNamedIntegerAttr(mlir::OpBuilder &builder, llvm::StringRef name, int64_t v) { | |
| assert(v > 0); | |
| return builder.getNamedAttr( | |
| name, builder.getIntegerAttr(builder.getIntegerType(64), v)); | |
| } | |
| void fir::StringLitOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| fir::CharacterType inType, llvm::StringRef val, | |
| std::optional<int64_t> len) { | |
| auto valAttr = builder.getNamedAttr(value(), builder.getStringAttr(val)); | |
| int64_t length = len ? *len : inType.getLen(); | |
| auto lenAttr = mkNamedIntegerAttr(builder, size(), length); | |
| result.addAttributes({valAttr, lenAttr}); | |
| result.addTypes(inType); | |
| } | |
| template <typename C> | |
| static mlir::ArrayAttr convertToArrayAttr(mlir::OpBuilder &builder, | |
| llvm::ArrayRef<C> xlist) { | |
| llvm::SmallVector<mlir::Attribute> attrs; | |
| auto ty = builder.getIntegerType(8 * sizeof(C)); | |
| for (auto ch : xlist) | |
| attrs.push_back(builder.getIntegerAttr(ty, ch)); | |
| return builder.getArrayAttr(attrs); | |
| } | |
| void fir::StringLitOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| fir::CharacterType inType, | |
| llvm::ArrayRef<char> vlist, | |
| std::optional<std::int64_t> len) { | |
| auto valAttr = | |
| builder.getNamedAttr(xlist(), convertToArrayAttr(builder, vlist)); | |
| std::int64_t length = len ? *len : inType.getLen(); | |
| auto lenAttr = mkNamedIntegerAttr(builder, size(), length); | |
| result.addAttributes({valAttr, lenAttr}); | |
| result.addTypes(inType); | |
| } | |
| void fir::StringLitOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| fir::CharacterType inType, | |
| llvm::ArrayRef<char16_t> vlist, | |
| std::optional<std::int64_t> len) { | |
| auto valAttr = | |
| builder.getNamedAttr(xlist(), convertToArrayAttr(builder, vlist)); | |
| std::int64_t length = len ? *len : inType.getLen(); | |
| auto lenAttr = mkNamedIntegerAttr(builder, size(), length); | |
| result.addAttributes({valAttr, lenAttr}); | |
| result.addTypes(inType); | |
| } | |
| void fir::StringLitOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, | |
| fir::CharacterType inType, | |
| llvm::ArrayRef<char32_t> vlist, | |
| std::optional<std::int64_t> len) { | |
| auto valAttr = | |
| builder.getNamedAttr(xlist(), convertToArrayAttr(builder, vlist)); | |
| std::int64_t length = len ? *len : inType.getLen(); | |
| auto lenAttr = mkNamedIntegerAttr(builder, size(), length); | |
| result.addAttributes({valAttr, lenAttr}); | |
| result.addTypes(inType); | |
| } | |
| mlir::ParseResult fir::StringLitOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| auto &builder = parser.getBuilder(); | |
| mlir::Attribute val; | |
| mlir::NamedAttrList attrs; | |
| llvm::SMLoc trailingTypeLoc; | |
| if (parser.parseAttribute(val, "fake", attrs)) | |
| return mlir::failure(); | |
| if (auto v = mlir::dyn_cast<mlir::StringAttr>(val)) | |
| result.attributes.push_back( | |
| builder.getNamedAttr(fir::StringLitOp::value(), v)); | |
| else if (auto v = mlir::dyn_cast<mlir::DenseElementsAttr>(val)) | |
| result.attributes.push_back( | |
| builder.getNamedAttr(fir::StringLitOp::xlist(), v)); | |
| else if (auto v = mlir::dyn_cast<mlir::ArrayAttr>(val)) | |
| result.attributes.push_back( | |
| builder.getNamedAttr(fir::StringLitOp::xlist(), v)); | |
| else | |
| return parser.emitError(parser.getCurrentLocation(), | |
| "found an invalid constant"); | |
| mlir::IntegerAttr sz; | |
| mlir::Type type; | |
| if (parser.parseLParen() || | |
| parser.parseAttribute(sz, fir::StringLitOp::size(), result.attributes) || | |
| parser.parseRParen() || parser.getCurrentLocation(&trailingTypeLoc) || | |
| parser.parseColonType(type)) | |
| return mlir::failure(); | |
| auto charTy = mlir::dyn_cast<fir::CharacterType>(type); | |
| if (!charTy) | |
| return parser.emitError(trailingTypeLoc, "must have character type"); | |
| type = fir::CharacterType::get(builder.getContext(), charTy.getFKind(), | |
| sz.getInt()); | |
| if (!type || parser.addTypesToList(type, result.types)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::StringLitOp::print(mlir::OpAsmPrinter &p) { | |
| p << ' ' << getValue() << '('; | |
| p << mlir::cast<mlir::IntegerAttr>(getSize()).getValue() << ") : "; | |
| p.printType(getType()); | |
| } | |
| llvm::LogicalResult fir::StringLitOp::verify() { | |
| if (mlir::cast<mlir::IntegerAttr>(getSize()).getValue().isNegative()) | |
| return emitOpError("size must be non-negative"); | |
| if (auto xl = getOperation()->getAttr(fir::StringLitOp::xlist())) { | |
| if (auto xList = mlir::dyn_cast<mlir::ArrayAttr>(xl)) { | |
| for (auto a : xList) | |
| if (!mlir::isa<mlir::IntegerAttr>(a)) | |
| return emitOpError("values in initializer must be integers"); | |
| } else if (mlir::isa<mlir::DenseElementsAttr>(xl)) { | |
| // do nothing | |
| } else { | |
| return emitOpError("has unexpected attribute"); | |
| } | |
| } | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // UnboxProcOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::UnboxProcOp::verify() { | |
| if (auto eleTy = fir::dyn_cast_ptrEleTy(getRefTuple().getType())) | |
| if (mlir::isa<mlir::TupleType>(eleTy)) | |
| return mlir::success(); | |
| return emitOpError("second output argument has bad type"); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // IfOp | |
| //===----------------------------------------------------------------------===// | |
| void fir::IfOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::Value cond, bool withElseRegion) { | |
| build(builder, result, {}, cond, withElseRegion); | |
| } | |
| void fir::IfOp::build(mlir::OpBuilder &builder, mlir::OperationState &result, | |
| mlir::TypeRange resultTypes, mlir::Value cond, | |
| bool withElseRegion) { | |
| result.addOperands(cond); | |
| result.addTypes(resultTypes); | |
| mlir::Region *thenRegion = result.addRegion(); | |
| thenRegion->push_back(new mlir::Block()); | |
| if (resultTypes.empty()) | |
| IfOp::ensureTerminator(*thenRegion, builder, result.location); | |
| mlir::Region *elseRegion = result.addRegion(); | |
| if (withElseRegion) { | |
| elseRegion->push_back(new mlir::Block()); | |
| if (resultTypes.empty()) | |
| IfOp::ensureTerminator(*elseRegion, builder, result.location); | |
| } | |
| } | |
| // These 3 functions copied from scf.if implementation. | |
| /// Given the region at `index`, or the parent operation if `index` is None, | |
| /// return the successor regions. These are the regions that may be selected | |
| /// during the flow of control. | |
| void fir::IfOp::getSuccessorRegions( | |
| mlir::RegionBranchPoint point, | |
| llvm::SmallVectorImpl<mlir::RegionSuccessor> ®ions) { | |
| // The `then` and the `else` region branch back to the parent operation. | |
| if (!point.isParent()) { | |
| regions.push_back(mlir::RegionSuccessor(getOperation())); | |
| return; | |
| } | |
| // Don't consider the else region if it is empty. | |
| regions.push_back(mlir::RegionSuccessor(&getThenRegion())); | |
| // Don't consider the else region if it is empty. | |
| mlir::Region *elseRegion = &this->getElseRegion(); | |
| if (elseRegion->empty()) | |
| regions.push_back(mlir::RegionSuccessor(getOperation())); | |
| else | |
| regions.push_back(mlir::RegionSuccessor(elseRegion)); | |
| } | |
| mlir::ValueRange | |
| fir::IfOp::getSuccessorInputs(mlir::RegionSuccessor successor) { | |
| if (successor.isOperation()) | |
| return getOperation()->getResults(); | |
| return mlir::ValueRange(); | |
| } | |
| void fir::IfOp::getEntrySuccessorRegions( | |
| llvm::ArrayRef<mlir::Attribute> operands, | |
| llvm::SmallVectorImpl<mlir::RegionSuccessor> ®ions) { | |
| FoldAdaptor adaptor(operands); | |
| auto boolAttr = | |
| mlir::dyn_cast_or_null<mlir::BoolAttr>(adaptor.getCondition()); | |
| if (!boolAttr || boolAttr.getValue()) | |
| regions.emplace_back(&getThenRegion()); | |
| // If the else region is empty, execution continues after the parent op. | |
| if (!boolAttr || !boolAttr.getValue()) { | |
| if (!getElseRegion().empty()) | |
| regions.emplace_back(&getElseRegion()); | |
| else | |
| regions.push_back(mlir::RegionSuccessor(getOperation())); | |
| } | |
| } | |
| void fir::IfOp::getRegionInvocationBounds( | |
| llvm::ArrayRef<mlir::Attribute> operands, | |
| llvm::SmallVectorImpl<mlir::InvocationBounds> &invocationBounds) { | |
| if (auto cond = mlir::dyn_cast_or_null<mlir::BoolAttr>(operands[0])) { | |
| // If the condition is known, then one region is known to be executed once | |
| // and the other zero times. | |
| invocationBounds.emplace_back(0, cond.getValue() ? 1 : 0); | |
| invocationBounds.emplace_back(0, cond.getValue() ? 0 : 1); | |
| } else { | |
| // Non-constant condition. Each region may be executed 0 or 1 times. | |
| invocationBounds.assign(2, {0, 1}); | |
| } | |
| } | |
| mlir::ParseResult fir::IfOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| result.regions.reserve(2); | |
| mlir::Region *thenRegion = result.addRegion(); | |
| mlir::Region *elseRegion = result.addRegion(); | |
| auto &builder = parser.getBuilder(); | |
| mlir::OpAsmParser::UnresolvedOperand cond; | |
| mlir::Type i1Type = builder.getIntegerType(1); | |
| if (parser.parseOperand(cond) || | |
| parser.resolveOperand(cond, i1Type, result.operands)) | |
| return mlir::failure(); | |
| if (mlir::succeeded( | |
| parser.parseOptionalKeyword(getWeightsAttrAssemblyName()))) { | |
| if (parser.parseLParen()) | |
| return mlir::failure(); | |
| mlir::DenseI32ArrayAttr weights; | |
| if (parser.parseCustomAttributeWithFallback(weights, mlir::Type{})) | |
| return mlir::failure(); | |
| if (weights) | |
| result.addAttribute(getRegionWeightsAttrName(result.name), weights); | |
| if (parser.parseRParen()) | |
| return mlir::failure(); | |
| } | |
| if (parser.parseOptionalArrowTypeList(result.types)) | |
| return mlir::failure(); | |
| if (parser.parseRegion(*thenRegion, {}, {})) | |
| return mlir::failure(); | |
| fir::IfOp::ensureTerminator(*thenRegion, parser.getBuilder(), | |
| result.location); | |
| if (mlir::succeeded(parser.parseOptionalKeyword("else"))) { | |
| if (parser.parseRegion(*elseRegion, {}, {})) | |
| return mlir::failure(); | |
| fir::IfOp::ensureTerminator(*elseRegion, parser.getBuilder(), | |
| result.location); | |
| } | |
| // Parse the optional attribute list. | |
| if (parser.parseOptionalAttrDict(result.attributes)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| llvm::LogicalResult fir::IfOp::verify() { | |
| if (getNumResults() != 0 && getElseRegion().empty()) | |
| return emitOpError("must have an else block if defining values"); | |
| return mlir::success(); | |
| } | |
| void fir::IfOp::print(mlir::OpAsmPrinter &p) { | |
| bool printBlockTerminators = false; | |
| p << ' ' << getCondition(); | |
| if (auto weights = getRegionWeightsAttr()) { | |
| p << ' ' << getWeightsAttrAssemblyName() << '('; | |
| p.printStrippedAttrOrType(weights); | |
| p << ')'; | |
| } | |
| if (!getResults().empty()) { | |
| p << " -> (" << getResultTypes() << ')'; | |
| printBlockTerminators = true; | |
| } | |
| p << ' '; | |
| p.printRegion(getThenRegion(), /*printEntryBlockArgs=*/false, | |
| printBlockTerminators); | |
| // Print the 'else' regions if it exists and has a block. | |
| auto &otherReg = getElseRegion(); | |
| if (!otherReg.empty()) { | |
| p << " else "; | |
| p.printRegion(otherReg, /*printEntryBlockArgs=*/false, | |
| printBlockTerminators); | |
| } | |
| p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue()); | |
| } | |
| void fir::IfOp::resultToSourceOps(llvm::SmallVectorImpl<mlir::Value> &results, | |
| unsigned resultNum) { | |
| auto *term = getThenRegion().front().getTerminator(); | |
| if (resultNum < term->getNumOperands()) | |
| results.push_back(term->getOperand(resultNum)); | |
| term = getElseRegion().front().getTerminator(); | |
| if (resultNum < term->getNumOperands()) | |
| results.push_back(term->getOperand(resultNum)); | |
| } | |
| // Fold away a fir.if that only forwards an optional argument or returns | |
| // fir.absent when it is not present: | |
| // | |
| // %present = fir.is_present %var : (T) -> i1 | |
| // %r = fir.if %present -> (T) { | |
| // fir.result %var : T | |
| // } else { | |
| // %absent = fir.absent T | |
| // fir.result %absent : T | |
| // } | |
| // | |
| // The result is always %var: optional arguments already encode presence. | |
| struct FoldPresentAbsentIfOp : public mlir::OpRewritePattern<fir::IfOp> { | |
| using mlir::OpRewritePattern<fir::IfOp>::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::IfOp ifOp, | |
| mlir::PatternRewriter &rewriter) const override { | |
| if (ifOp.getNumResults() != 1) | |
| return mlir::failure(); | |
| auto isPresentOp = ifOp.getCondition().getDefiningOp<fir::IsPresentOp>(); | |
| if (!isPresentOp) | |
| return mlir::failure(); | |
| mlir::Value optionalVal = isPresentOp.getVal(); | |
| mlir::Type resultType = ifOp.getResult(0).getType(); | |
| if (optionalVal.getType() != resultType) | |
| return mlir::failure(); | |
| mlir::Block &thenBlock = ifOp.getThenRegion().front(); | |
| if (thenBlock.getOperations().size() != 1) | |
| return mlir::failure(); | |
| auto thenResult = mlir::dyn_cast<fir::ResultOp>(thenBlock.getTerminator()); | |
| if (!thenResult || thenResult.getNumOperands() != 1 || | |
| thenResult.getOperand(0) != optionalVal) | |
| return mlir::failure(); | |
| if (ifOp.getElseRegion().empty()) | |
| return mlir::failure(); | |
| mlir::Block &elseBlock = ifOp.getElseRegion().front(); | |
| if (elseBlock.getOperations().size() > 2) | |
| return mlir::failure(); | |
| auto elseResult = mlir::dyn_cast<fir::ResultOp>(elseBlock.getTerminator()); | |
| if (!elseResult || elseResult.getNumOperands() != 1) | |
| return mlir::failure(); | |
| auto absentOp = elseResult.getOperand(0).getDefiningOp<fir::AbsentOp>(); | |
| if (!absentOp) | |
| return mlir::failure(); | |
| if (elseBlock.getOperations().size() == 2 && | |
| absentOp.getOperation() != &elseBlock.front()) | |
| return mlir::failure(); | |
| rewriter.replaceOp(ifOp, optionalVal); | |
| return mlir::success(); | |
| } | |
| }; | |
| void fir::IfOp::getCanonicalizationPatterns(mlir::RewritePatternSet &patterns, | |
| mlir::MLIRContext *context) { | |
| patterns.add<FoldPresentAbsentIfOp>(context); | |
| mlir::populateRegionBranchOpInterfaceInliningPattern( | |
| patterns, fir::IfOp::getOperationName()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxOffsetOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::BoxOffsetOp::verify() { | |
| auto boxType = mlir::dyn_cast_or_null<fir::BaseBoxType>( | |
| fir::dyn_cast_ptrEleTy(getBoxRef().getType())); | |
| mlir::Type boxCharType; | |
| if (!boxType) { | |
| boxCharType = mlir::dyn_cast_or_null<fir::BoxCharType>( | |
| fir::dyn_cast_ptrEleTy(getBoxRef().getType())); | |
| if (!boxCharType) | |
| return emitOpError("box_ref operand must have !fir.ref<!fir.box<T>> or " | |
| "!fir.ref<!fir.boxchar<k>> type"); | |
| if (getField() == fir::BoxFieldAttr::derived_type) | |
| return emitOpError("cannot address derived_type field of a fir.boxchar"); | |
| } | |
| if (getField() != fir::BoxFieldAttr::base_addr && | |
| getField() != fir::BoxFieldAttr::derived_type) | |
| return emitOpError("cannot address provided field"); | |
| if (getField() == fir::BoxFieldAttr::derived_type) { | |
| if (!fir::boxHasAddendum(boxType)) | |
| return emitOpError("can only address derived_type field of derived type " | |
| "or unlimited polymorphic fir.box"); | |
| } | |
| return mlir::success(); | |
| } | |
| void fir::BoxOffsetOp::build(mlir::OpBuilder &builder, | |
| mlir::OperationState &result, mlir::Value boxRef, | |
| fir::BoxFieldAttr field) { | |
| mlir::Type valueType = | |
| fir::unwrapPassByRefType(fir::unwrapRefType(boxRef.getType())); | |
| mlir::Type resultType = valueType; | |
| if (field == fir::BoxFieldAttr::base_addr) | |
| resultType = fir::LLVMPointerType::get(fir::ReferenceType::get(valueType)); | |
| else if (field == fir::BoxFieldAttr::derived_type) | |
| resultType = fir::LLVMPointerType::get( | |
| fir::TypeDescType::get(fir::unwrapSequenceType(valueType))); | |
| build(builder, result, {resultType}, boxRef, field); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| mlir::ParseResult fir::isValidCaseAttr(mlir::Attribute attr) { | |
| if (mlir::isa<mlir::UnitAttr, fir::ClosedIntervalAttr, fir::PointIntervalAttr, | |
| fir::LowerBoundAttr, fir::UpperBoundAttr>(attr)) | |
| return mlir::success(); | |
| return mlir::failure(); | |
| } | |
| unsigned fir::getCaseArgumentOffset(llvm::ArrayRef<mlir::Attribute> cases, | |
| unsigned dest) { | |
| unsigned o = 0; | |
| for (unsigned i = 0; i < dest; ++i) { | |
| auto &attr = cases[i]; | |
| if (!mlir::dyn_cast_or_null<mlir::UnitAttr>(attr)) { | |
| ++o; | |
| if (mlir::dyn_cast_or_null<fir::ClosedIntervalAttr>(attr)) | |
| ++o; | |
| } | |
| } | |
| return o; | |
| } | |
| mlir::ParseResult | |
| fir::parseSelector(mlir::OpAsmParser &parser, mlir::OperationState &result, | |
| mlir::OpAsmParser::UnresolvedOperand &selector, | |
| mlir::Type &type) { | |
| if (parser.parseOperand(selector) || parser.parseColonType(type) || | |
| parser.resolveOperand(selector, type, result.operands) || | |
| parser.parseLSquare()) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| mlir::func::FuncOp fir::createFuncOp(mlir::Location loc, mlir::ModuleOp module, | |
| llvm::StringRef name, | |
| mlir::FunctionType type, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs, | |
| const mlir::SymbolTable *symbolTable) { | |
| if (symbolTable) | |
| if (auto f = symbolTable->lookup<mlir::func::FuncOp>(name)) { | |
| #ifdef EXPENSIVE_CHECKS | |
| assert(f == module.lookupSymbol<mlir::func::FuncOp>(name) && | |
| "symbolTable and module out of sync"); | |
| #endif | |
| return f; | |
| } | |
| if (auto f = module.lookupSymbol<mlir::func::FuncOp>(name)) | |
| return f; | |
| mlir::OpBuilder modBuilder(module.getBodyRegion()); | |
| modBuilder.setInsertionPointToEnd(module.getBody()); | |
| auto result = mlir::func::FuncOp::create(modBuilder, loc, name, type, attrs); | |
| result.setVisibility(mlir::SymbolTable::Visibility::Private); | |
| return result; | |
| } | |
| fir::GlobalOp fir::createGlobalOp(mlir::Location loc, mlir::ModuleOp module, | |
| llvm::StringRef name, mlir::Type type, | |
| llvm::ArrayRef<mlir::NamedAttribute> attrs, | |
| const mlir::SymbolTable *symbolTable) { | |
| if (symbolTable) | |
| if (auto g = symbolTable->lookup<fir::GlobalOp>(name)) { | |
| #ifdef EXPENSIVE_CHECKS | |
| assert(g == module.lookupSymbol<fir::GlobalOp>(name) && | |
| "symbolTable and module out of sync"); | |
| #endif | |
| return g; | |
| } | |
| if (auto g = module.lookupSymbol<fir::GlobalOp>(name)) | |
| return g; | |
| mlir::OpBuilder modBuilder(module.getBodyRegion()); | |
| auto result = fir::GlobalOp::create(modBuilder, loc, name, type, attrs); | |
| result.setVisibility(mlir::SymbolTable::Visibility::Private); | |
| return result; | |
| } | |
| bool fir::hasHostAssociationArgument(mlir::func::FuncOp func) { | |
| if (auto allArgAttrs = func.getAllArgAttrs()) | |
| for (auto attr : allArgAttrs) | |
| if (auto dict = mlir::dyn_cast_or_null<mlir::DictionaryAttr>(attr)) | |
| if (dict.get(fir::getHostAssocAttrName())) | |
| return true; | |
| return false; | |
| } | |
| // Test if value's definition has the specified set of | |
| // attributeNames. The value's definition is one of the operations | |
| // that are able to carry the Fortran variable attributes, e.g. | |
| // fir.alloca or fir.allocmem. Function arguments may also represent | |
| // value definitions and carry relevant attributes. | |
| // | |
| // If it is not possible to reach the limited set of definition | |
| // entities from the given value, then the function will return | |
| // std::nullopt. Otherwise, the definition is known and the return | |
| // value is computed as: | |
| // * if checkAny is true, then the function will return true | |
| // iff any of the attributeNames attributes is set on the definition. | |
| // * if checkAny is false, then the function will return true | |
| // iff all of the attributeNames attributes are set on the definition. | |
| static std::optional<bool> | |
| valueCheckFirAttributes(mlir::Value value, | |
| llvm::ArrayRef<llvm::StringRef> attributeNames, | |
| bool checkAny) { | |
| auto testAttributeSets = [&](llvm::ArrayRef<mlir::NamedAttribute> setAttrs, | |
| llvm::ArrayRef<llvm::StringRef> checkAttrs) { | |
| if (checkAny) { | |
| // Return true iff any of checkAttrs attributes is present | |
| // in setAttrs set. | |
| for (llvm::StringRef checkAttrName : checkAttrs) | |
| if (llvm::any_of(setAttrs, [&](mlir::NamedAttribute setAttr) { | |
| return setAttr.getName() == checkAttrName; | |
| })) | |
| return true; | |
| return false; | |
| } | |
| // Return true iff all attributes from checkAttrs are present | |
| // in setAttrs set. | |
| for (mlir::StringRef checkAttrName : checkAttrs) | |
| if (llvm::none_of(setAttrs, [&](mlir::NamedAttribute setAttr) { | |
| return setAttr.getName() == checkAttrName; | |
| })) | |
| return false; | |
| return true; | |
| }; | |
| auto testOperationAttributes = [&](mlir::Operation *op) { | |
| auto hasAttribute = [&](llvm::StringRef name) { | |
| return op->hasDiscardableAttr(name); | |
| }; | |
| if (checkAny) | |
| return llvm::any_of(attributeNames, hasAttribute); | |
| return llvm::all_of(attributeNames, hasAttribute); | |
| }; | |
| // If this is a fir.box that was loaded, the fir attributes will be on the | |
| // related fir.ref<fir.box> creation. | |
| if (mlir::isa<fir::BoxType>(value.getType())) | |
| if (auto definingOp = value.getDefiningOp()) | |
| if (auto loadOp = mlir::dyn_cast<fir::LoadOp>(definingOp)) | |
| value = loadOp.getMemref(); | |
| // If this is a function argument, look in the argument attributes. | |
| if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(value)) { | |
| if (blockArg.getOwner() && blockArg.getOwner()->isEntryBlock()) | |
| if (auto funcOp = mlir::dyn_cast<mlir::func::FuncOp>( | |
| blockArg.getOwner()->getParentOp())) | |
| return testAttributeSets( | |
| mlir::cast<mlir::FunctionOpInterface>(*funcOp).getArgAttrs( | |
| blockArg.getArgNumber()), | |
| attributeNames); | |
| // If it is not a function argument, the attributes are unknown. | |
| return std::nullopt; | |
| } | |
| if (auto definingOp = value.getDefiningOp()) { | |
| // If this is an allocated value, look at the allocation attributes. | |
| if (mlir::isa<fir::AllocMemOp>(definingOp) || | |
| mlir::isa<fir::AllocaOp>(definingOp)) | |
| return testOperationAttributes(definingOp); | |
| // If this is an imported global, look at AddrOfOp and GlobalOp attributes. | |
| // Both operations are looked at because use/host associated variable (the | |
| // AddrOfOp) can have ASYNCHRONOUS/VOLATILE attributes even if the ultimate | |
| // entity (the globalOp) does not have them. | |
| if (auto addressOfOp = mlir::dyn_cast<fir::AddrOfOp>(definingOp)) { | |
| if (testOperationAttributes(addressOfOp)) | |
| return true; | |
| if (auto module = definingOp->getParentOfType<mlir::ModuleOp>()) | |
| if (auto globalOp = | |
| module.lookupSymbol<fir::GlobalOp>(addressOfOp.getSymbol())) | |
| return testOperationAttributes(globalOp); | |
| } | |
| } | |
| // TODO: Construct associated entities attributes. Decide where the fir | |
| // attributes must be placed/looked for in this case. | |
| return std::nullopt; | |
| } | |
| bool fir::valueMayHaveFirAttributes( | |
| mlir::Value value, llvm::ArrayRef<llvm::StringRef> attributeNames) { | |
| std::optional<bool> mayHaveAttr = | |
| valueCheckFirAttributes(value, attributeNames, /*checkAny=*/true); | |
| return mayHaveAttr.value_or(true); | |
| } | |
| bool fir::valueHasFirAttribute(mlir::Value value, | |
| llvm::StringRef attributeName) { | |
| std::optional<bool> mayHaveAttr = | |
| valueCheckFirAttributes(value, {attributeName}, /*checkAny=*/false); | |
| return mayHaveAttr.value_or(false); | |
| } | |
| bool fir::anyFuncArgsHaveAttr(mlir::func::FuncOp func, llvm::StringRef attr) { | |
| for (unsigned i = 0, end = func.getNumArguments(); i < end; ++i) | |
| if (func.getArgAttr(i, attr)) | |
| return true; | |
| return false; | |
| } | |
| std::optional<llvm::APInt> fir::getIntIfConstant(mlir::Value value) { | |
| if (auto *definingOp = value.getDefiningOp()) { | |
| if (auto cst = mlir::dyn_cast<mlir::arith::ConstantOp>(definingOp)) | |
| if (auto intAttr = mlir::dyn_cast<mlir::IntegerAttr>(cst.getValue())) | |
| return intAttr.getValue(); | |
| if (auto llConstOp = mlir::dyn_cast<mlir::LLVM::ConstantOp>(definingOp)) | |
| if (auto attr = mlir::dyn_cast<mlir::IntegerAttr>(llConstOp.getValue())) | |
| return attr.getValue(); | |
| } | |
| return {}; | |
| } | |
| bool fir::isDummyArgument(mlir::Value v) { | |
| auto blockArg{mlir::dyn_cast<mlir::BlockArgument>(v)}; | |
| if (!blockArg) { | |
| auto defOp = v.getDefiningOp(); | |
| if (defOp) { | |
| if (auto declareOp = mlir::dyn_cast<fir::DeclareOp>(defOp)) | |
| if (declareOp.getDummyScope()) | |
| return true; | |
| } | |
| return false; | |
| } | |
| auto *owner{blockArg.getOwner()}; | |
| return owner->isEntryBlock() && | |
| mlir::isa<mlir::FunctionOpInterface>(owner->getParentOp()); | |
| } | |
| mlir::Type fir::applyPathToType(mlir::Type eleTy, mlir::ValueRange path) { | |
| for (auto i = path.begin(), end = path.end(); eleTy && i < end;) { | |
| eleTy = llvm::TypeSwitch<mlir::Type, mlir::Type>(eleTy) | |
| .Case([&](fir::RecordType ty) { | |
| if (auto *op = (*i++).getDefiningOp()) { | |
| if (auto off = mlir::dyn_cast<fir::FieldIndexOp>(op)) | |
| return ty.getType(off.getFieldName()); | |
| if (auto off = mlir::dyn_cast<mlir::arith::ConstantOp>(op)) | |
| return ty.getType(fir::toInt(off)); | |
| } | |
| return mlir::Type{}; | |
| }) | |
| .Case([&](fir::SequenceType ty) { | |
| bool valid = true; | |
| const auto rank = ty.getDimension(); | |
| for (std::remove_const_t<decltype(rank)> ii = 0; | |
| valid && ii < rank; ++ii) | |
| valid = i < end && fir::isa_integer((*i++).getType()); | |
| return valid ? ty.getEleTy() : mlir::Type{}; | |
| }) | |
| .Case([&](mlir::TupleType ty) { | |
| if (auto *op = (*i++).getDefiningOp()) | |
| if (auto off = mlir::dyn_cast<mlir::arith::ConstantOp>(op)) | |
| return ty.getType(fir::toInt(off)); | |
| return mlir::Type{}; | |
| }) | |
| .Case([&](mlir::ComplexType ty) { | |
| if (fir::isa_integer((*i++).getType())) | |
| return ty.getElementType(); | |
| return mlir::Type{}; | |
| }) | |
| .Default([&](const auto &) { return mlir::Type{}; }); | |
| } | |
| return eleTy; | |
| } | |
| bool fir::reboxPreservesContinuity(fir::ReboxOp rebox, | |
| bool mayHaveNonDefaultLowerBounds, | |
| bool checkWhole) { | |
| // If slicing is not involved, then the rebox does not affect | |
| // the continuity of the array. | |
| auto sliceArg = rebox.getSlice(); | |
| if (!sliceArg) | |
| return true; | |
| if (auto sliceOp = | |
| mlir::dyn_cast_or_null<fir::SliceOp>(sliceArg.getDefiningOp())) | |
| return isContiguousArraySlice(sliceOp, checkWhole, rebox.getBox(), | |
| mayHaveNonDefaultLowerBounds); | |
| return false; | |
| } | |
| std::optional<int64_t> fir::getAllocaByteSize(fir::AllocaOp alloca, | |
| const mlir::DataLayout &dl, | |
| const fir::KindMapping &kindMap) { | |
| mlir::Type type = alloca.getInType(); | |
| // TODO: should use the constant operands when all info is not available in | |
| // the type. | |
| if (!alloca.isDynamic()) | |
| if (auto sizeAndAlignment = | |
| getTypeSizeAndAlignment(alloca.getLoc(), type, dl, kindMap)) | |
| return sizeAndAlignment->first; | |
| return std::nullopt; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DeclareValueOp | |
| //===----------------------------------------------------------------------===// | |
| static bool isLegalTypeForValueDeclare(mlir::Type type) { | |
| return mlir::isa<mlir::IntegerType, mlir::FloatType, mlir::ComplexType, | |
| fir::LogicalType>(type); | |
| } | |
| llvm::LogicalResult fir::DeclareValueOp::verify() { | |
| if (!isLegalTypeForValueDeclare(getValue().getType())) | |
| return emitOpError( | |
| "value must be a simple scalar (integer, real, complex, or logical)"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DeclareOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::DeclareOp::verify() { | |
| auto fortranVar = | |
| mlir::cast<fir::FortranVariableOpInterface>(this->getOperation()); | |
| return fortranVar.verifyDeclareLikeOpImpl(getMemref()); | |
| } | |
| /// Return the pointee of the storage a fir.declare denotes, or null if that | |
| /// storage must not be promoted. | |
| static mlir::Type getPromotablePointeeOfDeclare(fir::DeclareOp op) { | |
| // Accesses through a volatile reference have to be preserved. | |
| if (fir::isa_volatile_type(op.getType())) | |
| return {}; | |
| mlir::Type pointee = fir::unwrapRefType(op.getType()); | |
| if (!isLegalTypeForValueDeclare(pointee)) | |
| return {}; | |
| // Values are not converted between the slot and its alias, so the pointee has | |
| // to be the same on both sides. | |
| if (fir::unwrapRefType(op.getMemref().getType()) != pointee) | |
| return {}; | |
| return pointee; | |
| } | |
| void fir::DeclareOp::getPromotableSlotAliases( | |
| mlir::OpOperand &aliasedSlotPointerOperand, | |
| const mlir::MemorySlot &parentSlot, | |
| llvm::SmallVectorImpl<mlir::MemorySlot> &newMemorySlots) { | |
| // fir.declare only attaches source information; the storage is the same. | |
| mlir::Type pointee = getPromotablePointeeOfDeclare(*this); | |
| if (pointee && isAliasableSlotPointee(pointee)) | |
| newMemorySlots.push_back({getResult(), pointee}); | |
| } | |
| bool fir::DeclareOp::canUsesBeRemoved( | |
| const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses, | |
| const mlir::DataLayout &dataLayout) { | |
| mlir::Type pointee = getPromotablePointeeOfDeclare(*this); | |
| if (!pointee) | |
| return false; | |
| // Without an alias, mem2reg does not see writes made through this declare as | |
| // definitions, so uses have to stay in one block for no block argument to be | |
| // needed. | |
| bool aliased = isAliasableSlotPointee(pointee); | |
| mlir::Block *declBlock = getOperation()->getBlock(); | |
| for (mlir::OpOperand &use : getResult().getUses()) { | |
| if (!aliased && use.getOwner()->getBlock() != declBlock) | |
| return false; | |
| newBlockingUses.push_back(&use); | |
| } | |
| return true; | |
| } | |
| mlir::DeletionKind fir::DeclareOp::removeBlockingUses( | |
| const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, | |
| mlir::OpBuilder &builder) { | |
| return mlir::DeletionKind::Delete; | |
| } | |
| bool fir::DeclareOp::requiresReplacedValues() { return true; } | |
| void fir::DeclareOp::visitReplacedValues( | |
| llvm::ArrayRef<std::pair<mlir::Operation *, mlir::Value>> definitions, | |
| mlir::OpBuilder &builder) { | |
| for (auto [op, value] : definitions) { | |
| // Do not emit DeclareValue when we have a dummy scope as this can | |
| // potentially result in us generating it where the DummyScope does not | |
| // dominate it. This can happen after inlining. | |
| if (getDummyScope()) | |
| continue; | |
| builder.setInsertionPointAfter(op); | |
| fir::DeclareValueOp::create(builder, getLoc(), value, nullptr, | |
| getUniqNameAttr(), getFortranAttrsAttr(), | |
| getDataAttrAttr(), getDummyArgNoAttr()); | |
| } | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // PackArrayOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::PackArrayOp::verify() { | |
| mlir::Type arrayType = getArray().getType(); | |
| if (!validTypeParams(arrayType, getTypeparams(), /*allowParamsForBox=*/true)) | |
| return emitOpError("invalid type parameters"); | |
| if (getInnermost() && fir::getBoxRank(arrayType) == 1) | |
| return emitOpError( | |
| "'innermost' is invalid for 1D arrays, use 'whole' instead"); | |
| return mlir::success(); | |
| } | |
| void fir::PackArrayOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| if (getStack()) | |
| effects.emplace_back( | |
| mlir::MemoryEffects::Allocate::get(), | |
| mlir::SideEffects::AutomaticAllocationScopeResource::get()); | |
| else | |
| effects.emplace_back(mlir::MemoryEffects::Allocate::get(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| if (!getNoCopy()) | |
| effects.emplace_back(mlir::MemoryEffects::Read::get(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| } | |
| static mlir::ParseResult | |
| parsePackArrayConstraints(mlir::OpAsmParser &parser, mlir::IntegerAttr &maxSize, | |
| mlir::IntegerAttr &maxElementSize, | |
| mlir::IntegerAttr &minStride) { | |
| mlir::OperationName opName = mlir::OperationName( | |
| fir::PackArrayOp::getOperationName(), parser.getContext()); | |
| struct { | |
| llvm::StringRef name; | |
| mlir::IntegerAttr &ref; | |
| } attributes[] = { | |
| {fir::PackArrayOp::getMaxSizeAttrName(opName), maxSize}, | |
| {fir::PackArrayOp::getMaxElementSizeAttrName(opName), maxElementSize}, | |
| {fir::PackArrayOp::getMinStrideAttrName(opName), minStride}}; | |
| mlir::NamedAttrList parsedAttrs; | |
| if (succeeded(parser.parseOptionalAttrDict(parsedAttrs))) { | |
| for (auto parsedAttr : parsedAttrs) { | |
| for (auto opAttr : attributes) { | |
| if (parsedAttr.getName() == opAttr.name) | |
| opAttr.ref = mlir::cast<mlir::IntegerAttr>(parsedAttr.getValue()); | |
| } | |
| } | |
| return mlir::success(); | |
| } | |
| return mlir::failure(); | |
| } | |
| static void printPackArrayConstraints(mlir::OpAsmPrinter &p, | |
| fir::PackArrayOp &op, | |
| const mlir::IntegerAttr &maxSize, | |
| const mlir::IntegerAttr &maxElementSize, | |
| const mlir::IntegerAttr &minStride) { | |
| llvm::SmallVector<mlir::NamedAttribute> attributes; | |
| if (maxSize) | |
| attributes.emplace_back(op.getMaxSizeAttrName(), maxSize); | |
| if (maxElementSize) | |
| attributes.emplace_back(op.getMaxElementSizeAttrName(), maxElementSize); | |
| if (minStride) | |
| attributes.emplace_back(op.getMinStrideAttrName(), minStride); | |
| p.printOptionalAttrDict(attributes); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // UnpackArrayOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::UnpackArrayOp::verify() { | |
| if (auto packOp = getTemp().getDefiningOp<fir::PackArrayOp>()) | |
| if (getStack() != packOp.getStack()) | |
| return emitOpError() << "the pack operation uses different memory for " | |
| "the temporary (stack vs heap): " | |
| << *packOp.getOperation() << "\n"; | |
| return mlir::success(); | |
| } | |
| void fir::UnpackArrayOp::getEffects( | |
| llvm::SmallVectorImpl< | |
| mlir::SideEffects::EffectInstance<mlir::MemoryEffects::Effect>> | |
| &effects) { | |
| if (getStack()) | |
| effects.emplace_back( | |
| mlir::MemoryEffects::Free::get(), | |
| mlir::SideEffects::AutomaticAllocationScopeResource::get()); | |
| else | |
| effects.emplace_back(mlir::MemoryEffects::Free::get(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| if (!getNoCopy()) | |
| effects.emplace_back(mlir::MemoryEffects::Write::get(), | |
| mlir::SideEffects::DefaultResource::get()); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // IsContiguousBoxOp | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct SimplifyIsContiguousBoxOp | |
| : public mlir::OpRewritePattern<fir::IsContiguousBoxOp> { | |
| using mlir::OpRewritePattern<fir::IsContiguousBoxOp>::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::IsContiguousBoxOp op, | |
| mlir::PatternRewriter &rewriter) const override; | |
| }; | |
| } // namespace | |
| mlir::LogicalResult SimplifyIsContiguousBoxOp::matchAndRewrite( | |
| fir::IsContiguousBoxOp op, mlir::PatternRewriter &rewriter) const { | |
| auto boxType = mlir::cast<fir::BaseBoxType>(op.getBox().getType()); | |
| // Nothing to do for assumed-rank arrays and !fir.box<none>. | |
| if (boxType.isAssumedRank() || fir::isBoxNone(boxType)) | |
| return mlir::failure(); | |
| if (fir::getBoxRank(boxType) == 0) { | |
| // Scalars are always contiguous. | |
| mlir::Type i1Type = rewriter.getI1Type(); | |
| rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>( | |
| op, i1Type, rewriter.getIntegerAttr(i1Type, 1)); | |
| return mlir::success(); | |
| } | |
| // TODO: support more patterns, e.g. a result of fir.embox without | |
| // the slice is contiguous. We can add fir::isSimplyContiguous(box) | |
| // that walks def-use to figure it out. | |
| return mlir::failure(); | |
| } | |
| void fir::IsContiguousBoxOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &patterns, mlir::MLIRContext *context) { | |
| patterns.add<SimplifyIsContiguousBoxOp>(context); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // BoxTotalElementsOp | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct SimplifyBoxTotalElementsOp | |
| : public mlir::OpRewritePattern<fir::BoxTotalElementsOp> { | |
| using mlir::OpRewritePattern<fir::BoxTotalElementsOp>::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::BoxTotalElementsOp op, | |
| mlir::PatternRewriter &rewriter) const override; | |
| }; | |
| } // namespace | |
| mlir::LogicalResult SimplifyBoxTotalElementsOp::matchAndRewrite( | |
| fir::BoxTotalElementsOp op, mlir::PatternRewriter &rewriter) const { | |
| auto boxType = mlir::cast<fir::BaseBoxType>(op.getBox().getType()); | |
| // Nothing to do for assumed-rank arrays and !fir.box<none>. | |
| if (boxType.isAssumedRank() || fir::isBoxNone(boxType)) | |
| return mlir::failure(); | |
| if (fir::getBoxRank(boxType) == 0) { | |
| // Scalar: 1 element. | |
| rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>( | |
| op, op.getType(), rewriter.getIntegerAttr(op.getType(), 1)); | |
| return mlir::success(); | |
| } | |
| // TODO: support more cases, e.g. !fir.box<!fir.array<10xi32>>. | |
| return mlir::failure(); | |
| } | |
| void fir::BoxTotalElementsOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &patterns, mlir::MLIRContext *context) { | |
| patterns.add<SimplifyBoxTotalElementsOp>(context); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // IsAssumedSizeExtentOp and AssumedSizeExtentOp | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| struct FoldIsAssumedSizeExtentOnCtor | |
| : public mlir::OpRewritePattern<fir::IsAssumedSizeExtentOp> { | |
| using mlir::OpRewritePattern<fir::IsAssumedSizeExtentOp>::OpRewritePattern; | |
| mlir::LogicalResult | |
| matchAndRewrite(fir::IsAssumedSizeExtentOp op, | |
| mlir::PatternRewriter &rewriter) const override { | |
| if (llvm::isa_and_nonnull<fir::AssumedSizeExtentOp>( | |
| op.getVal().getDefiningOp())) { | |
| mlir::Type i1 = rewriter.getI1Type(); | |
| rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>( | |
| op, i1, rewriter.getIntegerAttr(i1, 1)); | |
| return mlir::success(); | |
| } | |
| return mlir::failure(); | |
| } | |
| }; | |
| } // namespace | |
| void fir::IsAssumedSizeExtentOp::getCanonicalizationPatterns( | |
| mlir::RewritePatternSet &patterns, mlir::MLIRContext *context) { | |
| patterns.add<FoldIsAssumedSizeExtentOnCtor>(context); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // LocalitySpecifierOp | |
| //===----------------------------------------------------------------------===// | |
| // TODO This is a copy of omp::PrivateClauseOp::verifiyRegions(). Once we find a | |
| // solution to merge both ops into one this duplication will not be needed. See: | |
| // https://discourse.llvm.org/t/dialect-for-data-locality-sharing-specifiers-clauses-in-openmp-openacc-and-do-concurrent/86108. | |
| llvm::LogicalResult fir::LocalitySpecifierOp::verifyRegions() { | |
| mlir::Type argType = getArgType(); | |
| auto verifyTerminator = [&](mlir::Operation *terminator, | |
| bool yieldsValue) -> llvm::LogicalResult { | |
| if (!terminator->getBlock()->getSuccessors().empty()) | |
| return llvm::success(); | |
| if (!llvm::isa<fir::YieldOp>(terminator)) | |
| return mlir::emitError(terminator->getLoc()) | |
| << "expected exit block terminator to be an `fir.yield` op."; | |
| YieldOp yieldOp = llvm::cast<YieldOp>(terminator); | |
| mlir::TypeRange yieldedTypes = yieldOp.getResults().getTypes(); | |
| if (!yieldsValue) { | |
| if (yieldedTypes.empty()) | |
| return llvm::success(); | |
| return mlir::emitError(terminator->getLoc()) | |
| << "Did not expect any values to be yielded."; | |
| } | |
| if (yieldedTypes.size() == 1 && yieldedTypes.front() == argType) | |
| return llvm::success(); | |
| auto error = mlir::emitError(yieldOp.getLoc()) | |
| << "Invalid yielded value. Expected type: " << argType | |
| << ", got: "; | |
| if (yieldedTypes.empty()) | |
| error << "None"; | |
| else | |
| error << yieldedTypes; | |
| return error; | |
| }; | |
| auto verifyRegion = [&](mlir::Region ®ion, unsigned expectedNumArgs, | |
| llvm::StringRef regionName, | |
| bool yieldsValue) -> llvm::LogicalResult { | |
| assert(!region.empty()); | |
| if (region.getNumArguments() != expectedNumArgs) | |
| return mlir::emitError(region.getLoc()) | |
| << "`" << regionName << "`: " | |
| << "expected " << expectedNumArgs | |
| << " region arguments, got: " << region.getNumArguments(); | |
| for (mlir::Block &block : region) { | |
| // MLIR will verify the absence of the terminator for us. | |
| if (!block.mightHaveTerminator()) | |
| continue; | |
| if (failed(verifyTerminator(block.getTerminator(), yieldsValue))) | |
| return llvm::failure(); | |
| } | |
| return llvm::success(); | |
| }; | |
| // Ensure all of the region arguments have the same type | |
| for (mlir::Region *region : getRegions()) | |
| for (mlir::Type ty : region->getArgumentTypes()) | |
| if (ty != argType) | |
| return emitError() << "Region argument type mismatch: got " << ty | |
| << " expected " << argType << "."; | |
| mlir::Region &initRegion = getInitRegion(); | |
| if (!initRegion.empty() && | |
| failed(verifyRegion(getInitRegion(), /*expectedNumArgs=*/2, "init", | |
| /*yieldsValue=*/true))) | |
| return llvm::failure(); | |
| LocalitySpecifierType dsType = getLocalitySpecifierType(); | |
| if (dsType == LocalitySpecifierType::Local && !getCopyRegion().empty()) | |
| return emitError("`local` specifiers do not require a `copy` region."); | |
| if (dsType == LocalitySpecifierType::LocalInit && getCopyRegion().empty()) | |
| return emitError( | |
| "`local_init` specifiers require at least a `copy` region."); | |
| if (dsType == LocalitySpecifierType::LocalInit && | |
| failed(verifyRegion(getCopyRegion(), /*expectedNumArgs=*/2, "copy", | |
| /*yieldsValue=*/true))) | |
| return llvm::failure(); | |
| if (!getDeallocRegion().empty() && | |
| failed(verifyRegion(getDeallocRegion(), /*expectedNumArgs=*/1, "dealloc", | |
| /*yieldsValue=*/false))) | |
| return llvm::failure(); | |
| return llvm::success(); | |
| } | |
| // TODO This is a copy of omp::DeclareReductionOp::verifiyRegions(). Once we | |
| // find a solution to merge both ops into one this duplication will not be | |
| // needed. | |
| mlir::LogicalResult fir::DeclareReductionOp::verifyRegions() { | |
| if (!getAllocRegion().empty()) { | |
| for (YieldOp yieldOp : getAllocRegion().getOps<YieldOp>()) { | |
| if (yieldOp.getResults().size() != 1 || | |
| yieldOp.getResults().getTypes()[0] != getType()) | |
| return emitOpError() << "expects alloc region to yield a value " | |
| "of the reduction type"; | |
| } | |
| } | |
| if (getInitializerRegion().empty()) | |
| return emitOpError() << "expects non-empty initializer region"; | |
| mlir::Block &initializerEntryBlock = getInitializerRegion().front(); | |
| if (initializerEntryBlock.getNumArguments() == 1) { | |
| if (!getAllocRegion().empty()) | |
| return emitOpError() << "expects two arguments to the initializer region " | |
| "when an allocation region is used"; | |
| } else if (initializerEntryBlock.getNumArguments() == 2) { | |
| if (getAllocRegion().empty()) | |
| return emitOpError() << "expects one argument to the initializer region " | |
| "when no allocation region is used"; | |
| } else { | |
| return emitOpError() | |
| << "expects one or two arguments to the initializer region"; | |
| } | |
| for (mlir::Value arg : initializerEntryBlock.getArguments()) | |
| if (arg.getType() != getType()) | |
| return emitOpError() << "expects initializer region argument to match " | |
| "the reduction type"; | |
| for (YieldOp yieldOp : getInitializerRegion().getOps<YieldOp>()) { | |
| if (yieldOp.getResults().size() != 1 || | |
| yieldOp.getResults().getTypes()[0] != getType()) | |
| return emitOpError() << "expects initializer region to yield a value " | |
| "of the reduction type"; | |
| } | |
| if (getReductionRegion().empty()) | |
| return emitOpError() << "expects non-empty reduction region"; | |
| mlir::Block &reductionEntryBlock = getReductionRegion().front(); | |
| if (reductionEntryBlock.getNumArguments() != 2 || | |
| reductionEntryBlock.getArgumentTypes()[0] != | |
| reductionEntryBlock.getArgumentTypes()[1] || | |
| reductionEntryBlock.getArgumentTypes()[0] != getType()) | |
| return emitOpError() << "expects reduction region with two arguments of " | |
| "the reduction type"; | |
| for (YieldOp yieldOp : getReductionRegion().getOps<YieldOp>()) { | |
| if (yieldOp.getResults().size() != 1 || | |
| yieldOp.getResults().getTypes()[0] != getType()) | |
| return emitOpError() << "expects reduction region to yield a value " | |
| "of the reduction type"; | |
| } | |
| if (!getAtomicReductionRegion().empty()) { | |
| mlir::Block &atomicReductionEntryBlock = getAtomicReductionRegion().front(); | |
| if (atomicReductionEntryBlock.getNumArguments() != 2 || | |
| atomicReductionEntryBlock.getArgumentTypes()[0] != | |
| atomicReductionEntryBlock.getArgumentTypes()[1]) | |
| return emitOpError() << "expects atomic reduction region with two " | |
| "arguments of the same type"; | |
| } | |
| if (getCleanupRegion().empty()) | |
| return mlir::success(); | |
| mlir::Block &cleanupEntryBlock = getCleanupRegion().front(); | |
| if (cleanupEntryBlock.getNumArguments() != 1 || | |
| cleanupEntryBlock.getArgument(0).getType() != getType()) | |
| return emitOpError() << "expects cleanup region with one argument " | |
| "of the reduction type"; | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DoConcurrentOp | |
| //===----------------------------------------------------------------------===// | |
| llvm::LogicalResult fir::DoConcurrentOp::verify() { | |
| mlir::Block *body = getBody(); | |
| if (body->empty()) | |
| return emitOpError("body cannot be empty"); | |
| if (!body->mightHaveTerminator() || | |
| !mlir::isa<fir::DoConcurrentLoopOp>(body->getTerminator())) | |
| return emitOpError("must be terminated by 'fir.do_concurrent.loop'"); | |
| return mlir::success(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // DoConcurrentLoopOp | |
| //===----------------------------------------------------------------------===// | |
| static mlir::ParseResult parseSpecifierList( | |
| mlir::OpAsmParser &parser, mlir::OperationState &result, | |
| llvm::StringRef specifierKeyword, llvm::StringRef symsAttrName, | |
| llvm::SmallVectorImpl<mlir::OpAsmParser::Argument> ®ionArgs, | |
| llvm::SmallVectorImpl<mlir::Type> ®ionArgTypes, | |
| int32_t &numSpecifierOperands, bool isReduce = false) { | |
| auto &builder = parser.getBuilder(); | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand> specifierOperands; | |
| if (failed(parser.parseOptionalKeyword(specifierKeyword))) | |
| return mlir::success(); | |
| std::size_t oldArgTypesSize = regionArgTypes.size(); | |
| if (failed(parser.parseLParen())) | |
| return mlir::failure(); | |
| llvm::SmallVector<bool> isByRefVec; | |
| llvm::SmallVector<mlir::SymbolRefAttr> spceifierSymbolVec; | |
| llvm::SmallVector<fir::ReduceAttr> attributes; | |
| if (failed(parser.parseCommaSeparatedList([&]() { | |
| if (isReduce) | |
| isByRefVec.push_back( | |
| parser.parseOptionalKeyword("byref").succeeded()); | |
| if (failed(parser.parseAttribute(spceifierSymbolVec.emplace_back()))) | |
| return mlir::failure(); | |
| if (isReduce && | |
| failed(parser.parseAttribute(attributes.emplace_back()))) | |
| return mlir::failure(); | |
| if (parser.parseOperand(specifierOperands.emplace_back()) || | |
| parser.parseArrow() || | |
| parser.parseArgument(regionArgs.emplace_back())) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| }))) | |
| return mlir::failure(); | |
| if (failed(parser.parseColon())) | |
| return mlir::failure(); | |
| if (failed(parser.parseCommaSeparatedList([&]() { | |
| if (failed(parser.parseType(regionArgTypes.emplace_back()))) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| }))) | |
| return mlir::failure(); | |
| if (regionArgs.size() != regionArgTypes.size()) | |
| return parser.emitError(parser.getNameLoc(), "mismatch in number of " + | |
| specifierKeyword.str() + | |
| " arg and types"); | |
| if (failed(parser.parseRParen())) | |
| return mlir::failure(); | |
| for (auto operandType : | |
| llvm::zip_equal(specifierOperands, | |
| llvm::drop_begin(regionArgTypes, oldArgTypesSize))) | |
| if (parser.resolveOperand(std::get<0>(operandType), | |
| std::get<1>(operandType), result.operands)) | |
| return mlir::failure(); | |
| if (isReduce) | |
| result.addAttribute( | |
| fir::DoConcurrentLoopOp::getReduceByrefAttrName(result.name), | |
| isByRefVec.empty() | |
| ? nullptr | |
| : mlir::DenseBoolArrayAttr::get(builder.getContext(), isByRefVec)); | |
| llvm::SmallVector<mlir::Attribute> symbolAttrs(spceifierSymbolVec.begin(), | |
| spceifierSymbolVec.end()); | |
| result.addAttribute(symsAttrName, builder.getArrayAttr(symbolAttrs)); | |
| if (isReduce) { | |
| llvm::SmallVector<mlir::Attribute> arrayAttr(attributes.begin(), | |
| attributes.end()); | |
| result.addAttribute( | |
| fir::DoConcurrentLoopOp::getReduceAttrsAttrName(result.name), | |
| builder.getArrayAttr(arrayAttr)); | |
| } | |
| numSpecifierOperands = specifierOperands.size(); | |
| return mlir::success(); | |
| } | |
| mlir::ParseResult fir::DoConcurrentLoopOp::parse(mlir::OpAsmParser &parser, | |
| mlir::OperationState &result) { | |
| auto &builder = parser.getBuilder(); | |
| // Parse an opening `(` followed by induction variables followed by `)` | |
| llvm::SmallVector<mlir::OpAsmParser::Argument, 4> regionArgs; | |
| if (parser.parseArgumentList(regionArgs, mlir::OpAsmParser::Delimiter::Paren)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::Type> argTypes(regionArgs.size(), | |
| builder.getIndexType()); | |
| // Parse loop bounds. | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand, 4> lower; | |
| if (parser.parseEqual() || | |
| parser.parseOperandList(lower, regionArgs.size(), | |
| mlir::OpAsmParser::Delimiter::Paren) || | |
| parser.resolveOperands(lower, builder.getIndexType(), result.operands)) | |
| return mlir::failure(); | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand, 4> upper; | |
| if (parser.parseKeyword("to") || | |
| parser.parseOperandList(upper, regionArgs.size(), | |
| mlir::OpAsmParser::Delimiter::Paren) || | |
| parser.resolveOperands(upper, builder.getIndexType(), result.operands)) | |
| return mlir::failure(); | |
| // Parse step values. | |
| llvm::SmallVector<mlir::OpAsmParser::UnresolvedOperand, 4> steps; | |
| if (parser.parseKeyword("step") || | |
| parser.parseOperandList(steps, regionArgs.size(), | |
| mlir::OpAsmParser::Delimiter::Paren) || | |
| parser.resolveOperands(steps, builder.getIndexType(), result.operands)) | |
| return mlir::failure(); | |
| int32_t numLocalOperands = 0; | |
| if (failed(parseSpecifierList(parser, result, "local", | |
| getLocalSymsAttrName(result.name), regionArgs, | |
| argTypes, numLocalOperands))) | |
| return mlir::failure(); | |
| int32_t numReduceOperands = 0; | |
| if (failed(parseSpecifierList( | |
| parser, result, "reduce", getReduceSymsAttrName(result.name), | |
| regionArgs, argTypes, numReduceOperands, /*isReduce=*/true))) | |
| return mlir::failure(); | |
| // Set `operandSegmentSizes` attribute. | |
| result.addAttribute( | |
| DoConcurrentLoopOp::getOperandSegmentSizeAttr(), | |
| builder.getDenseI32ArrayAttr({static_cast<int32_t>(lower.size()), | |
| static_cast<int32_t>(upper.size()), | |
| static_cast<int32_t>(steps.size()), | |
| static_cast<int32_t>(numLocalOperands), | |
| static_cast<int32_t>(numReduceOperands)})); | |
| // Now parse the body. | |
| for (auto [arg, type] : llvm::zip_equal(regionArgs, argTypes)) | |
| arg.type = type; | |
| mlir::Region *body = result.addRegion(); | |
| if (parser.parseRegion(*body, regionArgs)) | |
| return mlir::failure(); | |
| // Parse properties and attributes. | |
| mlir::Attribute parsedProperties; | |
| if (parseOptionalProperties(parser, parsedProperties) || | |
| parser.parseOptionalAttrDict(result.attributes) || | |
| setParsedProperties<fir::DoConcurrentLoopOp>(parser, result, | |
| parsedProperties)) | |
| return mlir::failure(); | |
| return mlir::success(); | |
| } | |
| void fir::DoConcurrentLoopOp::print(mlir::OpAsmPrinter &p) { | |
| p << " (" << getBody()->getArguments().slice(0, getNumInductionVars()) | |
| << ") = (" << getLowerBound() << ") to (" << getUpperBound() << ") step (" | |
| << getStep() << ")"; | |
| if (!getLocalVars().empty()) { | |
| p << " local("; | |
| llvm::interleaveComma(llvm::zip_equal(getLocalSymsAttr(), getLocalVars(), | |
| getRegionLocalArgs()), | |
| p, [&](auto it) { | |
| p << std::get<0>(it) << " " << std::get<1>(it) | |
| << " -> " << std::get<2>(it); | |
| }); | |
| p << " : "; | |
| llvm::interleaveComma(getLocalVars(), p, | |
| [&](auto it) { p << it.getType(); }); | |
| p << ")"; | |
| } | |
| if (!getReduceVars().empty()) { | |
| p << " reduce("; | |
| llvm::interleaveComma( | |
| llvm::zip_equal(getReduceByrefAttr().asArrayRef(), getReduceSymsAttr(), | |
| getReduceAttrsAttr(), getReduceVars(), | |
| getRegionReduceArgs()), | |
| p, [&](auto it) { | |
| if (std::get<0>(it)) | |
| p << "byref "; | |
| p << std::get<1>(it) << " " << std::get<2>(it) << " " | |
| << std::get<3>(it) << " -> " << std::get<4>(it); | |
| }); | |
| p << " : "; | |
| llvm::interleaveComma(getReduceVars(), p, | |
| [&](auto it) { p << it.getType(); }); | |
| p << ")"; | |
| } | |
| p << ' '; | |
| p.printRegion(getRegion(), /*printEntryBlockArgs=*/false); | |
| fir::DoConcurrentLoopOp::printProperties( | |
| getContext(), p, getProperties(), | |
| /*elidedProps=*/ | |
| {DoConcurrentLoopOp::getOperandSegmentSizeAttr(), | |
| DoConcurrentLoopOp::getLocalSymsAttrName(), | |
| DoConcurrentLoopOp::getReduceSymsAttrName(), | |
| DoConcurrentLoopOp::getReduceAttrsAttrName(), | |
| DoConcurrentLoopOp::getReduceByrefAttrName()}); | |
| p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary()); | |
| } | |
| llvm::SmallVector<mlir::Region *> fir::DoConcurrentLoopOp::getLoopRegions() { | |
| return {&getRegion()}; | |
| } | |
| llvm::LogicalResult fir::DoConcurrentLoopOp::verify() { | |
| mlir::Operation::operand_range lbValues = getLowerBound(); | |
| mlir::Operation::operand_range ubValues = getUpperBound(); | |
| mlir::Operation::operand_range stepValues = getStep(); | |
| mlir::Operation::operand_range localVars = getLocalVars(); | |
| mlir::Operation::operand_range reduceVars = getReduceVars(); | |
| if (lbValues.empty()) | |
| return emitOpError( | |
| "needs at least one tuple element for lowerBound, upperBound and step"); | |
| if (lbValues.size() != ubValues.size() || | |
| ubValues.size() != stepValues.size()) | |
| return emitOpError("different number of tuple elements for lowerBound, " | |
| "upperBound or step"); | |
| // Check that the body defines the same number of block arguments as the | |
| // number of tuple elements in step. | |
| mlir::Block *body = getBody(); | |
| unsigned numIndVarArgs = | |
| body->getNumArguments() - localVars.size() - reduceVars.size(); | |
| if (numIndVarArgs != stepValues.size()) | |
| return emitOpError() << "expects the same number of induction variables: " | |
| << body->getNumArguments() | |
| << " as bound and step values: " << stepValues.size(); | |
| for (auto arg : body->getArguments().slice(0, numIndVarArgs)) | |
| if (!arg.getType().isIndex()) | |
| return emitOpError( | |
| "expects arguments for the induction variable to be of index type"); | |
| auto reduceAttrs = getReduceAttrsAttr(); | |
| if (getNumReduceOperands() != (reduceAttrs ? reduceAttrs.size() : 0)) | |
| return emitOpError( | |
| "mismatch in number of reduction variables and reduction attributes"); | |
| return mlir::success(); | |
| } | |
| std::optional<llvm::SmallVector<mlir::Value>> | |
| fir::DoConcurrentLoopOp::getLoopInductionVars() { | |
| return llvm::SmallVector<mlir::Value>{ | |
| getBody()->getArguments().slice(0, getLowerBound().size())}; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // FIROpsDialect | |
| //===----------------------------------------------------------------------===// | |
| void fir::FIROpsDialect::registerOpExternalInterfaces() { | |
| // Attach default declare target interfaces to operations which can be marked | |
| // as declare target. | |
| fir::GlobalOp::attachInterface< | |
| mlir::omp::DeclareTargetDefaultModel<fir::GlobalOp>>(*getContext()); | |
| } | |
| // Tablegen operators | |
| #define GET_OP_CLASSES | |
| #include "flang/Optimizer/Dialect/FIROps.cpp.inc" |