#How to use pointer alignment?

1 messages · Page 1 of 1 (latest)

hybrid iron
#

I'm trying to understand how pointer alignment works with a simple example that will demonstrate me what it does:

pub fn main() !void {
    var buf1: [10]u8 = undefined;
    assert(@sizeOf(@TypeOf(buf1)) == 10);
    @memset(&buf1, '\x30'); // fill up with '0'

    // here I'm trying to create a "sparsed" array, namely
    // the one with 1 byte padding after every u8 element.
    var buf2: [10]align(2) u8 = undefined; 
    // I know it doesn't work because align keywords applies only
    // to pointers. So, what should I do? Should I restructure the data itself?
    // [10]struct{elm: u8, pad: u8} ?
    @memcpy(&buf2, &buf1); // fill up a sparsed one
    std.log.debug("{s}", .{@as([20]u8, buf2)}); // see what's inside, including padding
}

The question in the comments.

#

How to use pointer alignment?

#

I think I'm particularly interested in alignment in the context of using @memcpy as documentation says that dest and source operands may have any alignment.

main atlas
#

You could maybe do something like

const Foo = extern struct {
  bar: align (2) u8,
}

But you still wouldnt be able to memcpy it directly

hybrid iron
# main atlas You could maybe do something like ``` const Foo = extern struct { bar: align (...

You can't put alignment on a non pointer type. So, in the reference to your example, you could not.

Alignment just determines what the pointer has to be a multiple of
It doesnt really control padding outside of a struct

I didn't think alignment controls the padding "outside of a struct" (not even sure what it means) but I thought that it controls how we read every next chunk of data inside the struct/array.

But how does memcpy not respect alignment if alignment applies to every type, and in order to read, say, an n-item of an array we need to know its alignment?

main atlas
#

its not just for pointers, but all it does is control their address

hybrid iron
#

Alignment just determines what the pointer has to be a multiple of
That's the thing. I personally think of it as how the pointer can be incremented by (maybe that's the wrong way to think of it). But I was hoping that if I can change the "increment" size, I can change how we read the original (raw) data. However, as soon as I reach type matching stuff I just can't get it work.

hybrid iron
main atlas
#

memcpy ignore the alignment so it wont do change how it loads anything based on alignment

main atlas
hybrid iron
#

could you please explain what does it do? Will it lead (implicitly) to the padding after the bar in the size of one byte?

#

For example, here I would expect buf[9] would go out of array boundaries because the 10th element of the 2-bytes aligned array should result in 18th address, reading which, should lead me to some kind of out of bounds panic :) but it doesn't. So, I certainly don't understand what alignment is and how it behaves.

pub fn main() !void {
    var buf1: [10]u8 = undefined;
    @memset(&buf1, '\x30'); // '0'

    const buf2: []align(2) u8 = @alignCast(&buf1);
    std.log.debug("{d}", .{buf2[9]});
}
main atlas
main atlas
hybrid iron
#

maybe it is illigal but it compiles and works

main atlas
#

itll work if you keep getting lucky

#

either way, alignCast wont change the data
Its just an assertion

#

also that align(2) is on the slice itself, not each individual u8

#

I believe

hybrid iron
# main atlas I believe

hm...indeed maybe it is an alignment for the fat pointer itself rather than on children elements...🧐

viral notch
#

const slice: []align(2) u8 means @intFromPtr(slice.ptr) & 1 == 0 and const slice: []u8 align(2) means @intFromPtr(&slice) & 1 == 0

hybrid iron
#

you mean that if the alignment was 4, then @intFromPtr(&slice) & 0b11 == 0?

viral notch
#

correct

#

put another way, for const slice: []align(2) u8, the type of &slice[0] is *align(2) u8 but the type of &slice[1] is *u8

hybrid iron
viral notch
#

also, to fix your @alignCast code, you just need var buf1: [10]u8 align(2) = undefined; instead of a cast

hybrid iron
#

I am trying to understand how align affects the size of that array.. Does it increase it?

viral notch
#

the alignment of the element type affects the size of the element type which affects the size of the array

#

the alignment of the array does not affect the size of the array, you just waste up to align-1 bytes of padding for the entire array (0-1 bytes in this case)

hybrid iron
# viral notch https://godbolt.org/z/rfa87n9a5

Now I'm confused even more than I was before :). Also,

const buf1: [10]u8 align(2) = undefined;
@compileLog(@sizeOf(@TypeOf(buf1)));

gives the same 10 bytes instead of me-expected 20.

viral notch
#

yes, like I said the size of the array is not affected

hybrid iron
#

what does it affect then?

viral notch
#

what you are expecting is what you would get if you did const buf1: [10]extern struct { byte: u8 align(2) } = undefined;

#

again like I said, the alignment of the element type is what affects the array size

hybrid iron
viral notch
#

no, that's the alignment of the entire array

#

so the entire 10 bytes are moved in memory until the address of the first one is a multiple of 2

#

the element type is still u8 and @alignOf(u8) is still 1

hybrid iron
#

hm...let me think

viral notch
#

when you do var name: Type align(a) you are saying that the type of &name should be *align(a) Type, you are not affecting the type of name in any way

hybrid iron
#

isn't there any way to specify an alignment of an array's element types?

viral notch
#

no

hybrid iron
#

any whys?

viral notch
#

@sizeOf([n]T) == n * @sizeOf(T)

#

because there's no way of modelling that other than changing the array element type

#

"setting the alignment" of something doesn't change its size

