blob: 307fc30344e0e0a7775a2f42e6a072f015523dde [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::mem::ManuallyDrop;
use std::ptr::NonNull;
use std::slice;
use std::sync::atomic::{AtomicUsize, Ordering, fence};
use aligned_vec::{AVec, ConstAlign};
#[derive(Debug)]
pub struct Owned<const ALIGN: usize = 4096> {
inner: AVec<u8, ConstAlign<ALIGN>>,
}
impl<const ALIGN: usize> From<AVec<u8, ConstAlign<ALIGN>>> for Owned<ALIGN> {
fn from(vec: AVec<u8, ConstAlign<ALIGN>>) -> Self {
Self { inner: vec }
}
}
impl<const ALIGN: usize> From<Owned<ALIGN>> for AVec<u8, ConstAlign<ALIGN>> {
fn from(value: Owned<ALIGN>) -> Self {
value.inner
}
}
impl<const ALIGN: usize> Owned<ALIGN> {
pub fn as_slice(&self) -> &[u8] {
&self.inner
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.inner
}
/// Split `Owned` buffer into two halves
///
/// # Panics
/// Panics if `split_at > self.len()` or if `split_at` is not a multiple of `ALIGN` bytes.
pub fn split_at(self, split_at: usize) -> TwoHalves<ALIGN> {
assert!(split_at <= self.inner.len());
// Take ownership of the AVec's allocation. After this, we are responsible
// for deallocating via `AVec::from_raw_parts` or equivalent.
let (ptr, _, len, capacity) = self.inner.into_raw_parts();
// SAFETY: both pointers are constructed from the same `Inner` allocation, the split_at bounds are validated.
// The control block captures original `Inner` metadata to allow reconstructing the original frame for merging/dropping.
// The ptr provenance rules are maintained by the use of `NonNull` apis.
let base: NonNull<u8> = unsafe { NonNull::new_unchecked(ptr) };
let tail = unsafe { NonNull::new_unchecked(ptr.add(split_at)) };
let ctrlb = ControlBlock::new(base, len, capacity);
TwoHalves {
inner: (
Extent {
ptr: base,
len: split_at,
ctrlb,
_pad: 0,
},
Extent {
ptr: tail,
len: len - split_at,
ctrlb,
_pad: 0,
},
),
}
}
}
pub struct TwoHalves<const ALIGN: usize> {
inner: (Extent, Extent),
}
impl<const ALIGN: usize> TwoHalves<ALIGN> {
pub fn head(&self) -> &[u8] {
self.inner.0.as_slice()
}
pub fn head_mut(&mut self) -> &mut [u8] {
// SAFETY: We are accessing the head half mutably, this is the only correct operation, as the head is not shared between clones,
// instead it gets copied.
unsafe { self.inner.0.as_mut_slice() }
}
pub fn tail(&self) -> &[u8] {
self.inner.1.as_slice()
}
pub fn split_at(&self) -> usize {
self.inner.0.len
}
pub fn total_len(&self) -> usize {
self.inner.0.len + self.inner.1.len
}
pub fn is_unique(&self) -> bool {
// `inner.1` is the authoritative owner of the original frame allocation.
// SAFETY: `inner.1.ctrlb` points to a live control block while `self` is alive.
unsafe {
self.inner
.1
.ctrlb
.as_ref()
.ref_count
.load(Ordering::Acquire)
== 1
}
}
pub fn try_merge(self) -> Result<Owned<ALIGN>, Self> {
if !self.is_unique() {
return Err(self);
}
// Transfer ownership to prevent double-free.
// SAFETY: We read the inner tuple out of ManuallyDrop, which won't run TwoHalves::drop.
let this = ManuallyDrop::new(self);
let (head, tail) = unsafe { std::ptr::read(&this.inner) };
let split_at = head.len;
// SAFETY: `tail.ctrlb` is unique at this point,
// If `head.ctrlb != tail.ctrlb`, the head owns a standalone allocation
// that must be released after copying.
unsafe {
let ctrlb_eq = std::ptr::addr_eq(head.ctrlb.as_ptr(), tail.ctrlb.as_ptr());
if !ctrlb_eq {
let tail_ctrlb = tail.ctrlb.as_ref();
// We are patching up the original allocation, with the current head data, so that the resulting `Owned` has correct content.
let dst = slice::from_raw_parts_mut(tail_ctrlb.base.as_ptr(), split_at);
dst.copy_from_slice(head.as_slice());
release_control_block_w_allocation::<ALIGN>(head.ctrlb);
}
let ctrlb = reclaim_unique_control_block(tail.ctrlb);
// SAFETY: `ctrlb.base,capacity` were captured from an `AVec<u8>` allocation and
// are now exclusively owned by this path.
let inner = AVec::from_raw_parts(ctrlb.base.as_ptr(), ALIGN, ctrlb.len, ctrlb.capacity);
Ok(Owned { inner })
}
}
}
impl<const ALIGN: usize> Clone for TwoHalves<ALIGN> {
fn clone(&self) -> Self {
Self {
inner: (
Extent::copy_from_slice::<ALIGN>(self.head()),
self.inner.1.clone(),
),
}
}
}
impl<const ALIGN: usize> Drop for TwoHalves<ALIGN> {
fn drop(&mut self) {
// SAFETY: `inner.0.ctrlb` / `inner.1.ctrlb` point to live control blocks while `self` is alive.
let ctrlb_eq = std::ptr::addr_eq(self.inner.0.ctrlb.as_ptr(), self.inner.1.ctrlb.as_ptr());
unsafe {
if ctrlb_eq {
release_control_block_w_allocation::<ALIGN>(self.inner.1.ctrlb);
} else {
// Different control blocks, release both
release_control_block_w_allocation::<ALIGN>(self.inner.0.ctrlb);
release_control_block_w_allocation::<ALIGN>(self.inner.1.ctrlb);
}
}
}
}
impl<const ALIGN: usize> std::fmt::Debug for TwoHalves<ALIGN> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TwoHalves")
.field("split_at", &self.split_at())
.field("head_len", &self.inner.0.len)
.field("tail_len", &self.inner.1.len)
.field("halves_alias", &(self.inner.0.ctrlb == self.inner.1.ctrlb))
.finish()
}
}
#[derive(Clone)]
pub struct Frozen<const ALIGN: usize> {
inner: Extent,
}
impl<const ALIGN: usize> Frozen<ALIGN> {
pub fn as_slice(&self) -> &[u8] {
self.inner.as_slice()
}
}
#[repr(C, align(64))]
struct ControlBlock {
ref_count: AtomicUsize,
base: NonNull<u8>,
len: usize,
capacity: usize,
_pad: [u8; 32],
}
impl ControlBlock {
fn new(base: NonNull<u8>, len: usize, capacity: usize) -> NonNull<Self> {
let ctrl = Box::new(ControlBlock {
ref_count: AtomicUsize::new(1),
base,
len,
capacity,
_pad: [0; 32],
});
// SAFETY: Box::into_raw returns a valid pointer
unsafe { NonNull::new_unchecked(Box::into_raw(ctrl)) }
}
}
struct Extent {
ptr: NonNull<u8>,
len: usize,
ctrlb: NonNull<ControlBlock>,
_pad: usize,
}
impl Extent {
fn as_slice(&self) -> &[u8] {
// SAFETY: ptr and len describe a valid allocation
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
}
unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
// SAFETY: caller guarantees exclusive access
unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
}
fn copy_from_slice<const ALIGN: usize>(src: &[u8]) -> Self {
let mut v: AVec<u8, ConstAlign<ALIGN>> = AVec::new(ALIGN);
v.extend_from_slice(src);
let (ptr, _, len, capacity) = v.into_raw_parts();
let data = unsafe { NonNull::new_unchecked(ptr) };
let ctrlb = ControlBlock::new(data, len, capacity);
Extent {
ptr: data,
len,
ctrlb,
_pad: 0,
}
}
}
impl Clone for Extent {
fn clone(&self) -> Self {
// SAFETY: `self.ctrlb` points to a live control block while `self` is alive.
unsafe {
self.ctrlb
.as_ref()
.ref_count
.fetch_add(1, Ordering::Relaxed);
}
Self {
ptr: self.ptr,
len: self.len,
ctrlb: self.ctrlb,
_pad: 0,
}
}
}
unsafe fn release_control_block_w_allocation<const ALIGN: usize>(ctrlb: NonNull<ControlBlock>) {
// SAFETY: ctrlb is valid per function preconditions
let old = unsafe { ctrlb.as_ref() }
.ref_count
.fetch_sub(1, Ordering::Release);
debug_assert!(old > 0, "control block refcount underflow");
if old != 1 {
return;
}
// This fence is needed to prevent reordering of use of the data and
// deletion of the data. Because it is marked `Release`, the decreasing
// of the reference count synchronizes with this `Acquire` fence. This
// means that use of the data happens before decreasing the reference
// count, which happens before this fence, which happens before the
// deletion of the data.
//
// As explained in the [Boost documentation][1],
//
// > It is important to enforce any possible access to the object in one
// > thread (through an existing reference) to *happen before* deleting
// > the object in a different thread. This is achieved by a "release"
// > operation after dropping a reference (any access to the object
// > through this reference must obviously happened before), and an
// > "acquire" operation before deleting the object.
//
// [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html)
//
fence(Ordering::Acquire);
// SAFETY: refcount is zero, we have exclusive ownership
let ctrlb = unsafe { Box::from_raw(ctrlb.as_ptr()) };
// SAFETY: `ctrlb.base`, `ctrlb.len` and `ctrlb.capacity` were captured from an `AVec`
// allocation. We reconstruct the AVec and let it deallocate properly.
let _ = unsafe {
AVec::<u8, ConstAlign<ALIGN>>::from_raw_parts(
ctrlb.base.as_ptr(),
ALIGN,
ctrlb.len,
ctrlb.capacity,
)
};
}
unsafe fn reclaim_unique_control_block(ctrlb: NonNull<ControlBlock>) -> ControlBlock {
debug_assert_eq!(
// SAFETY: caller guarantees `ctrlb` points to a live control block.
unsafe { ctrlb.as_ref() }.ref_count.load(Ordering::Acquire),
1
);
// SAFETY: caller guarantees uniqueness, so ownership of the control block can be reclaimed directly.
unsafe { *Box::from_raw(ctrlb.as_ptr()) }
}
// =============================================================================
// Tests
// =============================================================================
// TODO: Better tests & miri.
#[cfg(test)]
mod tests {
use super::Owned;
use aligned_vec::AVec;
use aligned_vec::ConstAlign;
fn make_owned(data: &[u8]) -> Owned {
let mut v: AVec<u8, ConstAlign<4096>> = AVec::new(4096);
v.extend_from_slice(data);
v.into()
}
#[test]
fn split_exposes_head_and_tail() {
let owned = make_owned(&[1, 2, 3, 4, 5]);
let mut buffer = owned.split_at(2);
assert_eq!(buffer.head(), &[1, 2]);
assert_eq!(buffer.tail(), &[3, 4, 5]);
assert_eq!(buffer.split_at(), 2);
assert_eq!(buffer.total_len(), 5);
buffer.head_mut().copy_from_slice(&[9, 8]);
assert_eq!(buffer.head(), &[9, 8]);
assert_eq!(buffer.tail(), &[3, 4, 5]);
}
#[test]
fn clone_copies_head_and_shares_tail() {
let owned = make_owned(&[1, 2, 3, 4, 5]);
let mut original = owned.split_at(2);
let mut cloned = original.clone();
assert!(!original.is_unique());
assert!(!cloned.is_unique());
original.head_mut().copy_from_slice(&[9, 9]);
cloned.head_mut().copy_from_slice(&[7, 7]);
assert_eq!(original.head(), &[9, 9]);
assert_eq!(cloned.head(), &[7, 7]);
assert_eq!(original.tail(), &[3, 4, 5]);
assert_eq!(cloned.tail(), &[3, 4, 5]);
}
#[test]
fn try_merge_reuses_original_frame_when_unique() {
let owned = make_owned(&[1, 2, 3, 4, 5]);
let mut buffer = owned.split_at(2);
buffer.head_mut().copy_from_slice(&[8, 9]);
let merged: AVec<u8, ConstAlign<4096>> = buffer.try_merge().unwrap().into();
assert_eq!(merged.as_slice(), &[8, 9, 3, 4, 5]);
}
#[test]
fn try_merge_fails_while_tail_is_shared() {
let owned = make_owned(&[1, 2, 3, 4, 5]);
let buffer = owned.split_at(2);
let clone = buffer.clone();
let buffer = buffer.try_merge().unwrap_err();
assert!(!buffer.is_unique());
drop(clone);
let merged: AVec<u8, ConstAlign<4096>> = buffer.try_merge().unwrap().into();
assert_eq!(merged.as_slice(), &[1, 2, 3, 4, 5]);
}
#[test]
fn merge_after_cloned_head_mutation_writes_back_to_original_frame() {
let owned = make_owned(&[1, 2, 3, 4, 5]);
let buffer = owned.split_at(2);
let mut clone = buffer.clone();
drop(buffer);
clone.head_mut().copy_from_slice(&[4, 2]);
assert!(clone.is_unique());
let merged: AVec<u8, ConstAlign<4096>> = clone.try_merge().unwrap().into();
assert_eq!(merged.as_slice(), &[4, 2, 3, 4, 5]);
}
#[test]
fn zero_length_splits_work() {
let owned = make_owned(&[1, 2, 3]);
let left_empty = owned.split_at(0);
assert_eq!(left_empty.head(), &[]);
assert_eq!(left_empty.tail(), &[1, 2, 3]);
let owned = make_owned(&[1, 2, 3]);
let right_empty = owned.split_at(3);
assert_eq!(right_empty.head(), &[1, 2, 3]);
assert_eq!(right_empty.tail(), &[]);
}
#[test]
fn clone_of_clone_keeps_tail_sharing_semantics() {
let owned = make_owned(&[1, 2, 3, 4, 5]);
let original = owned.split_at(2);
let clone1 = original.clone();
let clone2 = clone1.clone();
assert!(!original.is_unique());
assert!(!clone1.is_unique());
assert!(!clone2.is_unique());
}
}