CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
228 lines
9.4 KiB
C++
228 lines
9.4 KiB
C++
//===- IntegerRangeAnalysis.cpp - Integer range analysis --------*- 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 range inference
|
|
// which is used in transformations over the `arith` dialect such as
|
|
// branch elimination or signed->unsigned rewriting
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "mlir/Analysis/DataFlow/IntegerRangeAnalysis.h"
|
|
#include "mlir/Analysis/DataFlow/SparseAnalysis.h"
|
|
#include "mlir/Analysis/DataFlowFramework.h"
|
|
#include "mlir/IR/BuiltinAttributes.h"
|
|
#include "mlir/IR/Dialect.h"
|
|
#include "mlir/IR/OpDefinition.h"
|
|
#include "mlir/IR/Operation.h"
|
|
#include "mlir/IR/OperationSupport.h"
|
|
#include "mlir/IR/TypeUtilities.h"
|
|
#include "mlir/IR/Value.h"
|
|
#include "mlir/Interfaces/ControlFlowInterfaces.h"
|
|
#include "mlir/Interfaces/InferIntRangeInterface.h"
|
|
#include "mlir/Interfaces/LoopLikeInterface.h"
|
|
#include "mlir/Support/DebugStringHelper.h"
|
|
#include "mlir/Support/LLVM.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/DebugLog.h"
|
|
#include <cassert>
|
|
#include <optional>
|
|
#include <utility>
|
|
|
|
#define DEBUG_TYPE "int-range-analysis"
|
|
|
|
using namespace mlir;
|
|
using namespace mlir::dataflow;
|
|
|
|
namespace mlir::dataflow {
|
|
LogicalResult staticallyNonNegative(DataFlowSolver &solver, Value v) {
|
|
auto *result = solver.lookupState<IntegerValueRangeLattice>(v);
|
|
if (!result || result->getValue().isUninitialized())
|
|
return failure();
|
|
const ConstantIntRanges &range = result->getValue().getValue();
|
|
return success(range.smin().isNonNegative());
|
|
}
|
|
|
|
LogicalResult staticallyNonNegative(DataFlowSolver &solver, Operation *op) {
|
|
auto nonNegativePred = [&solver](Value v) -> bool {
|
|
return succeeded(staticallyNonNegative(solver, v));
|
|
};
|
|
return success(llvm::all_of(op->getOperands(), nonNegativePred) &&
|
|
llvm::all_of(op->getResults(), nonNegativePred));
|
|
}
|
|
} // namespace mlir::dataflow
|
|
|
|
/// Number of merge-site joins a single integer-range lattice element is
|
|
/// allowed to absorb before `IntegerValueRangeLattice::join` forces it to
|
|
/// its max as a sound over-approximation.
|
|
///
|
|
/// Trade-off: high enough that realistic loops with dynamic bounds (which
|
|
/// typically converge to a tight range in a small number of merge
|
|
/// iterations) are not widened prematurely; low enough that the +1
|
|
/// ratchet pathology this widening exists to cut off (loop-carried ranges
|
|
/// growing by one per worklist visit) terminates after at most this many
|
|
/// extra solver iterations rather than ~2^31.
|
|
static constexpr unsigned kIntegerRangeWideningBudget = 128;
|
|
|
|
ChangeResult IntegerValueRangeLattice::join(const AbstractSparseLattice &rhs) {
|
|
ChangeResult changed = Lattice::join(rhs);
|
|
if (mergeChangeCount >= kIntegerRangeWideningBudget) {
|
|
return changed | Lattice::join(IntegerValueRange::getMaxRange(
|
|
cast<Value>(getAnchor())));
|
|
}
|
|
if (changed == ChangeResult::Change)
|
|
++mergeChangeCount;
|
|
return changed;
|
|
}
|
|
|
|
LogicalResult IntegerRangeAnalysis::visitOperation(
|
|
Operation *op, ArrayRef<const IntegerValueRangeLattice *> operands,
|
|
ArrayRef<IntegerValueRangeLattice *> results) {
|
|
auto inferrable = dyn_cast<InferIntRangeInterface>(op);
|
|
if (!inferrable) {
|
|
setAllToEntryStates(results);
|
|
return success();
|
|
}
|
|
|
|
LDBG() << "Inferring ranges for "
|
|
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
|
|
auto argRanges = llvm::map_to_vector(
|
|
operands, [](const IntegerValueRangeLattice *lattice) {
|
|
return lattice->getValue();
|
|
});
|
|
|
|
auto joinCallback = [&](Value v, const IntegerValueRange &attrs) {
|
|
auto result = dyn_cast<OpResult>(v);
|
|
if (!result)
|
|
return;
|
|
assert(llvm::is_contained(op->getResults(), result));
|
|
|
|
LDBG() << "Inferred range " << attrs;
|
|
IntegerValueRangeLattice *lattice = results[result.getResultNumber()];
|
|
propagateIfChanged(lattice, lattice->join(attrs));
|
|
};
|
|
|
|
inferrable.inferResultRangesFromOptional(argRanges, joinCallback);
|
|
return success();
|
|
}
|
|
|
|
void IntegerRangeAnalysis::visitNonControlFlowArguments(
|
|
Operation *op, const RegionSuccessor &successor,
|
|
ValueRange nonSuccessorInputs,
|
|
ArrayRef<IntegerValueRangeLattice *> nonSuccessorInputLattices) {
|
|
assert(nonSuccessorInputs.size() == nonSuccessorInputLattices.size() &&
|
|
"size mismatch");
|
|
if (auto inferrable = dyn_cast<InferIntRangeInterface>(op)) {
|
|
LDBG() << "Inferring ranges for "
|
|
<< OpWithFlags(op, OpPrintingFlags().skipRegions());
|
|
|
|
auto argRanges = llvm::map_to_vector(op->getOperands(), [&](Value value) {
|
|
return getLatticeElementFor(getProgramPointAfter(op), value)->getValue();
|
|
});
|
|
|
|
auto joinCallback = [&](Value v, const IntegerValueRange &attrs) {
|
|
auto arg = dyn_cast<BlockArgument>(v);
|
|
if (!arg)
|
|
return;
|
|
if (!llvm::is_contained(successor.getSuccessor()->getArguments(), arg))
|
|
return;
|
|
|
|
LDBG() << "Inferred range " << attrs;
|
|
auto it = llvm::find(successor.getSuccessor()->getArguments(), arg);
|
|
unsigned nonSuccessorInputIdx =
|
|
std::distance(successor.getSuccessor()->getArguments().begin(), it);
|
|
IntegerValueRangeLattice *lattice =
|
|
nonSuccessorInputLattices[nonSuccessorInputIdx];
|
|
propagateIfChanged(lattice, lattice->join(attrs));
|
|
};
|
|
|
|
inferrable.inferResultRangesFromOptional(argRanges, joinCallback);
|
|
return;
|
|
}
|
|
|
|
/// Given a lower bound, upper bound, or step from a LoopLikeInterface return
|
|
/// the lower/upper bound for that result if possible.
|
|
auto getLoopBoundFromFold = [&](OpFoldResult loopBound, Type boundType,
|
|
Block *block, bool getUpper) {
|
|
unsigned int width = ConstantIntRanges::getStorageBitwidth(boundType);
|
|
if (auto attr = dyn_cast<Attribute>(loopBound)) {
|
|
if (auto bound = dyn_cast<IntegerAttr>(attr))
|
|
return bound.getValue();
|
|
} else if (auto value = llvm::dyn_cast<Value>(loopBound)) {
|
|
const IntegerValueRangeLattice *lattice =
|
|
getLatticeElementFor(getProgramPointBefore(block), value);
|
|
if (lattice != nullptr && !lattice->getValue().isUninitialized())
|
|
return getUpper ? lattice->getValue().getValue().smax()
|
|
: lattice->getValue().getValue().smin();
|
|
}
|
|
// Given the results of getConstant{Lower,Upper}Bound()
|
|
// or getConstantStep() on a LoopLikeInterface return the lower/upper
|
|
// bound
|
|
return getUpper ? APInt::getSignedMaxValue(width)
|
|
: APInt::getSignedMinValue(width);
|
|
};
|
|
|
|
// Infer bounds for loop arguments that have static bounds
|
|
if (auto loop = dyn_cast<LoopLikeOpInterface>(op)) {
|
|
std::optional<llvm::SmallVector<Value>> maybeIvs =
|
|
loop.getLoopInductionVars();
|
|
if (!maybeIvs) {
|
|
return SparseForwardDataFlowAnalysis ::visitNonControlFlowArguments(
|
|
op, successor, nonSuccessorInputs, nonSuccessorInputLattices);
|
|
}
|
|
// Some loop implementations may return nullopt for non-constant bounds
|
|
// (e.g. affine.for with a dynamic upper bound), even when induction
|
|
// variables exist. Fall back to the generic analysis in that case.
|
|
std::optional<SmallVector<OpFoldResult>> maybeLowerBounds =
|
|
loop.getLoopLowerBounds();
|
|
std::optional<SmallVector<OpFoldResult>> maybeUpperBounds =
|
|
loop.getLoopUpperBounds();
|
|
std::optional<SmallVector<OpFoldResult>> maybeSteps = loop.getLoopSteps();
|
|
if (!maybeLowerBounds || !maybeUpperBounds || !maybeSteps) {
|
|
return SparseForwardDataFlowAnalysis::visitNonControlFlowArguments(
|
|
op, successor, nonSuccessorInputs, nonSuccessorInputLattices);
|
|
}
|
|
SmallVector<OpFoldResult> lowerBounds = *maybeLowerBounds;
|
|
SmallVector<OpFoldResult> upperBounds = *maybeUpperBounds;
|
|
SmallVector<OpFoldResult> steps = *maybeSteps;
|
|
for (auto [iv, lowerBound, upperBound, step] :
|
|
llvm::zip_equal(*maybeIvs, lowerBounds, upperBounds, steps)) {
|
|
Block *block = iv.getParentBlock();
|
|
APInt min = getLoopBoundFromFold(lowerBound, iv.getType(), block,
|
|
/*getUpper=*/false);
|
|
APInt max = getLoopBoundFromFold(upperBound, iv.getType(), block,
|
|
/*getUpper=*/true);
|
|
// Assume positivity for uniscoverable steps by way of getUpper = true.
|
|
APInt stepVal =
|
|
getLoopBoundFromFold(step, iv.getType(), block, /*getUpper=*/true);
|
|
|
|
if (stepVal.isNegative()) {
|
|
std::swap(min, max);
|
|
} else {
|
|
// Correct the upper bound by subtracting 1 so that it becomes a <=
|
|
// bound, because loops do not generally include their upper bound.
|
|
max -= 1;
|
|
}
|
|
|
|
// If we infer the lower bound to be larger than the upper bound, the
|
|
// resulting range is meaningless and should not be used in further
|
|
// inferences.
|
|
if (max.sge(min)) {
|
|
IntegerValueRangeLattice *ivEntry = getLatticeElement(iv);
|
|
auto ivRange = ConstantIntRanges::fromSigned(min, max);
|
|
propagateIfChanged(ivEntry, ivEntry->join(IntegerValueRange{ivRange}));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
return SparseForwardDataFlowAnalysis::visitNonControlFlowArguments(
|
|
op, successor, nonSuccessorInputs, nonSuccessorInputLattices);
|
|
}
|