-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Integer-backed packed struct #5049
Comments
The only thing with this is that if your packed struct contains several fields that are structs, or you just have a lot of fields, or indeed, you care more that's it's packed than the final size of it, you have to manually calculate the bits - which would be a bit of PITA. |
This proposal doesn't remove the current Byte swapping a normal packed struct is a bridge we'll have to figure out how to cross eventually, since networking is one of its intended use cases. But the rules for that could be allowed to be quite complicated, since a standard packed struct can be much larger than a single integer. (note though, complicated does not necessarily imply slow in this case.) |
I think the enumflagset proposal also handles a similar set of use cases, so I think doing either one or the other makes sense. I first considered something like enumflagset, but there are a couple reasons why I prefer this solution:
I think it might also make sense to make a FlagsMixin class in the standard library that supplies functions for bulk operations ( |
In this project I'm only using volatile only on aligned word pointers (to u32 or to packed structs of 32 bits.) |
I agree with this, although I would make one change to the syntax: const ExampleFlags = packed(u32) struct { // ... because |
#4016 ? |
I would like it if each field could explicitly specify their bit offset (since that's 100% what you're trying to accomplish here), but I'm not sure how that syntax would look, but I'm certainly sure that it would look better than that |
Why don't we make this a compile error? What's the use-case? |
Good point, I agree. |
Does this mean the following would be a compile error? const ProtFlags = packed struct(u32) {
read: bool,
write: bool,
execute: bool,
}; I am only specifying 3 bits, but using a backing Today, I just add an extra |
Yes, this should absolutely be a compile error. If you are interfacing with a C API where these bits are reserved, you need to set them to the reserved value (probably 0). Same if you are writing a binary file. Padding has undefined values, and nonstandard values in padding may produce an undefined value. So there must not be any undefined padding bits in a packed struct where the underlying representation is important. |
Would it be possible to infer the number of necessary bits to pad the struct to the correct size? Again using my previous example: Proposed (status quo): const ProtFlags = packed struct(u32) {
read: bool,
write: bool,
execute: bool,
_: u29 = 0,
}; I am already specifying If the compiler could infer the necessary number of padding bits then I could do something like this instead: const ProtFlags = packed struct(u32) {
read: bool,
write: bool,
execute: bool,
_ = 0,
}; This way I can still initialize the unused/padding bits to avoid undefined values, but I also do not need to manually calculate the number needed. |
this is only useful in the case of one padding region. as soon as you have more than one region you have to specify the size of all but one padding region. it also might complicate the syntax, though i don't know the details there. it strikes me as another moving part for a very minor convenience. |
@gpanders I know I'm bikeshedding, but here would be my suggestion for an approach: Status Quo:const ProtFlags = packed struct(u32) {
read: bool,
write: bool,
execute: bool,
_: u29 = 0,
}; Alternative:const ProtFlags = packed struct(_) {
read: bool,
write: bool,
execute: bool,
_: u29 = 0,
};
|
The only problem with that is that the size of the struct is no longer obvious. In this example it’s easy enough to see, but in a more complex structure this would involve needing to add up the size of all of the individual fields.
So? Just have the final |
The size of an
I think this defeats one of the main benefits of the explicit backing integer, which is that a change to the backing integer or fields must be made while consciously considering the size and layout. Having a field of the Personally, I don't really care about whether we allow Edit: |
Right, but the difference is in those cases you are not explicitly specifying an integer width like you are in
Sure and I agree with this, but I don't think The only other point I want to make (after which I'll leave it be) is that this is not just a matter of convenience (though it is convenient), it also resolves a source of potential ambiguity: const Foo = packed struct(u8) {
foo: u3,
bar: u2,
_: u4,
}; This fails to compile because the field widths add up to Your suggestion to use |
This is a good thing, and helps prevent typos or other layout mistakes from compiling, which is of significant importance to osdev.
As it always has been. Our conversation really has settled me on the side that neither of these proposals are of value, and that |
Nitpick: In discussions outside of the issue tracker, the consensus seems to be that |
This is implemented in the self-hosted compiler, which is now the default compiler. |
I still miss being able to perform logic operations: const flags0 = Flags{ .b = true };
const flags1 = Flags{ .a = true, .c = true };
const flags01 = flags0 | flags1; |
So far, Zig doesn't have a good solution for flags/bitfields. Packed structs are the current recommended solution, and they provide a lot of improvements over the standard C approach of defining integer constants. But they are still not quite sufficient, even with #3133. For me, the requirements of a good flags/bitfield type are:
&
,|
, etc)Requirements (1) and (2) are already satisfied by packed structs. Requirements (3) and (4) are not satisfied by packed structs yet, but are kind of a package deal. If you define load/store size you get deterministic endianness behavior. There have been ways suggested to do this, such as putting a
u0
aligned to the required word size at the beginning of a packed struct. (I can't find that suggestion anymore, if anyone knows where it is please let me know and I'll link it here). But assuming packed structs will always behave like structs from an ABI standpoint, they may not be able to satisfy requirement (5) for all calling conventions. This can lead to some pretty ridiculous workarounds, that are not always feasible.To solve this, I propose a variant of packed structs which are backed by a specified integer type, similar to enums backed by a specified integer type. Just like enums are integers at the ABI level, integer-backed packed structs are also integers at the ABI level. They are passed in registers across function call boundaries whenever their backing integer type would be, and they are allowed on extern boundaries only when their backing integer type would be. They can be used in atomic operations just like their integer type would be (with the exception of RMW operations like add, sub, etc, similar to enums). Their default alignment matches the alignment of their backing integer type.
My proposed syntax is this:
Integer operators (+, -, &, etc) are not defined for integer packed structs, but
@bitCast
is allowed to convert to the integer type if you really want to do bulk operations. We could also make a more specialized cast, analagous to@enumToInt
. Like normal packed structs or enums, bindable functions can be specified in the struct namespace to facilitate bulk operations at the API level.Fields are specified from the LSB of the backing integer to the MSB. This definition will always mean that endianness conversion for the flags field is the same as endianness conversion for the backing integer type. If the number of specified bits is fewer than the number available in the backing type, or if there are padding bits due to aligned fields, the value of the extra bits is undefined. If you require these values to be zeroed (e.g. for interop with a C api, MMIO, or sort ordering), you must explicitly add padding fields that are set to zero. If more bits are specified than are available in the backing type, that's a compile error.
Integer-backed packed structs are allowed to contain anything that a packed struct can contain and follows all of the layout rules of packed structs, as long as the contained elements can fit inside the specified number of bits. The backing type must be an actual integer. It cannot be another integer-backed struct or an extensible enum. When necessary, conversions between those types can instead be performed with
@bitCast
.When an integer packed struct is nested into another integer packed struct, it behaves like its integer type and is bit-packed, unless an alignment is explicitly specified.
Writing the entire value of a packed struct is guaranteed to be implemented with the same semantics as writing to a value of the integer type. Writing to a field in an integer-backed packed struct is semantically a read-modify-write operation on the entire integer. Whenever the write size is semantically observable (e.g. through atomic or volatile operations), the size of the read and write instructions for an integer packed struct is guaranteed to be the same as that of the backing integer type. As always, if the read/write is not observable, the optimizer is free to do whatever it wants (load/store the smallest size it can, split into multiple stores, combine loads/stores together, ignore all of them and use a register, etc).
Consequently, a pointer to a field of a backing type has the type
*align(<parent struct alignment>:<bit offset in integer>:<byte size of integer>) <field type>
. So&(ExampleFlags{}).small_value
is of type*align(4:2:4) u3
, and&(ExampleFlags{}).aligned_value
is of type*align(4:8:4) u8
.This proposal would provide an explicit solution for #1834, #4056, and #4185, without limiting the capabilities of the more general
packed struct
. With some extra work, it could also be applied to solve #1761 and #3472.The text was updated successfully, but these errors were encountered: