Skip to content

Commit 13e43c1

Browse files
dplassgitcopybara-github
authored andcommitted
[DSLX Fuzz Testing] Fixed support for specifying domains of tuple parameters in fuzz tests.
FuzzTest unpacks `TupleOf` domains for free functions, causing a signature mismatch when the function expects a single tuple argument. This was fixed by: - Wrapping top-level tuple domains in an extra `fuzztest::TupleOf(...)`. - Generating C++ `std::tuple` for tuple parameters in the property function signature. - Generating recursive conversion logic to `xls::Value` in the property function body. Also, updated the catalog of examples in `fuzz_test_domains.x` and documented the solution in `README.md`. PiperOrigin-RevId: 913885815
1 parent 330736b commit 13e43c1

6 files changed

Lines changed: 150 additions & 18 deletions

File tree

xls/jit/fuzztest_cc.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ void {{fuzztest.property_function_name}}({% for param in fuzztest.params %}{{par
2222
{% else %}
2323
// Fallback: Manual binding for the standard Run(xls::Value...) overload.
2424
{% for param in fuzztest.params %}
25-
{% if param.is_native %}
25+
{% if param.conversion_snippet %}
2626
xls::Value {{param.name}}_val = {{param.conversion_snippet}};
2727
{% endif %}
2828
{% endfor %}
2929
XLS_ASSERT_OK_AND_ASSIGN(xls::Value result, f->Run(
3030
{% for param in fuzztest.params %}
31-
{{param.name}}{% if param.is_native %}_val{% endif %}{% if not loop.last %}, {% endif %}
31+
{{param.name}}{% if param.conversion_snippet %}_val{% endif %}{% if not loop.last %}, {% endif %}
3232
{% endfor %}
3333
));
3434
{% endif %}

xls/jit/jit_wrapper_generator.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,23 @@ def to_domain(
386386
raise app.UsageError(f"Unsupported domain: {d}")
387387

388388

389+
def to_value_conversion(t: type_pb2.TypeProto, expr: str) -> str:
390+
"""Generates C++ snippet to convert a native type to xls::Value."""
391+
if t.type_enum == type_pb2.TypeProto.BITS:
392+
return f"xls::Value(xls::UBits({expr}, {t.bit_count}))"
393+
elif t.type_enum == type_pb2.TypeProto.TUPLE:
394+
elems = []
395+
for i, e in enumerate(t.tuple_elements):
396+
elems.append(to_value_conversion(e, f"std::get<{i}>({expr})"))
397+
return f"xls::Value::Tuple({{{', '.join(elems)}}})"
398+
elif t.type_enum == type_pb2.TypeProto.ARRAY:
399+
elems = []
400+
for i in range(t.array_size):
401+
elems.append(to_value_conversion(t.array_element, f"{expr}[{i}]"))
402+
return f"xls::Value::Array({{{', '.join(elems)}}})"
403+
raise app.UsageError(f"Unsupported type for value conversion: {t}")
404+
405+
389406
def to_chan(
390407
c: ir_interface_pb2.PackageInterfaceProto.Channel, package_name: str
391408
) -> XlsChannel:
@@ -592,9 +609,6 @@ def wrapped_to_fuzztest(
592609
if wrapped.params:
593610
for idx, p in enumerate(wrapped.params):
594611
is_native = p.specialized_type is not None
595-
cpp_type = p.specialized_type or "xls::Value"
596-
conversion_snippet = None
597-
598612
domain_proto = p.fuzztest_info.domain_proto if p.fuzztest_info else None
599613
domain_snippet = (
600614
p.fuzztest_info.domain_snippet if p.fuzztest_info else None
@@ -603,11 +617,16 @@ def wrapped_to_fuzztest(
603617
if not is_native and can_use_uint64_range(p.type_proto, domain_proto):
604618
cpp_type = "uint64_t"
605619
is_native = True
620+
else:
621+
cpp_type = to_c_type(p.type_proto)
606622

607-
if is_native and p.type_proto.type_enum == type_pb2.TypeProto.BITS:
608-
conversion_snippet = (
609-
f"xls::Value(xls::UBits({p.name}, {p.type_proto.bit_count}))"
610-
)
623+
conversion_snippet = to_value_conversion(p.type_proto, p.name)
624+
625+
if (
626+
p.type_proto.type_enum == type_pb2.TypeProto.TUPLE
627+
and domain_snippet is not None
628+
):
629+
domain_snippet = f"fuzztest::TupleOf({domain_snippet})"
611630

612631
params.append(
613632
PropertyFunctionParam(

xls/jit/jit_wrapper_generator_test.py

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@ def test_function_tuple_param(self):
294294
packed_type='',
295295
unpacked_type='',
296296
specialized_type=None,
297+
fuzztest_info=jit_wrapper_generator.FuzzTestInfo(
298+
domain_snippet=(
299+
'fuzztest::TupleOf(fuzztest::Arbitrary<uint8_t>(),'
300+
' fuzztest::Arbitrary<uint64_t>())'
301+
)
302+
),
297303
),
298304
],
299305
result=jit_wrapper_generator.XlsNamedValue(
@@ -312,6 +318,14 @@ def test_function_tuple_param(self):
312318
self.assertLen(prop_func.params, 1)
313319
self.assertEqual(prop_func.params[0].name, 't')
314320
self.assertEqual(prop_func.params[0].index, 0)
321+
self.assertEqual(
322+
prop_func.params[0].cpp_type, 'std::tuple<uint8_t, uint64_t>'
323+
)
324+
self.assertEqual(
325+
prop_func.params[0].domain_snippet,
326+
'fuzztest::TupleOf(fuzztest::TupleOf(fuzztest::Arbitrary<uint8_t>(),'
327+
' fuzztest::Arbitrary<uint64_t>()))',
328+
)
315329

316330
def test_function_no_result(self):
317331
u8 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=8)
@@ -344,6 +358,72 @@ def test_function_no_result(self):
344358
self.assertEqual(prop_func.params[0].name, 'a')
345359

346360

361+
class JitWrapperGeneratorToValueConversionTest(absltest.TestCase):
362+
363+
def test_bits(self):
364+
u32 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=32)
365+
self.assertEqual(
366+
jit_wrapper_generator.to_value_conversion(u32, 'x'),
367+
'xls::Value(xls::UBits(x, 32))',
368+
)
369+
370+
def test_tuple_of_tuples(self):
371+
u8 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=8)
372+
inner_tup = type_pb2.TypeProto(
373+
type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[u8]
374+
)
375+
outer_tup = type_pb2.TypeProto(
376+
type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[inner_tup]
377+
)
378+
self.assertEqual(
379+
jit_wrapper_generator.to_value_conversion(outer_tup, 't'),
380+
'xls::Value::Tuple({xls::Value::Tuple({xls::Value(xls::UBits('
381+
'std::get<0>(std::get<0>(t)), 8))})})',
382+
)
383+
384+
def test_tuple_of_arrays(self):
385+
u8 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=8)
386+
a8 = type_pb2.TypeProto(
387+
type_enum=type_pb2.TypeProto.ARRAY, array_size=2, array_element=u8
388+
)
389+
tup = type_pb2.TypeProto(
390+
type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[a8]
391+
)
392+
self.assertEqual(
393+
jit_wrapper_generator.to_value_conversion(tup, 't'),
394+
'xls::Value::Tuple({xls::Value::Array({xls::Value(xls::UBits('
395+
'std::get<0>(t)[0], 8)), xls::Value(xls::UBits(std::get<0>(t)[1], 8))})'
396+
'})',
397+
)
398+
399+
def test_tuple_of_primitives(self):
400+
u8 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=8)
401+
u32 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=32)
402+
tup = type_pb2.TypeProto(
403+
type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[u8, u32]
404+
)
405+
self.assertEqual(
406+
jit_wrapper_generator.to_value_conversion(tup, 't'),
407+
'xls::Value::Tuple({xls::Value(xls::UBits(std::get<0>(t), 8)),'
408+
' xls::Value(xls::UBits(std::get<1>(t), 32))})',
409+
)
410+
411+
def test_mixed_tuple(self):
412+
u8 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=8)
413+
u32 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=32)
414+
a8 = type_pb2.TypeProto(
415+
type_enum=type_pb2.TypeProto.ARRAY, array_size=1, array_element=u8
416+
)
417+
tup = type_pb2.TypeProto(
418+
type_enum=type_pb2.TypeProto.TUPLE, tuple_elements=[u32, a8]
419+
)
420+
self.assertEqual(
421+
jit_wrapper_generator.to_value_conversion(tup, 't'),
422+
'xls::Value::Tuple({xls::Value(xls::UBits(std::get<0>(t), 32)),'
423+
' xls::Value::Array({xls::Value(xls::UBits(std::get<1>(t)[0], 8))})})',
424+
)
425+
426+
347427
class JitWrapperGeneratorRenderFuzztestTest(absltest.TestCase):
348428