#

you need to use a different type with a different alignment that has a different size

hybrid iron
#

other than changing the array element type
that was I'm trying to do, like arr: [10] u8 align(2) was intended to be arr: [10] <- aggr type (u8 align (2)) <- elm type

viral notch
#

u8 align(2) is not a type

hybrid iron
#

what is then?
Docs says Each type has an alignment, if so where should I put that to change it?

viral notch
#

you can't change the alignment of any type

#

you must construct a type that intrinsically has your desired alignment

hybrid iron
#

not sure what it means. probably you mean that basic types have intrinsic (predefined) alignment that you simply can't change. And in order to change it, you have to construct a new higher-level type that wraps the basic one, specifying a new alignment (eg. in a struct field). right?

#

because this indeed works:

const buf1: [10]struct { a: u8 align(2) } = undefined;
@compileLog(@sizeOf(@TypeOf(buf1))); // 20
viral notch
#

when you specify a field alignment you aren't specifying the struct alignment, it just happens that the alignment of a struct is always the maximum alignment of its fields

hybrid iron
#

I din't say I specify the struct alignment, I said a field

viral notch
#

predefined -> target defined

hybrid iron
#

it just happens that the alignment of a struct is always the maximum alignment of its fields
I remember that. Maybe it was you who said it to me back in time :)

viral notch
#

the alignment of a type is non-enforcing, as evidenced by the fact that *align(1) u16 exists

#

it simply provides a default, as in *T is equivalent to *align(@alignOf(T)) T

hybrid iron
#

how the alignment of 1 byte is possible for two bytes data (u16)?

viral notch
#

the address of the first byte could be a multiple of 2, or not

#

you are removing a restriction of *u16 which enforces that it does start on an even address

hybrid iron
#

yeah, I think I slowly come to understanding that alignment is about the address at which the data is stored and not about the data (size) itself

#

still coming back to the memcpy thing, I'm still not sure what it meant when someone said ~"memcpy will ignore alignment anyway"

#

Jodi said, sorry

main atlas
viral notch
#

not true in zig

main atlas
#

well actually with optimizations it might benefit if the alignment is the same, but its not required

viral notch
#

or C, but I'm not sure that behavior of clang is spec compliant

hybrid iron
#

i can't think how could it be true if in order to read or write data we need to access their addresses correctly, which implies the aweareness of alignemnt

viral notch
#

align(1) is an explicit lack of awareness

hybrid iron
#

because when you say memcpy ignores the alignemnt, it feels like whenever memcpy gets the address in src, it just reads all bytes raw, ignoring how the data is actually aligned in it

main atlas
viral notch
#

it could very well be aligned at runtime to 4096 bytes, align(1) is just admitting that you don't know

hybrid iron
#

from the asm perspective it makes sense but I'm not sure how it aligns with the memcpy

#

could someone provide an example where the "ignorance" of alignment while memcpy becomes obvious?

#

*please

viral notch
#

I haven't made any claims about alignment not mattering to memcpy, so I don't see why I would need to back that up, in fact I would argue that it matters a great deal

#

maybe the point is that memcpy is still possible without knowing alignment, mostly because alignment can be easily determined at runtime by just checking the bits

hybrid iron
#

because alignment can be easily determined at runtime by just checking the bits
o_O?

main atlas
#

it depends on how its implemented, you can optimize it if you know alignment but its not required and effectively it will have the same result if you removed alignment information from it’s inputs (if I understand correctly)
if it was just a

for (src, dst) |*s, *d| d.* = s.*;

then src and dst would need to have the same alignment
but if it was
for (asBytes(src), asBytes(dst)) |*s, *d| d.* = s.*;
the alignment wouldnt matter

#

but ofc the first is faster since it’s less instructions

(obviously there are more optimizations you can do if you know alignment too, im just not smart enough to come up with anything)

main atlas
viral notch
#

I mean it's a very different operation and result, memcpy is explicitly defined to not make guarantees about how it works

main atlas
#

I mean surely its still just copying the bytes over 1:1 no matter what, right?

#

just maybe not linearly or one byte at a time

hybrid iron
#

but ofc the first is faster since it’s less instructions
I think the second should be faster.. however, if the data is some kind vectors that would be treated with SIMD, maybe the first will be faster. Idk

main atlas
#

why would the second be faster? its categorically more iterations

hybrid iron
#

you're right but byte-wise access feels more "raw" i guess... that's why i automatically assumed it.

main atlas
#

fair enough just remember that on the cpu, generally speaking, the less instructions the better, even if one instruction is simpler
at least for the “basic” instructions like mov, add, sub, etc
doesnt necessarily work when you get more complicated instructions that take many cycles or instructions that repeat themselves multiple times but yeah

viral notch
main atlas
#

oh I see

hybrid iron
#

@viral notch thank you for this tricky example. makes the brain hot.
could you elaborate on this a bit?

alignment can be easily determined at runtime by just checking the bits

googling said it is possible through the analysis of an address (say, if it is multiple by n-something). still, having the address at 0xff00 doesn't mean the data in it is aligned by 64 bytes, right?

flat canopy
#

Every 16th number is also divisible by 4, in other words.

#

... and by 2, and by 1.

#

And by 8 of course

hybrid iron
#

thank you but I'm not sure what it explains with regards to detecting alignment in runtime...

my understanding from the above is that if we have a aligned as align(1) and b as align(4), then a is "more-aligned" because there its addresses has fewer multiples of 2.