Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Something I've been greatly enjoying in using Zig is builtin support for packed bitfields in the form of packed structs.

The data structure from the article would be:

    const RGB = packed struct(u16) {
                    b: u5,
                    g: u6,
                    r: u5,
                }
So the test becomes

       const gte: bool = x.r >= y.r and x.g >= y.g and x.b >= y.b;
Okay, so this doesn't get us the optimal form described in the article.

Or does it? Since the compiler knows about packed structs, it could perform the optimization for me.

Does it, right this instant? Eh, probably not. But compilers have a tendency to improve, and this is a local pattern which would be fairly easy to recognize. The recognition is the point: it's much, much harder to recognize the intent in the initial implementations described in the article, and then replace them with the optimal version. The way I was able to write it in Zig, in addition to being far more expressive of the algorithm's intent, conveys to the compiler: compare the values of these bitfields, I don't care how. The compiler doesn't have to prove that I have no other reason for the other operations, besides making said comparison possible: it can just emit the optimal code.



C++ also supports this. However, you still end up needing to do a bunch of operations, the compiler just hides it from you which is why I think Chen opted not to use it.

  struct my_bitfield
  {
    uint16_t b:5;
    uint16_t g:6;
    uint16_t r:5;
  };

  bool IsEveryComponentGreaterThanOrEqual4(my_bitfield x, my_bitfield y)
  {
    return x.b > y.b && x.g > y.b && x.r > y.r;
  }


Some differences: the standard doesn't guarantee that C++ bitfields will be densely packed (this is mostly a technicality), you can't have non-const bitfields, and you can't take a pointer to a bitfield. Zig's pointer-to-bitfield is a special type, for obvious reasons, but they can come in handy.

I have no idea if the requirement that each bitfield be reified as its declared type inhibits optimization of that example or not, in practice. Maybe, maybe not.


C bitfields aren't packed. You have to resort to compiler extensions to get that behavior.


Not sure where you got that from. I think you’re confusing it with structs.

> Consecutive bit fields are packed together, but each bit field must fit within a single object of its specified type

[1] https://www.gnu.org/software/c-intro-and-ref/manual/html_nod...


Which C compiler doesn't pack bitfields? Anything on x86 or ARM is bound by the ABI to pack in a standard and link-compatible way.

Seems there would only be a problem on some proprietary compiler for an embedded, bespoke target.


I think SDCC has weird bitfields, originally due to a bug but now baked into the ABI for some of its platforms?

But there are only a handful of ways to lay out bitfields:

* Use little-endian or big-endian bit order? (this need not match the byte order, but usually does - IIRC recent GCC no longer supports any platforms where it doesn't?). This is the only one you can't control. Networking headers rely on a 2-way preprocessor branch so no other ways are really possible, at least up to `unsigned int` (may be 16) bits.

* If padding is needed, is it on the left or on the right? or, on the top or on the bottom? (this might always be tied to bit-endianness, but theoretically it is detached). For maximum portability, you must add explicit padding, doing math with `CHAR_BIT`.

* What exactly happens if an over-sized bitfield is attempted? (beware, there is no stable ABI for this in practice!)

* Do zero-sized bitfields align to the next unit?

* Can bitfields cross units? Is perhaps a larger-than-user-specified unit used? Consider `struct {uint8_t a:4, b:8, c:4};`

* If a mixture of types is used, do they get aggregated unconditionally, by size, by type? Ignoring signedness or not? Consider `struct { int a:1; long b:1; long long c:1; }`

* Is `int` the same as `signed int` or `unsigned int`, and for what widths?

It's unfortunate that testing some of these can't be done at compile time (a few can via `sizeof`), so you may need to either manually inspect assembly code, or else rely on optimization to do the emit-constants-via-strings trick when cross-compiling (C++, if supported on your platform, may do better than C at accessible compile-time optimization).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: