Panic: incorrect alignment when casting slice to struct

    fn nodeAt(self: ChunkRing, index: Index) *Node {
        // buffer is of type []u8
        // node is of type struct {
        //     prev: Index,
        //     next: Index,
        //     size: u32
        // }
        return @ptrCast(@alignCast(&self.buffer[index.toInt().?])); // this line panics
    }

How can I fix this?

Index.toInt().? * @sizeOf(Node), maybe? Hard to say with what you’ve shown, but it’s pretty easy to under-align packing structs into a byte buffer, and that’s what the bug is. You could return *align(1) Node but that will come with its own hassle.

In my current implementation every single byte can be the start of a Node. A node is always followed by any number of reserved bytes, their count being saved in the size field.

I think I still am not really understanding alignment :confused: Is it a property of memory itself and not the slice? Would it be better to make the byte buffer be alignOf(Node) and just loose like 3 bytes maximum per Node-Associated Memory?

I am sort of confused about the incorrect alignment error. Of course it is incorrect, that’s why I am trying to cast it.

Edit: Wait isnt it a thing that alignment can only be decreased? Still I am confused about what alignment is fundamentally a property of

A struct has a ‘natural alignment’. In your case the u32 guarantees that this alignment will be at least four bytes, I don’t know what size Index is.

The natural alignment is always a power of two, and it’s how the compiler expects data to be laid out in memory. For an alignment of 4, the address is always divisible by four, and so on, and the compiler, left to its own devices, will always lay out the structure according to that alignment.

The type of a pointer to T has the natural alignment of T, unless otherwise specified with the align attribute.

So “every single byte can be the start of a Node” is not compatible with using *Node. However, it is compatible with using *align(1) Node, that’s a pointer to a Node, but with the specification that any pointer (divisible by one, trivially) can point to such a node.

Oddly-aligned pointers are a bit annoying, but if that’s how you need things to be, you can make it work. Modern chips allow it, and the performance penalty is little to none.

2 Likes

alignment is about divisibility of memory addresses yes. it is useful for compilers to be able to assume natural alignment, since they might need to emit different instructions for underaligned loads/stores.

So I guess I am also forced to make the user give an aligned buffer (or reslice the buffer to start at an aligned address but that would be weird)?

Btw thank you so much, I kind of already knew all of this but this hugely helped me put things together.

I think I was thinking too much with “all information is encoded in types” and need to accept that alignment is even more fundamental than that.

1 Like

Your options are: align the Nodes naturally, in which case you can use *Node, or pack them as you have been doing, in which case you have to use *align(1) Node, because (let’s say Index is also 32 bit) *Node is an *align(4) Node.

I would be inclined to align them naturally, because of the aforementioned annoyance. But it isn’t all that bad, either is a justifiable choice, but you’ll need to pick one.

1 Like

Yea I’ll go with aligning things naturally. The entire datastructure is basically a queue with dynamically sized nodes stored in a buffer. The plan is to try and make a little tcp-over-udp thing and this is where I want to store the packets. I think like 3 bytes per packet of lost data is utterly justifiable lol

How does C do this btw? I had to write an allocator with nearly the same strat for uni but the compiler never complained about me just casting nodes that start literally anywhere and just gave me plain node pointers.

1 Like

C doesn’t have pointee alignment as part of its type system, let alone verifying alignment when casting.

While C does have alignment for types it gave little control over it for a long time, you’d have to rely on compiler specific extensions. Only recently has it given some control into the standard, but I don’t think it’s nearly as much control as zig gives you (don’t quote me on that).

2 Likes

@alignCast never changes the actual alignment of the pointer, it just allows you to tell the compiler to change the alignment of the type and if that pointer has the wrong alignment, then the compiler will complain to you (in safe modes), because you broke your promise to the compiler that it is fine to change it.

Using @alignCast to try to change alignment to an invalid alignment is a programming error and the @alignCast itself works like an (safe-mode) assertion, not a conversion.

If you want to change the alignment (in cases where that pointer wasn’t compatible with your target alignment to begin with) then you need to move the data in memory to an address that is aligned before trying to cast it. (Or use non-naturally-aligned data like explained by @mnemnion)

6 Likes

Okay that pretty much aligns (haha) with my freshly earned knowledge, I appreciate the clarification!

1 Like