README revision 1.1 1 1.1 mrg This provides some background the design of the generated headers. We
2 1.1 mrg started out trying to generate bit fields but it evolved into the pack
3 1.1 mrg functions because of a few limitations:
4 1.1 mrg
5 1.1 mrg 1) Bit fields still generate terrible code today. Even with modern
6 1.1 mrg optimizing compilers you get multiple load+mask+store operations
7 1.1 mrg to the same dword in memory as you set individual bits. The
8 1.1 mrg compiler also has to generate code to mask out overflowing values
9 1.1 mrg (for example, if you assign 200 to a 2 bit field). Our driver
10 1.1 mrg never writes overflowing values so that's not needed. On the
11 1.1 mrg other hand, most compiler recognize that the template struct we
12 1.1 mrg use is a temporary variable and copy propagate the individual
13 1.1 mrg fields and do amazing constant folding. You should take a look
14 1.1 mrg at the code that gets generated when you compile in release mode
15 1.1 mrg with optimizations.
16 1.1 mrg
17 1.1 mrg 2) For some types we need to have overlapping bit fields. For
18 1.1 mrg example, some values are 64 byte aligned 32 bit offsets. The
19 1.1 mrg lower 5 bits of the offset are always zero, so the hw packs in a
20 1.1 mrg few misc bits in the lower 5 bits there. Other times a field can
21 1.1 mrg be either a u32 or a float. I tried to do this with overlapping
22 1.1 mrg anonymous unions and it became a big mess. Also, when using
23 1.1 mrg initializers, you can only initialize one union member so this
24 1.1 mrg just doesn't work with out approach.
25 1.1 mrg
26 1.1 mrg The pack functions on the other hand allows us a great deal of
27 1.1 mrg flexibility in how we combine things. In the case of overlapping
28 1.1 mrg fields (the u32 and float case), if we only set one of them in
29 1.1 mrg the pack function, the compiler will recognize that the other is
30 1.1 mrg initialized to 0 and optimize out the code to or it it.
31 1.1 mrg
32 1.1 mrg 3) Bit fields (and certainly overlapping anonymous unions of bit
33 1.1 mrg fields) aren't generally stable across compilers in how they're
34 1.1 mrg laid out and aligned. Our pack functions let us control exactly
35 1.1 mrg how things get packed, using only simple and unambiguous bitwise
36 1.1 mrg shifting and or'ing that works on any compiler.
37 1.1 mrg
38 1.1 mrg Once we have the pack function it allows us to hook in various
39 1.1 mrg transformations and validation as we go from template struct to dwords
40 1.1 mrg in memory:
41 1.1 mrg
42 1.1 mrg 1) Validation: As I said above, our driver isn't supposed to write
43 1.1 mrg overflowing values to the fields, but we've of course had lots of
44 1.1 mrg cases where we make mistakes and write overflowing values. With
45 1.1 mrg the pack function, we can actually assert on that and catch it at
46 1.1 mrg runtime. bitfields would just silently truncate.
47 1.1 mrg
48 1.1 mrg 2) Type conversions: some times it's just a matter of writing a
49 1.1 mrg float to a u32, but we also convert from bool to bits, from
50 1.1 mrg floats to fixed point integers.
51 1.1 mrg
52 1.1 mrg 3) Relocations: whenever we have a pointer from one buffer to
53 1.1 mrg another (for example a pointer from the meta data for a texture
54 1.1 mrg to the raw texture data), we have to tell the kernel about it so
55 1.1 mrg it can adjust the pointer to point to the final location. That
56 1.1 mrg means extra work we have to do extra work to record and annotate
57 1.1 mrg the dword location that holds the pointer. With bit fields, we'd
58 1.1 mrg have to call a function to do this, but with the pack function we
59 1.1 mrg generate code in the pack function to do this for us. That's a
60 1.1 mrg lot less error prone and less work.
61