From b6714a887c369673d6d7b3174775592752948c6d Mon Sep 17 00:00:00 2001 From: David Plass Date: Wed, 17 Jun 2026 06:56:32 -0700 Subject: [PATCH] [DSLX Fuzz testing] Update the JIT wrapper generator to use a C++ helper for "wide" bit values and element_of domains. Refactors FuzzTest domain generation in the JIT wrapper generator to: 1. Employ a new C++ helper `xls::ElementOfDomain` to convert an `ElementOf` into a complex Google Fuzz Test domain. 2. Fall back to `xls::ArbitraryValue` for wide bits (>64 bits) or tuples that contain otherwise unsupported types. PiperOrigin-RevId: 933699307 --- xls/ir/BUILD | 2 ++ xls/ir/value_test_util.cc | 18 ++++++++++ xls/ir/value_test_util.h | 4 +++ xls/ir/value_test_util_test.cc | 9 +++++ xls/jit/BUILD | 3 +- xls/jit/jit_wrapper_generator.py | 32 ++++++++--------- xls/jit/jit_wrapper_generator_test.py | 51 +++++++++++++++++++++++---- 7 files changed, 95 insertions(+), 24 deletions(-) diff --git a/xls/ir/BUILD b/xls/ir/BUILD index 32dab03c75..4c6f4f9c2d 100644 --- a/xls/ir/BUILD +++ b/xls/ir/BUILD @@ -1353,7 +1353,9 @@ cc_library( ":type_manager", ":value", ":value_flattening", + ":xls_ir_interface_cc_proto", ":xls_type_cc_proto", + "//xls/common/file:filesystem", "//xls/common/fuzzing:fuzztest", "@com_google_absl//absl/log:check", "@googletest//:gtest", diff --git a/xls/ir/value_test_util.cc b/xls/ir/value_test_util.cc index 0142eefe43..df621109d9 100644 --- a/xls/ir/value_test_util.cc +++ b/xls/ir/value_test_util.cc @@ -15,10 +15,13 @@ #include "xls/ir/value_test_util.h" #include +#include +#include #include "gtest/gtest.h" #include "xls/common/fuzzing/fuzztest.h" #include "absl/log/check.h" +#include "xls/common/file/filesystem.h" #include "xls/ir/bits.h" #include "xls/ir/bits_test_utils.h" #include "xls/ir/fuzz_type_domain.h" @@ -26,6 +29,7 @@ #include "xls/ir/type_manager.h" #include "xls/ir/value.h" #include "xls/ir/value_flattening.h" +#include "xls/ir/xls_ir_interface.pb.h" #include "xls/ir/xls_type.pb.h" namespace xls { @@ -75,4 +79,18 @@ fuzztest::Domain ArbitraryValue(TypeProto type) { return ArbitraryValue(fuzztest::Just(type)); } +fuzztest::Domain ElementOfDomain(std::string_view values_text_proto) { + xls::PackageInterfaceProto::FuzzTestDomain::ElementOf proto; + CHECK_OK(xls::ParseTextProto(values_text_proto, /*file_name=*/"", &proto)); + std::vector values; + values.reserve(proto.values_size()); + for (const auto& value_proto : proto.values()) { + auto value_or = Value::FromProto(value_proto); + CHECK_OK(value_or.status()) << "Failed to parse Value from proto: " + << value_proto.ShortDebugString(); + values.push_back(std::move(value_or.value())); + } + return fuzztest::ElementOf(values); +} + } // namespace xls diff --git a/xls/ir/value_test_util.h b/xls/ir/value_test_util.h index 7d532fbed5..37f99ad718 100644 --- a/xls/ir/value_test_util.h +++ b/xls/ir/value_test_util.h @@ -16,6 +16,7 @@ #define XLS_IR_VALUE_TEST_UTIL_H_ #include +#include #include "gtest/gtest.h" #include "xls/common/fuzzing/fuzztest.h" @@ -43,6 +44,9 @@ fuzztest::Domain ArbitraryValue(fuzztest::Domain type); // Create a domain for an arbitrary value which is of the given type. fuzztest::Domain ArbitraryValue(TypeProto type); +// Create an element_of domain from a serialized ElementOf proto (text format). +fuzztest::Domain ElementOfDomain(std::string_view values_text_proto); + } // namespace xls #endif // XLS_IR_VALUE_TEST_UTIL_H_ diff --git a/xls/ir/value_test_util_test.cc b/xls/ir/value_test_util_test.cc index 842baaa869..cf770e4e06 100644 --- a/xls/ir/value_test_util_test.cc +++ b/xls/ir/value_test_util_test.cc @@ -28,5 +28,14 @@ TEST(ValueTestUtilTest, ValuesEqual) { EXPECT_FALSE(ValuesEqual(Value(UBits(1, 1234)), Value(UBits(1, 10)))); } +void ElementOfDomainTestHelper(const Value& value) { + EXPECT_TRUE(value == Value(UBits(1, 32)) || value == Value(UBits(2, 32))); +} +FUZZ_TEST(ValueTestUtilTest, ElementOfDomainTestHelper) + .WithDomains(ElementOfDomain(R"pb( + values { bits { bit_count: 32 data: "\x01" } } + values { bits { bit_count: 32 data: "\x02" } } + )pb")); + } // namespace } // namespace xls diff --git a/xls/jit/BUILD b/xls/jit/BUILD index c05f1c1623..c5420a3a3b 100644 --- a/xls/jit/BUILD +++ b/xls/jit/BUILD @@ -375,6 +375,7 @@ pytype_strict_library( "//xls/ir:xls_ir_interface_py_pb2", "//xls/ir:xls_type_py_pb2", "@abseil-py//absl:app", + "@com_google_protobuf//:protobuf_python", "@xls_pip_deps//jinja2", ], ) @@ -413,8 +414,8 @@ pytype_strict_contrib_test( "//xls/common:runfiles", "//xls/ir:xls_ir_interface_py_pb2", "//xls/ir:xls_type_py_pb2", - "@abseil-py//absl:app", "@abseil-py//absl/testing:absltest", + "@com_google_protobuf//:protobuf_python", "@xls_pip_deps//jinja2", ], ) diff --git a/xls/jit/jit_wrapper_generator.py b/xls/jit/jit_wrapper_generator.py index 5bfaf482a0..de99d8e2ac 100644 --- a/xls/jit/jit_wrapper_generator.py +++ b/xls/jit/jit_wrapper_generator.py @@ -21,6 +21,7 @@ from typing import Optional from absl import app +from google.protobuf import text_format import jinja2 from xls.ir import xls_ir_interface_pb2 as ir_interface_pb2 @@ -368,10 +369,7 @@ def to_domain( cpp_type = to_specialized(t, int_only=True) if cpp_type is None: if not can_use_uint64_range(t, d): - raise app.UsageError( - "Range domain is only supported for specializable bits types or" - " ranges fitting in 64 bits" - ) + return None cpp_type = "uint64_t" min_val = extract_int_from_bytes(d.range.min.bits.data) max_val = extract_int_from_bytes(d.range.max.bits.data) @@ -379,24 +377,26 @@ def to_domain( if d.HasField("element_of"): c_type = to_specialized(t, int_only=True) - if c_type is None: - raise app.UsageError( - "ElementOf domain only supported for specializable bits types in" - " this CL" - ) - vals = [ - str(extract_int_from_bytes(v.bits.data)) for v in d.element_of.values - ] - return f"fuzztest::ElementOf(std::vector<{c_type}>{{{', '.join(vals)}}})" + if c_type is not None: + vals = [ + str(extract_int_from_bytes(v.bits.data)) for v in d.element_of.values + ] + return f"fuzztest::ElementOf(std::vector<{c_type}>{{{', '.join(vals)}}})" + else: + proto_str = text_format.MessageToString(d.element_of) + return f'xls::ElementOfDomain(R"pb({proto_str})pb")' if d.HasField("tuple"): if t.type_enum != type_pb2.TypeProto.TUPLE: raise app.UsageError("Tuple domain requires Tuple type") if len(d.tuple.elements) != len(t.tuple_elements): raise app.UsageError("Tuple domain and type element count mismatch") - elems = [ - to_domain(te, de) for te, de in zip(t.tuple_elements, d.tuple.elements) - ] + elems = [] + for te, de in zip(t.tuple_elements, d.tuple.elements): + elem_d = to_domain(te, de) + if elem_d is None: + return None + elems.append(elem_d) return f"fuzztest::TupleOf({', '.join(elems)})" raise app.UsageError(f"Unsupported domain: {d}") diff --git a/xls/jit/jit_wrapper_generator_test.py b/xls/jit/jit_wrapper_generator_test.py index d241c648cf..ca005aa33e 100644 --- a/xls/jit/jit_wrapper_generator_test.py +++ b/xls/jit/jit_wrapper_generator_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from absl import app +from google.protobuf import text_format import jinja2 from absl.testing import absltest @@ -920,7 +920,7 @@ def test_tuple_with_array_domain(self): ' fuzztest::ArrayOf<3>(fuzztest::Arbitrary()))', ) - def test_unsupported_domain_raises(self): + def test_unsupported_range_domain_returns_none(self): u32 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=32) tup = type_pb2.TypeProto( type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[u32] @@ -930,12 +930,49 @@ def test_unsupported_domain_raises(self): d.range.min.bits.data = b'\x00' d.range.max.bits.bit_count = 32 d.range.max.bits.data = b'\x0a' + self.assertIsNone(jit_wrapper_generator.to_domain(tup, d)) - with self.assertRaisesRegex( - app.UsageError, - 'Range domain is only supported for specializable bits types', - ): - jit_wrapper_generator.to_domain(tup, d) + def test_element_of_domain_non_specializable(self): + u128 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=128) + d = ir_interface_pb2.PackageInterfaceProto.FuzzTestDomain() + v1 = d.element_of.values.add() + v1.bits.bit_count = 128 + v1.bits.data = b'\x01' + v2 = d.element_of.values.add() + v2.bits.bit_count = 128 + v2.bits.data = b'\x02' + expected_proto_str = text_format.MessageToString(d.element_of) + self.assertEqual( + jit_wrapper_generator.to_domain(u128, d), + f'xls::ElementOfDomain(R"pb({expected_proto_str})pb")', + ) + + def test_range_domain_wide_bits_does_not_fit(self): + u128 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=128) + d = ir_interface_pb2.PackageInterfaceProto.FuzzTestDomain() + d.range.min.bits.bit_count = 128 + d.range.min.bits.data = b'\x01' + d.range.max.bits.bit_count = 128 + d.range.max.bits.data = b'\x00\x00\x00\x00\x00\x00\x00\x00\x01' + self.assertIsNone(jit_wrapper_generator.to_domain(u128, d)) + + def test_tuple_domain_with_unsupported_child_fallback(self): + u32 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=32) + u128 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=128) + tup = type_pb2.TypeProto( + type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[u32, u128] + ) + d = ir_interface_pb2.PackageInterfaceProto.FuzzTestDomain() + d.tuple.elements.add().range.min.bits.bit_count = 32 + d.tuple.elements[0].range.min.bits.data = b'\x00' + d.tuple.elements[0].range.max.bits.bit_count = 32 + d.tuple.elements[0].range.max.bits.data = b'\x0a' + d_child2 = d.tuple.elements.add() + d_child2.range.min.bits.bit_count = 128 + d_child2.range.min.bits.data = b'\x01' + d_child2.range.max.bits.bit_count = 128 + d_child2.range.max.bits.data = b'\x00\x00\x00\x00\x00\x00\x00\x00\x01' + self.assertIsNone(jit_wrapper_generator.to_domain(tup, d)) class JitWrapperGeneratorToParamTest(absltest.TestCase):