Author: River Riddle Date: 2021-01-11T12:06:22-08:00 New Revision: d79642b3db1d74524f41d37f78385950158787a4
URL: https://github.com/llvm/llvm-project/commit/d79642b3db1d74524f41d37f78385950158787a4 DIFF: https://github.com/llvm/llvm-project/commit/d79642b3db1d74524f41d37f78385950158787a4.diff LOG: [mlir][IR][NFC] Move the definitions of Complex/Function/Integer/Opaque/TupleType to ODS The type tablegen backend now has enough support to represent these types well enough, so we can now move them to be declaratively defined. Differential Revision: https://reviews.llvm.org/D94275 Added: Modified: mlir/include/mlir/IR/BuiltinTypes.h mlir/include/mlir/IR/BuiltinTypes.td mlir/lib/IR/BuiltinTypes.cpp mlir/lib/IR/MLIRContext.cpp mlir/lib/IR/TypeDetail.h Removed: ################################################################################ diff --git a/mlir/include/mlir/IR/BuiltinTypes.h b/mlir/include/mlir/IR/BuiltinTypes.h index d9d1a6e4e68c..835f46dc2295 100644 --- a/mlir/include/mlir/IR/BuiltinTypes.h +++ b/mlir/include/mlir/IR/BuiltinTypes.h @@ -29,119 +29,15 @@ class TypeRange; namespace detail { struct BaseMemRefTypeStorage; -struct ComplexTypeStorage; -struct FunctionTypeStorage; -struct IntegerTypeStorage; struct MemRefTypeStorage; -struct OpaqueTypeStorage; struct RankedTensorTypeStorage; struct ShapedTypeStorage; -struct TupleTypeStorage; struct UnrankedMemRefTypeStorage; struct UnrankedTensorTypeStorage; struct VectorTypeStorage; } // namespace detail -//===----------------------------------------------------------------------===// -// ComplexType -//===----------------------------------------------------------------------===// - -/// The 'complex' type represents a complex number with a parameterized element -/// type, which is composed of a real and imaginary value of that element type. -/// -/// The element must be a floating point or integer scalar type. -/// -class ComplexType - : public Type::TypeBase<ComplexType, Type, detail::ComplexTypeStorage> { -public: - using Base::Base; - - /// Get or create a ComplexType with the provided element type. - static ComplexType get(Type elementType); - - /// Get or create a ComplexType with the provided element type. This emits - /// and error at the specified location and returns null if the element type - /// isn't supported. - static ComplexType getChecked(Location location, Type elementType); - - /// Verify the construction of an integer type. - static LogicalResult verifyConstructionInvariants(Location loc, - Type elementType); - - Type getElementType(); -}; - -//===----------------------------------------------------------------------===// -// IntegerType -//===----------------------------------------------------------------------===// - -/// Integer types can have arbitrary bitwidth up to a large fixed limit. -class IntegerType - : public Type::TypeBase<IntegerType, Type, detail::IntegerTypeStorage> { -public: - using Base::Base; - - /// Signedness semantics. - enum SignednessSemantics : uint32_t { - Signless, /// No signedness semantics - Signed, /// Signed integer - Unsigned, /// Unsigned integer - }; - - /// Get or create a new IntegerType of the given width within the context. - /// The created IntegerType is signless (i.e., no signedness semantics). - /// Assume the width is within the allowed range and assert on failures. Use - /// getChecked to handle failures gracefully. - static IntegerType get(MLIRContext *context, unsigned width); - - /// Get or create a new IntegerType of the given width within the context. - /// The created IntegerType has signedness semantics as indicated via - /// `signedness`. Assume the width is within the allowed range and assert on - /// failures. Use getChecked to handle failures gracefully. - static IntegerType get(MLIRContext *context, unsigned width, - SignednessSemantics signedness); - - /// Get or create a new IntegerType of the given width within the context, - /// defined at the given, potentially unknown, location. The created - /// IntegerType is signless (i.e., no signedness semantics). If the width is - /// outside the allowed range, emit errors and return a null type. - static IntegerType getChecked(Location location, unsigned width); - - /// Get or create a new IntegerType of the given width within the context, - /// defined at the given, potentially unknown, location. The created - /// IntegerType has signedness semantics as indicated via `signedness`. If the - /// width is outside the allowed range, emit errors and return a null type. - static IntegerType getChecked(Location location, unsigned width, - SignednessSemantics signedness); - - /// Verify the construction of an integer type. - static LogicalResult - verifyConstructionInvariants(Location loc, unsigned width, - SignednessSemantics signedness); - - /// Return the bitwidth of this integer type. - unsigned getWidth() const; - - /// Return the signedness semantics of this integer type. - SignednessSemantics getSignedness() const; - - /// Return true if this is a signless integer type. - bool isSignless() const { return getSignedness() == Signless; } - /// Return true if this is a signed integer type. - bool isSigned() const { return getSignedness() == Signed; } - /// Return true if this is an unsigned integer type. - bool isUnsigned() const { return getSignedness() == Unsigned; } - - /// Get or create a new IntegerType with the same signedness as `this` and a - /// bitwidth scaled by `scale`. - /// Return null if the scaled element type cannot be represented. - IntegerType scaleElementBitwidth(unsigned scale); - - /// Integer representation maximal bitwidth. - static constexpr unsigned kMaxWidth = (1 << 24) - 1; // Aligned with LLVM -}; - //===----------------------------------------------------------------------===// // FloatType //===----------------------------------------------------------------------===// @@ -170,68 +66,6 @@ class FloatType : public Type { const llvm::fltSemantics &getFloatSemantics(); }; -//===----------------------------------------------------------------------===// -// FunctionType -//===----------------------------------------------------------------------===// - -/// Function types map from a list of inputs to a list of results. -class FunctionType - : public Type::TypeBase<FunctionType, Type, detail::FunctionTypeStorage> { -public: - using Base::Base; - - static FunctionType get(MLIRContext *context, TypeRange inputs, - TypeRange results); - - /// Input types. - unsigned getNumInputs() const; - Type getInput(unsigned i) const { return getInputs()[i]; } - ArrayRef<Type> getInputs() const; - - /// Result types. - unsigned getNumResults() const; - Type getResult(unsigned i) const { return getResults()[i]; } - ArrayRef<Type> getResults() const; - - /// Returns a new function type without the specified arguments and results. - FunctionType getWithoutArgsAndResults(ArrayRef<unsigned> argIndices, - ArrayRef<unsigned> resultIndices); -}; - -//===----------------------------------------------------------------------===// -// OpaqueType -//===----------------------------------------------------------------------===// - -/// Opaque types represent types of non-registered dialects. These are types -/// represented in their raw string form, and can only usefully be tested for -/// type equality. -class OpaqueType - : public Type::TypeBase<OpaqueType, Type, detail::OpaqueTypeStorage> { -public: - using Base::Base; - - /// Get or create a new OpaqueType with the provided dialect and string data. - static OpaqueType get(MLIRContext *context, Identifier dialect, - StringRef typeData); - - /// Get or create a new OpaqueType with the provided dialect and string data. - /// If the given identifier is not a valid namespace for a dialect, then a - /// null type is returned. - static OpaqueType getChecked(Location location, Identifier dialect, - StringRef typeData); - - /// Returns the dialect namespace of the opaque type. - Identifier getDialectNamespace() const; - - /// Returns the raw type data of the opaque type. - StringRef getTypeData() const; - - /// Verify the construction of an opaque type. - static LogicalResult verifyConstructionInvariants(Location loc, - Identifier dialect, - StringRef typeData); -}; - //===----------------------------------------------------------------------===// // ShapedType //===----------------------------------------------------------------------===// @@ -444,9 +278,7 @@ class BaseMemRefType : public ShapedType { using ShapedType::ShapedType; /// Return true if the specified element type is ok in a memref. - static bool isValidElementType(Type type) { - return type.isIntOrIndexOrFloat() || type.isa<VectorType, ComplexType>(); - } + static bool isValidElementType(Type type); /// Methods for support type inquiry through isa, cast, and dyn_cast. static bool classof(Type type); @@ -584,51 +416,6 @@ class UnrankedMemRefType ArrayRef<int64_t> getShape() const { return llvm::None; } }; - -//===----------------------------------------------------------------------===// -// TupleType -//===----------------------------------------------------------------------===// - -/// Tuple types represent a collection of other types. Note: This type merely -/// provides a common mechanism for representing tuples in MLIR. It is up to -/// dialect authors to provides operations for manipulating them, e.g. -/// extract_tuple_element. When possible, users should prefer multi-result -/// operations in the place of tuples. -class TupleType - : public Type::TypeBase<TupleType, Type, detail::TupleTypeStorage> { -public: - using Base::Base; - - /// Get or create a new TupleType with the provided element types. Assumes the - /// arguments define a well-formed type. - static TupleType get(MLIRContext *context, TypeRange elementTypes); - - /// Get or create an empty tuple type. - static TupleType get(MLIRContext *context); - - /// Return the elements types for this tuple. - ArrayRef<Type> getTypes() const; - - /// Accumulate the types contained in this tuple and tuples nested within it. - /// Note that this only flattens nested tuples, not any other container type, - /// e.g. a tuple<i32, tensor<i32>, tuple<f32, tuple<i64>>> is flattened to - /// (i32, tensor<i32>, f32, i64) - void getFlattenedTypes(SmallVectorImpl<Type> &types); - - /// Return the number of held types. - size_t size() const; - - /// Iterate over the held elements. - using iterator = ArrayRef<Type>::iterator; - iterator begin() const { return getTypes().begin(); } - iterator end() const { return getTypes().end(); } - - /// Return the element type at index 'index'. - Type getType(size_t index) const { - assert(index < size() && "invalid index for tuple type"); - return getTypes()[index]; - } -}; } // end namespace mlir //===----------------------------------------------------------------------===// @@ -647,6 +434,10 @@ inline bool BaseMemRefType::classof(Type type) { return type.isa<MemRefType, UnrankedMemRefType>(); } +inline bool BaseMemRefType::isValidElementType(Type type) { + return type.isIntOrIndexOrFloat() || type.isa<ComplexType, VectorType>(); +} + inline bool FloatType::classof(Type type) { return type.isa<BFloat16Type, Float16Type, Float32Type, Float64Type>(); } diff --git a/mlir/include/mlir/IR/BuiltinTypes.td b/mlir/include/mlir/IR/BuiltinTypes.td index b540554fb11e..1c225b9b405b 100644 --- a/mlir/include/mlir/IR/BuiltinTypes.td +++ b/mlir/include/mlir/IR/BuiltinTypes.td @@ -25,6 +25,42 @@ class Builtin_Type<string name> : TypeDef<Builtin_Dialect, name> { let mnemonic = ?; } +//===----------------------------------------------------------------------===// +// ComplexType +//===----------------------------------------------------------------------===// + +def Builtin_Complex : Builtin_Type<"Complex"> { + let summary = "Complex number with a parameterized element type"; + let description = [{ + Syntax: + + ``` + complex-type ::= `complex` `<` type `>` + ``` + + The value of `complex` type represents a complex number with a parameterized + element type, which is composed of a real and imaginary value of that + element type. The element must be a floating point or integer scalar type. + + Examples: + + ```mlir + complex<f32> + complex<i32> + ``` + }]; + let parameters = (ins "Type":$elementType); + let builders = [ + TypeBuilderWithInferredContext<(ins "Type":$elementType), [{ + return Base::get(elementType.getContext(), elementType); + }], [{ + return Base::getChecked($_loc, elementType); + }]> + ]; + let skipDefaultBuilders = 1; + let genVerifyInvariantsDecl = 1; +} + //===----------------------------------------------------------------------===// // FloatType //===----------------------------------------------------------------------===// @@ -65,6 +101,48 @@ def Builtin_Float64 : Builtin_FloatType<"Float64"> { let summary = "64-bit floating-point type"; } +//===----------------------------------------------------------------------===// +// FunctionType +//===----------------------------------------------------------------------===// + +def Builtin_Function : Builtin_Type<"Function"> { + let summary = "Map from a list of inputs to a list of results"; + let description = [{ + Syntax: + + ``` + // Function types may have multiple results. + function-result-type ::= type-list-parens | non-function-type + function-type ::= type-list-parens `->` function-result-type + ``` + + The function type can be thought of as a function signature. It consists of + a list of formal parameter types and a list of formal result types. + ``` + }]; + let parameters = (ins "ArrayRef<Type>":$inputs, "ArrayRef<Type>":$results); + let builders = [ + TypeBuilder<(ins CArg<"TypeRange">:$inputs, CArg<"TypeRange">:$results), [{ + return Base::get($_ctxt, inputs, results); + }]> + ]; + let skipDefaultBuilders = 1; + let genStorageClass = 0; + let extraClassDeclaration = [{ + /// Input types. + unsigned getNumInputs() const; + Type getInput(unsigned i) const { return getInputs()[i]; } + + /// Result types. + unsigned getNumResults() const; + Type getResult(unsigned i) const { return getResults()[i]; } + + /// Returns a new function type without the specified arguments and results. + FunctionType getWithoutArgsAndResults(ArrayRef<unsigned> argIndices, + ArrayRef<unsigned> resultIndices); + }]; +} + //===----------------------------------------------------------------------===// // IndexType //===----------------------------------------------------------------------===// @@ -96,6 +174,70 @@ def Builtin_Index : Builtin_Type<"Index"> { }]; } +//===----------------------------------------------------------------------===// +// IntegerType +//===----------------------------------------------------------------------===// + +def Builtin_Integer : Builtin_Type<"Integer"> { + let summary = "Integer type with arbitrary precision up to a fixed limit"; + let description = [{ + Syntax: + + ``` + // Sized integers like i1, i4, i8, i16, i32. + signed-integer-type ::= `si` [1-9][0-9]* + unsigned-integer-type ::= `ui` [1-9][0-9]* + signless-integer-type ::= `i` [1-9][0-9]* + integer-type ::= signed-integer-type | + unsigned-integer-type | + signless-integer-type + ``` + + Integer types have a designated bit width and may optionally have signedness + semantics. + + **Rationale:** low precision integers (like `i2`, `i4` etc) are useful for + low-precision inference chips, and arbitrary precision integers are useful + for hardware synthesis (where a 13 bit multiplier is a lot cheaper/smaller + than a 16 bit one). + }]; + let parameters = (ins "unsigned":$width, "SignednessSemantics":$signedness); + let builders = [ + TypeBuilder<(ins "unsigned":$width, + CArg<"SignednessSemantics", "Signless">:$signedness)> + ]; + + // IntegerType uses a special storage class that compacts parameters to save + // memory. + let genStorageClass = 0; + let skipDefaultBuilders = 1; + let genVerifyInvariantsDecl = 1; + let extraClassDeclaration = [{ + /// Signedness semantics. + enum SignednessSemantics : uint32_t { + Signless, /// No signedness semantics + Signed, /// Signed integer + Unsigned, /// Unsigned integer + }; + + /// Return true if this is a signless integer type. + bool isSignless() const { return getSignedness() == Signless; } + /// Return true if this is a signed integer type. + bool isSigned() const { return getSignedness() == Signed; } + /// Return true if this is an unsigned integer type. + bool isUnsigned() const { return getSignedness() == Unsigned; } + + /// Get or create a new IntegerType with the same signedness as `this` and a + /// bitwidth scaled by `scale`. + /// Return null if the scaled element type cannot be represented. + IntegerType scaleElementBitwidth(unsigned scale); + + /// Integer representation maximal bitwidth. + /// Note: This is aligned with the maximum width of llvm::IntegerType. + static constexpr unsigned kMaxWidth = (1 << 24) - 1; + }]; +} + //===----------------------------------------------------------------------===// // NoneType //===----------------------------------------------------------------------===// @@ -111,4 +253,102 @@ def Builtin_None : Builtin_Type<"None"> { }]; } +//===----------------------------------------------------------------------===// +// OpaqueType +//===----------------------------------------------------------------------===// + +def Builtin_Opaque : Builtin_Type<"Opaque"> { + let summary = "Type of a non-registered dialect"; + let description = [{ + Syntax: + + ``` + opaque-type ::= `opaque` `<` type `>` + ``` + + Opaque types represent types of non-registered dialects. These are types + represented in their raw string form, and can only usefully be tested for + type equality. + + Examples: + + ```mlir + opaque<"llvm", "struct<(i32, float)>"> + opaque<"pdl", "value"> + ``` + }]; + let parameters = (ins + "Identifier":$dialectNamespace, + StringRefParameter<"">:$typeData + ); + let genVerifyInvariantsDecl = 1; +} + +//===----------------------------------------------------------------------===// +// TupleType +//===----------------------------------------------------------------------===// + +def Builtin_Tuple : Builtin_Type<"Tuple"> { + let summary = "Fixed-sized collection of other types"; + let description = [{ + Syntax: + + ``` + tuple-type ::= `tuple` `<` (type ( `,` type)*)? `>` + ``` + + The value of `tuple` type represents a fixed-size collection of elements, + where each element may be of a diff erent type. + + **Rationale:** Though this type is first class in the type system, MLIR + provides no standard operations for operating on `tuple` types + ([rationale](Rationale/Rationale.md#tuple-types)). + + Examples: + + ```mlir + // Empty tuple. + tuple<> + + // Single element + tuple<f32> + + // Many elements. + tuple<i32, f32, tensor<i1>, i5> + ``` + }]; + let parameters = (ins "ArrayRef<Type>":$types); + let builders = [ + TypeBuilder<(ins "TypeRange":$elementTypes), [{ + return Base::get($_ctxt, elementTypes); + }]>, + TypeBuilder<(ins), [{ + return Base::get($_ctxt, TypeRange()); + }]> + ]; + let skipDefaultBuilders = 1; + let genStorageClass = 0; + let extraClassDeclaration = [{ + /// Accumulate the types contained in this tuple and tuples nested within + /// it. Note that this only flattens nested tuples, not any other container + /// type, e.g. a tuple<i32, tensor<i32>, tuple<f32, tuple<i64>>> is + /// flattened to (i32, tensor<i32>, f32, i64) + void getFlattenedTypes(SmallVectorImpl<Type> &types); + + /// Return the number of held types. + size_t size() const; + + /// Iterate over the held elements. + using iterator = ArrayRef<Type>::iterator; + iterator begin() const { return getTypes().begin(); } + iterator end() const { return getTypes().end(); } + + /// Return the element type at index 'index'. + Type getType(size_t index) const { + assert(index < size() && "invalid index for tuple type"); + return getTypes()[index]; + } + }]; +} + #endif // BUILTIN_TYPES diff --git a/mlir/lib/IR/BuiltinTypes.cpp b/mlir/lib/IR/BuiltinTypes.cpp index b4fc53d9f097..af8fdf381442 100644 --- a/mlir/lib/IR/BuiltinTypes.cpp +++ b/mlir/lib/IR/BuiltinTypes.cpp @@ -31,14 +31,6 @@ using namespace mlir::detail; /// ComplexType //===----------------------------------------------------------------------===// -ComplexType ComplexType::get(Type elementType) { - return Base::get(elementType.getContext(), elementType); -} - -ComplexType ComplexType::getChecked(Location location, Type elementType) { - return Base::getChecked(location, elementType); -} - /// Verify the construction of an integer type. LogicalResult ComplexType::verifyConstructionInvariants(Location loc, Type elementType) { @@ -47,8 +39,6 @@ LogicalResult ComplexType::verifyConstructionInvariants(Location loc, return success(); } -Type ComplexType::getElementType() { return getImpl()->elementType; } - //===----------------------------------------------------------------------===// // Integer Type //===----------------------------------------------------------------------===// @@ -126,11 +116,6 @@ FloatType FloatType::scaleElementBitwidth(unsigned scale) { // FunctionType //===----------------------------------------------------------------------===// -FunctionType FunctionType::get(MLIRContext *context, TypeRange inputs, - TypeRange results) { - return Base::get(context, inputs, results); -} - unsigned FunctionType::getNumInputs() const { return getImpl()->numInputs; } ArrayRef<Type> FunctionType::getInputs() const { @@ -189,24 +174,6 @@ FunctionType::getWithoutArgsAndResults(ArrayRef<unsigned> argIndices, // OpaqueType //===----------------------------------------------------------------------===// -OpaqueType OpaqueType::get(MLIRContext *context, Identifier dialect, - StringRef typeData) { - return Base::get(context, dialect, typeData); -} - -OpaqueType OpaqueType::getChecked(Location location, Identifier dialect, - StringRef typeData) { - return Base::getChecked(location, dialect, typeData); -} - -/// Returns the dialect namespace of the opaque type. -Identifier OpaqueType::getDialectNamespace() const { - return getImpl()->dialectNamespace; -} - -/// Returns the raw type data of the opaque type. -StringRef OpaqueType::getTypeData() const { return getImpl()->typeData; } - /// Verify the construction of an opaque type. LogicalResult OpaqueType::verifyConstructionInvariants(Location loc, Identifier dialect, @@ -693,15 +660,6 @@ LogicalResult mlir::getStridesAndOffset(MemRefType t, /// TupleType //===----------------------------------------------------------------------===// -/// Get or create a new TupleType with the provided element types. Assumes the -/// arguments define a well-formed type. -TupleType TupleType::get(MLIRContext *context, TypeRange elementTypes) { - return Base::get(context, elementTypes); -} - -/// Get or create an empty tuple type. -TupleType TupleType::get(MLIRContext *context) { return get(context, {}); } - /// Return the elements types for this tuple. ArrayRef<Type> TupleType::getTypes() const { return getImpl()->getTypes(); } @@ -721,6 +679,10 @@ void TupleType::getFlattenedTypes(SmallVectorImpl<Type> &types) { /// Return the number of element types. size_t TupleType::size() const { return getImpl()->size(); } +//===----------------------------------------------------------------------===// +// Type Utilities +//===----------------------------------------------------------------------===// + AffineMap mlir::makeStridedLinearLayoutMap(ArrayRef<int64_t> strides, int64_t offset, MLIRContext *context) { diff --git a/mlir/lib/IR/MLIRContext.cpp b/mlir/lib/IR/MLIRContext.cpp index 0837a6511669..3afc10876c73 100644 --- a/mlir/lib/IR/MLIRContext.cpp +++ b/mlir/lib/IR/MLIRContext.cpp @@ -772,10 +772,6 @@ getCachedIntegerType(unsigned width, } } -IntegerType IntegerType::get(MLIRContext *context, unsigned width) { - return get(context, width, IntegerType::Signless); -} - IntegerType IntegerType::get(MLIRContext *context, unsigned width, IntegerType::SignednessSemantics signedness) { if (auto cached = getCachedIntegerType(width, signedness, context)) @@ -783,10 +779,6 @@ IntegerType IntegerType::get(MLIRContext *context, unsigned width, return Base::get(context, width, signedness); } -IntegerType IntegerType::getChecked(Location location, unsigned width) { - return getChecked(location, width, IntegerType::Signless); -} - IntegerType IntegerType::getChecked(Location location, unsigned width, SignednessSemantics signedness) { if (auto cached = diff --git a/mlir/lib/IR/TypeDetail.h b/mlir/lib/IR/TypeDetail.h index cd027cde722b..1973fdd60f00 100644 --- a/mlir/lib/IR/TypeDetail.h +++ b/mlir/lib/IR/TypeDetail.h @@ -27,31 +27,6 @@ class MLIRContext; namespace detail { -/// Opaque Type Storage and Uniquing. -struct OpaqueTypeStorage : public TypeStorage { - OpaqueTypeStorage(Identifier dialectNamespace, StringRef typeData) - : dialectNamespace(dialectNamespace), typeData(typeData) {} - - /// The hash key used for uniquing. - using KeyTy = std::pair<Identifier, StringRef>; - bool operator==(const KeyTy &key) const { - return key == KeyTy(dialectNamespace, typeData); - } - - static OpaqueTypeStorage *construct(TypeStorageAllocator &allocator, - const KeyTy &key) { - StringRef tyData = allocator.copyInto(key.second); - return new (allocator.allocate<OpaqueTypeStorage>()) - OpaqueTypeStorage(key.first, tyData); - } - - // The dialect namespace. - Identifier dialectNamespace; - - // The parser type data for this opaque type. - StringRef typeData; -}; - /// Integer Type Storage and Uniquing. struct IntegerTypeStorage : public TypeStorage { IntegerTypeStorage(unsigned width, @@ -290,24 +265,6 @@ struct UnrankedMemRefTypeStorage : public BaseMemRefTypeStorage { } }; -/// Complex Type Storage. -struct ComplexTypeStorage : public TypeStorage { - ComplexTypeStorage(Type elementType) : elementType(elementType) {} - - /// The hash key used for uniquing. - using KeyTy = Type; - bool operator==(const KeyTy &key) const { return key == elementType; } - - /// Construction. - static ComplexTypeStorage *construct(TypeStorageAllocator &allocator, - Type elementType) { - return new (allocator.allocate<ComplexTypeStorage>()) - ComplexTypeStorage(elementType); - } - - Type elementType; -}; - /// A type representing a collection of other types. struct TupleTypeStorage final : public TypeStorage, _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits