75#include "llvm/ADT/TypeSwitch.h"
81#define GEN_PASS_DEF_TOSAREDUCETRANSPOSES
82#include "mlir/Dialect/Tosa/Transforms/Passes.h.inc"
95struct TosaReduceTransposes final
97 void runOnOperation()
override;
104 DenseMap<Value, Value> &valuesMap,
105 IRRewriter &rewriter,
106 ArrayRef<int32_t> hoistedPerms);
110 bool areInvolutionTransposes(ArrayRef<int32_t> perms1,
111 ArrayRef<int32_t> perms2);
116 buildMappedToValue(Operation *op,
const DenseMap<Value, Value> &valuesMap,
117 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms);
123 buildMappedToValue(TransposeOp transposeOp,
124 const DenseMap<Value, Value> &valuesMap,
125 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms);
130 buildMappedToValue(ReshapeOp reshapeOp,
131 const DenseMap<Value, Value> &valuesMap,
132 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms);
142 buildMappedToValue(ConstOp constOp,
const DenseMap<Value, Value> &valuesMap,
143 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms);
149 std::set<TransposeOp> getGoodReplacements(
150 ArrayRef<int32_t> perms,
155 bool userNotContainedInValidTransposeDependencies(
156 Operation *user, std::set<TransposeOp> &validTransposes,
162 bool dependenciesAreValid(
164 std::set<TransposeOp> &validTransposes,
173 std::optional<DenseElementsAttr>
174 transposeDenseAttribute(DenseElementsAttr input, ArrayRef<int32_t> perms);
183 bool nonUnitDimDetected =
false;
186 if (nonUnitDimDetected)
188 nonUnitDimDetected =
true;
194std::optional<DenseElementsAttr>
197 RankedTensorType oldType = llvm::cast<RankedTensorType>(input.
getType());
198 ArrayRef<int64_t> oldShape = oldType.getShape();
199 int64_t rank = oldType.getRank();
203 if (rank <= 0 || oldType.getNumElements() <= 0) {
209 RankedTensorType newType =
210 RankedTensorType::get(newShape, oldType.getElementType());
217 if (!rawData.data()) {
236 size_t elementSize = llvm::divideCeil(oldType.getElementTypeBitWidth(), 8);
237 int64_t numElements = oldType.getNumElements();
239 SmallVector<char> outputBuffer(numElements * elementSize);
240 const char *inputPtr = rawData.data();
241 char *outputPtr = outputBuffer.data();
243 auto calculateStrides = [](ArrayRef<int64_t> shape) -> SmallVector<int64_t> {
244 int64_t rank = shape.size();
245 SmallVector<int64_t> strides(rank);
246 strides[rank - 1] = 1;
247 for (int64_t i = rank - 2; i >= 0; --i) {
248 strides[i] = strides[i + 1] * shape[i + 1];
254 SmallVector<int64_t> inputStrides = calculateStrides(oldShape);
255 SmallVector<int64_t> outputStrides = calculateStrides(newShape);
257 auto mapCoordinates = [&](int64_t destLinearIndex) -> int64_t {
258 int64_t tempDestIndex = destLinearIndex;
259 int64_t sourceLinearIndex = 0;
265 for (
auto j : llvm::seq<int64_t>(rank)) {
266 int64_t destCoord = tempDestIndex / outputStrides[j];
267 tempDestIndex %= outputStrides[j];
268 sourceLinearIndex += destCoord * inputStrides[perms[j]];
271 return sourceLinearIndex;
274 for (
auto destLinearIndex : llvm::seq<int64_t>(numElements)) {
275 int64_t sourceLinearIndex = mapCoordinates(destLinearIndex);
279 std::memcpy(outputPtr + destLinearIndex * elementSize,
280 inputPtr + sourceLinearIndex * elementSize, elementSize);
289bool TosaReduceTransposes::collectFanIn(Operation *op,
295 if (!llvm::isa_and_present<tosa::TosaDialect>(op->
getDialect()))
299 if (collected.contains(op))
311 if (!llvm::isa<tosa::TransposeOp>(op) && !llvm::isa<tosa::ReshapeOp>(op) &&
312 !llvm::isa<tosa::ConstOp>(op)) {
314 if (!llvm::isa<tosa::MulOp>(op) &&
315 !op->
hasTrait<OpTrait::tosa::TosaElementwiseOperator>())
320 if (llvm::isa<tosa::MulOp>(op) && operand == op->
getOperand(2)) {
324 if (!collectFanIn(operand.getDefiningOp(), collected))
330 collected.insert(op);
337bool TosaReduceTransposes::areInvolutionTransposes(ArrayRef<int32_t> perms1,
338 ArrayRef<int32_t> perms2) {
339 if (perms1.size() != perms2.size())
341 int32_t n = perms1.size();
342 for (int32_t i = 0; i < n; i++)
343 if (perms2[perms1[i]] != i)
351std::optional<Value> TosaReduceTransposes::buildMappedToValue(
352 Operation *op,
const DenseMap<Value, Value> &valuesMap,
353 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms) {
355 (!llvm::isa<tosa::MulOp>(op) &&
356 !op->
hasTrait<OpTrait::tosa::TosaElementwiseOperator>()))
360 SmallVector<Value, 3> operands;
362 if (valuesMap.contains(v)) {
363 operands.push_back(valuesMap.at(v));
364 }
else if (llvm::isa<tosa::MulOp>(op) && v == op->
getOperand(2)) {
366 operands.push_back(v);
390 RankedTensorType::get(
392 resultType.getElementType()),
397std::optional<Value> TosaReduceTransposes::buildMappedToValue(
398 TransposeOp transposeOp,
const DenseMap<Value, Value> &valuesMap,
399 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms) {
400 if (!areInvolutionTransposes(hoistedPerms, transposeOp.getPerms()))
402 return transposeOp.getInput1();
405std::optional<Value> TosaReduceTransposes::buildMappedToValue(
406 ReshapeOp reshapeOp,
const DenseMap<Value, Value> &valuesMap,
407 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms) {
408 auto reshapeOutput = reshapeOp.getOutput();
409 auto reshapeInputType =
410 llvm::dyn_cast<RankedTensorType>(reshapeOp.getInput1().getType());
411 if (!reshapeInputType)
413 auto reshapeInputShape = reshapeInputType.getShape();
414 auto reshapeOutputType =
415 llvm::cast<RankedTensorType>(reshapeOutput.getType());
416 const ArrayRef<int64_t> reshapeOutputShape = reshapeOutputType.getShape();
421 if (
failed(verifyUnitExpandedVectorShape(reshapeInputShape)) ||
422 failed(verifyUnitExpandedVectorShape(reshapeOutputShape)))
426 llvm::SmallVector<int64_t> newShape;
432 ImplicitLocOpBuilder builder(reshapeOp.getLoc(), rewriter);
433 auto foldedReshape = ReshapeOp::create(
434 rewriter, reshapeOp.getLoc(),
435 RankedTensorType::get(
437 reshapeOutputType.getElementType()),
438 reshapeOp.getInput1(),
441 return foldedReshape->getResult(0);
444std::optional<Value> TosaReduceTransposes::buildMappedToValue(
445 ConstOp constOp,
const DenseMap<Value, Value> &valuesMap,
446 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms) {
447 auto denseAttr = llvm::dyn_cast<DenseElementsAttr>(constOp.getValues());
450 auto maybeNewDenseAttr = transposeDenseAttribute(denseAttr, hoistedPerms);
451 if (!maybeNewDenseAttr.has_value())
453 auto newDenseAttr = maybeNewDenseAttr.value();
454 auto newConstOp = ConstOp::create(rewriter, constOp.getLoc(),
455 newDenseAttr.getType(), newDenseAttr);
456 return newConstOp->getResult(0);
459bool TosaReduceTransposes::convertDependentOps(
461 IRRewriter &rewriter, ArrayRef<int32_t> hoistedPerms) {
463 for (Operation *op : dependentOps) {
471 if (valuesMap.contains(priorValue))
477 std::optional<Value> maybeValue =
478 llvm::TypeSwitch<Operation *, std::optional<Value>>(op)
479 .Case<TransposeOp, ReshapeOp, ConstOp>([&](
auto transposeOp) {
480 return buildMappedToValue(transposeOp, valuesMap, rewriter,
483 .Default([&](Operation *op) {
484 return buildMappedToValue(op, valuesMap, rewriter, hoistedPerms);
487 if (!maybeValue.has_value())
490 valuesMap[priorValue] = maybeValue.value();
496bool TosaReduceTransposes::userNotContainedInValidTransposeDependencies(
497 Operation *user, std::set<TransposeOp> &validTransposes,
500 return llvm::none_of(
504 const auto &[transposeOp, dependentOps] = info;
505 return validTransposes.count(transposeOp) &&
506 dependentOps.contains(user);
513bool TosaReduceTransposes::dependenciesAreValid(
515 std::set<TransposeOp> &validTransposes,
518 for (Operation *op : dependentOps) {
522 if (llvm::isa<ConstOp>(op))
525 for (OpOperand &use : op->
getUses()) {
531 Operation *user = use.getOwner();
532 if (
auto otherTranspose = llvm::dyn_cast<TransposeOp>(user)) {
537 if (!llvm::equal(perms, otherTranspose.getPerms()))
539 }
else if (userNotContainedInValidTransposeDependencies(
540 user, validTransposes, transposeInfo)) {
555std::set<TransposeOp> TosaReduceTransposes::getGoodReplacements(
556 ArrayRef<int32_t> perms,
561 std::set<TransposeOp> ableToReplace;
562 for (
const auto &[transposeOp, _] : transposeInfo)
563 ableToReplace.insert(transposeOp);
568 for (
const auto &[transposeOp, dependentOps] : transposeInfo) {
570 if (!ableToReplace.count(transposeOp))
574 if (!dependenciesAreValid(perms, dependentOps, ableToReplace,
576 ableToReplace.erase(transposeOp);
584 return ableToReplace;
587void TosaReduceTransposes::runOnOperation() {
589 if (!getOperation().getRegion().hasOneBlock())
598 std::vector<std::pair<TransposeOp, SetVector<Operation *>>>>
599 permsToTransposeInfo;
604 std::vector<SmallVector<int32_t>> collectedPerms;
608 std::stack<std::pair<TransposeOp, ArrayRef<int32_t>>> totalTransposeOrder;
613 size_t expectedMaxPerms = 0;
614 getOperation().walk([&](TransposeOp) { expectedMaxPerms += 1; });
615 collectedPerms.reserve(expectedMaxPerms);
617 getOperation().walk([&](TransposeOp transposeOp) {
619 collectedPerms.emplace_back();
620 SmallVector<int32_t> &perms = collectedPerms.back();
623 auto input = transposeOp.getInput1();
624 auto output = transposeOp.getOutput();
627 if (!llvm::isa<RankedTensorType>(input.
getType()) ||
628 !llvm::isa<RankedTensorType>(output.getType()))
631 llvm::append_range(perms, transposeOp.getPerms());
634 if (llvm::equal(llvm::seq<int32_t>(0, perms.size()), perms))
639 if (!collectFanIn(input.getDefiningOp(), dependentOps))
645 DenseMap<Value, Value> &valuesMap = permsToValues[perms];
650 if (!convertDependentOps(dependentOps, valuesMap, rewriter, perms))
657 if (!valuesMap.contains(input))
658 return signalPassFailure();
663 if (output.getType() != valuesMap.at(input).getType())
666 auto &transposeInfo = permsToTransposeInfo[perms];
673 transposeInfo.emplace_back(transposeOp, dependentOps);
676 totalTransposeOrder.emplace(transposeOp, perms);
683 std::set<TransposeOp> ableToReplace;
684 for (
auto &[perms, transposeInfo] : permsToTransposeInfo) {
692 auto goodReplacementsForPerms = getGoodReplacements(perms, transposeInfo);
693 ableToReplace.insert(goodReplacementsForPerms.begin(),
694 goodReplacementsForPerms.end());
700 while (!totalTransposeOrder.empty()) {
701 auto [transposeOp, perms] = totalTransposeOrder.top();
702 totalTransposeOrder.pop();
704 if (ableToReplace.count(transposeOp) == 0)
707 auto &valuesMap = permsToValues[perms];
708 auto input = transposeOp.getInput1();
713 if (!valuesMap.contains(input))
714 return signalPassFailure();
716 rewriter.
replaceOp(transposeOp, valuesMap.at(input));
722 getOperation().walk<WalkOrder::PostOrder, ReverseIterator>(
An attribute that represents a reference to a dense vector or tensor object.
static DenseElementsAttr getFromRawBuffer(ShapedType type, ArrayRef< char > rawBuffer)
Construct a dense elements attribute from a raw buffer representing the data for this attribute.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
ArrayRef< char > getRawData() const
Return the raw storage data held by this attribute.
ShapedType getType() const
Return the type of this ElementsAttr, guaranteed to be a vector or tensor with static shape.
DenseElementsAttr reshape(ShapedType newType)
Return a new DenseElementsAttr that has the same data as the current attribute, but has been reshaped...
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
StringAttr getIdentifier() const
Return the name of this operation as a StringAttr.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Value getOperand(unsigned idx)
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
ArrayRef< NamedAttribute > getAttrs()
Return all of the attributes on this operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Location getLoc()
The source location the operation was defined or derived from.
OperationName getName()
The name of an operation is the key identifier for it.
operand_range getOperands()
Returns an iterator on the underlying Value's.
use_range getUses()
Returns a range of all uses, which is useful for iterating over all uses.
unsigned getNumResults()
Return the number of results held by this operation.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
Type getType() const
Return the type of this value.
SmallVector< T > applyTOSAPermutation(ArrayRef< T > input, ArrayRef< int32_t > perms)
Value getTosaConstShape(ImplicitLocOpBuilder &builder, llvm::ArrayRef< int64_t > shape)
bool getConstShapeValues(Operation *op, llvm::SmallVector< int64_t > &result_shape)
Include the generated interface declarations.
llvm::SetVector< T, Vector, Set, N > SetVector
bool isOpTriviallyDead(Operation *op)
Return true if the given operation is unused, and has no side effects on memory that prevent erasing.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap