Introduction
The proposal here is to introduce a byteorder attribute on pointer types sitting alongside const, align, etc. The syntax looks like var ptr: *byteorder(.big) std.elf.Rela = .... When loading or storing through a pointer annotated with byteorder, the compiler ensures that the data is @byteSwap'd appropriately.
This proposal is intended to supersede https://github.com/ziglang/zig/issues/3380.
Motivation
It's very easy to accidentally leave off a @byteSwap or std.mem.byteSwapAllFields call and end up with endian bugs. Empirically, this has been a problem in the Elf2 implementation. A byteorder pointer attribute prevents this class of bugs almost entirely.
There is also a concern with validity of foreign-endian values; for example, it's not currently clear that loading a big-endian value of an exhaustive enum type on a little-endian system is even legal because the resulting bit pattern may not represent a valid member of the enum. I think the only way for this to work would be specifying that it's only illegal to use the value... except with @byteSwap specifically. But that seems rather janky, and practically screams "this should be handled by a language feature".
Less importantly but still noteworthy is the fact that byteorder also composes well with a future proposal that @mlugg and I are working on which will allow easily working with foreign-target structs in memory.
Semantics
Grammar:
ByteOrder <- KEYWORD_byteorder LPAREN Expr RPAREN
PrefixTypeOp <-
...snip...
/ (ManyPtrTypeStart / SliceTypeStart) KEYWORD_allowzero? ByteAlign? AddrSpace? ByteOrder? KEYWORD_const? KEYWORD_volatile?
/ SinglePtrTypeStart KEYWORD_allowzero? BitAlign? AddrSpace? ByteOrder? KEYWORD_const? KEYWORD_volatile?
...snip...
KEYWORD_byteorder <- 'byteorder' end_of_word
keyword <- KEYWORD_byteorder / ...snip...
(byteorder as the keyword is chosen because endian seemed like an unfortunate identifier to reserve in the language.)
Expr must be of type std.lang.Endian, which is also provided as the target type.
std.lang.Type.Pointer.Attributes gains an extra field:
pubconstAttributes=struct{@"const":bool=false,@"volatile":bool=false,@"allowzero":bool=false,@"addrspace":?AddressSpace=null,@"align":?usize=null,@"byteorder":?Endian=null,};
A null value is equivalent to omitting byteorder on the type and means that the native endianness for the compilation target will be used.
Types are byte-swapped during loads/stores according to these rules:
uN, iN where N is divisible by 8, usize, isize: @byteSwap
f16, f32, f64, f80, f128: Bitcast to uN and apply appropriate rule
*T, ?*T, [*]T, etc: Convert to usize and apply appropriate rule
bool: Do nothing
void: Do nothing
enum(T), packed struct, packed union: Convert to backing integer and apply appropriate rule
[n]T: Apply appropriate rule for each element
extern struct: Apply appropriate rule for each field
Loading/storing types not mentioned in these rules through a byteorder-annotated pointer is a compile error. For example, there is nothing meaningful we can do for extern union.
With regards to https://github.com/ziglang/zig/issues/24061, the byteorder attribute applies to the backing integer which is actually loaded/stored, similar to align.
## Introduction
The proposal here is to introduce a `byteorder` attribute on pointer types sitting alongside `const`, `align`, etc. The syntax looks like `var ptr: *byteorder(.big) std.elf.Rela = ...`. When loading or storing through a pointer annotated with `byteorder`, the compiler ensures that the data is `@byteSwap`'d appropriately.
This proposal is intended to supersede https://github.com/ziglang/zig/issues/3380.
## Motivation
It's very easy to accidentally leave off a `@byteSwap` or `std.mem.byteSwapAllFields` call and end up with endian bugs. Empirically, this has been a problem in the Elf2 implementation. A `byteorder` pointer attribute prevents this class of bugs almost entirely.
There is also a concern with validity of foreign-endian values; for example, it's not currently clear that loading a big-endian value of an exhaustive enum type on a little-endian system is even legal because the resulting bit pattern may not represent a valid member of the enum. I think the only way for this to work would be specifying that it's only illegal to *use* the value... except with `@byteSwap` specifically. But that seems rather janky, and practically screams "this should be handled by a language feature".
Less importantly but still noteworthy is the fact that `byteorder` also composes well with a future proposal that @mlugg and I are working on which will allow easily working with foreign-target structs in memory.
## Semantics
Grammar:
```
ByteOrder <- KEYWORD_byteorder LPAREN Expr RPAREN
PrefixTypeOp <-
...snip...
/ (ManyPtrTypeStart / SliceTypeStart) KEYWORD_allowzero? ByteAlign? AddrSpace? ByteOrder? KEYWORD_const? KEYWORD_volatile?
/ SinglePtrTypeStart KEYWORD_allowzero? BitAlign? AddrSpace? ByteOrder? KEYWORD_const? KEYWORD_volatile?
...snip...
KEYWORD_byteorder <- 'byteorder' end_of_word
keyword <- KEYWORD_byteorder / ...snip...
```
(`byteorder` as the keyword is chosen because `endian` seemed like an unfortunate identifier to reserve in the language.)
`Expr` must be of type `std.lang.Endian`, which is also provided as the target type.
`std.lang.Type.Pointer.Attributes` gains an extra field:
```zig
pub const Attributes = struct {
@"const": bool = false,
@"volatile": bool = false,
@"allowzero": bool = false,
@"addrspace": ?AddressSpace = null,
@"align": ?usize = null,
@"byteorder": ?Endian = null,
};
```
A null value is equivalent to omitting `byteorder` on the type and means that the native endianness for the compilation target will be used.
Types are byte-swapped during loads/stores according to these rules:
* `uN`, `iN` where `N` is divisible by 8, `usize`, `isize`: `@byteSwap`
* `f16`, `f32`, `f64`, `f80`, `f128`: Bitcast to `uN` and apply appropriate rule
* `*T`, `?*T`, `[*]T`, etc: Convert to `usize` and apply appropriate rule
* `bool`: Do nothing
* `void`: Do nothing
* `enum(T)`, `packed struct`, `packed union`: Convert to backing integer and apply appropriate rule
* `[n]T`: Apply appropriate rule for each element
* `extern struct`: Apply appropriate rule for each field
Loading/storing types not mentioned in these rules through a `byteorder`-annotated pointer is a compile error. For example, there is nothing meaningful we can do for `extern union`.
With regards to https://github.com/ziglang/zig/issues/24061, the `byteorder` attribute applies to the backing integer which is actually loaded/stored, similar to `align`.