Live data from Hacker News

A bug story: data alignment on x86

pzemtsov.github.io

21–30 of 111 posts

Re: A bug story: data alignment on x86

#21
post #17

Earlier quoted context omitted.

Anybody knows how Rust handles this problem?

Not sure if this is what you are asking: Last time I tried alignment in Rust I worked around the lack of explicit alignment support by adding a zero length array of the correct size to the end of the struct. Not sure if alignment support from proper attributes has landed yet. [repr(C)] struct Something { pub foo: f32, pub _alignment: [EightBytes, 0] } where "EightBytes" is a data type of size 8, to align the whole st…

I doubt you can count on all items being allocated at addresses that are multiples if their size.

It's not optimal but you can always use libc::posix_memalign()

Re: A bug story: data alignment on x86

#22
Compiler is allowed to assume alignment of pointers (what are you doing is creating a pointer to a value with invalid alignment, hence undefined behaviour (just creating a pointer is undefined behaviour)). The correct solution would be to read values indirectly. For example, a function like that could be used to replace every access to "q" variable.

    static uint32_t read(const char *p, size_t index) {
      uint32_t out;
      memcpy(&out, &p[index * sizeof out], sizeof out);
      return out;
    }
A compiler can recognize this pattern, and continue to use unaligned accesses that would work.

This has a cost of unaligned accesses on non-x86 platforms (a quite big at that), but considering the original code didn't work on these at all, it's an improvement.

Re: A bug story: data alignment on x86

#23
post #17

Earlier quoted context omitted.

Anybody knows how Rust handles this problem?

Not sure if this is what you are asking: Last time I tried alignment in Rust I worked around the lack of explicit alignment support by adding a zero length array of the correct size to the end of the struct. Not sure if alignment support from proper attributes has landed yet. [repr(C)] struct Something { pub foo: f32, pub _alignment: [EightBytes, 0] } where "EightBytes" is a data type of size 8, to align the whole st…

Structs already insert padding to give them alignment:

    struct One {
      foo: u8,
    }
    
    struct Two {
      bar: u16,
    }
    
    struct Three {
      foo: u8,
      bar: u16,
    }
    
    struct Four {
      foo: u16,
      bar: u16,
    }
    
    fn main() {
        assert_eq!(1, std::mem::size_of::());
        assert_eq!(2, std::mem::size_of::());
        assert_eq!(4, std::mem::size_of::());
        assert_eq!(4, std::mem::size_of::());
    }

Re: A bug story: data alignment on x86

#24
post #8

These SSE instructions that operate only on aligned data are a pain. It's not well known that Linux/x86 stack frames must always be 16 byte aligned. GCC uses this knowledge to use the SSE aligned instructions when accessing certain fields on the stack. Unfortunately a while back the OCaml compiler generated non-aligned stack frames. Which is no problem for pure OCaml code and even saves a little bit of memory. Howeve…

  > It's not well known that Linux/x86 stack frames must always be 16 byte aligned.
Always wasn't always always; that sad story is the source of your OCaml problems, among many others. Linux on x86 originally used 4-byte alignment, and 4-byte alignment is what you see if you RTFM¹. Later, gcc decided that they were in control, and unilaterally switched to 16-byte alignment. Backwards compatibility? Screw you. Other tools? Screw you.²

¹ https://refspecs.linuxfoundation.org/

² https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38496

Re: A bug story: data alignment on x86

#25

Earlier quoted context omitted.

Not sure if this is what you are asking: Last time I tried alignment in Rust I worked around the lack of explicit alignment support by adding a zero length array of the correct size to the end of the struct. Not sure if alignment support from proper attributes has landed yet. [repr(C)] struct Something { pub foo: f32, pub _alignment: [EightBytes, 0] } where "EightBytes" is a data type of size 8, to align the whole st…

Structs already insert padding to give them alignment: struct One { foo: u8, } struct Two { bar: u16, } struct Three { foo: u8, bar: u16, } struct Four { foo: u16, bar: u16, } fn main() { assert_eq!(1, std::mem::size_of:: ()); assert_eq!(2, std::mem::size_of:: ()); assert_eq!(4, std::mem::size_of:: ()); assert_eq!(4, std::mem::size_of:: ()); }

What I needed to do was to place e.g. a N byte struct exactly on an M byte alignment (e.g. 11 byte struct on 32 byte alignment etc).

Re: A bug story: data alignment on x86

#26
post #24
post #8

These SSE instructions that operate only on aligned data are a pain. It's not well known that Linux/x86 stack frames must always be 16 byte aligned. GCC uses this knowledge to use the SSE aligned instructions when accessing certain fields on the stack. Unfortunately a while back the OCaml compiler generated non-aligned stack frames. Which is no problem for pure OCaml code and even saves a little bit of memory. Howeve…

> It's not well known that Linux/x86 stack frames must always be 16 byte aligned. Always wasn't always always; that sad story is the source of your OCaml problems, among many others. Linux on x86 originally used 4-byte alignment, and 4-byte alignment is what you see if you RTFM¹. Later, gcc decided that they were in control, and unilaterally switched to 16-byte alignment. Backwards compatibility? Screw you. Other too…

The worst part is that today 16 bytes alignment is no longer necessary as x86 can do unaligned vector load with little to no penalty while keeping the stack aligned all the time still has a cost.

Re: A bug story: data alignment on x86

#27

Earlier quoted context omitted.

Structs already insert padding to give them alignment: struct One { foo: u8, } struct Two { bar: u16, } struct Three { foo: u8, bar: u16, } struct Four { foo: u16, bar: u16, } fn main() { assert_eq!(1, std::mem::size_of:: ()); assert_eq!(2, std::mem::size_of:: ()); assert_eq!(4, std::mem::size_of:: ()); assert_eq!(4, std::mem::size_of:: ()); }

What I needed to do was to place e.g. a N byte struct exactly on an M byte alignment (e.g. 11 byte struct on 32 byte alignment etc).

Ah! Yeah, that's different, and as far as I know your way is the current right way to do it, but I'm not an expert on the subject.

Re: A bug story: data alignment on x86

#28

Compiler is allowed to assume alignment of pointers (what are you doing is creating a pointer to a value with invalid alignment, hence undefined behaviour (just creating a pointer is undefined behaviour)). The correct solution would be to read values indirectly. For example, a function like that could be used to replace every access to "q" variable. static uint32_t read(const char *p, size_t index) { uint32_t out; me…

The article's final solution (sum3) is exactly this. It does a memcpy into a temporary.

Re: A bug story: data alignment on x86

#30
post #8

These SSE instructions that operate only on aligned data are a pain. It's not well known that Linux/x86 stack frames must always be 16 byte aligned. GCC uses this knowledge to use the SSE aligned instructions when accessing certain fields on the stack. Unfortunately a while back the OCaml compiler generated non-aligned stack frames. Which is no problem for pure OCaml code and even saves a little bit of memory. Howeve…

I had the same problem with my jit, which also generated stack frames not aligned to 16-byte. My test program crashed on an SSE instruction in the Rust standard library (I dont' recall if this bug only occured in release mode, may have been already compiled code). I was pretty proud when I fixed this. Although I have to admit that after finding out that the accessed address was actually valid, I was already supposing that alignment was a problem. Fixing it was then straightforward since it was my own toy compiler.
Post reply on HN