How can I atomic read and write an unaligned pointer?

In x86 architecture, cpp and go can do this, but rust has its own unaligned check, how can I escape the check with some trick? Appreciate any advice.

Can you post some example c++ code that you are trying to emulate?

You don't. Circumventing checks with "tricks" means Undefined Behavior. Just use read_unaligned.

Although x86 allows instructions such as mov to be unaligned, such accesses can tear if the memory is accessed concurrently. Individual atomic loads and stores are guaranteed to have no tearing, so such an atomic operation does not exist on x86.

If you are okay with tearing, perhaps you are looking for an atomic memcpy? There's a crate called atomic-memcpy that provides atomic operations that can tear. It requires the pointer to be aligned, but you can use the type [MaybeUninit<u8>; size_of::<YourType>] which has alignment one and transmute it to YourType afterwards.

6 Likes

While x86 does support unaligned atomics, it's a good idea to avoid depending on them as they are extremely slow and not portable (most architectures don't provide unaligned atomics). If you can avoid them (and you almost certainly can), please do.

Anyway, if you really want to do it, you can use std::arch::asm for this. Make sure your instructions are LOCK prefixed (or are implicitly locked, like XCHG) - standard x86 guarantees do not apply for unaligned accesses if they are not lock prefixed. Additionally, obviously it's not portable as it requires the code to be compiled for x86. For instance, reading and writing an unaligned atomic could look like this.

use std::arch::asm;

unsafe fn atomic_read_unaligned(ptr: *const u32) -> u32 {
    let value;
    asm!(
        "lock xadd [{}], {:e}",
        in(reg) ptr,
        inlateout(reg) 0 => value,
        options(nostack),
    );
    value
}

unsafe fn atomic_write_unaligned(ptr: *mut u32, value: u32) {
    asm!(
        "xchg [{}], {:e}",
        in(reg) ptr,
        inlateout(reg) value => _,
        options(preserves_flags, nostack),
    );
}
4 Likes

c++ code like this(forgive me for my poor c++ knowledge):

#include <iostream>

using namespace std;

int main()
{
  uint8_t data[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  uint8_t *p;
  p = data;
  cout << "pointer: " << (void *)p << endl;
  uint8_t* unaligned_ptr = p + 2;
  cout << "unaligned_pointer: " << (void *)unaligned_ptr << endl;
  std::atomic<int64_t>* head_ = reinterpret_cast<std::atomic<int64_t>*>(unaligned_ptr);
  head_->store(65536);
  cout << "head: " << head_->load() << endl;
  for (; p < data + 10; ++p) {
      cout << static_cast<unsigned int>(*p) << " ";
    }
}

playground

This C++ code has multiple instances of undefined behavior; the most obvious one is the violation of srict aliasing rules (ie., the reinterpret_cast).

7 Likes

C++ has a common problem with C: the syntax of the language encourages people to violate the rules of the language. Unlike Rust, the compiler doesn't error out when this happens. The syntax makes it trivial to violate alignment rules. The rules say it's undefined behavior. This can cause miscompiles even on Intel.

1 Like

If you run this code with UBSan it reports undefined behaviour even on the creation of an unaligned std::atomic<int64_t>* Compiler Explorer

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.