/* * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ #include "opto/rangeinference.hpp" #include "opto/type.hpp" #include "runtime/os.hpp" #include "unittest.hpp" #include "utilities/intn_t.hpp" #include "utilities/rbTree.hpp" #include #include #include template static U uniform_random() { return U(juint(os::random())); } template <> julong uniform_random() { return (julong(os::random()) << 32) | julong(juint(os::random())); } static void test_canonicalize_constraints_trivial() { ASSERT_FALSE(TypeInt::NON_ZERO->contains(0)); ASSERT_TRUE(TypeInt::NON_ZERO->contains(1)); ASSERT_TRUE(TypeInt::NON_ZERO->contains(-1)); ASSERT_TRUE(TypeInt::CC_NE->contains(-1)); ASSERT_TRUE(TypeInt::CC_NE->contains(1)); ASSERT_FALSE(TypeInt::CC_NE->contains(0)); ASSERT_FALSE(TypeInt::CC_NE->contains(-2)); ASSERT_FALSE(TypeInt::CC_NE->contains(2)); ASSERT_FALSE(TypeLong::NON_ZERO->contains(jlong(0))); ASSERT_TRUE(TypeLong::NON_ZERO->contains(jlong(1))); ASSERT_TRUE(TypeLong::NON_ZERO->contains(jlong(-1))); } template static void test_canonicalize_constraints_exhaustive() { { TypeIntPrototype t{{S(0), S(0)}, {U(0), U(0)}, {U(-1), U(0)}}; auto new_t = t.canonicalize_constraints(); ASSERT_TRUE(new_t._present); DEBUG_ONLY(ASSERT_TRUE(t.contains(S(0)))); DEBUG_ONLY(ASSERT_FALSE(t.contains(S(1)))); } { TypeIntPrototype t{{S(0), S(0)}, {U(1), U(1)}, {U(-1), U(0)}}; auto new_t = t.canonicalize_constraints(); ASSERT_FALSE(new_t._present); DEBUG_ONLY(ASSERT_FALSE(t.contains(S(0)))); DEBUG_ONLY(ASSERT_FALSE(t.contains(S(1)))); } { TypeIntPrototype t{{S(S::min), S(S::max)}, {U(U::min), U(U::max)}, {U(0), U(0)}}; auto new_t = t.canonicalize_constraints(); ASSERT_TRUE(new_t._present); for (int v = S::min; v <= S::max; v++) { DEBUG_ONLY(ASSERT_TRUE(t.contains(S(v)))); } } for (int lo = S::min; lo <= S::max; lo++) { for (int hi = lo; hi <= S::max; hi++) { for (int ulo = U::min; ulo <= U::max; ulo++) { for (int uhi = ulo; uhi <= U::max; uhi++) { for (int zeros = U::min; zeros <= U::max; zeros++) { for (int ones = U::min; ones <= U::max; ones++) { TypeIntPrototype t{{S(lo), S(hi)}, {U(ulo), U(uhi)}, {U(zeros), U(ones)}}; auto new_t = t.canonicalize_constraints(); if (new_t._present) { DEBUG_ONLY(new_t._data.verify_constraints()); } for (int v = S::min; v <= S::max; v++) { if (!new_t._present) { DEBUG_ONLY(ASSERT_FALSE(t.contains(S(v)))); } else { DEBUG_ONLY(ASSERT_EQ(t.contains(S(v)), new_t._data.contains(S(v)))); } } } } } } } } } template static void test_canonicalize_constraints_simple() { constexpr int parameters = 1000; for (int i = 0; i < parameters; i++) { S a = uniform_random(); S b = uniform_random(); { S lo = MIN2(a, b); S hi = MAX2(a, b); TypeIntPrototype t{{lo, hi}, {std::numeric_limits::min(), std::numeric_limits::max()}, {0, 0}}; auto new_t = t.canonicalize_constraints(); ASSERT_TRUE(new_t._present); DEBUG_ONLY(new_t._data.verify_constraints()); ASSERT_EQ(lo, new_t._data._srange._lo); ASSERT_EQ(hi, new_t._data._srange._hi); if (U(lo) <= U(hi)) { ASSERT_EQ(U(lo), new_t._data._urange._lo); ASSERT_EQ(U(hi), new_t._data._urange._hi); } else { ASSERT_EQ(std::numeric_limits::min(), new_t._data._urange._lo); ASSERT_EQ(std::numeric_limits::max(), new_t._data._urange._hi); } } { U ulo = MIN2(a, b); U uhi = MAX2(a, b); TypeIntPrototype t{{std::numeric_limits::min(), std::numeric_limits::max()}, {ulo, uhi}, {0, 0}}; auto new_t = t.canonicalize_constraints(); ASSERT_TRUE(new_t._present); DEBUG_ONLY(new_t._data.verify_constraints()); ASSERT_EQ(ulo, new_t._data._urange._lo); ASSERT_EQ(uhi, new_t._data._urange._hi); if (S(ulo) <= S(uhi)) { ASSERT_EQ(S(ulo), new_t._data._srange._lo); ASSERT_EQ(S(uhi), new_t._data._srange._hi); } else { ASSERT_EQ(std::numeric_limits::min(), new_t._data._srange._lo); ASSERT_EQ(std::numeric_limits::max(), new_t._data._srange._hi); } } { U intersection = a & b; U zeros = a ^ intersection; U ones = b ^ intersection; TypeIntPrototype t{{std::numeric_limits::min(), std::numeric_limits::max()}, {std::numeric_limits::min(), std::numeric_limits::max()}, {zeros, ones}}; auto new_t = t.canonicalize_constraints(); ASSERT_TRUE(new_t._present); DEBUG_ONLY(new_t._data.verify_constraints()); ASSERT_EQ(zeros, new_t._data._bits._zeros); ASSERT_EQ(ones, new_t._data._bits._ones); ASSERT_EQ(ones, new_t._data._urange._lo); ASSERT_EQ(~zeros, new_t._data._urange._hi); } } } template static void test_canonicalize_constraints_random() { constexpr int samples = 1000; constexpr int parameters = 1000; for (int i = 0; i < parameters; i++) { S s1 = uniform_random(); S s2 = uniform_random(); S lo = MIN2(s1, s2); S hi = MAX2(s1, s2); U u1 = uniform_random(); U u2 = uniform_random(); U ulo = MIN2(u1, u2); U uhi = MAX2(u1, u2); U b1 = uniform_random(); U b2 = uniform_random(); U intersection = b1 & b2; U zeros = b1 ^ intersection; U ones = b2 ^ intersection; TypeIntPrototype t{{lo, hi}, {ulo, uhi}, {zeros, ones}}; auto new_t = t.canonicalize_constraints(); if (new_t._present) { DEBUG_ONLY(new_t._data.verify_constraints()); } for (int j = 0; j < samples; j++) { S v = uniform_random(); if (!new_t._present) { DEBUG_ONLY(ASSERT_FALSE(t.contains(v))); } else { DEBUG_ONLY(ASSERT_EQ(t.contains(v), new_t._data.contains(v))); } } } } TEST_VM(opto, canonicalize_constraints) { test_canonicalize_constraints_trivial(); test_canonicalize_constraints_exhaustive, uintn_t<1>>(); test_canonicalize_constraints_exhaustive, uintn_t<2>>(); test_canonicalize_constraints_exhaustive, uintn_t<3>>(); test_canonicalize_constraints_exhaustive, uintn_t<4>>(); test_canonicalize_constraints_simple(); test_canonicalize_constraints_simple(); test_canonicalize_constraints_random(); test_canonicalize_constraints_random(); } // Implementations of TypeIntMirror methods for testing purposes template TypeIntMirror TypeIntMirror::make(const TypeIntMirror& t, int widen) { return t; } template const TypeIntMirror* TypeIntMirror::operator->() const { return this; } template bool TypeIntMirror::contains(U u) const { S s = S(u); return s >= _lo && s <= _hi && u >= _ulo && u <= _uhi && _bits.is_satisfied_by(u); } template bool TypeIntMirror::contains(const TypeIntMirror& o) const { return TypeIntHelper::int_type_is_subset(*this, o); } template bool TypeIntMirror::operator==(const TypeIntMirror& o) const { return TypeIntHelper::int_type_is_equal(*this, o); } template template TypeIntMirror TypeIntMirror::cast() const { static_assert(std::is_same_v); return *this; } // The number of TypeIntMirror instances for integral types with a few bits. These values are // calculated once and written down for usage in constexpr contexts. template static constexpr size_t all_instances_size() { using U = decltype(CTP::_ulo); constexpr juint max_unsigned = juint(std::numeric_limits::max()); if constexpr (max_unsigned == 1U) { // 1 bit return 3; } else if constexpr (max_unsigned == 3U) { // 2 bits return 15; } else if constexpr (max_unsigned == 7U) { // 3 bits return 134; } else { // 4 bits static_assert(max_unsigned == 15U); // For more than 4 bits, the number of instances is too large and it is not realistic to // compute all of them. return 1732; } } template static std::array()> compute_all_instances() { using S = decltype(CTP::_lo); using U = decltype(CTP::_ulo); class CTPComparator { public: static RBTreeOrdering cmp(const CTP& x, const RBNode* node) { // Quick helper for the tediousness below auto f = [](auto x, auto y) { assert(x != y, "we only handle lt and gt cases"); return x < y ? RBTreeOrdering::LT : RBTreeOrdering::GT; }; const CTP& y = node->key(); if (x._lo != y._lo) { return f(x._lo, y._lo); } else if (x._hi != y._hi) { return f(x._hi, y._hi); } else if (x._ulo != y._ulo) { return f(x._ulo, y._ulo); } else if (x._uhi != y._uhi) { return f(x._uhi, y._uhi); } else if (x._bits._zeros != y._bits._zeros) { return f(x._bits._zeros, y._bits._zeros); } else if (x._bits._ones != y._bits._ones) { return f(x._bits._ones, y._bits._ones); } else { return RBTreeOrdering::EQ; } } }; RBTreeCHeap collector; for (jint lo = jint(std::numeric_limits::min()); lo <= jint(std::numeric_limits::max()); lo++) { for (jint hi = lo; hi <= jint(std::numeric_limits::max()); hi++) { for (juint ulo = 0; ulo <= juint(std::numeric_limits::max()); ulo++) { for (juint uhi = ulo; uhi <= juint(std::numeric_limits::max()); uhi++) { for (juint zeros = 0; zeros <= juint(std::numeric_limits::max()); zeros++) { for (juint ones = 0; ones <= juint(std::numeric_limits::max()); ones++) { TypeIntPrototype t{{S(lo), S(hi)}, {U(ulo), U(uhi)}, {U(zeros), U(ones)}}; auto canonicalized_t = t.canonicalize_constraints(); if (canonicalized_t.empty()) { continue; } TypeIntPrototype ct = canonicalized_t._data; collector.upsert(CTP{ct._srange._lo, ct._srange._hi, ct._urange._lo, ct._urange._hi, ct._bits}, 0); } } } } } } assert(collector.size() == all_instances_size(), "unexpected size of all_instance, expected %d, actual %d", jint(all_instances_size()), jint(collector.size())); std::array()> res; size_t idx = 0; collector.visit_in_order([&](RBNode* node) { res[idx] = node->key(); idx++; return true; }); return res; } template static const std::array()>& all_instances() { static std::array()> res = compute_all_instances(); static_assert(std::is_trivially_destructible_v); return res; } // Check the correctness, that is, if v1 is an element of input1, v2 is an element of input2, then // op(v1, v2) must be an element of infer(input1, input2). This version does the check exhaustively // on all elements of input1 and input2. template static void test_binary_instance_correctness_exhaustive(Operation op, Inference infer, const InputType& input1, const InputType& input2) { using S = std::remove_const_t_lo)>; using U = std::remove_const_t_ulo)>; InputType result = infer(input1, input2); for (juint v1 = juint(std::numeric_limits::min()); v1 <= juint(std::numeric_limits::max()); v1++) { if (!input1.contains(U(v1))) { continue; } for (juint v2 = juint(std::numeric_limits::min()); v2 <= juint(std::numeric_limits::max()); v2++) { if (!input2.contains(U(v2))) { continue; } U r = op(U(v1), U(v2)); ASSERT_TRUE(result.contains(r)); } } } // Check the correctness, that is, if v1 is an element of input1, v2 is an element of input2, then // op(v1, v2) must be an element of infer(input1, input2). This version does the check randomly on // a number of elements in input1 and input2. template static void test_binary_instance_correctness_samples(Operation op, Inference infer, const InputType& input1, const InputType& input2) { using U = std::remove_const_t_ulo)>; auto result = infer(input1, input2); constexpr size_t sample_count = 6; U input1_samples[sample_count] {U(input1._lo), U(input1._hi), input1._ulo, input1._uhi, input1._ulo, input1._ulo}; U input2_samples[sample_count] {U(input2._lo), U(input2._hi), input2._ulo, input2._uhi, input2._ulo, input2._ulo}; auto random_sample = [](U* samples, const InputType& input) { constexpr size_t max_tries = 100; constexpr size_t start_random_idx = 4; for (size_t tries = 0, idx = start_random_idx; tries < max_tries && idx < sample_count; tries++) { U n = uniform_random(); if (input.contains(n)) { samples[idx] = n; idx++; } } }; random_sample(input1_samples, input1); random_sample(input2_samples, input2); for (size_t i = 0; i < sample_count; i++) { for (size_t j = 0; j < sample_count; j++) { U r = op(input1_samples[i], input2_samples[j]); ASSERT_TRUE(result.contains(r)); } } } // Check the monotonicity, that is, if input1 is a subset of super1, input2 is a subset of super2, // then infer(input1, input2) must be a subset of infer(super1, super2). This version does the // check exhaustively on all supersets of input1 and input2. template static void test_binary_instance_monotonicity_exhaustive(Inference infer, const InputType& input1, const InputType& input2) { InputType result = infer(input1, input2); for (const InputType& super1 : all_instances()) { if (!super1.contains(input1) || super1 == input1) { continue; } for (const InputType& super2 : all_instances()) { if (!super2.contains(input2) || super2 == input2) { continue; } ASSERT_TRUE(infer(input1, super2).contains(result)); ASSERT_TRUE(infer(super1, input2).contains(result)); ASSERT_TRUE(infer(super1, super2).contains(result)); } } } // Check the monotonicity, that is, if input1 is a subset of super1, input2 is a subset of super2, // then infer(input1, input2) must be a subset of infer(super1, super2). This version does the // check randomly on a number of supersets of input1 and input2. template static void test_binary_instance_monotonicity_samples(Inference infer, const InputType& input1, const InputType& input2) { using S = std::remove_const_t_lo)>; using U = std::remove_const_t_ulo)>; auto result = infer(input1, input2); // The set that is a superset of all other sets InputType universe = InputType{std::numeric_limits::min(), std::numeric_limits::max(), U(0), U(-1), {U(0), U(0)}}; ASSERT_TRUE(infer(universe, input2).contains(result)); ASSERT_TRUE(infer(input1, universe).contains(result)); ASSERT_TRUE(infer(universe, universe).contains(result)); auto random_superset = [](const InputType& input) { S lo = MIN2(input->_lo, S(uniform_random())); S hi = MAX2(input->_hi, S(uniform_random())); U ulo = MIN2(input->_ulo, uniform_random()); U uhi = MAX2(input->_uhi, uniform_random()); U zeros = input->_bits._zeros & uniform_random(); U ones = input->_bits._ones & uniform_random(); InputType super = InputType::make(TypeIntPrototype{{lo, hi}, {ulo, uhi}, {zeros, ones}}, 0); assert(super.contains(input), "impossible"); return super; }; InputType super1 = random_superset(input1); InputType super2 = random_superset(input2); ASSERT_TRUE(infer(super1, input2).contains(result)); ASSERT_TRUE(infer(input1, super2).contains(result)); ASSERT_TRUE(infer(super1, super2).contains(result)); } // Verify the correctness and monotonicity of an inference function by exhautively analyzing all // instances of InputType template static void test_binary_exhaustive(Operation op, Inference infer) { for (const InputType& input1 : all_instances()) { for (const InputType& input2 : all_instances()) { test_binary_instance_correctness_exhaustive(op, infer, input1, input2); if (all_instances().size() < 100) { // This effectively covers the cases up to uintn_t<2> test_binary_instance_monotonicity_exhaustive(infer, input1, input2); } else { // This effectively covers the cases of uintn_t<3> test_binary_instance_monotonicity_samples(infer, input1, input2); } } } } // Verify the correctness and monotonicity of an inference function by randomly sampling instances // of InputType template static void test_binary_random(Operation op, Inference infer) { using S = std::remove_const_t; using U = std::remove_const_t; constexpr size_t sample_count = 100; InputType samples[sample_count]; // Fill with {0} for (size_t i = 0; i < sample_count; i++) { samples[i] = InputType::make(TypeIntPrototype{{S(0), S(0)}, {U(0), U(0)}, {U(0), U(0)}}, 0); } // {1} samples[1] = InputType::make(TypeIntPrototype{{S(1), S(1)}, {U(1), U(1)}, {U(0), U(0)}}, 0); // {-1} samples[2] = InputType::make(TypeIntPrototype{{S(-1), S(-1)}, {U(-1), U(-1)}, {U(0), U(0)}}, 0); // {0, 1} samples[3] = InputType::make(TypeIntPrototype{{S(0), S(1)}, {U(0), U(1)}, {U(0), U(0)}}, 0); // {-1, 0, 1} samples[4] = InputType::make(TypeIntPrototype{{S(-1), S(1)}, {U(0), U(-1)}, {U(0), U(0)}}, 0); // {-1, 1} samples[5] = InputType::make(TypeIntPrototype{{S(-1), S(1)}, {U(1), U(-1)}, {U(0), U(0)}}, 0); // {0, 1, 2} samples[6] = InputType::make(TypeIntPrototype{{S(0), S(2)}, {U(0), U(2)}, {U(0), U(0)}}, 0); // {0, 2} samples[7] = InputType::make(TypeIntPrototype{{S(0), S(2)}, {U(0), U(2)}, {U(1), U(0)}}, 0); // [min_signed, max_signed] samples[8] = InputType::make(TypeIntPrototype{{std::numeric_limits::min(), std::numeric_limits::max()}, {U(0), U(-1)}, {U(0), U(0)}}, 0); // [0, max_signed] samples[9] = InputType::make(TypeIntPrototype{{S(0), std::numeric_limits::max()}, {U(0), U(-1)}, {U(0), U(0)}}, 0); // [min_signed, 0) samples[10] = InputType::make(TypeIntPrototype{{std::numeric_limits::min(), S(-1)}, {U(0), U(-1)}, {U(0), U(0)}}, 0); constexpr size_t max_tries = 1000; constexpr size_t start_random_idx = 11; for (size_t tries = 0, idx = start_random_idx; tries < max_tries && idx < sample_count; tries++) { // Try to have lo < hi S signed_bound1 = S(uniform_random()); S signed_bound2 = S(uniform_random()); S lo = MIN2(signed_bound1, signed_bound2); S hi = MAX2(signed_bound1, signed_bound2); // Try to have ulo < uhi U unsigned_bound1 = uniform_random(); U unsigned_bound2 = uniform_random(); U ulo = MIN2(unsigned_bound1, unsigned_bound2); U uhi = MAX2(unsigned_bound1, unsigned_bound2); // Try to have (zeros & ones) == 0 U zeros = uniform_random(); U ones = uniform_random(); U common = zeros & ones; zeros = zeros ^ common; ones = ones ^ common; TypeIntPrototype t{{lo, hi}, {ulo, uhi}, {zeros, ones}}; auto canonicalized_t = t.canonicalize_constraints(); if (canonicalized_t.empty()) { continue; } samples[idx] = TypeIntMirror{canonicalized_t._data._srange._lo, canonicalized_t._data._srange._hi, canonicalized_t._data._urange._lo, canonicalized_t._data._urange._hi, canonicalized_t._data._bits}; idx++; } for (size_t i = 0; i < sample_count; i++) { for (size_t j = 0; j < sample_count; j++) { test_binary_instance_correctness_samples(op, infer, samples[i], samples[j]); test_binary_instance_monotonicity_samples(infer, samples[i], samples[j]); } } } template