349429
def setUp(self):
@@ -396,7 +476,7 @@ def test_render_fuzztest_basic(self):
396476
'xls::test::MyFuncJit',
397477
'my_func_jit.h',
398478
)
399-
self.assertIn('void my_func(xls::Value a, xls::Value b)', rendered_code)
479+
self.assertIn('void my_func(uint8_t a, uint32_t b)', rendered_code)
400480
self.assertIn(
401481
'XLS_ASSERT_OK_AND_ASSIGN(xls::Value result, f->Run(', rendered_code
402482
)
@@ -441,7 +521,9 @@ def test_render_fuzztest_array_of_int(self):
441521
'xls::test::ArrayIntFuncJit',
442522
'array_int_func_jit.h',
443523
)
444-
self.assertIn('void array_int_func(xls::Value x)', rendered_code)
524+
self.assertIn(
525+
'void array_int_func(std::array<uint16_t, 4> x)', rendered_code
526+
)
445527
self.assertIn(
446528
'FUZZ_TEST(array_int_func_fuzztest, array_int_func)', rendered_code
447529
)
@@ -483,7 +565,10 @@ def test_render_fuzztest_array_of_tuple(self):
483565
'xls::test::ArrayTupleFuncJit',
484566
'array_tuple_func_jit.h',
485567
)
486-
self.assertIn('void array_tuple_func(xls::Value y)', rendered_code)
568+
self.assertIn(
569+
'void array_tuple_func(std::array<std::tuple<uint8_t, uint32_t>, 3> y)',
570+
rendered_code,
571+
)
487572
self.assertIn(
488573
'FUZZ_TEST(array_tuple_func_fuzztest, array_tuple_func)', rendered_code
489574
)
@@ -524,7 +609,9 @@ def test_render_fuzztest_tuple_of_int(self):
524609
'xls::test::TupleIntFuncJit',
525610
'tuple_int_func_jit.h',
526611
)
527-
self.assertIn('void tuple_int_func(xls::Value t)', rendered_code)
612+
self.assertIn(
613+
'void tuple_int_func(std::tuple<uint16_t, uint64_t> t)', rendered_code
614+
)
528615
self.assertIn(
529616
'FUZZ_TEST(tuple_int_func_fuzztest, tuple_int_func)', rendered_code
530617
)
@@ -569,7 +656,11 @@ def test_render_fuzztest_tuple_mixed(self):
569656
'xls::test::TupleMixedFuncJit',
570657
'tuple_mixed_func_jit.h',
571658
)
572-
self.assertIn('void tuple_mixed_func(xls::Value m)', rendered_code)
659+
self.assertIn(
660+
'void tuple_mixed_func(std::tuple<uint8_t, std::tuple<uint8_t,'
661+
' uint16_t>> m)',
662+
rendered_code,
663+
)
573664
self.assertIn(
574665
'FUZZ_TEST(tuple_mixed_func_fuzztest, tuple_mixed_func)', rendered_code
575666
)
@@ -607,7 +698,7 @@ def test_render_fuzztest_uses_property_param_filter(self):
607698
'xls::test::MyFuncJit',
608699
'my_func_jit.h',
609700
)
610-
self.assertEqual(rendered_code, 'const xls::Value& a')
701+
self.assertEqual(rendered_code, 'uint8_t a')
611702

