| //===- IntegerDivisibilityAnalysis.cpp - Integer divisibility ---*- C++ -*-===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines the dataflow analysis class for integer divisibility |
| // inference. Operations participate in the analysis by implementing |
| // `InferIntDivisibilityOpInterface`. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "mlir/Analysis/DataFlow/IntegerDivisibilityAnalysis.h" |
| |
| #include "mlir/Dialect/Utils/StaticValueUtils.h" |
| #include "mlir/Interfaces/LoopLikeInterface.h" |
| #include "llvm/Support/Debug.h" |
| |
| #define DEBUG_TYPE "int-divisibility-analysis" |
| |
| using llvm::dbgs; |
| |
| namespace mlir::dataflow { |
| |
| void IntegerDivisibilityAnalysis::setToEntryState( |
| IntegerDivisibilityLattice *lattice) { |
| propagateIfChanged(lattice, |
| lattice->join(IntegerDivisibility::getMinDivisibility())); |
| } |
| |
| LogicalResult IntegerDivisibilityAnalysis::visitOperation( |
| Operation *op, ArrayRef<const IntegerDivisibilityLattice *> operands, |
| ArrayRef<IntegerDivisibilityLattice *> results) { |
| auto inferrable = dyn_cast<InferIntDivisibilityOpInterface>(op); |
| if (!inferrable) { |
| setAllToEntryStates(results); |
| return success(); |
| } |
| |
| LLVM_DEBUG(dbgs() << "Inferring divisibility for " << *op << "\n"); |
| auto argDivs = llvm::map_to_vector( |
| operands, [](const IntegerDivisibilityLattice *lattice) { |
| return lattice->getValue(); |
| }); |
| auto joinCallback = [&](Value v, const IntegerDivisibility &newDiv) { |
| auto result = dyn_cast<OpResult>(v); |
| if (!result) { |
| return; |
| } |
| assert(llvm::is_contained(op->getResults(), result)); |
| |
| LLVM_DEBUG(dbgs() << "Inferred divisibility " << newDiv << "\n"); |
| IntegerDivisibilityLattice *lattice = results[result.getResultNumber()]; |
| IntegerDivisibility oldDiv = lattice->getValue(); |
| |
| ChangeResult changed = lattice->join(newDiv); |
| |
| // Catch loop results with loop-variant divisibility and conservatively |
| // set them to divisibility 1 (no information) so we don't ratchet |
| // indefinitely (the dataflow analysis in MLIR doesn't attempt to work |
| // out trip counts and often can't). |
| bool isYieldedResult = llvm::any_of(v.getUsers(), [](Operation *op) { |
| return op->hasTrait<OpTrait::IsTerminator>(); |
| }); |
| if (isYieldedResult && !oldDiv.isUninitialized() && |
| !(lattice->getValue() == oldDiv)) { |
| LLVM_DEBUG(llvm::dbgs() << "Loop variant loop result detected\n"); |
| changed |= lattice->join(IntegerDivisibility::getMinDivisibility()); |
| } |
| propagateIfChanged(lattice, changed); |
| }; |
| |
| inferrable.inferResultDivisibility(argDivs, joinCallback); |
| return success(); |
| } |
| |
| void IntegerDivisibilityAnalysis::visitNonControlFlowArguments( |
| Operation *op, const RegionSuccessor &successor, ValueRange successorInputs, |
| ArrayRef<IntegerDivisibilityLattice *> argLattices) { |
| // Get the constant divisibility, or query the lattice for Values. |
| auto getDivFromOfr = [&](std::optional<OpFoldResult> ofr, Block *block, |
| bool isUnsigned) -> uint64_t { |
| if (ofr.has_value()) { |
| if (auto constBound = getConstantIntValue(*ofr)) { |
| return constBound.value(); |
| } |
| auto value = cast<Value>(ofr.value()); |
| const IntegerDivisibilityLattice *lattice = |
| getLatticeElementFor(getProgramPointBefore(block), value); |
| if (lattice != nullptr && !lattice->getValue().isUninitialized()) { |
| return isUnsigned ? lattice->getValue().getValue().udiv() |
| : lattice->getValue().getValue().sdiv(); |
| } |
| } |
| return isUnsigned |
| ? IntegerDivisibility::getMinDivisibility().getValue().udiv() |
| : IntegerDivisibility::getMinDivisibility().getValue().sdiv(); |
| }; |
| |
| // Infer bounds for loop arguments that have static bounds |
| if (auto loop = dyn_cast<LoopLikeOpInterface>(op)) { |
| std::optional<SmallVector<Value>> ivs = loop.getLoopInductionVars(); |
| std::optional<SmallVector<OpFoldResult>> lbs = loop.getLoopLowerBounds(); |
| std::optional<SmallVector<OpFoldResult>> steps = loop.getLoopSteps(); |
| if (!ivs || !lbs || !steps) { |
| return SparseForwardDataFlowAnalysis::visitNonControlFlowArguments( |
| op, successor, successorInputs, argLattices); |
| } |
| for (auto [iv, lb, step] : llvm::zip_equal(*ivs, *lbs, *steps)) { |
| IntegerDivisibilityLattice *ivEntry = getLatticeElement(iv); |
| Block *block = iv.getParentBlock(); |
| uint64_t stepUDiv = getDivFromOfr(step, block, /*unsigned=*/true); |
| uint64_t stepSDiv = getDivFromOfr(step, block, /*unsigned=*/false); |
| uint64_t lbUDiv = getDivFromOfr(lb, block, /*unsigned=*/true); |
| uint64_t lbSDiv = getDivFromOfr(lb, block, /*unsigned=*/false); |
| ConstantIntDivisibility lbDiv(lbUDiv, lbSDiv); |
| ConstantIntDivisibility stepDiv(stepUDiv, stepSDiv); |
| |
| // Loop induction variables are computed as `lb + i * step`. The |
| // divisibility for `i * step` is just the divisibility of `step`, so |
| // the total divisibility is obtained by unioning the step divisibility |
| // with the lower bound divisibility, which takes the GCD of the two. |
| ConstantIntDivisibility ivDiv = stepDiv.getUnion(lbDiv); |
| propagateIfChanged(ivEntry, ivEntry->join(ivDiv)); |
| } |
| return; |
| } |
| |
| return SparseForwardDataFlowAnalysis::visitNonControlFlowArguments( |
| op, successor, successorInputs, argLattices); |
| } |
| |
| } // namespace mlir::dataflow |