From 9ecf6887d6e488da2ec80aad31dbd96704bdf81d Mon Sep 17 00:00:00 2001 From: Stella Laurenzo Date: Mon, 29 Apr 2019 17:05:05 -0700 Subject: [PATCH] Add an "any" quantized type, which contains the underlying type correlation but does not specify any mapping. Also fixes some comments that were stale after the last syntax upgrade. -- PiperOrigin-RevId: 245853462 --- mlir/include/mlir/Quantization/QuantTypes.h | 68 ++++++++--- mlir/lib/Quantization/IR/QuantOps.cpp | 3 +- mlir/lib/Quantization/IR/QuantTypes.cpp | 89 ++++++++++++-- mlir/lib/Quantization/IR/TypeDetail.h | 50 ++++++++ mlir/lib/Quantization/IR/TypeParser.cpp | 167 ++++++++++++++++++++------ mlir/test/Quantization/parse-any-invalid.mlir | 61 ++++++++++ mlir/test/Quantization/parse-any.mlir | 28 +++++ 7 files changed, 401 insertions(+), 65 deletions(-) create mode 100644 mlir/test/Quantization/parse-any-invalid.mlir create mode 100644 mlir/test/Quantization/parse-any.mlir diff --git a/mlir/include/mlir/Quantization/QuantTypes.h b/mlir/include/mlir/Quantization/QuantTypes.h index 6e9e97d..2804c92 100644 --- a/mlir/include/mlir/Quantization/QuantTypes.h +++ b/mlir/include/mlir/Quantization/QuantTypes.h @@ -34,6 +34,7 @@ class QuantizedIntegerType; namespace detail { struct QuantizedTypeStorage; +struct AnyQuantizedTypeStorage; struct UniformQuantizedTypeStorage; struct UniformQuantizedPerAxisTypeStorage; @@ -41,7 +42,8 @@ struct UniformQuantizedPerAxisTypeStorage; namespace QuantizationTypes { enum Kind { - UniformQuantized = Type::FIRST_QUANTIZATION_TYPE, + Any = Type::FIRST_QUANTIZATION_TYPE, + UniformQuantized, UniformQuantizedPerAxis, LAST_USED_QUANTIZATION_TYPE = UniformQuantizedPerAxis, }; @@ -80,8 +82,8 @@ public: /// Support method to enable LLVM-style type casting. static bool kindof(unsigned kind) { - return kind == QuantizationTypes::UniformQuantized || - kind == QuantizationTypes::UniformQuantizedPerAxis; + return kind >= Type::FIRST_QUANTIZATION_TYPE && + kind <= QuantizationTypes::LAST_USED_QUANTIZATION_TYPE; } /// Gets the minimum possible stored by a storageType. storageTypeMin must @@ -145,24 +147,24 @@ public: /// primitive type or a container type whose element type equals this /// QuantizedType's expressed type. /// Examples of compatible candidateExpressedType: - /// !quant<"uniform[i8:f32]{1.0}"> =~ f32 - /// !quant<"uniform[i8:f32]{1.0}"> =~ tensor<4xf32> + /// !quant.uniform =~ f32 + /// !quant.uniform =~ tensor<4xf32> bool isCompatibleExpressedType(Type candidateExpressedType); /// Returns the element type as a QuantizedType or nullptr if it is not /// a quantized type. If the type is primitive, returns that. If it is a /// container (vector/tensor), return the element type. /// Examples: - /// !quant<"uniform[i8:f32]{1.0}"> -> !quant<"uniform[i8:f32]{1.0}"> - /// tensor<4x!quant<"uniform[i8:f32]{1.0}"> -> quant<"uniform[i8:f32]{1.0}"> + /// !quant.uniform -> !quant.uniform + /// tensor<4x!quant.uniform -> quant.uniform static QuantizedType getQuantizedElementType(Type primitiveOrContainerType); /// Casts from a type based on the storageType to a corresponding type based /// on this type (returns nullptr if the cast is not valid). /// Examples: - /// i8 -> !quant<"uniform[i8:f32]{1.0}"> - /// tensor<4xi8> -> tensor<4x!quant<"uniform[i8:f32]{1.0}">> - /// vector<4xi8> -> vector<4x!quant<"uniform[i8:f32]{1.0}">> + /// i8 -> !quant.uniform + /// tensor<4xi8> -> tensor<4x!quant.uniform> + /// vector<4xi8> -> vector<4x!quant.uniform> Type castFromStorageType(Type candidateType); /// Casts from a type based on a QuantizedType to a corresponding type based @@ -173,9 +175,9 @@ public: /// Casts from a type based on the expressedType to a corresponding type based /// on this type (returns nullptr if the cast is not valid). /// Examples: - /// f32 -> !quant<"uniform[i8:f32]{1.0}"> - /// tensor<4xf32> -> tensor<4x!quant<"uniform[i8:f32]{1.0}">> - /// vector<4xf32> -> vector<4x!quant<"uniform[i8:f32]{1.0}">> + /// f32 -> !quant.uniform + /// tensor<4xf32> -> tensor<4x!quant.uniform> + /// vector<4xf32> -> vector<4x!quant.uniform> Type castFromExpressedType(Type candidateType); /// Casts from a type based on QuantizedType to a corresponding type based @@ -187,11 +189,49 @@ public: /// based on storageType by way of this QuantizedType. Equivalent to: /// QuantizedType::castToStorageType(castFromExpressedType(candidateType)) /// (but with validity checks). - /// Example (for this = !quant<"uniform[i8:f32]{1.0}">): + /// Example (for this = !quant.uniform): /// tensor<4xf32> -> tensor<4xi8> Type castExpressedToStorageType(Type candidateType); }; +/// A quantized type that maps storage to/from expressed types in an +/// unspecified way. +/// +/// Typical syntax: +/// quant.any +/// quant.any +/// quant.any> +/// +/// Note that for the any type, the expressed type is optional. +class AnyQuantizedType + : public Type::TypeBase { +public: + using Base::Base; + + /// Support method to enable LLVM-style type casting. + static bool kindof(unsigned kind) { return kind == QuantizationTypes::Any; } + + /// Gets an instance of the type with all parameters specified but not + /// checked. + static AnyQuantizedType get(unsigned flags, Type storageType, + Type expressedType, int64_t storageTypeMin, + int64_t storageTypeMax); + + /// Gets an instance of the type with all specified parameters checked. + /// Returns a nullptr convertible type on failure. + static AnyQuantizedType getChecked(unsigned flags, Type storageType, + Type expressedType, int64_t storageTypeMin, + int64_t storageTypeMax, Location location); + + /// Verifies construction invariants and issues errors/warnings. + static LogicalResult + verifyConstructionInvariants(llvm::Optional loc, + MLIRContext *context, unsigned flags, + Type storageType, Type expressedType, + int64_t storageTypeMin, int64_t storageTypeMax); +}; + /// Represents a family of uniform, quantized types. /// /// Each instance of this type expresses a mapping between real values (most diff --git a/mlir/lib/Quantization/IR/QuantOps.cpp b/mlir/lib/Quantization/IR/QuantOps.cpp index a183a24..046ad85 100644 --- a/mlir/lib/Quantization/IR/QuantOps.cpp +++ b/mlir/lib/Quantization/IR/QuantOps.cpp @@ -70,7 +70,8 @@ void StorageCastOp::getCanonicalizationPatterns( QuantizationDialect::QuantizationDialect(MLIRContext *context) : Dialect(/*name=*/"quant", context) { - addTypes(); + addTypes(); addOperations< #define GET_OP_LIST #include "mlir/Quantization/QuantOps.cpp.inc" diff --git a/mlir/lib/Quantization/IR/QuantTypes.cpp b/mlir/lib/Quantization/IR/QuantTypes.cpp index 9311f2c..efe0924 100644 --- a/mlir/lib/Quantization/IR/QuantTypes.cpp +++ b/mlir/lib/Quantization/IR/QuantTypes.cpp @@ -35,16 +35,6 @@ LogicalResult QuantizedType::verifyConstructionInvariants( llvm::Optional loc, MLIRContext *context, unsigned flags, Type storageType, Type expressedType, int64_t storageTypeMin, int64_t storageTypeMax) { - // Verify that the expressed type is floating point. - // If this restriction is ever eliminated, the parser/printer must be - // extended. - if (!expressedType.isa()) { - if (loc) { - context->emitError(*loc, "expressed type must be floating point"); - } - return failure(); - } - // Verify that the storage type is integral. // This restriction may be lifted at some point in favor of using bf16 // or f16 as exact representations on hardware where that is advantageous. @@ -228,6 +218,47 @@ Type QuantizedType::castExpressedToStorageType(Type candidateType) { return QuantizedType::castToStorageType(expressedQuantizedType); } +AnyQuantizedType AnyQuantizedType::get(unsigned flags, Type storageType, + Type expressedType, + int64_t storageTypeMin, + int64_t storageTypeMax) { + return Base::get(storageType.getContext(), QuantizationTypes::Any, flags, + storageType, expressedType, storageTypeMin, storageTypeMax); +} + +AnyQuantizedType AnyQuantizedType::getChecked(unsigned flags, Type storageType, + Type expressedType, + int64_t storageTypeMin, + int64_t storageTypeMax, + Location location) { + return Base::getChecked(location, storageType.getContext(), + QuantizationTypes::Any, flags, storageType, + expressedType, storageTypeMin, storageTypeMax); +} + +LogicalResult AnyQuantizedType::verifyConstructionInvariants( + llvm::Optional loc, MLIRContext *context, unsigned flags, + Type storageType, Type expressedType, int64_t storageTypeMin, + int64_t storageTypeMax) { + if (failed(QuantizedType::verifyConstructionInvariants( + loc, context, flags, storageType, expressedType, storageTypeMin, + storageTypeMax))) { + return failure(); + } + + // Verify that the expressed type is floating point. + // If this restriction is ever eliminated, the parser/printer must be + // extended. + if (expressedType && !expressedType.isa()) { + if (loc) { + context->emitError(*loc, "expressed type must be floating point"); + } + return failure(); + } + + return success(); +} + UniformQuantizedType UniformQuantizedType::get(unsigned flags, Type storageType, Type expressedType, double scale, int64_t zeroPoint, @@ -260,6 +291,25 @@ LogicalResult UniformQuantizedType::verifyConstructionInvariants( return failure(); } + // Uniform quantization requires fully expressed parameters, including + // expressed type. + if (!expressedType) { + if (loc) { + context->emitError(*loc, "uniform quantization requires expressed type"); + } + return failure(); + } + + // Verify that the expressed type is floating point. + // If this restriction is ever eliminated, the parser/printer must be + // extended. + if (!expressedType.isa()) { + if (loc) { + context->emitError(*loc, "expressed type must be floating point"); + } + return failure(); + } + // Verify scale. if (scale <= 0.0 || std::isinf(scale) || std::isnan(scale)) { if (loc) { @@ -311,6 +361,25 @@ LogicalResult UniformQuantizedPerAxisType::verifyConstructionInvariants( return failure(); } + // Uniform quantization requires fully expressed parameters, including + // expressed type. + if (!expressedType) { + if (loc) { + context->emitError(*loc, "uniform quantization requires expressed type"); + } + return failure(); + } + + // Verify that the expressed type is floating point. + // If this restriction is ever eliminated, the parser/printer must be + // extended. + if (!expressedType.isa()) { + if (loc) { + context->emitError(*loc, "expressed type must be floating point"); + } + return failure(); + } + // Ensure that the number of scales and zeroPoints match. if (scales.size() != zeroPoints.size()) { if (loc) { diff --git a/mlir/lib/Quantization/IR/TypeDetail.h b/mlir/lib/Quantization/IR/TypeDetail.h index d3db91e..66aa422 100644 --- a/mlir/lib/Quantization/IR/TypeDetail.h +++ b/mlir/lib/Quantization/IR/TypeDetail.h @@ -51,6 +51,56 @@ struct QuantizedTypeStorage : public mlir::TypeStorage { int64_t storageTypeMax; }; +struct AnyQuantizedTypeStorage : public QuantizedTypeStorage { + struct KeyTy { + KeyTy(unsigned flags, Type storageType, Type expressedType, + int64_t storageTypeMin, int64_t storageTypeMax) + : flags(flags), storageType(storageType), expressedType(expressedType), + storageTypeMin(storageTypeMin), storageTypeMax(storageTypeMax) {} + unsigned flags; + Type storageType; + Type expressedType; + int64_t storageTypeMin; + int64_t storageTypeMax; + + // Check for equality of two structures that share KeyTy data members + // (by name). + template + static bool genericIsEqual(const T &lhs, const U &rhs) { + return lhs.flags == rhs.flags && lhs.storageType == rhs.storageType && + lhs.expressedType == rhs.expressedType && + lhs.storageTypeMin == rhs.storageTypeMin && + lhs.storageTypeMax == rhs.storageTypeMax; + } + + bool operator==(const KeyTy &other) const { + return genericIsEqual(*this, other); + } + + unsigned getHashValue() const { + return llvm::hash_combine(flags, storageType, expressedType, + storageTypeMin, storageTypeMax); + } + }; + + AnyQuantizedTypeStorage(const KeyTy &key) + : QuantizedTypeStorage(key.flags, key.storageType, key.expressedType, + key.storageTypeMin, key.storageTypeMax) {} + + bool operator==(const KeyTy &key) const { + return KeyTy::genericIsEqual(*this, key); + } + + /// Construction. + static AnyQuantizedTypeStorage *construct(TypeStorageAllocator &allocator, + const KeyTy &key) { + return new (allocator.allocate()) + AnyQuantizedTypeStorage(key); + } + + static unsigned hashKey(const KeyTy &key) { return key.getHashValue(); } +}; + struct UniformQuantizedTypeStorage : public QuantizedTypeStorage { struct KeyTy { KeyTy(unsigned flags, Type storageType, Type expressedType, double scale, diff --git a/mlir/lib/Quantization/IR/TypeParser.cpp b/mlir/lib/Quantization/IR/TypeParser.cpp index cd26716..62f9c98 100644 --- a/mlir/lib/Quantization/IR/TypeParser.cpp +++ b/mlir/lib/Quantization/IR/TypeParser.cpp @@ -262,8 +262,11 @@ private: } // Parsers. + Type parseAnyType(); Type parseUniformType(); IntegerType parseStorageType(bool &isSigned); + bool parseStorageRange(IntegerType storageType, bool isSigned, + int64_t &storageTypeMin, int64_t &storageTypeMax); FloatType parseExpressedType(); bool parseQuantParams(double &scale, int64_t &zeroPoint); @@ -289,6 +292,11 @@ Type TypeParser::parseType() { if (!result) { return nullptr; } + } else if (typeNameSpelling == "any") { + result = parseAnyType(); + if (!result) { + return nullptr; + } } else { return (emitError("unknown quantized type " + typeNameSpelling), nullptr); } @@ -306,14 +314,67 @@ Type TypeParser::parseType() { /// Parses a UniformQuantizedType. /// +/// uniform_per_layer ::= `any<` storage-spec (expressed-type-spec)?`>` +/// storage-spec ::= storage-type (`<` storage-range `>`)? +/// storage-range ::= integer-literal `:` integer-literal +/// storage-type ::= (`i` | `u`) integer-literal +/// expressed-type-spec ::= `:` `f` integer-literal +Type TypeParser::parseAnyType() { + IntegerType storageType; + FloatType expressedType; + unsigned typeFlags = 0; + int64_t storageTypeMin; + int64_t storageTypeMax; + + // Type specification. + if (!consumeIf(TokenKind::l_angle)) { + return (emitError("unrecognized token: " + curToken.spelling), nullptr); + } + + // Storage type. + bool isSigned = false; + storageType = parseStorageType(isSigned); + if (!storageType) { + return nullptr; + } + if (isSigned) { + typeFlags |= QuantizationFlags::Signed; + } + + // Storage type range. + if (parseStorageRange(storageType, isSigned, storageTypeMin, + storageTypeMax)) { + return nullptr; + } + + // Optional expressed type. + if (consumeIf(TokenKind::colon)) { + expressedType = parseExpressedType(); + if (!expressedType) { + return nullptr; + } + } + + if (!consumeIf(TokenKind::r_angle)) { + return (emitError("unrecognized token: " + curToken.spelling), nullptr); + } + + return AnyQuantizedType::getChecked(typeFlags, storageType, expressedType, + storageTypeMin, storageTypeMax, location); +} + +/// Parses a UniformQuantizedType. +/// /// uniform_type ::= uniform_per_layer /// | uniform_per_axis -/// uniform_per_layer ::= `uniform<` storage-spec `,` scale-zero `>` -/// uniform_per_axis ::= `uniform<` storage-spec axis-spec -/// `,` scale-zero-list `>` +/// uniform_per_layer ::= `uniform<` storage-spec expressed-type-spec +/// `,` scale-zero `>` +/// uniform_per_axis ::= `uniform<` storage-spec expressed-type-spec +/// axis-spec `,` scale-zero-list `>` /// storage-spec ::= storage-type (`<` storage-range `>`)? /// storage-range ::= integer-literal `:` integer-literal /// storage-type ::= (`i` | `u`) integer-literal +/// expressed-type-spec ::= `:` `f` integer-literal /// axis-spec ::= `:` integer-literal /// scale-zero ::= float-literal `:` integer-literal /// scale-zero-list ::= `{` scale-zero (`,` scale-zero)* `}` @@ -344,45 +405,12 @@ Type TypeParser::parseUniformType() { } // Storage type range. - int64_t defaultIntegerMin = QuantizedType::getDefaultMininumForInteger( - isSigned, storageType.getWidth()); - int64_t defaultIntegerMax = QuantizedType::getDefaultMaxinumForInteger( - isSigned, storageType.getWidth()); - if (consumeIf(TokenKind::l_angle)) { - // Explicit storage min and storage max. - if (curToken.kind != TokenKind::integer_literal) { - return (emitError("expected storage type minimum"), nullptr); - } - if (curToken.spelling.getAsInteger(10, storageTypeMin) || - storageTypeMin < defaultIntegerMin) { - return (emitError("illegal storage type minimum: " + curToken.spelling), - nullptr); - } - consumeToken(TokenKind::integer_literal); - - if (!consumeIf(TokenKind::colon)) { - return (emitError("unrecognized token: " + curToken.spelling), nullptr); - } - - if (curToken.kind != TokenKind::integer_literal) { - return (emitError("expected storage type maximum"), nullptr); - } - if (curToken.spelling.getAsInteger(10, storageTypeMax) || - storageTypeMax > defaultIntegerMax) { - return (emitError("illegal storage type maximum: " + curToken.spelling), - nullptr); - } - consumeToken(TokenKind::integer_literal); - - if (!consumeIf(TokenKind::r_angle)) { - return (emitError("unrecognized token: " + curToken.spelling), nullptr); - } - } else { - storageTypeMin = defaultIntegerMin; - storageTypeMax = defaultIntegerMax; + if (parseStorageRange(storageType, isSigned, storageTypeMin, + storageTypeMax)) { + return nullptr; } - // Repr type. + // Expressed type. if (!consumeIf(TokenKind::colon)) { return (emitError("unrecognized token: " + curToken.spelling), nullptr); } @@ -487,6 +515,51 @@ IntegerType TypeParser::parseStorageType(bool &isSigned) { } } +bool TypeParser::parseStorageRange(IntegerType storageType, bool isSigned, + int64_t &storageTypeMin, + int64_t &storageTypeMax) { + + int64_t defaultIntegerMin = QuantizedType::getDefaultMininumForInteger( + isSigned, storageType.getWidth()); + int64_t defaultIntegerMax = QuantizedType::getDefaultMaxinumForInteger( + isSigned, storageType.getWidth()); + if (consumeIf(TokenKind::l_angle)) { + // Explicit storage min and storage max. + if (curToken.kind != TokenKind::integer_literal) { + return (emitError("expected storage type minimum"), true); + } + if (curToken.spelling.getAsInteger(10, storageTypeMin) || + storageTypeMin < defaultIntegerMin) { + return (emitError("illegal storage type minimum: " + curToken.spelling), + true); + } + consumeToken(TokenKind::integer_literal); + + if (!consumeIf(TokenKind::colon)) { + return (emitError("unrecognized token: " + curToken.spelling), true); + } + + if (curToken.kind != TokenKind::integer_literal) { + return (emitError("expected storage type maximum"), true); + } + if (curToken.spelling.getAsInteger(10, storageTypeMax) || + storageTypeMax > defaultIntegerMax) { + return (emitError("illegal storage type maximum: " + curToken.spelling), + true); + } + consumeToken(TokenKind::integer_literal); + + if (!consumeIf(TokenKind::r_angle)) { + return (emitError("unrecognized token: " + curToken.spelling), true); + } + } else { + storageTypeMin = defaultIntegerMin; + storageTypeMax = defaultIntegerMax; + } + + return false; +} + FloatType TypeParser::parseExpressedType() { // Expect an alpha_ident followed by integer literal that we concat back // together. @@ -601,6 +674,17 @@ static void printQuantParams(double scale, int64_t zeroPoint, } /// Helper that prints a UniformQuantizedType. +static void printAnyQuantizedType(AnyQuantizedType type, raw_ostream &out) { + out << "any<"; + printStorageType(type, out); + if (type.getExpressedType()) { + out << ":"; + printExpressedType(type, out); + } + out << ">"; +} + +/// Helper that prints a UniformQuantizedType. static void printUniformQuantizedType(UniformQuantizedType type, raw_ostream &out) { out << "uniform<"; @@ -643,6 +727,9 @@ void QuantizationDialect::printType(Type type, raw_ostream &os) const { switch (type.getKind()) { default: llvm_unreachable("Unhandled quantized type"); + case QuantizationTypes::Any: + printAnyQuantizedType(type.cast(), os); + break; case QuantizationTypes::UniformQuantized: printUniformQuantizedType(type.cast(), os); break; diff --git a/mlir/test/Quantization/parse-any-invalid.mlir b/mlir/test/Quantization/parse-any-invalid.mlir new file mode 100644 index 0000000..caff5ec --- /dev/null +++ b/mlir/test/Quantization/parse-any-invalid.mlir @@ -0,0 +1,61 @@ +// RUN: mlir-opt %s -split-input-file -verify + +// ----- +// Unrecognized token: missing storage type maximum +// expected-error@+1 {{unrecognized token: >}} +!qalias = type !quant.any:f32> + +// ----- +// Unrecognized token: missing closing angle bracket +// expected-error@+1 {{unrecognized token: :}} +!qalias = type !quant<"any"> + +// ----- +// Unrecognized token: missing type colon +// expected-error@+1 {{unrecognized token: f}} +!qalias = type !quant.anyf32> + +// ----- +// Unrecognized storage type: illegal prefix +// expected-error@+1 {{illegal storage type prefix: int}} +!qalias = type !quant.any:f32> + +// ----- +// Unrecognized storage type: no width +// expected-error@+1 {{expected storage type width}} +!qalias = type !quant.any:f32> + +// ----- +// Unrecognized storage type: storage size > 32 +// expected-error@+1 {{illegal storage type size: 33}} +!qalias = type !quant.any + +// ----- +// Unrecognized storage type: storage size < 0 +// expected-error@+1 {{illegal storage type size: -1}} +!qalias = type !quant.any:f32> + +// ----- +// Unrecognized storage type: storage size == 0 +// expected-error@+1 {{illegal storage type size: 0}} +!qalias = type !quant.any:f32> + +// ----- +// Illegal storage min/max: max - min < 0 +// expected-error@+1 {{illegal storage min and storage max: (2:1)}} +!qalias = type !quant.any:f32> + +// ----- +// Illegal storage min/max: max - min == 0 +// expected-error@+1 {{illegal storage min and storage max: (1:1)}} +!qalias = type !quant.any:f32> + +// ----- +// Illegal storage min/max: max > defaultMax +// expected-error@+1 {{illegal storage type maximum: 9}} +!qalias = type !quant.any:f32> + +// ----- +// Illegal storage min/max: min < defaultMin +// expected-error@+1 {{illegal storage type minimum: -9}} +!qalias = type !quant.any:f32> diff --git a/mlir/test/Quantization/parse-any.mlir b/mlir/test/Quantization/parse-any.mlir new file mode 100644 index 0000000..90976e8 --- /dev/null +++ b/mlir/test/Quantization/parse-any.mlir @@ -0,0 +1,28 @@ +// RUN: mlir-opt %s -split-input-file | FileCheck %s + +// ----- +// CHECK-LABEL: parseFullySpecified +// CHECK: !quant.any:f32> +!qalias = type !quant.any:f32> +func @parseFullySpecified() -> !qalias { + %0 = "foo"() : () -> !qalias + return %0 : !qalias +} + +// ----- +// CHECK-LABEL: parseNoExpressedType +// CHECK: !quant.any> +!qalias = type !quant.any> +func @parseNoExpressedType() -> !qalias { + %0 = "foo"() : () -> !qalias + return %0 : !qalias +} + +// ----- +// CHECK-LABEL: parseOnlyStorageType +// CHECK: !quant.any +!qalias = type !quant.any +func @parseOnlyStorageType() -> !qalias { + %0 = "foo"() : () -> !qalias + return %0 : !qalias +} -- 2.7.4