From 89d572f06cb86d813eb4760f5c7580c115b6b41c Mon Sep 17 00:00:00 2001 From: Jean Perier Date: Thu, 27 Feb 2020 08:40:04 -0800 Subject: [PATCH] Add Fortran IR (FIR) MLIR dialect implementation Adds FIR library that implements an MLIR dialect to which Fortran parse-tree will be lowered to. FIR is defined and documented inside FIROps.td added in this commit. Also add tco tool that is meant to process FIR input files and drives transformations on it. The tco tool is used for testing. In this patch, it is only used to demonstrate parsing/verifying/ and dumping FIR with round-trip tests. Note: This commit does not reflect an actual work log, it is a feature based split of the changes done in the FIR experimental branch. The related work log can be found in the commits between: https://github.com/schweitzpgi/f18/commit/742edde572bd74d77cf7d447132ccf0949187fce and https://github.com/schweitzpgi/f18/commit/2ff55242126d86061f4fed9ef7b59d3636b5fd0b --- CMakeLists.txt | 7 + include/fir/.clang-format | 2 - include/flang/CMakeLists.txt | 11 +- include/flang/Optimizer/CMakeLists.txt | 1 + .../flang/Optimizer/Dialect/CMakeLists.txt | 4 + include/flang/Optimizer/Dialect/FIRAttr.h | 166 + include/flang/Optimizer/Dialect/FIRDialect.h | 86 + include/flang/Optimizer/Dialect/FIROps.h | 74 + include/flang/Optimizer/Dialect/FIROps.td | 2779 +++++++++++++++++ .../flang/Optimizer/Dialect/FIROpsSupport.h | 73 + include/flang/Optimizer/Dialect/FIRType.h | 357 +++ include/flang/Optimizer/Support/KindMapping.h | 89 + lib/CMakeLists.txt | 4 + lib/Fir/.clang-format | 2 - lib/Optimizer/CMakeLists.txt | 5 + lib/Optimizer/Dialect/CMakeLists.txt | 27 + lib/Optimizer/Dialect/FIRAttr.cpp | 236 ++ lib/Optimizer/Dialect/FIRDialect.cpp | 77 + lib/Optimizer/Dialect/FIROps.cpp | 573 ++++ lib/Optimizer/Dialect/FIRType.cpp | 1288 ++++++++ lib/Optimizer/Support/CMakeLists.txt | 10 + lib/Optimizer/Support/KindMapping.cpp | 239 ++ test-lit/CMakeLists.txt | 5 + test-lit/Fir/fir-ops.fir | 458 +++ test-lit/Fir/fir-types.fir | 70 + test-lit/lit.cfg.py | 8 +- test-lit/lit.site.cfg.py.in | 1 + tools/CMakeLists.txt | 4 +- tools/tco/CMakeLists.txt | 22 + tools/tco/tco.cpp | 119 + 30 files changed, 6783 insertions(+), 14 deletions(-) delete mode 100644 include/fir/.clang-format create mode 100644 include/flang/Optimizer/CMakeLists.txt create mode 100644 include/flang/Optimizer/Dialect/CMakeLists.txt create mode 100644 include/flang/Optimizer/Dialect/FIRAttr.h create mode 100644 include/flang/Optimizer/Dialect/FIRDialect.h create mode 100644 include/flang/Optimizer/Dialect/FIROps.h create mode 100644 include/flang/Optimizer/Dialect/FIROps.td create mode 100644 include/flang/Optimizer/Dialect/FIROpsSupport.h create mode 100644 include/flang/Optimizer/Dialect/FIRType.h create mode 100644 include/flang/Optimizer/Support/KindMapping.h delete mode 100644 lib/Fir/.clang-format create mode 100644 lib/Optimizer/CMakeLists.txt create mode 100644 lib/Optimizer/Dialect/CMakeLists.txt create mode 100644 lib/Optimizer/Dialect/FIRAttr.cpp create mode 100644 lib/Optimizer/Dialect/FIRDialect.cpp create mode 100644 lib/Optimizer/Dialect/FIROps.cpp create mode 100644 lib/Optimizer/Dialect/FIRType.cpp create mode 100644 lib/Optimizer/Support/CMakeLists.txt create mode 100644 lib/Optimizer/Support/KindMapping.cpp create mode 100644 test-lit/Fir/fir-ops.fir create mode 100644 test-lit/Fir/fir-types.fir create mode 100644 tools/tco/CMakeLists.txt create mode 100644 tools/tco/tco.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b7a823f7ec3..b11ca12ec6e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,13 @@ add_definitions(${LLVM_DEFINITIONS}) set(LLVM_EXTERNAL_LIT ${LLVM_TOOLS_BINARY_DIR}/llvm-lit CACHE STRING "Command used to spawn lit") if(LINK_WITH_FIR) + include(TableGen) + include(AddMLIR) + find_program(MLIR_TABLEGEN_EXE "mlir-tblgen" ${LLVM_TOOLS_BINARY_DIR} + NO_DEFAULT_PATH) + # tco tool and FIR lib output directories + set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/bin) + set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/lib) message(STATUS "Linking driver with FIR and LLVM") llvm_map_components_to_libnames(LLVM_COMMON_LIBS support) message(STATUS "LLVM libraries: ${LLVM_COMMON_LIBS}") diff --git a/include/fir/.clang-format b/include/fir/.clang-format deleted file mode 100644 index a74fda4b6734..000000000000 --- a/include/fir/.clang-format +++ /dev/null @@ -1,2 +0,0 @@ -BasedOnStyle: LLVM -AlwaysBreakTemplateDeclarations: Yes diff --git a/include/flang/CMakeLists.txt b/include/flang/CMakeLists.txt index 7fae707bdc46..db5d26a83b56 100644 --- a/include/flang/CMakeLists.txt +++ b/include/flang/CMakeLists.txt @@ -1,8 +1,3 @@ -#===-- include/flang/CMakeLists.txt ----------------------------------------===# -# -# 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 -# -#===------------------------------------------------------------------------===# - +if(LINK_WITH_FIR) + add_subdirectory(Optimizer) +endif() diff --git a/include/flang/Optimizer/CMakeLists.txt b/include/flang/Optimizer/CMakeLists.txt new file mode 100644 index 000000000000..0ca0f41c5af4 --- /dev/null +++ b/include/flang/Optimizer/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Dialect) diff --git a/include/flang/Optimizer/Dialect/CMakeLists.txt b/include/flang/Optimizer/Dialect/CMakeLists.txt new file mode 100644 index 000000000000..9528b1abffb0 --- /dev/null +++ b/include/flang/Optimizer/Dialect/CMakeLists.txt @@ -0,0 +1,4 @@ +set(LLVM_TARGET_DEFINITIONS FIROps.td) +mlir_tablegen(FIROps.h.inc -gen-op-decls) +mlir_tablegen(FIROps.cpp.inc -gen-op-defs) +add_public_tablegen_target(FIROpsIncGen) diff --git a/include/flang/Optimizer/Dialect/FIRAttr.h b/include/flang/Optimizer/Dialect/FIRAttr.h new file mode 100644 index 000000000000..a65675aa7d40 --- /dev/null +++ b/include/flang/Optimizer/Dialect/FIRAttr.h @@ -0,0 +1,166 @@ +//===-- Optimizer/Dialect/FIRAttr.h -- FIR attributes -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef OPTIMIZER_DIALECT_FIRATTR_H +#define OPTIMIZER_DIALECT_FIRATTR_H + +#include "mlir/IR/Attributes.h" + +namespace mlir { +class DialectAsmParser; +class DialectAsmPrinter; +} // namespace mlir + +namespace fir { + +class FIROpsDialect; + +namespace detail { +struct RealAttributeStorage; +struct TypeAttributeStorage; +} // namespace detail + +enum AttributeKind { + FIR_ATTR = mlir::Attribute::FIRST_FIR_ATTR, + FIR_EXACTTYPE, // instance_of, precise type relation + FIR_SUBCLASS, // subsumed_by, is-a (subclass) relation + FIR_POINT, + FIR_CLOSEDCLOSED_INTERVAL, + FIR_OPENCLOSED_INTERVAL, + FIR_CLOSEDOPEN_INTERVAL, + FIR_REAL_ATTR, +}; + +class ExactTypeAttr + : public mlir::Attribute::AttrBase { +public: + using Base::Base; + using ValueType = mlir::Type; + + static constexpr llvm::StringRef getAttrName() { return "instance"; } + static ExactTypeAttr get(mlir::Type value); + + mlir::Type getType() const; + + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { return AttributeKind::FIR_EXACTTYPE; } +}; + +class SubclassAttr + : public mlir::Attribute::AttrBase { +public: + using Base::Base; + using ValueType = mlir::Type; + + static constexpr llvm::StringRef getAttrName() { return "subsumed"; } + static SubclassAttr get(mlir::Type value); + + mlir::Type getType() const; + + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { return AttributeKind::FIR_SUBCLASS; } +}; + +// Attributes for building SELECT CASE multiway branches + +/// A closed interval (including the bound values) is an interval with both an +/// upper and lower bound as given as ssa-values. +/// A case selector of `CASE (n:m)` corresponds to any value from `n` to `m` and +/// is encoded as `#fir.interval, %n, %m`. +class ClosedIntervalAttr + : public mlir::Attribute::AttrBase { +public: + using Base::Base; + + static constexpr llvm::StringRef getAttrName() { return "interval"; } + static ClosedIntervalAttr get(mlir::MLIRContext *ctxt); + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { + return AttributeKind::FIR_CLOSEDCLOSED_INTERVAL; + } +}; + +/// An upper bound is an open interval (including the bound value) as given as +/// an ssa-value. +/// A case selector of `CASE (:m)` corresponds to any value up to and including +/// `m` and is encoded as `#fir.upper, %m`. +class UpperBoundAttr : public mlir::Attribute::AttrBase { +public: + using Base::Base; + + static constexpr llvm::StringRef getAttrName() { return "upper"; } + static UpperBoundAttr get(mlir::MLIRContext *ctxt); + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { + return AttributeKind::FIR_OPENCLOSED_INTERVAL; + } +}; + +/// A lower bound is an open interval (including the bound value) as given as +/// an ssa-value. +/// A case selector of `CASE (n:)` corresponds to any value down to and +/// including `n` and is encoded as `#fir.lower, %n`. +class LowerBoundAttr : public mlir::Attribute::AttrBase { +public: + using Base::Base; + + static constexpr llvm::StringRef getAttrName() { return "lower"; } + static LowerBoundAttr get(mlir::MLIRContext *ctxt); + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { + return AttributeKind::FIR_CLOSEDOPEN_INTERVAL; + } +}; + +/// A pointer interval is an closed interval as given as an ssa-value. The +/// interval contains exactly one value. +/// A case selector of `CASE (p)` corresponds to exactly the value `p` and is +/// encoded as `#fir.point, %p`. +class PointIntervalAttr : public mlir::Attribute::AttrBase { +public: + using Base::Base; + + static constexpr llvm::StringRef getAttrName() { return "point"; } + static PointIntervalAttr get(mlir::MLIRContext *ctxt); + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { return AttributeKind::FIR_POINT; } +}; + +/// A real attribute is used to workaround MLIR's default parsing of a real +/// constant. +/// `#fir.real<10, 3.14>` is used to introduce a real constant of value `3.14` +/// with a kind of `10`. +class RealAttr + : public mlir::Attribute::AttrBase { +public: + using Base::Base; + using ValueType = std::pair; + + static constexpr llvm::StringRef getAttrName() { return "real"; } + static RealAttr get(mlir::MLIRContext *ctxt, const ValueType &key); + + int getFKind() const; + llvm::APFloat getValue() const; + + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { return AttributeKind::FIR_REAL_ATTR; } +}; + +mlir::Attribute parseFirAttribute(FIROpsDialect *dialect, + mlir::DialectAsmParser &parser, + mlir::Type type); + +void printFirAttribute(FIROpsDialect *dialect, mlir::Attribute attr, + mlir::DialectAsmPrinter &p); + +} // namespace fir + +#endif // OPTIMIZER_DIALECT_FIRATTR_H diff --git a/include/flang/Optimizer/Dialect/FIRDialect.h b/include/flang/Optimizer/Dialect/FIRDialect.h new file mode 100644 index 000000000000..a6761b0c0ed9 --- /dev/null +++ b/include/flang/Optimizer/Dialect/FIRDialect.h @@ -0,0 +1,86 @@ +//===-- Optimizer/Dialect/FIRDialect.h -- FIR dialect -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef OPTIMIZER_DIALECT_FIRDIALECT_H +#define OPTIMIZER_DIALECT_FIRDIALECT_H + +#include "mlir/IR/Dialect.h" +#include "mlir/InitAllPasses.h" + +namespace llvm { +class raw_ostream; +class StringRef; +} // namespace llvm + +namespace mlir { +class Attribute; +class DialectAsmParser; +class DialectAsmPrinter; +class Location; +class MLIRContext; +class Type; +} // namespace mlir + +namespace fir { + +/// FIR dialect +class FIROpsDialect final : public mlir::Dialect { +public: + explicit FIROpsDialect(mlir::MLIRContext *ctx); + virtual ~FIROpsDialect(); + + static llvm::StringRef getDialectNamespace() { return "fir"; } + + mlir::Type parseType(mlir::DialectAsmParser &parser) const override; + void printType(mlir::Type ty, mlir::DialectAsmPrinter &p) const override; + + mlir::Attribute parseAttribute(mlir::DialectAsmParser &parser, + mlir::Type type) const override; + void printAttribute(mlir::Attribute attr, + mlir::DialectAsmPrinter &p) const override; +}; + +/// Register the dialect with MLIR +inline void registerFIR() { + // we want to register exactly once + [[maybe_unused]] static bool init_once = [] { + mlir::registerDialect(); + return true; + }(); +} + +/// Register the standard passes we use. This comes from registerAllPasses(), +/// but is a smaller set since we aren't using many of the passes found there. +inline void registerGeneralPasses() { + mlir::createCanonicalizerPass(); + mlir::createCSEPass(); + mlir::createVectorizePass({}); + mlir::createLoopUnrollPass(); + mlir::createLoopUnrollAndJamPass(); + mlir::createSimplifyAffineStructuresPass(); + mlir::createLoopFusionPass(); + mlir::createLoopInvariantCodeMotionPass(); + mlir::createAffineLoopInvariantCodeMotionPass(); + mlir::createPipelineDataTransferPass(); + mlir::createLowerAffinePass(); + mlir::createLoopTilingPass(0); + mlir::createLoopCoalescingPass(); + mlir::createAffineDataCopyGenerationPass(0, 0); + mlir::createMemRefDataFlowOptPass(); + mlir::createStripDebugInfoPass(); + mlir::createPrintOpStatsPass(); + mlir::createInlinerPass(); + mlir::createSymbolDCEPass(); + mlir::createLocationSnapshotPass({}); +} + +inline void registerFIRPasses() { registerGeneralPasses(); } + +} // namespace fir + +#endif // OPTIMIZER_DIALECT_FIRDIALECT_H diff --git a/include/flang/Optimizer/Dialect/FIROps.h b/include/flang/Optimizer/Dialect/FIROps.h new file mode 100644 index 000000000000..cd5c8b6ef66a --- /dev/null +++ b/include/flang/Optimizer/Dialect/FIROps.h @@ -0,0 +1,74 @@ +//===-- Optimizer/Dialect/FIROps.h - FIR operations -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef OPTIMIZER_DIALECT_FIROPS_H +#define OPTIMIZER_DIALECT_FIROPS_H + +#include "mlir/IR/Builders.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/SymbolTable.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" + +using namespace mlir; + +namespace fir { + +class FirEndOp; + +enum class CmpFPredicate { + FirstValidValue, + // Always false + AlwaysFalse = FirstValidValue, + // Ordered comparisons + OEQ, + OGT, + OGE, + OLT, + OLE, + ONE, + // Both ordered + ORD, + // Unordered comparisons + UEQ, + UGT, + UGE, + ULT, + ULE, + UNE, + // Any unordered + UNO, + // Always true + AlwaysTrue, + // Number of predicates. + NumPredicates +}; + +ParseResult isValidCaseAttr(Attribute attr); +unsigned getCaseArgumentOffset(ArrayRef cases, unsigned dest); +ParseResult parseSelector(OpAsmParser *parser, OperationState *result, + OpAsmParser::OperandType &selector, mlir::Type &type); + +void buildCmpFOp(Builder *builder, OperationState &result, + CmpFPredicate predicate, Value lhs, Value rhs); +void buildCmpCOp(Builder *builder, OperationState &result, + CmpFPredicate predicate, Value lhs, Value rhs); +ParseResult parseCmpfOp(OpAsmParser &parser, OperationState &result); +ParseResult parseCmpcOp(OpAsmParser &parser, OperationState &result); + +#define GET_OP_CLASSES +#include "flang/Optimizer/Dialect/FIROps.h.inc" + +LoopOp getForInductionVarOwner(Value val); + +bool isReferenceLike(mlir::Type type); + +} // namespace fir + +#endif // OPTIMIZER_DIALECT_FIROPS_H diff --git a/include/flang/Optimizer/Dialect/FIROps.td b/include/flang/Optimizer/Dialect/FIROps.td new file mode 100644 index 000000000000..7ce9efd40786 --- /dev/null +++ b/include/flang/Optimizer/Dialect/FIROps.td @@ -0,0 +1,2779 @@ +//===-- FIROps.td - FIR operation definitions --------------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Definition of the FIR dialect operations +/// +//===----------------------------------------------------------------------===// + +#ifndef FIR_DIALECT_FIR_OPS +#define FIR_DIALECT_FIR_OPS + +#ifndef OP_BASE +include "mlir/IR/OpBase.td" +#endif + +def fir_Dialect : Dialect { + let name = "fir"; +} + +// Types and predicates + +def fir_Type : Type, + "FIR dialect type">; + +// Fortran intrinsic types +def fir_CharacterType : Type()">, + "FIR character type">; +def fir_ComplexType : Type()">, + "FIR complex type">; +def fir_IntegerType : Type()">, + "FIR integer type">; +def fir_LogicalType : Type()">, + "FIR logical type">; +def fir_RealType : Type()">, + "FIR real type">; + +// Generalized FIR and standard dialect types representing intrinsic types +def AnyIntegerLike : TypeConstraint, "any integer">; +def AnyLogicalLike : TypeConstraint, "any logical">; +def AnyRealLike : TypeConstraint, "any real">; +def AnyIntegerType : Type; + +// Fortran derived (user defined) type +def fir_RecordType : Type()">, + "FIR derived type">; + +// Fortran array attribute +def fir_SequenceType : Type()">, + "array type">; + +// Composable types +def AnyCompositeLike : TypeConstraint, "any composite">; + +// Reference to an entity type +def fir_ReferenceType : Type()">, + "reference type">; + +// Reference to an ALLOCATABLE attribute type +def fir_HeapType : Type()">, + "allocatable type">; + +// Reference to a POINTER attribute type +def fir_PointerType : Type()">, + "pointer type">; + +// Reference types +def AnyReferenceLike : TypeConstraint, "any reference">; + +// A descriptor tuple (captures a reference to an entity and other information) +def fir_BoxType : Type()">, "box type">; + +// CHARACTER type descriptor. A pair of a data reference and a LEN value. +def fir_BoxCharType : Type()">, + "box character type">; + +// PROCEDURE POINTER descriptor. A pair that can capture a host closure. +def fir_BoxProcType : Type()">, + "box procedure type">; + +def AnyBoxLike : TypeConstraint, "any box">; + +def AnyRefOrBox : TypeConstraint, + "any reference or box">; + +// A vector of Fortran triple notation describing a multidimensional array +def fir_DimsType : Type()">, "dim type">; +def AnyEmboxLike : TypeConstraint, + "any legal embox argument type">; +def AnyEmboxArg : Type; + +// A type descriptor's type +def fir_TypeDescType : Type()">, + "type desc type">; + +// A field (in a RecordType) argument's type +def fir_FieldType : Type()">, "field type">; + +// A LEN parameter (in a RecordType) argument's type +def fir_LenType : Type()">, + "LEN parameter type">; + +def AnyComponentLike : TypeConstraint, + "any coordinate index">; +def AnyComponentType : Type; + +def AnyCoordinateLike : TypeConstraint, "any coordinate index">; +def AnyCoordinateType : Type; + +// Base class for FIR operations. +// All operations automatically get a prefix of "fir.". +class fir_Op traits> + : Op; + +// Base class for FIR operations that take a single argument +class fir_SimpleOp traits> + : fir_Op { + + let assemblyFormat = [{ + operands attr-dict `:` functional-type(operands, results) + }]; +} + +// Base builder for allocate operations +def fir_AllocateOpBuilder : OpBuilder< + "Builder *builder, OperationState &result, Type inType," + "ArrayRef lenParams = {}, ArrayRef sizes = {}," + "ArrayRef attributes = {}", + [{ + result.addTypes(getRefTy(inType)); + result.addAttribute("in_type", mlir::TypeAttr::get(inType)); + result.addOperands(sizes); + for (auto namedAttr : attributes) + result.addAttribute(namedAttr.first, namedAttr.second); + }]>; + +def fir_NamedAllocateOpBuilder : OpBuilder< + "Builder *builder, OperationState &result, Type inType, StringRef name," + "ArrayRef lenParams = {}, ArrayRef sizes = {}," + "ArrayRef attributes = {}", + [{ + result.addTypes(getRefTy(inType)); + result.addAttribute("in_type", mlir::TypeAttr::get(inType)); + result.addAttribute("name", builder->getStringAttr(name)); + result.addOperands(sizes); + for (auto namedAttr : attributes) + result.addAttribute(namedAttr.first, namedAttr.second); + }]>; + +def fir_OneResultOpBuilder : OpBuilder< + "Builder *, OperationState &result, Type resultType," + "ArrayRef operands, ArrayRef attributes = {}", + [{ + if (resultType) + result.addTypes(resultType); + result.addOperands(operands); + for (auto namedAttr : attributes) + result.addAttribute(namedAttr.first, namedAttr.second); + }]>; + +// Base class of FIR operations that return 1 result +class fir_OneResultOp traits = []> : + fir_Op, Results<(outs fir_Type:$res)> { + let builders = [fir_OneResultOpBuilder]; +} + +// Base class of FIR operations that have 1 argument and return 1 result +class fir_SimpleOneResultOp traits = []> : + fir_SimpleOp { + let builders = [fir_OneResultOpBuilder]; +} + +class fir_TwoBuilders { + list builders = [b1, b2]; +} + +class fir_AllocatableBaseOp traits = []> : + fir_Op, Results<(outs fir_Type:$res)> { + let arguments = (ins + OptionalAttr:$name, + OptionalAttr:$target + ); +} + +class fir_AllocatableOp traits =[]> : + fir_AllocatableBaseOp, + fir_TwoBuilders, + Arguments<(ins TypeAttr:$in_type, Variadic:$args)> { + + let parser = [{ + mlir::Type intype; + if (parser.parseType(intype)) + return mlir::failure(); + auto &builder = parser.getBuilder(); + result.addAttribute(inType(), mlir::TypeAttr::get(intype)); + llvm::SmallVector operands; + llvm::SmallVector typeVec; + bool hasOperands = false; + if (!parser.parseOptionalLParen()) { + // parse the LEN params of the derived type. ( : ) + if (parser.parseOperandList(operands, + mlir::OpAsmParser::Delimiter::None) || + parser.parseColonTypeList(typeVec) || + parser.parseRParen()) + return mlir::failure(); + auto lens = builder.getI32IntegerAttr(operands.size()); + result.addAttribute(lenpName(), lens); + hasOperands = true; + } + if (!parser.parseOptionalComma()) { + // parse size to scale by, vector of n dimensions of type index + auto opSize = operands.size(); + if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::None)) + return mlir::failure(); + for (auto i = opSize, end = operands.size(); i != end; ++i) + typeVec.push_back(builder.getIndexType()); + 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(); + } + if (parser.parseOptionalAttrDict(result.attributes) || + parser.addTypeToList(restype, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr(inType()); + if (hasLenParams()) { + // print the LEN parameters to a derived type in parens + p << '('; + p.printOperands(getLenParams()); + p << " : "; + mlir::interleaveComma(getLenParams(), p.getStream(), + [&](const auto &opnd) { + p.printType(opnd.getType()); + }); + p << ')'; + } + // print the shape of the allocation (if any); all must be index type + for (auto sh : getShapeOperands()) { + p << ", "; + p.printOperand(sh); + } + p.printOptionalAttrDict(getAttrs(), {inType(), lenpName()}); + }]; + + string extraAllocClassDeclaration = [{ + static constexpr llvm::StringRef inType() { return "in_type"; } + static constexpr llvm::StringRef lenpName() { return "len_param_count"; } + mlir::Type getAllocatedType(); + bool hasLenParams() { return bool{getAttr(lenpName())}; } + unsigned numLenParams() { + if (auto val = getAttrOfType(lenpName())) + return val.getInt(); + return 0; + } + operand_range getLenParams() { + return {operand_begin(), operand_begin() + numLenParams()}; + } + operand_range getShapeOperands() { + return {operand_begin() + numLenParams(), operand_end()}; + } + static mlir::Type getRefTy(mlir::Type ty); + + /// Get the input type of the allocation + mlir::Type getInType() { + return getAttrOfType(inType()).getValue(); + } + }]; + + // Verify checks common to all allocation operations + string allocVerify = [{ + llvm::SmallVector visited; + if (verifyInType(getInType(), visited)) + return emitOpError("invalid type for allocation"); + if (verifyRecordLenParams(getInType(), numLenParams())) + return emitOpError("LEN params do not correspond to type"); + }]; +} + +// Memory SSA operations + +def fir_AllocaOp : fir_AllocatableOp<"alloca"> { + let summary = "allocate storage for a temporary on the stack given a type"; + let description = [{ + This primitive operation is used to allocate an object on the stack. A + reference to the object of type `!fir.ref` is returned. The returned + object has an undefined/uninitialized state. The allocation can be given + an optional name. The allocation may have a dynamic repetition count + for allocating a sequence of locations for the specified type. + + %c = ... : i64 + %x = fir.alloca i32 + %y = fir.alloca !fir.array<8 x i64> + %z = fir.alloca f32, %c + + %i = ... : i16 + %j = ... : i32 + %w = fir.alloca !fir.type (%i, %j : i16, i32) + + Note that in the case of `%z`, a contiguous block of memory is allocated + and its size is a runtime multiple of a 32-bit REAL value. + + In the case of `%w`, the arguments `%i` and `%j` are LEN parameters + (`len1`, `len2`) to the type `PT`. + + Finally, the operation is undefined if the ssa-value `%c` is negative. + }]; + + let results = (outs fir_ReferenceType); + + let verifier = allocVerify#[{ + mlir::Type outType = getType(); + if (!outType.isa()) + return emitOpError("must be a !fir.ref type"); + return mlir::success(); + }]; + + let extraClassDeclaration = extraAllocClassDeclaration#[{ + static mlir::Type wrapResultType(mlir::Type intype); + }]; +} + +def fir_LoadOp : fir_OneResultOp<"load", []> { + let summary = "load a value from a memory reference"; + let description = [{ + Load a value from a memory reference into an ssa-value (virtual register). + Produces an immutable ssa-value of the referent type. A memory reference + has type `!fir.ref`, `!fir.heap`, or `!fir.ptr`. + + %a = fir.alloca i32 + %l = fir.load %a : !fir.ref + + The ssa-value has an undefined value if the memory reference is undefined + or null. + }]; + + let arguments = (ins AnyReferenceLike:$memref); + + let builders = [OpBuilder< + "Builder *builder, OperationState &result, Value refVal", + [{ + if (!refVal) { + mlir::emitError(result.location, "LoadOp has null argument"); + return; + } + fir::ReferenceType refTy = refVal.getType().cast(); + result.addOperands(refVal); + result.addTypes(refTy.getEleTy()); + }] + >]; + + let parser = [{ + mlir::Type type; + mlir::OpAsmParser::OperandType oper; + if (parser.parseOperand(oper) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(type) || + parser.resolveOperand(oper, type, result.operands)) + return mlir::failure(); + mlir::Type eleTy; + if (getElementOf(eleTy, type) || + parser.addTypeToList(eleTy, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' '; + p.printOperand(memref()); + p.printOptionalAttrDict(getAttrs(), {}); + p << " : " << memref().getType(); + }]; + + let extraClassDeclaration = [{ + static mlir::ParseResult getElementOf(mlir::Type &ele, mlir::Type ref); + }]; +} + +def fir_StoreOp : fir_Op<"store", []> { + let summary = "store an SSA-value to a memory location"; + + let description = [{ + Store an ssa-value (virtual register) to a memory reference. The stored + value must be of the same type as the referent type of the memory + reference. + + %v = ... : f64 + %p = ... : !fir.ptr + fir.store %v to %p : !fir.ptr + + The above store changes the value to which the pointer is pointing and not + the pointer itself. The operation is undefined if the memory reference, + `%p`, is undefined or null. + }]; + + let arguments = (ins AnyType:$value, AnyReferenceLike:$memref); + + let parser = [{ + mlir::Type type; + mlir::OpAsmParser::OperandType oper; + mlir::OpAsmParser::OperandType store; + if (parser.parseOperand(oper) || + parser.parseKeyword("to") || + parser.parseOperand(store) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(type) || + parser.resolveOperand(oper, elementType(type), + result.operands) || + parser.resolveOperand(store, type, result.operands)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' '; + p.printOperand(value()); + p << " to "; + p.printOperand(memref()); + p.printOptionalAttrDict(getAttrs(), {}); + p << " : " << memref().getType(); + }]; + + let verifier = [{ + if (value().getType() != fir::dyn_cast_ptrEleTy(memref().getType())) + return emitOpError("store value type must match memory reference type"); + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + static mlir::Type elementType(mlir::Type refType); + }]; +} + +def fir_UndefOp : fir_OneResultOp<"undefined", [NoSideEffect]> { + let summary = "explicit undefined value of some type"; + let description = [{ + Constructs an ssa-value of the specified type with an undefined value. + This operation is typically created internally by the mem2reg conversion + pass. An undefined value can be of any type except `!fir.ref`. + + %a = fir.undefined !fir.array<10 x !fir.type> + + The example creates an array shaped ssa value. The array is rank 1, extent + 10, and each element has type `!fir.type`. + }]; + + let verifier = [{ + if (auto ref = getType().dyn_cast()) + return emitOpError("undefined values of type !fir.ref not allowed"); + return mlir::success(); + }]; + + let parser = [{ + mlir::Type intype; + if (parser.parseType(intype) || + parser.parseOptionalAttrDict(result.attributes) || + parser.addTypeToList(intype, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getType(); + p.printOptionalAttrDict(getAttrs(), {}); + }]; +} + +def fir_AllocMemOp : fir_AllocatableOp<"allocmem"> { + let summary = "allocate storage on the heap for an object of a given type"; + + let description = [{ + Creates a heap memory reference suitable for storing a value of the + given type, T. The heap refernce returned has type `!fir.heap`. + The memory object is in an undefined state. `allocmem` operations must + be paired with `freemem` operations to avoid memory leaks. + + %0 = fir.allocmem !fir.array<10 x f32> + fir.freemem %0 : !fir.heap> + }]; + + let results = (outs fir_HeapType); + + let verifier = allocVerify#[{ + mlir::Type outType = getType(); + if (!outType.dyn_cast()) + return emitOpError("must be a !fir.heap type"); + return mlir::success(); + }]; + + let extraClassDeclaration = extraAllocClassDeclaration#[{ + static mlir::Type wrapResultType(mlir::Type intype); + }]; +} + +def fir_FreeMemOp : fir_Op<"freemem", []> { + let summary = "free a heap object"; + + let description = [{ + Deallocates a heap memory reference that was allocated by an `allocmem`. + The memory object that is deallocated is placed in an undefined state + after `fir.freemem`. Optimizations may treat the loading of an object + in the undefined state as undefined behavior. This includes aliasing + references, such as the result of an `fir.embox`. + + %21 = fir.allocmem !fir.type + ... + fir.freemem %21 : !fir.heap> + }]; + + let arguments = (ins fir_HeapType:$heapref); + + let assemblyFormat = "$heapref attr-dict `:` type($heapref)"; +} + +// Terminator operations + +class fir_SwitchTerminatorOp traits = []> : + fir_Op { + let arguments = (ins Variadic:$args); + + let results = (outs); + + let builders = [OpBuilder< + "Builder *, OperationState &result, Value selector," + "ArrayRef properOperands, ArrayRef destinations," + "ArrayRef> operands = {}," + "ArrayRef attributes = {}", + [{ + result.addOperands(selector); + result.addOperands(properOperands); + for (auto kvp : llvm::zip(destinations, operands)) { + result.addSuccessor(std::get<0>(kvp), std::get<1>(kvp)); + } + for (auto namedAttr : attributes) { + result.addAttribute(namedAttr.first, namedAttr.second); + } + }] + >]; + + string extraSwitchClassDeclaration = [{ + using Conditions = mlir::Value; + static constexpr auto AttrName = "cases"; + + // The number of destination conditions that may be tested + unsigned getNumConditions() { return getNumDest(); } + + // The selector is the value being tested to determine the destination + mlir::Value getSelector() { return getOperand(0); } + + // The number of blocks that may be branched to + unsigned getNumDest() { return getOperation()->getNumSuccessors(); } + }]; +} + +class fir_IntegralSwitchTerminatorOp traits = []> : fir_SwitchTerminatorOp { + let parser = [{ + mlir::OpAsmParser::OperandType selector; + mlir::Type type; + if (parseSelector(parser, result, selector, type)) + return mlir::failure(); + + llvm::SmallVector ivalues; + llvm::SmallVector dests; + llvm::SmallVector, 4> destArgs; + while (true) { + mlir::Attribute ivalue; // Integer or Unit + mlir::Block *dest; + llvm::SmallVector destArg; + llvm::SmallVector 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(); + } + result.addAttribute(AttrName, parser.getBuilder().getArrayAttr(ivalues)); + for (unsigned i = 0, count = dests.size(); i != count; ++i) + result.addSuccessor(dests[i], destArgs[i]); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' '; + p.printOperand(getSelector()); + p << " : " << getSelector().getType() << " ["; + auto cases = getAttrOfType(AttrName).getValue(); + for (unsigned i = 0, count = getNumConditions(); i != count; ++i) { + if (i) + p << ", "; + auto &attr = cases[i]; + if (auto intAttr = attr.dyn_cast_or_null()) + p << intAttr.getValue(); + else + p.printAttribute(attr); + p << ", "; + p.printSuccessorAndUseList(getOperation(), i); + } + p << ']'; + p.printOptionalAttrDict(getAttrs(), {AttrName}); + }]; + + let verifier = [{ + if (!(getSelector().getType().isa() || + getSelector().getType().isa() || + getSelector().getType().isa())) + return emitOpError("must be an integer"); + auto cases = getAttrOfType(AttrName).getValue(); + for (unsigned i = 0, count = getNumConditions(); i != count; ++i) { + auto &attr = cases[i]; + if (attr.dyn_cast_or_null()) { + // ok + } else if (attr.dyn_cast_or_null()) { + // ok + } else { + return emitOpError("invalid case alternative"); + } + } + return mlir::success(); + }]; + + let successors = (successor VariadicSuccessor:$targets); + + let extraClassDeclaration = extraSwitchClassDeclaration; +} + +def fir_SelectOp : fir_IntegralSwitchTerminatorOp<"select"> { + let summary = "a multiway branch"; + + let description = [{ + A multiway branch terminator with similar semantics to C's `switch` + statement. A selector value is matched against a list of constants + of the same type for a match. When a match is found, control is + transferred to the corresponding basic block. A `select` must have + at least one basic block with a corresponding `unit` match, and + that block will be selected when all other conditions fail to match. + + fir.select %arg:i32 [1, ^bb1(%0 : i32), + 2, ^bb2(%2,%arg,%arg2 : i32,i32,i32), + -3, ^bb3(%arg2,%2 : i32,i32), + 4, ^bb4(%1 : i32), + unit, ^bb5] + }]; +} + +def fir_SelectRankOp : fir_IntegralSwitchTerminatorOp<"select_rank"> { + let summary = "Fortran's SELECT RANK statement"; + + let description = [{ + Similar to `select`, `select_rank` provides a way to express Fortran's + SELECT RANK construct. In this case, the rank of the selector value + is matched against constants of integer type. The structure is the + same as `select`, but `select_rank` determines the rank of the selector + variable at runtime to determine the best match. + + fir.select_rank %arg:i32 [1, ^bb1(%0 : i32), + 2, ^bb2(%2,%arg,%arg2 : i32,i32,i32), + 3, ^bb3(%arg2,%2 : i32,i32), + -1, ^bb4(%1 : i32), + unit, ^bb5] + }]; +} + +def fir_SelectCaseOp : fir_SwitchTerminatorOp<"select_case"> { + let summary = "Fortran's SELECT CASE statement"; + + let description = [{ + Similar to `select`, `select_case` provides a way to express Fortran's + SELECT CASE construct. In this case, the selector value is matched + against variables (not just constants) and ranges. The structure is + the same as `select`, but `select_case` allows for the expression of + more complex match conditions. + + fir.select_case %arg : i32 [ + #fir.point, %0, ^bb1(%0 : i32), + #fir.lower, %1, ^bb2(%2,%arg,%arg2,%1 : i32,i32,i32,i32), + #fir.interval, %2, %3, ^bb3(%2,%arg2 : i32,i32), + #fir.upper, %arg, ^bb4(%1 : i32), + unit, ^bb5] + }]; + + let parser = [{ + mlir::OpAsmParser::OperandType selector; + mlir::Type type; + if (parseSelector(parser, result, selector, type)) + return mlir::failure(); + + llvm::SmallVector attrs; + llvm::SmallVector opers; + llvm::SmallVector dests; + llvm::SmallVector, 4> destArgs; + while (true) { + mlir::Attribute attr; + mlir::Block *dest; + llvm::SmallVector destArg; + llvm::SmallVector temp; + if (parser.parseAttribute(attr, "a", temp) || + isValidCaseAttr(attr) || + parser.parseComma()) + return mlir::failure(); + attrs.push_back(attr); + if (attr.dyn_cast_or_null()) { + // do nothing + } else if (attr.dyn_cast_or_null()) { + mlir::OpAsmParser::OperandType oper1; + mlir::OpAsmParser::OperandType oper2; + if (parser.parseOperand(oper1) || + parser.parseComma() || + parser.parseOperand(oper2) || + parser.parseComma()) + return mlir::failure(); + opers.push_back(oper1); + opers.push_back(oper2); + } else { + mlir::OpAsmParser::OperandType oper; + if (parser.parseOperand(oper) || + parser.parseComma()) + return mlir::failure(); + opers.push_back(oper); + } + if (parser.parseSuccessorAndUseList(dest, destArg)) + return mlir::failure(); + dests.push_back(dest); + destArgs.push_back(destArg); + if (!parser.parseOptionalRSquare()) + break; + if (parser.parseComma()) + return mlir::failure(); + } + result.addAttribute(AttrName, parser.getBuilder().getArrayAttr(attrs)); + if (parser.resolveOperands(opers, type, result.operands)) + return mlir::failure(); + for (unsigned i = 0, count = dests.size(); i != count; ++i) + result.addSuccessor(dests[i], destArgs[i]); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' '; + p.printOperand(getSelector()); + p << " : " << getSelector().getType() << " ["; + auto cases = getAttrOfType(AttrName).getValue(); + for (unsigned i = 0, count = getNumConditions(); i != count; ++i) { + if (i) + p << ", "; + p << cases[i] << ", "; + if (!cases[i].dyn_cast_or_null()) { + p.printOperand(getCaseArg(i, 0)); + p << ", "; + if (cases[i].dyn_cast_or_null()) { + p.printOperand(getCaseArg(i, 1)); + p << ", "; + } + } + p.printSuccessorAndUseList(getOperation(), i); + } + p << ']'; + p.printOptionalAttrDict(getAttrs(), {AttrName}); + }]; + + let verifier = [{ + if (!(getSelector().getType().isa() || + getSelector().getType().isa() || + getSelector().getType().isa() || + getSelector().getType().isa() || + getSelector().getType().isa())) + return emitOpError("must be an integer, character, or logical"); + auto cases = getAttrOfType(AttrName).getValue(); + for (unsigned i = 0, count = getNumConditions(); i != count; ++i) { + auto &attr = cases[i]; + if (attr.isa() || + attr.isa() || + attr.isa() || + attr.isa() || + attr.isa()) { + // ok + } else { + return emitOpError("incorrect select case attribute type"); + } + } + return mlir::success(); + }]; + + let successors = (successor VariadicSuccessor:$targets); + + let extraClassDeclaration = extraSwitchClassDeclaration#[{ + mlir::Value getCaseArg(unsigned dest, unsigned ele) { + assert(ele < 2); + assert(dest < getNumConditions()); + auto cases = getAttrOfType(AttrName).getValue(); + assert(cases.size() == getNumConditions()); + unsigned o = getCaseArgumentOffset(cases, dest); + return getOperand(o + 1 + ele); + } + }]; +} + +def fir_SelectTypeOp : fir_SwitchTerminatorOp<"select_type"> { + let summary = "Fortran's SELECT TYPE statement"; + + let description = [{ + Similar to `select`, `select_type` provides a way to express Fortran's + SELECT TYPE construct. In this case, the type of the selector value + is matched against a list of type descriptors. The structure is the + same as `select`, but `select_type` determines the type of the selector + variable at runtime to determine the best match. + + fir.select_type %arg : !fir.box<()> [ + #fir.instance>, ^bb1(%0 : i32), + #fir.instance>, ^bb2(%2 : i32), + #fir.subsumed>, ^bb3(%2 : i32), + #fir.instance>, ^bb4(%1,%3 : i32,f32), + unit, ^bb5] + }]; + + let parser = [{ + mlir::OpAsmParser::OperandType selector; + mlir::Type type; + if (parseSelector(parser, result, selector, type)) + return mlir::failure(); + + llvm::SmallVector attrs; + llvm::SmallVector dests; + llvm::SmallVector, 4> destArgs; + while (true) { + mlir::Attribute attr; + mlir::Block *dest; + llvm::SmallVector destArg; + llvm::SmallVector 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 (!parser.parseOptionalRSquare()) + break; + if (parser.parseComma()) + return mlir::failure(); + } + result.addAttribute(AttrName, parser.getBuilder().getArrayAttr(attrs)); + for (unsigned i = 0, count = dests.size(); i != count; ++i) + result.addSuccessor(dests[i], destArgs[i]); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' '; + p.printOperand(getSelector()); + p << " : " << getSelector().getType() << " ["; + auto cases = getAttrOfType(AttrName).getValue(); + for (unsigned i = 0, count = getNumConditions(); i != count; ++i) { + if (i) + p << ", "; + p << cases[i] << ", "; + p.printSuccessorAndUseList(getOperation(), i); + } + p << ']'; + p.printOptionalAttrDict(getAttrs(), {AttrName}); + }]; + + let verifier = [{ + if (!(getSelector().getType().isa())) + return emitOpError("must be a boxed type"); + auto cases = getAttrOfType(AttrName).getValue(); + for (unsigned i = 0, count = getNumConditions(); i != count; ++i) { + auto &attr = cases[i]; + if (attr.dyn_cast_or_null() || + attr.dyn_cast_or_null() || + attr.dyn_cast_or_null()) { + // ok + } else { + return emitOpError("invalid type-case alternative"); + } + } + return mlir::success(); + }]; + + let successors = (successor VariadicSuccessor:$targets); + + let extraClassDeclaration = extraSwitchClassDeclaration; +} + +def fir_UnreachableOp : fir_Op<"unreachable", [Terminator]> { + let summary = "the unreachable instruction"; + + let description = [{ + Terminates a basic block with the assertion that the end of the block + will never be reached at runtime. This instruction can be used + immediately after a call to the Fortran runtime to terminate the + program, for example. This instruction corresponds to the LLVM IR + instruction `unreachable`. + + fir.unreachable + }]; + + let parser = "return mlir::success();"; + + let printer = "p << getOperationName();"; +} + +def fir_FirEndOp : fir_Op<"end", [Terminator]> { + let summary = "the end instruction"; + + let description = [{ + The end terminator is a special terminator used inside various FIR + operations that have regions. End is thus the custom invisible terminator + for these operations. It is implicit and need not appear in the textual + representation. + }]; +} + +def fir_HasValueOp : fir_Op<"has_value", [Terminator, HasParent<"GlobalOp">]> { + let summary = "terminator for GlobalOp"; + let description = [{ + The terminator for a GlobalOp with a body. + + global @variable : tuple { + %0 = constant 45 : i32 + %1 = constant 100.0 : f32 + %2 = fir.undefined tuple + %3 = constant 0 : index + %4 = fir.insert_value %2, %0, %3 : (tuple, i32, index) -> tuple + %5 = constant 1 : index + %6 = fir.insert_value %4, %1, %5 : (tuple, f32, index) -> tuple + fir.has_value %6 : tuple + } + }]; + + let arguments = (ins AnyType:$resval); + + let assemblyFormat = "$resval attr-dict `:` type($resval)"; +} + +// Operations on !fir.box type objects + +def fir_EmboxOp : fir_Op<"embox", [NoSideEffect]> { + let summary = "boxes a given reference and (optional) dimension information"; + + let description = [{ + Create a boxed reference value. In Fortran, the implementation can require + extra information about an entity, such as its type, rank, etc. This + auxilliary information is packaged and abstracted as a value with box type + by the calling routine. (In Fortran, these are called descriptors.) + + %c1 = constant 1 : index + %c10 = constant 10 : index + %4 = fir.dims(%c1, %c10, %c1) : (index, index, index) -> !fir.dims<1> + %5 = ... : !fir.ref> + %6 = fir.embox %5, %4 : (!fir.ref>, !fir.dims<1>) -> !fir.box> + + The descriptor tuple may contain additional implementation-specific + information through the use of additional attributes. + }]; + + let arguments = (ins AnyReferenceLike:$memref, Variadic:$args); + + let results = (outs fir_BoxType); + + let parser = [{ + mlir::FunctionType type; + llvm::SmallVector operands; + mlir::OpAsmParser::OperandType memref; + if (parser.parseOperand(memref)) + return mlir::failure(); + operands.push_back(memref); + auto &builder = parser.getBuilder(); + if (!parser.parseOptionalLParen()) { + if (parser.parseOperandList(operands, + mlir::OpAsmParser::Delimiter::None) || + parser.parseRParen()) + return mlir::failure(); + auto lens = builder.getI32IntegerAttr(operands.size()); + result.addAttribute(lenpName(), lens); + } + if (!parser.parseOptionalComma()) { + mlir::OpAsmParser::OperandType dims; + if (parser.parseOperand(dims)) + return mlir::failure(); + operands.push_back(dims); + } else if (!parser.parseOptionalLSquare()) { + mlir::AffineMapAttr map; + if (parser.parseAttribute(map, layoutName(), result.attributes) || + parser.parseRSquare()) + return mlir::failure(); + } + if (parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(type) || + parser.resolveOperands(operands, type.getInputs(), + parser.getNameLoc(), result.operands) || + parser.addTypesToList(type.getResults(), result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' '; + p.printOperand(memref()); + if (hasLenParams()) { + p << '('; + p.printOperands(getLenParams()); + p << ')'; + } + if (getNumOperands() == 2) { + p << ", "; + p.printOperands(dims()); + } else if (auto map = getAttr(layoutName())) { + p << " [" << map << ']'; + } + p.printOptionalAttrDict(getAttrs(), {layoutName(), lenpName()}); + p << " : "; + p.printFunctionalType(getOperation()); + }]; + + let verifier = [{ + if (hasLenParams()) { + auto lenParams = numLenParams(); + auto eleTy = fir::dyn_cast_ptrEleTy(memref().getType()); + if (!eleTy) + return emitOpError("must embox a memory reference type"); + if (auto rt = eleTy.dyn_cast()) { + if (lenParams != rt.getNumLenParams()) + return emitOpError("number of LEN params does not correspond" + " to the !fir.type type"); + } else { + return emitOpError("LEN parameters require !fir.type type"); + } + for (auto lp : getLenParams()) + if (lp.getType().isa()) + return emitOpError("LEN parameters must be integral type"); + } + if (dims().size() == 0) { + // Ok. If there is no dims and no layout map, then emboxing a scalar. + // TODO: Should the type be enforced? It already must agree. + } else if (dims().size() == 1) { + auto d = *dims().begin(); + if (!d.getType().isa()) + return emitOpError("dimension argument must have !fir.dims type"); + } else { + return emitOpError("embox can only have one !fir.dim argument"); + } + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef layoutName() { return "layout_map"; } + static constexpr llvm::StringRef lenpName() { return "len_param_count"; } + bool hasLenParams() { return bool{getAttr(lenpName())}; } + unsigned numLenParams() { + if (auto x = getAttrOfType(lenpName())) + return x.getInt(); + return 0; + } + operand_range getLenParams() { + return {operand_begin(), operand_begin() + numLenParams()}; + } + operand_range dims() { + return {operand_begin() + numLenParams() + 1, operand_end()}; + } + }]; +} + +def fir_EmboxCharOp : fir_Op<"emboxchar", [NoSideEffect]> { + let arguments = (ins AnyReferenceLike:$memref, AnyIntegerLike:$len); + let results = (outs fir_BoxCharType); + + let summary = "boxes a given CHARACTER reference and its LEN parameter"; + + let description = [{ + Create a boxed CHARACTER value. The CHARACTER type has the LEN type + parameter, the value of which may only be known at runtime. Therefore, + a variable of type CHARACTER has both its data reference as well as a + LEN type parameter. + + CHARACTER(LEN=10) :: var + + %4 = ... : !fir.ref>> + %5 = constant 10 : i32 + %6 = fir.emboxchar %4, %5 : (!fir.ref>>, i32) -> !fir.boxchar<1> + + In the above `%4` is a memory reference to a buffer of 10 CHARACTER units. + This buffer and its LEN value (10) are wrapped into a pair in `%6`. + }]; + + let assemblyFormat = [{ + $memref `,` $len attr-dict `:` functional-type(operands, results) + }]; + + let verifier = [{ + auto eleTy = elementTypeOf(memref().getType()); + if (!eleTy.dyn_cast()) + return mlir::failure(); + return mlir::success(); + }]; +} + +def fir_EmboxProcOp : fir_Op<"emboxproc", [NoSideEffect]> { + + let summary = "boxes a given procedure and optional host context"; + + let description = [{ + Creates an abstract encapsulation of a PROCEDURE POINTER along with an + optional pointer to a host instance context. If the pointer is not to an + internal procedure or the internal procedure does not need a host context + then the form takes only the procedure's symbol. + + %0 = fir.emboxproc @f : ((i32) -> i32) -> !fir.boxproc<(i32) -> i32> + + An internal procedure requiring a host instance for correct execution uses + the second form. The closure of the host procedure's state is passed as a + reference to a tuple. It is the responsibility of the host to manage the + context's values accordingly, up to and including inhibiting register + promotion of local values. + + %4 = ... : !fir.ref> + %5 = fir.emboxproc @g, %4 : ((i32) -> i32, !fir.ref>) -> + !fir.boxproc<(i32) -> i32> + }]; + + let arguments = (ins SymbolRefAttr:$funcname, AnyReferenceLike:$host); + + let results = (outs fir_BoxProcType); + + let parser = [{ + mlir::SymbolRefAttr procRef; + if (parser.parseAttribute(procRef, "funcname", result.attributes)) + return mlir::failure(); + bool hasTuple = false; + mlir::OpAsmParser::OperandType tupleRef; + if (!parser.parseOptionalComma()) { + if (parser.parseOperand(tupleRef)) + return mlir::failure(); + hasTuple = true; + } + mlir::FunctionType type; + if (parser.parseColon() || + parser.parseLParen() || + parser.parseType(type)) + return mlir::failure(); + result.addAttribute("functype", mlir::TypeAttr::get(type)); + if (hasTuple) { + mlir::Type tupleType; + if (parser.parseComma() || + parser.parseType(tupleType) || + parser.resolveOperand(tupleRef, tupleType, result.operands)) + return mlir::failure(); + } + mlir::Type boxType; + if (parser.parseRParen() || + parser.parseArrow() || + parser.parseType(boxType) || + parser.addTypesToList(boxType, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr("funcname"); + auto h = host(); + if (h) { + p << ", "; + p.printOperand(h); + } + p << " : (" << getAttr("functype"); + if (h) + p << ", " << h.getType(); + p << ") -> " << getType(); + }]; + + let verifier = [{ + // host bindings (optional) must be a reference to a tuple + if (auto h = host()) { + if (auto r = h.getType().dyn_cast()) { + if (!r.getEleTy().dyn_cast()) + return mlir::failure(); + } else { + return mlir::failure(); + } + } + return mlir::success(); + }]; +} + +def fir_UnboxOp : fir_SimpleOp<"unbox", [NoSideEffect]> { + let summary = "unbox the boxed value into a tuple value"; + + let description = [{ + Unbox a boxed value into a result of multiple values from the box's + component data. The values are, minimally, a reference to the data of the + entity, the byte-size of one element, the rank, the type descriptor, a set + of flags (packed in an integer, and an array of dimension information (of + size rank). + + %40 = ... : !fir.box> + %41:6 = fir.unbox %40 : (!fir.box>) -> (!fir.ref>, i32, i32, !fir.tdesc>, i32, !fir.dims<4>) + }]; + + let arguments = (ins fir_BoxType:$box); + + let results = (outs + fir_ReferenceType, // pointer to data + AnyIntegerLike, // size of a data element + AnyIntegerLike, // rank of data + fir_TypeDescType, // abstract type descriptor + AnyIntegerLike, // attribute flags (bitfields) + fir_DimsType // dimension information (if any) + ); +} + +def fir_UnboxCharOp : fir_SimpleOp<"unboxchar", [NoSideEffect]> { + let summary = "unbox a boxchar value into a pair value"; + + let description = [{ + Unboxes a value of `boxchar` type into a pair consisting of a memory + reference to the CHARACTER data and the LEN type parameter. + + %45 = ... : !fir.boxchar<1> + %46:2 = fir.unboxchar %45 : (!fir.boxchar<1>) -> (!fir.ref>, i32) + }]; + + let arguments = (ins fir_BoxCharType:$boxchar); + + let results = (outs fir_ReferenceType, AnyIntegerLike); +} + +def fir_UnboxProcOp : fir_SimpleOp<"unboxproc", [NoSideEffect]> { + let summary = "unbox a boxproc value into a pair value"; + + let description = [{ + Unboxes a value of `boxproc` type into a pair consisting of a procedure + pointer and a pointer to a host context. + + %47 = ... : !fir.boxproc<() -> i32> + %48:2 = fir.unboxproc %47 : (!fir.ref<() -> i32>, !fir.ref>) + }]; + + let verifier = [{ + if (auto eleTy = fir::dyn_cast_ptrEleTy(refTuple().getType())) + if (eleTy.isa()) + return mlir::success(); + return emitOpError("second output argument has bad type"); + }]; + + let arguments = (ins fir_BoxProcType:$boxproc); + + let results = (outs FunctionType, fir_ReferenceType:$refTuple); +} + +def fir_BoxAddrOp : fir_SimpleOneResultOp<"box_addr", [NoSideEffect]> { + let summary = "return a memory reference to the boxed value"; + + let description = [{ + This operator is overloaded to work with values of type `box`, + `boxchar`, and `boxproc`. The result for each of these + cases, respectively, is the address of the data, the address of the + CHARACTER data, and the address of the procedure. + + %51 = fir.box_addr %box : (!fir.box) -> !fir.ref + %52 = fir.box_addr %boxchar : (!fir.boxchar<1>) -> !fir.ref> + %53 = fir.box_addr %boxproc : (!fir.boxproc) -> !fir.ref + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs AnyReferenceLike); +} + +def fir_BoxCharLenOp : fir_SimpleOp<"boxchar_len", [NoSideEffect]> { + let summary = "return the LEN type parameter from a boxchar value"; + + let description = [{ + Extracts the LEN type parameter from a `boxchar` value. + + %45 = ... : !boxchar<1> // CHARACTER(20) + %59 = fir.boxchar_len %45 : (!fir.boxchar<1>) -> i64 // len=20 + }]; + + let arguments = (ins fir_BoxCharType:$val); + + let results = (outs AnyIntegerLike); +} + +def fir_BoxDimsOp : fir_Op<"box_dims", [NoSideEffect]> { + let summary = "return the dynamic dimension information for the boxed value"; + + let description = [{ + Returns the triple of lower bound, extent, and stride for `dim` dimension + of `val`, which must have a `box` type. The dimensions are enumerated from + left to right from 0 to rank-1. This operation has undefined behavior if + `dim` is out of bounds. + + %c1 = constant 0 : i32 + %52:3 = fir.box_dims %40, %c1 : (!fir.box>, i32) -> (i32, i32, i32) + + The above is a request to return the left most row (at index 0) triple from + the box. The triple will be the lower bound, upper bound, and stride. + }]; + + let arguments = (ins fir_BoxType:$val, AnyIntegerLike:$dim); + + let results = (outs AnyIntegerLike, AnyIntegerLike, AnyIntegerLike); + + let assemblyFormat = [{ + $val `,` $dim attr-dict `:` functional-type(operands, results) + }]; + + let extraClassDeclaration = [{ + mlir::Type getTupleType(); + }]; +} + +def fir_BoxEleSizeOp : fir_SimpleOneResultOp<"box_elesize", [NoSideEffect]> { + let summary = "return the size of an element of the boxed value"; + + let description = [{ + Returns the size of an element in an entity of `box` type. This size may + not be known until runtime. + + %53 = fir.box_elesize %40 : (!fir.box, i32) -> i32 // size=4 + %54 = fir.box_elesize %40 : (!fir.box>, i32) -> i32 + + In the above example, `%53` may box an array of REAL values while `%54` + must box an array of REAL values (with dynamic rank and extent). + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs AnyIntegerLike); +} + +def fir_BoxIsAllocOp : fir_SimpleOp<"box_isalloc", [NoSideEffect]> { + let summary = "is the boxed value an ALLOCATABLE?"; + + let description = [{ + Determine if the boxed value was from an ALLOCATABLE entity. This will + return true if the originating box value was from a `fir.embox` op + with a mem-ref value that had the type !fir.heap. + + %r = ... : !fir.heap + %b = fir.embox %r : (!fir.heap) -> !fir.box + %a = fir.box_isalloc %b : (!fir.box) -> i1 // true + + The canonical descriptor implementation will carry a flag to record if the + variable is an ALLOCATABLE. + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs BoolLike); +} + +def fir_BoxIsArrayOp : fir_SimpleOp<"box_isarray", [NoSideEffect]> { + let summary = "is the boxed value an array?"; + + let description = [{ + Determine if the boxed value has a positive (> 0) rank. This will return + true if the originating box value was from a fir.embox with a memory + reference value that had the type !fir.array and/or a dims argument. + + %r = ... : !fir.ref + %d = fir.gendims(1, 100, 1) : (i32, i32, i32) -> !fir.dims<1> + %b = fir.embox %r, %d : (!fir.ref, !fir.dims<1>) -> !fir.box + %a = fir.box_isarray %b : (!fir.box) -> i1 // true + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs BoolLike); +} + +def fir_BoxIsPtrOp : fir_SimpleOp<"box_isptr", [NoSideEffect]> { + let summary = "is the boxed value a POINTER?"; + + let description = [{ + Determine if the boxed value was from a POINTER entity. + + %p = ... : !fir.ptr + %b = fir.embox %p : (!fir.ptr) -> !fir.box + %a = fir.box_isptr %b : (!fir.box) -> i1 // true + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs BoolLike); +} + +def fir_BoxProcHostOp : fir_SimpleOp<"boxproc_host", [NoSideEffect]> { + let summary = "returns the host instance pointer (or null)"; + + let description = [{ + Extract the host context pointer from a boxproc value. + + %8 = ... : !fir.boxproc<(!fir.ref>) -> i32> + %9 = fir.boxproc_host %8 : (!fir.boxproc<(!fir.ref>) -> i32>) -> !fir.ref> + + In the example, the reference to the closure over the host procedure's + variables is returned. This allows an internal procedure to access the + host's variables. It is up to lowering to determine the contract between + the host and the internal procedure. + }]; + + let arguments = (ins fir_BoxProcType:$val); + + let results = (outs fir_ReferenceType); +} + +def fir_BoxRankOp : fir_SimpleOneResultOp<"box_rank", [NoSideEffect]> { + let summary = "return the number of dimensions for the boxed value"; + + let description = [{ + Return the rank of a value of `box` type. If the value is scalar, the + rank is 0. + + %57 = fir.box_rank %40 : (!fir.box>) -> i32 + %58 = fir.box_rank %41 : (!fir.box) -> i32 + + The example `%57` shows how one would determine the rank of an array that + has deferred rank at runtime. This rank should be at least 1. In %58, the + descriptor may be either an array or a scalar, so the value is nonnegative. + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs AnyIntegerType); +} + +def fir_BoxTypeDescOp : fir_SimpleOneResultOp<"box_tdesc", [NoSideEffect]> { + let summary = "return the type descriptor for the boxed value"; + + let description = [{ + Return the opaque type descriptor of a value of `box` type. A type + descriptor is an implementation defined value that fully describes a type + to the Fortran runtime. + + %7 = fir.box_tdesc %41 : (!fir.box) -> !fir.tdesc + }]; + + let arguments = (ins fir_BoxType:$val); + + let results = (outs fir_TypeDescType); +} + +// Record and array type operations + +def fir_CoordinateOp : fir_Op<"coordinate_of", [NoSideEffect]> { + let summary = "Finds the coordinate (location) of a value in memory"; + + let description = [{ + Compute the internal coordinate address starting from a boxed value or + unboxed memory reference. Returns a memory reference. When computing the + coordinate of an array element, the rank of the array must be known and + the number of indexing expressions must equal the rank of the array. + + This operation will apply the access map from a boxed value implicitly. + + Unlike LLVM's GEP instruction, one cannot stride over the outermost + reference; therefore, the leading 0 index must be omitted. + + %i = ... : index + %h = ... : !fir.heap> + %p = fir.coordinate_of %h, %i : (!fir.heap>, index) -> !fir.ref + + In the example, `%p` will be a pointer to the `%i`-th f32 value in the + array `%h`. + }]; + + let arguments = (ins AnyRefOrBox:$ref, Variadic:$coor); + + let results = (outs fir_ReferenceType); + + let assemblyFormat = [{ + operands attr-dict `:` functional-type(operands, results) + }]; + + let verifier = [{ + // Recovering a LEN type parameter only makes sense from a boxed value + for (auto co : coor()) + if (auto *s = co.getDefiningOp()) + if (dyn_cast_or_null(s)) { + if (getNumOperands() != 2) + return emitOpError("len_param_index must be last argument"); + if (!ref().getType().dyn_cast()) + return emitOpError("len_param_index must be used on box type"); + } + return mlir::success(); + }]; +} + +def fir_ExtractValueOp : fir_OneResultOp<"extract_value", [NoSideEffect]> { + let summary = "Extract a value from an aggregate SSA-value"; + + let description = [{ + Extract a value from an entity with a type composed of tuples, arrays, + and/or derived types. Returns the value from entity with the type of the + specified component. Cannot be used on values of `!fir.box` type. + + Note that the entity ssa-value must be of compile-time known size in order + to use this operation. + + %f = fir.field_index field, !fir.type + %s = ... : !fir.type + %v = fir.extract_value %s, %f : (!fir.type, !fir.field) -> i32 + }]; + + let arguments = (ins + AnyCompositeLike:$adt, + Variadic:$coor + ); + + let assemblyFormat = [{ + $adt `,` $coor attr-dict `:` functional-type(operands, results) + }]; +} + +def fir_FieldIndexOp : fir_OneResultOp<"field_index", [NoSideEffect]> { + let summary = "create a field index value from a field identifier"; + + let description = [{ + Generate a field (offset) value from an identifier. Field values may be + lowered into exact offsets when the layout of a Fortran derived type is + known at compile-time. The type of a field value is `!fir.field` and + these values can be used with the `fir.coordinate_of`, `fir.extract_value`, + or `fir.insert_value` instructions to compute (abstract) addresses of + subobjects. + + %f = fir.field_index field, !fir.type + }]; + + let arguments = (ins + StrAttr:$field_id, + TypeAttr:$on_type, + Variadic:$lenparams + ); + + let parser = [{ + llvm::StringRef fieldName; + auto &builder = parser.getBuilder(); + mlir::Type recty; + if (parser.parseOptionalKeyword(&fieldName) || + parser.parseComma() || + parser.parseType(recty)) + return mlir::failure(); + result.addAttribute(fieldAttrName(), builder.getStringAttr(fieldName)); + if (!recty.dyn_cast()) + return mlir::failure(); + result.addAttribute(typeAttrName(), mlir::TypeAttr::get(recty)); + if (!parser.parseOptionalLParen()) { + llvm::SmallVector operands; + llvm::SmallVector types; + auto loc = parser.getNameLoc(); + if (parser.parseOperandList(operands, + mlir::OpAsmParser::Delimiter::None) || + parser.parseRParen() || + parser.parseColonTypeList(types) || + parser.resolveOperands(operands, types, loc, result.operands)) + return mlir::failure(); + } + mlir::Type fieldType = fir::FieldType::get(builder.getContext()); + if (parser.addTypeToList(fieldType, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' + << getAttrOfType(fieldAttrName()).getValue() << ", " + << getAttr(typeAttrName()); + if (getNumOperands()) { + p << '('; + p.printOperands(lenparams()); + auto sep = ") : "; + for (auto op : lenparams()) { + p << sep; + if (op) + p.printType(op.getType()); + else + p << "()"; + sep = ", "; + } + } + }]; + + let builders = [OpBuilder< + "Builder *builder, OperationState &result, StringRef fieldName," + "Type recTy, ArrayRef operands = {}", + [{ + result.addAttribute(fieldAttrName(), builder->getStringAttr(fieldName)); + result.addAttribute(typeAttrName(), TypeAttr::get(recTy)); + result.addOperands(operands); + }] + >]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef fieldAttrName() { return "field_id"; } + static constexpr llvm::StringRef typeAttrName() { return "on_type"; } + }]; +} + +def fir_GenDimsOp : fir_OneResultOp<"gendims", [NoSideEffect]> { + + let summary = "generate a value of type `!fir.dims`"; + + let description = [{ + The arguments are an ordered list of integral type values that is a + multiple of 3 in length. Each such triple is defined as: the lower + index, the extent, and the stride for that dimension. The dimension + information is given in the same row-to-column order as Fortran. This + abstract dimension value must describe a reified object, so all dimension + information must be specified. The extent must be nonnegative and the + stride must not be zero. + + %d = fir.gendims %l, %u, %s : (index, index, index) -> !fir.dims<1> + }]; + + let arguments = (ins Variadic:$triples); + + let results = (outs fir_DimsType); + + let assemblyFormat = [{ + operands attr-dict `:` functional-type(operands, results) + }]; + + let verifier = [{ + auto size = triples().size(); + if (size < 1 && size <= 16 * 3) + return emitOpError("incorrect number of args"); + if (size % 3 != 0) + return emitOpError("requires a multiple of 3 args"); + return mlir::success(); + }]; +} + +def fir_InsertValueOp : fir_OneResultOp<"insert_value", [NoSideEffect]> { + let summary = "insert a new sub-value into a copy of an existing aggregate"; + + let description = [{ + Insert a value from an entity with a type composed of tuples, arrays, + and/or derived types. Returns a new ssa value with the same type as the + original entity. Cannot be used on values of `!fir.box` type. + + Note that the entity ssa-value must be of compile-time known size in order + to use this operation. + + %a = ... : !fir.array<10xtuple> + %f = ... : f32 + %o = ... : i32 + %c = constant 1 : i32 + %b = fir.insert_value %a, %f, %o, %c : (!fir.array<10x20xtuple>, f32, i32, i32) -> !fir.array<10x20xtuple> + }]; + + let arguments = (ins AnyCompositeLike:$adt, AnyType:$val, + Variadic:$coor); + let results = (outs AnyCompositeLike); + + let assemblyFormat = [{ + operands attr-dict `:` functional-type(operands, results) + }]; +} + +def fir_LenParamIndexOp : fir_OneResultOp<"len_param_index", [NoSideEffect]> { + let summary = + "create a field index value from a LEN type parameter identifier"; + + let description = [{ + Generate a LEN parameter (offset) value from an LEN parameter identifier. + The type of a LEN parameter value is `!fir.len` and these values can be + used with the `fir.coordinate_of` instructions to compute (abstract) + addresses of LEN parameters. + + %e = fir.len_param_index len1, !fir.type + %p = ... : !fir.box> + %q = fir.coordinate_of %p, %e : (!fir.box>, !fir.len) -> !fir.ref + }]; + + let arguments = (ins StrAttr:$field_id, TypeAttr:$on_type); + + let parser = [{ + llvm::StringRef fieldName; + auto &builder = parser.getBuilder(); + mlir::Type recty; + if (parser.parseOptionalKeyword(&fieldName) || + parser.parseComma() || + parser.parseType(recty)) + return mlir::failure(); + result.addAttribute(fieldAttrName(), builder.getStringAttr(fieldName)); + if (!recty.dyn_cast()) + return mlir::failure(); + result.addAttribute(typeAttrName(), mlir::TypeAttr::get(recty)); + mlir::Type lenType = fir::LenType::get(builder.getContext()); + if (parser.addTypeToList(lenType, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' + << getAttrOfType(fieldAttrName()).getValue() << ", " + << getAttr(typeAttrName()); + }]; + + let builders = [OpBuilder< + "Builder *builder, OperationState &result, StringRef fieldName, Type recTy", + [{ + result.addAttribute(fieldAttrName(), builder->getStringAttr(fieldName)); + result.addAttribute(typeAttrName(), TypeAttr::get(recTy)); + }] + >]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef fieldAttrName() { return "field_id"; } + static constexpr llvm::StringRef typeAttrName() { return "on_type"; } + mlir::Type getOnType() { + return getAttrOfType(typeAttrName()).getValue(); + } + }]; +} + +// Fortran loops + +def ImplicitFirTerminator : SingleBlockImplicitTerminator<"FirEndOp">; + +def fir_LoopOp : fir_Op<"loop", [ImplicitFirTerminator]> { + let summary = "generalized loop operation"; + let description = [{ + Generalized high-level looping construct. This operation is similar to + MLIR's `loop.for`. An ordered loop will return the final value of `%i`. + + %l = constant 0 : index + %u = constant 9 : index + fir.loop %i = %l to %u unordered { + %x = fir.convert %i : (index) -> i32 + %v = fir.call @compute(%x) : (i32) -> f32 + %p = fir.coordinate_of %A, %i : (!fir.ref, index) -> !fir.ref + fir.store %v to %p : !fir.ref + } + + The above example iterates over the interval `[%l, %u]`. The unordered + keyword indicates that the iterations can be executed in any order. + }]; + + let arguments = (ins + Index:$lowerBound, + Index:$upperBound, + Variadic:$optStep, + OptionalAttr:$constantStep, + OptionalAttr:$unordered + ); + + let results = (outs Variadic:$lastVal); + + let regions = (region SizedRegion<1>:$region); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<"mlir::Builder *builder, OperationState &result," + "int64_t lowerBound, int64_t upperBound, int64_t step = 1">, + OpBuilder<"mlir::Builder *builder, OperationState &result," + "mlir::Value lowerBound, mlir::Value upperBound," + "ArrayRef step = {}"> + ]; + + let parser = "return parseLoopOp(parser, result);"; + + let printer = [{ + p << getOperationName() << ' ' << getInductionVar() << " = " + << lowerBound() << " to " << upperBound(); + auto s = optStep(); + if (s.begin() != s.end()) { + p << " step "; + p.printOperand(*s.begin()); + } + if (unordered()) + p << " unordered"; + p.printRegion(region(), /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/false); + p.printOptionalAttrDict(getAttrs(), {unorderedKeyword(), stepKeyword()}); + }]; + + let verifier = [{ + auto step = optStep(); + if (step.begin() != step.end()) { + // FIXME: size of step must be 1 + auto *s = (*step.begin()).getDefiningOp(); + if (auto cst = dyn_cast_or_null(s)) + if (cst.getValue() == 0) + return emitOpError("constant step operand must be nonzero"); + } + + // Check that the body defines as single block argument for the induction + // variable. + auto *body = getBody(); + if (body->getNumArguments() != 1 || + !body->getArgument(0).getType().isIndex()) + return emitOpError("expected body to have a single index argument for " + "the induction variable"); + if (lastVal().size() > 1) + return emitOpError("can only return one final value of iterator"); + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + static constexpr const char *unorderedKeyword() { return "unordered"; } + static constexpr const char *stepKeyword() { return "step"; } + + /// Is this an unordered loop? + bool isUnordered() { return getAttr(unorderedKeyword()).isa(); } + + /// Does loop set (and return) the final value of the control variable? + bool hasLastValue() { return lastVal().size(); } + + /// Get the body of the loop + mlir::Block *getBody() { return ®ion().front(); } + + /// Get the block argument corresponding to the loop control value (PHI) + mlir::Value getInductionVar() { return getBody()->getArgument(0); } + + /// Get a builder to insert operations into the LoopOp + mlir::OpBuilder getBodyBuilder() { + return mlir::OpBuilder(getBody(), std::prev(getBody()->end())); + } + + void setLowerBound(mlir::Value bound) { + getOperation()->setOperand(0, bound); + } + + void setUpperBound(mlir::Value bound) { + getOperation()->setOperand(1, bound); + } + + void setStep(mlir::Value step) { + getOperation()->setOperand(2, step); + } + }]; +} + +def fir_WhereOp : fir_Op<"where", [ImplicitFirTerminator]> { + let summary = "generalized conditional operation"; + let description = [{ + To conditionally execute operations (typically) within the body of a + `fir.loop` operation. This operation is similar to `loop.if`. + + %56 = ... : i1 + %78 = ... : !fir.ref + fir.where %56 { + fir.store %76 to %78 : !fir.ref + } otherwise { + fir.store %77 to %78 : !fir.ref + } + }]; + + let arguments = (ins I1:$condition); + + let regions = (region SizedRegion<1>:$whereRegion, AnyRegion:$otherRegion); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<"Builder *builder, OperationState &result, " + "Value cond, bool withOtherRegion"> + ]; + + let parser = [{ return parseWhereOp(parser, result); }]; + + let printer = [{ + p << getOperationName() << ' ' << condition(); + p.printRegion(whereRegion(), /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/false); + + // Print the 'else' regions if it exists and has a block. + auto &otherReg = otherRegion(); + if (!otherReg.empty()) { + p << " otherwise"; + p.printRegion(otherReg, /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/false); + } + p.printOptionalAttrDict(getAttrs()); + }]; + + let verifier = [{ + for (auto ®ion : getOperation()->getRegions()) { + if (region.empty()) + continue; + for (auto &b : region) + if (b.getNumArguments() != 0) + return emitOpError("requires that child entry blocks have no args"); + } + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + mlir::OpBuilder getWhereBodyBuilder() { + assert(!whereRegion().empty() && "Unexpected empty 'where' region."); + mlir::Block &body = whereRegion().front(); + return mlir::OpBuilder(&body, std::prev(body.end())); + } + mlir::OpBuilder getOtherBodyBuilder() { + assert(!otherRegion().empty() && "Unexpected empty 'other' region."); + mlir::Block &body = otherRegion().front(); + return mlir::OpBuilder(&body, std::prev(body.end())); + } + }]; +} + +// Procedure call operations + +def fir_CallOp : fir_Op<"call", []> { + let summary = "call a procedure"; + + let description = [{ + Call the specified function or function reference. + + Provides a custom parser and pretty printer to allow a more readable syntax + in the FIR dialect, e.g. `fir.call @sub(%12)` or `fir.call %20(%22,%23)`. + + %a = fir.call %funcref(%arg0) : (!fir.ref) -> f32 + %b = fir.call @function(%arg1, %arg2) : (!fir.ref, !fir.ref) -> f32 + }]; + + let arguments = (ins + OptionalAttr:$callee, + Variadic:$args + ); + + let results = (outs Variadic); + + let parser = "return parseCallOp(parser, result);"; + let printer = "printCallOp(p, *this);"; +} + +def fir_DispatchOp : fir_Op<"dispatch", []> { + let summary = "call a type-bound procedure"; + + let description = [{ + Perform a dynamic dispatch on the method name via the dispatch table + associated with the first argument. The attribute 'pass_arg_pos' can be + used to select a dispatch argument other than the first one. + + %r = fir.dispatch methodA(%o) : (!fir.box) -> i32 + }]; + + let arguments = (ins + StrAttr:$method, + fir_BoxType:$object, + Variadic:$args + ); + + let results = (outs Variadic); + + let parser = [{ + mlir::FunctionType calleeType; + llvm::SmallVector operands; + auto calleeLoc = parser.getNameLoc(); + llvm::StringRef calleeName; + if (parser.parseOptionalKeyword(&calleeName)) { + mlir::StringAttr calleeAttr; + if (parser.parseAttribute(calleeAttr, "method", result.attributes)) + return mlir::failure(); + } else { + result.addAttribute("method", + parser.getBuilder().getStringAttr(calleeName)); + } + if (parser.parseOperandList(operands, + mlir::OpAsmParser::Delimiter::Paren) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(calleeType) || + parser.addTypesToList(calleeType.getResults(), result.types) || + parser.resolveOperands( + operands, calleeType.getInputs(), calleeLoc, result.operands)) + return mlir::failure(); + result.addAttribute("fn_type", mlir::TypeAttr::get(calleeType)); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr("method") << '('; + p.printOperand(object()); + if (arg_operand_begin() != arg_operand_end()) { + p << ", "; + p.printOperands(args()); + } + p << ')'; + p.printOptionalAttrDict(getAttrs(), {"fn_type", "method"}); + auto resTy{getResultTypes()}; + llvm::SmallVector argTy(getOperandTypes()); + p << " : " << mlir::FunctionType::get(argTy, resTy, getContext()); + }]; + + let extraClassDeclaration = [{ + mlir::FunctionType getFunctionType(); + operand_range getArgOperands() { + return {arg_operand_begin(), arg_operand_end()}; + } + operand_iterator arg_operand_begin() { return operand_begin() + 1; } + operand_iterator arg_operand_end() { return operand_end(); } + llvm::StringRef passArgAttrName() { return "pass_arg_pos"; } + unsigned passArgPos(); + }]; +} + +// Constant operations that support Fortran + +def fir_StringLitOp : fir_Op<"string_lit", [NoSideEffect]>, + Results<(outs fir_SequenceType)> { + let summary = "create a string literal constant"; + + let description = [{ + An FIR constant that represents a sequence of characters that correspond + to Fortran's CHARACTER type, including a LEN. We support CHARACTER values + of different KINDs (different constant sizes). + + Example: + + %1 = fir.string_lit "Hello, World!"(13) : !fir.char<1> // ASCII + %2 = fir.string_lit [158, 2345](2) : !fir.char<2> // Wide chars + }]; + + let parser = [{ + auto &builder = parser.getBuilder(); + mlir::Attribute val; + llvm::SmallVector attrs; + if (parser.parseAttribute(val, "fake", attrs)) + return mlir::failure(); + if (auto v = val.dyn_cast()) + result.attributes.push_back(builder.getNamedAttr(value(), v)); + else if (auto v = val.dyn_cast()) + result.attributes.push_back(builder.getNamedAttr(xlist(), v)); + else + return mlir::failure(); + mlir::IntegerAttr sz; + mlir::Type type; + if (parser.parseLParen() || + parser.parseAttribute(sz, size(), result.attributes) || + parser.parseRParen() || + parser.parseColonType(type)) + return mlir::failure(); + type = fir::SequenceType::get({sz.getInt()}, type); + if (!type || + parser.addTypesToList(type, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getValue() << '('; + p << getSize().cast().getValue() << ") : "; + p.printType(getType().cast().getEleTy()); + }]; + + let verifier = [{ + if (getSize().cast().getValue().isNegative()) + return emitOpError("size must be non-negative"); + auto eleTy = getType().cast().getEleTy(); + if (!eleTy.isa()) + return emitOpError("must have !fir.char type"); + if (auto xl = getAttr(xlist())) { + auto xList = xl.cast(); + for (auto a : xList) + if (!a.isa()) + return emitOpError("values in list must be integers"); + } + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + static constexpr const char *size() { return "size"; } + static constexpr const char *value() { return "value"; } + static constexpr const char *xlist() { return "xlist"; } + + // Get the LEN attribute of this character constant + mlir::Attribute getSize() { return getAttr(size()); } + // Get the string value of this character constant + mlir::Attribute getValue() { + if (auto attr = getAttr(value())) + return attr; + return getAttr(xlist()); + } + + /// Is this a wide character literal (1 character > 8 bits) + bool isWideValue(); + }]; +} + +// Complex operations + +class fir_ArithmeticOp traits = []> : + fir_Op, + Results<(outs AnyType)> { + let parser = [{ + return impl::parseOneResultSameOperandTypeOp(parser, result); + }]; + + let printer = [{ return fir::printBinaryOp(this->getOperation(), p); }]; +} + +class fir_UnaryArithmeticOp traits = []> : + fir_Op, + Results<(outs AnyType)> { + let parser = [{ + return impl::parseOneResultSameOperandTypeOp(parser, result); + }]; + + let printer = [{ return fir::printUnaryOp(this->getOperation(), p); }]; +} + +def fir_ConstfOp : fir_Op<"constf", [NoSideEffect]>, + Results<(outs fir_RealType)> { + let summary = "create a floating point constant"; + + let description = [{ + A floating-point constant. This operation is to augment MLIR to be able + to represent APFloat values that are not supported in the standard dialect. + }]; + + let parser = [{ + fir::RealAttr flt; + mlir::Type type; + if (parser.parseLParen() || + parser.parseAttribute(flt, constAttrName(), result.attributes) || + parser.parseRParen() || parser.parseColonType(type) || + parser.addTypesToList(type, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr(constAttrName()) << " : "; + p.printType(getType()); + }]; + + let verifier = [{ + if (!getType().isa()) + return emitOpError("must be a !fir.real type"); + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef constAttrName() { return "constant"; } + + mlir::Attribute getValue() { return getAttr(constAttrName()); } + }]; +} + +class RealUnaryArithmeticOp traits = []> : + fir_UnaryArithmeticOp, + Arguments<(ins AnyRealLike:$operand)>; + +def fir_NegfOp : RealUnaryArithmeticOp<"negf">; + +class RealArithmeticOp traits = []> : + fir_ArithmeticOp, + Arguments<(ins AnyRealLike:$lhs, AnyRealLike:$rhs)>; + +def fir_AddfOp : RealArithmeticOp<"addf", [Commutative]>; +def fir_SubfOp : RealArithmeticOp<"subf">; +def fir_MulfOp : RealArithmeticOp<"mulf", [Commutative]>; +def fir_DivfOp : RealArithmeticOp<"divf">; +def fir_ModfOp : RealArithmeticOp<"modf">; +// Pow is a builtin call and not a primitive + +def fir_CmpfOp : fir_Op<"cmpf", + [NoSideEffect, SameTypeOperands, SameOperandsAndResultShape]> { + let summary = "floating-point comparison operator"; + + let description = [{ + Extends the standard floating-point comparison to handle the extended + floating-point types found in FIR. + }]; + + let arguments = (ins AnyRealLike:$lhs, AnyRealLike:$rhs); + + let results = (outs AnyLogicalLike); + + let builders = [OpBuilder< + "Builder *builder, OperationState &result, CmpFPredicate predicate," + "Value lhs, Value rhs", [{ + fir::buildCmpFOp(builder, result, predicate, lhs, rhs); + }]>]; + + let parser = [{ return fir::parseCmpfOp(parser, result); }]; + + let printer = [{ fir::printCmpfOp(p, *this); }]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef getPredicateAttrName() { + return "predicate"; + } + static CmpFPredicate getPredicateByName(llvm::StringRef name); + + CmpFPredicate getPredicate() { + return (CmpFPredicate)getAttrOfType( + getPredicateAttrName()).getInt(); + } + }]; +} + +def fir_ConstcOp : fir_Op<"constc", [NoSideEffect]>, + Results<(outs fir_ComplexType)> { + let summary = "create a complex constant"; + + let description = [{ + A complex constant. Similar to the standard dialect complex type, but this + extension allows constants with APFloat values that are not supported in + the standard dialect. + }]; + + let parser = [{ + fir::RealAttr realp; + fir::RealAttr imagp; + mlir::Type type; + if (parser.parseLParen() || + parser.parseAttribute(realp, realAttrName(), result.attributes) || + parser.parseComma() || + parser.parseAttribute(imagp, imagAttrName(), result.attributes) || + parser.parseRParen() || + parser.parseColonType(type) || + parser.addTypesToList(type, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << " (0x"; + auto f1 = getAttr(realAttrName()).cast(); + auto i1 = f1.getValue().bitcastToAPInt(); + p.getStream().write_hex(i1.getZExtValue()); + p << ", 0x"; + auto f2 = getAttr(imagAttrName()).cast(); + auto i2 = f2.getValue().bitcastToAPInt(); + p.getStream().write_hex(i2.getZExtValue()); + p << ") : "; + p.printType(getType()); + }]; + + let verifier = [{ + if (!getType().isa()) + return emitOpError("must be a !fir.complex type"); + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef realAttrName() { return "real"; } + static constexpr llvm::StringRef imagAttrName() { return "imaginary"; } + + mlir::Attribute getReal() { return getAttr(realAttrName()); } + mlir::Attribute getImaginary() { return getAttr(imagAttrName()); } + }]; +} + +class ComplexUnaryArithmeticOp traits = []> : + fir_UnaryArithmeticOp, + Arguments<(ins fir_ComplexType:$operand)>; + +def fir_NegcOp : ComplexUnaryArithmeticOp<"negc">; + +class ComplexArithmeticOp traits = []> : + fir_ArithmeticOp, + Arguments<(ins fir_ComplexType:$lhs, fir_ComplexType:$rhs)>; + +def fir_AddcOp : ComplexArithmeticOp<"addc", [Commutative]>; +def fir_SubcOp : ComplexArithmeticOp<"subc">; +def fir_MulcOp : ComplexArithmeticOp<"mulc", [Commutative]>; +def fir_DivcOp : ComplexArithmeticOp<"divc">; +// Pow is a builtin call and not a primitive + +def fir_CmpcOp : fir_Op<"cmpc", + [NoSideEffect, SameTypeOperands, SameOperandsAndResultShape]> { + let summary = "complex floating-point comparison operator"; + + let description = [{ + A complex comparison to handle complex types found in FIR. + }]; + + let arguments = (ins fir_ComplexType:$lhs, fir_ComplexType:$rhs); + + let results = (outs AnyLogicalLike); + + let parser = "return fir::parseCmpcOp(parser, result);"; + + let printer = "fir::printCmpcOp(p, *this);"; + + let builders = [OpBuilder< + "Builder *builder, OperationState &result, CmpFPredicate predicate," + "Value lhs, Value rhs", [{ + fir::buildCmpCOp(builder, result, predicate, lhs, rhs); + }]>]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef getPredicateAttrName() { + return "predicate"; + } + + CmpFPredicate getPredicate() { + return (CmpFPredicate)getAttrOfType( + getPredicateAttrName()).getInt(); + } + }]; +} + +// Other misc. operations + +def fir_AddrOfOp : fir_OneResultOp<"address_of", [NoSideEffect]> { + let summary = "convert a symbol to an SSA value"; + let description = [{ + Convert a symbol (a function or global reference) to an SSA-value to be + used in other Operations. + + %p = fir.address_of(@symbol) : !fir.ref + }]; + + let arguments = (ins SymbolRefAttr:$symbol); + + let parser = [{ + mlir::SymbolRefAttr attr; + mlir::Type type; + if (parser.parseLParen() || + parser.parseAttribute(attr, "symbol", result.attributes) || + parser.parseRParen() || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(type) || + parser.addTypeToList(type, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << " (" << getAttr("symbol") << ')'; + p.printOptionalAttrDict(getAttrs(), {"symbol"}); + p << " : " << getType(); + }]; +} + +def fir_ConvertOp : fir_OneResultOp<"convert", [NoSideEffect]> { + let summary = "encapsulates all Fortran scalar type conversions"; + let description = [{ + Generalized type conversion. Convert the ssa value from type T to type U. + Not all pairs of types have conversions. When types T and U are the same + type, this instruction is a NOP and may be folded away. + + %v = ... : i64 + %w = fir.convert %v : (i64) -> i32 + + The example truncates the value `%v` from an i64 to an i32. + }]; + + let arguments = (ins AnyType:$value); + + let assemblyFormat = [{ + $value attr-dict `:` functional-type($value, results) + }]; +} + +def FortranTypeAttr : Attr()">, + Or<[CPred<"$_self.cast().getValue().isa()">, + CPred<"$_self.cast().getValue().isa()">, + CPred<"$_self.cast().getValue().isa()">, + CPred<"$_self.cast().getValue().isa()">, + CPred<"$_self.cast().getValue().isa()">, + CPred<"$_self.cast().getValue().isa()">]>]>, + "Fortran surface type"> { + let storageType = [{ TypeAttr }]; + let returnType = "Type"; + let convertFromStorage = "$_self.getValue().cast()"; +} + +def fir_GenTypeDescOp : fir_OneResultOp<"gentypedesc", [NoSideEffect]> { + let summary = "generate a type descriptor for a given type"; + let description = [{ + Generates a constant object that is an abstract type descriptor of the + specified type. The meta-type of a type descriptor for the type `T` + is `!fir.tdesc`. + + !T = type !fir.type + %t = fir.gentypedesc !T // returns value of !fir.tdesc + }]; + + let arguments = (ins FortranTypeAttr:$in_type); + + let parser = [{ + mlir::Type intype; + if (parser.parseType(intype)) + return mlir::failure(); + result.addAttribute("in_type", mlir::TypeAttr::get(intype)); + mlir::Type restype = TypeDescType::get(intype); + if (parser.addTypeToList(restype, result.types)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr("in_type"); + p.printOptionalAttrDict(getAttrs(), {"in_type"}); + }]; + + let builders = [ + OpBuilder<"Builder *, OperationState &result, mlir::TypeAttr inty"> + ]; + + let verifier = [{ + mlir::Type resultTy = getType(); + if (auto tdesc = resultTy.dyn_cast()) { + if (tdesc.getOfTy() != getInType()) + return emitOpError("wrapped type mismatched"); + } else { + return emitOpError("must be !fir.tdesc type"); + } + return mlir::success(); + }]; + + let extraClassDeclaration = [{ + mlir::Type getInType() { + // get the type that the type descriptor describes + return getAttrOfType("in_type").getValue(); + } + }]; +} + +def fir_NoReassocOp : fir_OneResultOp<"no_reassoc", + [SameOperandsAndResultType]> { + let summary = "synthetic op to prevent reassociation"; + let description = [{ + Primitive operation meant to intrusively prevent operator reassociation. + The operation is otherwise a nop and the value returned is the same as the + argument. + + The presence of this operation prevents any local optimizations. In the + example below, this would prevent possibly replacing the multiply and add + operations with a single FMA operation. + + %98 = mulf %96, %97 : f32 + %99 = fir.no_reassoc %98 : f32 + %a0 = addf %99, %95 : f32 + }]; + + let arguments = (ins AnyType:$val); + + let assemblyFormat = "$val attr-dict `:` type($val)"; +} + +class AtMostRegion : Region< + CPred<"$_self.getBlocks().size() <= " # numBlocks>, + "region with " # numBlocks # " blocks">; + +def fir_GlobalOp : fir_Op<"global", [IsolatedFromAbove, Symbol]> { + let summary = "Global data"; + let description = [{ + A global variable or constant with initial values. + + The example creates a global variable (writable) named + `@_QV_Mquark_Vvarble` with some initial values. The initializer should + conform to the variable's type. + + fir.global @_QV_Mquark_Vvarble : tuple { + %1 = constant 1 : i32 + %2 = constant 2.0 : f32 + %3 = fir.undefined tuple + %z = constant 0 : index + %o = constant 1 : index + %4 = fir.insert_value %3, %1, %z : (tuple, i32, index) -> tuple + %5 = fir.insert_value %4, %1, %o : (tuple, f32, index) -> tuple + return %5 + } + }]; + + let arguments = (ins + StrAttr:$sym_name, + OptionalAttr:$initval, + UnitAttr:$constant, + TypeAttr:$type + ); + + let results = (outs fir_ReferenceType:$resultType); + + let regions = (region AtMostRegion<1>:$region); + + let parser = [{ + // Parse the name as a symbol reference attribute. + SymbolRefAttr nameAttr; + if (parser.parseAttribute(nameAttr, mlir::SymbolTable::getSymbolAttrName(), + result.attributes)) + return failure(); + + auto &builder = parser.getBuilder(); + auto name = nameAttr.getRootReference(); + result.attributes.back().second = builder.getStringAttr(name); + + bool simpleInitializer = false; + if (!parser.parseOptionalLParen()) { + Attribute attr; + if (parser.parseAttribute(attr, initValAttrName(), result.attributes) || + parser.parseRParen()) + return failure(); + simpleInitializer = true; + } + + if (!parser.parseOptionalKeyword(constantAttrName())) { + // if "constant" keyword then mark this as a constant, not a variable + result.addAttribute(constantAttrName(), builder.getUnitAttr()); + } + + mlir::Type globalType; + if (parser.parseColonType(globalType)) + return failure(); + + result.addAttribute(typeAttrName(), mlir::TypeAttr::get(globalType)); + + if (!simpleInitializer) { + // Parse the optional initializer body. + if (parser.parseRegion(*result.addRegion(), llvm::None, llvm::None)) + return failure(); + } + + auto refTy = AllocaOp::wrapResultType(globalType); + if (parser.addTypeToList(refTy, result.types)) + return failure(); + return success(); + }]; + + let printer = [{ + auto varName = getAttrOfType( + mlir::SymbolTable::getSymbolAttrName()).getValue(); + p << getOperationName() << " @" << varName; + if (auto iv = initval().getValueOr(Attribute())) { + p << '('; + p.printAttribute(iv); + p << ')'; + } + if (getAttr(constantAttrName())) + p << ' ' << constantAttrName(); + p << " : "; + p.printType(getType()); + Region &body = getOperation()->getRegion(0); + if (!body.empty()) + p.printRegion(body, /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/true); + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<"mlir::Builder *builder, OperationState &result," + "StringRef name, Type type, ArrayRef attrs = {}", + [{ + result.addAttribute(typeAttrName(), mlir::TypeAttr::get(type)); + result.addAttribute(mlir::SymbolTable::getSymbolAttrName(), + builder->getStringAttr(name)); + for (const auto &pair : attrs) + result.addAttribute(pair.first, pair.second); + }]> + ]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef constantAttrName() { return "constant"; } + static constexpr llvm::StringRef initValAttrName() { return "initval"; } + static constexpr llvm::StringRef typeAttrName() { return "type"; } + + mlir::Type getType() { + return getAttrOfType(typeAttrName()).getValue(); + } + + /// Append the next initializer value to the `GlobalOp` to construct + /// the variable's initial value. + void appendInitialValue(mlir::Operation *op); + + /// A GlobalOp has one region. + mlir::Region &getRegion() { return getOperation()->getRegion(0); } + + /// A GlobalOp has one block. + mlir::Block &getBlock() { return getRegion().front(); } + }]; +} + +def fir_GlobalLenOp : fir_Op<"global_len", []> { + let summary = "map a LEN parameter to a global"; + let description = [{ + A global entity (that is not an automatic data object) can have extra LEN + parameter (compile-time) constants associated with the instance's type. + These values can be bound to the global instance used `fir.global_len`. + + global @g : !fir.type { + fir.global_len len1, 10 : i32 + %1 = fir.undefined : !fir.type + return %1 : !fir.type + } + }]; + + let arguments = (ins StrAttr:$lenparam, APIntAttr:$intval); + + let parser = [{ + llvm::StringRef fieldName; + if (parser.parseOptionalKeyword(&fieldName)) { + mlir::StringAttr fieldAttr; + if (parser.parseAttribute(fieldAttr, lenParamAttrName(), + result.attributes)) + return mlir::failure(); + } else { + result.addAttribute(lenParamAttrName(), + parser.getBuilder().getStringAttr(fieldName)); + } + mlir::IntegerAttr constant; + if (parser.parseComma() || + parser.parseAttribute(constant, intAttrName(), result.attributes)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr(lenParamAttrName()) << ", " + << getAttr(intAttrName()); + }]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef lenParamAttrName() { return "lenparam"; } + static constexpr llvm::StringRef intAttrName() { return "intval"; } + }]; +} + +def fir_DispatchTableOp : fir_Op<"dispatch_table", [IsolatedFromAbove, Symbol, + ImplicitFirTerminator]> { + let summary = "Dispatch table definition"; + + let description = [{ + Define a dispatch table for a derived type with type-bound procedures. + + A dispatch table is an untyped symbol that contains a list of associations + between method identifiers and corresponding `FuncOp` symbols. + + The ordering of associations in the map is determined by the front-end. + + fir.dispatch_table @_QDTMquuzTfoo { + fir.dt_entry method1, @_QFNMquuzTfooPmethod1AfooR + fir.dt_entry method2, @_QFNMquuzTfooPmethod2AfooII + } + }]; + + let parser = [{ + // Parse the name as a symbol reference attribute. + SymbolRefAttr nameAttr; + if (parser.parseAttribute(nameAttr, mlir::SymbolTable::getSymbolAttrName(), + result.attributes)) + return failure(); + + // Convert the parsed name attr into a string attr. + result.attributes.back().second = + parser.getBuilder().getStringAttr(nameAttr.getRootReference()); + + // Parse the optional table body. + mlir::Region *body = result.addRegion(); + if (parser.parseOptionalRegion(*body, llvm::None, llvm::None)) + return mlir::failure(); + + ensureTerminator(*body, parser.getBuilder(), result.location); + return mlir::success(); + }]; + + let printer = [{ + auto tableName = getAttrOfType( + mlir::SymbolTable::getSymbolAttrName()).getValue(); + p << getOperationName() << " @" << tableName; + + Region &body = getOperation()->getRegion(0); + if (!body.empty()) + p.printRegion(body, /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/false); + }]; + + let verifier = [{ + for (auto &op : getBlock()) + if (!(isa(op) || isa(op))) + return emitOpError("dispatch table must contain dt_entry"); + return mlir::success(); + }]; + + let regions = (region SizedRegion<1>:$region); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<"mlir::Builder *builder, OperationState *result," + "StringRef name, Type type, ArrayRef attrs = {}", + [{ + result->addAttribute(mlir::SymbolTable::getSymbolAttrName(), + builder->getStringAttr(name)); + for (const auto &pair : attrs) + result->addAttribute(pair.first, pair.second); + }]> + ]; + + let extraClassDeclaration = [{ + /// Append a dispatch table entry to the table. + void appendTableEntry(mlir::Operation *op); + + mlir::Region &getRegion() { + return this->getOperation()->getRegion(0); + } + + mlir::Block &getBlock() { + return getRegion().front(); + } + }]; +} + +def fir_DTEntryOp : fir_Op<"dt_entry", []> { + let summary = "map entry in a dispatch table"; + + let description = [{ + An entry in a dispatch table. Allows a function symbol to be bound + to a specifier method identifier. A dispatch operation uses the dynamic + type of a distinguished argument to determine an exact dispatch table + and uses the method identifier to select the type-bound procedure to + be called. + + fir.dt_entry method_name, @uniquedProcedure + }]; + + let arguments = (ins StrAttr:$method, SymbolRefAttr:$proc); + + let parser = [{ + llvm::StringRef methodName; + // allow `methodName` or `"methodName"` + if (parser.parseOptionalKeyword(&methodName)) { + mlir::StringAttr methodAttr; + if (parser.parseAttribute(methodAttr, methodAttrName(), + result.attributes)) + return mlir::failure(); + } else { + result.addAttribute(methodAttrName(), + parser.getBuilder().getStringAttr(methodName)); + } + mlir::SymbolRefAttr calleeAttr; + if (parser.parseComma() || + parser.parseAttribute(calleeAttr, procAttrName(), result.attributes)) + return mlir::failure(); + return mlir::success(); + }]; + + let printer = [{ + p << getOperationName() << ' ' << getAttr(methodAttrName()) << ", " + << getAttr(procAttrName()); + }]; + + let extraClassDeclaration = [{ + static constexpr llvm::StringRef methodAttrName() { return "method"; } + static constexpr llvm::StringRef procAttrName() { return "proc"; } + }]; +} + +#endif diff --git a/include/flang/Optimizer/Dialect/FIROpsSupport.h b/include/flang/Optimizer/Dialect/FIROpsSupport.h new file mode 100644 index 000000000000..b8bb5480d157 --- /dev/null +++ b/include/flang/Optimizer/Dialect/FIROpsSupport.h @@ -0,0 +1,73 @@ +//===-- Optimizer/Dialect/FIROpsSupport.h -- FIR op support -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef OPTIMIZER_DIALECT_FIROPSSUPPORT_H +#define OPTIMIZER_DIALECT_FIROPSSUPPORT_H + +#include "flang/Optimizer/Dialect/FIROps.h" +#include "mlir/Dialect/StandardOps/IR/Ops.h" + +namespace fir { + +/// return true iff the Operation is a non-volatile LoadOp +inline bool nonVolatileLoad(mlir::Operation *op) { + if (auto load = dyn_cast(op)) + return !load.getAttr("volatile"); + return false; +} + +/// return true iff the Operation is a fir::CallOp, fir::DispatchOp, +/// mlir::CallOp, or mlir::CallIndirectOp and not pure +/// NB: this is not the same as `!pureCall(op)` +inline bool impureCall(mlir::Operation *op) { + // Should we also auto-detect that the called function is pure if its + // arguments are not references? For now, rely on a "pure" attribute. + if (auto call = dyn_cast(op)) + return !call.getAttr("pure"); + if (auto dispatch = dyn_cast(op)) + return !dispatch.getAttr("pure"); + if (auto call = dyn_cast(op)) + return !call.getAttr("pure"); + if (auto icall = dyn_cast(op)) + return !icall.getAttr("pure"); + return false; +} + +/// return true iff the Operation is a fir::CallOp, fir::DispatchOp, +/// mlir::CallOp, or mlir::CallIndirectOp and is also pure. +/// NB: this is not the same as `!impureCall(op)` +inline bool pureCall(mlir::Operation *op) { + // Should we also auto-detect that the called function is pure if its + // arguments are not references? For now, rely on a "pure" attribute. + if (auto call = dyn_cast(op)) + return bool(call.getAttr("pure")); + if (auto dispatch = dyn_cast(op)) + return bool(dispatch.getAttr("pure")); + if (auto call = dyn_cast(op)) + return bool(call.getAttr("pure")); + if (auto icall = dyn_cast(op)) + return bool(icall.getAttr("pure")); + return false; +} + +/// Get or create a FuncOp in a module. +/// +/// If `module` already contains FuncOp `name`, it is returned. Otherwise, a new +/// FuncOp is created, and that new FuncOp is returned. +mlir::FuncOp createFuncOp(mlir::Location loc, mlir::ModuleOp module, + llvm::StringRef name, mlir::FunctionType type, + llvm::ArrayRef attrs = {}); + +/// Get or create a GlobalOp in a module. +fir::GlobalOp createGlobalOp(mlir::Location loc, mlir::ModuleOp module, + llvm::StringRef name, mlir::Type type, + llvm::ArrayRef attrs = {}); + +} // namespace fir + +#endif // OPTIMIZER_DIALECT_FIROPSSUPPORT_H diff --git a/include/flang/Optimizer/Dialect/FIRType.h b/include/flang/Optimizer/Dialect/FIRType.h new file mode 100644 index 000000000000..6b85635897b3 --- /dev/null +++ b/include/flang/Optimizer/Dialect/FIRType.h @@ -0,0 +1,357 @@ +//===-- Optimizer/Dialect/FIRType.h -- FIR types ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef OPTIMIZER_DIALECT_FIRTYPE_H +#define OPTIMIZER_DIALECT_FIRTYPE_H + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/Types.h" +#include "llvm/ADT/SmallVector.h" + +namespace llvm { +class raw_ostream; +class StringRef; +template +class ArrayRef; +class hash_code; +} // namespace llvm + +namespace mlir { +class DialectAsmParser; +class DialectAsmPrinter; +} // namespace mlir + +namespace fir { + +class FIROpsDialect; + +using KindTy = int; + +namespace detail { +struct BoxTypeStorage; +struct BoxCharTypeStorage; +struct BoxProcTypeStorage; +struct CharacterTypeStorage; +struct CplxTypeStorage; +struct DimsTypeStorage; +struct FieldTypeStorage; +struct HeapTypeStorage; +struct IntTypeStorage; +struct LenTypeStorage; +struct LogicalTypeStorage; +struct PointerTypeStorage; +struct RealTypeStorage; +struct RecordTypeStorage; +struct ReferenceTypeStorage; +struct SequenceTypeStorage; +struct TypeDescTypeStorage; +} // namespace detail + +/// Integral identifier for all the types comprising the FIR type system +enum TypeKind { + // The enum starts at the range reserved for this dialect. + FIR_TYPE = mlir::Type::FIRST_FIR_TYPE, + FIR_BOX, // (static) descriptor + FIR_BOXCHAR, // CHARACTER pointer and length + FIR_BOXPROC, // procedure with host association + FIR_CHARACTER, // intrinsic type + FIR_COMPLEX, // intrinsic type + FIR_DERIVED, // derived + FIR_DIMS, + FIR_FIELD, + FIR_HEAP, + FIR_INT, // intrinsic type + FIR_LEN, + FIR_LOGICAL, // intrinsic type + FIR_POINTER, // POINTER attr + FIR_REAL, // intrinsic type + FIR_REFERENCE, + FIR_SEQUENCE, // DIMENSION attr + FIR_TYPEDESC, +}; + +/// Is `t` any of the FIR dialect types? +bool isa_fir_type(mlir::Type t); + +/// Is `t` any of the Standard dialect types? +bool isa_std_type(mlir::Type t); + +/// Is `t` any of the FIR dialect or Standard dialect types? +bool isa_fir_or_std_type(mlir::Type t); + +/// Is `t` a FIR dialect type that implies a memory (de)reference? +bool isaMemref(mlir::Type t); + +/// Is `t` a FIR dialect aggregate type? +bool isanAggregate(mlir::Type t); + +/// Extract the `Type` pointed to from a FIR memory reference type. If `t` is +/// not a memory reference type, then returns a null `Type`. +mlir::Type dyn_cast_ptrEleTy(mlir::Type t); + +/// Boilerplate mixin template +template +struct IntrinsicTypeMixin { + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { return Id; } +}; + +class CharacterType + : public mlir::Type::TypeBase, + public IntrinsicTypeMixin { +public: + using Base::Base; + static CharacterType get(mlir::MLIRContext *ctxt, KindTy kind); + KindTy getFKind() const; +}; + +class CplxType : public mlir::Type::TypeBase, + public IntrinsicTypeMixin { +public: + using Base::Base; + static CplxType get(mlir::MLIRContext *ctxt, KindTy kind); + KindTy getFKind() const; +}; + +class IntType + : public mlir::Type::TypeBase, + public IntrinsicTypeMixin { +public: + using Base::Base; + static IntType get(mlir::MLIRContext *ctxt, KindTy kind); + KindTy getFKind() const; +}; + +class LogicalType + : public mlir::Type::TypeBase, + public IntrinsicTypeMixin { +public: + using Base::Base; + static LogicalType get(mlir::MLIRContext *ctxt, KindTy kind); + KindTy getFKind() const; +}; + +class RealType : public mlir::Type::TypeBase, + public IntrinsicTypeMixin { +public: + using Base::Base; + static RealType get(mlir::MLIRContext *ctxt, KindTy kind); + KindTy getFKind() const; +}; + +// FIR support types + +class BoxType + : public mlir::Type::TypeBase { +public: + using Base::Base; + static BoxType get(mlir::Type eleTy, mlir::AffineMapAttr map = {}); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_BOX; } + mlir::Type getEleTy() const; + mlir::AffineMapAttr getLayoutMap() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, mlir::Type eleTy, + mlir::AffineMapAttr map); +}; + +class BoxCharType : public mlir::Type::TypeBase { +public: + using Base::Base; + static BoxCharType get(mlir::MLIRContext *ctxt, KindTy kind); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_BOXCHAR; } + CharacterType getEleTy() const; +}; + +class BoxProcType : public mlir::Type::TypeBase { +public: + using Base::Base; + static BoxProcType get(mlir::Type eleTy); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_BOXPROC; } + mlir::Type getEleTy() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy); +}; + +class DimsType : public mlir::Type::TypeBase { +public: + using Base::Base; + static DimsType get(mlir::MLIRContext *ctx, unsigned rank); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_DIMS; } + + /// returns -1 if the rank is unknown + int getRank() const; +}; + +class FieldType : public mlir::Type::TypeBase { +public: + using Base::Base; + static FieldType get(mlir::MLIRContext *ctxt, KindTy _ = 0); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_FIELD; } +}; + +class HeapType : public mlir::Type::TypeBase { +public: + using Base::Base; + static HeapType get(mlir::Type elementType); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_HEAP; } + + mlir::Type getEleTy() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy); +}; + +class LenType + : public mlir::Type::TypeBase { +public: + using Base::Base; + static LenType get(mlir::MLIRContext *ctxt, KindTy _ = 0); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_LEN; } +}; + +class PointerType : public mlir::Type::TypeBase { +public: + using Base::Base; + static PointerType get(mlir::Type elementType); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_POINTER; } + + mlir::Type getEleTy() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy); +}; + +class ReferenceType + : public mlir::Type::TypeBase { +public: + using Base::Base; + static ReferenceType get(mlir::Type elementType); + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_REFERENCE; } + + mlir::Type getEleTy() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy); +}; + +/// A sequence type is a multi-dimensional array of values. The sequence type +/// may have an unknown number of dimensions or the extent of dimensions may be +/// unknown. A sequence type models a Fortran array entity, giving it a type in +/// FIR. A sequence type is assumed to be stored in a column-major order, which +/// differs from LLVM IR and other dialects of MLIR. +class SequenceType : public mlir::Type::TypeBase { +public: + using Base::Base; + using Extent = int64_t; + using Shape = llvm::SmallVector; + + /// Return a sequence type with the specified shape and element type + static SequenceType get(const Shape &shape, mlir::Type elementType, + mlir::AffineMapAttr map = {}); + + /// The element type of this sequence + mlir::Type getEleTy() const; + + /// The shape of the sequence. If the sequence has an unknown shape, the shape + /// returned will be empty. + Shape getShape() const; + + mlir::AffineMapAttr getLayoutMap() const; + + /// The number of dimensions of the sequence + unsigned getDimension() const { return getShape().size(); } + + /// The value `-1` represents an unknown extent for a dimension + static constexpr Extent getUnknownExtent() { return -1; } + + static bool kindof(unsigned kind) { return kind == TypeKind::FIR_SEQUENCE; } + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *attributeStorage, + const Shape &shape, mlir::Type eleTy, + mlir::AffineMapAttr map); +}; + +bool operator==(const SequenceType::Shape &, const SequenceType::Shape &); +llvm::hash_code hash_value(const SequenceType::Extent &); +llvm::hash_code hash_value(const SequenceType::Shape &); + +class TypeDescType : public mlir::Type::TypeBase { +public: + using Base::Base; + static TypeDescType get(mlir::Type ofType); + static constexpr bool kindof(unsigned kind) { + return kind == TypeKind::FIR_TYPEDESC; + } + mlir::Type getOfTy() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type ofType); +}; + +// Derived types + +class RecordType : public mlir::Type::TypeBase { +public: + using Base::Base; + using TypePair = std::pair; + using TypeList = std::vector; + + llvm::StringRef getName(); + TypeList getTypeList(); + TypeList getLenParamList(); + + mlir::Type getType(llvm::StringRef ident); + mlir::Type getType(unsigned index) { + assert(index < getNumFields()); + return getTypeList()[index].second; + } + unsigned getNumFields() { return getTypeList().size(); } + unsigned getNumLenParams() { return getLenParamList().size(); } + + static RecordType get(mlir::MLIRContext *ctxt, llvm::StringRef name); + void finalize(llvm::ArrayRef lenPList, + llvm::ArrayRef typeList); + static constexpr bool kindof(unsigned kind) { return kind == getId(); } + static constexpr unsigned getId() { return TypeKind::FIR_DERIVED; } + + detail::RecordTypeStorage const *uniqueKey() const; + + static mlir::LogicalResult + verifyConstructionInvariants(const mlir::AttributeStorage *, + llvm::StringRef name); +}; + +mlir::Type parseFirType(FIROpsDialect *, mlir::DialectAsmParser &parser); + +void printFirType(FIROpsDialect *, mlir::Type ty, mlir::DialectAsmPrinter &p); + +} // namespace fir + +#endif // OPTIMIZER_DIALECT_FIRTYPE_H diff --git a/include/flang/Optimizer/Support/KindMapping.h b/include/flang/Optimizer/Support/KindMapping.h new file mode 100644 index 000000000000..7bec822ebfbd --- /dev/null +++ b/include/flang/Optimizer/Support/KindMapping.h @@ -0,0 +1,89 @@ +//===-- Optimizer/Support/KindMapping.h -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef OPTIMIZER_SUPPORT_KINDMAPPING_H +#define OPTIMIZER_SUPPORT_KINDMAPPING_H + +#include "llvm/IR/Type.h" +#include + +namespace llvm { +template +class Optional; +struct fltSemantics; +} // namespace llvm + +namespace mlir { +class MLIRContext; +} // namespace mlir + +namespace fir { + +/// The kind mapping is an encoded string that informs FIR how the Fortran KIND +/// values from the front-end should be converted to LLVM IR types. This +/// encoding allows the mapping from front-end KIND values to backend LLVM IR +/// types to be customized by the front-end. +/// +/// The provided string uses the following syntax. +/// +/// intrinsic-key `:` kind-value (`,` intrinsic-key `:` kind-value)* +/// +/// intrinsic-key is a single character for the intrinsic type. +/// 'i' : INTEGER (size in bits) +/// 'l' : LOGICAL (size in bits) +/// 'a' : CHARACTER (size in bits) +/// 'r' : REAL (encoding value) +/// 'c' : COMPLEX (encoding value) +/// +/// kind-value is either an unsigned integer (for 'i', 'l', and 'a') or one of +/// 'Half', 'Float', 'Double', 'X86_FP80', or 'FP128' (for 'r' and 'c'). +/// +/// If LLVM adds support for new floating-point types, the final list should be +/// extended. +class KindMapping { +public: + using KindTy = unsigned; + using Bitsize = unsigned; + using LLVMTypeID = llvm::Type::TypeID; + using MatchResult = llvm::Optional; + + explicit KindMapping(mlir::MLIRContext *context); + explicit KindMapping(mlir::MLIRContext *context, llvm::StringRef map); + + /// Get the size in bits of !fir.char + Bitsize getCharacterBitsize(KindTy kind); + + /// Get the size in bits of !fir.int + Bitsize getIntegerBitsize(KindTy kind); + + /// Get the size in bits of !fir.logical + Bitsize getLogicalBitsize(KindTy kind); + + /// Get the LLVM Type::TypeID of !fir.real + LLVMTypeID getRealTypeID(KindTy kind); + + /// Get the LLVM Type::TypeID of !fir.complex + LLVMTypeID getComplexTypeID(KindTy kind); + + mlir::MLIRContext *getContext() const { return context; } + + /// Get the float semantics of !fir.real + const llvm::fltSemantics &getFloatSemantics(KindTy kind); + +private: + MatchResult badMapString(llvm::Twine const &ptr); + MatchResult parse(llvm::StringRef kindMap); + + mlir::MLIRContext *context; + std::map> intMap; + std::map> floatMap; +}; + +} // namespace fir + +#endif // OPTIMIZER_SUPPORT_KINDMAPPING_H diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fae2eed92fa8..f2fe30dca5a1 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -12,3 +12,7 @@ add_subdirectory(Decimal) add_subdirectory(Lower) add_subdirectory(Parser) add_subdirectory(Semantics) + +if(LINK_WITH_FIR) + add_subdirectory(Optimizer) +endif() diff --git a/lib/Fir/.clang-format b/lib/Fir/.clang-format deleted file mode 100644 index a74fda4b6734..000000000000 --- a/lib/Fir/.clang-format +++ /dev/null @@ -1,2 +0,0 @@ -BasedOnStyle: LLVM -AlwaysBreakTemplateDeclarations: Yes diff --git a/lib/Optimizer/CMakeLists.txt b/lib/Optimizer/CMakeLists.txt new file mode 100644 index 000000000000..2f8cd269dd9f --- /dev/null +++ b/lib/Optimizer/CMakeLists.txt @@ -0,0 +1,5 @@ +# Sources generated by tablegen have unused parameters. +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter") + +add_subdirectory(Dialect) +add_subdirectory(Support) diff --git a/lib/Optimizer/Dialect/CMakeLists.txt b/lib/Optimizer/Dialect/CMakeLists.txt new file mode 100644 index 000000000000..711fc64c5a2a --- /dev/null +++ b/lib/Optimizer/Dialect/CMakeLists.txt @@ -0,0 +1,27 @@ +add_llvm_library(FIRDialect + FIRAttr.cpp + FIRDialect.cpp + FIROps.cpp + FIRType.cpp +) + +add_dependencies(FIRDialect FIROpsIncGen) + +target_link_libraries(FIRDialect + MLIRTargetLLVMIR + MLIRTargetLLVMIRModuleTranslation + MLIREDSC + MLIRExecutionEngine + MLIRParser + MLIRSupport + MLIRStandardToLLVM + MLIRTransforms + LLVMAsmParser + LLVMAsmPrinter + LLVMRemarks +) + +install (TARGETS FIRDialect + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) diff --git a/lib/Optimizer/Dialect/FIRAttr.cpp b/lib/Optimizer/Dialect/FIRAttr.cpp new file mode 100644 index 000000000000..c7695de1d5aa --- /dev/null +++ b/lib/Optimizer/Dialect/FIRAttr.cpp @@ -0,0 +1,236 @@ +//===-- FIRAttr.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Dialect/FIRAttr.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" +#include "flang/Optimizer/Dialect/FIRType.h" +#include "flang/Optimizer/Support/KindMapping.h" +#include "mlir/IR/AttributeSupport.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/Types.h" +#include "mlir/Parser.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" + +using namespace fir; + +namespace fir { +namespace detail { + +struct RealAttributeStorage : public mlir::AttributeStorage { + using KeyTy = std::pair; + + RealAttributeStorage(int kind, const llvm::APFloat &value) + : kind(kind), value(value) {} + RealAttributeStorage(const KeyTy &key) + : RealAttributeStorage(key.first, key.second) {} + + static unsigned hashKey(const KeyTy &key) { + auto hashVal = llvm::hash_combine(key.first); + return llvm::hash_combine(hashVal, key.second); + } + + bool operator==(const KeyTy &key) const { + return key.first == kind && + key.second.compare(value) == llvm::APFloatBase::cmpEqual; + } + + static RealAttributeStorage * + construct(mlir::AttributeStorageAllocator &allocator, const KeyTy &key) { + return new (allocator.allocate()) + RealAttributeStorage(key); + } + + int getFKind() const { return kind; } + llvm::APFloat getValue() const { return value; } + +private: + int kind; + llvm::APFloat value; +}; + +/// An attribute representing a reference to a type. +struct TypeAttributeStorage : public mlir::AttributeStorage { + using KeyTy = mlir::Type; + + TypeAttributeStorage(mlir::Type value) : value(value) { + assert(value && "must not be of Type null"); + } + + /// Key equality function. + bool operator==(const KeyTy &key) const { return key == value; } + + /// Construct a new storage instance. + static TypeAttributeStorage * + construct(mlir::AttributeStorageAllocator &allocator, KeyTy key) { + return new (allocator.allocate()) + TypeAttributeStorage(key); + } + + mlir::Type getType() const { return value; } + +private: + mlir::Type value; +}; +} // namespace detail + +ExactTypeAttr ExactTypeAttr::get(mlir::Type value) { + return Base::get(value.getContext(), FIR_EXACTTYPE, value); +} + +mlir::Type ExactTypeAttr::getType() const { return getImpl()->getType(); } + +SubclassAttr SubclassAttr::get(mlir::Type value) { + return Base::get(value.getContext(), FIR_SUBCLASS, value); +} + +mlir::Type SubclassAttr::getType() const { return getImpl()->getType(); } + +using AttributeUniquer = mlir::detail::AttributeUniquer; + +ClosedIntervalAttr ClosedIntervalAttr::get(mlir::MLIRContext *ctxt) { + return AttributeUniquer::get(ctxt, getId()); +} + +UpperBoundAttr UpperBoundAttr::get(mlir::MLIRContext *ctxt) { + return AttributeUniquer::get(ctxt, getId()); +} + +LowerBoundAttr LowerBoundAttr::get(mlir::MLIRContext *ctxt) { + return AttributeUniquer::get(ctxt, getId()); +} + +PointIntervalAttr PointIntervalAttr::get(mlir::MLIRContext *ctxt) { + return AttributeUniquer::get(ctxt, getId()); +} + +// RealAttr + +RealAttr RealAttr::get(mlir::MLIRContext *ctxt, + const RealAttr::ValueType &key) { + return Base::get(ctxt, getId(), key); +} + +int RealAttr::getFKind() const { return getImpl()->getFKind(); } + +llvm::APFloat RealAttr::getValue() const { return getImpl()->getValue(); } + +// FIR attribute parsing + +namespace { +mlir::Attribute parseFirRealAttr(FIROpsDialect *dialect, + mlir::DialectAsmParser &parser, + mlir::Type type) { + int kind; + if (parser.parseLess() || parser.parseInteger(kind) || parser.parseComma()) { + parser.emitError(parser.getNameLoc(), "expected '<' kind ','"); + return {}; + } + KindMapping kindMap(dialect->getContext()); + llvm::APFloat value(0.); + if (parser.parseOptionalKeyword("i")) { + // `i` not present, so literal float must be present + double dontCare; + if (parser.parseFloat(dontCare) || parser.parseGreater()) { + parser.emitError(parser.getNameLoc(), "expected real constant '>'"); + return {}; + } + auto fltStr = parser.getFullSymbolSpec() + .drop_until([](char c) { return c == ','; }) + .drop_front() + .drop_while([](char c) { return c == ' ' || c == '\t'; }) + .take_until([](char c) { + return c == '>' || c == ' ' || c == '\t'; + }); + value = llvm::APFloat(kindMap.getFloatSemantics(kind), fltStr); + } else { + // `i` is present, so literal bitstring (hex) must be present + llvm::StringRef hex; + if (parser.parseKeyword(&hex) || parser.parseGreater()) { + parser.emitError(parser.getNameLoc(), "expected real constant '>'"); + return {}; + } + auto bits = llvm::APInt(kind * 8, hex.drop_front(), 16); + value = llvm::APFloat(kindMap.getFloatSemantics(kind), bits); + } + return RealAttr::get(dialect->getContext(), {kind, value}); +} +} // namespace + +mlir::Attribute parseFirAttribute(FIROpsDialect *dialect, + mlir::DialectAsmParser &parser, + mlir::Type type) { + auto loc = parser.getNameLoc(); + llvm::StringRef attrName; + if (parser.parseKeyword(&attrName)) { + parser.emitError(loc, "expected an attribute name"); + return {}; + } + + if (attrName == ExactTypeAttr::getAttrName()) { + mlir::Type type; + if (parser.parseLess() || parser.parseType(type) || parser.parseGreater()) + parser.emitError(loc, "expected a type"); + return ExactTypeAttr::get(type); + } + if (attrName == SubclassAttr::getAttrName()) { + mlir::Type type; + if (parser.parseLess() || parser.parseType(type) || parser.parseGreater()) + parser.emitError(loc, "expected a subtype"); + return SubclassAttr::get(type); + } + if (attrName == PointIntervalAttr::getAttrName()) + return PointIntervalAttr::get(dialect->getContext()); + if (attrName == LowerBoundAttr::getAttrName()) + return LowerBoundAttr::get(dialect->getContext()); + if (attrName == UpperBoundAttr::getAttrName()) + return UpperBoundAttr::get(dialect->getContext()); + if (attrName == ClosedIntervalAttr::getAttrName()) + return ClosedIntervalAttr::get(dialect->getContext()); + if (attrName == RealAttr::getAttrName()) + return parseFirRealAttr(dialect, parser, type); + + llvm::Twine msg{"unknown FIR attribute: "}; + parser.emitError(loc, msg.concat(attrName)); + return {}; +} + +// FIR attribute pretty printer + +void printFirAttribute(FIROpsDialect *dialect, mlir::Attribute attr, + mlir::DialectAsmPrinter &p) { + auto &os = p.getStream(); + if (auto exact = attr.dyn_cast()) { + os << fir::ExactTypeAttr::getAttrName() << '<'; + p.printType(exact.getType()); + os << '>'; + } else if (auto sub = attr.dyn_cast()) { + os << fir::SubclassAttr::getAttrName() << '<'; + p.printType(sub.getType()); + os << '>'; + } else if (attr.dyn_cast_or_null()) { + os << fir::PointIntervalAttr::getAttrName(); + } else if (attr.dyn_cast_or_null()) { + os << fir::ClosedIntervalAttr::getAttrName(); + } else if (attr.dyn_cast_or_null()) { + os << fir::LowerBoundAttr::getAttrName(); + } else if (attr.dyn_cast_or_null()) { + os << fir::UpperBoundAttr::getAttrName(); + } else if (auto a = attr.dyn_cast_or_null()) { + os << fir::RealAttr::getAttrName() << '<' << a.getFKind() << ", i x"; + llvm::SmallString<40> ss; + a.getValue().bitcastToAPInt().toStringUnsigned(ss, 16); + os << ss << '>'; + } else { + assert(false && "attribute pretty-printer is not implemented"); + } +} + +} // namespace fir diff --git a/lib/Optimizer/Dialect/FIRDialect.cpp b/lib/Optimizer/Dialect/FIRDialect.cpp new file mode 100644 index 000000000000..e6b04072bd0b --- /dev/null +++ b/lib/Optimizer/Dialect/FIRDialect.cpp @@ -0,0 +1,77 @@ +//===-- FIRDialect.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Dialect/FIRDialect.h" +#include "flang/Optimizer/Dialect/FIRAttr.h" +#include "flang/Optimizer/Dialect/FIROps.h" +#include "flang/Optimizer/Dialect/FIRType.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/IR/StandardTypes.h" +#include "mlir/Transforms/SideEffectsInterface.h" + +using namespace fir; + +namespace { + +template +void selectBuild(mlir::OpBuilder *builder, mlir::OperationState *result, + mlir::Value condition, + llvm::ArrayRef tuples) { + result->addOperands(condition); + for (auto &tup : tuples) { + auto *cond{std::get(tup)}; + result->addOperands(cond); + } + // Note: succs must be added *after* operands + for (auto &tup : tuples) { + auto *block{std::get(tup)}; + assert(block); + auto blkArgs{std::get>(tup)}; + result->addSuccessor(block, blkArgs); + } +} + +} // namespace + +fir::FIROpsDialect::FIROpsDialect(mlir::MLIRContext *ctx) + : mlir::Dialect("fir", ctx) { + addTypes(); + addAttributes(); + addOperations< +#define GET_OP_LIST +#include "flang/Optimizer/Dialect/FIROps.cpp.inc" + >(); +} + +// anchor the class vtable to this compilation unit +fir::FIROpsDialect::~FIROpsDialect() { + // do nothing +} + +mlir::Type fir::FIROpsDialect::parseType(mlir::DialectAsmParser &parser) const { + return parseFirType(const_cast(this), parser); +} + +void fir::FIROpsDialect::printType(mlir::Type ty, + mlir::DialectAsmPrinter &p) const { + return printFirType(const_cast(this), ty, p); +} + +mlir::Attribute +fir::FIROpsDialect::parseAttribute(mlir::DialectAsmParser &parser, + mlir::Type type) const { + return parseFirAttribute(const_cast(this), parser, type); +} + +void fir::FIROpsDialect::printAttribute(mlir::Attribute attr, + mlir::DialectAsmPrinter &p) const { + printFirAttribute(const_cast(this), attr, p); +} diff --git a/lib/Optimizer/Dialect/FIROps.cpp b/lib/Optimizer/Dialect/FIROps.cpp new file mode 100644 index 000000000000..aa3bbcfd5942 --- /dev/null +++ b/lib/Optimizer/Dialect/FIROps.cpp @@ -0,0 +1,573 @@ +//===-- 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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Dialect/FIROps.h" +#include "flang/Optimizer/Dialect/FIRAttr.h" +#include "flang/Optimizer/Dialect/FIROpsSupport.h" +#include "flang/Optimizer/Dialect/FIRType.h" +#include "mlir/Dialect/StandardOps/IR/Ops.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/Function.h" +#include "mlir/IR/Module.h" +#include "mlir/IR/StandardTypes.h" +#include "mlir/IR/SymbolTable.h" +#include "llvm/ADT/StringSwitch.h" + +namespace fir { + +/// return true if the sequence type is abstract or the record type is malformed +/// or contains an abstract sequence type +static bool verifyInType(mlir::Type inType, + llvm::SmallVectorImpl &visited) { + if (auto st = inType.dyn_cast()) { + auto shape = st.getShape(); + if (shape.size() == 0) + return true; + for (auto ext : shape) + if (ext < 0) + return true; + } else if (auto rt = inType.dyn_cast()) { + // don't recurse if we're already visiting this one + for (auto name : visited) + if (name == 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(); + } else if (auto rt = inType.dyn_cast()) { + return verifyInType(rt.getEleTy(), visited); + } + return false; +} + +static bool verifyRecordLenParams(mlir::Type inType, unsigned numLenParams) { + if (numLenParams > 0) { + if (auto rt = inType.dyn_cast()) + return numLenParams != rt.getNumLenParams(); + return true; + } + return false; +} + +// AllocaOp + +mlir::Type AllocaOp::getAllocatedType() { + return getType().cast().getEleTy(); +} + +/// Create a legal memory reference as return type +mlir::Type AllocaOp::wrapResultType(mlir::Type intype) { + // FIR semantics: memory references to memory references are disallowed + if (intype.dyn_cast()) + return {}; + return ReferenceType::get(intype); +} + +mlir::Type AllocaOp::getRefTy(mlir::Type ty) { return ReferenceType::get(ty); } + +// AllocMemOp + +mlir::Type AllocMemOp::getAllocatedType() { + return getType().cast().getEleTy(); +} + +mlir::Type AllocMemOp::getRefTy(mlir::Type ty) { return HeapType::get(ty); } + +/// Create a legal heap reference as return type +mlir::Type AllocMemOp::wrapResultType(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 (intype.dyn_cast() || intype.dyn_cast() || + intype.dyn_cast() || intype.dyn_cast()) + return {}; + return HeapType::get(intype); +} + +// BoxDimsOp + +/// Get the result types packed in a tuple tuple +mlir::Type BoxDimsOp::getTupleType() { + llvm::SmallVector triple{ + getResult(0).getType(), getResult(1).getType(), getResult(2).getType()}; + return mlir::TupleType::get(triple, getContext()); +} + +// CallOp + +void printCallOp(mlir::OpAsmPrinter &p, fir::CallOp &op) { + auto callee = op.callee(); + bool isDirect = callee.hasValue(); + p << op.getOperationName() << ' '; + if (isDirect) + p << callee.getValue(); + else + p << op.getOperand(0); + p << '('; + p.printOperands(llvm::drop_begin(op.getOperands(), isDirect ? 0 : 1)); + p << ')'; + p.printOptionalAttrDict(op.getAttrs(), {"callee"}); + auto resultTypes{op.getResultTypes()}; + llvm::SmallVector argTypes( + llvm::drop_begin(op.getOperandTypes(), isDirect ? 0 : 1)); + p << " : " << FunctionType::get(argTypes, resultTypes, op.getContext()); +} + +mlir::ParseResult parseCallOp(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + llvm::SmallVector operands; + + if (parser.parseOperandList(operands)) + return mlir::failure(); + bool isDirect = operands.empty(); + SmallVector attrs; + SymbolRefAttr funcAttr; + + if (isDirect) + if (parser.parseAttribute(funcAttr, "callee", attrs)) + return mlir::failure(); + Type type; + + if (parser.parseOperandList(operands, mlir::OpAsmParser::Delimiter::Paren) || + parser.parseOptionalAttrDict(attrs) || parser.parseColon() || + parser.parseType(type)) + return mlir::failure(); + + auto funcType = type.dyn_cast(); + if (!funcType) + return parser.emitError(parser.getNameLoc(), "expected function type"); + if (isDirect) { + if (parser.resolveOperands(operands, funcType.getInputs(), + parser.getNameLoc(), result.operands)) + return mlir::failure(); + } else { + auto funcArgs = + llvm::ArrayRef(operands).drop_front(); + llvm::SmallVector resultArgs( + result.operands.begin() + (result.operands.empty() ? 0 : 1), + result.operands.end()); + if (parser.resolveOperand(operands[0], funcType, result.operands) || + parser.resolveOperands(funcArgs, funcType.getInputs(), + parser.getNameLoc(), resultArgs)) + return mlir::failure(); + } + result.addTypes(funcType.getResults()); + result.attributes = attrs; + return mlir::success(); +} + +// CmpfOp + +fir::CmpFPredicate CmpfOp::getPredicateByName(llvm::StringRef name) { + return llvm::StringSwitch(name) + .Case("false", CmpFPredicate::AlwaysFalse) + .Case("oeq", CmpFPredicate::OEQ) + .Case("ogt", CmpFPredicate::OGT) + .Case("oge", CmpFPredicate::OGE) + .Case("olt", CmpFPredicate::OLT) + .Case("ole", CmpFPredicate::OLE) + .Case("one", CmpFPredicate::ONE) + .Case("ord", CmpFPredicate::ORD) + .Case("ueq", CmpFPredicate::UEQ) + .Case("ugt", CmpFPredicate::UGT) + .Case("uge", CmpFPredicate::UGE) + .Case("ult", CmpFPredicate::ULT) + .Case("ule", CmpFPredicate::ULE) + .Case("une", CmpFPredicate::UNE) + .Case("uno", CmpFPredicate::UNO) + .Case("true", CmpFPredicate::AlwaysTrue) + .Default(CmpFPredicate::NumPredicates); +} + +void buildCmpFOp(Builder *builder, OperationState &result, + CmpFPredicate predicate, Value lhs, Value rhs) { + result.addOperands({lhs, rhs}); + result.types.push_back(builder->getI1Type()); + result.addAttribute( + CmpfOp::getPredicateAttrName(), + builder->getI64IntegerAttr(static_cast(predicate))); +} + +template +void printCmpOp(OpAsmPrinter &p, OPTY op) { + static const char *predicateNames[] = { + /*AlwaysFalse*/ "false", + /*OEQ*/ "oeq", + /*OGT*/ "ogt", + /*OGE*/ "oge", + /*OLT*/ "olt", + /*OLE*/ "ole", + /*ONE*/ "one", + /*ORD*/ "ord", + /*UEQ*/ "ueq", + /*UGT*/ "ugt", + /*UGE*/ "uge", + /*ULT*/ "ult", + /*ULE*/ "ule", + /*UNE*/ "une", + /*UNO*/ "uno", + /*AlwaysTrue*/ "true", + }; + static_assert(std::extent::value == + (size_t)fir::CmpFPredicate::NumPredicates, + "wrong number of predicate names"); + p << op.getOperationName() << ' '; + auto predicateValue = + op.template getAttrOfType(OPTY::getPredicateAttrName()) + .getInt(); + assert(predicateValue >= static_cast(CmpFPredicate::FirstValidValue) && + predicateValue < static_cast(CmpFPredicate::NumPredicates) && + "unknown predicate index"); + Builder b(op.getContext()); + auto predicateStringAttr = b.getStringAttr(predicateNames[predicateValue]); + p.printAttribute(predicateStringAttr); + p << ", "; + p.printOperand(op.lhs()); + p << ", "; + p.printOperand(op.rhs()); + p.printOptionalAttrDict(op.getAttrs(), + /*elidedAttrs=*/{OPTY::getPredicateAttrName()}); + p << " : " << op.lhs().getType(); +} + +void printCmpfOp(OpAsmPrinter &p, CmpfOp op) { printCmpOp(p, op); } + +template +mlir::ParseResult parseCmpOp(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + llvm::SmallVector ops; + llvm::SmallVector attrs; + mlir::Attribute predicateNameAttr; + mlir::Type type; + if (parser.parseAttribute(predicateNameAttr, OPTY::getPredicateAttrName(), + attrs) || + parser.parseComma() || parser.parseOperandList(ops, 2) || + parser.parseOptionalAttrDict(attrs) || parser.parseColonType(type) || + parser.resolveOperands(ops, type, result.operands)) + return failure(); + + if (!predicateNameAttr.isa()) + return parser.emitError(parser.getNameLoc(), + "expected string comparison predicate attribute"); + + // Rewrite string attribute to an enum value. + llvm::StringRef predicateName = + predicateNameAttr.cast().getValue(); + auto predicate = CmpfOp::getPredicateByName(predicateName); + if (predicate == CmpFPredicate::NumPredicates) + return parser.emitError(parser.getNameLoc(), + "unknown comparison predicate \"" + predicateName + + "\""); + + auto builder = parser.getBuilder(); + mlir::Type i1Type = builder.getI1Type(); + attrs[0].second = builder.getI64IntegerAttr(static_cast(predicate)); + result.attributes = attrs; + result.addTypes({i1Type}); + return success(); +} + +mlir::ParseResult parseCmpfOp(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + return parseCmpOp(parser, result); +} + +// CmpcOp + +void buildCmpCOp(Builder *builder, OperationState &result, + CmpFPredicate predicate, Value lhs, Value rhs) { + result.addOperands({lhs, rhs}); + result.types.push_back(builder->getI1Type()); + result.addAttribute( + CmpcOp::getPredicateAttrName(), + builder->getI64IntegerAttr(static_cast(predicate))); +} + +void printCmpcOp(OpAsmPrinter &p, CmpcOp op) { printCmpOp(p, op); } + +mlir::ParseResult parseCmpcOp(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + return parseCmpOp(parser, result); +} + +// DispatchOp + +mlir::FunctionType DispatchOp::getFunctionType() { + auto attr = getAttr("fn_type").cast(); + return attr.getValue().cast(); +} + +// DispatchTableOp + +void DispatchTableOp::appendTableEntry(mlir::Operation *op) { + assert(mlir::isa(*op) && "operation must be a DTEntryOp"); + auto &block = getBlock(); + block.getOperations().insert(block.end(), op); +} + +// GenTypeDescOp + +void GenTypeDescOp::build(Builder *, OperationState &result, + mlir::TypeAttr inty) { + result.addAttribute("in_type", inty); + result.addTypes(TypeDescType::get(inty.getValue())); +} + +// GlobalOp + +void GlobalOp::appendInitialValue(mlir::Operation *op) { + auto &block = getBlock(); + block.getOperations().insert(block.end(), op); +} + +// LoadOp + +/// Get the element type of a reference like type; otherwise null +mlir::Type elementTypeOf(mlir::Type ref) { + if (auto r = ref.dyn_cast_or_null()) + return r.getEleTy(); + if (auto r = ref.dyn_cast_or_null()) + return r.getEleTy(); + if (auto r = ref.dyn_cast_or_null()) + return r.getEleTy(); + return {}; +} + +mlir::ParseResult LoadOp::getElementOf(mlir::Type &ele, mlir::Type ref) { + if (mlir::Type r = elementTypeOf(ref)) { + ele = r; + return mlir::success(); + } + return mlir::failure(); +} + +// LoopOp + +void LoopOp::build(mlir::Builder *builder, mlir::OperationState &result, + int64_t lowerBound, int64_t upperBound, int64_t step) { + assert(false && "not implemented"); +} + +void LoopOp::build(mlir::Builder *builder, mlir::OperationState &result, + mlir::Value lb, mlir::Value ub, + llvm::ArrayRef step) { + if (step.empty()) + result.addOperands({lb, ub}); + else + result.addOperands({lb, ub, step[0]}); + mlir::Region *bodyRegion = result.addRegion(); + LoopOp::ensureTerminator(*bodyRegion, *builder, result.location); + bodyRegion->front().addArgument(builder->getIndexType()); +} + +mlir::ParseResult parseLoopOp(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + auto &builder = parser.getBuilder(); + mlir::OpAsmParser::OperandType inductionVariable, lb, ub, step; + // Parse the induction variable followed by '='. + if (parser.parseRegionArgument(inductionVariable) || parser.parseEqual()) + return mlir::failure(); + + // Parse loop bounds. + mlir::Type indexType = builder.getIndexType(); + if (parser.parseOperand(lb) || + parser.resolveOperand(lb, indexType, result.operands) || + parser.parseKeyword("to") || parser.parseOperand(ub) || + parser.resolveOperand(ub, indexType, result.operands)) + return mlir::failure(); + + if (parser.parseOptionalKeyword(LoopOp::stepKeyword())) { + result.addAttribute(LoopOp::stepKeyword(), + builder.getIntegerAttr(builder.getIndexType(), 1)); + } else if (parser.parseOperand(step) || + parser.resolveOperand(step, indexType, result.operands)) { + return mlir::failure(); + } + + // Parse the optional `unordered` keyword + bool isUnordered = false; + if (!parser.parseOptionalKeyword(LoopOp::unorderedKeyword())) { + result.addAttribute(LoopOp::unorderedKeyword(), builder.getUnitAttr()); + isUnordered = true; + } + + // Parse the body region. + mlir::Region *body = result.addRegion(); + if (parser.parseRegion(*body, inductionVariable, indexType)) + return mlir::failure(); + + fir::LoopOp::ensureTerminator(*body, builder, result.location); + + // Parse the optional attribute list. + if (parser.parseOptionalAttrDict(result.attributes)) + return mlir::failure(); + if (!isUnordered) + result.addTypes(builder.getIndexType()); + return mlir::success(); +} + +fir::LoopOp getForInductionVarOwner(mlir::Value val) { + auto ivArg = val.dyn_cast(); + if (!ivArg) + return fir::LoopOp(); + assert(ivArg.getOwner() && "unlinked block argument"); + auto *containingInst = ivArg.getOwner()->getParentOp(); + return dyn_cast_or_null(containingInst); +} + +// StoreOp + +mlir::Type StoreOp::elementType(mlir::Type refType) { + if (auto ref = refType.dyn_cast()) + return ref.getEleTy(); + if (auto ref = refType.dyn_cast()) + return ref.getEleTy(); + if (auto ref = refType.dyn_cast()) + return ref.getEleTy(); + return {}; +} + +// StringLitOp + +bool StringLitOp::isWideValue() { + auto eleTy = getType().cast().getEleTy(); + return eleTy.cast().getFKind() != 1; +} + +// WhereOp + +void WhereOp::build(mlir::Builder *builder, mlir::OperationState &result, + mlir::Value cond, bool withElseRegion) { + result.addOperands(cond); + mlir::Region *thenRegion = result.addRegion(); + mlir::Region *elseRegion = result.addRegion(); + WhereOp::ensureTerminator(*thenRegion, *builder, result.location); + if (withElseRegion) + WhereOp::ensureTerminator(*elseRegion, *builder, result.location); +} + +mlir::ParseResult parseWhereOp(mlir::OpAsmParser &parser, + mlir::OperationState &result) { + // Create the regions for 'then'. + result.regions.reserve(2); + mlir::Region *thenRegion = result.addRegion(); + mlir::Region *elseRegion = result.addRegion(); + + auto &builder = parser.getBuilder(); + mlir::OpAsmParser::OperandType cond; + mlir::Type i1Type = builder.getIntegerType(1); + if (parser.parseOperand(cond) || + parser.resolveOperand(cond, i1Type, result.operands)) + return mlir::failure(); + + if (parser.parseRegion(*thenRegion, {}, {})) + return mlir::failure(); + + WhereOp::ensureTerminator(*thenRegion, parser.getBuilder(), result.location); + + if (!parser.parseOptionalKeyword("otherwise")) { + if (parser.parseRegion(*elseRegion, {}, {})) + return mlir::failure(); + WhereOp::ensureTerminator(*elseRegion, parser.getBuilder(), + result.location); + } + + // Parse the optional attribute list. + if (parser.parseOptionalAttrDict(result.attributes)) + return mlir::failure(); + + return mlir::success(); +} + +mlir::ParseResult isValidCaseAttr(mlir::Attribute attr) { + if (attr.dyn_cast_or_null() || + attr.dyn_cast_or_null() || + attr.dyn_cast_or_null() || + attr.dyn_cast_or_null() || + attr.dyn_cast_or_null()) + return mlir::success(); + return mlir::failure(); +} + +unsigned getCaseArgumentOffset(llvm::ArrayRef cases, + unsigned dest) { + unsigned o = 0; + for (unsigned i = 0; i < dest; ++i) { + auto &attr = cases[i]; + if (!attr.dyn_cast_or_null()) { + ++o; + if (attr.dyn_cast_or_null()) + ++o; + } + } + return o; +} + +mlir::ParseResult parseSelector(mlir::OpAsmParser &parser, + mlir::OperationState &result, + mlir::OpAsmParser::OperandType &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(); +} + +/// Generic pretty-printer of a binary operation +void printBinaryOp(Operation *op, OpAsmPrinter &p) { + assert(op->getNumOperands() == 2 && "binary op must have two operands"); + assert(op->getNumResults() == 1 && "binary op must have one result"); + + p << op->getName() << ' ' << op->getOperand(0) << ", " << op->getOperand(1); + p.printOptionalAttrDict(op->getAttrs()); + p << " : " << op->getResult(0).getType(); +} + +/// Generic pretty-printer of an unary operation +void printUnaryOp(Operation *op, OpAsmPrinter &p) { + assert(op->getNumOperands() == 1 && "unary op must have one operand"); + assert(op->getNumResults() == 1 && "unary op must have one result"); + + p << op->getName() << ' ' << op->getOperand(0); + p.printOptionalAttrDict(op->getAttrs()); + p << " : " << op->getResult(0).getType(); +} + +bool isReferenceLike(mlir::Type type) { + return type.isa() || type.isa() || + type.isa(); +} + +mlir::FuncOp createFuncOp(mlir::Location loc, mlir::ModuleOp module, + StringRef name, mlir::FunctionType type, + llvm::ArrayRef attrs) { + if (auto f = module.lookupSymbol(name)) + return f; + mlir::OpBuilder modBuilder(module.getBodyRegion()); + return modBuilder.create(loc, name, type, attrs); +} + +GlobalOp createGlobalOp(mlir::Location loc, mlir::ModuleOp module, + StringRef name, mlir::Type type, + llvm::ArrayRef attrs) { + if (auto g = module.lookupSymbol(name)) + return g; + mlir::OpBuilder modBuilder(module.getBodyRegion()); + return modBuilder.create(loc, name, type, attrs); +} + +// Tablegen operators + +#define GET_OP_CLASSES +#include "flang/Optimizer/Dialect/FIROps.cpp.inc" + +} // namespace fir diff --git a/lib/Optimizer/Dialect/FIRType.cpp b/lib/Optimizer/Dialect/FIRType.cpp new file mode 100644 index 000000000000..7fd4547448ff --- /dev/null +++ b/lib/Optimizer/Dialect/FIRType.cpp @@ -0,0 +1,1288 @@ +//===-- FIRType.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Dialect/FIRType.h" +#include "flang/Optimizer/Dialect/FIRDialect.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/Dialect.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/StandardTypes.h" +#include "mlir/Parser.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringSet.h" + +using namespace fir; + +namespace { + +template +TYPE parseIntSingleton(mlir::DialectAsmParser &parser) { + int kind = 0; + if (parser.parseLess() || parser.parseInteger(kind) || + parser.parseGreater()) { + parser.emitError(parser.getCurrentLocation(), "kind value expected"); + return {}; + } + return TYPE::get(parser.getBuilder().getContext(), kind); +} + +template +TYPE parseKindSingleton(mlir::DialectAsmParser &parser) { + return parseIntSingleton(parser); +} + +template +TYPE parseRankSingleton(mlir::DialectAsmParser &parser) { + return parseIntSingleton(parser); +} + +template +TYPE parseTypeSingleton(mlir::DialectAsmParser &parser, mlir::Location) { + mlir::Type ty; + if (parser.parseLess() || parser.parseType(ty) || parser.parseGreater()) { + parser.emitError(parser.getCurrentLocation(), "type expected"); + return {}; + } + return TYPE::get(ty); +} + +// `box` `<` type (',' affine-map)? `>` +BoxType parseBox(mlir::DialectAsmParser &parser, mlir::Location loc) { + mlir::Type ofTy; + if (parser.parseLess() || parser.parseType(ofTy)) { + parser.emitError(parser.getCurrentLocation(), "expected type parameter"); + return {}; + } + + mlir::AffineMapAttr map; + if (!parser.parseOptionalComma()) + if (parser.parseAttribute(map)) { + parser.emitError(parser.getCurrentLocation(), "expected affine map"); + return {}; + } + if (parser.parseGreater()) { + parser.emitError(parser.getCurrentLocation(), "expected '>'"); + return {}; + } + return BoxType::get(ofTy, map); +} + +// `boxchar` `<` kind `>` +BoxCharType parseBoxChar(mlir::DialectAsmParser &parser) { + return parseKindSingleton(parser); +} + +// `boxproc` `<` return-type `>` +BoxProcType parseBoxProc(mlir::DialectAsmParser &parser, mlir::Location loc) { + return parseTypeSingleton(parser, loc); +} + +// `char` `<` kind `>` +CharacterType parseCharacter(mlir::DialectAsmParser &parser) { + return parseKindSingleton(parser); +} + +// `complex` `<` kind `>` +CplxType parseComplex(mlir::DialectAsmParser &parser) { + return parseKindSingleton(parser); +} + +// `dims` `<` rank `>` +DimsType parseDims(mlir::DialectAsmParser &parser) { + return parseRankSingleton(parser); +} + +// `field` +FieldType parseField(mlir::DialectAsmParser &parser) { + return FieldType::get(parser.getBuilder().getContext()); +} + +// `heap` `<` type `>` +HeapType parseHeap(mlir::DialectAsmParser &parser, mlir::Location loc) { + return parseTypeSingleton(parser, loc); +} + +// `int` `<` kind `>` +IntType parseInteger(mlir::DialectAsmParser &parser) { + return parseKindSingleton(parser); +} + +// `len` +LenType parseLen(mlir::DialectAsmParser &parser) { + return LenType::get(parser.getBuilder().getContext()); +} + +// `logical` `<` kind `>` +LogicalType parseLogical(mlir::DialectAsmParser &parser) { + return parseKindSingleton(parser); +} + +// `ptr` `<` type `>` +PointerType parsePointer(mlir::DialectAsmParser &parser, mlir::Location loc) { + return parseTypeSingleton(parser, loc); +} + +// `real` `<` kind `>` +RealType parseReal(mlir::DialectAsmParser &parser) { + return parseKindSingleton(parser); +} + +// `ref` `<` type `>` +ReferenceType parseReference(mlir::DialectAsmParser &parser, + mlir::Location loc) { + return parseTypeSingleton(parser, loc); +} + +// `tdesc` `<` type `>` +TypeDescType parseTypeDesc(mlir::DialectAsmParser &parser, mlir::Location loc) { + return parseTypeSingleton(parser, loc); +} + +// `void` +mlir::Type parseVoid(mlir::DialectAsmParser &parser) { + return parser.getBuilder().getNoneType(); +} + +// `array` `<` `*` | bounds (`x` bounds)* `:` type (',' affine-map)? `>` +// bounds ::= `?` | int-lit +SequenceType parseSequence(mlir::DialectAsmParser &parser, mlir::Location) { + if (parser.parseLess()) { + parser.emitError(parser.getNameLoc(), "expecting '<'"); + return {}; + } + SequenceType::Shape shape; + if (parser.parseOptionalStar()) { + if (parser.parseDimensionList(shape, true)) { + parser.emitError(parser.getNameLoc(), "invalid shape"); + return {}; + } + } else if (parser.parseColon()) { + parser.emitError(parser.getNameLoc(), "expected ':'"); + return {}; + } + mlir::Type eleTy; + if (parser.parseType(eleTy) || parser.parseGreater()) { + parser.emitError(parser.getNameLoc(), "expecting element type"); + return {}; + } + mlir::AffineMapAttr map; + if (!parser.parseOptionalComma()) + if (parser.parseAttribute(map)) { + parser.emitError(parser.getNameLoc(), "expecting affine map"); + return {}; + } + return SequenceType::get(shape, eleTy, map); +} + +bool verifyIntegerType(mlir::Type ty) { + return ty.isa() || ty.isa(); +} + +bool verifyRecordMemberType(mlir::Type ty) { + return !(ty.isa() || ty.isa() || + ty.isa() || ty.isa() || ty.isa() || + ty.isa() || ty.isa() || + ty.isa()); +} + +bool verifySameLists(llvm::ArrayRef a1, + llvm::ArrayRef a2) { + // FIXME: do we need to allow for any variance here? + return a1 == a2; +} + +RecordType verifyDerived(mlir::DialectAsmParser &parser, RecordType derivedTy, + llvm::ArrayRef lenPList, + llvm::ArrayRef typeList) { + auto loc = parser.getNameLoc(); + if (!verifySameLists(derivedTy.getLenParamList(), lenPList) || + !verifySameLists(derivedTy.getTypeList(), typeList)) { + parser.emitError(loc, "cannot redefine record type members"); + return {}; + } + for (auto &p : lenPList) + if (!verifyIntegerType(p.second)) { + parser.emitError(loc, "LEN parameter must be integral type"); + return {}; + } + for (auto &p : typeList) + if (!verifyRecordMemberType(p.second)) { + parser.emitError(loc, "field parameter has invalid type"); + return {}; + } + llvm::StringSet<> uniq; + for (auto &p : lenPList) + if (!uniq.insert(p.first).second) { + parser.emitError(loc, "LEN parameter cannot have duplicate name"); + return {}; + } + for (auto &p : typeList) + if (!uniq.insert(p.first).second) { + parser.emitError(loc, "field cannot have duplicate name"); + return {}; + } + return derivedTy; +} + +// Fortran derived type +// `type` `<` name +// (`(` id `:` type (`,` id `:` type)* `)`)? +// (`{` id `:` type (`,` id `:` type)* `}`)? '>' +RecordType parseDerived(mlir::DialectAsmParser &parser, mlir::Location) { + llvm::StringRef name; + if (parser.parseLess() || parser.parseKeyword(&name)) { + parser.emitError(parser.getNameLoc(), + "expected a identifier as name of derived type"); + return {}; + } + RecordType result = RecordType::get(parser.getBuilder().getContext(), name); + + RecordType::TypeList lenParamList; + if (!parser.parseOptionalLParen()) { + while (true) { + llvm::StringRef lenparam; + mlir::Type intTy; + if (parser.parseKeyword(&lenparam) || parser.parseColon() || + parser.parseType(intTy)) { + parser.emitError(parser.getNameLoc(), "expected LEN parameter list"); + return {}; + } + lenParamList.emplace_back(lenparam, intTy); + if (parser.parseOptionalComma()) + break; + } + if (parser.parseRParen()) { + parser.emitError(parser.getNameLoc(), "expected ')'"); + return {}; + } + } + + RecordType::TypeList typeList; + if (!parser.parseOptionalLBrace()) { + while (true) { + llvm::StringRef field; + mlir::Type fldTy; + if (parser.parseKeyword(&field) || parser.parseColon() || + parser.parseType(fldTy)) { + parser.emitError(parser.getNameLoc(), "expected field type list"); + return {}; + } + typeList.emplace_back(field, fldTy); + if (parser.parseOptionalComma()) + break; + } + if (parser.parseRBrace()) { + parser.emitError(parser.getNameLoc(), "expected '}'"); + return {}; + } + } + + if (parser.parseGreater()) { + parser.emitError(parser.getNameLoc(), "expected '>' in type type"); + return {}; + } + + if (lenParamList.empty() && typeList.empty()) + return result; + + result.finalize(lenParamList, typeList); + return verifyDerived(parser, result, lenParamList, typeList); +} + +// !fir.ptr and !fir.heap where X is !fir.ptr, !fir.heap, or !fir.ref +// is undefined and disallowed. +inline bool singleIndirectionLevel(mlir::Type ty) { + return !fir::isaMemref(ty); +} + +} // namespace + +// Implementation of the thin interface from dialect to type parser + +mlir::Type fir::parseFirType(FIROpsDialect *, mlir::DialectAsmParser &parser) { + llvm::StringRef typeNameLit; + if (mlir::failed(parser.parseKeyword(&typeNameLit))) + return {}; + + auto loc = parser.getEncodedSourceLoc(parser.getNameLoc()); + if (typeNameLit == "array") + return parseSequence(parser, loc); + if (typeNameLit == "box") + return parseBox(parser, loc); + if (typeNameLit == "boxchar") + return parseBoxChar(parser); + if (typeNameLit == "boxproc") + return parseBoxProc(parser, loc); + if (typeNameLit == "char") + return parseCharacter(parser); + if (typeNameLit == "complex") + return parseComplex(parser); + if (typeNameLit == "dims") + return parseDims(parser); + if (typeNameLit == "field") + return parseField(parser); + if (typeNameLit == "heap") + return parseHeap(parser, loc); + if (typeNameLit == "int") + return parseInteger(parser); + if (typeNameLit == "len") + return parseLen(parser); + if (typeNameLit == "logical") + return parseLogical(parser); + if (typeNameLit == "ptr") + return parsePointer(parser, loc); + if (typeNameLit == "real") + return parseReal(parser); + if (typeNameLit == "ref") + return parseReference(parser, loc); + if (typeNameLit == "tdesc") + return parseTypeDesc(parser, loc); + if (typeNameLit == "type") + return parseDerived(parser, loc); + if (typeNameLit == "void") + return parseVoid(parser); + + parser.emitError(parser.getNameLoc(), "unknown FIR type " + typeNameLit); + return {}; +} + +namespace fir { +namespace detail { + +// Type storage classes + +/// `CHARACTER` storage +struct CharacterTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getFKind(); } + + static CharacterTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy kind) { + auto *storage = allocator.allocate(); + return new (storage) CharacterTypeStorage{kind}; + } + + KindTy getFKind() const { return kind; } + +protected: + KindTy kind; + +private: + CharacterTypeStorage() = delete; + explicit CharacterTypeStorage(KindTy kind) : kind{kind} {} +}; + +struct DimsTypeStorage : public mlir::TypeStorage { + using KeyTy = unsigned; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { + return key == static_cast(getRank()); + } + + static DimsTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + int rank) { + auto *storage = allocator.allocate(); + return new (storage) DimsTypeStorage{rank}; + } + + int getRank() const { return rank; } + +protected: + int rank; + +private: + DimsTypeStorage() = delete; + explicit DimsTypeStorage(int rank) : rank{rank} {} +}; + +/// The type of a derived type part reference +struct FieldTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &) { return llvm::hash_combine(0); } + + bool operator==(const KeyTy &) const { return true; } + + static FieldTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy) { + auto *storage = allocator.allocate(); + return new (storage) FieldTypeStorage{0}; + } + +private: + FieldTypeStorage() = delete; + explicit FieldTypeStorage(KindTy) {} +}; + +/// The type of a derived type LEN parameter reference +struct LenTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &) { return llvm::hash_combine(0); } + + bool operator==(const KeyTy &) const { return true; } + + static LenTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy) { + auto *storage = allocator.allocate(); + return new (storage) LenTypeStorage{0}; + } + +private: + LenTypeStorage() = delete; + explicit LenTypeStorage(KindTy) {} +}; + +/// `LOGICAL` storage +struct LogicalTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getFKind(); } + + static LogicalTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy kind) { + auto *storage = allocator.allocate(); + return new (storage) LogicalTypeStorage{kind}; + } + + KindTy getFKind() const { return kind; } + +protected: + KindTy kind; + +private: + LogicalTypeStorage() = delete; + explicit LogicalTypeStorage(KindTy kind) : kind{kind} {} +}; + +/// `INTEGER` storage +struct IntTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getFKind(); } + + static IntTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy kind) { + auto *storage = allocator.allocate(); + return new (storage) IntTypeStorage{kind}; + } + + KindTy getFKind() const { return kind; } + +protected: + KindTy kind; + +private: + IntTypeStorage() = delete; + explicit IntTypeStorage(KindTy kind) : kind{kind} {} +}; + +/// `COMPLEX` storage +struct CplxTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getFKind(); } + + static CplxTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy kind) { + auto *storage = allocator.allocate(); + return new (storage) CplxTypeStorage{kind}; + } + + KindTy getFKind() const { return kind; } + +protected: + KindTy kind; + +private: + CplxTypeStorage() = delete; + explicit CplxTypeStorage(KindTy kind) : kind{kind} {} +}; + +/// `REAL` storage (for reals of unsupported sizes) +struct RealTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getFKind(); } + + static RealTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy kind) { + auto *storage = allocator.allocate(); + return new (storage) RealTypeStorage{kind}; + } + + KindTy getFKind() const { return kind; } + +protected: + KindTy kind; + +private: + RealTypeStorage() = delete; + explicit RealTypeStorage(KindTy kind) : kind{kind} {} +}; + +/// Boxed object (a Fortran descriptor) +struct BoxTypeStorage : public mlir::TypeStorage { + using KeyTy = std::tuple; + + static unsigned hashKey(const KeyTy &key) { + auto hashVal{llvm::hash_combine(std::get(key))}; + return llvm::hash_combine( + hashVal, llvm::hash_combine(std::get(key))); + } + + bool operator==(const KeyTy &key) const { + return std::get(key) == getElementType() && + std::get(key) == getLayoutMap(); + } + + static BoxTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + const KeyTy &key) { + auto *storage = allocator.allocate(); + return new (storage) BoxTypeStorage{std::get(key), + std::get(key)}; + } + + mlir::Type getElementType() const { return eleTy; } + mlir::AffineMapAttr getLayoutMap() const { return map; } + +protected: + mlir::Type eleTy; + mlir::AffineMapAttr map; + +private: + BoxTypeStorage() = delete; + explicit BoxTypeStorage(mlir::Type eleTy, mlir::AffineMapAttr map) + : eleTy{eleTy}, map{map} {} +}; + +/// Boxed CHARACTER object type +struct BoxCharTypeStorage : public mlir::TypeStorage { + using KeyTy = KindTy; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getFKind(); } + + static BoxCharTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + KindTy kind) { + auto *storage = allocator.allocate(); + return new (storage) BoxCharTypeStorage{kind}; + } + + KindTy getFKind() const { return kind; } + + // a !fir.boxchar always wraps a !fir.char + CharacterType getElementType(mlir::MLIRContext *ctxt) const { + return CharacterType::get(ctxt, getFKind()); + } + +protected: + KindTy kind; + +private: + BoxCharTypeStorage() = delete; + explicit BoxCharTypeStorage(KindTy kind) : kind{kind} {} +}; + +/// Boxed PROCEDURE POINTER object type +struct BoxProcTypeStorage : public mlir::TypeStorage { + using KeyTy = mlir::Type; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getElementType(); } + + static BoxProcTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + mlir::Type eleTy) { + assert(eleTy && "element type is null"); + auto *storage = allocator.allocate(); + return new (storage) BoxProcTypeStorage{eleTy}; + } + + mlir::Type getElementType() const { return eleTy; } + +protected: + mlir::Type eleTy; + +private: + BoxProcTypeStorage() = delete; + explicit BoxProcTypeStorage(mlir::Type eleTy) : eleTy{eleTy} {} +}; + +/// Pointer-like object storage +struct ReferenceTypeStorage : public mlir::TypeStorage { + using KeyTy = mlir::Type; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getElementType(); } + + static ReferenceTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + mlir::Type eleTy) { + assert(eleTy && "element type is null"); + auto *storage = allocator.allocate(); + return new (storage) ReferenceTypeStorage{eleTy}; + } + + mlir::Type getElementType() const { return eleTy; } + +protected: + mlir::Type eleTy; + +private: + ReferenceTypeStorage() = delete; + explicit ReferenceTypeStorage(mlir::Type eleTy) : eleTy{eleTy} {} +}; + +/// Pointer object storage +struct PointerTypeStorage : public mlir::TypeStorage { + using KeyTy = mlir::Type; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getElementType(); } + + static PointerTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + mlir::Type eleTy) { + assert(eleTy && "element type is null"); + auto *storage = allocator.allocate(); + return new (storage) PointerTypeStorage{eleTy}; + } + + mlir::Type getElementType() const { return eleTy; } + +protected: + mlir::Type eleTy; + +private: + PointerTypeStorage() = delete; + explicit PointerTypeStorage(mlir::Type eleTy) : eleTy{eleTy} {} +}; + +/// Heap memory reference object storage +struct HeapTypeStorage : public mlir::TypeStorage { + using KeyTy = mlir::Type; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getElementType(); } + + static HeapTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + mlir::Type eleTy) { + assert(eleTy && "element type is null"); + auto *storage = allocator.allocate(); + return new (storage) HeapTypeStorage{eleTy}; + } + + mlir::Type getElementType() const { return eleTy; } + +protected: + mlir::Type eleTy; + +private: + HeapTypeStorage() = delete; + explicit HeapTypeStorage(mlir::Type eleTy) : eleTy{eleTy} {} +}; + +/// Sequence-like object storage +struct SequenceTypeStorage : public mlir::TypeStorage { + using KeyTy = + std::tuple; + + static unsigned hashKey(const KeyTy &key) { + auto shapeHash{hash_value(std::get(key))}; + shapeHash = llvm::hash_combine(shapeHash, std::get(key)); + return llvm::hash_combine(shapeHash, std::get(key)); + } + + bool operator==(const KeyTy &key) const { + return key == KeyTy{getShape(), getElementType(), getLayoutMap()}; + } + + static SequenceTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + const KeyTy &key) { + auto *storage = allocator.allocate(); + return new (storage) SequenceTypeStorage{ + std::get(key), std::get(key), + std::get(key)}; + } + + SequenceType::Shape getShape() const { return shape; } + mlir::Type getElementType() const { return eleTy; } + mlir::AffineMapAttr getLayoutMap() const { return map; } + +protected: + SequenceType::Shape shape; + mlir::Type eleTy; + mlir::AffineMapAttr map; + +private: + SequenceTypeStorage() = delete; + explicit SequenceTypeStorage(const SequenceType::Shape &shape, + mlir::Type eleTy, mlir::AffineMapAttr map) + : shape{shape}, eleTy{eleTy}, map{map} {} +}; + +/// Derived type storage +struct RecordTypeStorage : public mlir::TypeStorage { + using KeyTy = llvm::StringRef; + + static unsigned hashKey(const KeyTy &key) { + return llvm::hash_combine(key.str()); + } + + bool operator==(const KeyTy &key) const { return key == getName(); } + + static RecordTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + const KeyTy &key) { + auto *storage = allocator.allocate(); + return new (storage) RecordTypeStorage{key}; + } + + llvm::StringRef getName() const { return name; } + + void setLenParamList(llvm::ArrayRef list) { + lens = list; + } + llvm::ArrayRef getLenParamList() const { return lens; } + + void setTypeList(llvm::ArrayRef list) { types = list; } + llvm::ArrayRef getTypeList() const { return types; } + + void finalize(llvm::ArrayRef lenParamList, + llvm::ArrayRef typeList) { + if (finalized) + return; + finalized = true; + setLenParamList(lenParamList); + setTypeList(typeList); + } + +protected: + std::string name; + bool finalized; + std::vector lens; + std::vector types; + +private: + RecordTypeStorage() = delete; + explicit RecordTypeStorage(llvm::StringRef name) + : name{name}, finalized{false} {} +}; + +/// Type descriptor type storage +struct TypeDescTypeStorage : public mlir::TypeStorage { + using KeyTy = mlir::Type; + + static unsigned hashKey(const KeyTy &key) { return llvm::hash_combine(key); } + + bool operator==(const KeyTy &key) const { return key == getOfType(); } + + static TypeDescTypeStorage *construct(mlir::TypeStorageAllocator &allocator, + mlir::Type ofTy) { + assert(ofTy && "descriptor type is null"); + auto *storage = allocator.allocate(); + return new (storage) TypeDescTypeStorage{ofTy}; + } + + // The type described by this type descriptor instance + mlir::Type getOfType() const { return ofTy; } + +protected: + mlir::Type ofTy; + +private: + TypeDescTypeStorage() = delete; + explicit TypeDescTypeStorage(mlir::Type ofTy) : ofTy{ofTy} {} +}; + +} // namespace detail + +template +bool inbounds(A v, B lb, B ub) { + return v >= lb && v < ub; +} + +bool isa_fir_type(mlir::Type t) { + return inbounds(t.getKind(), mlir::Type::FIRST_FIR_TYPE, + mlir::Type::LAST_FIR_TYPE); +} + +bool isa_std_type(mlir::Type t) { + return inbounds(t.getKind(), mlir::Type::FIRST_STANDARD_TYPE, + mlir::Type::LAST_STANDARD_TYPE); +} + +bool isa_fir_or_std_type(mlir::Type t) { + return isa_fir_type(t) || isa_std_type(t); +} + +bool isaMemref(mlir::Type t) { + return t.isa() || t.isa() || t.isa(); +} + +bool isanAggregate(mlir::Type t) { + return t.isa() || t.isa(); +} + +mlir::Type dyn_cast_ptrEleTy(mlir::Type t) { + if (auto p = t.dyn_cast()) + return p.getEleTy(); + if (auto p = t.dyn_cast()) + return p.getEleTy(); + if (auto p = t.dyn_cast()) + return p.getEleTy(); + return {}; +} + +} // namespace fir + +// CHARACTER + +CharacterType fir::CharacterType::get(mlir::MLIRContext *ctxt, KindTy kind) { + return Base::get(ctxt, FIR_CHARACTER, kind); +} + +int fir::CharacterType::getFKind() const { return getImpl()->getFKind(); } + +// Dims + +DimsType fir::DimsType::get(mlir::MLIRContext *ctxt, unsigned rank) { + return Base::get(ctxt, FIR_DIMS, rank); +} + +int fir::DimsType::getRank() const { return getImpl()->getRank(); } + +// Field + +FieldType fir::FieldType::get(mlir::MLIRContext *ctxt, KindTy) { + return Base::get(ctxt, FIR_FIELD, 0); +} + +// Len + +LenType fir::LenType::get(mlir::MLIRContext *ctxt, KindTy) { + return Base::get(ctxt, FIR_LEN, 0); +} + +// LOGICAL + +LogicalType fir::LogicalType::get(mlir::MLIRContext *ctxt, KindTy kind) { + return Base::get(ctxt, FIR_LOGICAL, kind); +} + +int fir::LogicalType::getFKind() const { return getImpl()->getFKind(); } + +// INTEGER + +IntType fir::IntType::get(mlir::MLIRContext *ctxt, KindTy kind) { + return Base::get(ctxt, FIR_INT, kind); +} + +int fir::IntType::getFKind() const { return getImpl()->getFKind(); } + +// COMPLEX + +CplxType fir::CplxType::get(mlir::MLIRContext *ctxt, KindTy kind) { + return Base::get(ctxt, FIR_COMPLEX, kind); +} + +KindTy fir::CplxType::getFKind() const { return getImpl()->getFKind(); } + +// REAL + +RealType fir::RealType::get(mlir::MLIRContext *ctxt, KindTy kind) { + return Base::get(ctxt, FIR_REAL, kind); +} + +int fir::RealType::getFKind() const { return getImpl()->getFKind(); } + +// Box + +BoxType fir::BoxType::get(mlir::Type elementType, mlir::AffineMapAttr map) { + return Base::get(elementType.getContext(), FIR_BOX, elementType, map); +} + +mlir::Type fir::BoxType::getEleTy() const { + return getImpl()->getElementType(); +} + +mlir::AffineMapAttr fir::BoxType::getLayoutMap() const { + return getImpl()->getLayoutMap(); +} + +mlir::LogicalResult fir::BoxType::verifyConstructionInvariants( + const mlir::AttributeStorage *, mlir::Type eleTy, mlir::AffineMapAttr map) { + // TODO + return mlir::success(); +} + +// BoxChar + +BoxCharType fir::BoxCharType::get(mlir::MLIRContext *ctxt, KindTy kind) { + return Base::get(ctxt, FIR_BOXCHAR, kind); +} + +CharacterType fir::BoxCharType::getEleTy() const { + return getImpl()->getElementType(getContext()); +} + +// BoxProc + +BoxProcType fir::BoxProcType::get(mlir::Type elementType) { + return Base::get(elementType.getContext(), FIR_BOXPROC, elementType); +} + +mlir::Type fir::BoxProcType::getEleTy() const { + return getImpl()->getElementType(); +} + +mlir::LogicalResult +fir::BoxProcType::verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy) { + if (eleTy.isa() || eleTy.isa()) + return mlir::success(); + return mlir::failure(); +} + +// Reference + +ReferenceType fir::ReferenceType::get(mlir::Type elementType) { + return Base::get(elementType.getContext(), FIR_REFERENCE, elementType); +} + +mlir::Type fir::ReferenceType::getEleTy() const { + return getImpl()->getElementType(); +} + +mlir::LogicalResult fir::ReferenceType::verifyConstructionInvariants( + const mlir::AttributeStorage *context, mlir::Type eleTy) { + if (eleTy.isa() || eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa()) + return mlir::failure(); + return mlir::success(); +} + +// Pointer + +PointerType fir::PointerType::get(mlir::Type elementType) { + if (!singleIndirectionLevel(elementType)) { + assert(false && "FIXME: invalid element type"); + return {}; + } + return Base::get(elementType.getContext(), FIR_POINTER, elementType); +} + +mlir::Type fir::PointerType::getEleTy() const { + return getImpl()->getElementType(); +} + +mlir::LogicalResult fir::PointerType::verifyConstructionInvariants( + const mlir::AttributeStorage *context, mlir::Type eleTy) { + if (eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa()) + return mlir::failure(); + return mlir::success(); +} + +// Heap + +HeapType fir::HeapType::get(mlir::Type elementType) { + if (!singleIndirectionLevel(elementType)) { + assert(false && "FIXME: invalid element type"); + return {}; + } + return Base::get(elementType.getContext(), FIR_HEAP, elementType); +} + +mlir::Type fir::HeapType::getEleTy() const { + return getImpl()->getElementType(); +} + +mlir::LogicalResult +fir::HeapType::verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy) { + if (eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa()) + return mlir::failure(); + return mlir::success(); +} + +// Sequence + +SequenceType fir::SequenceType::get(const Shape &shape, mlir::Type elementType, + mlir::AffineMapAttr map) { + auto *ctxt = elementType.getContext(); + return Base::get(ctxt, FIR_SEQUENCE, shape, elementType, map); +} + +mlir::Type fir::SequenceType::getEleTy() const { + return getImpl()->getElementType(); +} + +mlir::AffineMapAttr fir::SequenceType::getLayoutMap() const { + return getImpl()->getLayoutMap(); +} + +SequenceType::Shape fir::SequenceType::getShape() const { + return getImpl()->getShape(); +} + +mlir::LogicalResult fir::SequenceType::verifyConstructionInvariants( + const mlir::AttributeStorage *attributeStorage, + const SequenceType::Shape &shape, mlir::Type eleTy, + mlir::AffineMapAttr map) { + // DIMENSION attribute can only be applied to an intrinsic or record type + if (eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa()) { + auto context = attributeStorage->getDialect().getContext(); + mlir::emitError(mlir::UnknownLoc::get(context), + "cannot build an array of this element type: ") + << eleTy << '\n'; + return mlir::failure(); + } + return mlir::success(); +} + +// compare if two shapes are equivalent +bool fir::operator==(const SequenceType::Shape &sh_1, + const SequenceType::Shape &sh_2) { + if (sh_1.size() != sh_2.size()) + return false; + for (std::size_t i = 0, e{sh_1.size()}; i != e; ++i) + if (sh_1[i] != sh_2[i]) + return false; + return true; +} + +// compute the hash of a Shape +llvm::hash_code fir::hash_value(const SequenceType::Shape &sh) { + if (sh.size()) { + return llvm::hash_combine_range(sh.begin(), sh.end()); + } + return llvm::hash_combine(0); +} + +/// RecordType +/// +/// This type captures a Fortran "derived type" + +RecordType fir::RecordType::get(mlir::MLIRContext *ctxt, llvm::StringRef name) { + return Base::get(ctxt, FIR_DERIVED, name); +} + +void fir::RecordType::finalize(llvm::ArrayRef lenPList, + llvm::ArrayRef typeList) { + getImpl()->finalize(lenPList, typeList); +} + +llvm::StringRef fir::RecordType::getName() { return getImpl()->getName(); } + +RecordType::TypeList fir::RecordType::getTypeList() { + return getImpl()->getTypeList(); +} + +RecordType::TypeList fir::RecordType::getLenParamList() { + return getImpl()->getLenParamList(); +} + +detail::RecordTypeStorage const *fir::RecordType::uniqueKey() const { + return getImpl(); +} + +mlir::LogicalResult +fir::RecordType::verifyConstructionInvariants(const mlir::AttributeStorage *, + llvm::StringRef name) { + if (name.size() == 0) + return mlir::failure(); + return mlir::success(); +} + +mlir::Type fir::RecordType::getType(llvm::StringRef ident) { + for (auto f : getTypeList()) + if (ident == f.first) + return f.second; + assert(false && "query for field not present in record"); + return {}; +} + +/// Type descriptor type +/// +/// This is the type of a type descriptor object (similar to a class instance) + +TypeDescType fir::TypeDescType::get(mlir::Type ofType) { + assert(!ofType.isa()); + return Base::get(ofType.getContext(), FIR_TYPEDESC, ofType); +} + +mlir::Type fir::TypeDescType::getOfTy() const { return getImpl()->getOfType(); } + +mlir::LogicalResult +fir::TypeDescType::verifyConstructionInvariants(const mlir::AttributeStorage *, + mlir::Type eleTy) { + if (eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa() || + eleTy.isa() || eleTy.isa()) + return mlir::failure(); + return mlir::success(); +} + +namespace { + +void printBounds(llvm::raw_ostream &os, const SequenceType::Shape &bounds) { + os << '<'; + for (auto &b : bounds) { + if (b >= 0) { + os << b << 'x'; + } else { + os << "?x"; + } + } +} + +llvm::SmallPtrSet recordTypeVisited; + +} // namespace + +void fir::printFirType(FIROpsDialect *, mlir::Type ty, + mlir::DialectAsmPrinter &p) { + auto &os = p.getStream(); + switch (ty.getKind()) { + case fir::FIR_BOX: { + auto type = ty.cast(); + os << "box<"; + p.printType(type.getEleTy()); + if (auto map = type.getLayoutMap()) { + os << ", "; + p.printAttribute(map); + } + os << '>'; + } break; + case fir::FIR_BOXCHAR: { + auto type = ty.cast().getEleTy(); + os << "boxchar<" << type.cast().getFKind() << '>'; + } break; + case fir::FIR_BOXPROC: + os << "boxproc<"; + p.printType(ty.cast().getEleTy()); + os << '>'; + break; + case fir::FIR_CHARACTER: // intrinsic + os << "char<" << ty.cast().getFKind() << '>'; + break; + case fir::FIR_COMPLEX: // intrinsic + os << "complex<" << ty.cast().getFKind() << '>'; + break; + case fir::FIR_DERIVED: { // derived + auto type = ty.cast(); + os << "type<" << type.getName(); + if (!recordTypeVisited.count(type.uniqueKey())) { + recordTypeVisited.insert(type.uniqueKey()); + if (type.getLenParamList().size()) { + char ch = '('; + for (auto p : type.getLenParamList()) { + os << ch << p.first << ':'; + p.second.print(os); + ch = ','; + } + os << ')'; + } + if (type.getTypeList().size()) { + char ch = '{'; + for (auto p : type.getTypeList()) { + os << ch << p.first << ':'; + p.second.print(os); + ch = ','; + } + os << '}'; + } + recordTypeVisited.erase(type.uniqueKey()); + } + os << '>'; + } break; + case fir::FIR_DIMS: + os << "dims<" << ty.cast().getRank() << '>'; + break; + case fir::FIR_FIELD: + os << "field"; + break; + case fir::FIR_HEAP: + os << "heap<"; + p.printType(ty.cast().getEleTy()); + os << '>'; + break; + case fir::FIR_INT: // intrinsic + os << "int<" << ty.cast().getFKind() << '>'; + break; + case fir::FIR_LEN: + os << "len"; + break; + case fir::FIR_LOGICAL: // intrinsic + os << "logical<" << ty.cast().getFKind() << '>'; + break; + case fir::FIR_POINTER: + os << "ptr<"; + p.printType(ty.cast().getEleTy()); + os << '>'; + break; + case fir::FIR_REAL: // intrinsic + os << "real<" << ty.cast().getFKind() << '>'; + break; + case fir::FIR_REFERENCE: + os << "ref<"; + p.printType(ty.cast().getEleTy()); + os << '>'; + break; + case fir::FIR_SEQUENCE: { + os << "array"; + auto type = ty.cast(); + auto shape = type.getShape(); + if (shape.size()) { + printBounds(os, shape); + } else { + os << "<*:"; + } + p.printType(ty.cast().getEleTy()); + if (auto map = type.getLayoutMap()) { + os << ", "; + map.print(os); + } + os << '>'; + } break; + case fir::FIR_TYPEDESC: + os << "tdesc<"; + p.printType(ty.cast().getOfTy()); + os << '>'; + break; + } +} diff --git a/lib/Optimizer/Support/CMakeLists.txt b/lib/Optimizer/Support/CMakeLists.txt new file mode 100644 index 000000000000..88a1fc78a5f6 --- /dev/null +++ b/lib/Optimizer/Support/CMakeLists.txt @@ -0,0 +1,10 @@ +add_llvm_library(FIRSupport + KindMapping.cpp +) + +target_link_libraries(FIRSupport FIRDialect) + +install (TARGETS FIRSupport + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) diff --git a/lib/Optimizer/Support/KindMapping.cpp b/lib/Optimizer/Support/KindMapping.cpp new file mode 100644 index 000000000000..957486ab0c1b --- /dev/null +++ b/lib/Optimizer/Support/KindMapping.cpp @@ -0,0 +1,239 @@ +//===-- KindMapping.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Support/KindMapping.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/CommandLine.h" + +/// Allow the user to set the FIR intrinsic type kind value to LLVM type +/// mappings. Note that these are not mappings from kind values to any +/// other MLIR dialect, only to LLVM IR. The default values follow the f18 +/// front-end kind mappings. + +using Bitsize = fir::KindMapping::Bitsize; +using KindTy = fir::KindMapping::KindTy; +using LLVMTypeID = fir::KindMapping::LLVMTypeID; +using MatchResult = fir::KindMapping::MatchResult; + +static llvm::cl::opt ClKindMapping( + "kind-mapping", llvm::cl::desc("kind mapping string to set kind precision"), + llvm::cl::value_desc("kind-mapping-string"), llvm::cl::init("")); + +namespace fir { +namespace { + +/// Integral types default to the kind value being the size of the value in +/// bytes. The default is to scale from bytes to bits. +Bitsize defaultScalingKind(KindTy kind) { + const unsigned BITS_IN_BYTE = 8; + return kind * BITS_IN_BYTE; +} + +/// Floating-point types default to the kind value being the size of the value +/// in bytes. The default is to translate kinds of 2, 4, 8, 10, and 16 to a +/// valid llvm::Type::TypeID value. Otherwise, the default is FloatTyID. +LLVMTypeID defaultRealKind(KindTy kind) { + switch (kind) { + case 2: + return LLVMTypeID::HalfTyID; + case 4: + return LLVMTypeID::FloatTyID; + case 8: + return LLVMTypeID::DoubleTyID; + case 10: + return LLVMTypeID::X86_FP80TyID; + case 16: + return LLVMTypeID::FP128TyID; + default: + return LLVMTypeID::FloatTyID; + } +} + +// lookup the kind-value given the defaults, the mappings, and a KIND key +template +RT doLookup(std::function def, + const std::map> &map, KindTy kind) { + auto iter = map.find(KEY); + if (iter != map.end()) { + auto iter2 = iter->second.find(kind); + if (iter2 != iter->second.end()) + return iter2->second; + } + return def(kind); +} + +// do a lookup for INTERGER, LOGICAL, or CHARACTER +template +Bitsize getIntegerLikeBitsize(KindTy kind, const MAP &map) { + return doLookup(defaultScalingKind, map, kind); +} + +// do a lookup for REAL or COMPLEX +template +LLVMTypeID getFloatLikeTypeID(KindTy kind, const MAP &map) { + return doLookup(defaultRealKind, map, kind); +} + +template +const llvm::fltSemantics &getFloatSemanticsOfKind(KindTy kind, const MAP &map) { + switch (doLookup(defaultRealKind, map, kind)) { + case LLVMTypeID::HalfTyID: + return llvm::APFloat::IEEEhalf(); + case LLVMTypeID::FloatTyID: + return llvm::APFloat::IEEEsingle(); + case LLVMTypeID::DoubleTyID: + return llvm::APFloat::IEEEdouble(); + case LLVMTypeID::X86_FP80TyID: + return llvm::APFloat::x87DoubleExtended(); + case LLVMTypeID::FP128TyID: + return llvm::APFloat::IEEEquad(); + case LLVMTypeID::PPC_FP128TyID: + return llvm::APFloat::PPCDoubleDouble(); + default: + llvm_unreachable("Invalid floating type"); + } +} + +MatchResult parseCode(char &code, const char *&ptr) { + if (*ptr != 'a' && *ptr != 'c' && *ptr != 'i' && *ptr != 'l' && *ptr != 'r') + return {}; + code = *ptr++; + return {true}; +} + +template +MatchResult parseSingleChar(const char *&ptr) { + if (*ptr != ch) + return {}; + ++ptr; + return {true}; +} + +MatchResult parseColon(const char *&ptr) { return parseSingleChar<':'>(ptr); } + +MatchResult parseComma(const char *&ptr) { return parseSingleChar<','>(ptr); } + +MatchResult parseInt(unsigned &result, const char *&ptr) { + const char *beg = ptr; + while (*ptr >= '0' && *ptr <= '9') + ptr++; + if (beg == ptr) + return {}; + llvm::StringRef ref(beg, ptr - beg); + int temp; + if (ref.consumeInteger(10, temp)) + return {}; + result = temp; + return {true}; +} + +bool matchString(const char *&ptr, llvm::StringRef literal) { + llvm::StringRef s(ptr); + if (s.startswith(literal)) { + ptr += literal.size(); + return true; + } + return false; +} + +MatchResult parseTypeID(LLVMTypeID &result, const char *&ptr) { + if (matchString(ptr, "Half")) { + result = LLVMTypeID::HalfTyID; + return {true}; + } + if (matchString(ptr, "Float")) { + result = LLVMTypeID::FloatTyID; + return {true}; + } + if (matchString(ptr, "Double")) { + result = LLVMTypeID::DoubleTyID; + return {true}; + } + if (matchString(ptr, "X86_FP80")) { + result = LLVMTypeID::X86_FP80TyID; + return {true}; + } + if (matchString(ptr, "FP128")) { + result = LLVMTypeID::FP128TyID; + return {true}; + } + return {}; +} + +} // namespace + +KindMapping::KindMapping(mlir::MLIRContext *context, llvm::StringRef map) + : context{context} { + parse(map); +} + +KindMapping::KindMapping(mlir::MLIRContext *context) + : KindMapping{context, ClKindMapping} {} + +MatchResult KindMapping::badMapString(const llvm::Twine &ptr) { + auto unknown = mlir::UnknownLoc::get(context); + mlir::emitError(unknown, ptr); + return {}; +} + +MatchResult KindMapping::parse(llvm::StringRef kindMap) { + if (kindMap.empty()) + return {true}; + const char *srcPtr = kindMap.begin(); + while (true) { + char code = '\0'; + KindTy kind = 0; + if (parseCode(code, srcPtr) || parseInt(kind, srcPtr)) + return badMapString(srcPtr); + if (code == 'a' || code == 'i' || code == 'l') { + Bitsize bits = 0; + if (parseColon(srcPtr) || parseInt(bits, srcPtr)) + return badMapString(srcPtr); + intMap[code][kind] = bits; + } else if (code == 'r' || code == 'c') { + LLVMTypeID id{}; + if (parseColon(srcPtr) || parseTypeID(id, srcPtr)) + return badMapString(srcPtr); + floatMap[code][kind] = id; + } else { + return badMapString(srcPtr); + } + if (parseComma(srcPtr)) + break; + } + if (*srcPtr) + return badMapString(srcPtr); + return {true}; +} + +Bitsize KindMapping::getCharacterBitsize(KindTy kind) { + return getIntegerLikeBitsize<'a'>(kind, intMap); +} + +Bitsize KindMapping::getIntegerBitsize(KindTy kind) { + return getIntegerLikeBitsize<'i'>(kind, intMap); +} + +Bitsize KindMapping::getLogicalBitsize(KindTy kind) { + return getIntegerLikeBitsize<'l'>(kind, intMap); +} + +LLVMTypeID KindMapping::getRealTypeID(KindTy kind) { + return getFloatLikeTypeID<'r'>(kind, floatMap); +} + +LLVMTypeID KindMapping::getComplexTypeID(KindTy kind) { + return getFloatLikeTypeID<'c'>(kind, floatMap); +} + +const llvm::fltSemantics &KindMapping::getFloatSemantics(KindTy kind) { + return getFloatSemanticsOfKind<'r'>(kind, floatMap); +} + +} // namespace fir diff --git a/test-lit/CMakeLists.txt b/test-lit/CMakeLists.txt index 111814303b0b..72a2c17cb547 100644 --- a/test-lit/CMakeLists.txt +++ b/test-lit/CMakeLists.txt @@ -21,6 +21,11 @@ set(FLANG_TEST_DEPENDS count not ) + +if (LINK_WITH_FIR) + list(APPEND FLANG_TEST_DEPENDS tco) +endif() + add_lit_testsuite(check-all "Running the Flang regression tests" ${CMAKE_CURRENT_BINARY_DIR} PARAMS ${FLANG_TEST_PARAMS} diff --git a/test-lit/Fir/fir-ops.fir b/test-lit/Fir/fir-ops.fir new file mode 100644 index 000000000000..661cf4c5835c --- /dev/null +++ b/test-lit/Fir/fir-ops.fir @@ -0,0 +1,458 @@ +// Test the FIR operations + +// RUN: tco -emit-fir %s | tco -emit-fir | FileCheck %s +// UNSUPPORTED: !fir + +// Fortran Intrinsic types +// CHECK-LABEL: func @it1() -> !fir.int<4> +// CHECK-LABEL: func @it2() -> !fir.real<8> +// CHECK-LABEL: func @it3() -> !fir.complex<8> +// CHECK-LABEL: func @it4() -> !fir.logical<1> +// CHECK-LABEL: func @it5() -> !fir.char<1> +func @it1() -> !fir.int<4> +func @it2() -> !fir.real<8> +func @it3() -> !fir.complex<8> +func @it4() -> !fir.logical<1> +func @it5() -> !fir.char<1> + +// Fortran Derived types (records) +// CHECK-LABEL: func @dvd1() -> !fir.type +// CHECK-LABEL: func @dvd2() -> !fir.type +// CHECK-LABEL: func @dvd3() -> !fir.type +// CHECK-LABEL: func @dvd4() -> !fir.type +func @dvd1() -> !fir.type +func @dvd2() -> !fir.type +func @dvd3() -> !fir.type +func @dvd4() -> !fir.type + +// FIR array types +// CHECK-LABEL: func @arr1() -> !fir.array<10xf32> +// CHECK-LABEL: func @arr2() -> !fir.array<10x10xf32> +// CHECK-LABEL: func @arr3() -> !fir.array +// CHECK-LABEL: func @arr4() -> !fir.array<10x?xf32> +// CHECK-LABEL: func @arr5() -> !fir.array +// CHECK-LABEL: func @arr6() -> !fir.array<*:f32> +func @arr1() -> !fir.array<10xf32> +func @arr2() -> !fir.array<10x10xf32> +func @arr3() -> !fir.array +func @arr4() -> !fir.array<10x?xf32> +func @arr5() -> !fir.array +func @arr6() -> !fir.array<*:f32> + +// FIR pointer-like types +// CHECK-LABEL: func @mem1() -> !fir.ref +// CHECK-LABEL: func @mem2() -> !fir.ptr +// CHECK-LABEL: func @mem3() -> !fir.heap +// CHECK-LABEL: func @mem4() -> !fir.ref<() -> ()> +func @mem1() -> !fir.ref +func @mem2() -> !fir.ptr +func @mem3() -> !fir.heap +func @mem4() -> !fir.ref<() -> ()> + +// FIR box types (descriptors) +// CHECK-LABEL: func @box1() -> !fir.box> +// CHECK-LABEL: func @box2() -> !fir.boxchar<2> +// CHECK-LABEL: func @box3() -> !fir.boxproc<(i32, i32) -> i64> +// CHECK-LABEL: func @box4() -> !fir.box> +func @box1() -> !fir.box> +func @box2() -> !fir.boxchar<2> +func @box3() -> !fir.boxproc<(i32, i32) -> i64> +func @box4() -> !fir.box> + +// FIR misc. types +// CHECK-LABEL: func @oth1() -> !fir.dims<1> +// CHECK-LABEL: func @oth2() -> !fir.field +// CHECK-LABEL: func @oth3() -> !fir.tdesc> +func @oth1() -> !fir.dims<1> +func @oth2() -> !fir.field +func @oth3() -> !fir.tdesc> + +// Fortran SUBROUTINE and FUNCTION +// CHECK-LABEL: func @print_index3(index, index, index) +// CHECK-LABEL: func @user_i64(i64) +// CHECK-LABEL: func @user_tdesc(!fir.tdesc>) +func @print_index3(index, index, index) +func @user_i64(i64) +func @user_tdesc(!fir.tdesc>) + +// expect the void return to be omitted +// CHECK-LABEL: func @store_tuple(tuple>) +func @store_tuple(tuple>) -> () + +// CHECK-LABEL: func @get_method_box() -> !fir.box> +// CHECK-LABEL: func @method_impl(!fir.box>) +func @get_method_box() -> !fir.box> +func @method_impl(!fir.box>) + +// CHECK-LABEL: func @nop() +func @nop() + +// CHECK-LABEL: func @get_func() -> (() -> ()) +func @get_func() -> (() -> ()) + +// CHECK-LABEL: @instructions +func @instructions() { + // CHECK: %[[A0:.*]] = fir.alloca !fir.array<10xi32> + %0 = fir.alloca !fir.array<10xi32> + // CHECK: fir.load %[[A0]] : !fir.ref> + %1 = fir.load %0 : !fir.ref> + %2 = fir.alloca i32 + %3 = constant 22 : i32 + // CHECK: fir.store %{{.*}} to %{{.*}} : !fir.ref + fir.store %3 to %2 : !fir.ref + // CHECK: fir.undefined i32 + %4 = fir.undefined i32 + // CHECK: %[[A5:.*]] = fir.allocmem !fir.array<100xf32> + %5 = fir.allocmem !fir.array<100xf32> + // CHECK: %[[A6:.*]] = fir.embox %[[A5]] : (!fir.heap>) -> !fir.box> + %6 = fir.embox %5 : (!fir.heap>) -> !fir.box> + // CHECK: fir.box_addr %{{.*}} : (!fir.box>) -> !fir.ref> + %7 = fir.box_addr %6 : (!fir.box>) -> !fir.ref> + %c0 = constant 0 : index + // CHECK: %[[A8:.*]]:3 = fir.box_dims %{{.*}}, %{{.*}} : (!fir.box>, index) -> (index, index, index) + %d1:3 = fir.box_dims %6, %c0 : (!fir.box>, index) -> (index, index, index) + // CHECK: fir.call @print_index3(%[[A8]]#0, %[[A8]]#1, %[[A8]]#2) : (index, index, index) + fir.call @print_index3(%d1#0, %d1#1, %d1#2) : (index, index, index) -> () + %8 = fir.call @it1() : () -> !fir.int<4> + // CHECK: fir.box_elesize %[[A6]] : (!fir.box>) -> i64 + %9 = fir.box_elesize %6 : (!fir.box>) -> i64 + // CHECK: fir.box_isalloc %[[A6]] : (!fir.box>) -> i1 + %10 = fir.box_isalloc %6 : (!fir.box>) -> i1 + // CHECK: fir.box_isarray %[[A6]] : (!fir.box>) -> i1 + %11 = fir.box_isarray %6 : (!fir.box>) -> i1 + // CHECK: fir.box_isptr %[[A6]] : (!fir.box>) -> i1 + %12 = fir.box_isptr %6 : (!fir.box>) -> i1 + // CHECK: fir.box_rank %[[A6]] : (!fir.box>) -> i64 + %13 = fir.box_rank %6 : (!fir.box>) -> i64 + // CHECK: fir.box_tdesc %[[A6]] : (!fir.box>) -> !fir.tdesc> + %14 = fir.box_tdesc %6 : (!fir.box>) -> !fir.tdesc> + %15 = fir.call @box2() : () -> !fir.boxchar<2> + // CHECK: fir.boxchar_len %{{.*}} : (!fir.boxchar<2>) -> i32 + %16 = fir.boxchar_len %15 : (!fir.boxchar<2>) -> i32 + %17 = fir.call @box3() : () -> !fir.boxproc<(i32, i32) -> i64> + // CHECK: fir.boxproc_host %{{.*}} : (!fir.boxproc<(i32, i32) -> i64>) -> !fir.ref + %18 = fir.boxproc_host %17 : (!fir.boxproc<(i32, i32) -> i64>) -> !fir.ref + %19 = constant 10 : i32 + // CHECK: fir.coordinate_of %{{.*}}, %{{.*}} : (!fir.heap>, i32) -> !fir.ref + %20 = fir.coordinate_of %5, %19 : (!fir.heap>, i32) -> !fir.ref + // CHECK: fir.field_index f, !fir.type + %21 = fir.field_index f, !fir.type + // CHECK: fir.undefined !fir.type + %22 = fir.undefined !fir.type + // CHECK: fir.extract_value %{{.*}}, %{{.*}} : (!fir.type, !fir.field) -> f32 + %23 = fir.extract_value %22, %21 : (!fir.type, !fir.field) -> f32 + %c1 = constant 1 : i32 + // CHECK: fir.gendims %{{.*}}, %{{.*}}, %{{.*}} : (i32, i32, i32) -> !fir.dims<1> + %24 = fir.gendims %c1, %19, %c1 : (i32, i32, i32) -> !fir.dims<1> + %cf1 = constant 1.0 : f32 + // CHECK: fir.insert_value %{{.*}}, %{{.*}}, %{{.*}} : (!fir.type, f32, !fir.field) -> !fir.type + %25 = fir.insert_value %22, %cf1, %21 : (!fir.type, f32, !fir.field) -> !fir.type + // CHECK: fir.len_param_index f, !fir.type + %26 = fir.len_param_index f, !fir.type + %27 = fir.call @box4() : () -> !fir.box> + // CHECK: fir.dispatch "method"(%{{.*}}) : (!fir.box>) -> i32 + %28 = fir.dispatch "method"(%27) : (!fir.box>) -> i32 + // CHECK: fir.convert %{{.*}} : (i32) -> i64 + %29 = fir.convert %28 : (i32) -> i64 + // CHECK: fir.gentypedesc !fir.type + %30 = fir.gentypedesc !fir.type + fir.call @user_tdesc(%30) : (!fir.tdesc>) -> () + // CHECK: fir.no_reassoc %{{.*}} : i64 + %31 = fir.no_reassoc %29 : i64 + fir.call @user_i64(%31) : (i64) -> () + // CHECK: fir.freemem %{{.*}} : !fir.heap> + fir.freemem %5 : !fir.heap> + %32 = fir.call @get_func() : () -> (() -> ()) + fir.call %32() : () -> () + // CHECK: fir.address_of (@it1) : !fir.ref<() -> !fir.int<4>> + %33 = fir.address_of (@it1) : !fir.ref<() -> !fir.int<4>> + return +} + +// CHECK-LABEL: @boxing_match +func @boxing_match() { + %0 = fir.alloca i32 + %d6 = fir.alloca !fir.type + %d3 = fir.alloca !fir.char<1> + %e6 = fir.alloca tuple + %1 = fir.embox %0 : (!fir.ref) -> !fir.box + // CHECK: fir.unbox %{{.*}} : (!fir.box) -> (!fir.ref, i32, i32, !fir.tdesc, i32, !fir.dims<0>) + %2:6 = fir.unbox %1 : (!fir.box) -> (!fir.ref,i32,i32,!fir.tdesc,i32,!fir.dims<0>) + %c8 = constant 8 : i32 + %3 = fir.undefined !fir.char<1> + // CHECK: fir.emboxchar %{{.*}}, %{{.*}} : (!fir.ref>, i32) -> !fir.boxchar<1> + // CHECK: fir.unboxchar %{{.*}} : (!fir.boxchar<1>) -> (!fir.ref>, i32) + %4 = fir.emboxchar %d3, %c8 : (!fir.ref>, i32) -> !fir.boxchar<1> + %5:2 = fir.unboxchar %4 : (!fir.boxchar<1>) -> (!fir.ref>, i32) + %6 = fir.undefined !fir.type + %z = constant 0 : i32 + %c12 = constant 12 : i32 + %a2 = fir.insert_value %6, %c12, %z : (!fir.type, i32, i32) -> !fir.type + %z1 = constant 1 : i32 + %c42 = constant 42.13 : f64 + %a3 = fir.insert_value %6, %c42, %z1 : (!fir.type, f64, i32) -> !fir.type + fir.store %a3 to %d6 : !fir.ref> + %7 = fir.emboxproc @method_impl, %e6 : ((!fir.box>) -> (), !fir.ref>) -> !fir.boxproc<(!fir.box>) -> ()> + %8:2 = fir.unboxproc %7 : (!fir.boxproc<(!fir.box>) -> ()>) -> ((!fir.box>) -> (), !fir.ref>>) + // CHECK: fir.emboxproc @method_impl, %{{.*}} : ((!fir.box>) -> (), !fir.ref>) -> !fir.boxproc<(!fir.box>) -> ()> + // CHECK: fir.unboxproc %{{.*}} : (!fir.boxproc<(!fir.box>) -> ()>) -> ((!fir.box>) -> (), !fir.ref>>) + %9 = call @box3() : () -> !fir.boxproc<(i32, i32) -> i64> + %10:2 = fir.unboxproc %9 : (!fir.boxproc<(i32, i32) -> i64>) -> ((i32, i32) -> i64, !fir.ref>>) + %11 = fir.load %10#1 : !fir.ref>> + fir.call @store_tuple(%11) : (tuple>) -> () + return +} + +// CHECK-LABEL: @loop +func @loop() { + %c1 = constant 1 : index + %c10 = constant 10 : index + %ct = constant true + // CHECK: fir.loop %{{.*}} = %{{.*}} to %{{.*}} { + %i = fir.loop %i = %c1 to %c10 { + // CHECK: fir.where %{{.*}} { + fir.where %ct { + fir.call @nop() : () -> () + // CHECK: } otherwise { + } otherwise { + fir.call @nop() : () -> () + } + } + // CHECK: fir.unreachable + fir.unreachable +} + +// CHECK-LABEL: @bar_select +func @bar_select(%arg : i32, %arg2 : i32) -> i32 { + %0 = constant 1 : i32 + %1 = constant 2 : i32 + %2 = constant 3 : i32 + %3 = constant 4 : i32 + // CHECK: fir.select %{{.*}} : i32 [1, ^bb1(%{{.*}} : i32), 2, ^bb2(%{{.*}}, %{{.*}}, %{{.*}} : i32, i32, i32), -3, ^bb3(%{{.*}}, %{{.*}} : i32, i32), 4, ^bb4(%{{.*}} : i32), unit, ^bb5] + fir.select %arg:i32 [ 1,^bb1(%0:i32), 2,^bb2(%2,%arg,%arg2:i32,i32,i32), -3,^bb3(%arg2,%2:i32,i32), 4,^bb4(%1:i32), unit,^bb5 ] +^bb1(%a : i32) : + return %a : i32 +^bb2(%b : i32, %b2 : i32, %b3:i32) : + %4 = addi %b, %b2 : i32 + %5 = addi %4, %b3 : i32 + return %5 : i32 +^bb3(%c:i32, %c2:i32) : + %6 = addi %c, %c2 : i32 + return %6 : i32 +^bb4(%d : i32) : + return %d : i32 +^bb5 : + %zero = constant 0 : i32 + return %zero : i32 +} + +// CHECK-LABEL: @bar_select_rank +func @bar_select_rank(%arg : i32, %arg2 : i32) -> i32 { + %0 = constant 1 : i32 + %1 = constant 2 : i32 + %2 = constant 3 : i32 + %3 = constant 4 : i32 + // CHECK: fir.select_rank %{{.*}} : i32 [1, ^bb1(%{{.*}} : i32), 2, ^bb2(%{{.*}}, %{{.*}}, %{{.*}} : i32, i32, i32), 3, ^bb3(%{{.*}}, %{{.*}} : i32, i32), -1, ^bb4(%{{.*}} : i32), unit, ^bb5] + fir.select_rank %arg:i32 [ 1,^bb1(%0:i32), 2,^bb2(%2,%arg,%arg2:i32,i32,i32), 3,^bb3(%arg2,%2:i32,i32), -1,^bb4(%1:i32), unit,^bb5 ] +^bb1(%a : i32) : + return %a : i32 +^bb2(%b : i32, %b2 : i32, %b3:i32) : + %4 = addi %b, %b2 : i32 + %5 = addi %4, %b3 : i32 + return %5 : i32 +^bb3(%c:i32, %c2:i32) : + %6 = addi %c, %c2 : i32 + return %6 : i32 +^bb4(%d : i32) : + return %d : i32 +^bb5 : + %zero = constant 0 : i32 + %7 = fir.call @get_method_box() : () -> !fir.box> + fir.dispatch method(%7) : (!fir.box>) -> () + return %zero : i32 +} + +// CHECK-LABEL: @bar_select_type +func @bar_select_type(%arg : !fir.box}>>) -> i32 { + %0 = constant 1 : i32 + %1 = constant 2 : i32 + %2 = constant 3 : i32 + %3 = constant 4 : i32 + // CHECK: fir.select_type %{{.*}} : !fir.box}>> [#fir.instance>, ^bb1(%{{.*}} : i32), #fir.instance>, ^bb2(%{{.*}} : i32), #fir.subsumed>, ^bb3(%{{.*}} : i32), #fir.instance>, ^bb4(%{{.*}} : i32), unit, ^bb5] + fir.select_type %arg : !fir.box}>> [ #fir.instance>,^bb1(%0:i32), #fir.instance>,^bb2(%2:i32), #fir.subsumed>,^bb3(%2:i32), #fir.instance>,^bb4(%1:i32), unit,^bb5 ] +^bb1(%a : i32) : + return %a : i32 +^bb2(%b : i32) : + return %b : i32 +^bb3(%c : i32) : + return %c : i32 +^bb4(%d : i32) : + return %d : i32 +^bb5 : + %zero = constant 0 : i32 + return %zero : i32 +} + +// CHECK-LABEL: @bar_select_case +func @bar_select_case(%arg : i32, %arg2 : i32) -> i32 { + %0 = constant 1 : i32 + %1 = constant 2 : i32 + %2 = constant 3 : i32 + %3 = constant 4 : i32 + // CHECK: fir.select_case %{{.*}} : i32 [#fir.point, %{{.*}}, ^bb1(%{{.*}} : i32), #fir.lower, %{{.*}}, ^bb2(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}} : i32, i32, i32, i32), #fir.interval, %{{.*}}, %{{.*}}, ^bb3(%{{.*}}, %{{.*}} : i32, i32), #fir.upper, %{{.*}}, ^bb4(%{{.*}} : i32), unit, ^bb5] + fir.select_case %arg : i32 [#fir.point, %0, ^bb1(%0:i32), #fir.lower, %1, ^bb2(%2,%arg,%arg2,%1:i32,i32,i32,i32), #fir.interval, %2, %3, ^bb3(%2,%arg2:i32,i32), #fir.upper, %arg, ^bb4(%1:i32), unit, ^bb5] +^bb1(%a : i32) : + return %a : i32 +^bb2(%b : i32, %b2:i32, %b3:i32, %b4:i32) : + %4 = addi %b, %b2 : i32 + %5 = muli %4, %b3 : i32 + %6 = addi %5, %b4 : i32 + return %6 : i32 +^bb3(%c : i32, %c2 : i32) : + %7 = addi %c, %c2 : i32 + return %7 : i32 +^bb4(%d : i32) : + return %d : i32 +^bb5 : + %zero = constant 0 : i32 + return %zero : i32 +} + +// CHECK-LABEL: @global_var +fir.global @global_var : i32 { + %0 = constant 1 : i32 + fir.has_value %0 : i32 +} + +// CHECK-LABEL: @global_constant +fir.global @global_constant constant : i32 { + %0 = constant 934 : i32 + fir.has_value %0 : i32 +} + +// CHECK-LABEL: @global_derived +fir.global @global_derived : !fir.type { + // CHECK: fir.global_len "f", 1 : i32 + fir.global_len f, 1 : i32 + %0 = fir.undefined !fir.type + fir.has_value %0 : !fir.type +} + +// CHECK-LABEL: @dispatch_tbl +fir.dispatch_table @dispatch_tbl { + // CHECK: fir.dt_entry "method", @method_impl + fir.dt_entry "method", @method_impl +} + +// CHECK-LABEL: @compare_real +func @compare_real(%a : !fir.real<16>, %b : !fir.real<16>) { + // CHECK: fir.cmpf "false", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "oeq", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "ogt", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "oge", %{{.*}}, %{{.*}} : !fir.real<16> + %d0 = fir.cmpf "false", %a, %b : !fir.real<16> + %d1 = fir.cmpf "oeq", %a, %b : !fir.real<16> + %d2 = fir.cmpf "ogt", %a, %b : !fir.real<16> + %d3 = fir.cmpf "oge", %a, %b : !fir.real<16> + // CHECK: fir.cmpf "olt", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "ole", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "one", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "ord", %{{.*}}, %{{.*}} : !fir.real<16> + %a0 = fir.cmpf "olt", %a, %b : !fir.real<16> + %a1 = fir.cmpf "ole", %a, %b : !fir.real<16> + %a2 = fir.cmpf "one", %a, %b : !fir.real<16> + %a3 = fir.cmpf "ord", %a, %b : !fir.real<16> + // CHECK: fir.cmpf "ueq", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "ugt", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "uge", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "ult", %{{.*}}, %{{.*}} : !fir.real<16> + %b0 = fir.cmpf "ueq", %a, %b : !fir.real<16> + %b1 = fir.cmpf "ugt", %a, %b : !fir.real<16> + %b2 = fir.cmpf "uge", %a, %b : !fir.real<16> + %b3 = fir.cmpf "ult", %a, %b : !fir.real<16> + // CHECK: fir.cmpf "ule", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "une", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "uno", %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.cmpf "true", %{{.*}}, %{{.*}} : !fir.real<16> + %c0 = fir.cmpf "ule", %a, %b : !fir.real<16> + %c1 = fir.cmpf "une", %a, %b : !fir.real<16> + %c2 = fir.cmpf "uno", %a, %b : !fir.real<16> + %c3 = fir.cmpf "true", %a, %b : !fir.real<16> + return +} + +// CHECK-LABEL: @compare_complex +func @compare_complex(%a : !fir.complex<16>, %b : !fir.complex<16>) { + // CHECK: fir.cmpc "false", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "oeq", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "ogt", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "oge", %{{.*}}, %{{.*}} : !fir.complex<16> + %d0 = fir.cmpc "false", %a, %b : !fir.complex<16> + %d1 = fir.cmpc "oeq", %a, %b : !fir.complex<16> + %d2 = fir.cmpc "ogt", %a, %b : !fir.complex<16> + %d3 = fir.cmpc "oge", %a, %b : !fir.complex<16> + // CHECK: fir.cmpc "olt", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "ole", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "one", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "ord", %{{.*}}, %{{.*}} : !fir.complex<16> + %a0 = fir.cmpc "olt", %a, %b : !fir.complex<16> + %a1 = fir.cmpc "ole", %a, %b : !fir.complex<16> + %a2 = fir.cmpc "one", %a, %b : !fir.complex<16> + %a3 = fir.cmpc "ord", %a, %b : !fir.complex<16> + // CHECK: fir.cmpc "ueq", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "ugt", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "uge", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "ult", %{{.*}}, %{{.*}} : !fir.complex<16> + %b0 = fir.cmpc "ueq", %a, %b : !fir.complex<16> + %b1 = fir.cmpc "ugt", %a, %b : !fir.complex<16> + %b2 = fir.cmpc "uge", %a, %b : !fir.complex<16> + %b3 = fir.cmpc "ult", %a, %b : !fir.complex<16> + // CHECK: fir.cmpc "ule", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "une", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "uno", %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.cmpc "true", %{{.*}}, %{{.*}} : !fir.complex<16> + %c0 = fir.cmpc "ule", %a, %b : !fir.complex<16> + %c1 = fir.cmpc "une", %a, %b : !fir.complex<16> + %c2 = fir.cmpc "uno", %a, %b : !fir.complex<16> + %c3 = fir.cmpc "true", %a, %b : !fir.complex<16> + return +} + +// CHECK-LABEL: @arith_real +func @arith_real(%a : !fir.real<16>, %b : !fir.real<16>) -> !fir.real<16> { + %c1 = constant 1.0 : f32 + %0 = fir.convert %c1 : (f32) -> !fir.real<16> + // CHECK: %[[R1:.*]] = fir.negf %{{.*}} : !fir.real<16> + // CHECK: fir.addf %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: %[[R3:.*]] = fir.subf %{{.*}}, %{{.*}} : !fir.real<16> + // CHECK: fir.mulf %[[R1]], %[[R3]] : !fir.real<16> + // CHECK: fir.divf %{{.*}}, %{{.*}} : !fir.real<16> + %1 = fir.negf %a : !fir.real<16> + %2 = fir.addf %0, %1 : !fir.real<16> + %3 = fir.subf %2, %b : !fir.real<16> + %4 = fir.mulf %1, %3 : !fir.real<16> + %5 = fir.divf %4, %a : !fir.real<16> + return %5 : !fir.real<16> +} + +// CHECK-LABEL: @arith_complex +func @arith_complex(%a : !fir.complex<16>, %b : !fir.complex<16>) -> !fir.complex<16> { + // CHECK: fir.negc %{{.*}} : !fir.complex<16> + // CHECK: fir.addc %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.subc %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.mulc %{{.*}}, %{{.*}} : !fir.complex<16> + // CHECK: fir.divc %{{.*}}, %{{.*}} : !fir.complex<16> + %1 = fir.negc %a : !fir.complex<16> + %2 = fir.addc %b, %1 : !fir.complex<16> + %3 = fir.subc %2, %b : !fir.complex<16> + %4 = fir.mulc %1, %3 : !fir.complex<16> + %5 = fir.divc %4, %a : !fir.complex<16> + return %5 : !fir.complex<16> +} + +// CHECK-LABEL: @character_literal +func @character_literal() -> !fir.array<13 x !fir.char<1>> { + // CHECK: fir.string_lit "Hello, World!"(13) : !fir.char<1> + %0 = fir.string_lit "Hello, World!"(13) : !fir.char<1> + return %0 : !fir.array<13 x !fir.char<1>> +} diff --git a/test-lit/Fir/fir-types.fir b/test-lit/Fir/fir-types.fir new file mode 100644 index 000000000000..a9e194c1f15b --- /dev/null +++ b/test-lit/Fir/fir-types.fir @@ -0,0 +1,70 @@ +// Test the FIR types + +// RUN: tco -emit-fir %s | tco -emit-fir | FileCheck %s +// UNSUPPORTED: !fir + +// CHECK-LABEL: func @it1() -> !fir.int<4> +// CHECK-LABEL: func @it2() -> !fir.real<8> +// CHECK-LABEL: func @it3() -> !fir.complex<8> +// CHECK-LABEL: func @it4() -> !fir.logical<1> +// CHECK-LABEL: func @it5() -> !fir.char<1> +func @it1() -> !fir.int<4> +func @it2() -> !fir.real<8> +func @it3() -> !fir.complex<8> +func @it4() -> !fir.logical<1> +func @it5() -> !fir.char<1> + +// CHECK-LABEL: func @dvd1() -> !fir.type +// CHECK-LABEL: func @dvd2() -> !fir.type +// CHECK-LABEL: func @dvd3() -> !fir.type +// CHECK-LABEL: func @dvd4() -> !fir.type +// CHECK-LABEL: func @dvd5() -> !fir.type +// CHECK-LABEL: func @dvd6() -> !fir.type>}> +func @dvd1() -> !fir.type +func @dvd2() -> !fir.type +func @dvd3() -> !fir.type +func @dvd4() -> !fir.type +func @dvd5() -> !fir.type +func @dvd6() -> !fir.type>}> + +// CHECK-LABEL: func @arr1() -> !fir.array<10xf32> +// CHECK-LABEL: func @arr2() -> !fir.array<10x10xf32> +// CHECK-LABEL: func @arr3() -> !fir.array +// CHECK-LABEL: func @arr4() -> !fir.array<10x?xf32> +// CHECK-LABEL: func @arr5() -> !fir.array +// CHECK-LABEL: func @arr6() -> !fir.array<*:f32> +// CHECK-LABEL: func @arr7() -> !fir.array<1x2x?x4x5x6x7x8x9xf32> +func @arr1() -> !fir.array<10xf32> +func @arr2() -> !fir.array<10x10xf32> +func @arr3() -> !fir.array +func @arr4() -> !fir.array<10x?xf32> +func @arr5() -> !fir.array +func @arr6() -> !fir.array<*:f32> +func @arr7() -> !fir.array<1x2x?x4x5x6x7x8x9xf32> + +// CHECK-LABEL: func @mem1() -> !fir.ref +// CHECK-LABEL: func @mem2() -> !fir.ptr +// CHECK-LABEL: func @mem3() -> !fir.heap +// CHECK-LABEL: func @mem4() -> !fir.ref<() -> ()> +func @mem1() -> !fir.ref +func @mem2() -> !fir.ptr +func @mem3() -> !fir.heap +func @mem4() -> !fir.ref<() -> ()> + +// CHECK-LABEL: func @box1() -> !fir.box> +// CHECK-LABEL: func @box2() -> !fir.boxchar<2> +// CHECK-LABEL: func @box3() -> !fir.boxproc<(i32, i32) -> i64> +// CHECK-LABEL: func @box4() -> !fir.box +func @box1() -> !fir.box> +func @box2() -> !fir.boxchar<2> +func @box3() -> !fir.boxproc<(i32, i32) -> i64> +func @box4() -> !fir.box + +// CHECK-LABEL: func @oth1() -> !fir.dims<1> +// CHECK-LABEL: func @oth2() -> !fir.field +// CHECK-LABEL: func @oth3() -> !fir.tdesc> +// CHECK-LABEL: func @oth4() -> !fir.dims<15> +func @oth1() -> !fir.dims<1> +func @oth2() -> !fir.field +func @oth3() -> !fir.tdesc> +func @oth4() -> !fir.dims<15> diff --git a/test-lit/lit.cfg.py b/test-lit/lit.cfg.py index 3ca3c8ad23f7..2dce888c33fd 100644 --- a/test-lit/lit.cfg.py +++ b/test-lit/lit.cfg.py @@ -28,7 +28,7 @@ # suffixes: A list of file extensions to treat as test files. config.suffixes = ['.f', '.F', '.ff','.FOR', '.for', '.f77', '.f90', '.F90', '.ff90', '.f95', '.F95', '.ff95', '.fpp', '.FPP', '.cuf', - '.CUF', '.f18', '.F18'] + '.CUF', '.f18', '.F18', '.fir' ] # test_source_root: The root path where tests are located. config.test_source_root = os.path.dirname(__file__) @@ -56,6 +56,12 @@ llvm_config.with_environment('PATH', config.flang_tools_dir, append_path=True) llvm_config.with_environment('PATH', config.llvm_tools_dir, append_path=True) +# For builds with FIR, set path for tco and enable related tests +if config.flang_llvm_tools_dir != "" : + config.available_features.add('fir') + if config.llvm_tools_dir != config.flang_llvm_tools_dir : + llvm_config.with_environment('PATH', config.flang_llvm_tools_dir, append_path=True) + # For each occurrence of a flang tool name, replace it with the full path to # the build directory holding that tool. We explicitly specify the directories # to search to ensure that we get the tools just built and not some random diff --git a/test-lit/lit.site.cfg.py.in b/test-lit/lit.site.cfg.py.in index d00f3856fb41..fe428f9bee06 100644 --- a/test-lit/lit.site.cfg.py.in +++ b/test-lit/lit.site.cfg.py.in @@ -7,6 +7,7 @@ config.flang_obj_root = "@FLANG_BINARY_DIR@" config.flang_src_dir = "@FLANG_SOURCE_DIR@" config.flang_tools_dir = "@FLANG_TOOLS_DIR@" config.flang_intrinsic_modules_dir = "@FLANG_INTRINSIC_MODULES_DIR@" +config.flang_llvm_tools_dir = "@LLVM_RUNTIME_OUTPUT_INTDIR@" config.python_executable = "@PYTHON_EXECUTABLE@" # Support substitution of the tools_dir with user parameters. This is diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 41983a7b2e39..a02679436d14 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -6,5 +6,7 @@ # #===------------------------------------------------------------------------===# - add_subdirectory(f18) +if(LINK_WITH_FIR) + add_subdirectory(tco) +endif() diff --git a/tools/tco/CMakeLists.txt b/tools/tco/CMakeLists.txt new file mode 100644 index 000000000000..f08134e16b9e --- /dev/null +++ b/tools/tco/CMakeLists.txt @@ -0,0 +1,22 @@ +set(LIBS + FIRDialect + FIRSupport + MLIRAllDialects + MLIRIR + MLIRLLVMIR + MLIRPass + MLIRStandardToLLVM + MLIRTransforms + MLIRAffineToStandard + MLIRAnalysis + MLIRLoopToStandard + MLIREDSC + MLIRParser + MLIRStandardToLLVM + MLIRSupport + MLIRVectorToLLVM +) + +add_llvm_tool(tco tco.cpp) +llvm_update_compile_flags(tco) +target_link_libraries(tco PRIVATE ${LIBS}) diff --git a/tools/tco/tco.cpp b/tools/tco/tco.cpp new file mode 100644 index 000000000000..d1fbd3b656a3 --- /dev/null +++ b/tools/tco/tco.cpp @@ -0,0 +1,119 @@ +//===- tco.cpp - Tilikum Crossing Opt ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is to be like LLVM's opt program, only for FIR. Such a program is +// required for roundtrip testing, etc. +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Dialect/FIRDialect.h" +#include "flang/Optimizer/Support/KindMapping.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Module.h" +#include "mlir/InitAllDialects.h" +#include "mlir/Parser.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Transforms/Passes.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +namespace { + +using namespace llvm; + +cl::opt inputFilename(cl::Positional, cl::desc(""), + cl::init("-")); + +cl::opt outputFilename("o", cl::desc("Specify output filename"), + cl::value_desc("filename"), cl::init("-")); + +cl::opt emitFir("emit-fir", cl::desc("Parse and pretty-print the input"), + cl::init(false)); + +void printModuleBody(mlir::ModuleOp mod, raw_ostream &output) { + // don't output the terminator bogo-op + auto e{--mod.end()}; + for (auto i{mod.begin()}; i != e; ++i) { + i->print(output); + output << "\n\n"; + } +} + +// compile a .fir file +int compileFIR() { + // check that there is a file to load + ErrorOr> fileOrErr = + MemoryBuffer::getFileOrSTDIN(inputFilename); + + if (std::error_code EC = fileOrErr.getError()) { + errs() << "Could not open file: " << EC.message() << '\n'; + return 1; + } + + // load the file into a module + SourceMgr sourceMgr; + sourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), SMLoc()); + auto context = std::make_unique(); + auto owningRef = mlir::parseSourceFile(sourceMgr, context.get()); + + if (!owningRef) { + errs() << "Error can't load file " << inputFilename << '\n'; + return 2; + } + if (mlir::failed(owningRef->verify())) { + errs() << "Error verifying FIR module\n"; + return 4; + } + + std::error_code ec; + ToolOutputFile out(outputFilename, ec, sys::fs::OF_None); + + // run passes + mlir::PassManager pm{context.get()}; + mlir::applyPassManagerCLOptions(pm); + if (emitFir) { + // parse the input and pretty-print it back out + // -emit-fir intentionally disables all the passes + } else { + // TODO: Actually add passes when added to FIR code base + // add all the passes + // the user can disable them individually + } + + // run the pass manager + if (mlir::succeeded(pm.run(*owningRef))) { + // passes ran successfully, so keep the output + if (emitFir) + printModuleBody(*owningRef, out.os()); + out.keep(); + return 0; + } + + // pass manager failed + printModuleBody(*owningRef, errs()); + errs() << "\nFAILED: " << inputFilename << '\n'; + return 8; +} +} // namespace + +int main(int argc, char **argv) { + mlir::registerAllDialects(); + fir::registerFIR(); + fir::registerFIRPasses(); + [[maybe_unused]] InitLLVM y(argc, argv); + mlir::registerPassManagerCLOptions(); + mlir::PassPipelineCLParser passPipe("", "Compiler passes to run"); + cl::ParseCommandLineOptions(argc, argv, "Tilikum Crossing Optimizer\n"); + return compileFIR(); +}