29 #include "llvm/ADT/ArrayRef.h"
30 #include "llvm/ADT/STLExtras.h"
31 #include "llvm/ADT/SmallVector.h"
32 #include "llvm/ADT/SmallVectorExtras.h"
33 #include "llvm/Support/FormatVariadic.h"
44 return values[0].getDefiningOp<arith::ConstantIndexOp>().value();
47 return cast<IntegerAttr>(attr[0]).getInt();
50 return (*attr.getAsValueRange<IntegerAttr>().begin()).getZExtValue();
53 auto attr = foldResults[0].dyn_cast<
Attribute>();
65 if (
auto vectorType = dyn_cast<VectorType>(type))
66 return vectorType.getNumElements() * vectorType.getElementTypeBitWidth();
76 matchAndRewrite(vector::ShapeCastOp shapeCastOp, OpAdaptor adaptor,
78 Type dstType = getTypeConverter()->convertType(shapeCastOp.getType());
84 if (dstType == adaptor.getSource().getType() ||
85 shapeCastOp.getResultVectorType().getNumElements() == 1) {
86 rewriter.
replaceOp(shapeCastOp, adaptor.getSource());
95 struct VectorBitcastConvert final
100 matchAndRewrite(vector::BitCastOp bitcastOp, OpAdaptor adaptor,
102 Type dstType = getTypeConverter()->convertType(bitcastOp.getType());
106 if (dstType == adaptor.getSource().getType()) {
107 rewriter.
replaceOp(bitcastOp, adaptor.getSource());
114 Type srcType = adaptor.getSource().getType();
118 llvm::formatv(
"different source ({0}) and target ({1}) bitwidth",
123 adaptor.getSource());
128 struct VectorBroadcastConvert final
133 matchAndRewrite(vector::BroadcastOp castOp, OpAdaptor adaptor,
136 getTypeConverter()->convertType(castOp.getResultVectorType());
140 if (isa<spirv::ScalarType>(resultType)) {
141 rewriter.
replaceOp(castOp, adaptor.getSource());
146 adaptor.getSource());
153 struct VectorExtractOpConvert final
158 matchAndRewrite(vector::ExtractOp extractOp, OpAdaptor adaptor,
160 if (extractOp.hasDynamicPosition())
163 Type dstType = getTypeConverter()->convertType(extractOp.getType());
167 if (isa<spirv::ScalarType>(adaptor.getVector().getType())) {
168 rewriter.
replaceOp(extractOp, adaptor.getVector());
174 extractOp, adaptor.getVector(), id);
179 struct VectorExtractStridedSliceOpConvert final
184 matchAndRewrite(vector::ExtractStridedSliceOp extractOp, OpAdaptor adaptor,
186 Type dstType = getTypeConverter()->convertType(extractOp.getType());
196 Value srcVector = adaptor.getOperands().front();
199 if (isa<spirv::ScalarType>(dstType)) {
206 std::iota(indices.begin(), indices.end(), offset);
209 extractOp, dstType, srcVector, srcVector,
216 template <
class SPIRVFMAOp>
221 matchAndRewrite(vector::FMAOp fmaOp, OpAdaptor adaptor,
223 Type dstType = getTypeConverter()->convertType(fmaOp.getType());
227 adaptor.getRhs(), adaptor.getAcc());
232 struct VectorInsertOpConvert final
237 matchAndRewrite(vector::InsertOp insertOp, OpAdaptor adaptor,
239 if (isa<VectorType>(insertOp.getSourceType()))
241 if (!getTypeConverter()->convertType(insertOp.getDestVectorType()))
243 "unsupported dest vector type");
246 if (insertOp.getSourceType().isIntOrFloat() &&
247 insertOp.getDestVectorType().getNumElements() == 1) {
248 rewriter.
replaceOp(insertOp, adaptor.getSource());
254 insertOp, adaptor.getSource(), adaptor.getDest(), id);
259 struct VectorExtractElementOpConvert final
264 matchAndRewrite(vector::ExtractElementOp extractOp, OpAdaptor adaptor,
266 Type resultType = getTypeConverter()->convertType(extractOp.getType());
270 if (isa<spirv::ScalarType>(adaptor.getVector().getType())) {
271 rewriter.
replaceOp(extractOp, adaptor.getVector());
278 extractOp, resultType, adaptor.getVector(),
282 extractOp, resultType, adaptor.getVector(), adaptor.getPosition());
287 struct VectorInsertElementOpConvert final
292 matchAndRewrite(vector::InsertElementOp insertOp, OpAdaptor adaptor,
294 Type vectorType = getTypeConverter()->convertType(insertOp.getType());
298 if (isa<spirv::ScalarType>(vectorType)) {
299 rewriter.
replaceOp(insertOp, adaptor.getSource());
306 insertOp, adaptor.getSource(), adaptor.getDest(),
307 cstPos.getSExtValue());
310 insertOp, vectorType, insertOp.getDest(), adaptor.getSource(),
311 adaptor.getPosition());
316 struct VectorInsertStridedSliceOpConvert final
321 matchAndRewrite(vector::InsertStridedSliceOp insertOp, OpAdaptor adaptor,
323 Value srcVector = adaptor.getOperands().front();
324 Value dstVector = adaptor.getOperands().back();
331 if (isa<spirv::ScalarType>(srcVector.
getType())) {
332 assert(!isa<spirv::ScalarType>(dstVector.
getType()));
334 insertOp, dstVector.
getType(), srcVector, dstVector,
339 uint64_t totalSize = cast<VectorType>(dstVector.getType()).getNumElements();
340 uint64_t insertSize =
341 cast<VectorType>(srcVector.
getType()).getNumElements();
344 std::iota(indices.begin(), indices.end(), 0);
345 std::iota(indices.begin() + offset, indices.begin() + offset + insertSize,
349 insertOp, dstVector.getType(), dstVector, srcVector,
357 vector::ReductionOp reduceOp, vector::ReductionOp::Adaptor adaptor,
359 int numElements =
static_cast<int>(srcVectorType.getDimSize(0));
361 values.reserve(numElements + (adaptor.getAcc() ? 1 : 0));
364 for (
int i = 0; i < numElements; ++i) {
365 values.push_back(rewriter.
create<spirv::CompositeExtractOp>(
366 loc, srcVectorType.getElementType(), adaptor.getVector(),
369 if (
Value acc = adaptor.getAcc())
370 values.push_back(acc);
375 struct ReductionRewriteInfo {
380 FailureOr<ReductionRewriteInfo>
static getReductionInfo(
381 vector::ReductionOp op, vector::ReductionOp::Adaptor adaptor,
387 auto srcVectorType = dyn_cast<VectorType>(adaptor.getVector().getType());
388 if (!srcVectorType || srcVectorType.getRank() != 1)
392 extractAllElements(op, adaptor, srcVectorType, rewriter);
394 return ReductionRewriteInfo{resultType, std::move(extractedElements)};
397 template <
typename SPIRVUMaxOp,
typename SPIRVUMinOp,
typename SPIRVSMaxOp,
398 typename SPIRVSMinOp>
403 matchAndRewrite(vector::ReductionOp reduceOp, OpAdaptor adaptor,
406 getReductionInfo(reduceOp, adaptor, rewriter, *getTypeConverter());
407 if (failed(reductionInfo))
410 auto [resultType, extractedElements] = *reductionInfo;
412 Value result = extractedElements.front();
413 for (
Value next : llvm::drop_begin(extractedElements)) {
414 switch (reduceOp.getKind()) {
416 #define INT_AND_FLOAT_CASE(kind, iop, fop) \
417 case vector::CombiningKind::kind: \
418 if (llvm::isa<IntegerType>(resultType)) { \
419 result = rewriter.create<spirv::iop>(loc, resultType, result, next); \
421 assert(llvm::isa<FloatType>(resultType)); \
422 result = rewriter.create<spirv::fop>(loc, resultType, result, next); \
426 #define INT_OR_FLOAT_CASE(kind, fop) \
427 case vector::CombiningKind::kind: \
428 result = rewriter.create<fop>(loc, resultType, result, next); \
438 case vector::CombiningKind::AND:
439 case vector::CombiningKind::OR:
440 case vector::CombiningKind::XOR:
445 #undef INT_AND_FLOAT_CASE
446 #undef INT_OR_FLOAT_CASE
454 template <
typename SPIRVFMaxOp,
typename SPIRVFMinOp>
455 struct VectorReductionFloatMinMax final
460 matchAndRewrite(vector::ReductionOp reduceOp, OpAdaptor adaptor,
463 getReductionInfo(reduceOp, adaptor, rewriter, *getTypeConverter());
464 if (failed(reductionInfo))
467 auto [resultType, extractedElements] = *reductionInfo;
469 Value result = extractedElements.front();
470 for (
Value next : llvm::drop_begin(extractedElements)) {
471 switch (reduceOp.getKind()) {
473 #define INT_OR_FLOAT_CASE(kind, fop) \
474 case vector::CombiningKind::kind: \
475 result = rewriter.create<fop>(loc, resultType, result, next); \
486 #undef INT_OR_FLOAT_CASE
499 matchAndRewrite(vector::SplatOp op, OpAdaptor adaptor,
501 Type dstType = getTypeConverter()->convertType(op.getType());
504 if (isa<spirv::ScalarType>(dstType)) {
505 rewriter.
replaceOp(op, adaptor.getInput());
507 auto dstVecType = cast<VectorType>(dstType);
517 struct VectorShuffleOpConvert final
522 matchAndRewrite(vector::ShuffleOp shuffleOp, OpAdaptor adaptor,
524 VectorType oldResultType = shuffleOp.getResultVectorType();
525 Type newResultType = getTypeConverter()->convertType(oldResultType);
528 "unsupported result vector type");
530 auto mask = llvm::to_vector_of<int32_t>(shuffleOp.getMask());
532 VectorType oldV1Type = shuffleOp.getV1VectorType();
533 VectorType oldV2Type = shuffleOp.getV2VectorType();
537 if (oldV1Type.getNumElements() > 1 && oldV2Type.getNumElements() > 1 &&
538 oldResultType.getNumElements() > 1) {
540 shuffleOp, newResultType, adaptor.getV1(), adaptor.getV2(),
548 auto getElementAtIdx = [&rewriter, loc = shuffleOp.getLoc()](
550 if (
auto vecTy = dyn_cast<VectorType>(scalarOrVec.getType()))
551 return rewriter.
create<spirv::CompositeExtractOp>(loc, scalarOrVec,
554 assert(idx == 0 &&
"Invalid scalar element index");
558 int32_t numV1Elems = oldV1Type.getNumElements();
560 for (
auto [shuffleIdx, newOperand] : llvm::zip_equal(mask, newOperands)) {
561 Value vec = adaptor.getV1();
562 int32_t elementIdx = shuffleIdx;
563 if (elementIdx >= numV1Elems) {
564 vec = adaptor.getV2();
565 elementIdx -= numV1Elems;
568 newOperand = getElementAtIdx(vec, elementIdx);
572 if (newOperands.size() == 1) {
573 rewriter.
replaceOp(shuffleOp, newOperands.front());
578 shuffleOp, newResultType, newOperands);
583 struct VectorInterleaveOpConvert final
588 matchAndRewrite(vector::InterleaveOp interleaveOp, OpAdaptor adaptor,
591 VectorType oldResultType = interleaveOp.getResultVectorType();
592 Type newResultType = getTypeConverter()->convertType(oldResultType);
595 "unsupported result vector type");
598 VectorType sourceType = interleaveOp.getSourceVectorType();
599 int n = sourceType.getNumElements();
605 Value newOperands[] = {adaptor.getLhs(), adaptor.getRhs()};
607 interleaveOp, newResultType, newOperands);
611 auto seq = llvm::seq<int64_t>(2 * n);
612 auto indices = llvm::map_to_vector(
613 seq, [n](
int i) {
return (i % 2 ? n : 0) + i / 2; });
617 interleaveOp, newResultType, adaptor.getLhs(), adaptor.getRhs(),
624 struct VectorDeinterleaveOpConvert final
629 matchAndRewrite(vector::DeinterleaveOp deinterleaveOp, OpAdaptor adaptor,
633 VectorType oldResultType = deinterleaveOp.getResultVectorType();
634 Type newResultType = getTypeConverter()->convertType(oldResultType);
637 "unsupported result vector type");
639 Location loc = deinterleaveOp->getLoc();
642 Value sourceVector = adaptor.getSource();
643 VectorType sourceType = deinterleaveOp.getSourceVectorType();
644 int n = sourceType.getNumElements();
650 auto elem0 = rewriter.
create<spirv::CompositeExtractOp>(
653 auto elem1 = rewriter.
create<spirv::CompositeExtractOp>(
656 rewriter.
replaceOp(deinterleaveOp, {elem0, elem1});
661 auto seqEven = llvm::seq<int64_t>(n / 2);
663 llvm::map_to_vector(seqEven, [](
int i) {
return i * 2; });
666 auto seqOdd = llvm::seq<int64_t>(n / 2);
668 llvm::map_to_vector(seqOdd, [](
int i) {
return i * 2 + 1; });
671 auto shuffleEven = rewriter.
create<spirv::VectorShuffleOp>(
672 loc, newResultType, sourceVector, sourceVector,
675 auto shuffleOdd = rewriter.
create<spirv::VectorShuffleOp>(
676 loc, newResultType, sourceVector, sourceVector,
679 rewriter.
replaceOp(deinterleaveOp, {shuffleEven, shuffleOdd});
684 struct VectorLoadOpConverter final
689 matchAndRewrite(vector::LoadOp loadOp, OpAdaptor adaptor,
691 auto memrefType = loadOp.getMemRefType();
693 dyn_cast_or_null<spirv::StorageClassAttr>(memrefType.getMemorySpace());
696 loadOp,
"expected spirv.storage_class memory space");
698 const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
699 auto loc = loadOp.getLoc();
702 adaptor.getIndices(), loc, rewriter);
705 loadOp,
"failed to get memref element pointer");
707 spirv::StorageClass storageClass = attr.getValue();
708 auto vectorType = loadOp.getVectorType();
710 Value castedAccessChain =
711 rewriter.
create<spirv::BitcastOp>(loc, vectorPtrType, accessChain);
719 struct VectorStoreOpConverter final
724 matchAndRewrite(vector::StoreOp storeOp, OpAdaptor adaptor,
726 auto memrefType = storeOp.getMemRefType();
728 dyn_cast_or_null<spirv::StorageClassAttr>(memrefType.getMemorySpace());
731 storeOp,
"expected spirv.storage_class memory space");
733 const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
734 auto loc = storeOp.getLoc();
737 adaptor.getIndices(), loc, rewriter);
740 storeOp,
"failed to get memref element pointer");
742 spirv::StorageClass storageClass = attr.getValue();
743 auto vectorType = storeOp.getVectorType();
745 Value castedAccessChain =
746 rewriter.
create<spirv::BitcastOp>(loc, vectorPtrType, accessChain);
748 adaptor.getValueToStore());
754 struct VectorReductionToIntDotProd final
758 LogicalResult matchAndRewrite(vector::ReductionOp op,
760 if (op.getKind() != vector::CombiningKind::ADD)
763 auto resultType = dyn_cast<IntegerType>(op.getType());
768 if (!llvm::is_contained({32, 64}, resultBitwidth))
771 VectorType inVecTy = op.getSourceVectorType();
772 if (!llvm::is_contained({4, 3}, inVecTy.getNumElements()) ||
773 inVecTy.getShape().size() != 1 || inVecTy.isScalable())
776 auto mul = op.getVector().getDefiningOp<arith::MulIOp>();
779 op,
"reduction operand is not 'arith.muli'");
781 if (succeeded(handleCase<arith::ExtSIOp, arith::ExtSIOp, spirv::SDotOp,
782 spirv::SDotAccSatOp,
false>(op, mul, rewriter)))
785 if (succeeded(handleCase<arith::ExtUIOp, arith::ExtUIOp, spirv::UDotOp,
786 spirv::UDotAccSatOp,
false>(op, mul, rewriter)))
789 if (succeeded(handleCase<arith::ExtSIOp, arith::ExtUIOp, spirv::SUDotOp,
790 spirv::SUDotAccSatOp,
false>(op, mul, rewriter)))
793 if (succeeded(handleCase<arith::ExtUIOp, arith::ExtSIOp, spirv::SUDotOp,
794 spirv::SUDotAccSatOp,
true>(op, mul, rewriter)))
801 template <
typename LhsExtensionOp,
typename RhsExtensionOp,
typename DotOp,
802 typename DotAccOp,
bool SwapOperands>
803 static LogicalResult handleCase(vector::ReductionOp op, arith::MulIOp mul,
805 auto lhs = mul.getLhs().getDefiningOp<LhsExtensionOp>();
808 Value lhsIn = lhs.getIn();
809 auto lhsInType = cast<VectorType>(lhsIn.
getType());
810 if (!lhsInType.getElementType().isInteger(8))
813 auto rhs = mul.getRhs().getDefiningOp<RhsExtensionOp>();
816 Value rhsIn = rhs.getIn();
817 auto rhsInType = cast<VectorType>(rhsIn.
getType());
818 if (!rhsInType.getElementType().isInteger(8))
821 if (op.getSourceVectorType().getNumElements() == 3) {
822 IntegerType i8Type = rewriter.
getI8Type();
826 lhsIn = rewriter.
create<spirv::CompositeConstructOp>(
828 rhsIn = rewriter.
create<spirv::CompositeConstructOp>(
835 std::swap(lhsIn, rhsIn);
837 if (
Value acc = op.getAcc()) {
849 struct VectorReductionToFPDotProd final
854 matchAndRewrite(vector::ReductionOp op, OpAdaptor adaptor,
856 if (op.getKind() != vector::CombiningKind::ADD)
859 auto resultType = getTypeConverter()->convertType<
FloatType>(op.getType());
863 Value vec = adaptor.getVector();
864 Value acc = adaptor.getAcc();
866 auto vectorType = dyn_cast<VectorType>(vec.
getType());
868 assert(isa<FloatType>(vec.
getType()) &&
869 "Expected the vector to be scalarized");
890 rewriter.
getFloatAttr(vectorType.getElementType(), 1.0);
892 rhs = rewriter.
create<spirv::ConstantOp>(loc, vectorType, oneAttr);
897 Value res = rewriter.
create<spirv::DotOp>(loc, resultType, lhs, rhs);
899 res = rewriter.
create<spirv::FAddOp>(loc, acc, res);
910 matchAndRewrite(vector::StepOp stepOp, OpAdaptor adaptor,
912 const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
918 int64_t numElements = stepOp.getType().getNumElements();
924 if (numElements == 1) {
931 source.reserve(numElements);
932 for (int64_t i = 0; i < numElements; ++i) {
934 Value constOp = rewriter.
create<spirv::ConstantOp>(loc, intType, intAttr);
935 source.push_back(constOp);
944 #define CL_INT_MAX_MIN_OPS \
945 spirv::CLUMaxOp, spirv::CLUMinOp, spirv::CLSMaxOp, spirv::CLSMinOp
947 #define GL_INT_MAX_MIN_OPS \
948 spirv::GLUMaxOp, spirv::GLUMinOp, spirv::GLSMaxOp, spirv::GLSMinOp
950 #define CL_FLOAT_MAX_MIN_OPS spirv::CLFMaxOp, spirv::CLFMinOp
951 #define GL_FLOAT_MAX_MIN_OPS spirv::GLFMaxOp, spirv::GLFMinOp
956 VectorBitcastConvert, VectorBroadcastConvert,
957 VectorExtractElementOpConvert, VectorExtractOpConvert,
958 VectorExtractStridedSliceOpConvert, VectorFmaOpConvert<spirv::GLFmaOp>,
959 VectorFmaOpConvert<spirv::CLFmaOp>, VectorInsertElementOpConvert,
960 VectorInsertOpConvert, VectorReductionPattern<GL_INT_MAX_MIN_OPS>,
961 VectorReductionPattern<CL_INT_MAX_MIN_OPS>,
962 VectorReductionFloatMinMax<CL_FLOAT_MAX_MIN_OPS>,
963 VectorReductionFloatMinMax<GL_FLOAT_MAX_MIN_OPS>, VectorShapeCast,
964 VectorInsertStridedSliceOpConvert, VectorShuffleOpConvert,
965 VectorInterleaveOpConvert, VectorDeinterleaveOpConvert,
966 VectorSplatPattern, VectorLoadOpConverter, VectorStoreOpConverter,
967 VectorStepOpConvert>(typeConverter, patterns.
getContext(),
972 patterns.
add<VectorReductionToFPDotProd>(typeConverter, patterns.
getContext(),
978 patterns.
add<VectorReductionToIntDotProd>(patterns.
getContext());
static Value getZero(OpBuilder &b, Location loc, Type elementType)
Get zero value for an element type.
static uint64_t getFirstIntValue(ValueRange values)
Returns the integer value from the first valid input element, assuming Value inputs are defined by a ...
static int getNumBits(Type type)
Returns the number of bits for the given scalar/vector type.
#define INT_AND_FLOAT_CASE(kind, iop, fop)
#define INT_OR_FLOAT_CASE(kind, fop)
Attributes are known-constant values of operations.
IntegerAttr getIntegerAttr(Type type, int64_t value)
ArrayAttr getI32ArrayAttr(ArrayRef< int32_t > values)
FloatAttr getFloatAttr(Type type, double value)
IntegerType getIntegerType(unsigned width)
This class implements a pattern rewriter for use with ConversionPatterns.
void replaceOp(Operation *op, ValueRange newValues) override
PatternRewriter hook for replacing an operation.
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
OpConversionPattern is a wrapper around ConversionPattern that allows for matching and rewriting agai...
OpConversionPattern(MLIRContext *context, PatternBenefit benefit=1)
Location getLoc()
The source location the operation was defined or derived from.
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Type conversion from builtin types to SPIR-V types for shader interface.
LogicalResult convertType(Type t, SmallVectorImpl< Type > &results) const
Convert the given type.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static PointerType get(Type pointeeType, StorageClass storageClass)
Value getElementPtr(const SPIRVTypeConverter &typeConverter, MemRefType baseType, Value basePtr, ValueRange indices, Location loc, OpBuilder &builder)
Performs the index computation to get to the element at indices of the memory pointed to by basePtr,...
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
void populateVectorToSPIRVPatterns(SPIRVTypeConverter &typeConverter, RewritePatternSet &patterns)
Appends to a pattern list additional patterns for translating Vector Ops to SPIR-V ops.
void populateVectorReductionToSPIRVDotProductPatterns(RewritePatternSet &patterns)
Appends patterns to convert vector reduction of the form:
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
Patterns must specify the root operation name they match against, and can also specify the benefit of...