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