From 4c74e87023cdc8a8db9803cce115b09e0390ab3d Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Fri, 4 Mar 2022 08:48:45 +0100 Subject: [PATCH] [Moore] Add SystemVerilog types (#2699) Add an implementation of the SystemVerilog type system to the Moore dialect, modeled after the one in Moore's Rust codebase: https://github.com/fabianschuiki/moore/blob/master/src/svlog/ty.rs This is the first step towards migrating a larger chunk of the Moore codebase into CIRCT, as it allows Moore's codegen to start emitting higher-level operations (e.g., `moore.mir.concat`, to be added later) instead of directly dropping to LLHD/HW. Doing so will allow us to eventually move the codegen over into CIRCT, and start work on implementing the type checking and type inference on the higher-level operations. My hope is that we might eventually be able to reconcile the Moore types and some of the higher-level operations with the SV dialect, since both work with SystemVerilog, albeit for two diametrically opposed purposes. The types are designed to very clearly distinguish between packed and unpacked types, and provide a certain level of guarantees about the structure of nested types through C++ types. For example, struct aggregate types and typedefs/decltype constructs come in a packed and unpacked flavor to enforce proper nesting of these types. Where user-defined types are involved, for example through structs and typedefs/decltype, the MLIR types aim to capture enough information to faithfully reconstruct the type as it was originally formulated by the user. This helps provide good and understandable diagnostics. As a concrete example, integer types capture whether their sign was provided explicitly by the user, in order to distinguish `int` and `int signed`, despite those two types being semantically identical. This commit also adds a `unittests` directory as seen in LLVM and MLIR, to test the human-readable serialization of the Moore types and other type attributes. --- CMakeLists.txt | 44 +- include/circt/Dialect/Moore/MooreDialect.td | 5 + include/circt/Dialect/Moore/MooreTypes.h | 1031 ++++++++++++ include/circt/Dialect/Moore/MooreTypesImpl.td | 9 - lib/Conversion/MooreToCore/MooreToCore.cpp | 4 +- lib/Dialect/Moore/MooreTypes.cpp | 1450 ++++++++++++++++- test/CMakeLists.txt | 10 + test/Conversion/MooreToCore/basic.mlir | 16 +- test/Dialect/Moore/basic.mlir | 12 +- test/Dialect/Moore/types-errors.mlir | 33 + test/Dialect/Moore/types.mlir | 153 ++ test/Unit/lit.cfg.py | 39 + test/Unit/lit.site.cfg.py.in | 27 + unittests/CMakeLists.txt | 8 + unittests/Dialect/CMakeLists.txt | 1 + unittests/Dialect/Moore/CMakeLists.txt | 8 + unittests/Dialect/Moore/TypesTest.cpp | 402 +++++ 17 files changed, 3217 insertions(+), 35 deletions(-) create mode 100644 test/Dialect/Moore/types-errors.mlir create mode 100644 test/Dialect/Moore/types.mlir create mode 100644 test/Unit/lit.cfg.py create mode 100644 test/Unit/lit.site.cfg.py.in create mode 100644 unittests/CMakeLists.txt create mode 100644 unittests/Dialect/CMakeLists.txt create mode 100644 unittests/Dialect/Moore/CMakeLists.txt create mode 100644 unittests/Dialect/Moore/TypesTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 533eb7d2bf..6eca9a4aa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,46 @@ endif () set(CIRCT_BUILT_STANDALONE 1) set(BACKEND_PACKAGE_STRING "LLVM ${LLVM_PACKAGE_VERSION}") + + # Handle unittests when building out-of-tree against an installed version of + # LLVM/MLIR (not a build tree). Adapted from `llvm/flang/CMakeLists.txt`. + set(CIRCT_GTEST_AVAILABLE 0) + set(UNITTEST_DIR ${LLVM_BUILD_MAIN_SRC_DIR}/utils/unittest) + if (NOT EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h) + set(UNITTEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/llvm/llvm/utils/unittest) + endif() + if (EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h) + if (NOT TARGET gtest) + find_package(Threads) + add_llvm_library(gtest + ${UNITTEST_DIR}/googletest/src/gtest-all.cc + ${UNITTEST_DIR}/googlemock/src/gmock-all.cc + LINK_LIBS pthread + LINK_COMPONENTS Support # llvm::raw_ostream + BUILDTREE_ONLY + ) + target_include_directories(gtest + PUBLIC + "${UNITTEST_DIR}/googletest/include" + "${UNITTEST_DIR}/googlemock/include" + PRIVATE + "${UNITTEST_DIR}/googletest" + "${UNITTEST_DIR}/googlemock" + ) + add_llvm_library(gtest_main + ${UNITTEST_DIR}/UnitTestMain/TestMain.cpp + LINK_LIBS gtest + LINK_COMPONENTS Support # llvm::cl + BUILDTREE_ONLY + ) + endif() + set(CIRCT_GTEST_AVAILABLE 1) + else() + message(WARNING "Skipping unittests since LLVM install does not include \ + gtest headers and libraries") + set(CIRCT_GTEST_AVAILABLE 0) + endif() + else() # CMake library generation settings. set(BUILD_SHARED_LIBS OFF CACHE BOOL "Default to building a static mondo-lib") @@ -369,7 +409,9 @@ endif() add_subdirectory(include/circt) add_subdirectory(lib) add_subdirectory(tools) -#add_subdirectory(unittests) +if (CIRCT_GTEST_AVAILABLE) + add_subdirectory(unittests) +endif() add_subdirectory(test) add_subdirectory(integration_test) add_subdirectory(frontends) diff --git a/include/circt/Dialect/Moore/MooreDialect.td b/include/circt/Dialect/Moore/MooreDialect.td index 0507084045..2139a7c111 100644 --- a/include/circt/Dialect/Moore/MooreDialect.td +++ b/include/circt/Dialect/Moore/MooreDialect.td @@ -26,7 +26,12 @@ def MooreDialect : Dialect { let extraClassDeclaration = [{ /// Register all Moore types. void registerTypes(); + + /// Type parsing and printing. + Type parseType(DialectAsmParser &parser) const override; + void printType(Type, DialectAsmPrinter &) const override; }]; + let useDefaultTypePrinterParser = 0; } #endif // CIRCT_DIALECT_MOORE_MOOREDIALECT diff --git a/include/circt/Dialect/Moore/MooreTypes.h b/include/circt/Dialect/Moore/MooreTypes.h index ddb99696c5..ad1b03d397 100644 --- a/include/circt/Dialect/Moore/MooreTypes.h +++ b/include/circt/Dialect/Moore/MooreTypes.h @@ -13,9 +13,1040 @@ #ifndef CIRCT_DIALECT_MOORE_MOORETYPES_H #define CIRCT_DIALECT_MOORE_MOORETYPES_H +#include "circt/Support/LLVM.h" #include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Location.h" #include "mlir/IR/Types.h" +#include +namespace circt { +namespace moore { + +/// The number of values each bit of a type can assume. +enum class Domain { + /// Two-valued types such as `bit` or `int`. + TwoValued, + /// Four-valued types such as `logic` or `integer`. + FourValued, +}; + +/// Whether a type is signed or unsigned. +enum class Sign { + /// An `unsigned` type. + Unsigned, + /// A `signed` type. + Signed, +}; + +/// Map a `Sign` to the corresponding keyword. +StringRef getKeywordFromSign(const Sign &sign); +/// Map the keywords `unsigned` and `signed` to the corresponding `Sign`. +Optional getSignFromKeyword(StringRef keyword); + +template +Os &operator<<(Os &os, const Sign &sign) { + os << getKeywordFromSign(sign); + return os; +} + +/// Which side is greater in a range `[a:b]`. +enum class RangeDir { + /// `a < b` + Up, + /// `a >= b` + Down, +}; + +/// The `[a:b]` part in a vector/array type such as `logic [a:b]`. +struct Range { + /// The total number of bits, given as `|a-b|+1`. + unsigned size; + /// The direction of the vector, i.e. whether `a > b` or `a < b`. + RangeDir dir; + /// The starting offset of the range. + int offset; + + /// Construct a range `[size-1:0]`. + explicit Range(unsigned size) : Range(size, RangeDir::Down, 0) {} + + /// Construct a range `[offset+size-1:offset]` if `dir` is `Down`, or + /// `[offset:offset+size-1]` if `dir` is `Up`. + Range(unsigned size, RangeDir dir, int offset) + : size(size), dir(dir), offset(offset) {} + + /// Construct a range [left:right]`, with the direction inferred as `Down` if + /// `left >= right`, or `Up` otherwise. + Range(int left, int right) { + if (left >= right) { + size = left + 1 - right; + dir = RangeDir::Down; + offset = right; + } else { + size = right + 1 - left; + dir = RangeDir::Up; + offset = left; + } + } + + bool operator==(const Range &other) const { + return size == other.size && dir == other.dir && offset == other.offset; + } + + /// Get the `$left` dimension. + int left() const { return dir == RangeDir::Up ? low() : high(); } + /// Get the `$right` dimension. + int right() const { return dir == RangeDir::Up ? high() : low(); } + /// Get the `$low` dimension. + int low() const { return offset; } + /// Get the `$high` dimension. + int high() const { return offset + size - 1; } + /// Get the `$increment` size. + int increment() const { return dir == RangeDir::Up ? 1 : -1; } + + /// Format this range as a string. + std::string toString() const { + std::string buffer; + llvm::raw_string_ostream(buffer) << *this; + return buffer; + } +}; + +inline llvm::hash_code hash_value(const Range &x) { + return llvm::hash_combine(x.size, x.dir, x.offset); +} + +template +Os &operator<<(Os &os, const Range &range) { + os << range.left() << ":" << range.right(); + return os; +} + +namespace detail { +struct RealTypeStorage; +struct IntTypeStorage; +struct IndirectTypeStorage; +struct DimStorage; +struct UnsizedDimStorage; +struct RangeDimStorage; +struct SizedDimStorage; +struct AssocDimStorage; +struct EnumTypeStorage; +struct StructTypeStorage; +} // namespace detail + +/// Base class for all SystemVerilog types in the Moore dialect. +class SVType : public Type { +protected: + using Type::Type; +}; + +//===----------------------------------------------------------------------===// +// Unpacked Type +//===----------------------------------------------------------------------===// + +class PackedType; +class StringType; +class ChandleType; +class EventType; +class RealType; +class UnpackedIndirectType; +class UnpackedDim; +class UnpackedStructType; + +/// An unpacked SystemVerilog type. +/// +/// Unpacked types are a second level of types in SystemVerilog. They extend a +/// core unpacked type with a variety of unpacked dimensions, depending on which +/// syntactic construct generated the type (variable or otherwise). The core +/// unpacked types are: +/// +/// - Packed types +/// - Non-integer types: `shortreal`, `real`, `realtime` +/// - Unpacked structs and unions +/// - `string`, `chandle`, `event` +/// - Virtual interfaces +/// - Class types +/// - Covergroups +/// - Unpacked named types +/// - Unpacked type references +/// +/// The unpacked dimensions are: +/// +/// - Unsized (`[]`) +/// - Arrays (`[x]`) +/// - Ranges (`[x:y]`) +/// - Associative (`[T]` or `[*]`) +/// - Queues (`[$]` or `[$:x]`) +class UnpackedType : public SVType { +public: + static bool classof(Type type) { + return type.isa() || type.isa() || + type.isa() || type.isa() || + type.isa() || type.isa() || + type.isa() || type.isa(); + } + + /// Resolve one level of name or type reference indirection. + /// + /// For example, given `typedef int foo; typedef foo bar;`, resolves `bar` + /// to `foo`. + UnpackedType resolved() const; + + /// Resolve all name or type reference indirections. + /// + /// For example, given `typedef int foo; typedef foo bar;`, resolves `bar` + /// to `int`. + UnpackedType fullyResolved() const; + + /// Get the value domain of this type. + Domain getDomain() const; + + /// Get the sign for this type. + Sign getSign() const; + + /// Get the size of this type in bits. + /// + /// Returns `None` if any of the type's dimensions is unsized, associative, or + /// a queue, or the core type itself has no known size. + Optional getBitSize() const; + + /// Format this type in SystemVerilog syntax into an output stream. Useful to + /// present the type back to the user in diagnostics. + void + format(llvm::raw_ostream &os, + llvm::function_ref around = {}) const; + + void format(llvm::raw_ostream &os, StringRef around) const { + format(os, [&](llvm::raw_ostream &os) { os << around; }); + } + + /// Format this type in SystemVerilog syntax into a string. Useful to present + /// the type back to the user in diagnostics. Prefer the `format` function if + /// possible, as that does not need to allocate a string. + template + std::string toString(Args... args) const { + std::string buffer; + llvm::raw_string_ostream os(buffer); + format(os, args...); + return buffer; + } + +protected: + using SVType::SVType; +}; + +template < + typename Ty, + std::enable_if_t::value, bool> = true> +llvm::raw_ostream &operator<<(llvm::raw_ostream &os, Ty type) { + type.format(os); + return os; +} + +//===----------------------------------------------------------------------===// +// Packed Type +//===----------------------------------------------------------------------===// + +class VoidType; +class IntType; +class PackedIndirectType; +class PackedDim; +class EnumType; +class PackedStructType; + +/// A packed SystemVerilog type. +/// +/// Packed types are the core types of SystemVerilog. They combine a core packed +/// type with an optional sign and zero or more packed dimensions. The core +/// packed types are: +/// +/// - Integer vector types: `bit`, `logic`, `reg` +/// - Integer atom types: `byte`, `shortint`, `int`, `longint`, `integer`, +/// `time` +/// - Packed structs and unions +/// - Enums +/// - Packed named types +/// - Packed type references +/// +/// The packed dimensions can be: +/// +/// - Unsized (`[]`) +/// - Ranges (`[x:y]`) +/// +/// Note that every packed type is also a valid unpacked type. But unpacked +/// types are *not* valid packed types. +class PackedType : public UnpackedType { +public: + static bool classof(Type type) { + return type.isa() || type.isa() || + type.isa() || type.isa() || + type.isa() || type.isa(); + } + + /// Resolve one level of name or type reference indirection. + /// + /// For example, given `typedef int foo; typedef foo bar;`, resolves `bar` + /// to `foo`. + PackedType resolved() const; + + /// Resolve all name or type reference indirections. + /// + /// For example, given `typedef int foo; typedef foo bar;`, resolves `bar` + /// to `int`. + PackedType fullyResolved() const; + + /// Get the value domain of this type. + Domain getDomain() const; + + /// Get the sign for this type. + Sign getSign() const; + + /// Get the size of this type in bits. + /// + /// Returns `None` if any of the type's dimensions is unsized. + Optional getBitSize() const; + + /// Format this type in SystemVerilog syntax into an output stream. Useful to + /// present the type back to the user in diagnostics. + void format(llvm::raw_ostream &os) const; + +protected: + using UnpackedType::UnpackedType; +}; + +//===----------------------------------------------------------------------===// +// Unit Types +//===----------------------------------------------------------------------===// + +/// The `void` type. +class VoidType + : public Type::TypeBase { +public: + static VoidType get(MLIRContext *context); + +protected: + using Base::Base; +}; + +/// The `string` type. +class StringType + : public Type::TypeBase { +public: + static StringType get(MLIRContext *context); + +protected: + using Base::Base; +}; + +/// The `chandle` type. +class ChandleType + : public Type::TypeBase { +public: + static ChandleType get(MLIRContext *context); + +protected: + using Base::Base; +}; + +/// The `event` type. +class EventType + : public Type::TypeBase { +public: + static EventType get(MLIRContext *context); + +protected: + using Base::Base; +}; + +//===----------------------------------------------------------------------===// +// Packed Integers +//===----------------------------------------------------------------------===// + +/// An integer vector or atom type. +class IntType + : public Type::TypeBase { +public: + enum Kind { + // The integer vector types. These are the builtin single-bit integer types. + /// A `bit`. + Bit, + /// A `logic`. + Logic, + /// A `reg`. + Reg, + + // The integer atom types. These are the builtin multi-bit integer types. + /// A `byte`. + Byte, + /// A `shortint`. + ShortInt, + /// An `int`. + Int, + /// A `longint`. + LongInt, + /// An `integer`. + Integer, + /// A `time`. + Time, + }; + + /// Get the integer type that corresponds to a keyword (like `bit`). + static Optional getKindFromKeyword(StringRef keyword); + /// Get the keyword (like `bit`) for one of the integer types. + static StringRef getKeyword(Kind kind); + /// Get the default sign for one of the integer types. + static Sign getDefaultSign(Kind kind); + /// Get the value domain for one of the integer types. + static Domain getDomain(Kind kind); + /// Get the size of one of the integer types. + static unsigned getBitSize(Kind kind); + + static IntType get(MLIRContext *context, Kind kind, Optional sign = {}); + + /// Create a `logic` type. + static IntType getLogic(MLIRContext *context) { return get(context, Logic); } + + /// Create a `int` type. + static IntType getInt(MLIRContext *context) { return get(context, Int); } + + /// Create a `time` type. + static IntType getTime(MLIRContext *context) { return get(context, Time); } + + /// Get the concrete integer vector or atom type. + Kind getKind() const; + /// Get the sign of this type. + Sign getSign() const; + /// Whether the sign of the type was specified explicitly. This allows us to + /// distinguish `bit unsigned` from `bit`. + bool isSignExplicit() const; + + /// Get the keyword (like `bit`) for this type. + StringRef getKeyword() const { return getKeyword(getKind()); } + /// Get the default sign for this type. + Sign getDefaultSign() const { return getDefaultSign(getKind()); } + /// Get the value domain for this type. + Domain getDomain() const { return getDomain(getKind()); } + /// Get the size of this type. + unsigned getBitSize() const { return getBitSize(getKind()); } + + /// Format this type in SystemVerilog syntax. Useful to present the type back + /// to the user in diagnostics. + void format(llvm::raw_ostream &os) const; + +protected: + using Base::Base; +}; + +//===----------------------------------------------------------------------===// +// Unpacked Reals +//===----------------------------------------------------------------------===// + +/// A real type. +class RealType + : public Type::TypeBase { +public: + enum Kind { + /// A `shortreal`. + ShortReal, + /// A `real`. + Real, + /// A `realtime`. + RealTime, + }; + + /// Get the integer type that corresponds to a keyword (like `bit`). + static Optional getKindFromKeyword(StringRef keyword); + /// Get the keyword (like `bit`) for one of the integer types. + static StringRef getKeyword(Kind kind); + /// Get the size of one of the integer types. + static unsigned getBitSize(Kind kind); + + static RealType get(MLIRContext *context, Kind kind); + + /// Get the concrete integer vector or atom type. + Kind getKind() const; + + /// Get the keyword (like `bit`) for this type. + StringRef getKeyword() const { return getKeyword(getKind()); } + /// Get the size of this type. + unsigned getBitSize() const { return getBitSize(getKind()); } + +protected: + using Base::Base; +}; + +//===----------------------------------------------------------------------===// +// Packed and Unpacked Type Indirections +//===----------------------------------------------------------------------===// + +class PackedNamedType; +class PackedRefType; +class UnpackedNamedType; +class UnpackedRefType; + +namespace detail { +UnpackedType getIndirectTypeInner(const TypeStorage *impl); +Location getIndirectTypeLoc(const TypeStorage *impl); +StringAttr getIndirectTypeName(const TypeStorage *impl); +} // namespace detail + +/// Common base class for name and type reference indirections. +/// +/// These handle the cases where the source text uses a `typedef` or +/// `type()` construct to build a type. We keep track of these +/// indirections alongside the location in the source text where they were +/// created, in order to be able to reproduce the exact source text type in +/// diagnostics. +/// +/// We use this templated base class to construct separate packed and unpacked +/// indirect types, where holding a packed indirect type guarantees that the +/// inner type is a packed type as well. The resulting inheritance trees are: +/// +/// - `PackedNamedType -> PackedIndirectType -> PackedType` +/// - `PackedRefType -> PackedIndirectType -> PackedType` +/// - `UnpackedNamedType -> UnpackedIndirectType -> UnpackedType` +/// - `UnpackedRefType -> UnpackedIndirectType -> UnpackedType` +template +class IndirectTypeBase : public BaseTy { +protected: + using InnerType = BaseTy; + using BaseTy::BaseTy; + using Base = IndirectTypeBase; + +public: + /// Get the type this indirection wraps. + BaseTy getInner() const { + return detail::getIndirectTypeInner(this->impl).template cast(); + } + + /// Get the location in the source text where the indirection was generated. + Location getLoc() const { return detail::getIndirectTypeLoc(this->impl); } + + /// Resolve one level of name or type reference indirection. This simply + /// returns the inner type, which removes the name indirection introduced by + /// this type. See `PackedType::resolved` and `UnpackedType::resolved`. + BaseTy resolved() const { return getInner(); } + + /// Resolve all name or type reference indirections. This always returns the + /// fully resolved inner type. See `PackedType::fullyResolved` and + /// `UnpackedType::fullyResolved`. + BaseTy fullyResolved() const { return getInner().fullyResolved(); } +}; + +/// A named type. +/// +/// Named types are user-defined types that are introduced with a `typedef +/// ` construct in the source file. They are composed of the +/// following information: +/// +/// - `inner: The type that this name expands to. +/// - `name`: How the user originally called the type. +/// - `loc`: The location of the typedef in the source file. +template +class NamedTypeBase + : public Type::TypeBase { +protected: + using InnerType = typename BaseTy::InnerType; + using Type::TypeBase::TypeBase; + using NamedBase = NamedTypeBase; + +public: + static ConcreteTy get(InnerType inner, StringAttr name, Location loc); + static ConcreteTy get(InnerType inner, StringRef name, Location loc) { + return get(inner, StringAttr::get(inner.getContext(), name), loc); + } + + /// Get the name assigned to the wrapped type. + StringAttr getName() const { return detail::getIndirectTypeName(this->impl); } +}; + +/// A type reference. +/// +/// Type references are introduced with a `type()` construct in the source +/// file. They are composed of the following information: +/// +/// - `inner`: The type that this reference expands to. +/// - `loc`: The location of the `type(...)` in the source file. +template +class RefTypeBase + : public Type::TypeBase { +protected: + using InnerType = typename BaseTy::InnerType; + using Type::TypeBase::TypeBase; + using RefBase = RefTypeBase; + +public: + static ConcreteTy get(InnerType inner, Location loc); +}; + +/// A packed type indirection. See `IndirectTypeBase` for details. +class PackedIndirectType : public IndirectTypeBase { +public: + static bool classof(Type type) { + return type.isa() || type.isa(); + } + +protected: + using Base::Base; +}; + +/// An unpacked type indirection. See `IndirectTypeBase` for details. +class UnpackedIndirectType : public IndirectTypeBase { +public: + static bool classof(Type type) { + return type.isa() || type.isa(); + } + +protected: + using Base::Base; +}; + +/// A packed named type. See `NamedTypeBase` for details. +class PackedNamedType + : public NamedTypeBase { +protected: + using NamedBase::NamedBase; +}; + +/// An unpacked named type. See `NamedTypeBase` for details. +class UnpackedNamedType + : public NamedTypeBase { +protected: + using NamedBase::NamedBase; +}; + +/// A packed named type. See `NamedTypeBase` for details. +class PackedRefType : public RefTypeBase { +protected: + using RefBase::RefBase; +}; + +/// An unpacked named type. See `NamedTypeBase` for details. +class UnpackedRefType + : public RefTypeBase { +protected: + using RefBase::RefBase; +}; + +//===----------------------------------------------------------------------===// +// Packed Dimensions +//===----------------------------------------------------------------------===// + +class PackedRangeDim; +class PackedUnsizedDim; + +/// A packed dimension. +class PackedDim : public PackedType { +public: + static bool classof(Type type) { + return type.isa() || type.isa(); + } + + /// Get the element type of the dimension. This is the `x` in `x[a:b]`. + PackedType getInner() const; + + /// Format this type in SystemVerilog syntax. Useful to present the type back + /// to the user in diagnostics. + void format(llvm::raw_ostream &os) const; + /// Format just the dimension part, `[...]`. + void formatDim(llvm::raw_ostream &os) const; + + /// Resolve one level of name or type reference indirection. See + /// `PackedType::resolved`. + PackedType resolved() const; + + /// Resolve all name or type reference indirections. See + /// `PackedType::fullyResolved`. + PackedType fullyResolved() const; + + /// Get the dimension's range, or `None` if it is unsized. + Optional getRange() const; + /// Get the dimension's size, or `None` if it is unsized. + Optional getSize() const; + +protected: + using PackedType::PackedType; + const detail::DimStorage *getImpl() const; +}; + +/// A packed unsized dimension, like `[]`. +class PackedUnsizedDim : public Type::TypeBase { +public: + static PackedUnsizedDim get(PackedType inner); + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +/// A packed range dimension, like `[a:b]`. +class PackedRangeDim : public Type::TypeBase { +public: + static PackedRangeDim get(PackedType inner, Range range); + + /// Get a packed range with arguments forwarded to the `Range` constructor. + /// See `Range::Range` for details. + template + static PackedRangeDim get(PackedType inner, Args... args) { + return get(inner, Range(args...)); + } + + /// Get the range of this dimension. + Range getRange() const; + + /// Allow implicit casts from `PackedRangeDim` to the actual range. + operator Range() const { return getRange(); } + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +//===----------------------------------------------------------------------===// +// Unpacked Dimensions +//===----------------------------------------------------------------------===// + +class UnpackedUnsizedDim; +class UnpackedArrayDim; +class UnpackedRangeDim; +class UnpackedAssocDim; +class UnpackedQueueDim; + +/// An unpacked dimension. +class UnpackedDim : public UnpackedType { +public: + static bool classof(Type type) { + return type.isa() || type.isa() || + type.isa() || type.isa() || + type.isa(); + } + + /// Get the element type of the dimension. This is the `x` in `x[a:b]`. + UnpackedType getInner() const; + + /// Format this type in SystemVerilog syntax. Useful to present the type back + /// to the user in diagnostics. The unpacked dimensions are separated from any + /// packed dimensions by calling the provided `around` callback, or a `$` if + /// no callback has been provided. This can be useful when printing + /// declarations like `bit [7:0] foo [16]` to have the type properly surround + /// the declaration name `foo`, and to easily tell packed from unpacked + /// dimensions in types like `bit [7:0] $ [15]`. + void format(llvm::raw_ostream &os, + llvm::function_ref around = {}) const; + /// Format just the dimension part, `[...]`. + void formatDim(llvm::raw_ostream &os) const; + + /// Resolve one level of name or type reference indirection. See + /// `UnpackedType::resolved`. + UnpackedType resolved() const; + + /// Resolve all name or type reference indirections. See + /// `UnpackedType::fullyResolved`. + UnpackedType fullyResolved() const; + +protected: + using UnpackedType::UnpackedType; + const detail::DimStorage *getImpl() const; +}; + +/// An unpacked unsized dimension, like `[]`. +class UnpackedUnsizedDim + : public Type::TypeBase { +public: + static UnpackedUnsizedDim get(UnpackedType inner); + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +/// An unpacked array dimension, like `[a]`. +class UnpackedArrayDim : public Type::TypeBase { +public: + static UnpackedArrayDim get(UnpackedType inner, unsigned size); + + /// Get the size of the array, i.e. the `a` in `[a]`. + unsigned getSize() const; + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +/// An unpacked range dimension, like `[a:b]`. +class UnpackedRangeDim : public Type::TypeBase { +public: + static UnpackedRangeDim get(UnpackedType inner, Range range); + + /// Get a packed range with arguments forwarded to the `Range` constructor. + /// See `Range::Range` for details. + template + static UnpackedRangeDim get(UnpackedType inner, Args... args) { + return get(inner, Range(args...)); + } + + /// Get the range of this dimension. + Range getRange() const; + + /// Allow implicit casts from `UnpackedRangeDim` to the actual range. + operator Range() const { return getRange(); } + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +/// An unpacked associative dimension, like `[T]` or `[*]`. +/// +/// Associative arrays in SystemVerilog can have a concrete index type (`[T]`), +/// or a wildcard index type (`[*]`, §7.8.1). The latter is exceptionally +/// strange, as it applies only to integer indices, but supports arbitrarily +/// sized indices by always removing leading zeros from any index that is used +/// in the lookup. This is interesting if a `string` is used to index into such +/// an array, because strings are automatically cast to a bit vector of +/// equivalent size, which results in a sort-of string key lookup. However, note +/// that there are also dedicated semantics for using `string` as the actual +/// index type (§7.8.2). +/// +/// See IEEE 1800-2017 §7.8 "Associative arrays". +class UnpackedAssocDim : public Type::TypeBase { +public: + static UnpackedAssocDim get(UnpackedType inner, UnpackedType indexType = {}); + + /// Get the index type of the associative dimension. This returns either the + /// type `T` in a dimension `[T]`, or a null type in a dimension `[*]`. + UnpackedType getIndexType() const; + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +/// An unpacked queue dimension with optional bound, like `[$]` or `[$:a]`. +class UnpackedQueueDim : public Type::TypeBase { +public: + static UnpackedQueueDim get(UnpackedType inner, + Optional bound = {}); + + /// Get the bound of the queue, i.e. the `a` in `[$:a]`. Returns `None` if the + /// queue is unbounded. + Optional getBound() const; + +protected: + using Base::Base; + friend struct detail::DimStorage; +}; + +//===----------------------------------------------------------------------===// +// Enumerations +//===----------------------------------------------------------------------===// + +/// An enum type. +class EnumType + : public Type::TypeBase { +public: + static EnumType get(StringAttr name, Location loc, PackedType base = {}); + + /// Get the base type of the enumeration. + PackedType getBase() const; + /// Returns whether the base type was explicitly specified by the user. This + /// allows us to distinguish `enum` from `enum int`. + bool isBaseExplicit() const; + /// Get the name of the surrounding typedef, if this enum is embedded in a + /// typedef. Otherwise this returns a null attribute. + StringAttr getName() const; + /// Get the location in the source text where the enum was declared. This + /// shall be the location of the `enum` keyword or, if the enum is embedded in + /// a typedef, the location of the typedef name. + Location getLoc() const; + + /// Format this enum in SystemVerilog syntax. Useful to present the enum back + /// to the user in diagnostics. + void format(llvm::raw_ostream &os) const; + +protected: + using Base::Base; +}; + +//===----------------------------------------------------------------------===// +// Packed and Unpacked Structs +//===----------------------------------------------------------------------===// + +/// Whether a struct is a `struct`, `union`, or `union tagged`. +enum class StructKind { + /// A `struct`. + Struct, + /// A `union`. + Union, + /// A `union tagged`. + TaggedUnion, +}; + +/// Map a `StructKind` to the corresponding mnemonic. +StringRef getMnemonicFromStructKind(StructKind kind); +/// Map a mnemonic to the corresponding `StructKind`. +Optional getStructKindFromMnemonic(StringRef mnemonic); + +template +Os &operator<<(Os &os, const StructKind &kind) { + static constexpr StringRef keywords[] = {"struct", "union", "union tagged"}; + os << keywords[static_cast(kind)]; + return os; +} + +/// A member of a struct. +struct StructMember { + /// The name of this member. + StringAttr name; + /// The location in the source text where this member was declared. + Location loc; + /// The type of this member. + UnpackedType type; + + bool operator==(const StructMember &other) const { + return name == other.name && loc == other.loc && type == other.type; + } +}; + +inline llvm::hash_code hash_value(const StructMember &x) { + return llvm::hash_combine(x.name, x.loc, x.type); +} + +/// A struct. +/// +/// This represents both packed and unpacked structs. Which one it is depends on +/// whether this struct is embedded in a `PackedStructType` or a +/// `UnpackedStructType`. For the packed version the struct members are +/// guaranteed to be packed types as well. +struct Struct { + /// Whether this is a `struct`, `union`, or `union tagged`. + StructKind kind; + /// The list of members. + SmallVector members; + /// The value domain of this struct. If all members are two-valued, the + /// overall struct is two-valued. Otherwise the struct is four-valued. + Domain domain; + /// The size of this struct in bits. This is `None` if any member type has an + /// unknown size. This is commonly the case for unpacked member types, or + /// dimensions with unknown size such as `[]` or `[$]`. + Optional bitSize; + /// The name of the surrounding typedef, if this struct is embedded in a + /// typedef. Otherwise this is a null attribute. + StringAttr name; + /// The location in the source text where the struct was declared. This shall + /// be the location of the `struct` or `union` keyword, or, if the struct is + /// embedded in a typedef, the location of the typedef name. + Location loc; + + /// Create a new struct. + Struct(StructKind kind, ArrayRef members, StringAttr name, + Location loc); + + /// Format this struct in SystemVerilog syntax. Useful to present the struct + /// back to the user in diagnostics. + void format(llvm::raw_ostream &os, bool packed = false, + Optional signing = {}) const; +}; + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os, + const Struct &strukt) { + strukt.format(os); + return os; +} + +/// A packed struct. +class PackedStructType : public Type::TypeBase { +public: + static PackedStructType get(StructKind kind, ArrayRef members, + StringAttr name, Location loc, + Optional sign = {}); + static PackedStructType get(const Struct &strukt, Optional sign = {}) { + return get(strukt.kind, strukt.members, strukt.name, strukt.loc, sign); + } + + /// Get the sign of this struct. + Sign getSign() const; + /// Returns whether the sign was explicitly mentioned by the user. + bool isSignExplicit() const; + /// Get the struct definition. + const Struct &getStruct() const; + + /// Format this struct in SystemVerilog syntax. Useful to present the struct + /// back to the user in diagnostics. + void format(llvm::raw_ostream &os) const { + getStruct().format(os, true, + isSignExplicit() ? Optional(getSign()) + : Optional()); + } + + /// Allow implicit casts from `PackedStructType` to the actual struct + /// definition. + operator const Struct &() const { return getStruct(); } + +protected: + using Base::Base; +}; + +/// An unpacked struct. +class UnpackedStructType + : public Type::TypeBase { +public: + static UnpackedStructType get(StructKind kind, ArrayRef members, + StringAttr name, Location loc); + static UnpackedStructType get(const Struct &strukt) { + return get(strukt.kind, strukt.members, strukt.name, strukt.loc); + } + + /// Get the struct definition. + const Struct &getStruct() const; + + /// Format this struct in SystemVerilog syntax. Useful to present the struct + /// back to the user in diagnostics. + void format(llvm::raw_ostream &os) const { getStruct().format(os); } + + /// Allow implicit casts from `UnpackedStructType` to the actual struct + /// definition. + operator const Struct &() const { return getStruct(); } + +protected: + using Base::Base; +}; + +} // namespace moore +} // namespace circt + +//===----------------------------------------------------------------------===// +// Hashing +//===----------------------------------------------------------------------===// + +namespace llvm { + +template <> +struct DenseMapInfo { + using Range = circt::moore::Range; + static inline Range getEmptyKey() { return Range(-1); } + static inline Range getTombstoneKey() { return Range(-2); } + static unsigned getHashValue(const Range &x) { + return circt::moore::hash_value(x); + } + static bool isEqual(const Range &lhs, const Range &rhs) { return lhs == rhs; } +}; + +} // namespace llvm + +// Include generated types. #define GET_TYPEDEF_CLASSES #include "circt/Dialect/Moore/MooreTypes.h.inc" diff --git a/include/circt/Dialect/Moore/MooreTypesImpl.td b/include/circt/Dialect/Moore/MooreTypesImpl.td index 9a93c92756..a5930bde8b 100644 --- a/include/circt/Dialect/Moore/MooreTypesImpl.td +++ b/include/circt/Dialect/Moore/MooreTypesImpl.td @@ -30,12 +30,3 @@ class WrapperTypeBase : MooreType { def LValueTypeImpl : WrapperTypeBase<"LValue", "lvalue">; def RValueTypeImpl : WrapperTypeBase<"RValue", "rvalue">; - -//===----------------------------------------------------------------------===// -// Integer atom types -//===----------------------------------------------------------------------===// - -def IntTypeImpl : MooreType<"Int"> { - let summary = "System-Verilog int type"; - let mnemonic = "sv.int"; -} diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index a85d59e69a..eefadf7f79 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -44,8 +44,8 @@ struct AssignOpConv; static Type convertMooreType(Type type) { return TypeSwitch(type) - .Case([](moore::IntType ty) { - return IntegerType::get(ty.getContext(), 32); + .Case([](auto type) { + return IntegerType::get(type.getContext(), type.getBitSize()); }) .Case( [](auto type) { return convertMooreType(type.getNestedType()); }) diff --git a/lib/Dialect/Moore/MooreTypes.cpp b/lib/Dialect/Moore/MooreTypes.cpp index 24f0012197..9308e85dfb 100644 --- a/lib/Dialect/Moore/MooreTypes.cpp +++ b/lib/Dialect/Moore/MooreTypes.cpp @@ -12,28 +12,1460 @@ #include "circt/Dialect/Moore/MooreTypes.h" #include "circt/Dialect/Moore/MooreDialect.h" - #include "mlir/IR/DialectImplementation.h" #include "llvm/ADT/TypeSwitch.h" using namespace circt; using namespace circt::moore; +using mlir::DialectAsmParser; +using mlir::DialectAsmPrinter; +using mlir::LocationAttr; +using mlir::OptionalParseResult; +using mlir::StringSwitch; +using mlir::TypeStorage; +using mlir::TypeStorageAllocator; //===----------------------------------------------------------------------===// -// TableGen generated logic. +// Generated logic //===----------------------------------------------------------------------===// -// Provide the autogenerated implementation guts for the Op classes. #define GET_TYPEDEF_CLASSES #include "circt/Dialect/Moore/MooreTypes.cpp.inc" -//===----------------------------------------------------------------------===// -// Register types to dialect -//===----------------------------------------------------------------------===// - void MooreDialect::registerTypes() { - addTypes< + addTypes(); + >(); +} + +//===----------------------------------------------------------------------===// +// Utilities +//===----------------------------------------------------------------------===// + +StringRef moore::getKeywordFromSign(const Sign &sign) { + switch (sign) { + case Sign::Unsigned: + return "unsigned"; + case Sign::Signed: + return "signed"; + } + llvm_unreachable("all signs should be handled"); +} + +Optional moore::getSignFromKeyword(StringRef keyword) { + return StringSwitch>(keyword) + .Case("unsigned", Sign::Unsigned) + .Case("signed", Sign::Signed) + .Default({}); +} + +//===----------------------------------------------------------------------===// +// Unpacked Type +//===----------------------------------------------------------------------===// + +UnpackedType UnpackedType::resolved() const { + return TypeSwitch(*this) + .Case( + [&](auto type) { return type.resolved(); }) + .Default([](auto type) { return type; }); +} + +UnpackedType UnpackedType::fullyResolved() const { + return TypeSwitch(*this) + .Case( + [&](auto type) { return type.fullyResolved(); }) + .Default([](auto type) { return type; }); +} + +Domain UnpackedType::getDomain() const { + return TypeSwitch(*this) + .Case([](auto type) { return type.getDomain(); }) + .Case( + [&](auto type) { return type.getInner().getDomain(); }) + .Case( + [](auto type) { return type.getStruct().domain; }) + .Default([](auto) { return Domain::TwoValued; }); +} + +Sign UnpackedType::getSign() const { + return TypeSwitch(*this) + .Case([](auto type) { return type.getSign(); }) + .Case( + [&](auto type) { return type.getInner().getSign(); }) + .Default([](auto) { return Sign::Unsigned; }); +} + +Optional UnpackedType::getBitSize() const { + return TypeSwitch>(*this) + .Case([](auto type) { return type.getBitSize(); }) + .Case([](auto) { return Optional{}; }) + .Case([](auto type) -> Optional { + if (auto size = type.getInner().getBitSize()) + return (*size) * type.getSize(); + return {}; + }) + .Case([](auto type) -> Optional { + if (auto size = type.getInner().getBitSize()) + return (*size) * type.getRange().size; + return {}; + }) + .Case( + [](auto type) { return type.getInner().getBitSize(); }) + .Case( + [](auto type) { return type.getStruct().bitSize; }) + .Default([](auto) { return llvm::None; }); +} + +void UnpackedType::format( + llvm::raw_ostream &os, + llvm::function_ref around) const { + TypeSwitch(*this) + .Case([&](auto) { os << "string"; }) + .Case([&](auto) { os << "chandle"; }) + .Case([&](auto) { os << "event"; }) + .Case([&](auto type) { os << type.getKeyword(); }) + .Case([&](auto type) { type.format(os); }) + .Case([&](auto type) { type.format(os, around); }) + .Case( + [&](auto type) { os << type.getName().getValue(); }) + .Case( + [&](auto type) { os << "type(" << type.getInner() << ")"; }) + .Default([](auto) { llvm_unreachable("all types should be handled"); }); + + // In case there were no unpacked dimensions, the `around` function was never + // called. However, callers expect us to be able to format things like `bit + // [7:0] fieldName`, where `fieldName` would be printed by `around`. So in + // case `around` is non-null, but no unpacked dimension had a chance to print + // it, simply print it now. + if (!isa() && around) { + os << " "; + around(os); + } +} + +//===----------------------------------------------------------------------===// +// Packed Type +//===----------------------------------------------------------------------===// + +PackedType PackedType::resolved() const { + return TypeSwitch(*this) + .Case( + [&](auto type) { return type.resolved(); }) + .Default([](auto type) { return type; }); +} + +PackedType PackedType::fullyResolved() const { + return TypeSwitch(*this) + .Case( + [&](auto type) { return type.fullyResolved(); }) + .Default([](auto type) { return type; }); +} + +Domain PackedType::getDomain() const { + return TypeSwitch(*this) + .Case([](auto) { return Domain::TwoValued; }) + .Case([&](auto type) { return type.getDomain(); }) + .Case( + [&](auto type) { return type.getInner().getDomain(); }) + .Case([](auto type) { return type.getBase().getDomain(); }) + .Case( + [](auto type) { return type.getStruct().domain; }); +} + +Sign PackedType::getSign() const { + return TypeSwitch(*this) + .Case([](auto) { return Sign::Unsigned; }) + .Case( + [&](auto type) { return type.getSign(); }) + .Case( + [&](auto type) { return type.getInner().getSign(); }) + .Case([](auto type) { return type.getBase().getSign(); }); +} + +Optional PackedType::getBitSize() const { + return TypeSwitch>(*this) + .Case([](auto) { return 0; }) + .Case([](auto type) { return type.getBitSize(); }) + .Case([](auto) { return Optional{}; }) + .Case([](auto type) -> Optional { + if (auto size = type.getInner().getBitSize()) + return (*size) * type.getRange().size; + return {}; + }) + .Case( + [](auto type) { return type.getInner().getBitSize(); }) + .Case([](auto type) { return type.getBase().getBitSize(); }) + .Case( + [](auto type) { return type.getStruct().bitSize; }); +} + +void PackedType::format(llvm::raw_ostream &os) const { + TypeSwitch(*this) + .Case([&](auto) { os << "void"; }) + .Case([&](auto type) { type.format(os); }) + .Case( + [&](auto type) { os << type.getName().getValue(); }) + .Case( + [&](auto type) { os << "type(" << type.getInner() << ")"; }) + .Default([](auto) { llvm_unreachable("all types should be handled"); }); +} + +//===----------------------------------------------------------------------===// +// Unit Types +//===----------------------------------------------------------------------===// + +VoidType VoidType::get(MLIRContext *context) { return Base::get(context); } + +StringType StringType::get(MLIRContext *context) { return Base::get(context); } + +ChandleType ChandleType::get(MLIRContext *context) { + return Base::get(context); +} + +EventType EventType::get(MLIRContext *context) { return Base::get(context); } + +//===----------------------------------------------------------------------===// +// Packed Integers +//===----------------------------------------------------------------------===// + +namespace circt { +namespace moore { +namespace detail { +struct IntTypeStorage : TypeStorage { + using KeyTy = unsigned; + using Kind = IntType::Kind; + + IntTypeStorage(KeyTy key) + : kind(static_cast((key >> 16) & 0xFF)), + sign(static_cast((key >> 8) & 0xFF)), explicitSign(key & 1) {} + static KeyTy pack(Kind kind, Sign sign, bool explicitSign) { + return static_cast(kind) << 16 | + static_cast(sign) << 8 | explicitSign; + } + bool operator==(const KeyTy &key) const { + return pack(kind, sign, explicitSign) == key; + } + static IntTypeStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) IntTypeStorage(key); + } + + Kind kind; + Sign sign; + bool explicitSign; +}; +} // namespace detail +} // namespace moore +} // namespace circt + +Optional IntType::getKindFromKeyword(StringRef keyword) { + return StringSwitch>(keyword) + .Case("bit", IntType::Bit) + .Case("logic", IntType::Logic) + .Case("reg", IntType::Reg) + .Case("byte", IntType::Byte) + .Case("shortint", IntType::ShortInt) + .Case("int", IntType::Int) + .Case("longint", IntType::LongInt) + .Case("integer", IntType::Integer) + .Case("time", IntType::Time) + .Default({}); +} + +StringRef IntType::getKeyword(Kind kind) { + switch (kind) { + case IntType::Bit: + return "bit"; + case IntType::Logic: + return "logic"; + case IntType::Reg: + return "reg"; + case IntType::Byte: + return "byte"; + case IntType::ShortInt: + return "shortint"; + case IntType::Int: + return "int"; + case IntType::LongInt: + return "longint"; + case IntType::Integer: + return "integer"; + case IntType::Time: + return "time"; + } + llvm_unreachable("all kinds should be handled"); +} + +Sign IntType::getDefaultSign(Kind kind) { + switch (kind) { + case IntType::Bit: + case IntType::Logic: + case IntType::Reg: + case IntType::Time: + return Sign::Unsigned; + case IntType::Byte: + case IntType::ShortInt: + case IntType::Int: + case IntType::LongInt: + case IntType::Integer: + return Sign::Signed; + } + llvm_unreachable("all kinds should be handled"); +} + +Domain IntType::getDomain(Kind kind) { + switch (kind) { + case IntType::Bit: + case IntType::Byte: + case IntType::ShortInt: + case IntType::Int: + case IntType::LongInt: + case IntType::Time: + return Domain::TwoValued; + case IntType::Logic: + case IntType::Reg: + case IntType::Integer: + return Domain::FourValued; + } + llvm_unreachable("all kinds should be handled"); +} + +unsigned IntType::getBitSize(Kind kind) { + switch (kind) { + case IntType::Bit: + case IntType::Logic: + case IntType::Reg: + return 1; + case IntType::Byte: + return 8; + case IntType::ShortInt: + return 16; + case IntType::Int: + return 32; + case IntType::LongInt: + return 64; + case IntType::Integer: + return 32; + case IntType::Time: + return 64; + } + llvm_unreachable("all kinds should be handled"); +} + +IntType IntType::get(MLIRContext *context, Kind kind, Optional sign) { + return Base::get(context, detail::IntTypeStorage::pack( + kind, sign.getValueOr(getDefaultSign(kind)), + sign.hasValue())); +} + +IntType::Kind IntType::getKind() const { return getImpl()->kind; } + +Sign IntType::getSign() const { return getImpl()->sign; } + +bool IntType::isSignExplicit() const { return getImpl()->explicitSign; } + +void IntType::format(llvm::raw_ostream &os) const { + os << getKeyword(); + auto sign = getSign(); + if (isSignExplicit() || sign != getDefaultSign()) + os << " " << sign; +} + +//===----------------------------------------------------------------------===// +// Unpacked Reals +//===----------------------------------------------------------------------===// + +namespace circt { +namespace moore { +namespace detail { +struct RealTypeStorage : TypeStorage { + using KeyTy = unsigned; + using Kind = RealType::Kind; + + RealTypeStorage(KeyTy key) : kind(static_cast(key)) {} + bool operator==(const KeyTy &key) const { + return kind == static_cast(key); + } + static RealTypeStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) RealTypeStorage(key); + } + + Kind kind; +}; +} // namespace detail +} // namespace moore +} // namespace circt + +Optional RealType::getKindFromKeyword(StringRef keyword) { + return StringSwitch>(keyword) + .Case("shortreal", ShortReal) + .Case("real", Real) + .Case("realtime", RealTime) + .Default({}); +} + +StringRef RealType::getKeyword(Kind kind) { + switch (kind) { + case ShortReal: + return "shortreal"; + case Real: + return "real"; + case RealTime: + return "realtime"; + } + llvm_unreachable("all kinds should be handled"); +} + +unsigned RealType::getBitSize(Kind kind) { + switch (kind) { + case ShortReal: + return 32; + case Real: + return 64; + case RealTime: + return 64; + } + llvm_unreachable("all kinds should be handled"); +} + +RealType RealType::get(MLIRContext *context, Kind kind) { + return Base::get(context, static_cast(kind)); +} + +RealType::Kind RealType::getKind() const { return getImpl()->kind; } + +//===----------------------------------------------------------------------===// +// Packed Type Indirections +//===----------------------------------------------------------------------===// + +namespace circt { +namespace moore { +namespace detail { + +struct IndirectTypeStorage : TypeStorage { + using KeyTy = std::tuple; + + IndirectTypeStorage(KeyTy key) + : IndirectTypeStorage(std::get<0>(key), std::get<1>(key), + std::get<2>(key)) {} + IndirectTypeStorage(UnpackedType inner, StringAttr name, LocationAttr loc) + : inner(inner), name(name), loc(loc) {} + bool operator==(const KeyTy &key) const { + return std::get<0>(key) == inner && std::get<1>(key) == name && + std::get<2>(key) == loc; + } + static IndirectTypeStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) + IndirectTypeStorage(key); + } + + UnpackedType inner; + StringAttr name; + LocationAttr loc; +}; + +UnpackedType getIndirectTypeInner(const TypeStorage *impl) { + return static_cast(impl)->inner; +} + +Location getIndirectTypeLoc(const TypeStorage *impl) { + return static_cast(impl)->loc; +} + +StringAttr getIndirectTypeName(const TypeStorage *impl) { + return static_cast(impl)->name; +} + +} // namespace detail +} // namespace moore +} // namespace circt + +template <> +PackedNamedType NamedTypeBase::get( + PackedType inner, StringAttr name, Location loc) { + return Base::get(inner.getContext(), inner, name, loc); +} + +template <> +UnpackedNamedType NamedTypeBase::get( + UnpackedType inner, StringAttr name, Location loc) { + return Base::get(inner.getContext(), inner, name, loc); +} + +template <> +PackedRefType +RefTypeBase::get(PackedType inner, + Location loc) { + return Base::get(inner.getContext(), inner, StringAttr{}, loc); +} + +template <> +UnpackedRefType +RefTypeBase::get(UnpackedType inner, + Location loc) { + return Base::get(inner.getContext(), inner, StringAttr{}, loc); +} + +//===----------------------------------------------------------------------===// +// Packed Dimensions +//===----------------------------------------------------------------------===// + +namespace circt { +namespace moore { +namespace detail { + +struct DimStorage : TypeStorage { + using KeyTy = UnpackedType; + + DimStorage(KeyTy key) : inner(key) {} + bool operator==(const KeyTy &key) const { return key == inner; } + static DimStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) DimStorage(key); + } + + // Mutation function to late-initialize the resolved versions of the type. + LogicalResult mutate(TypeStorageAllocator &allocator, + UnpackedType newResolved, + UnpackedType newFullyResolved) { + // Cannot set change resolved types once they've been initialized. + if (resolved && resolved != newResolved) + return failure(); + if (fullyResolved && fullyResolved != newFullyResolved) + return failure(); + + // Update the resolved types. + resolved = newResolved; + fullyResolved = newFullyResolved; + return success(); + } + + /// Each dimension type calls this function from its `get` method. The first + /// argument, `dim`, is set to the type that was constructed by the call to + /// `Base::get`. If that type has just been created, its `resolved` and + /// `fullyResolved` fields are not yet set. If that is the case, the + /// `finalize` method constructs the these resolved types by resolving the + /// inner type appropriately and wrapping it in the dimension type. These + /// wrapped types, which are equivalent to the `dim` itself but with the inner + /// type resolved, are passed to `DimStorage::mutate` which fills in the + /// `resolved` and `fullyResolved` fields behind a storage lock in the + /// MLIRContext. + /// + /// This has been inspired by https://reviews.llvm.org/D84171. + template + void finalize(ConcreteDim dim, Args... args) const { + if (resolved && fullyResolved) + return; + auto inner = dim.getInner(); + auto newResolved = dim; + auto newFullyResolved = dim; + if (inner != inner.resolved()) + newResolved = ConcreteDim::get(inner.resolved(), args...); + if (inner != inner.fullyResolved()) + newFullyResolved = ConcreteDim::get(inner.fullyResolved(), args...); + auto result = dim.mutate(newResolved, newFullyResolved); + assert(succeeded(result)); + } + + UnpackedType inner; + UnpackedType resolved; + UnpackedType fullyResolved; +}; + +struct UnsizedDimStorage : DimStorage { + UnsizedDimStorage(KeyTy key) : DimStorage(key) {} + static UnsizedDimStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) UnsizedDimStorage(key); + } +}; + +struct RangeDimStorage : DimStorage { + using KeyTy = std::pair; + + RangeDimStorage(KeyTy key) : DimStorage(key.first), range(key.second) {} + bool operator==(const KeyTy &key) const { + return key.first == inner && key.second == range; + } + static RangeDimStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) RangeDimStorage(key); + } + + Range range; +}; + +} // namespace detail +} // namespace moore +} // namespace circt + +PackedType PackedDim::getInner() const { + return getImpl()->inner.cast(); +} + +void PackedDim::format(llvm::raw_ostream &os) const { + SmallVector dims; + dims.push_back(*this); + for (;;) { + PackedType inner = dims.back().getInner(); + if (auto dim = inner.dyn_cast()) { + dims.push_back(dim); + } else { + inner.format(os); + break; + } + } + os << " "; + for (auto dim : dims) { + dim.formatDim(os); + } +} + +void PackedDim::formatDim(llvm::raw_ostream &os) const { + TypeSwitch(*this) + .Case( + [&](auto dim) { os << "[" << dim.getRange() << "]"; }) + .Case([&](auto dim) { os << "[]"; }) + .Default([&](auto) { llvm_unreachable("unhandled dim type"); }); +} + +PackedType PackedDim::resolved() const { + return getImpl()->resolved.cast(); +} + +PackedType PackedDim::fullyResolved() const { + return getImpl()->fullyResolved.cast(); +} + +Optional PackedDim::getRange() const { + if (auto dim = dyn_cast()) + return dim.getRange(); + return {}; +} + +Optional PackedDim::getSize() const { + return getRange().map([](auto r) { return r.size; }); +} + +const detail::DimStorage *PackedDim::getImpl() const { + return static_cast(this->impl); +} + +PackedUnsizedDim PackedUnsizedDim::get(PackedType inner) { + auto type = Base::get(inner.getContext(), inner); + type.getImpl()->finalize(type); + return type; +} + +PackedRangeDim PackedRangeDim::get(PackedType inner, Range range) { + auto type = Base::get(inner.getContext(), inner, range); + type.getImpl()->finalize(type, range); + return type; +} + +Range PackedRangeDim::getRange() const { return getImpl()->range; } + +//===----------------------------------------------------------------------===// +// Unpacked Dimensions +//===----------------------------------------------------------------------===// + +namespace circt { +namespace moore { +namespace detail { + +struct SizedDimStorage : DimStorage { + using KeyTy = std::pair; + + SizedDimStorage(KeyTy key) : DimStorage(key.first), size(key.second) {} + bool operator==(const KeyTy &key) const { + return key.first == inner && key.second == size; + } + static SizedDimStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) SizedDimStorage(key); + } + + unsigned size; +}; + +struct AssocDimStorage : DimStorage { + using KeyTy = std::pair; + + AssocDimStorage(KeyTy key) : DimStorage(key.first), indexType(key.second) {} + bool operator==(const KeyTy &key) const { + return key.first == inner && key.second == indexType; + } + static AssocDimStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) AssocDimStorage(key); + } + + UnpackedType indexType; +}; + +} // namespace detail +} // namespace moore +} // namespace circt + +UnpackedType UnpackedDim::getInner() const { return getImpl()->inner; } + +void UnpackedDim::format( + llvm::raw_ostream &os, + llvm::function_ref around) const { + SmallVector dims; + dims.push_back(*this); + for (;;) { + UnpackedType inner = dims.back().getInner(); + if (auto dim = inner.dyn_cast()) { + dims.push_back(dim); + } else { + inner.format(os); + break; + } + } + os << " "; + if (around) + around(os); + else + os << "$"; + os << " "; + for (auto dim : dims) { + dim.formatDim(os); + } +} + +void UnpackedDim::formatDim(llvm::raw_ostream &os) const { + TypeSwitch(*this) + .Case([&](auto dim) { os << "[]"; }) + .Case( + [&](auto dim) { os << "[" << dim.getSize() << "]"; }) + .Case( + [&](auto dim) { os << "[" << dim.getRange() << "]"; }) + .Case([&](auto dim) { + os << "["; + if (auto indexType = dim.getIndexType()) + indexType.format(os); + else + os << "*"; + os << "]"; + }) + .Case([&](auto dim) { + os << "[$"; + if (auto bound = dim.getBound()) + os << ":" << *bound; + os << "]"; + }) + .Default([&](auto) { llvm_unreachable("unhandled dim type"); }); +} + +UnpackedType UnpackedDim::resolved() const { return getImpl()->resolved; } + +UnpackedType UnpackedDim::fullyResolved() const { + return getImpl()->fullyResolved; +} + +const detail::DimStorage *UnpackedDim::getImpl() const { + return static_cast(this->impl); +} + +UnpackedUnsizedDim UnpackedUnsizedDim::get(UnpackedType inner) { + auto type = Base::get(inner.getContext(), inner); + type.getImpl()->finalize(type); + return type; +} + +UnpackedArrayDim UnpackedArrayDim::get(UnpackedType inner, unsigned size) { + auto type = Base::get(inner.getContext(), inner, size); + type.getImpl()->finalize(type, size); + return type; +} + +unsigned UnpackedArrayDim::getSize() const { return getImpl()->size; } + +UnpackedRangeDim UnpackedRangeDim::get(UnpackedType inner, Range range) { + auto type = Base::get(inner.getContext(), inner, range); + type.getImpl()->finalize(type, range); + return type; +} + +Range UnpackedRangeDim::getRange() const { return getImpl()->range; } + +UnpackedAssocDim UnpackedAssocDim::get(UnpackedType inner, + UnpackedType indexType) { + auto type = Base::get(inner.getContext(), inner, indexType); + type.getImpl()->finalize(type, indexType); + return type; +} + +UnpackedType UnpackedAssocDim::getIndexType() const { + return getImpl()->indexType; +} + +UnpackedQueueDim UnpackedQueueDim::get(UnpackedType inner, + Optional bound) { + auto type = Base::get(inner.getContext(), inner, bound.getValueOr(-1)); + type.getImpl()->finalize(type, bound); + return type; +} + +Optional UnpackedQueueDim::getBound() const { + unsigned bound = getImpl()->size; + if (bound == static_cast(-1)) + return {}; + return bound; +} + +//===----------------------------------------------------------------------===// +// Enumerations +//===----------------------------------------------------------------------===// + +namespace circt { +namespace moore { +namespace detail { + +struct EnumTypeStorage : TypeStorage { + using KeyTy = std::tuple; + + EnumTypeStorage(KeyTy key) + : name(std::get<0>(key)), loc(std::get<1>(key)), base(std::get<2>(key)), + explicitBase(std::get<3>(key)) {} + bool operator==(const KeyTy &key) const { + return std::get<0>(key) == name && std::get<1>(key) == loc && + std::get<2>(key) == base && std::get<3>(key) == explicitBase; + } + static EnumTypeStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) EnumTypeStorage(key); + } + + StringAttr name; + Location loc; + PackedType base; + bool explicitBase; +}; + +} // namespace detail +} // namespace moore +} // namespace circt + +EnumType EnumType::get(StringAttr name, Location loc, PackedType base) { + return Base::get(loc.getContext(), name, loc, + base ? base : IntType::getInt(loc.getContext()), !!base); +} + +PackedType EnumType::getBase() const { return getImpl()->base; } + +bool EnumType::isBaseExplicit() const { return getImpl()->explicitBase; } + +StringAttr EnumType::getName() const { return getImpl()->name; } + +Location EnumType::getLoc() const { return getImpl()->loc; } + +void EnumType::format(llvm::raw_ostream &os) const { + os << "enum"; + + // If the enum is part of a typedefm simply print it as `enum `. + if (auto name = getName()) { + os << " " << name.getValue(); + return; + } + + // Otherwise print `enum ` or just `enum`. + if (isBaseExplicit()) + os << " " << getBase(); +} + +//===----------------------------------------------------------------------===// +// Packed and Unpacked Structs +//===----------------------------------------------------------------------===// + +StringRef moore::getMnemonicFromStructKind(StructKind kind) { + switch (kind) { + case StructKind::Struct: + return "struct"; + case StructKind::Union: + return "union"; + case StructKind::TaggedUnion: + return "tagged_union"; + } + llvm_unreachable("all struct kinds should be handled"); +} + +Optional moore::getStructKindFromMnemonic(StringRef mnemonic) { + return StringSwitch>(mnemonic) + .Case("struct", StructKind::Struct) + .Case("union", StructKind::Union) + .Case("tagged_union", StructKind::TaggedUnion) + .Default({}); +} + +Struct::Struct(StructKind kind, ArrayRef members, StringAttr name, + Location loc) + : kind(kind), members(members.begin(), members.end()), name(name), + loc(loc) { + // The struct's value domain is two-valued if all members are two-valued. + // Otherwise it is four-valued. + domain = llvm::all_of(members, + [](auto &member) { + return member.type.getDomain() == Domain::TwoValued; + }) + ? Domain::TwoValued + : Domain::FourValued; + + // The bit size is the sum of all member bit sizes, or `None` if any of the + // member bit sizes are `None`. + bitSize = 0; + for (const auto &member : members) { + if (auto memberSize = member.type.getBitSize()) { + *bitSize += *memberSize; + } else { + bitSize = llvm::None; + break; + } + } +} + +void Struct::format(llvm::raw_ostream &os, bool packed, + Optional signing) const { + os << kind; + if (packed) + os << " packed"; + if (signing) + os << " " << *signing; + + // If the struct is part of a typedef, simply print it as `struct `. + if (name) { + os << " " << name.getValue(); + return; + } + + // Otherwise actually print the struct definition inline. + os << " {"; + for (auto &member : members) + os << " " << member.type << " " << member.name.getValue() << ";"; + if (!members.empty()) + os << " "; + os << "}"; +} + +namespace circt { +namespace moore { +namespace detail { + +struct StructTypeStorage : TypeStorage { + using KeyTy = + std::tuple, StringAttr, Location>; + + StructTypeStorage(KeyTy key) + : strukt(static_cast((std::get<0>(key) >> 16) & 0xFF), + std::get<1>(key), std::get<2>(key), std::get<3>(key)), + sign(static_cast((std::get<0>(key) >> 8) & 0xFF)), + explicitSign((std::get<0>(key) >> 0) & 1) {} + static unsigned pack(StructKind kind, Sign sign, bool explicitSign) { + return static_cast(kind) << 16 | + static_cast(sign) << 8 | explicitSign; + } + bool operator==(const KeyTy &key) const { + return std::get<0>(key) == pack(strukt.kind, sign, explicitSign) && + std::get<1>(key) == ArrayRef(strukt.members) && + std::get<2>(key) == strukt.name && std::get<3>(key) == strukt.loc; + } + static StructTypeStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) StructTypeStorage(key); + } + + Struct strukt; + Sign sign; + bool explicitSign; +}; + +} // namespace detail +} // namespace moore +} // namespace circt + +PackedStructType PackedStructType::get(StructKind kind, + ArrayRef members, + StringAttr name, Location loc, + Optional sign) { + auto isPacked = [](const StructMember &member) { + return member.type.isa(); + }; + assert(llvm::all_of(members, isPacked) && + "packed struct members must be packed"); + return Base::get(loc.getContext(), + detail::StructTypeStorage::pack( + kind, sign.getValueOr(Sign::Unsigned), sign.hasValue()), + members, name, loc); +} + +Sign PackedStructType::getSign() const { return getImpl()->sign; } + +bool PackedStructType::isSignExplicit() const { + return getImpl()->explicitSign; +} + +const Struct &PackedStructType::getStruct() const { return getImpl()->strukt; } + +UnpackedStructType UnpackedStructType::get(StructKind kind, + ArrayRef members, + StringAttr name, Location loc) { + return Base::get(loc.getContext(), + detail::StructTypeStorage::pack(kind, Sign::Unsigned, false), + members, name, loc); +} + +const Struct &UnpackedStructType::getStruct() const { + return getImpl()->strukt; +} + +//===----------------------------------------------------------------------===// +// Parsing and Printing +//===----------------------------------------------------------------------===// + +struct Subset { + enum { None, Unpacked, Packed } implied = None; + bool allowUnpacked = true; +}; + +static ParseResult parseMooreType(DialectAsmParser &parser, Subset subset, + Type &type); +static void printMooreType(Type type, DialectAsmPrinter &printer, + Subset subset); + +/// Parse a type with custom syntax. +static OptionalParseResult customTypeParser(DialectAsmParser &parser, + StringRef mnemonic, Subset subset, + llvm::SMLoc loc, Type &type) { + auto *context = parser.getContext(); + + auto yieldPacked = [&](PackedType x) { + type = x; + return success(); + }; + auto yieldUnpacked = [&](UnpackedType x) { + if (!subset.allowUnpacked) { + parser.emitError(loc) + << "unpacked type " << x << " where only packed types are allowed"; + return failure(); + } + type = x; + return success(); + }; + auto yieldImplied = + [&](llvm::function_ref ifPacked, + llvm::function_ref ifUnpacked) { + if (subset.implied == Subset::Packed) + return yieldPacked(ifPacked()); + else if (subset.implied == Subset::Unpacked) + return yieldUnpacked(ifUnpacked()); + parser.emitError(loc) + << "ambiguous packing; wrap `" << mnemonic + << "` in `packed<...>` or `unpacked<...>` to disambiguate"; + return failure(); + }; + + // Explicit packing indicators, like `unpacked.named`. + if (mnemonic == "unpacked") { + UnpackedType inner; + if (parser.parseLess() || + parseMooreType(parser, {Subset::Unpacked, true}, inner) || + parser.parseGreater()) + return failure(); + return yieldUnpacked(inner); + } + if (mnemonic == "packed") { + PackedType inner; + if (parser.parseLess() || + parseMooreType(parser, {Subset::Packed, false}, inner) || + parser.parseGreater()) + return failure(); + return yieldPacked(inner); + } + + // Packed primary types. + if (mnemonic == "void") + return yieldPacked(VoidType::get(context)); + + if (auto kind = IntType::getKindFromKeyword(mnemonic)) { + Optional sign; + if (succeeded(parser.parseOptionalLess())) { + StringRef signKeyword; + if (parser.parseKeyword(&signKeyword) || parser.parseGreater()) + return failure(); + sign = getSignFromKeyword(signKeyword); + if (!sign) { + parser.emitError(parser.getCurrentLocation()) + << "expected keyword `unsigned` or `signed`"; + return failure(); + } + } + return yieldPacked(IntType::get(context, *kind, sign)); + } + + // Unpacked primary types. + if (mnemonic == "string") + return yieldUnpacked(StringType::get(context)); + if (mnemonic == "chandle") + return yieldUnpacked(ChandleType::get(context)); + if (mnemonic == "event") + return yieldUnpacked(EventType::get(context)); + if (auto kind = RealType::getKindFromKeyword(mnemonic)) + return yieldUnpacked(RealType::get(context, *kind)); + + // Enums + if (mnemonic == "enum") { + if (parser.parseLess()) + return failure(); + StringAttr name; + auto result = parser.parseOptionalAttribute(name); + if (result.hasValue()) + if (*result || parser.parseComma()) + return failure(); + LocationAttr loc; + PackedType base; + result = parser.parseOptionalAttribute(loc); + if (result.hasValue()) { + if (*result) + return failure(); + } else { + if (parseMooreType(parser, {Subset::Packed, false}, base) || + parser.parseComma() || parser.parseAttribute(loc)) + return failure(); + } + if (parser.parseGreater()) + return failure(); + return yieldPacked(EnumType::get(name, loc, base)); + } + + // Everything that follows can be packed or unpacked. The packing is inferred + // from the last `packed<...>` or `unpacked<...>` that we've seen. The + // `yieldImplied` function will call the first lambda to construct a packed + // type, or the second lambda to construct an unpacked type. If the + // `subset.implied` field is not set, which means there hasn't been any prior + // `packed` or `unpacked`, the function will emit an error properly. + + // Packed and unpacked type indirections. + if (mnemonic == "named") { + UnpackedType inner; + StringAttr name; + LocationAttr loc; + if (parser.parseLess() || parser.parseAttribute(name) || + parser.parseComma() || parseMooreType(parser, subset, inner) || + parser.parseComma() || parser.parseAttribute(loc) || + parser.parseGreater()) + return failure(); + return yieldImplied( + [&]() { + return PackedNamedType::get(inner.cast(), name, loc); + }, + [&]() { return UnpackedNamedType::get(inner, name, loc); }); + } + if (mnemonic == "ref") { + UnpackedType inner; + LocationAttr loc; + if (parser.parseLess() || parseMooreType(parser, subset, inner) || + parser.parseComma() || parser.parseAttribute(loc) || + parser.parseGreater()) + return failure(); + return yieldImplied( + [&]() { return PackedRefType::get(inner.cast(), loc); }, + [&]() { return UnpackedRefType::get(inner, loc); }); + } + + // Packed and unpacked ranges. + if (mnemonic == "unsized") { + UnpackedType inner; + if (parser.parseLess() || parseMooreType(parser, subset, inner) || + parser.parseGreater()) + return failure(); + return yieldImplied( + [&]() { return PackedUnsizedDim::get(inner.cast()); }, + [&]() { return UnpackedUnsizedDim::get(inner); }); + } + if (mnemonic == "range") { + UnpackedType inner; + int left, right; + if (parser.parseLess() || parseMooreType(parser, subset, inner) || + parser.parseComma() || parser.parseInteger(left) || + parser.parseColon() || parser.parseInteger(right) || + parser.parseGreater()) + return failure(); + return yieldImplied( + [&]() { + return PackedRangeDim::get(inner.cast(), left, right); + }, + [&]() { return UnpackedRangeDim::get(inner, left, right); }); + } + if (mnemonic == "array") { + UnpackedType inner; + unsigned size; + if (parser.parseLess() || parseMooreType(parser, subset, inner) || + parser.parseComma() || parser.parseInteger(size) || + parser.parseGreater()) + return failure(); + return yieldUnpacked(UnpackedArrayDim::get(inner, size)); + } + if (mnemonic == "assoc") { + UnpackedType inner; + UnpackedType index; + if (parser.parseLess() || parseMooreType(parser, subset, inner)) + return failure(); + if (succeeded(parser.parseOptionalComma())) { + if (parseMooreType(parser, {Subset::Unpacked, true}, index)) + return failure(); + } + if (parser.parseGreater()) + return failure(); + return yieldUnpacked(UnpackedAssocDim::get(inner, index)); + } + if (mnemonic == "queue") { + UnpackedType inner; + Optional size; + if (parser.parseLess() || parseMooreType(parser, subset, inner)) + return failure(); + if (succeeded(parser.parseOptionalComma())) { + unsigned tmpSize; + if (parser.parseInteger(tmpSize)) + return failure(); + size = tmpSize; + } + if (parser.parseGreater()) + return failure(); + return yieldUnpacked(UnpackedQueueDim::get(inner, size)); + } + + // Structs + if (auto kind = getStructKindFromMnemonic(mnemonic)) { + if (parser.parseLess()) + return failure(); + + StringAttr name; + auto result = parser.parseOptionalAttribute(name); + if (result.hasValue()) + if (*result || parser.parseComma()) + return failure(); + + Optional sign; + StringRef keyword; + if (succeeded(parser.parseOptionalKeyword(&keyword))) { + sign = getSignFromKeyword(keyword); + if (!sign) { + parser.emitError(loc) << "expected keyword `unsigned` or `signed`"; + return failure(); + } + if (subset.implied == Subset::Unpacked) { + parser.emitError(loc) << "unpacked struct cannot have a sign"; + return failure(); + } + if (parser.parseComma()) + return failure(); + } + + SmallVector members; + auto result2 = + parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Braces, [&]() { + if (parser.parseKeyword(&keyword)) + return failure(); + UnpackedType type; + LocationAttr loc; + if (parser.parseColon() || parseMooreType(parser, subset, type) || + parser.parseAttribute(loc)) + return failure(); + members.push_back( + {StringAttr::get(parser.getContext(), keyword), loc, type}); + return success(); + }); + if (result2) + return failure(); + + LocationAttr loc; + if (parser.parseComma() || parser.parseAttribute(loc) || + parser.parseGreater()) + return failure(); + + return yieldImplied( + [&]() { + return PackedStructType::get(*kind, members, name, loc, sign); + }, + [&]() { return UnpackedStructType::get(*kind, members, name, loc); }); + } + + return {}; +} + +/// Print a type with custom syntax. +static LogicalResult customTypePrinter(Type type, DialectAsmPrinter &printer, + Subset subset) { + // If we are printing a type that may be both packed or unpacked, emit a + // wrapping `packed<...>` or `unpacked<...>` accordingly if not done so + // previously, in order to disambiguate between the two. + if (type.isa() || type.isa() || + type.isa() || type.isa() || + type.isa() || type.isa()) { + auto needed = type.isa() ? Subset::Packed : Subset::Unpacked; + if (needed != subset.implied) { + printer << (needed == Subset::Packed ? "packed" : "unpacked") << "<"; + printMooreType(type, printer, {needed, true}); + printer << ">"; + return success(); + } + } + + return TypeSwitch(type) + // Unit types + .Case([&](auto) { return printer << "void", success(); }) + .Case([&](auto) { return printer << "string", success(); }) + .Case([&](auto) { return printer << "chandle", success(); }) + .Case([&](auto) { return printer << "event", success(); }) + + // Integers and reals + .Case([&](auto type) { + printer << type.getKeyword(); + auto sign = type.getSign(); + if (type.isSignExplicit()) + printer << "<" << getKeywordFromSign(sign) << ">"; + return success(); + }) + .Case( + [&](auto type) { return printer << type.getKeyword(), success(); }) + + // Enums + .Case([&](auto type) { + printer << "enum<"; + if (type.getName()) + printer << type.getName() << ", "; + if (type.isBaseExplicit()) { + printMooreType(type.getBase(), printer, subset); + printer << ", "; + } + printer << type.getLoc() << ">"; + return success(); + }) + + // Type indirections + .Case([&](auto type) { + printer << "named<" << type.getName() << ", "; + printMooreType(type.getInner(), printer, subset); + printer << ", " << type.getLoc() << ">"; + return success(); + }) + .Case([&](auto type) { + printer << "ref<"; + printMooreType(type.getInner(), printer, subset); + printer << ", " << type.getLoc() << ">"; + return success(); + }) + + // Packed and unpacked dimensions + .Case([&](auto type) { + printer << "unsized<"; + printMooreType(type.getInner(), printer, subset); + printer << ">"; + return success(); + }) + .Case([&](auto type) { + printer << "range<"; + printMooreType(type.getInner(), printer, subset); + printer << ", " << type.getRange() << ">"; + return success(); + }) + .Case([&](auto type) { + printer << "array<"; + printMooreType(type.getInner(), printer, subset); + printer << ", " << type.getSize() << ">"; + return success(); + }) + .Case([&](auto type) { + printer << "assoc<"; + printMooreType(type.getInner(), printer, subset); + if (auto indexType = type.getIndexType()) { + printer << ", "; + printMooreType(indexType, printer, {Subset::Unpacked, true}); + } + printer << ">"; + return success(); + }) + .Case([&](auto type) { + printer << "queue<"; + printMooreType(type.getInner(), printer, subset); + if (auto bound = type.getBound()) + printer << ", " << *bound; + printer << ">"; + return success(); + }) + + // Structs + .Case([&](auto type) { + const auto &strukt = type.getStruct(); + printer << getMnemonicFromStructKind(strukt.kind) << "<"; + if (strukt.name) + printer << strukt.name << ", "; + auto packed = type.template dyn_cast(); + if (packed && packed.isSignExplicit()) + printer << packed.getSign() << ", "; + printer << "{"; + llvm::interleaveComma(strukt.members, printer, [&](const auto &member) { + printer << member.name.getValue() << ": "; + printMooreType(member.type, printer, subset); + printer << " " << member.loc; + }); + printer << "}, "; + printer << strukt.loc << ">"; + return success(); + }) + + .Default([](auto) { return failure(); }); +} + +/// Parse a type registered with this dialect. +static ParseResult parseMooreType(DialectAsmParser &parser, Subset subset, + Type &type) { + llvm::SMLoc loc = parser.getCurrentLocation(); + StringRef mnemonic; + if (parser.parseKeyword(&mnemonic)) + return failure(); + + OptionalParseResult result = generatedTypeParser(parser, mnemonic, type); + if (result.hasValue()) + return result.getValue(); + + result = customTypeParser(parser, mnemonic, subset, loc, type); + if (result.hasValue()) + return result.getValue(); + + parser.emitError(loc) << "unknown type `" << mnemonic + << "` in dialect `moore`"; + return failure(); +} + +/// Print a type registered with this dialect. +static void printMooreType(Type type, DialectAsmPrinter &printer, + Subset subset) { + if (succeeded(generatedTypePrinter(type, printer))) + return; + if (succeeded(customTypePrinter(type, printer, subset))) + return; + assert(false && "no printer for unknown `moore` dialect type"); +} + +/// Parse a type registered with this dialect. +Type MooreDialect::parseType(DialectAsmParser &parser) const { + Type type; + if (parseMooreType(parser, {Subset::None, true}, type)) + return {}; + return type; +} + +/// Print a type registered with this dialect. +void MooreDialect::printType(Type type, DialectAsmPrinter &printer) const { + printMooreType(type, printer, {Subset::None, true}); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b29444b70a..a9592561f1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,12 @@ configure_lit_site_cfg( MAIN_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py ) +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg.py + MAIN_CONFIG + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.cfg.py + ) set(CIRCT_TEST_DEPENDS FileCheck count not @@ -22,6 +28,10 @@ set(CIRCT_TEST_DEPENDS mlir-cpu-runner ) +if (CIRCT_GTEST_AVAILABLE) + list(APPEND CIRCT_TEST_DEPENDS CIRCTUnitTests) +endif() + if(CIRCT_LLHD_SIM_ENABLED) list(APPEND CIRCT_TEST_DEPENDS llhd-sim) list(APPEND CIRCT_TEST_DEPENDS circt-llhd-signals-runtime-wrappers) diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 0517ee02f0..462a91cb11 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -2,12 +2,12 @@ // CHECK-LABEL: llhd.entity @test1 llhd.entity @test1() -> () { - // CHECK-NEXT: %c5_i32 = hw.constant 5 : i32 - %0 = moore.mir.constant 5 : !moore.rvalue - // CHECK-NEXT: %c3_i32 = hw.constant 3 : i32 - // CHECK-NEXT: [[SIG:%.*]] = llhd.sig "varname" %c3_i32 : i32 - %1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue - // CHECK-NEXT: [[TIME:%.*]] = llhd.constant_time <0s, 0d, 1e> - // CHECK-NEXT: llhd.drv [[SIG]], %c5_i32 after [[TIME]] : !llhd.sig - moore.mir.assign %1, %0 : !moore.lvalue, !moore.rvalue + // CHECK-NEXT: %c5_i32 = hw.constant 5 : i32 + %0 = moore.mir.constant 5 : !moore.rvalue + // CHECK-NEXT: %c3_i32 = hw.constant 3 : i32 + // CHECK-NEXT: [[SIG:%.*]] = llhd.sig "varname" %c3_i32 : i32 + %1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue + // CHECK-NEXT: [[TIME:%.*]] = llhd.constant_time <0s, 0d, 1e> + // CHECK-NEXT: llhd.drv [[SIG]], %c5_i32 after [[TIME]] : !llhd.sig + moore.mir.assign %1, %0 : !moore.lvalue, !moore.rvalue } diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index 59fac00f20..bf6234984d 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -2,10 +2,10 @@ // CHECK-LABEL: llhd.entity @test1 llhd.entity @test1() -> () { - // CHECK-NEXT: [[CONST:%.*]] = moore.mir.constant 5 : !moore.rvalue - // CHECK-NEXT: [[VAR:%.*]] = moore.mir.vardecl "varname" = 3 : !moore.lvalue - // CHECK-NEXT: moore.mir.assign [[VAR]], [[CONST]] : !moore.lvalue, !moore.rvalue - %0 = moore.mir.constant 5 : !moore.rvalue - %1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue - moore.mir.assign %1, %0 : !moore.lvalue, !moore.rvalue + // CHECK-NEXT: [[CONST:%.*]] = moore.mir.constant 5 : !moore.rvalue + // CHECK-NEXT: [[VAR:%.*]] = moore.mir.vardecl "varname" = 3 : !moore.lvalue + // CHECK-NEXT: moore.mir.assign [[VAR]], [[CONST]] : !moore.lvalue, !moore.rvalue + %0 = moore.mir.constant 5 : !moore.rvalue + %1 = moore.mir.vardecl "varname" = 3 : !moore.lvalue + moore.mir.assign %1, %0 : !moore.lvalue, !moore.rvalue } diff --git a/test/Dialect/Moore/types-errors.mlir b/test/Dialect/Moore/types-errors.mlir new file mode 100644 index 0000000000..46df3b8ca5 --- /dev/null +++ b/test/Dialect/Moore/types-errors.mlir @@ -0,0 +1,33 @@ +// RUN: circt-opt --verify-diagnostics --split-input-file %s + +// expected-error @+1 {{ambiguous packing; wrap `named` in `packed<...>` or `unpacked<...>` to disambiguate}} +func @Foo(%arg0: !moore.named<"foo", bit, loc(unknown)>) { return } + +// ----- +// expected-error @+1 {{ambiguous packing; wrap `ref` in `packed<...>` or `unpacked<...>` to disambiguate}} +func @Foo(%arg0: !moore.ref) { return } + +// ----- +// expected-error @+1 {{ambiguous packing; wrap `unsized` in `packed<...>` or `unpacked<...>` to disambiguate}} +func @Foo(%arg0: !moore.unsized) { return } + +// ----- +// expected-error @+1 {{ambiguous packing; wrap `range` in `packed<...>` or `unpacked<...>` to disambiguate}} +func @Foo(%arg0: !moore.range) { return } + +// ----- +// expected-error @+1 {{ambiguous packing; wrap `struct` in `packed<...>` or `unpacked<...>` to disambiguate}} +func @Foo(%arg0: !moore.struct<{}, loc(unknown)>) { return } + +// ----- +// expected-error @+1 {{unpacked struct cannot have a sign}} +func @Foo(%arg0: !moore.unpacked>) { return } +func @Bar(%arg0: !moore.packed>) { return } + +// ----- +// expected-error @+1 {{unpacked type '!moore.string' where only packed types are allowed}} +func @Foo(%arg0: !moore.packed>) { return } + +// ----- +// expected-error @+1 {{unpacked type '!moore.string' where only packed types are allowed}} +func @Foo(%arg0: !moore.packed>) { return } diff --git a/test/Dialect/Moore/types.mlir b/test/Dialect/Moore/types.mlir new file mode 100644 index 0000000000..41f6cf87aa --- /dev/null +++ b/test/Dialect/Moore/types.mlir @@ -0,0 +1,153 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +// CHECK-LABEL: func @UnitTypes( +func @UnitTypes( + // CHECK-SAME: %arg0: !moore.void + // CHECK-SAME: %arg1: !moore.string + // CHECK-SAME: %arg2: !moore.chandle + // CHECK-SAME: %arg3: !moore.event + %arg0: !moore.void, + %arg1: !moore.string, + %arg2: !moore.chandle, + %arg3: !moore.event +) { return } + +// CHECK-LABEL: func @IntTypes( +func @IntTypes( + // CHECK-SAME: %arg0: !moore.bit + // CHECK-SAME: %arg1: !moore.logic + // CHECK-SAME: %arg2: !moore.reg + // CHECK-SAME: %arg3: !moore.byte + // CHECK-SAME: %arg4: !moore.shortint + // CHECK-SAME: %arg5: !moore.int + // CHECK-SAME: %arg6: !moore.longint + // CHECK-SAME: %arg7: !moore.integer + // CHECK-SAME: %arg8: !moore.time + %arg0: !moore.bit, + %arg1: !moore.logic, + %arg2: !moore.reg, + %arg3: !moore.byte, + %arg4: !moore.shortint, + %arg5: !moore.int, + %arg6: !moore.longint, + %arg7: !moore.integer, + %arg8: !moore.time, + // CHECK-SAME: %arg9: !moore.bit + // CHECK-SAME: %arg10: !moore.logic + // CHECK-SAME: %arg11: !moore.reg + // CHECK-SAME: %arg12: !moore.byte + // CHECK-SAME: %arg13: !moore.shortint + // CHECK-SAME: %arg14: !moore.int + // CHECK-SAME: %arg15: !moore.longint + // CHECK-SAME: %arg16: !moore.integer + // CHECK-SAME: %arg17: !moore.time + %arg9: !moore.bit, + %arg10: !moore.logic, + %arg11: !moore.reg, + %arg12: !moore.byte, + %arg13: !moore.shortint, + %arg14: !moore.int, + %arg15: !moore.longint, + %arg16: !moore.integer, + %arg17: !moore.time, + // CHECK-SAME: %arg18: !moore.bit + // CHECK-SAME: %arg19: !moore.logic + // CHECK-SAME: %arg20: !moore.reg + // CHECK-SAME: %arg21: !moore.byte + // CHECK-SAME: %arg22: !moore.shortint + // CHECK-SAME: %arg23: !moore.int + // CHECK-SAME: %arg24: !moore.longint + // CHECK-SAME: %arg25: !moore.integer + // CHECK-SAME: %arg26: !moore.time + %arg18: !moore.bit, + %arg19: !moore.logic, + %arg20: !moore.reg, + %arg21: !moore.byte, + %arg22: !moore.shortint, + %arg23: !moore.int, + %arg24: !moore.longint, + %arg25: !moore.integer, + %arg26: !moore.time +) { return } + +// CHECK-LABEL: func @RealTypes( +func @RealTypes( + // CHECK-SAME: %arg0: !moore.shortreal + // CHECK-SAME: %arg1: !moore.real + // CHECK-SAME: %arg2: !moore.realtime + %arg0: !moore.shortreal, + %arg1: !moore.real, + %arg2: !moore.realtime +) { return } + +// CHECK-LABEL: func @EnumType( +func @EnumType( + // CHECK-SAME: %arg0: !moore.enum + // CHECK-SAME: %arg1: !moore.enum + // CHECK-SAME: %arg2: !moore.enum<"Foo", loc("foo.sv":42:9001)> + // CHECK-SAME: %arg3: !moore.enum<"Foo", int, loc("foo.sv":42:9001)> + %arg0: !moore.enum, + %arg1: !moore.enum, + %arg2: !moore.enum<"Foo", loc("foo.sv":42:9001)>, + %arg3: !moore.enum<"Foo", int, loc("foo.sv":42:9001)> +) { return } + +// CHECK-LABEL: func @IndirectTypes( +func @IndirectTypes( + // CHECK-SAME: %arg0: !moore.packed> + // CHECK-SAME: %arg1: !moore.packed> + %arg0: !moore.packed>, + %arg1: !moore.packed>, + // CHECK-SAME: %arg2: !moore.unpacked> + // CHECK-SAME: %arg3: !moore.unpacked> + // CHECK-SAME: %arg4: !moore.unpacked> + // CHECK-SAME: %arg5: !moore.unpacked> + %arg2: !moore.unpacked>, + %arg3: !moore.unpacked>, + %arg4: !moore.unpacked>, + %arg5: !moore.unpacked> +) { return } + +// CHECK-LABEL: func @DimTypes( +func @DimTypes( + // CHECK-SAME: %arg0: !moore.packed>, + // CHECK-SAME: %arg1: !moore.packed>, + %arg0: !moore.packed>, + %arg1: !moore.packed>, + // CHECK-SAME: %arg2: !moore.unpacked>, + // CHECK-SAME: %arg3: !moore.unpacked>, + // CHECK-SAME: %arg4: !moore.unpacked>, + // CHECK-SAME: %arg5: !moore.unpacked>, + // CHECK-SAME: %arg6: !moore.unpacked>, + // CHECK-SAME: %arg7: !moore.unpacked>, + // CHECK-SAME: %arg8: !moore.unpacked> + %arg2: !moore.unpacked>, + %arg3: !moore.unpacked>, + %arg4: !moore.unpacked>, + %arg5: !moore.unpacked>, + %arg6: !moore.unpacked>, + %arg7: !moore.unpacked>, + %arg8: !moore.unpacked> +) { + return +} + +// CHECK-LABEL: func @StructTypes( +func @StructTypes( + // CHECK-SAME: %arg0: !moore.packed> + // CHECK-SAME: %arg1: !moore.packed> + // CHECK-SAME: %arg2: !moore.packed> + // CHECK-SAME: %arg3: !moore.packed> + // CHECK-SAME: %arg4: !moore.packed> + %arg0: !moore.packed>, + %arg1: !moore.packed>, + %arg2: !moore.packed>, + %arg3: !moore.packed>, + %arg4: !moore.packed>, + // CHECK-SAME: %arg5: !moore.unpacked> + // CHECK-SAME: %arg6: !moore.unpacked> + // CHECK-SAME: %arg7: !moore.unpacked> + %arg5: !moore.unpacked>, + %arg6: !moore.unpacked>, + %arg7: !moore.unpacked> +) { return } diff --git a/test/Unit/lit.cfg.py b/test/Unit/lit.cfg.py new file mode 100644 index 0000000000..c6ba6945bf --- /dev/null +++ b/test/Unit/lit.cfg.py @@ -0,0 +1,39 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +import os +import subprocess + +import lit.formats + +# name: The name of this test suite. +config.name = 'CIRCT-Unit' + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = [] + +# test_source_root: The root path where tests are located. +# test_exec_root: The root path where tests should be run. +config.test_exec_root = os.path.join(config.circt_obj_root, 'unittests') +config.test_source_root = config.test_exec_root + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.GoogleTest(config.llvm_build_mode, 'Tests') + +# Propagate the temp directory. Windows requires this because it uses \Windows\ +# if none of these are present. +if 'TMP' in os.environ: + config.environment['TMP'] = os.environ['TMP'] +if 'TEMP' in os.environ: + config.environment['TEMP'] = os.environ['TEMP'] + +# Propagate HOME as it can be used to override incorrect homedir in passwd +# that causes the tests to fail. +if 'HOME' in os.environ: + config.environment['HOME'] = os.environ['HOME'] + +# Propagate path to symbolizer for ASan/MSan. +for symbolizer in ['ASAN_SYMBOLIZER_PATH', 'MSAN_SYMBOLIZER_PATH']: + if symbolizer in os.environ: + config.environment[symbolizer] = os.environ[symbolizer] diff --git a/test/Unit/lit.site.cfg.py.in b/test/Unit/lit.site.cfg.py.in new file mode 100644 index 0000000000..696590b0a9 --- /dev/null +++ b/test/Unit/lit.site.cfg.py.in @@ -0,0 +1,27 @@ +@LIT_SITE_CFG_IN_HEADER@ + +import sys + +config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.llvm_obj_root = "@LLVM_BINARY_DIR@" +config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" +config.llvm_build_mode = "@LLVM_BUILD_MODE@" +config.enable_shared = @ENABLE_SHARED@ +config.shlibdir = "@SHLIBDIR@" +config.circt_src_root = "@CIRCT_SOURCE_DIR@" +config.circt_obj_root = "@CIRCT_BINARY_DIR@" +config.circt_tools_dir = "@CIRCT_TOOLS_DIR@" + +# Support substitution of the tools_dir and build_mode with user parameters. +# This is used when we can't determine the tool dir at configuration time. +try: + config.llvm_tools_dir = config.llvm_tools_dir % lit_config.params + config.llvm_build_mode = config.llvm_build_mode % lit_config.params + config.shlibdir = config.shlibdir % lit_config.params +except KeyError: + e = sys.exc_info()[1] + key, = e.args + lit_config.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key,key)) + +# Let the main config do the real work. +lit_config.load_config(config, "@CIRCT_SOURCE_DIR@/test/Unit/lit.cfg.py") diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt new file mode 100644 index 0000000000..210812f25b --- /dev/null +++ b/unittests/CMakeLists.txt @@ -0,0 +1,8 @@ +add_custom_target(CIRCTUnitTests) +set_target_properties(CIRCTUnitTests PROPERTIES FOLDER "CIRCT Tests") + +function(add_circt_unittest test_dirname) + add_unittest(CIRCTUnitTests ${test_dirname} ${ARGN}) +endfunction() + +add_subdirectory(Dialect) diff --git a/unittests/Dialect/CMakeLists.txt b/unittests/Dialect/CMakeLists.txt new file mode 100644 index 0000000000..f61f91d5fb --- /dev/null +++ b/unittests/Dialect/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Moore) diff --git a/unittests/Dialect/Moore/CMakeLists.txt b/unittests/Dialect/Moore/CMakeLists.txt new file mode 100644 index 0000000000..e2066b3e5b --- /dev/null +++ b/unittests/Dialect/Moore/CMakeLists.txt @@ -0,0 +1,8 @@ +add_circt_unittest(CIRCTMooreTests + TypesTest.cpp +) + +target_link_libraries(CIRCTMooreTests + PRIVATE + CIRCTMoore +) diff --git a/unittests/Dialect/Moore/TypesTest.cpp b/unittests/Dialect/Moore/TypesTest.cpp new file mode 100644 index 0000000000..0e23b00738 --- /dev/null +++ b/unittests/Dialect/Moore/TypesTest.cpp @@ -0,0 +1,402 @@ +//===- TypesTest.cpp - Moore type unit tests ------------------------------===// +// +// 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 "circt/Dialect/Moore/MooreDialect.h" +#include "circt/Dialect/Moore/MooreTypes.h" +#include "gtest/gtest.h" + +using namespace mlir; +using namespace circt; +using namespace moore; + +namespace { + +TEST(TypesTest, UnitTypes) { + MLIRContext context; + context.loadDialect(); + + auto voidType = VoidType::get(&context); + auto stringType = StringType::get(&context); + auto chandleType = ChandleType::get(&context); + auto eventType = EventType::get(&context); + + ASSERT_EQ(voidType.toString(), "void"); + ASSERT_EQ(stringType.toString(), "string"); + ASSERT_EQ(chandleType.toString(), "chandle"); + ASSERT_EQ(eventType.toString(), "event"); + + ASSERT_EQ(voidType.getBitSize(), 0u); + ASSERT_EQ(stringType.getBitSize(), llvm::None); + ASSERT_EQ(chandleType.getBitSize(), llvm::None); + ASSERT_EQ(eventType.getBitSize(), llvm::None); + + ASSERT_EQ(voidType.getDomain(), Domain::TwoValued); + ASSERT_EQ(stringType.getDomain(), Domain::TwoValued); + ASSERT_EQ(chandleType.getDomain(), Domain::TwoValued); + ASSERT_EQ(eventType.getDomain(), Domain::TwoValued); + + ASSERT_EQ(voidType.getSign(), Sign::Unsigned); + ASSERT_EQ(stringType.getSign(), Sign::Unsigned); + ASSERT_EQ(chandleType.getSign(), Sign::Unsigned); + ASSERT_EQ(eventType.getSign(), Sign::Unsigned); +} + +TEST(TypesTest, Ranges) { + Range a(42); + Range b(32, RangeDir::Down, -5); + Range c(16, RangeDir::Up, -3); + + ASSERT_EQ(a.toString(), "41:0"); + ASSERT_EQ(b.toString(), "26:-5"); + ASSERT_EQ(c.toString(), "-3:12"); + + ASSERT_EQ(a.left(), 41); + ASSERT_EQ(a.right(), 0); + ASSERT_EQ(a.low(), 0); + ASSERT_EQ(a.high(), 41); + ASSERT_EQ(a.increment(), -1); + + ASSERT_EQ(b.left(), 26); + ASSERT_EQ(b.right(), -5); + ASSERT_EQ(b.low(), -5); + ASSERT_EQ(b.high(), 26); + ASSERT_EQ(b.increment(), -1); + + ASSERT_EQ(c.left(), -3); + ASSERT_EQ(c.right(), 12); + ASSERT_EQ(c.low(), -3); + ASSERT_EQ(c.high(), 12); + ASSERT_EQ(c.increment(), 1); +} + +TEST(TypesTest, PackedInt) { + MLIRContext context; + context.loadDialect(); + + std::tuple pairs[] = { + {IntType::Bit, "bit", Domain::TwoValued, Sign::Unsigned}, + {IntType::Logic, "logic", Domain::FourValued, Sign::Unsigned}, + {IntType::Reg, "reg", Domain::FourValued, Sign::Unsigned}, + {IntType::Byte, "byte", Domain::TwoValued, Sign::Signed}, + {IntType::ShortInt, "shortint", Domain::TwoValued, Sign::Signed}, + {IntType::Int, "int", Domain::TwoValued, Sign::Signed}, + {IntType::LongInt, "longint", Domain::TwoValued, Sign::Signed}, + {IntType::Integer, "integer", Domain::FourValued, Sign::Signed}, + {IntType::Time, "time", Domain::TwoValued, Sign::Unsigned}, + }; + + for (auto pair : pairs) { + auto kind = std::get<0>(pair); + auto keyword = std::get<1>(pair); + auto type = IntType::get(&context, kind); + auto unsignedType = IntType::get(&context, kind, Sign::Unsigned); + auto signedType = IntType::get(&context, kind, Sign::Signed); + + // Check the formatting. + ASSERT_EQ(type.toString(), keyword); + ASSERT_EQ(unsignedType.toString(), std::string(keyword) + " unsigned"); + ASSERT_EQ(signedType.toString(), std::string(keyword) + " signed"); + + // Check the domain. + ASSERT_EQ(type.getDomain(), std::get<2>(pair)); + ASSERT_EQ(unsignedType.getDomain(), std::get<2>(pair)); + ASSERT_EQ(signedType.getDomain(), std::get<2>(pair)); + + // Check the sign. + ASSERT_EQ(type.getSign(), std::get<3>(pair)); + ASSERT_EQ(unsignedType.getSign(), Sign::Unsigned); + ASSERT_EQ(signedType.getSign(), Sign::Signed); + ASSERT_FALSE(type.isSignExplicit()); + ASSERT_TRUE(unsignedType.isSignExplicit()); + ASSERT_TRUE(signedType.isSignExplicit()); + } +} + +TEST(TypesTest, Reals) { + MLIRContext context; + context.loadDialect(); + + auto t0 = RealType::get(&context, RealType::ShortReal); + auto t1 = RealType::get(&context, RealType::Real); + auto t2 = RealType::get(&context, RealType::RealTime); + + ASSERT_EQ(t0.toString(), "shortreal"); + ASSERT_EQ(t1.toString(), "real"); + ASSERT_EQ(t2.toString(), "realtime"); + + ASSERT_EQ(t0.getDomain(), Domain::TwoValued); + ASSERT_EQ(t1.getDomain(), Domain::TwoValued); + ASSERT_EQ(t2.getDomain(), Domain::TwoValued); + + ASSERT_EQ(t0.getBitSize(), 32u); + ASSERT_EQ(t1.getBitSize(), 64u); + ASSERT_EQ(t2.getBitSize(), 64u); + + ASSERT_EQ(t0.getSign(), Sign::Unsigned); + ASSERT_EQ(t1.getSign(), Sign::Unsigned); + ASSERT_EQ(t2.getSign(), Sign::Unsigned); +} + +TEST(TypesTest, PackedDim) { + MLIRContext context; + context.loadDialect(); + + auto bitType = IntType::get(&context, IntType::Bit); + auto arrayType1 = PackedRangeDim::get(bitType, 3); + auto arrayType2 = PackedRangeDim::get(arrayType1, 2); + auto arrayType3 = PackedUnsizedDim::get(arrayType2); + + ASSERT_EQ(arrayType1.toString(), "bit [2:0]"); + ASSERT_EQ(arrayType2.toString(), "bit [1:0][2:0]"); + ASSERT_EQ(arrayType3.toString(), "bit [][1:0][2:0]"); + + ASSERT_EQ(arrayType1.getRange(), Range(3)); + ASSERT_EQ(arrayType3.getRange(), llvm::None); + ASSERT_EQ(arrayType1.getSize(), 3u); + ASSERT_EQ(arrayType3.getSize(), llvm::None); +} + +TEST(TypesTest, UnpackedDim) { + MLIRContext context; + context.loadDialect(); + + auto stringType = StringType::get(&context); + auto arrayType1 = UnpackedUnsizedDim::get(stringType); + auto arrayType2 = UnpackedArrayDim::get(arrayType1, 42); + auto arrayType3 = UnpackedRangeDim::get(arrayType2, 2); + auto arrayType4 = UnpackedAssocDim::get(arrayType3); + auto arrayType5 = UnpackedAssocDim::get(arrayType4, stringType); + auto arrayType6 = UnpackedQueueDim::get(arrayType5); + auto arrayType7 = UnpackedQueueDim::get(arrayType6, 9); + + ASSERT_EQ(arrayType1.toString(), "string $ []"); + ASSERT_EQ(arrayType2.toString(), "string $ [42][]"); + ASSERT_EQ(arrayType3.toString(), "string $ [1:0][42][]"); + ASSERT_EQ(arrayType4.toString(), "string $ [*][1:0][42][]"); + ASSERT_EQ(arrayType5.toString(), "string $ [string][*][1:0][42][]"); + ASSERT_EQ(arrayType6.toString(), "string $ [$][string][*][1:0][42][]"); + ASSERT_EQ(arrayType7.toString(), "string $ [$:9][$][string][*][1:0][42][]"); + + ASSERT_EQ(arrayType2.getSize(), 42); + ASSERT_EQ(arrayType3.getRange(), Range(2)); + ASSERT_EQ(arrayType4.getIndexType(), UnpackedType{}); + ASSERT_EQ(arrayType5.getIndexType(), stringType); + ASSERT_EQ(arrayType6.getBound(), llvm::None); + ASSERT_EQ(arrayType7.getBound(), 9u); +} + +TEST(TypesTest, UnpackedFormattingAroundStuff) { + MLIRContext context; + context.loadDialect(); + + auto bitType = IntType::get(&context, IntType::Bit); + auto arrayType1 = PackedRangeDim::get(bitType, 3); + auto arrayType2 = UnpackedArrayDim::get(arrayType1, 42); + + // Packed type formatting with custom separator. + ASSERT_EQ(arrayType1.toString(), "bit [2:0]"); + ASSERT_EQ(arrayType1.toString("foo"), "bit [2:0] foo"); + ASSERT_EQ(arrayType1.toString([](auto &os) { os << "bar"; }), + "bit [2:0] bar"); + + // Unpacked type formatting with custom separator. + ASSERT_EQ(arrayType2.toString(), "bit [2:0] $ [42]"); + ASSERT_EQ(arrayType2.toString("foo"), "bit [2:0] foo [42]"); + ASSERT_EQ(arrayType2.toString([](auto &os) { os << "bar"; }), + "bit [2:0] bar [42]"); +} + +TEST(TypesTest, Resolution) { + MLIRContext context; + context.loadDialect(); + + auto loc = UnknownLoc::get(&context); + auto t0 = IntType::get(&context, IntType::Bit); + auto t1 = PackedRangeDim::get(t0, 3); + auto t2 = PackedNamedType::get(t1, "foo", loc); + + ASSERT_EQ(t2.toString(), "foo"); + ASSERT_EQ(t2.resolved().toString(), "bit [2:0]"); + ASSERT_EQ(t2.fullyResolved().toString(), "bit [2:0]"); + + auto t3 = PackedRangeDim::get(t2, 2); + auto t4 = PackedNamedType::get(t3, "bar", loc); + + ASSERT_EQ(t4.toString(), "bar"); + ASSERT_EQ(t4.resolved().toString(), "foo [1:0]"); + ASSERT_EQ(t4.fullyResolved().toString(), "bit [1:0][2:0]"); + + auto t5 = UnpackedArrayDim::get(t4, 4); + auto t6 = UnpackedNamedType::get(t5, "tony", loc); + + ASSERT_EQ(t6.toString(), "tony"); + ASSERT_EQ(t6.resolved().toString(), "bar $ [4]"); + ASSERT_EQ(t6.fullyResolved().toString(), "bit [1:0][2:0] $ [4]"); + + auto t7 = UnpackedAssocDim::get(t6); + auto t8 = UnpackedNamedType::get(t7, "ada", loc); + + ASSERT_EQ(t8.toString(), "ada"); + ASSERT_EQ(t8.resolved().toString(), "tony $ [*]"); + ASSERT_EQ(t8.fullyResolved().toString(), "bit [1:0][2:0] $ [*][4]"); + + // Type references + auto r0 = PackedRefType::get(t2, loc); + + ASSERT_EQ(r0.toString(), "type(foo)"); + ASSERT_EQ(r0.resolved().toString(), "foo"); + ASSERT_EQ(r0.fullyResolved().toString(), "bit [2:0]"); + + auto r1 = UnpackedRefType::get(t8, loc); + + ASSERT_EQ(r1.toString(), "type(ada)"); + ASSERT_EQ(r1.resolved().toString(), "ada"); + ASSERT_EQ(r1.fullyResolved().toString(), "bit [1:0][2:0] $ [*][4]"); +} + +TEST(TypesTest, NamedStructFormatting) { + MLIRContext context; + context.loadDialect(); + auto loc = UnknownLoc::get(&context); + auto foo = StringAttr::get(&context, "Foo"); + + auto s0 = UnpackedStructType::get(StructKind::Struct, {}, foo, loc); + auto s1 = UnpackedStructType::get(StructKind::Union, {}, foo, loc); + auto s2 = UnpackedStructType::get(StructKind::TaggedUnion, {}, foo, loc); + auto s3 = PackedStructType::get(StructKind::Struct, {}, foo, loc); + auto s4 = PackedStructType::get(StructKind::Union, {}, foo, loc); + auto s5 = PackedStructType::get(StructKind::TaggedUnion, {}, foo, loc); + auto s6 = + PackedStructType::get(StructKind::Struct, {}, foo, loc, Sign::Unsigned); + auto s7 = + PackedStructType::get(StructKind::Union, {}, foo, loc, Sign::Unsigned); + auto s8 = PackedStructType::get(StructKind::TaggedUnion, {}, foo, loc, + Sign::Unsigned); + auto s9 = + PackedStructType::get(StructKind::Struct, {}, foo, loc, Sign::Signed); + auto s10 = + PackedStructType::get(StructKind::Union, {}, foo, loc, Sign::Signed); + auto s11 = PackedStructType::get(StructKind::TaggedUnion, {}, foo, loc, + Sign::Signed); + + ASSERT_EQ(s0.toString(), "struct Foo"); + ASSERT_EQ(s1.toString(), "union Foo"); + ASSERT_EQ(s2.toString(), "union tagged Foo"); + ASSERT_EQ(s3.toString(), "struct packed Foo"); + ASSERT_EQ(s4.toString(), "union packed Foo"); + ASSERT_EQ(s5.toString(), "union tagged packed Foo"); + ASSERT_EQ(s6.toString(), "struct packed unsigned Foo"); + ASSERT_EQ(s7.toString(), "union packed unsigned Foo"); + ASSERT_EQ(s8.toString(), "union tagged packed unsigned Foo"); + ASSERT_EQ(s9.toString(), "struct packed signed Foo"); + ASSERT_EQ(s10.toString(), "union packed signed Foo"); + ASSERT_EQ(s11.toString(), "union tagged packed signed Foo"); +} + +TEST(TypesTest, Structs) { + MLIRContext context; + context.loadDialect(); + auto loc = UnknownLoc::get(&context); + auto foo = StringAttr::get(&context, "foo"); + auto bar = StringAttr::get(&context, "bar"); + + auto bitType = IntType::get(&context, IntType::Bit); + auto logicType = IntType::get(&context, IntType::Logic); + auto bit8Type = PackedRangeDim::get(bitType, 8); + auto bitDynArrayType = PackedUnsizedDim::get(bitType); + + auto s0 = UnpackedStructType::get(StructKind::Struct, + {StructMember{foo, loc, bitType}}, {}, loc); + auto s1 = UnpackedStructType::get( + StructKind::Struct, + {StructMember{foo, loc, bitType}, StructMember{bar, loc, bit8Type}}, {}, + loc); + auto s2 = UnpackedStructType::get( + StructKind::Struct, + {StructMember{foo, loc, bitType}, StructMember{bar, loc, logicType}}, {}, + loc); + auto s3 = UnpackedStructType::get(StructKind::Struct, + {StructMember{foo, loc, bitType}, + StructMember{bar, loc, bitDynArrayType}}, + {}, loc); + + // Member formatting + ASSERT_EQ(s0.toString(), "struct { bit foo; }"); + ASSERT_EQ(s1.toString(), "struct { bit foo; bit [7:0] bar; }"); + + // Value domain + ASSERT_EQ(s1.getDomain(), Domain::TwoValued); + ASSERT_EQ(s2.getDomain(), Domain::FourValued); + + // Bit size + ASSERT_EQ(s0.getBitSize(), 1u); + ASSERT_EQ(s1.getBitSize(), 9u); + ASSERT_EQ(s2.getBitSize(), 2u); + ASSERT_EQ(s3.getBitSize(), llvm::None); +} + +TEST(TypesTest, Enums) { + MLIRContext context; + context.loadDialect(); + auto loc = UnknownLoc::get(&context); + auto foo = StringAttr::get(&context, "Foo"); + auto intType = IntType::getInt(&context); + auto bitType = IntType::get(&context, IntType::Bit); + auto bit8Type = PackedRangeDim::get(bitType, 8); + auto slogicType = IntType::get(&context, IntType::Logic, Sign::Signed); + auto slogic8Type = PackedRangeDim::get(slogicType, 8); + + auto e0 = EnumType::get({}, loc); + auto e1 = EnumType::get(foo, loc); + auto e2 = EnumType::get({}, loc, bit8Type); + auto e3 = EnumType::get(foo, loc, bit8Type); + auto e4 = EnumType::get({}, loc, slogic8Type); + auto e5 = EnumType::get(foo, loc, slogic8Type); + + // Formatting + ASSERT_EQ(e0.toString(), "enum"); + ASSERT_EQ(e1.toString(), "enum Foo"); + ASSERT_EQ(e2.toString(), "enum bit [7:0]"); + ASSERT_EQ(e3.toString(), "enum Foo"); + ASSERT_EQ(e4.toString(), "enum logic signed [7:0]"); + ASSERT_EQ(e5.toString(), "enum Foo"); + + // Base types + ASSERT_EQ(e0.getBase(), intType); + ASSERT_EQ(e1.getBase(), intType); + ASSERT_EQ(e2.getBase(), bit8Type); + ASSERT_EQ(e3.getBase(), bit8Type); + ASSERT_EQ(e4.getBase(), slogic8Type); + ASSERT_EQ(e5.getBase(), slogic8Type); + + // Sign + ASSERT_EQ(e0.getSign(), Sign::Signed); // implicit int + ASSERT_EQ(e1.getSign(), Sign::Signed); // implicit int + ASSERT_EQ(e2.getSign(), Sign::Unsigned); + ASSERT_EQ(e3.getSign(), Sign::Unsigned); + ASSERT_EQ(e4.getSign(), Sign::Signed); + ASSERT_EQ(e5.getSign(), Sign::Signed); + + // Value domain + ASSERT_EQ(e0.getDomain(), Domain::TwoValued); + ASSERT_EQ(e1.getDomain(), Domain::TwoValued); + ASSERT_EQ(e2.getDomain(), Domain::TwoValued); + ASSERT_EQ(e3.getDomain(), Domain::TwoValued); + ASSERT_EQ(e4.getDomain(), Domain::FourValued); + ASSERT_EQ(e5.getDomain(), Domain::FourValued); + + // Bit size + ASSERT_EQ(e0.getBitSize(), 32u); + ASSERT_EQ(e1.getBitSize(), 32u); + ASSERT_EQ(e2.getBitSize(), 8u); + ASSERT_EQ(e3.getBitSize(), 8u); + ASSERT_EQ(e4.getBitSize(), 8u); + ASSERT_EQ(e5.getBitSize(), 8u); +} + +} // namespace