kaist-cp / smr-benchmark

SMR Benchmark: A Microbenchmark Suite for Concurrent Safe Memory Reclamation Schemes
http://cp.kaist.ac.kr/gc
MIT License
36 stars 5 forks source link

Fix: Add `#[repr(C)]` to ensure the memory alignment #46

Closed powergee closed 1 year ago

powergee commented 1 year ago

요약: 최신 Rust nightly(2023-04-30 이상)에서는 컴파일러가 struct의 필드 순서를 적극적으로 바꾸는 것으로 보이는데, 이 때문에 List와 SkipList의 traverse 코드가 undefined behavior에 빠집니다. 문제가 되는 SkipList와 List의 Node#[repr(C)]를 적용했습니다.

우리의 List 실험 코드에서는 traverse를 하기 전에 루트 노드를 가리키는 &AtomicPtr&Node 타입으로 형변환해 Cursor를 생성하는 Hack을 자주 이용했습니다. 지금까지는 이렇게 형변환한 뒤 Node::next 멤버에 접근하는 것이 안전했는데, Node의 첫 번째 멤버가 next이므로 alignment 상 &AtomicPtr를 직접 사용하는 것과 형변환한 뒤 next를 사용하는 것이 사실상 같았기 때문입니다.

그러나 04-30 이후 nightly에서는 컴파일러가 구조체의 변수 순서를 적극적으로 바꿉니다. 예를 들어, 아래 코드는 stable channel과 nightly-2023-04-29 이하에서는 모두 동작하지만, 04-30과 05-01에서만 실패합니다.

use std::sync::atomic::{AtomicPtr, Ordering};

// Let's imagine a `Node` type for a linked list.
struct Node {
    // Note that `next` pointer is the first
    // member in the memory alignment of `Node`.
    next: AtomicPtr<Node>,
    _key: i32,
    _value: String,
}

fn main() {
    // A root node with an arbitrary `next` pointer.
    let root = Node {
        next: AtomicPtr::new(123 as *mut _),
        _key: 123,
        _value: "123".to_string(),
    };

    // Cast a reference to the `next` member
    // into a pointer to `Node`.
    let link = &root.next;
    let node = link as *const _ as *mut Node;

    // An address of `next` member should be same with
    // an address of the entire node.
    assert!(link as *const _ as usize == &root as *const _ as usize);

    // Accessing `next` member from `node` pointer must be safe,
    // because the offset of `Node::next` in the memory alignment is 0.
    assert!(unsafe { &*node }
        .next
        .compare_exchange(
            123 as *mut _,
            456 as *mut _,
            Ordering::SeqCst,
            Ordering::SeqCst
        )
        .is_ok());
}