Skip to content

Conversation

@nnethercote
Copy link
Contributor

Fieldless enums cannot trigger recursion. Avoiding the recursive count guard for them makes the generated code a lot smaller and faster.

`u32` is not a recursive type, so there is no need to use
`try_size_hint`.
Fieldless enums cannot trigger recursion. Avoiding the recursive count
guard for them makes the generated code a lot smaller and faster.
@nnethercote
Copy link
Contributor Author

Best reviewed one commit at a time.

This change came about after I ran the compiler with the new -Zmacro-stats flag (which measures how much code is generated by macros) on a project using arbitrary. The numbers for derive(Arbitrary) were high, and when I looked at the produced code with cargo expand I realized the fieldless enum case could be optimized.

An example: for enum Fieldless { A, B }, the old generated code is this:

const _: () = {
    #[allow(non_upper_case_globals)]
    const RECURSIVE_COUNT_Fieldless: ::std::thread::LocalKey<::core::cell::Cell<u32>> = {
        const __INIT: ::core::cell::Cell<u32> = { ::core::cell::Cell::new(0) };
        unsafe {
            ::std::thread::LocalKey::new(const {
                if ::std::mem::needs_drop::<::core::cell::Cell<u32>>() {
                    |_| {
                        #[thread_local]
                        static VAL: ::std::thread::local_impl::EagerStorage<
                            ::core::cell::Cell<u32>,
                        > = ::std::thread::local_impl::EagerStorage::new(__INIT);
                        VAL.get()
                    }
                } else {
                    |_| {
                        #[thread_local]
                        static VAL: ::core::cell::Cell<u32> = __INIT;
                        &VAL
                    }
                }
            })
        }
    };
    #[automatically_derived]
    impl<'arbitrary> arbitrary::Arbitrary<'arbitrary> for Fieldless {
        fn arbitrary(
            u: &mut arbitrary::Unstructured<'arbitrary>,
        ) -> arbitrary::Result<Self> {
            let guard_against_recursion = u.is_empty();
            if guard_against_recursion {
                RECURSIVE_COUNT_Fieldless
                    .with(|count| {
                        if count.get() > 0 {
                            return Err(arbitrary::Error::NotEnoughData);
                        }
                        count.set(count.get() + 1);
                        Ok(())
                    })?;
            }
            let result = (|| {
                Ok(
                    match (u64::from(<u32 as arbitrary::Arbitrary>::arbitrary(u)?)
                        * 2u64) >> 32
                    {
                        0u64 => Fieldless::A,
                        1u64 => Fieldless::B,
                        _ => {
                            ::core::panicking::panic(
                                "internal error: entered unreachable code",
                            )
                        }
                    },
                )
            })();
            if guard_against_recursion {
                RECURSIVE_COUNT_Fieldless
                    .with(|count| {
                        count.set(count.get() - 1);
                    });
            }
            result
        }
        fn arbitrary_take_rest(
            mut u: arbitrary::Unstructured<'arbitrary>,
        ) -> arbitrary::Result<Self> {
            let guard_against_recursion = u.is_empty();
            if guard_against_recursion {
                RECURSIVE_COUNT_Fieldless
                    .with(|count| {
                        if count.get() > 0 {
                            return Err(arbitrary::Error::NotEnoughData);
                        }
                        count.set(count.get() + 1);
                        Ok(())
                    })?;
            }
            let result = (|| {
                Ok(
                    match (u64::from(<u32 as arbitrary::Arbitrary>::arbitrary(&mut u)?)
                        * 2u64) >> 32
                    {
                        0u64 => Fieldless::A,
                        1u64 => Fieldless::B,
                        _ => {
                            ::core::panicking::panic(
                                "internal error: entered unreachable code",
                            )
                        }
                    },
                )
            })();
            if guard_against_recursion {
                RECURSIVE_COUNT_Fieldless
                    .with(|count| {
                        count.set(count.get() - 1);
                    });
            }
            result
        }
        fn size_hint(depth: usize) -> (usize, ::core::option::Option<usize>) {
            Self::try_size_hint(depth).unwrap_or_default()
        }
        #[inline]
        fn try_size_hint(
            depth: usize,
        ) -> ::core::result::Result<
            (usize, ::core::option::Option<usize>),
            arbitrary::MaxRecursionReached,
        > {
            Ok(
                arbitrary::size_hint::and(
                    <u32 as arbitrary::Arbitrary>::try_size_hint(depth)?,
                    arbitrary::size_hint::try_recursion_guard(
                        depth,
                        |depth| {
                            Ok(
                                arbitrary::size_hint::or_all(
                                    &[
                                        Ok(arbitrary::size_hint::and_all(&[]))?,
                                        Ok(arbitrary::size_hint::and_all(&[]))?,
                                    ],
                                ),
                            )
                        },
                    )?,
                ),
            )
        }
    }
};

The new generated code is this:

const _: () = {
    #[automatically_derived]
    impl<'arbitrary> arbitrary::Arbitrary<'arbitrary> for Fieldless {
        fn arbitrary(
            u: &mut arbitrary::Unstructured<'arbitrary>,
        ) -> arbitrary::Result<Self> {
            Ok(
                match (u64::from(<u32 as arbitrary::Arbitrary>::arbitrary(u)?) * 2u64)
                    >> 32
                {
                    0u64 => Fieldless::A,
                    1u64 => Fieldless::B,
                    _ => {
                        ::core::panicking::panic(
                            "internal error: entered unreachable code",
                        )
                    }
                },
            )
        }
        fn arbitrary_take_rest(
            mut u: arbitrary::Unstructured<'arbitrary>,
        ) -> arbitrary::Result<Self> {
            Ok(
                match (u64::from(<u32 as arbitrary::Arbitrary>::arbitrary(&mut u)?)
                    * 2u64) >> 32
                {
                    0u64 => Fieldless::A,
                    1u64 => Fieldless::B,
                    _ => {
                        ::core::panicking::panic(
                            "internal error: entered unreachable code",
                        )
                    }
                },
            )
        }
        fn size_hint(depth: usize) -> (usize, ::core::option::Option<usize>) {
            <u32 as arbitrary::Arbitrary>::size_hint(depth)
        }
    }
};

Copy link
Member

@fitzgen fitzgen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@fitzgen fitzgen merged commit 3fbc2d7 into rust-fuzz:main Aug 12, 2025
6 checks passed
@nnethercote nnethercote deleted the fieldless-enums-no-recursion branch August 13, 2025 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants