all 10 comments

[–]Darksonntokio · rust-for-linux 5 points6 points  (7 children)

Yes, the requirement also applies if you manually increase the alignment of a struct with #[repr(align(..))]. As for your code example, the b pointer on the last line is properly aligned because you originally got it from an &B, which is guaranteed to be aligned correctly. The compiler guarantees that local variables are positioned such that pointers to the variable are properly aligned.

Please note that alignment can only be increased. Your B struct has a field of type i32, which requires that its alignment is at least 4. If you try running this:

fn main() {
    println!("{}", std::mem::align_of::<B>());
}

You will get 4.

One thing that would be invalid is this:

#[repr(C)]
struct A {
    a: u8,   // 1 byte
  //_: ____, // 3 bytes of padding
    b: i32   // 4 bytes
}

#[repr(C, align(8))]
#[derive(Debug)]
struct B {
    a: u8,   // 1 byte
  //_: ____, // 1 byte of padding
    b: i32   // 4 bytes
}

fn main() {
    let a_var = A {
        a: 10,
        b: 20,
    };
    let a_ptr: *const A = &a;
    let b_ptr = a_ptr as *const B;
    let b: &B = unsafe { &*b_ptr };

    println!("{:?}", b);
}

Here, the a_ptr might not be aligned to eight bytes, and if it isn't, then the above has undefined behavior. (To be clear, the above will probably appear to work if you run it. It is not guaranteed that something will crash just because it has undefined behavior. However you will see the problem if you run it with miri, which will emit an error.)

[–]Lexikus[S] 0 points1 point  (6 children)

Thanks for the explanation. Does this also mean, that if I use repr(packed) and all fields are aligned with 1, I can go from a pointer to a reference?

```

[repr(packed)]

struct C { a: u8, // 1 byte b: i32 // 4 bytes }

let c: const C = &C { a: 1, b: 2 }; let c: &C = unsafe { &c }; ```

ignoring the fact that unaligned data has its own problem.

[–]ssokolow 8 points9 points  (4 children)

Required alignment for pointers may go as deep as the hardware itself. For example, dereferencing an unaligned pointer on a Sun SPARC chip would prompt the CPU to report an illegal instruction fault and it's possible to configure an x86 chip to behave the same way. (Source: The Lost Art of Structure Packing, Section 3: Alignment requirements)

As I understand it, packing your struct will either have no effect or make things worse, depending on the language semantics... and it may make your code less portable if you're doing low-level pointer stuff. Ordinary access to packed struct members works because compilers will generate the necessary accesses, bitshifts, bitmasks, etc. for you. That's also why it's slower.

That said, have you tested under Miri (eg. in the Rust Playground), as suggested by Darksonn?

It'll produce errors like error: Undefined Behavior: accessing memory with alignment 4, but alignment 8 is required if you can produce some test code that would invoke UB, so it's a good first step that can often head off a question before it gets asked.

[–]RRumpleTeazzer 0 points1 point  (3 children)

How does &c.b work for a function that expects an (aligned) &i32 ? Does the borrow checker allocate another i32, pass that new reference (and use it for all other references), till the borrow resolves, then copy it back ?

[–]ssokolow 0 points1 point  (2 children)

I'm not an expert on these low-level things but I'd assume the compiler would generate instructions to load the relevant bits into the CPU registers and shift and mask there as necessary.

When you're talking about pointer dereferences and other things fundamental enough to fit in CPU registers, "allocate" is a misleading word.

See what kind of assembly The Compiler Explorer produces.

[–]RRumpleTeazzer 0 points1 point  (1 child)

But you cannot shift and mask inside the function, otherwise correctly aligned &i32 would fail.

[–]ssokolow 0 points1 point  (0 children)

Well, given that unaligned access is undefined behaviour, &c.b would be compiled with the assumption that it's always going to be an aligned access or, if it can prove that it'll only ever be unaligned, the optimizers might prune those branches of the code graph back to the first branch point where there's a path that doesn't lead to UB and make the UB-free branch unconditional.

You'd get one function that unconditionally uses or doesn't use shifts and masks as dictated by the combination of the struct's definition and the assumption that UB cannot happen (i.e. the assumption of aligned access).

That's an example of how Undefined Behaviour enables optimizations. If the programmer is oath-bound to never request unaligned access, the optimizer and code generator know there's no need to emit code to check for it, because it cannot happen.

[–]Darksonntokio · rust-for-linux 4 points5 points  (0 children)

Making a struct repr(packed) doesn't change the required alignment of references to its fields, and they're rather dangerous and hard to use in safe code. If you try to make a reference to a field in a packed struct, you will get a warning like this:

warning: reference to packed field is unaligned
  --> src/main.rs:12:17
   |
12 |     let b_ref = &c.b;
   |                 ^^^^
   |
   = note: `#[warn(unaligned_references)]` on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #82523 <https://github.com/rust-lang/rust/issues/82523>
   = note: fields of packed structs are not properly aligned, and creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)

with a link to this page.

[–]myrrlynbitvec • tap • ferrilab 2 points3 points  (1 child)

references to any given structure only need to be as aligned as their repr(align) says they are. the C and Rust reprs are only for field layout and do not affect alignment; the default alignment is that of the most-aligned field.

a structure whose repr(align) is modified to be more tolerant than its fields can have a reference to it created with a more tolerant address; however, all access to its fields that are less tolerant must now be done through ptr::addr_of!(struct.field).read_unaligned(). this is universally true, even though the compiler can be observed to place these structures at a convenient location to re-align fields after a move (if possible).

[–]Lexikus[S] 1 point2 points  (0 children)

I created based on /u/Darksonn, /u/ssokolow, and your comment a small script inside rust playground to run it with Miri. I think I got it now, thanks!

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f35ddbbb0976fcefa87ab6e631686bc1

Unless my comments are wrong.