ziglang/zig
261
5.9k
Fork
You've already forked zig
763

Introduce byteorder attribute on pointer types #35569

Open
opened 2026年06月01日 19:34:37 +02:00 by alexrp · 5 comments

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`.
alexrp added this to the 0.18.0 milestone 2026年06月01日 19:34:37 +02:00
Member
Copy link

I really like this proposal, especially the part about exhaustive enums! It would make it much more feasible for the compiler to treat invalid enum tag values as invalid state more aggressively (e.g. by treating them effectively as undefined) without introducing endianness-related footguns/surprising behavior.

I really like this proposal, especially the part about exhaustive enums! It would make it much more feasible for the compiler to treat invalid enum tag values as invalid state more aggressively (e.g. by treating them effectively as `undefined`) without introducing endianness-related footguns/surprising behavior.
Contributor
Copy link

This proposal is intended to supersede https://github.com/ziglang/zig/issues/3380.

how is this so when one of the main motivations for that one was desiring integration with std.Io.Reader ?

> This proposal is intended to supersede https://github.com/ziglang/zig/issues/3380. how is this so when one of the main motivations for that one was desiring integration with `std.Io.Reader` ?
Author
Owner
Copy link

@nektro wrote in #35569 (comment):

integration with std.Io.Reader

I'm not sure what this means?

In any case... I'm going off months-old memory here so take with a grain of salt, but I believe at least 3 core team members (myself included) do not particularly like #3380 because:

  • it doesn't actually solve the endianness problems we've had in the compiler in practice
  • it doesn't address the value validity problem at all
  • it doesn't apply to types other than integers, so all other types mentioned above will remain painful to work with
  • it doesn't provide as strong safety as a pointer attribute
@nektro wrote in https://codeberg.org/ziglang/zig/issues/35569#issuecomment-16313396: > integration with `std.Io.Reader` I'm not sure what this means? In any case... I'm going off months-old memory here so take with a grain of salt, but I believe at least 3 core team members (myself included) do not particularly like #3380 because: * it doesn't actually solve the endianness problems we've had in the compiler in practice * it doesn't address the value validity problem at all * it doesn't apply to types other than integers, so all other types mentioned above will remain painful to work with * it doesn't provide as strong safety as a pointer attribute
Member
Copy link

To me it seems like this would allow all endian-aware reader functions to share basically the same implementation:

inlinefntakeEndian(r:*Reader,comptimeT:type,endian:Endian)Error!T{returnswitch(endian){inlineelse=>|e|(tryr.takeEndianPointer(T,e)).*,};}fntakeEndianPointer(r:*Reader,comptimeT:type,comptimeendian:Endian)Error!*constalign(1)byteorder(endian)T{// Type validation here...return@ptrCast(tryr.takeArray(@sizeOf(T)));}

(not saying that we necessarily want this API, but the new impls for endian-aware fns would probably look similar to this)

To me it seems like this would allow all endian-aware reader functions to share basically the same implementation: ```zig inline fn takeEndian(r: *Reader, comptime T: type, endian: Endian) Error!T { return switch (endian) { inline else => |e| (try r.takeEndianPointer(T, e)).*, }; } fn takeEndianPointer(r: *Reader, comptime T: type, comptime endian: Endian) Error!*const align(1) byteorder(endian) T { // Type validation here... return @ptrCast(try r.takeArray(@sizeOf(T))); } ``` (not saying that we necessarily want this API, but the new impls for endian-aware fns would probably look similar to this)
Contributor
Copy link

I'm not sure what this means?

there was std.io.Reader.readStruct in the past but getting those values into native ints is probably the way to go anyway, alas likely why it was removed

> I'm not sure what this means? there was `std.io.Reader.readStruct` in the past but getting those values into native ints is probably the way to go anyway, alas likely why it was removed
Sign in to join this conversation.
No Branch/Tag specified
master
elfv2
spork8
restricted
0.16.x
panic-rewrite
parking-futex-lockfree
windows-Io-cleanup
Io-watch
ParseCommandLineOptions
windows-async-files
poll-ring
debug-file-leaks-differently
debug-file-leaks
ProcessPrng
elfv2-dyn
jobserver
threadtheft
io-threaded-no-queue
0.15.x
Io.net
comptime-allocator
restricted-function-pointers
cli
wasm-linker-writer
wrangle-writer-buffering
sha1-stream
async-await-demo
fixes
0.14.x
ast-node-methods
macos-debug-info
make-vs-configure
fuzz-macos
sans-aro
ArrayList-reserve
incr-bug
llvm-ir-nosanitize-metadata
ci-tarballs
ci-scripts
threadpool
0.12.x
new-pkg-hash
json-diagnostics
more-doctests
rework-comptime-mutation
0.11.x
ci-perf-comment
stage2-async
0.10.x
autofix
0.9.x
aro
hcs
0.8.x
0.7.x
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.12.1
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
Labels
Clear labels
abi/f32
abi/ilp32
abi/sf
accepted
This proposal is planned.
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
The web application for interactive documentation and generation of its assets.
backend/c
The C backend outputs C source code.
backend/llvm
The LLVM backend outputs an LLVM bitcode module.
backend/self-hosted
The self-hosted backends produce machine code directly.
binutils
Zig's included binary utilities: zig ar, zig dlltool, zig lib, zig ranlib, zig objcopy, and zig rc.
breaking
Implementing this issue could cause existing code to no longer compile or have different behavior.
build system
The Zig build system - zig build, std.Build, the build runner, and package management.
debug info
An issue related to debug information (e.g. DWARF) produced by the Zig compiler.
docs
An issue with documentation, e.g. the language reference or standard library doc comments.
error message
This issue points out an error message that is unhelpful and should be improved.
frontend
Tokenization, parsing, AstGen, ZonGen, Sema, Legalize, and Liveness.
fuzzing
An issue related to Zig's integrated fuzz testing.
incremental
Reuse of internal compiler state for faster compilation.
lib/c
This issue relates to Zig's libc implementation and/or vendored libcs.
lib/compiler-rt
This issue relates to Zig's compiler-rt library.
lib/cxx
This issue relates to Zig's vendored libc++ and/or libc++abi.
lib/std
This issue relates to Zig's standard library.
lib/tsan
This issue relates to Zig's vendored libtsan.
lib/ubsan-rt
This issue relates to Zig's ubsan-rt library.
lib/unwind
This issue relates to Zig's vendored libunwind.
linking
Zig's integrated object file and incremental linker.
miscompilation
The compiler reports success but produces semantically incorrect code.
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
This issue suggests language modifications. If it also has the "accepted" label then it is planned.
release notes
This issue or pull request should be mentioned in the release notes.
testing
This issue is related to testing the compiler, standard library, or other parts of Zig.
zig cc
Zig as a drop-in C-family compiler.
zig fmt
The Zig source code formatter.
zig reduce
The Zig source code reduction tool.
bounty
https://ziglang.org/news/announcing-donor-bounties
bug
Observed behavior contradicts documented or intended behavior.
contributor-friendly
This issue is limited in scope and/or knowledge of project internals.
downstream
An issue with a third-party project that uses this project.
enhancement
Solving this issue will likely involve adding new logic or components to the codebase.
infra
An issue related to project infrastructure, e.g. continuous integration.
optimization
A task to improve performance and/or resource usage.
question
No questions on the issue tracker; use a community space instead.
regression
Something that used to work in a previous version stopped working
upstream
An issue with a third-party project that this project uses.
use case
Describes a real use case that is difficult or impossible, but does not propose a solution.
No labels
abi/f32
abi/ilp32
abi/sf
accepted
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
backend/c
backend/llvm
backend/self-hosted
binutils
breaking
build system
debug info
docs
error message
frontend
fuzzing
incremental
lib/c
lib/compiler-rt
lib/cxx
lib/std
lib/tsan
lib/ubsan-rt
lib/unwind
linking
miscompilation
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
release notes
testing
zig cc
zig fmt
zig reduce
bounty
bug
contributor-friendly
downstream
enhancement
infra
optimization
question
regression
upstream
use case
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ziglang/zig#35569
Reference in a new issue
ziglang/zig
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?