A compile-time check is implemented in a new src/assumptions.h
which verifies several aspects that are implementation-defined in C:
- size of bytes
- conversion between unsigned and (negative) signed types
- right-shifts of negative signed types.
A compile-time check is implemented in a new src/assumptions.h
which verifies several aspects that are implementation-defined in C:
It seems fairly easy to turn this into a compile time check, by using (that_expression - 1) as the size of array.
However, maybe we want to include endianness into the check, and I’m not sure that can be done at compile time.
as the size of array.
Var arrays are C99 and “optional” in later standards and not supported by MSVC.
@gmaxwell Yes, but this isn’t relying on vararrays - the expression is a compile-time constant.
In fact, it has to be constant. A vararray of negative size could compile. So you have to do something like define a struct with a member array (where a vararray isn’t allowed).
int
etc?
One downside is that when it fails you get an extremely opaque error.
We could use static_assert
if it’s supported by the compiler but I don’t think it’s worth the additional #ifdef
s. These errors should be super rare, so it’s acceptable that they’re somewhat opaque. And it’s probably better to have a compiler error instead of a runtime error.
I read up a little bit on these techniques. The issues with VLAs was something else: In some cases this array trick silently didn’t do anything when the expression is non-constant, e.g., when you have a compiler like GCC that supports VLAs as an extension and you happen to pass a non-constant value, then it can’t be a compiler error even if the size is negative. This hit the Linux kernel for example.
But what we do here is robust against this failure mode because VLAs are never supported on a file level / global scope. I verified this in godbolt. Even GCC errors out if you pass a non-constant value. (Clang fails at another stage but the message is pretty harsh: fields must have a constant size: 'variable length array in structure' extension will never be supported
:D) The difference in the Linux kernel is probably that they don’t want to collect all the assumptions in a single place and they want a statement instead.
Approach ACK
Maybe we want to move the assumptions to a separate header like in Bitcoin Core (https://github.com/bitcoin/bitcoin/blob/master/src/compat/assumptions.h). That’s cleaner and it’s easier to point people to the file, e.g., in the README.
A separate file might make it marginally more likely to get a more useful error message (e.g. telling you what file it was in) from more compilers.
That’s a good point.
Moved.
You might want to a comment above it that says something like:
/* This library, like most software, relies on a number of compiler implementation defined (not undefined) behaviours. Although the behaviours we require are essentially universal we test them specifically here to reduce the odds of experiencing an unwelcome surprise. */
25+ implementation-defined. Verify that they function as reinterpreting the lower
26+ bits of the input in two's complement notation. Do this for conversions:
27+ - from uint(N)_t to int(N)_t with negative result
28+ - from uint(2N)_t to int(N)_t with negative result
29+ - from int(2N)_t to int(N)_t with negative result
30+ - from int(2N)_t to int(N)_t with positivie result */
57+ /* To int128_t. */
58+ ((int128_t)(((uint128_t)0xB1234567C8901234ULL << 64) + 0xD5678901E2345678ULL) == (int128_t)(-(int128_t)0x8E1648B3F50E80DCULL * 0x8E1648B3F50E80DDULL + 0x5EA688D5482F9464ULL)) &&
59+#endif
60+
61+ /* Right shift on negative signed values is implementation defined. Verify that it
62+ acts as a conversion to unsigned, right shift, and conversion back to signed.
Added conversions from signed to signed, and a check for CHAR_BIT. Anything else? Do we have any unusual assumptions on the size of
int
etc?
ACK except nit. We should then add more assumptions from #792 later.