Field Pointer Alignment

Consider the code:

test {
    const MyStruct = struct { x: u32 };
    var my_struct: MyStruct align(8) = .{ .x = 5 };
    const ptr: *align(8) u32 = &my_struct.x;
    _ = ptr;
}

Currently, it fails, with this compiler error:

repro.zig:4:32: error: expected type '*align(8) u32', found '*u32'
    const ptr: *align(8) u32 = &my_struct.x;
                               ^~~~~~~~~~~~
repro.zig:4:32: note: pointer alignment '4' cannot cast into pointer alignment '8'

While, if x has an offset of zero relative to the entire struct (which would be the case most of the time), the compiler could actually guarantee that its alignment is equal to that of &my_struct, 8.

This can even happen without over-alignment with unions:

test {
    const MyUnion = union { a: u32, b: u64 };
    var my_union: MyUnion = .{ .a = 42 };
    const ptr: *align(@alignOf(MyUnion)) u32 = &my_union.a;
    _ = ptr;
}

Here, both fields could lie at the union’s base address, which is aligned to accommodate a u64, and thus the compiler could guarantee a pointer to an a field has that alignment as well.

Therefore, I propose that pointers to fields are aligned to what the compiler can guarantee, which in my example cases of an offset of zero could be higher than the explicitly declared field alignment or the field type’s natural alignment, without @alignCast(...), and that a new builtin akin to a @fieldPtrAlignment(comptime Ptr: type, comptime field_name: []const u8) is added to determine the alignment of a field pointer given its name and the struct/union pointer type.

With this implemented both tests would pass, given that in their specific compilation mode, @fieldPtrAlignment(*align(8) MyStruct, "x") >= 8 and @fieldPtrAlignment(*MyUnion, "a") >= @alignOf(MyUnion) respectively are true.

The layout of a normal struct is undefined, so it is not guaranteed that given a struct with a single field, that field will have the same alignment as the (instance in this case) struct.

If you make it extern struct, who’s rules do -indirectly- guarantee this, then the code does compile and run.

This also applies to union’s

If your wondering why this is the case, its to allow the compiler to insert information for safety checks or perform layout optimisations etc. structs currently don’t get much special treatment but there are plans to do so.
Unions, if untagged, do get a secret tag in safe modes for safety checks.

1 Like

This would hold only when the compiler happens to arrange the fields in the way I described. My point is that it should expose the alignment guarantees that follow from whether it did.

The alignment guarantee of a field, in a normal (auto layout) struct/union, is the field alignment override (field: T align(N)), or if that is not specified the default alignment of the type.

Over-aligning an instance does not guarantee that any fields are over-aligned, even if there is only a single field, because of the reasons I described earlier: the layout is undefined.

However, under-aligning an instance does, I am not sure how to explain that well, it’s because of math :3.

Once again: I agree that this (that the fields become over-aligned as well) is not always the case. But depending on what layout the compiler chooses, it might be able to provide a stronger alignment guarantee, while there is currently no way to know in userland if that is the case.

To be clear, I am not saying that my example code should always pass. Because, like you say, structs and unions have an ill-defined layout. But there are cases where than compiler can know this can compile, but will still reject it. Which is what I want to eliminate, or at least reduce with this.

why would you want to rely on unreliable special cases? if you need more control over layout extern and packed attributes are available.

1 Like

My personal use-case is this:

I was looking at the standard library memory pool, which manually overlays free list nodes with allocated values, and wondered if you could use a union instead:

    const Cell = union {
        free: std.SinglyLinkedList.Node,
        used: Item align(min_item_alignment.toByteUnits()), // `min_item_alignment` is the user-specified alignment or `Item`'s natural alignment
    };

My ItemPtr, its standard library equivalent defined as

      pub const item_alignment: Alignment = .max(pool_options.alignment orelse .of(Item), .of(Node));

        const Node = std.SinglyLinkedList.Node;
        const ItemPtr = *align(item_alignment.toByteUnits()) Item;

would always be a pointer to the used field of a Cell. Which, sometimes, is greater than my item_alignment, namely @alignOf(Cell). But precisely because unions are opaque, I cannot safely assume this, nor determine the actual alignment guarantees.

With my change, ItemPtr could be defined as *align(@fieldPtrAlignment(*Cell, "used")) Item. Though now I think about it: perhaps @FieldPtr(*Cell, "used"), returning the entire pointer type, would be better, as a direct mirror to @fieldParentPtr’s second operand.

it would have to be an extern union, but yes its possible, just personal taste between it and ptr casting tbh.

ah, that does have the limitation of only allowing c abi types, you can get around that by making the payload type a raw byte array

That would indeed work but at that point why bother with a union at all that would make the code semantically dependent on the (C) layout. While it doesn’t have to be: you do not want any specific layout, you just want to know one property of that layout. You could query that with @FieldPtr/@fieldPtrAlignment.

zig does not provide an in-between of control over layout, either you have complete control (through c semantics) or you don’t.

The alternative is ptr shenanigans.

If by “in-between of control over layout” you mean introspection of otherwise opaque layouts, how about things like @offsetOf?

Comptime introspection of the picked layout isn’t control over the layout, it is just reacting to whatever layout got picked.

Control would be if you could write other comptime code that chooses the layout for a type that gets declared without layout information.


Also I don’t get it, why can’t you just use @alignCast (in cases where the wider alignment works) to increase the alignment?
Or alternatively specify the alignment on the field explicitly too.
If you want a field to have that alignment, then give it that alignment?