612703
def test_render_fuzztest_default_domain(self):
613704
u32 = type_pb2.TypeProto(type_enum=type_pb2.TypeProto.BITS, bit_count=32)

xls/tests/fuzz_test/BUILD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ dslx_fuzz_test(
3535
test_function = "test_arbitrary",
3636
)
3737

38+
dslx_fuzz_test(
39+
name = "test_no_domains_fuzz_test",
40+
library = ":fuzz_test_dslx",
41+
test_function = "test_no_domains",
42+
)
43+
3844
dslx_fuzz_test(
3945
name = "test_arbitrary_opt_fuzz_test",
4046
library = ":fuzz_test_dslx",
@@ -53,3 +59,9 @@ dslx_fuzz_test(
5359
library = ":fuzz_test_dslx",
5460
test_function = "test_element_of",
5561
)
62+
63+
dslx_fuzz_test(
64+
name = "test_tuple_fuzz_test",
65+
library = ":fuzz_test_dslx",
66+
test_function = "test_tuple",
67+
)

xls/tests/fuzz_test/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ the `#[fuzz_test]` attribute:
99

1010
- **Arbitrary**: `()` - Explores the full range of the type.
1111
- **Numeric Range**: `Type:min..max` - Explores values in the range `[min,
12-
max)`. Example: `u32:0..100`.
12+
max)`. Example: `u32:0..100`. "End-inclusive" ranges work too, e.g.,
13+
`Type:min..=max` explores values in the range `[min, max]`.
1314
- **Element Of**: `[val1, val2, ...]` - Explores only the listed values.
1415
Example: `[u32:5, 10, 15]`.
16+
- **Tuples**: `(Domain1, Domain2, ...)` - For tuple parameters. Example:
17+
`(u32:0..10, [u8:1, 2])`. The parentheses are required.
1518

1619
## Known Limitations
1720

1821
- **Array parameters**: `(ElementDomain)[N]` is not yet supported; however if
19-
you specify the parameter as arbitrary (i.e., `()`) it will Just Work.
22+
you specify the parameter "as arbitrary" (i.e., `()`) it will Just Work.
2023
- **Struct parameters**: `StructName { field: Domain, ... }` is not yet
2124
supported.
22-
- **Tuple parameters**: `(Domain1, Domain2, ...)` is not yet supported.
2325
- Fuzzing is currently limited to types that can be mapped to native C++ types
2426
up to 64 bits for full specialization. Larger types fallback to `xls::Value`
2527
and may have limited mutation capabilities.

xls/tests/fuzz_test/fuzz_tests.x

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// No domain attribute is the same as #[fuzz_test(domains=`()`)]
16+
fn test_no_domains(x: u32) -> bool { x == x }
17+
1518
#[fuzz_test(domains=`()`)]
1619
fn test_arbitrary(x: u32) -> bool {
1720
x == x
@@ -26,3 +29,8 @@ fn test_range(x: u32) -> bool {
2629
fn test_element_of(x: u32) -> bool {
2730
x == u32:5 || x == u32:10 || x == u32:15
2831
}
32+
33+
#[fuzz_test(domains=`(u32:0..10, [u8:1, 2])`)]
34+
fn test_tuple(t: (u32, u8)) -> bool {
35+
t.0 <= u32:10 && (t.1 == u8:1 || t.1 == u8:2)
36+
}

0 commit comments

Comments
 (0)