From 17a7d9079681e456718a9585ae3949605f3f5504 Mon Sep 17 00:00:00 2001 From: Eric Astor Date: Mon, 13 Jul 2026 11:46:43 -0700 Subject: [PATCH] [opt] Migrate more passes to take advantage of PartialInformation We adjust our passes that interact with ProcStateRangeQueryEngine to avoid redundant query engine instances, now that PSRQE supports full PartialInformation, and more generally to take advantage of more information where it makes sense. PiperOrigin-RevId: 947159466 --- xls/ir/interval_ops.cc | 58 ++-- xls/ir/interval_ops_test.cc | 45 +++ xls/ir/partial_information.cc | 16 + xls/ir/partial_information.h | 3 + xls/passes/BUILD | 12 +- xls/passes/back_propagate_range_analysis.cc | 17 +- xls/passes/back_propagate_range_analysis.h | 8 +- xls/passes/bdd_query_engine.cc | 14 +- xls/passes/narrowing_pass.cc | 12 +- xls/passes/partial_info_query_engine.cc | 26 +- xls/passes/partial_info_query_engine.h | 3 + xls/passes/proc_state_analysis.cc | 1 - xls/passes/proc_state_narrowing_pass.cc | 21 +- xls/passes/proc_state_range_query_engine.cc | 300 ++++++------------ xls/passes/proc_state_range_query_engine.h | 66 ++-- .../proc_state_range_query_engine_test.cc | 30 ++ 16 files changed, 326 insertions(+), 306 deletions(-) diff --git a/xls/ir/interval_ops.cc b/xls/ir/interval_ops.cc index 675c800418..6d1723b70b 100644 --- a/xls/ir/interval_ops.cc +++ b/xls/ir/interval_ops.cc @@ -141,33 +141,41 @@ IntervalSet FromTernary(TernarySpan tern, int64_t max_interval_bits) { // Need to extend the x-s to avoid creating too many intervals. lsb_xs = (x_locations.front() - tern.cbegin()) + 1; x_locations.pop_front(); + + // Make sure to include any contiguous X's in the trailing unknown region, + // maintaining that `lsb_xs` points to the first known bit (that we retain), + // and `x_locations` only includes the X's above that. + while (!x_locations.empty() && + lsb_xs == (x_locations.front() - tern.cbegin())) { + ++lsb_xs; + x_locations.pop_front(); + } } - IntervalSet is(tern.size()); - if (x_locations.empty()) { - // All bits from 0 -> lsb_xs are unknown. - Bits high_bits = ternary_ops::ToKnownBitsValues(tern.subspan(lsb_xs)); - is.AddInterval(Interval::Closed( + // Capture the input ternary above the last lsb_x. + TernarySpan prefix = tern.subspan(lsb_xs); + + if (x_locations.empty() || lsb_xs == tern.size()) { + // All bits from 0 -> lsb_xs are unknown, and everything above it is known. + Bits high_bits = ternary_ops::ToKnownBitsValues(prefix); + return IntervalSet::Of({Interval::Closed( bits_ops::UMax(lb, bits_ops::Concat({high_bits, Bits(lsb_xs)})), bits_ops::UMin(ub, - bits_ops::Concat({high_bits, Bits::AllOnes(lsb_xs)})))); - is.Normalize(); - return is; + bits_ops::Concat({high_bits, Bits::AllOnes(lsb_xs)})))}); } - TernaryVector vec(tern.size() - lsb_xs, TernaryValue::kKnownZero); - // Copy input ternary from after the last lsb_x. - std::copy(tern.cbegin() + lsb_xs, tern.cend(), vec.begin()); - - Bits high_lsb = Bits::AllOnes(lsb_xs); - Bits low_lsb(lsb_xs); - for (const Bits& v : ternary_ops::AllBitsValues(vec)) { - is.AddInterval( - Interval::Closed(bits_ops::UMax(lb, bits_ops::Concat({v, low_lsb})), - bits_ops::UMin(ub, bits_ops::Concat({v, high_lsb})))); + std::vector intervals; + intervals.reserve(uint64_t{1} << x_locations.size()); + Bits lsbs_low(lsb_xs); + Bits lsbs_high = Bits::AllOnes(lsb_xs); + for (const Bits& v : ternary_ops::AllBitsValues(prefix)) { + // Since prefix's LSB is known (see above), the intervals we create here + // will never abut. + intervals.push_back( + Interval::Closed(bits_ops::UMax(lb, bits_ops::Concat({v, lsbs_low})), + bits_ops::UMin(ub, bits_ops::Concat({v, lsbs_high})))); } - is.Normalize(); - return is; + return IntervalSet::UnsafeFromNormalized(tern.size(), std::move(intervals)); } bool CoversTernary(const Interval& interval, TernarySpan ternary) { @@ -1509,7 +1517,7 @@ IntervalSet Xor(const IntervalSet& a, const IntervalSet& b) { IntervalSet AndReduce(const IntervalSet& a) { if (a.IsEmpty()) { // If the input is empty, so is the output. - return IntervalSet(a.BitCount()); + return IntervalSet(/*bit_count=*/1); } // Unless the intervals cover max, the and_reduce of the input must be 0. if (!a.CoversMax()) { @@ -1526,7 +1534,7 @@ IntervalSet AndReduce(const IntervalSet& a) { IntervalSet OrReduce(const IntervalSet& a) { if (a.IsEmpty()) { // If the input is empty, so is the output. - return IntervalSet(a.BitCount()); + return IntervalSet(/*bit_count=*/1); } // Unless the intervals cover 0, the or_reduce of the input must be 1. if (!a.CoversZero()) { @@ -1542,7 +1550,7 @@ IntervalSet OrReduce(const IntervalSet& a) { IntervalSet XorReduce(const IntervalSet& a) { if (a.IsEmpty()) { // If the input is empty, so is the output. - return IntervalSet(a.BitCount()); + return IntervalSet(/*bit_count=*/1); } // XorReduce determines the parity of the number of 1s in a bitstring. // Incrementing a bitstring always outputs in a bitstring with a different @@ -1594,7 +1602,7 @@ IntervalSet ULt(const IntervalSet& a, const IntervalSet& b) { CHECK_EQ(a.BitCount(), b.BitCount()); if (a.IsEmpty() || b.IsEmpty()) { // If the input is empty, so is the output. - return IntervalSet(a.BitCount()); + return IntervalSet(/*bit_count=*/1); } if (a.IsPrecise() && a.GetPreciseValue() == Bits::AllOnes(a.BitCount())) { // If a is all ones, then it is not less than any value. @@ -1623,7 +1631,7 @@ IntervalSet SLt(const IntervalSet& a, const IntervalSet& b) { CHECK_EQ(a.BitCount(), b.BitCount()); if (a.IsEmpty() || b.IsEmpty()) { // If the input is empty, so is the output. - return IntervalSet(a.BitCount()); + return IntervalSet(/*bit_count=*/1); } CHECK(a.IsNormalized()); CHECK(b.IsNormalized()); diff --git a/xls/ir/interval_ops_test.cc b/xls/ir/interval_ops_test.cc index aee6e03821..66cc7fc464 100644 --- a/xls/ir/interval_ops_test.cc +++ b/xls/ir/interval_ops_test.cc @@ -216,6 +216,12 @@ TEST(IntervalOpsTest, FromTernarySegmentsExtended) { FromRanges({{0b10101000, 0b11111101}}, 8)); } +TEST(IntervalOpsTest, FromTernaryAdjacentUnknownBits) { + // Test case where advancing lsb_xs encounters adjacent unknown bits. + EXPECT_EQ(FromTernaryString("0b0XX0XX", /*max_unknown_bits=*/1), + FromRanges({{0b000000, 0b011011}}, 6)); +} + TEST(IntervalOpsTest, ExactResultsForSmallRanges) { // Only 8 possible multiplies so try them all. IntervalSet lhs = FromRanges({{1234, 1235}}, 64); @@ -1507,6 +1513,45 @@ FUZZ_TEST(IntervalOpsTest, OneHotZ3Fuzz) .WithDomains(IntervalDomain(8), fuzztest::ElementOf({LsbOrMsb::kLsb, LsbOrMsb::kMsb})); +TEST(IntervalOpsTest, EmptyAndReduce) { + EXPECT_EQ(AndReduce(IntervalSet(/*bit_count=*/5)), FromRanges({}, 1)); +} + +void AndReduceZ3Fuzz(absl::Span const> lhs) { + UnaryOpFuzz( + "and_reduce", + [&](FunctionBuilder& fb, BValue l) { return fb.AndReduce(l); }, + [&](const auto& l) { return AndReduce(l); }, lhs, + /*bits=*/8); +} +FUZZ_TEST(IntervalOpsTest, AndReduceZ3Fuzz).WithDomains(IntervalDomain(8)); + +TEST(IntervalOpsTest, EmptyOrReduce) { + EXPECT_EQ(OrReduce(IntervalSet(/*bit_count=*/5)), FromRanges({}, 1)); +} + +void OrReduceZ3Fuzz(absl::Span const> lhs) { + UnaryOpFuzz( + "or_reduce", + [&](FunctionBuilder& fb, BValue l) { return fb.OrReduce(l); }, + [&](const auto& l) { return OrReduce(l); }, lhs, + /*bits=*/8); +} +FUZZ_TEST(IntervalOpsTest, OrReduceZ3Fuzz).WithDomains(IntervalDomain(8)); + +TEST(IntervalOpsTest, EmptyXorReduce) { + EXPECT_EQ(XorReduce(IntervalSet(/*bit_count=*/5)), FromRanges({}, 1)); +} + +void XorReduceZ3Fuzz(absl::Span const> lhs) { + UnaryOpFuzz( + "xor_reduce", + [&](FunctionBuilder& fb, BValue l) { return fb.XorReduce(l); }, + [&](const auto& l) { return XorReduce(l); }, lhs, + /*bits=*/8); +} +FUZZ_TEST(IntervalOpsTest, XorReduceZ3Fuzz).WithDomains(IntervalDomain(8)); + TEST(IntervalOpsTest, ReduceIntervalFragmentation) { // Create a set with 10 separate intervals. IntervalSet lhs = FromValues({0, 2, 4, 6, 8, 10, 12, 14, 16, 18}, 8); diff --git a/xls/ir/partial_information.cc b/xls/ir/partial_information.cc index 2456f89835..65807c18d9 100644 --- a/xls/ir/partial_information.cc +++ b/xls/ir/partial_information.cc @@ -204,6 +204,22 @@ int64_t PartialInformation::KnownLeadingSignBits() const { return 1 + bit_count_ - interval_ops::MinimumSignedBitCount(*range_); } +int64_t PartialInformation::KnownLeadingBits() const { + if (IsImpossible()) { + return 0; + } + if (IsUnconstrained()) { + return 0; + } + if (!ternary_) { + return 0; + } + return std::find_if( + ternary_->rbegin(), ternary_->rend(), + [](TernaryValue v) { return v == TernaryValue::kUnknown; }) - + ternary_->rbegin(); +} + int64_t PartialInformation::MaxPopCount() const { if (IsImpossible()) { return 0; diff --git a/xls/ir/partial_information.h b/xls/ir/partial_information.h index 08896f64ff..2073a457d8 100644 --- a/xls/ir/partial_information.h +++ b/xls/ir/partial_information.h @@ -140,6 +140,9 @@ class PartialInformation { // Gets the number of leading sign bits known. int64_t KnownLeadingSignBits() const; + // Gets the number of leading (high bit) bits known. + int64_t KnownLeadingBits() const; + // Returns an upper bound on the popcount of any value that can satisfy this // PartialInformation. int64_t MaxPopCount() const; diff --git a/xls/passes/BUILD b/xls/passes/BUILD index 75b509f0e3..e30a4c267b 100644 --- a/xls/passes/BUILD +++ b/xls/passes/BUILD @@ -946,6 +946,7 @@ cc_library( srcs = ["back_propagate_range_analysis.cc"], hdrs = ["back_propagate_range_analysis.h"], deps = [ + ":query_engine", ":range_query_engine", "//xls/common/status:ret_check", "//xls/common/status:status_macros", @@ -1816,6 +1817,7 @@ cc_library( "//xls/data_structures:leaf_type_tree", "//xls/ir", "//xls/ir:bits", + "//xls/ir:bits_ops", "//xls/ir:interval", "//xls/ir:interval_ops", "//xls/ir:interval_set", @@ -2470,8 +2472,10 @@ xls_pass( "//xls/ir", "//xls/ir:bits", "//xls/ir:interval_ops", + "//xls/ir:interval_set", "//xls/ir:node_util", "//xls/ir:op", + "//xls/ir:partial_info", "//xls/ir:state_element", "//xls/ir:ternary", "//xls/ir:type", @@ -4446,9 +4450,6 @@ cc_library( ":partial_info_query_engine", ":predicate_state", ":query_engine", - ":range_query_engine", - ":ternary_query_engine", - ":union_query_engine", "//xls/common/status:ret_check", "//xls/common/status:status_macros", "//xls/data_structures:leaf_type_tree", @@ -4457,14 +4458,12 @@ cc_library( "//xls/ir:bits", "//xls/ir:bits_ops", "//xls/ir:interval", - "//xls/ir:interval_ops", "//xls/ir:interval_set", "//xls/ir:op", "//xls/ir:partial_info", "//xls/ir:state_element", "//xls/ir:ternary", "//xls/ir:type", - "//xls/ir:value", "//xls/ir:value_utils", "@abseil-cpp//absl/algorithm:container", "@abseil-cpp//absl/container:btree", @@ -4476,6 +4475,7 @@ cc_library( "@abseil-cpp//absl/status:statusor", "@abseil-cpp//absl/strings", "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/types:optional_ref", "@abseil-cpp//absl/types:span", ], ) @@ -4488,12 +4488,14 @@ cc_test( ":range_query_engine", "//xls/common:xls_gunit_main", "//xls/common/status:matchers", + "//xls/data_structures:leaf_type_tree", "//xls/ir", "//xls/ir:bits", "//xls/ir:channel", "//xls/ir:channel_ops", "//xls/ir:function_builder", "//xls/ir:ir_test_base", + "//xls/ir:ternary", "//xls/ir:value", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/strings:str_format", diff --git a/xls/passes/back_propagate_range_analysis.cc b/xls/passes/back_propagate_range_analysis.cc index e0fab406e8..31085f6a72 100644 --- a/xls/passes/back_propagate_range_analysis.cc +++ b/xls/passes/back_propagate_range_analysis.cc @@ -41,6 +41,7 @@ #include "xls/ir/op.h" #include "xls/ir/topo_sort.h" #include "xls/ir/type.h" +#include "xls/passes/query_engine.h" #include "xls/passes/range_query_engine.h" namespace xls { @@ -54,7 +55,7 @@ namespace { // based on that node and (2) the inputs to that node have updated information. class BackPropagate : public DfsVisitorWithDefault { public: - explicit BackPropagate(const RangeQueryEngine& query_engine, + explicit BackPropagate(const QueryEngine& query_engine, absl::flat_hash_map givens) : query_engine_(query_engine), result_(std::move(givens)) { for (const auto& [node, _] : result_) { @@ -240,11 +241,7 @@ class BackPropagate : public DfsVisitorWithDefault { if (result_.contains(node)) { return result_[node]; } - if (query_engine_.HasExplicitIntervals(node)) { - // Try to avoid allocating LTTs needlessly. - return query_engine_.GetIntervalSetTreeView(node)->Get({}); - } - return query_engine_.GetIntervalSetTree(node).Get({}); + return query_engine_.GetIntervals(node).Get({}); } // Merge the given 'new_data' with the already known facts about the given @@ -254,7 +251,7 @@ class BackPropagate : public DfsVisitorWithDefault { XLS_RET_CHECK(node->GetType()->IsBits()); XLS_RET_CHECK(new_data.IsNormalized()); if (!result_.contains(node)) { - result_[node] = query_engine_.GetIntervalSetTree(node).Get({}); + result_[node] = GetIntervals(node); } IntervalSet old_data = std::move(result_[node]); result_[node] = IntervalSet::Intersect(old_data, new_data); @@ -590,7 +587,7 @@ class BackPropagate : public DfsVisitorWithDefault { } // Underlying query-engine providing base ranges. - const RangeQueryEngine& query_engine_; + const QueryEngine& query_engine_; // Set of all givens and any calculated refined ranges. absl::flat_hash_map result_; // Set of nodes which we have updated data for which might be possible to @@ -602,7 +599,7 @@ class BackPropagate : public DfsVisitorWithDefault { absl::StatusOr> PropagateGivensBackwards( - const RangeQueryEngine& engine, FunctionBase* function, + const QueryEngine& engine, FunctionBase* function, absl::flat_hash_map givens, std::optional> reverse_topo_sort) { XLS_RET_CHECK(!givens.empty()); @@ -629,7 +626,7 @@ PropagateGivensBackwards( } absl::StatusOr> -PropagateOneGivenBackwards(const RangeQueryEngine& engine, Node* node, +PropagateOneGivenBackwards(const QueryEngine& engine, Node* node, const Bits& given) { return PropagateOneGivenBackwards(engine, node, IntervalSet::Precise(given)); } diff --git a/xls/passes/back_propagate_range_analysis.h b/xls/passes/back_propagate_range_analysis.h index faa46592ef..91e51395f8 100644 --- a/xls/passes/back_propagate_range_analysis.h +++ b/xls/passes/back_propagate_range_analysis.h @@ -25,7 +25,7 @@ #include "xls/ir/function_base.h" #include "xls/ir/interval_set.h" #include "xls/ir/node.h" -#include "xls/passes/range_query_engine.h" +#include "xls/passes/query_engine.h" namespace xls { @@ -40,7 +40,7 @@ namespace xls { // another). absl::StatusOr> PropagateGivensBackwards( - const RangeQueryEngine& engine, FunctionBase* function, + const QueryEngine& engine, FunctionBase* function, absl::flat_hash_map given, std::optional> reverse_topo_sort = std::nullopt); @@ -48,14 +48,14 @@ PropagateGivensBackwards( // // Returns the data extracted from analyzing the given computation. inline absl::StatusOr> -PropagateOneGivenBackwards(const RangeQueryEngine& engine, Node* node, +PropagateOneGivenBackwards(const QueryEngine& engine, Node* node, const IntervalSet& given) { return PropagateGivensBackwards(engine, node->function_base(), {{node, given}}); } absl::StatusOr> -PropagateOneGivenBackwards(const RangeQueryEngine& engine, Node* node, +PropagateOneGivenBackwards(const QueryEngine& engine, Node* node, const Bits& given); } // namespace xls diff --git a/xls/passes/bdd_query_engine.cc b/xls/passes/bdd_query_engine.cc index 47d256db41..effa5891a0 100644 --- a/xls/passes/bdd_query_engine.cc +++ b/xls/passes/bdd_query_engine.cc @@ -670,16 +670,12 @@ std::unique_ptr BddQueryEngine::SpecializeGiven( value_knowledge.intervals->AsView(), [&](Type*, const IntervalSet& intervals, absl::Span tree_index) -> absl::Status { - std::vector bits; - bits.reserve(intervals.BitCount()); - for (int64_t i = 0; i < intervals.BitCount(); ++i) { - std::optional bit = - GetBddNode(TreeBitLocation(node, i, tree_index)); - if (!bit.has_value()) { - return absl::OkStatus(); - } - bits.push_back(*bit); + std::optional info = GetInfo(node); + if (!info.has_value()) { + return absl::OkStatus(); } + absl::Span bits = + info->Get(tree_index); SaturatingBddNodeVector in_interval_checks; for (const Interval& interval : intervals.Intervals()) { diff --git a/xls/passes/narrowing_pass.cc b/xls/passes/narrowing_pass.cc index ffc02b5967..c2dd4a11e2 100644 --- a/xls/passes/narrowing_pass.cc +++ b/xls/passes/narrowing_pass.cc @@ -2195,20 +2195,22 @@ absl::StatusOr GetQueryEngine( unowned_engines.push_back(context.SharedQueryEngine(f)); if (analysis == AnalysisType::kRangeWithContext) { if (ProcStateRangeQueryEngine::CanAnalyzeProcStateEvolution(f)) { - // NB ProcStateRange already includes a ternary qe + // NB ProcStateRange already includes a PartialInfoQueryEngine qe owned_engines.push_back(std::make_unique()); + } else { + unowned_engines.push_back( + context.SharedQueryEngine(f)); } - unowned_engines.push_back( - context.SharedQueryEngine(f)); owned_engines.push_back( std::make_unique()); } else if (analysis == AnalysisType::kRange) { if (ProcStateRangeQueryEngine::CanAnalyzeProcStateEvolution(f)) { // NB ProcStateRange already includes a ternary qe owned_engines.push_back(std::make_unique()); + } else { + unowned_engines.push_back( + context.SharedQueryEngine(f)); } - unowned_engines.push_back( - context.SharedQueryEngine(f)); } else { CHECK_EQ(analysis, AnalysisType::kTernary); unowned_engines.push_back( diff --git a/xls/passes/partial_info_query_engine.cc b/xls/passes/partial_info_query_engine.cc index e2a3e031e3..4550d1d68e 100644 --- a/xls/passes/partial_info_query_engine.cc +++ b/xls/passes/partial_info_query_engine.cc @@ -35,6 +35,7 @@ #include "xls/common/status/status_macros.h" #include "xls/data_structures/leaf_type_tree.h" #include "xls/ir/bits.h" +#include "xls/ir/bits_ops.h" #include "xls/ir/interval.h" #include "xls/ir/interval_ops.h" #include "xls/ir/interval_set.h" @@ -756,10 +757,7 @@ std::optional PartialInfoQueryEngine::GetTernary( Node* node) const { std::optional> info_tree = GetInfo(node); - if (!info_tree.has_value() || - absl::c_all_of(info_tree->elements(), [](const PartialInformation& info) { - return !info.Ternary().has_value(); - })) { + if (!info_tree.has_value()) { return std::nullopt; } absl::InlinedVector ternary_elements; @@ -926,4 +924,24 @@ std::optional PartialInfoQueryEngine::KnownLeadingSignBits( return info_tree->Get({}).KnownLeadingSignBits(); } +Bits PartialInfoQueryEngine::MinUnsignedValue(Node* node) const { + Bits bit_bound = QueryEngine::MinUnsignedValue(node); + IntervalSetTree intervals = GetIntervals(node); + std::optional lb = intervals.Get({}).LowerBound(); + if (lb.has_value()) { + return bits_ops::UMax(*lb, bit_bound); + } + return bit_bound; +} + +Bits PartialInfoQueryEngine::MaxUnsignedValue(Node* node) const { + Bits bit_bound = QueryEngine::MaxUnsignedValue(node); + IntervalSetTree intervals = GetIntervals(node); + std::optional ub = intervals.Get({}).UpperBound(); + if (ub.has_value()) { + return bits_ops::UMin(*ub, bit_bound); + } + return bit_bound; +} + } // namespace xls diff --git a/xls/passes/partial_info_query_engine.h b/xls/passes/partial_info_query_engine.h index 6b2d9db607..5cb7195828 100644 --- a/xls/passes/partial_info_query_engine.h +++ b/xls/passes/partial_info_query_engine.h @@ -44,6 +44,9 @@ class PartialInfoQueryEngine : public LazyQueryEngine { const TreeBitLocation& b) const override; bool Covers(Node* node, const Bits& value) const override; + Bits MinUnsignedValue(Node* node) const override; + Bits MaxUnsignedValue(Node* node) const override; + // Returns true if at most/at least/exactly one of the bits in 'node' is true. // 'node' must be bits-typed. bool AtMostOneBitTrue(Node* node) const override; diff --git a/xls/passes/proc_state_analysis.cc b/xls/passes/proc_state_analysis.cc index 4a17f93b6f..f0d8b18f95 100644 --- a/xls/passes/proc_state_analysis.cc +++ b/xls/passes/proc_state_analysis.cc @@ -28,7 +28,6 @@ #include "xls/passes/proc_state_range_query_engine.h" #include "xls/passes/query_engine.h" #include "xls/solvers/solver.h" -#include "xls/solvers/z3_ir_translator.h" namespace xls { diff --git a/xls/passes/proc_state_narrowing_pass.cc b/xls/passes/proc_state_narrowing_pass.cc index 009e0fcedf..b228260a0c 100644 --- a/xls/passes/proc_state_narrowing_pass.cc +++ b/xls/passes/proc_state_narrowing_pass.cc @@ -31,10 +31,12 @@ #include "xls/data_structures/leaf_type_tree.h" #include "xls/ir/bits.h" #include "xls/ir/interval_ops.h" +#include "xls/ir/interval_set.h" #include "xls/ir/node.h" #include "xls/ir/node_util.h" #include "xls/ir/nodes.h" #include "xls/ir/op.h" +#include "xls/ir/partial_information.h" #include "xls/ir/proc.h" #include "xls/ir/state_element.h" #include "xls/ir/ternary.h" @@ -161,17 +163,17 @@ absl::StatusOr ProcStateNarrowingPass::RunOnProcInternal( continue; } StateRead* state_read = proc->GetStateReadByStateElement(state_element); - std::optional> ternary = - qe.GetTernary(state_read); - if (!ternary) { + std::optional> info_ltt = + qe.GetInfo(state_read); + if (!info_ltt.has_value()) { continue; } - int64_t known_leading = - ternary_ops::ToKnownBits(ternary->Get({})).CountLeadingOnes(); + const PartialInformation& info = info_ltt->Get({}); + int64_t known_leading = info.KnownLeadingBits(); if (known_leading != 0) { // TODO(allight): We could also narrow internal/trailing bits. TernarySpan known_leading_tern = - absl::MakeConstSpan(ternary->Get({})).last(known_leading); + absl::MakeConstSpan(*info.Ternary()).last(known_leading); XLS_RET_CHECK(ternary_ops::IsFullyKnown(known_leading_tern)); Value orig_init_value = state_element->initial_value(); VLOG(2) << "Narrowing state_read " << state_read << " from " @@ -185,8 +187,11 @@ absl::StatusOr ProcStateNarrowingPass::RunOnProcInternal( made_changes = true; continue; } - int64_t signed_bits = interval_ops::MinimumSignedBitCount( - qe.GetIntervals(state_read).Get({})); + if (!info.Range().has_value()) { + continue; + } + const IntervalSet& intervals = *info.Range(); + int64_t signed_bits = interval_ops::MinimumSignedBitCount(intervals); int64_t signed_bits_removed = state_read->BitCountOrDie() - signed_bits; if (signed_bits_removed != 0) { Value orig_init_value = state_element->initial_value(); diff --git a/xls/passes/proc_state_range_query_engine.cc b/xls/passes/proc_state_range_query_engine.cc index f65623a14f..df35baf23e 100644 --- a/xls/passes/proc_state_range_query_engine.cc +++ b/xls/passes/proc_state_range_query_engine.cc @@ -30,6 +30,7 @@ #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" +#include "absl/types/optional_ref.h" #include "absl/types/span.h" #include "xls/common/status/ret_check.h" #include "xls/common/status/status_macros.h" @@ -39,7 +40,6 @@ #include "xls/ir/bits_ops.h" #include "xls/ir/function_base.h" #include "xls/ir/interval.h" -#include "xls/ir/interval_ops.h" #include "xls/ir/interval_set.h" #include "xls/ir/node.h" #include "xls/ir/nodes.h" @@ -50,99 +50,25 @@ #include "xls/ir/ternary.h" #include "xls/ir/topo_sort.h" #include "xls/ir/type.h" -#include "xls/ir/value.h" #include "xls/ir/value_utils.h" #include "xls/passes/back_propagate_range_analysis.h" #include "xls/passes/dataflow_visitor.h" #include "xls/passes/node_dependency_analysis.h" #include "xls/passes/partial_info_query_engine.h" #include "xls/passes/query_engine.h" -#include "xls/passes/range_query_engine.h" -#include "xls/passes/ternary_query_engine.h" namespace xls { namespace { -class ProcStateGivens : public RangeDataProvider, public TernaryDataProvider { - public: - ProcStateGivens(Proc* proc, absl::flat_hash_map intervals) - : proc_(proc), intervals_(std::move(intervals)) {} - absl::Status IterateFunction(DfsVisitor* visitor) override { - return proc_->Accept(visitor); - } - - std::optional GetKnownIntervals(Node* node) final { - if (intervals_.contains(node) && !intervals_.at(node).IsEmpty()) { - return RangeData{ - .ternary = interval_ops::ExtractTernaryVector(intervals_.at(node)), - .interval_set = IntervalSetTree::CreateSingleElementTree( - node->GetType(), intervals_.at(node))}; - } - return std::nullopt; - } - - std::optional> GetKnownTernary( - Node* node) const final { - if (intervals_.contains(node) && !intervals_.at(node).IsEmpty()) { - CHECK(node->GetType()->IsBits()); - return LeafTypeTree( - node->GetType(), - interval_ops::ExtractTernaryVector(intervals_.at(node))); - } - return std::nullopt; - } - - private: - Proc* proc_; - absl::flat_hash_map intervals_; -}; - -// A givens that restricts the iteration to only values that hit the proc-state -// directly. -class ProcStateEvolutionGivens : public ProcStateGivens { - public: - ProcStateEvolutionGivens(absl::Span reverse_topo_sort, - Node* target, - absl::flat_hash_map intervals, - const absl::flat_hash_set& interesting_nodes) - : ProcStateGivens(target->function_base()->AsProcOrDie(), - std::move(intervals)), - reverse_topo_sort_(reverse_topo_sort), - target_(target), - interesting_nodes_(interesting_nodes) {} - - absl::Status IterateFunction(DfsVisitor* visitor) final { - for (auto it = reverse_topo_sort_.crbegin(); - it != reverse_topo_sort_.crend(); ++it) { - // Don't bother filling in information for nodes which don't lead to the - // 'next' we're looking at. - if (interesting_nodes_.contains(*it)) { - XLS_RETURN_IF_ERROR((*it)->VisitSingleNode(visitor)) << *it; - } - if (*it == target_) { - // We got the actual next value, no need to continue; - break; - } - } - return absl::OkStatus(); - } - - private: - absl::Span reverse_topo_sort_; - Node* target_; - const absl::flat_hash_set& interesting_nodes_; -}; - -absl::StatusOr>> -ExtractContextSensitiveRange( - Proc* proc, Next* next, const RangeQueryEngine& rqe, +absl::StatusOr> ExtractContextSensitiveRange( + Proc* proc, Next* next, const QueryEngine& qe, absl::Span reverse_topo_sort, const NodeForwardDependencyAnalysis& next_dependent_information) { Node* pred = *next->predicate(); XLS_ASSIGN_OR_RETURN( (absl::flat_hash_map results), - PropagateGivensBackwards(rqe, proc, + PropagateGivensBackwards(qe, proc, {{pred, IntervalSet::Precise(UBits(1, 1))}}, reverse_topo_sort)); // Check if anything interesting was found. @@ -151,7 +77,7 @@ ExtractContextSensitiveRange( const auto& [node, interval] = entry; return node == pred || node->Is() || interval.IsMaximal() || - interval == rqe.GetIntervals(node).Get({}); + interval == qe.GetIntervals(node).Get({}); })) { // Nothing except for literals, unconstrained values or already discovered // values found. There's no point in doing anything more. @@ -161,20 +87,27 @@ ExtractContextSensitiveRange( // TODO(allight): A heuristic to avoid doing this in some cases (such as none // of the discovered facts are in the predecessors of value) might be // worthwhile here. - absl::flat_hash_set dependencies = - next_dependent_information.NodesDependedOnBy(next); - ProcStateEvolutionGivens givens(reverse_topo_sort, next->value(), - std::move(results), dependencies); - RangeQueryEngine contextual_range; - XLS_RETURN_IF_ERROR(contextual_range.PopulateWithGivens(givens).status()); - std::optional> ternary = - contextual_range.GetTernary(next->value()); - TernaryVector ternary_vec = - ternary.has_value() ? ternary->Get({}) - : TernaryVector(next->value()->BitCountOrDie(), - TernaryValue::kUnknown); - return std::make_pair(ternary_vec, - contextual_range.GetIntervals(next->value()).Get({})); + absl::flat_hash_map> givens; + givens.reserve(results.size()); + for (const auto& [node, interval_set] : results) { + if (node->GetType()->IsBits() && !interval_set.IsEmpty()) { + PartialInformation info(interval_set); + if (info.IsImpossible()) { + return std::nullopt; + } + givens[node] = LeafTypeTree::CreateSingleElementTree( + node->GetType(), std::move(info)); + } + } + PartialInfoQueryEngine contextual_range; + XLS_RETURN_IF_ERROR( + contextual_range.PopulateWithGivens(proc, std::move(givens)).status()); + std::optional> info_tree = + contextual_range.GetInfo(next->value()); + if (info_tree.has_value()) { + return info_tree->Get({}); + } + return PartialInformation::Unconstrained(next->value()->BitCountOrDie()); } bool AbsoluteValueLessThan(const Bits& l, const Bits& r) { @@ -456,10 +389,11 @@ absl::StatusOr> FindConstantUpdateValues( // function is relatively dense and any entry into a segment makes the entire // segment live. This enables us to do this state exploration with a relatively // small number of runs. -absl::StatusOr> NarrowUsingSegments( +absl::StatusOr> NarrowUsingSegments( Proc* proc, StateElement* state_element, const IntervalSet& intervals, absl::Span topo_sort, const NodeForwardDependencyAnalysis& nda, - const absl::flat_hash_map& ground_truth) { + const absl::flat_hash_map& + ground_truth) { VLOG(3) << "Doing segment walk for " << state_element->ToString() << " on " << intervals; absl::flat_hash_set remaining_intervals( @@ -512,11 +446,11 @@ absl::StatusOr> NarrowUsingSegments( PartialInfoQueryEngine piqe; absl::flat_hash_map> givens; givens.reserve(ground_truth.size()); - for (const auto& [se, rd] : ground_truth) { + for (const auto& [se, info] : ground_truth) { StateRead* sr = proc->GetStateReadByStateElement(se); if (sr->GetType()->IsBits()) { givens[sr] = LeafTypeTree::CreateSingleElementTree( - sr->GetType(), PartialInformation(rd.interval_set.Get({}))); + sr->GetType(), info); } } XLS_RETURN_IF_ERROR( @@ -570,10 +504,7 @@ absl::StatusOr> NarrowUsingSegments( if (overlap == remaining_intervals.cend()) { // Didn't discover anything new. The current active intervals are the // final result. - return RangeData{ - .ternary = interval_ops::ExtractTernaryVector(active_intervals), - .interval_set = IntervalSetTree::CreateSingleElementTree( - state_element->type(), active_intervals)}; + return PartialInformation(active_intervals); } active_intervals.AddInterval(*overlap); active_intervals.Normalize(); @@ -585,9 +516,8 @@ absl::StatusOr> NarrowUsingSegments( return std::nullopt; } // Narrow ranges using the contextual information of the next predicates. -absl::StatusOr> -FindContextualRanges(Proc* proc, const QueryEngine& qe, - const RangeQueryEngine& rqe, +absl::StatusOr> +FindContextualRanges(Proc* proc, const PartialInfoQueryEngine& qe, const NodeForwardDependencyAnalysis& dependency_analysis, absl::Span reverse_topo_sort) { // List of all the next instructions that change the param for each param. @@ -613,27 +543,16 @@ FindContextualRanges(Proc* proc, const QueryEngine& qe, } // To avoid issues where changes to the param values leads to invalidating the // TernaryQueryEngine we do all the modifications at the end. - absl::flat_hash_map transforms; + absl::flat_hash_map transforms; for (const auto& [orig_state_element, updates] : modifying_nexts_for_state) { if (updates.empty()) { // The state only has identity updates? Strange but this will be cleaned // up by NextValueOptimizationPass so we can ignore it. continue; } - Value orig_init_value = orig_state_element->initial_value(); - TernaryVector possible_values = - ternary_ops::BitsToTernary(orig_init_value.bits()); - - IntervalSet contextual_intervals = - IntervalSet::Precise(orig_init_value.bits()); + PartialInformation info = + PartialInformation::Precise(orig_state_element->initial_value().bits()); for (Next* next : updates) { - std::optional> context_free_ltt = - qe.GetTernary(next->value()); - TernaryVector context_free = - context_free_ltt.has_value() - ? context_free_ltt->Get({}) - : TernaryVector(next->value()->BitCountOrDie(), - TernaryValue::kUnknown); // NB Only doing context-sensitive range analysis is a heuristic to avoid // performing the (somewhat) expensive range propagation when we have // already narrowed using static analysis. While its possible that better @@ -644,42 +563,36 @@ FindContextualRanges(Proc* proc, const QueryEngine& qe, // TODO(allight): Once signed bounds are supported better we should check // this again and determine more precisely the sort of performance impact // always doing (non-trivial) range analysis would have. - if (ternary_ops::ToKnownBits(context_free).CountLeadingOnes() == 0 && - next->predicate()) { - // Context-free query engine wasn't able to narrow this at all and we do - // have additional information in the form of a predicate. Try again - // with contextual information. - XLS_ASSIGN_OR_RETURN( - (std::optional> - contextual_result), - ExtractContextSensitiveRange(proc, next, rqe, reverse_topo_sort, - dependency_analysis), - _ << next); - // Keep track of all the values that we can update to using ranges. - if (contextual_result) { - const auto& [contextual_tern, contextual_range] = *contextual_result; - possible_values = - ternary_ops::Intersection(possible_values, contextual_tern); - contextual_intervals = - IntervalSet::Combine(contextual_intervals, contextual_range); - } else { - possible_values = - ternary_ops::Intersection(possible_values, context_free); - contextual_intervals = IntervalSet::Combine( - contextual_intervals, interval_ops::FromTernary(context_free)); - } + + std::optional> context_free_ltt = + qe.GetInfo(next->value()); + absl::optional_ref context_free = + context_free_ltt.has_value() ? &context_free_ltt->Get({}) : nullptr; + if (!next->predicate() || + (context_free.has_value() && context_free->KnownLeadingBits() > 0)) { + // We don't have any contextual information to add, or else we can + // narrow using just context-free analysis. + info.MeetWith(*context_free); + continue; + } + + // Context-free query engine wasn't able to narrow this at all and we do + // have additional information in the form of a predicate. Try again + // with contextual information. + XLS_ASSIGN_OR_RETURN( + std::optional contextual_result, + ExtractContextSensitiveRange(proc, next, qe, reverse_topo_sort, + dependency_analysis), + _ << next); + // Keep track of all the values that we can update to using ranges. + if (contextual_result) { + info.MeetWith(*contextual_result); } else { - possible_values = - ternary_ops::Intersection(possible_values, context_free); - contextual_intervals = IntervalSet::Combine( - contextual_intervals, interval_ops::FromTernary(context_free)); + // No such luck; fall back to the context-free result. + info.MeetWith(*context_free); } } - transforms[orig_state_element] = RangeData{ - .ternary = possible_values, - .interval_set = IntervalSetTree::CreateSingleElementTree( - orig_state_element->type(), contextual_intervals), - }; + transforms.emplace(orig_state_element, std::move(info)); } return transforms; } @@ -693,7 +606,7 @@ FindContextualRanges(Proc* proc, const QueryEngine& qe, absl::StatusOr ProcStateRangeQueryEngine::Populate( FunctionBase* f) { // Start with a basic range and ternary analysis to get base cases. - XLS_ASSIGN_OR_RETURN(ReachedFixpoint fixpoint, inner_.Populate(f)); + XLS_ASSIGN_OR_RETURN(ReachedFixpoint fixpoint, inner_->Populate(f)); // If we aren't able to actually analyze proc-state we are done here. if (!ProcStateRangeQueryEngine::CanAnalyzeProcStateEvolution(f)) { return fixpoint; @@ -704,32 +617,25 @@ absl::StatusOr ProcStateRangeQueryEngine::Populate( ReverseTopoSort(proc)); std::vector topo_sort = reverse_topo_sort; absl::c_reverse(topo_sort); - // Get the nodes which actually affect the next-value nodes. We don't really - // care about anything else. - std::vector interesting_nodes; - interesting_nodes.reserve(2 * proc->next_values().size()); - for (Next* n : proc->next_values()) { - interesting_nodes.push_back(n); - interesting_nodes.push_back(n->value()); - } NodeForwardDependencyAnalysis next_node_sources; XLS_RETURN_IF_ERROR(next_node_sources.Attach(proc).status()); // TODO(allight): We could repeat the below and the loop until we hit a // fixed-point to fully incorporate all cross-param knowledge. This could be // quite slow however. - XLS_ASSIGN_OR_RETURN( - (absl::flat_hash_map initial_transforms), - FindContextualRanges(proc, inner_, *range_, next_node_sources, - reverse_topo_sort)); + XLS_ASSIGN_OR_RETURN((absl::flat_hash_map + initial_transforms), + FindContextualRanges(proc, *inner_, next_node_sources, + reverse_topo_sort)); // Find implied ranges for each param. Note that we consider each parameter in // isolation. Technically we could go to fixed-point and maybe get better // bounds but that could take a while. - absl::flat_hash_map final_range_data; - for (const auto& [orig_state_element, t] : initial_transforms) { - const auto& [ternary, interval_set] = t; + absl::flat_hash_map final_range_data; + for (const auto& [orig_state_element, info] : initial_transforms) { int64_t known_leading = - ternary_ops::ToKnownBits(*ternary).CountLeadingOnes(); + info.Ternary().has_value() + ? ternary_ops::ToKnownBits(*info.Ternary()).CountLeadingOnes() + : 0; // If we have known leading bits from the ternary analysis and only care // about state params use that. These are usually good enough except with // signed integer things (identified as only being able to eliminate the @@ -740,8 +646,8 @@ absl::StatusOr ProcStateRangeQueryEngine::Populate( << (orig_state_element->type()->GetFlatBitCount() - known_leading) << " bits (savings: " << known_leading << ") using back-prop/ternary. Interval is: " - << interval_set.Get({}); - final_range_data[orig_state_element] = t; + << info.RangeOrMaximal(); + final_range_data.emplace(orig_state_element, info); continue; } // Try for signed value compression. We *only* do this if there are no @@ -756,43 +662,45 @@ absl::StatusOr ProcStateRangeQueryEngine::Populate( // can move from one partition to another cutting down in the possible // values. XLS_ASSIGN_OR_RETURN( - std::optional narrowed, - NarrowUsingSegments(proc, orig_state_element, interval_set.Get({}), + std::optional narrowed, + NarrowUsingSegments(proc, orig_state_element, info.RangeOrMaximal(), topo_sort, next_node_sources, initial_transforms)); if (narrowed) { - VLOG(2) - << "Narrowed range of " << orig_state_element->ToString() << " to " - << (orig_state_element->type()->GetFlatBitCount() - - ternary_ops::ToKnownBits(*narrowed->ternary).CountLeadingOnes()) - << " bits (savings: " - << ternary_ops::ToKnownBits(*narrowed->ternary).CountLeadingOnes() - << ") using segment walking. Interval is " - << narrowed->interval_set.Get({}); - final_range_data[orig_state_element] = *narrowed; + int64_t narrowed_known_leading = + narrowed->Ternary().has_value() + ? ternary_ops::ToKnownBits(*narrowed->Ternary()) + .CountLeadingOnes() + : 0; + VLOG(2) << "Narrowed range of " << orig_state_element->ToString() + << " to " + << (orig_state_element->type()->GetFlatBitCount() - + narrowed_known_leading) + << " bits (savings: " << narrowed_known_leading + << ") using segment walking. Interval is " + << narrowed->RangeOrMaximal(); + final_range_data.emplace(orig_state_element, *narrowed); } else { VLOG(2) << "Unable to narrow range " << orig_state_element->ToString() << ". Segment walking unable to eliminate high bits. Interval is " - << interval_set.Get({}); + << info.RangeOrMaximal(); } } // We now have intervals for all params. We run one more query-engine run to // get final results. - TernaryQueryEngine spec_ternary; - RangeQueryEngine spec_range; - - absl::flat_hash_map state_read_intervals; - state_read_intervals.reserve(final_range_data.size()); - for (const auto& [state_element, range] : final_range_data) { - state_read_intervals[proc->GetStateReadByStateElement(state_element)] = - range.interval_set.Get({}); + absl::flat_hash_map> givens; + givens.reserve(final_range_data.size()); + for (const auto& [state_element, info] : final_range_data) { + StateRead* sr = proc->GetStateReadByStateElement(state_element); + if (sr->GetType()->IsBits() && !info.IsImpossible()) { + givens[sr] = LeafTypeTree::CreateSingleElementTree( + sr->GetType(), info); + } } - ProcStateGivens givens(proc, std::move(state_read_intervals)); - XLS_RETURN_IF_ERROR(spec_ternary.PopulateWithGivens(proc, givens).status()); - XLS_RETURN_IF_ERROR(spec_range.PopulateWithGivens(givens).status()); - - *ternary_ = std::move(spec_ternary); - *range_ = std::move(spec_range); + auto spec_piqe = std::make_unique(); + XLS_RETURN_IF_ERROR( + spec_piqe->PopulateWithGivens(proc, std::move(givens)).status()); + inner_ = std::move(spec_piqe); return ReachedFixpoint::Changed; } diff --git a/xls/passes/proc_state_range_query_engine.h b/xls/passes/proc_state_range_query_engine.h index 4a3b956c85..32ba4a8073 100644 --- a/xls/passes/proc_state_range_query_engine.h +++ b/xls/passes/proc_state_range_query_engine.h @@ -23,18 +23,16 @@ #include "absl/log/check.h" #include "absl/status/statusor.h" #include "absl/types/span.h" -#include "xls/common/status/ret_check.h" #include "xls/data_structures/leaf_type_tree.h" #include "xls/ir/bits.h" #include "xls/ir/function_base.h" #include "xls/ir/interval_set.h" #include "xls/ir/node.h" +#include "xls/ir/partial_information.h" #include "xls/ir/ternary.h" +#include "xls/passes/partial_info_query_engine.h" #include "xls/passes/predicate_state.h" #include "xls/passes/query_engine.h" -#include "xls/passes/range_query_engine.h" -#include "xls/passes/ternary_query_engine.h" -#include "xls/passes/union_query_engine.h" namespace xls { @@ -51,9 +49,7 @@ class ProcStateRangeQueryEngine final : public QueryEngine { // some bounds on the proc state elements themselves. This is useful for (eg) // proc_state_narrowing. ProcStateRangeQueryEngine() - : ternary_(std::make_unique()), - range_(std::make_unique()), - inner_(UnownedUnionQueryEngine({ternary_.get(), range_.get()})) {} + : inner_(std::make_unique()) {} ProcStateRangeQueryEngine(ProcStateRangeQueryEngine&&) = default; ProcStateRangeQueryEngine(const ProcStateRangeQueryEngine&) = delete; ProcStateRangeQueryEngine& operator=(const ProcStateRangeQueryEngine&) = @@ -65,99 +61,91 @@ class ProcStateRangeQueryEngine final : public QueryEngine { // information over normal range analysis. The query engine can still be // populated if this is false but it is no different than a union of ternary // and range analyses. - inline static bool CanAnalyzeProcStateEvolution(FunctionBase* f) { + static bool CanAnalyzeProcStateEvolution(FunctionBase* f) { return f->IsProc(); } + std::optional> GetInfo( + Node* node) const { + return inner_->GetInfo(node); + } + LeafTypeTree GetIntervals(Node* node) const override { - return inner_.GetIntervals(node); + return inner_->GetIntervals(node); } bool AtMostOneTrue(absl::Span bits) const override { - return inner_.AtMostOneTrue(bits); + return inner_->AtMostOneTrue(bits); } bool AtLeastOneTrue(absl::Span bits) const override { - return inner_.AtLeastOneTrue(bits); + return inner_->AtLeastOneTrue(bits); } bool KnownEquals(const TreeBitLocation& a, const TreeBitLocation& b) const override { - return inner_.KnownEquals(a, b); + return inner_->KnownEquals(a, b); } bool KnownNotEquals(const TreeBitLocation& a, const TreeBitLocation& b) const override { - return inner_.KnownNotEquals(a, b); + return inner_->KnownNotEquals(a, b); } bool Implies(const TreeBitLocation& a, const TreeBitLocation& b) const override { - return inner_.Implies(a, b); + return inner_->Implies(a, b); } std::optional ImpliedNodeValue( absl::Span> predicate_bit_values, Node* node) const override { - return inner_.ImpliedNodeValue(predicate_bit_values, node); + return inner_->ImpliedNodeValue(predicate_bit_values, node); } std::optional ImpliedNodeTernary( absl::Span> predicate_bit_values, Node* node) const override { - return inner_.ImpliedNodeTernary(predicate_bit_values, node); - } - - IntervalSetTree GetIntervalSetTree(Node* node) const { - CHECK(range_); - return range_->GetIntervalSetTree(node); - } - - absl::StatusOr GetIntervalSetTreeView(Node* node) const { - XLS_RET_CHECK(range_); - return range_->GetIntervalSetTreeView(node); + return inner_->ImpliedNodeTernary(predicate_bit_values, node); } bool AtMostOneBitTrue(Node* node) const override { - return inner_.AtMostOneBitTrue(node); + return inner_->AtMostOneBitTrue(node); } bool AtLeastOneBitTrue(Node* node) const override { - return inner_.AtLeastOneBitTrue(node); + return inner_->AtLeastOneBitTrue(node); } bool ExactlyOneBitTrue(Node* node) const override { - return inner_.ExactlyOneBitTrue(node); + return inner_->ExactlyOneBitTrue(node); } bool Covers(Node* n, const Bits& value) const override { - return inner_.Covers(n, value); + return inner_->Covers(n, value); } Bits MaxUnsignedValue(Node* n) const override { - return inner_.MaxUnsignedValue(n); + return inner_->MaxUnsignedValue(n); } Bits MinUnsignedValue(Node* n) const override { - return inner_.MinUnsignedValue(n); + return inner_->MinUnsignedValue(n); } // Returns whether any information is available for this node. - bool IsTracked(Node* node) const override { return inner_.IsTracked(node); } + bool IsTracked(Node* node) const override { return inner_->IsTracked(node); } std::optional> GetTernary( Node* node) const override { - return inner_.GetTernary(node); + return inner_->GetTernary(node); } std::unique_ptr SpecializeGivenPredicate( const absl::btree_set& state) const override { - return inner_.SpecializeGivenPredicate(state); + return inner_->SpecializeGivenPredicate(state); } private: - // Actual range results from the proc-state aware analysis. - std::unique_ptr ternary_; - std::unique_ptr range_; - UnownedUnionQueryEngine inner_; + std::unique_ptr inner_; }; } // namespace xls diff --git a/xls/passes/proc_state_range_query_engine_test.cc b/xls/passes/proc_state_range_query_engine_test.cc index cb5a57479e..494a129cf3 100644 --- a/xls/passes/proc_state_range_query_engine_test.cc +++ b/xls/passes/proc_state_range_query_engine_test.cc @@ -14,22 +14,29 @@ #include "xls/passes/proc_state_range_query_engine.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/log/check.h" #include "absl/strings/str_format.h" #include "xls/common/status/matchers.h" +#include "xls/data_structures/leaf_type_tree.h" #include "xls/ir/bits.h" #include "xls/ir/channel.h" #include "xls/ir/channel_ops.h" #include "xls/ir/function_builder.h" #include "xls/ir/ir_test_base.h" #include "xls/ir/proc.h" +#include "xls/ir/ternary.h" #include "xls/ir/value.h" #include "xls/passes/range_query_engine.h" namespace xls { namespace { +using ::testing::Eq; +using ::testing::Optional; +using ::testing::ResultOf; + class ProcStateRangeQueryEngineTest : public IrTestBase {}; TEST_F(ProcStateRangeQueryEngineTest, BasicNarrow) { @@ -216,5 +223,28 @@ TEST_F(ProcStateRangeQueryEngineTest, OneBitStateIsZero) { EXPECT_EQ(IntervalSetTreeToString(qe.GetIntervals(state.node())), "[[0, 0]]"); } +TEST_F(ProcStateRangeQueryEngineTest, MixedBitwiseAndArithOperations) { + auto p = CreatePackage(); + ProcBuilder pb(TestName(), p.get()); + BValue state = pb.StateElement("the_state", UBits(0, 16)); + BValue masked = pb.And(state, pb.Literal(UBits(0x0FF0, 16))); + BValue nxt = pb.Add(masked, pb.Literal(UBits(16, 16))); + BValue cond = pb.ULt(state, pb.Literal(UBits(2000, 16))); + pb.Next(state, nxt, cond); + pb.Next(state, pb.Literal(UBits(0, 16)), pb.Not(cond)); + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, pb.Build()); + + ProcStateRangeQueryEngine qe; + XLS_ASSERT_OK(qe.Populate(proc).status()); + EXPECT_EQ(IntervalSetTreeToString(qe.GetIntervals(state.node())), + "[[0, 0], [16, 4096]]"); + EXPECT_THAT(qe.GetTernary(state.node()), + Optional(ResultOf( + [](const SharedLeafTypeTree& ltt) { + return ToString(ltt.Get({})); + }, + "0b000X_XXXX_XXXX_0000"))); +} + } // namespace } // namespace xls