Skip to content

Proposal: allow load/store widths for a pointer to be specified, like alignment #5591

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

Closed
cartr opened this issue Jun 12, 2020 · 1 comment
Closed
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@cartr
Copy link
Contributor

cartr commented Jun 12, 2020

In embedded development, sometimes certain areas of memory can only be correctly read from/written to using memory access instructions of specific widths. For example, the Game Boy Advance's VRAM must always be written in units of 2 bytes or greater. C programs often deal with these limitations using inline assembly, or by writing functions that use volatile like:

fn setVramByte(dest: *u8, value: u8) void {
    @setRuntimeSafety(false);

    const dest_int = @ptrToInt(dest);
    const value_u16: u16 = value;
    if (dest_int & 1 == 1) {
        const dest_halfword = @intToPtr(*volatile u16, dest_int - 1);
        const extra_dest_byte = @ptrCast(*u8, dest_halfword).*;
        dest_halfword.* = (value_u16 << 8) | extra_dest_byte;
    } else {
        const dest_halfword = @intToPtr(*volatile u16, dest_int);
        const extra_dest_byte: u16 = @intToPtr(*u8, dest_int + 1).*;
        dest_halfword.* = (extra_dest_byte << 8) | value_u16;
    }
}

This works, but it has some drawbacks. The use of volatile unnecessarily restricts the compiler from doing things like combining two stores of adjacent VRAM addresses into one halfword store. It's also challenging to safely use structs located in VRAM with this approach.

Zig could solve this problem by adding a new set of attributes to pointers: loadwidth and storewidth. So a pointer to a byte in VRAM could have the type *loadwidth(1, 2, 4) storewidth(2, 4) u8, and the compiler would guarantee that writing to the pointer would never result in a single-byte store instruction. In the simplest case, attempting to write a single byte to a single storewidth-restricted pointer would generate a read/modify/write sequence similarly to a packed struct member pointer. But the compiler would be free to remove, reorder, or combine writes so long as it never generated a single-byte write. (Any reads/writes through loadwidth/storewidth-restricted pointers would also need to be aligned to the minimum permitted load/store width.)

Like packed struct member pointers, you wouldn't be able to pass a loadwidth/storewidth-restricted pointer to a function expecting a regular pointer. Instead, the following type-conversion rules would apply:

  • When loadwidth or storewidth isn't specified, any load or store width is permitted.
    • On const pointers, no store width is ever permitted.
  • Values can be cast to a subset of their permitted load/store widths. So if a function expects *const loadwidth(2) u8, you can pass a *u8, but you can't pass a *loadwidth(1) u8.
  • Using @ptrCast, you can add or remove permitted load/store widths arbitrarily.

A related proposal is #5049, which suggests creating a kind of struct that guarantees the size of its loads/stores with the same kind of read/modify/write behavior I suggest. But it always restricts the loads/stores to a single width -- the size of the struct -- which isn't always what you want. (setVramByte is a little bit shorter on ARM when you use a single-byte load, but the store always has to be two bytes.)

@daurnimator daurnimator added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jun 12, 2020
@Vexu Vexu added this to the 0.7.0 milestone Jun 12, 2020
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 27, 2020
@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 Nov 23, 2021
kyrias added a commit to kyrias/zig-embedded that referenced this issue Feb 5, 2022
Some of the MMIO registers only support word-sized stores, but in the
release modes LLVM optimizes these volatile stores into multiple
smaller reads and writes.

Until zig gets a way to override this behaviour somehow we're going to
need to use some inline assembly.  svd4zig should be sent a PR to emit
assembly instead of the direct stores. Might be good if there's

This proposal would be useful here: ziglang/zig#5591

Signed-off-by: Johannes Löthberg <[email protected]>
@andrewrk andrewrk modified the milestones: 0.10.0, 0.11.0 Apr 16, 2022
@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jul 9, 2023
@andrewrk
Copy link
Member

andrewrk commented Feb 9, 2025

I think this is perfectly solved with volatile + pointer alignment + arbitrary integer sizes.

@andrewrk andrewrk closed this as not planned Won't fix, can't repro, duplicate, stale Feb 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

4 participants