[turboshaft] Fix type refinement for unreachable branches

Bug: v8:12783, chromium:1400034
Change-Id: Ifdd105fba500ebc7678ee223947743eb0283f950
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4097428
Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84814}
This commit is contained in:
Nico Hartmann 2022-12-13 13:50:55 +01:00 committed by V8 LUCI CQ
parent 8f911e423e
commit 2e9b4afa38

View File

@ -64,6 +64,7 @@ struct WordOperationTyper {
using word_t = uint_type<Bits>;
using type_t = WordType<Bits>;
using ElementsVector = base::SmallVector<word_t, type_t::kMaxSetSize * 2>;
static constexpr word_t max = std::numeric_limits<word_t>::max();
static type_t FromElements(ElementsVector elements, Zone* zone) {
base::sort(elements);
@ -92,8 +93,7 @@ struct WordOperationTyper {
const base::Vector<const word_t>& elements) {
DCHECK(!elements.empty());
DCHECK(detail::is_unique_and_sorted(elements));
if (elements[elements.size() - 1] - elements[0] <=
std::numeric_limits<word_t>::max() / 2) {
if (elements[elements.size() - 1] - elements[0] <= max / 2) {
// Construct a non-wrapping range.
return {elements[0], elements[elements.size() - 1]};
}
@ -112,8 +112,7 @@ struct WordOperationTyper {
}
static word_t distance(const std::pair<word_t, word_t>& range) {
return is_wrapping(range) ? (std::numeric_limits<word_t>::max() -
range.first + range.second)
return is_wrapping(range) ? (max - range.first + range.second)
: range.second - range.first;
}
@ -143,7 +142,7 @@ struct WordOperationTyper {
// Check: (lhs.to + rhs.to + 1) - (lhs.from + rhs.from + 1) < max
// =====> (lhs.to - lhs.from) + (rhs.to - rhs.from) < max
// =====> (lhs.to - lhs.from) < max - (rhs.to - rhs.from)
if (distance(x) < std::numeric_limits<word_t>::max() - distance(y)) {
if (distance(x) < max - distance(y)) {
return type_t::Range(x.first + y.first, x.second + y.second, zone);
}
@ -175,6 +174,72 @@ struct WordOperationTyper {
// TODO(nicohartmann@): Improve the wrapping cases.
return type_t::Any();
}
// Computes the ranges to which the sides of the unsigned comparison (lhs <
// rhs) can be restricted when the comparison is true. When the comparison is
// true, we learn: lhs cannot be >= rhs.max and rhs cannot be <= lhs.min.
static std::pair<Type, Type> RestrictionForUnsignedLessThan_True(
const type_t& lhs, const type_t& rhs, Zone* zone) {
Type restrict_lhs;
if (rhs.unsigned_max() == 0) {
// There is no value for lhs that could make (lhs < 0) true.
restrict_lhs = Type::None();
} else {
restrict_lhs = type_t::Range(0, next_smaller(rhs.unsigned_max()), zone);
}
Type restrict_rhs;
if (lhs.unsigned_min() == max) {
// There is no value for rhs that could make (max < rhs) true.
restrict_rhs = Type::None();
} else {
restrict_rhs = type_t::Range(next_larger(lhs.unsigned_min()), max, zone);
}
return {restrict_lhs, restrict_rhs};
}
// Computes the ranges to which the sides of the unsigned comparison (lhs <
// rhs) can be restricted when the comparison is false. When the comparison is
// false, we learn: lhs cannot be < rhs.min and rhs cannot be > lhs.max.
static std::pair<Type, Type> RestrictionForUnsignedLessThan_False(
const type_t& lhs, const type_t& rhs, Zone* zone) {
return {type_t::Range(rhs.unsigned_min(), max, zone),
type_t::Range(0, lhs.unsigned_max(), zone)};
}
// Computes the ranges to which the sides of the unsigned comparison (lhs <=
// rhs) can be restricted when the comparison is true. When the comparison is
// true, we learn: lhs cannot be > rhs.max and rhs cannot be < lhs.min.
static std::pair<Type, Type> RestrictionForUnsignedLessThanOrEqual_True(
const type_t& lhs, const type_t& rhs, Zone* zone) {
return {type_t::Range(0, rhs.unsigned_max(), zone),
type_t::Range(lhs.unsigned_min(), max, zone)};
}
// Computes the ranges to which the sides of the unsigned comparison (lhs <=
// rhs) can be restricted when the comparison is false. When the comparison is
// false, we learn: lhs cannot be <= rhs.min and rhs cannot be >= lhs.max.
static std::pair<Type, Type> RestrictionForUnsignedLessThanOrEqual_False(
const type_t& lhs, const type_t& rhs, Zone* zone) {
Type restrict_lhs;
if (rhs.unsigned_min() == max) {
// There is no value for lhs that could make (lhs <= max) false.
restrict_lhs = Type::None();
} else {
restrict_lhs = type_t::Range(next_larger(rhs.unsigned_min()), max, zone);
}
Type restrict_rhs;
if (lhs.unsigned_max() == 0) {
// There is no value for rhs that could make (0 <= rhs) false.
restrict_rhs = Type::None();
} else {
restrict_rhs = type_t::Range(0, next_smaller(lhs.unsigned_max()), zone);
}
return {restrict_lhs, restrict_rhs};
}
};
template <size_t Bits>
@ -182,6 +247,7 @@ struct FloatOperationTyper {
static_assert(Bits == 32 || Bits == 64);
using float_t = std::conditional_t<Bits == 32, float, double>;
using type_t = FloatType<Bits>;
static constexpr float_t inf = std::numeric_limits<float_t>::infinity();
static constexpr int kSetThreshold = type_t::kMaxSetSize;
static type_t Range(float_t min, float_t max, bool maybe_nan, Zone* zone) {
@ -297,6 +363,78 @@ struct FloatOperationTyper {
const float_t result_max = array_max(results);
return Range(result_min, result_max, maybe_nan, zone);
}
// Computes the ranges to which the sides of the comparison (lhs < rhs) can be
// restricted when the comparison is true. When the comparison is true, we
// learn: lhs cannot be >= rhs.max and rhs cannot be <= lhs.min and neither
// can be NaN.
static std::pair<Type, Type> RestrictionForLessThan_True(const type_t& lhs,
const type_t& rhs,
Zone* zone) {
Type restrict_lhs;
if (rhs.max() == -inf) {
// There is no value for lhs that could make (lhs < -inf) true.
restrict_lhs = Type::None();
} else {
restrict_lhs = type_t::Range(-inf, next_smaller(rhs.max()), zone);
}
Type restrict_rhs;
if (lhs.min() == inf) {
// There is no value for rhs that could make (inf < rhs) true.
restrict_rhs = Type::None();
} else {
restrict_rhs = type_t::Range(next_larger(lhs.min()), inf, zone);
}
return {restrict_lhs, restrict_rhs};
}
// Computes the ranges to which the sides of the comparison (lhs < rhs) can be
// restricted when the comparison is false. When the comparison is false, we
// learn: lhs cannot be < rhs.min and rhs cannot be > lhs.max.
static std::pair<Type, Type> RestrictionForLessThan_False(const type_t& lhs,
const type_t& rhs,
Zone* zone) {
return {type_t::Range(rhs.min(), inf, type_t::kNaN, zone),
type_t::Range(-inf, lhs.max(), type_t::kNaN, zone)};
}
// Computes the ranges to which the sides of the comparison (lhs <= rhs) can
// be restricted when the comparison is true. When the comparison is true, we
// learn: lhs cannot be > rhs.max and rhs cannot be < lhs.min and neither can
// be NaN.
static std::pair<Type, Type> RestrictionForLessThanOrEqual_True(
const type_t& lhs, const type_t& rhs, Zone* zone) {
return {type_t::Range(-inf, rhs.max(), zone),
type_t::Range(lhs.min(), inf, zone)};
}
// Computes the ranges to which the sides of the comparison (lhs <= rhs) can
// be restricted when the comparison is false. When the comparison is false,
// we learn: lhs cannot be <= rhs.min and rhs cannot be >= lhs.max.
static std::pair<Type, Type> RestrictionForLessThanOrEqual_False(
const type_t& lhs, const type_t& rhs, Zone* zone) {
Type restrict_lhs;
if (rhs.min() == inf) {
// The only value for lhs that could make (lhs <= inf) false is NaN.
restrict_lhs = type_t::NaN();
} else {
restrict_lhs =
type_t::Range(next_larger(rhs.min()), inf, type_t::kNaN, zone);
}
Type restrict_rhs;
if (lhs.max() == -inf) {
// The only value for rhs that could make (-inf <= rhs) false is NaN.
restrict_rhs = type_t::NaN();
} else {
restrict_rhs =
type_t::Range(-inf, next_smaller(lhs.max()), type_t::kNaN, zone);
}
return {restrict_lhs, restrict_rhs};
}
};
class Typer {
@ -580,6 +718,30 @@ class TypeInferenceReducer : public Next {
current_block_ = new_block;
}
template <bool allow_implicit_word64_truncation>
Type RefineWord32Type(const Type& type, const Type& refinement, Zone* zone) {
// If refinement is Type::None(), the operation/branch is unreachable.
if (refinement.IsNone()) return Type::None();
DCHECK(refinement.IsWord32());
if constexpr (allow_implicit_word64_truncation) {
// Turboshaft allows implicit trunction of Word64 values to Word32. When
// an operation on Word32 representation computes a refinement type, this
// is going to be a Type::Word32() even if the actual {type} was Word64
// before truncation. To correctly refine this type, we need to extend the
// {refinement} to Word64 such that it reflects the corresponding values
// in the original type (before truncation) before we intersect.
if (type.IsWord64()) {
return Word64Type::Intersect(
type.AsWord64(),
Typer::ExtendWord32ToWord64(refinement.AsWord32(), zone),
Type::ResolutionMode::kOverApproximate, zone);
}
}
// We limit the values of {type} to those in {refinement}.
return Word32Type::Intersect(type.AsWord32(), refinement.AsWord32(),
Type::ResolutionMode::kOverApproximate, zone);
}
void RefineTypesAfterBranch(const BranchOp* branch, bool then_branch) {
Zone* zone = Asm().graph_zone();
// Inspect branch condition.
@ -609,80 +771,53 @@ class TypeInferenceReducer : public Next {
}
Word32Type l = Typer::TruncateWord32Input(lhs, true, zone).AsWord32();
Word32Type r = Typer::TruncateWord32Input(rhs, true, zone).AsWord32();
uint32_t l_min, l_max, r_min, r_max;
if (then_branch) {
l_min = 0;
l_max = r.unsigned_max();
r_min = l.unsigned_min();
r_max = std::numeric_limits<uint32_t>::max();
if (is_less_than) {
l_max = next_smaller(l_max);
r_min = next_larger(r_min);
}
Type l_restrict, r_restrict;
using OpTyper = WordOperationTyper<32>;
if (is_less_than) {
std::tie(l_restrict, r_restrict) =
then_branch
? OpTyper::RestrictionForUnsignedLessThan_True(l, r, zone)
: OpTyper ::RestrictionForUnsignedLessThan_False(l, r,
zone);
} else {
l_min = r.unsigned_min();
l_max = std::numeric_limits<uint32_t>::max();
r_min = 0;
r_max = l.unsigned_max();
if (!is_less_than) {
l_min = next_larger(l_min);
r_max = next_smaller(r_max);
}
}
auto l_restrict = Word32Type::Range(l_min, l_max, zone);
auto r_restrict = Word32Type::Range(r_min, r_max, zone);
if (l_restrict.IsWord32() && lhs.IsWord64()) {
l_refined = Word64Type::Intersect(
lhs.AsWord64(),
Typer::ExtendWord32ToWord64(l_restrict.AsWord32(), zone),
Type::ResolutionMode::kOverApproximate, zone);
} else {
l_refined = Word32Type::Intersect(
l, l_restrict, Type::ResolutionMode::kOverApproximate, zone);
}
if (r_restrict.IsWord32() && rhs.IsWord64()) {
r_refined = Word64Type::Intersect(
rhs.AsWord64(),
Typer::ExtendWord32ToWord64(r_restrict.AsWord32(), zone),
Type::ResolutionMode::kOverApproximate, zone);
} else {
r_refined = Word32Type::Intersect(
r, r_restrict, Type::ResolutionMode::kOverApproximate, zone);
std::tie(l_restrict, r_restrict) =
then_branch
? OpTyper::RestrictionForUnsignedLessThanOrEqual_True(l, r,
zone)
: OpTyper::RestrictionForUnsignedLessThanOrEqual_False(
l, r, zone);
}
// Special handling for word32 restriction, because the inputs might
// have been truncated from word64 implicitly.
l_refined = RefineWord32Type<true>(lhs, l_restrict, zone);
r_refined = RefineWord32Type<true>(rhs, r_restrict, zone);
break;
}
case RegisterRepresentation::Float64(): {
constexpr double infty = std::numeric_limits<double>::infinity();
Float64Type l = lhs.AsFloat64();
Float64Type r = rhs.AsFloat64();
double l_min, l_max, r_min, r_max;
uint32_t special_values = Float64Type::kNoSpecialValues;
if (then_branch) {
l_min = -infty;
l_max = r.max();
r_min = l.min();
r_max = infty;
if (is_less_than) {
l_max = next_smaller(l_max);
r_min = next_larger(r_min);
}
Type l_restrict, r_restrict;
using OpTyper = FloatOperationTyper<64>;
if (is_less_than) {
std::tie(l_restrict, r_restrict) =
then_branch ? OpTyper::RestrictionForLessThan_True(l, r, zone)
: OpTyper::RestrictionForLessThan_False(l, r, zone);
} else {
l_min = r.min();
l_max = infty;
r_min = -infty;
r_max = l.max();
special_values = Float64Type::kNaN;
if (!is_less_than) {
l_min = next_larger(l_min);
r_max = next_smaller(r_max);
}
std::tie(l_restrict, r_restrict) =
then_branch
? OpTyper::RestrictionForLessThanOrEqual_True(l, r, zone)
: OpTyper::RestrictionForLessThanOrEqual_False(l, r, zone);
}
auto l_restrict =
Float64Type::Range(l_min, l_max, special_values, zone);
auto r_restrict =
Float64Type::Range(r_min, r_max, special_values, zone);
l_refined = Float64Type::Intersect(l, l_restrict, zone);
r_refined = Float64Type::Intersect(r, r_restrict, zone);
l_refined =
l_restrict.IsNone()
? Type::None()
: Float64Type::Intersect(l, l_restrict.AsFloat64(), zone);
r_refined =
l_restrict.IsNone()
? Type::None()
: Float64Type::Intersect(r, r_restrict.AsFloat64(), zone);
break;
}
default: