Integer range analysis will not update the range of an operation when
any of the inferred input lattices are uninitialized. In the current
behavior, all lattice values for non integer types are uninitialized.
For operations like arith.cmpf
```mlir
%3 = arith.cmpf ugt, %arg0, %arg1 : f32
```
that will result in the range of the output also being uninitialized,
and so on for any consumer of the arith.cmpf result. When control-flow
ops are involved, the lack of propagation results in incorrect ranges,
as the back edges for loop carried values are not properly joined with
the definitions from the body region.
For example, an scf.while loop whose body region produces a value that
is in a dataflow relationship with some floating-point values through an
arith.cmpf operation:
```mlir
func.func @test_bad_range(%arg0: f32, %arg1: f32) -> (index, index) {
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c0 = arith.constant 0 : index
%3 = arith.cmpf ugt, %arg0, %arg1 : f32
%1:2 = scf.while (%arg2 = %c0, %arg3 = %c0) : (index, index) -> (index, index) {
%2 = arith.cmpi ult, %arg2, %c4 : index
scf.condition(%2) %arg2, %arg3 : index, index
} do {
^bb0(%arg2: index, %arg3: index):
%4 = arith.select %3, %arg3, %arg3 : index
%5 = arith.addi %arg2, %c1 : index
scf.yield %5, %4 : index, index
}
return %1#0, %1#1 : index, index
}
```
The existing behavior results in the control condition %2 being
optimized to true, turning the while loop into an infinite loop. The
update to %arg2 through the body region is never factored into the range
calculation, as the ranges for the body ops all test as uninitialized.
This change causes all values initialized with setToEntryState to be set
to some initialized range, even if the values are not integers.
---------
Co-authored-by: Spenser Bauman <sabauma@fastmail>
207 lines
8.3 KiB
C++
207 lines
8.3 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/ConstantPropagationAnalysis.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/Value.h"
|
|
#include "mlir/Interfaces/ControlFlowInterfaces.h"
|
|
#include "mlir/Interfaces/InferIntRangeInterface.h"
|
|
#include "mlir/Interfaces/LoopLikeInterface.h"
|
|
#include "mlir/Support/LLVM.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include <cassert>
|
|
#include <optional>
|
|
#include <utility>
|
|
|
|
#define DEBUG_TYPE "int-range-analysis"
|
|
|
|
using namespace mlir;
|
|
using namespace mlir::dataflow;
|
|
|
|
void IntegerValueRangeLattice::onUpdate(DataFlowSolver *solver) const {
|
|
Lattice::onUpdate(solver);
|
|
|
|
// If the integer range can be narrowed to a constant, update the constant
|
|
// value of the SSA value.
|
|
std::optional<APInt> constant = getValue().getValue().getConstantValue();
|
|
auto value = point.get<Value>();
|
|
auto *cv = solver->getOrCreateState<Lattice<ConstantValue>>(value);
|
|
if (!constant)
|
|
return solver->propagateIfChanged(
|
|
cv, cv->join(ConstantValue::getUnknownConstant()));
|
|
|
|
Dialect *dialect;
|
|
if (auto *parent = value.getDefiningOp())
|
|
dialect = parent->getDialect();
|
|
else
|
|
dialect = value.getParentBlock()->getParentOp()->getDialect();
|
|
solver->propagateIfChanged(
|
|
cv, cv->join(ConstantValue(IntegerAttr::get(value.getType(), *constant),
|
|
dialect)));
|
|
}
|
|
|
|
void IntegerRangeAnalysis::visitOperation(
|
|
Operation *op, ArrayRef<const IntegerValueRangeLattice *> operands,
|
|
ArrayRef<IntegerValueRangeLattice *> results) {
|
|
auto inferrable = dyn_cast<InferIntRangeInterface>(op);
|
|
if (!inferrable)
|
|
return setAllToEntryStates(results);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Inferring ranges for " << *op << "\n");
|
|
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));
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Inferred range " << attrs << "\n");
|
|
IntegerValueRangeLattice *lattice = results[result.getResultNumber()];
|
|
IntegerValueRange oldRange = lattice->getValue();
|
|
|
|
ChangeResult changed = lattice->join(attrs);
|
|
|
|
// Catch loop results with loop variant bounds and conservatively make
|
|
// them [-inf, inf] so we don't circle around infinitely often (because
|
|
// 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 && !oldRange.isUninitialized() &&
|
|
!(lattice->getValue() == oldRange)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Loop variant loop result detected\n");
|
|
changed |= lattice->join(IntegerValueRange::getMaxRange(v));
|
|
}
|
|
propagateIfChanged(lattice, changed);
|
|
};
|
|
|
|
inferrable.inferResultRangesFromOptional(argRanges, joinCallback);
|
|
}
|
|
|
|
void IntegerRangeAnalysis::visitNonControlFlowArguments(
|
|
Operation *op, const RegionSuccessor &successor,
|
|
ArrayRef<IntegerValueRangeLattice *> argLattices, unsigned firstIndex) {
|
|
if (auto inferrable = dyn_cast<InferIntRangeInterface>(op)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Inferring ranges for " << *op << "\n");
|
|
|
|
auto argRanges = llvm::map_to_vector(op->getOperands(), [&](Value value) {
|
|
return getLatticeElementFor(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;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Inferred range " << attrs << "\n");
|
|
IntegerValueRangeLattice *lattice = argLattices[arg.getArgNumber()];
|
|
IntegerValueRange oldRange = lattice->getValue();
|
|
|
|
ChangeResult changed = lattice->join(attrs);
|
|
|
|
// Catch loop results with loop variant bounds and conservatively make
|
|
// them [-inf, inf] so we don't circle around infinitely often (because
|
|
// the dataflow analysis in MLIR doesn't attempt to work out trip counts
|
|
// and often can't).
|
|
bool isYieldedValue = llvm::any_of(v.getUsers(), [](Operation *op) {
|
|
return op->hasTrait<OpTrait::IsTerminator>();
|
|
});
|
|
if (isYieldedValue && !oldRange.isUninitialized() &&
|
|
!(lattice->getValue() == oldRange)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Loop variant loop result detected\n");
|
|
changed |= lattice->join(IntegerValueRange::getMaxRange(v));
|
|
}
|
|
propagateIfChanged(lattice, changed);
|
|
};
|
|
|
|
inferrable.inferResultRangesFromOptional(argRanges, joinCallback);
|
|
return;
|
|
}
|
|
|
|
/// Given the results of getConstant{Lower,Upper}Bound() or getConstantStep()
|
|
/// on a LoopLikeInterface return the lower/upper bound for that result if
|
|
/// possible.
|
|
auto getLoopBoundFromFold = [&](std::optional<OpFoldResult> loopBound,
|
|
Type boundType, bool getUpper) {
|
|
unsigned int width = ConstantIntRanges::getStorageBitwidth(boundType);
|
|
if (loopBound.has_value()) {
|
|
if (loopBound->is<Attribute>()) {
|
|
if (auto bound =
|
|
dyn_cast_or_null<IntegerAttr>(loopBound->get<Attribute>()))
|
|
return bound.getValue();
|
|
} else if (auto value = llvm::dyn_cast_if_present<Value>(*loopBound)) {
|
|
const IntegerValueRangeLattice *lattice =
|
|
getLatticeElementFor(op, 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<Value> iv = loop.getSingleInductionVar();
|
|
if (!iv) {
|
|
return SparseForwardDataFlowAnalysis ::visitNonControlFlowArguments(
|
|
op, successor, argLattices, firstIndex);
|
|
}
|
|
std::optional<OpFoldResult> lowerBound = loop.getSingleLowerBound();
|
|
std::optional<OpFoldResult> upperBound = loop.getSingleUpperBound();
|
|
std::optional<OpFoldResult> step = loop.getSingleStep();
|
|
APInt min = getLoopBoundFromFold(lowerBound, iv->getType(),
|
|
/*getUpper=*/false);
|
|
APInt max = getLoopBoundFromFold(upperBound, iv->getType(),
|
|
/*getUpper=*/true);
|
|
// Assume positivity for uniscoverable steps by way of getUpper = true.
|
|
APInt stepVal =
|
|
getLoopBoundFromFold(step, iv->getType(), /*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;
|
|
}
|
|
|
|
IntegerValueRangeLattice *ivEntry = getLatticeElement(*iv);
|
|
auto ivRange = ConstantIntRanges::fromSigned(min, max);
|
|
propagateIfChanged(ivEntry, ivEntry->join(IntegerValueRange{ivRange}));
|
|
return;
|
|
}
|
|
|
|
return SparseForwardDataFlowAnalysis::visitNonControlFlowArguments(
|
|
op, successor, argLattices, firstIndex);
|
|
}
|