blob: 93578d5efdd218ed7c3a59c0a8530d891f1e2e0c [file] [log] [blame]
// Copyright 2025 The Pigweed Authors
//
// Licensed 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
//
// https://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.
#![no_std]
#![cfg_attr(test, no_main)]
#![allow(dead_code)]
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::ptr::NonNull;
// Intrusive link structures are particularly tricky in Rust because mutable
// references are expected to be globally unique. Accessing the data through
// other methods is UB. There is a good writeup of Tokio's soundness challenges
// at: https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462.
//
// Here we adapt the strategy that Tokio uses:
// * The link pointer structure is marked with `PhantomPinned`. This does two
// things. First it "poisons" the containing structure so that it's
// unpinnable (immovable). Second it triggers the heuristic from
// https://github.com/rust-lang/rust/pull/82834 which will cause the compiler
// to omit emitting a `noalias` annotation on mutable references to disable
// compiler optimization that assume the mutable reference is unique.
// * `next` and `prev` themselves are accessed by `Link` through direct pointer
// math on the `LinkInner` struct. This avoids creating mutable references to
// those fields themselves which can not be annotated with `PhantomPinned`.
// `LinkInner` is `#[repr(C)]` to make the pointer math deterministic.
// `LinkInner` is declared in an `inner` module so that `next` and `prev` can
// not be accessed directly be the rest of the code.
//
// TODO: konkers - Understand if we need to annotate the alignment of LinkInner.
mod inner {
use core::{marker::PhantomPinned, mem::offset_of, ptr::NonNull};
use super::Link;
#[repr(C)]
pub struct LinkInner {
#[allow(dead_code)]
next: Option<NonNull<Link>>,
#[allow(dead_code)]
prev: Option<NonNull<Link>>,
_pin: PhantomPinned,
}
impl LinkInner {
pub const NEXT_OFFSET: usize = offset_of!(LinkInner, next);
pub const PREV_OFFSET: usize = offset_of!(LinkInner, prev);
pub const fn new() -> Self {
Self {
next: None,
prev: None,
_pin: PhantomPinned,
}
}
}
}
use inner::LinkInner;
pub struct Link {
// UnsafeCell here is used to allow the code to access the data mutably.
// Bare mutable pointer access is unsound without this.
inner: UnsafeCell<LinkInner>,
}
#[inline]
unsafe fn get_element(inner: &UnsafeCell<LinkInner>, offset: usize) -> Option<NonNull<Link>> {
let inner_ptr = inner.get() as *const Option<NonNull<Link>>;
let element_ptr = inner_ptr.byte_add(offset);
core::ptr::read(element_ptr)
}
#[inline]
unsafe fn set_element(inner: &UnsafeCell<LinkInner>, offset: usize, value: Option<NonNull<Link>>) {
let inner_ptr = inner.get() as *mut Option<NonNull<Link>>;
let element_ptr = inner_ptr.byte_add(offset);
core::ptr::write(element_ptr, value);
}
impl Link {
pub const fn new() -> Self {
Self {
inner: UnsafeCell::new(LinkInner::new()),
}
}
pub fn is_unlinked(&self) -> bool {
self.get_next().is_none() && self.get_prev().is_none()
}
pub fn is_linked(&self) -> bool {
!self.is_unlinked()
}
#[inline]
fn get_next(&self) -> Option<NonNull<Link>> {
unsafe { get_element(&self.inner, LinkInner::NEXT_OFFSET) }
}
#[inline]
fn set_next(&mut self, value: Option<NonNull<Link>>) {
unsafe { set_element(&self.inner, LinkInner::NEXT_OFFSET, value) }
}
#[inline]
fn get_prev(&self) -> Option<NonNull<Link>> {
unsafe { get_element(&self.inner, LinkInner::PREV_OFFSET) }
}
#[inline]
fn set_prev(&mut self, value: Option<NonNull<Link>>) {
unsafe { set_element(&self.inner, LinkInner::PREV_OFFSET, value) }
}
}
impl Default for Link {
fn default() -> Self {
Self::new()
}
}
// `UnsafeList` uses None to mark the beginning and end of lists as opposed to
// pointers to the base list node. This means that there are never pointers to
// `UnsafeList` and the same care is not needed to avoid mutable references as
// is taken with the `Link` structure.
pub struct UnsafeList<T, A: Adapter> {
head: Option<NonNull<Link>>,
tail: Option<NonNull<Link>>,
_phantom_type: PhantomData<T>,
_phantom_adapter: PhantomData<A>,
}
pub trait Adapter {
const LINK_OFFSET: usize;
}
impl<T, A: Adapter> UnsafeList<T, A> {
pub const fn new() -> Self {
Self {
head: None,
tail: None,
_phantom_type: PhantomData,
_phantom_adapter: PhantomData,
}
}
/// # Safety
/// It is up to the caller to ensure exclusive access to the list and its
/// members.
pub unsafe fn is_empty(&self) -> bool {
self.head.is_none()
}
unsafe fn get_link_ptr(element: &T) -> NonNull<Link> {
let element_ptr: NonNull<Link> = core::mem::transmute::<&T, NonNull<Link>>(element);
element_ptr.byte_sub(A::LINK_OFFSET)
}
unsafe fn get_element_ptr(link: NonNull<Link>) -> *const T {
link.byte_add(A::LINK_OFFSET).as_ptr() as *const T
}
unsafe fn get_element_mut(link: NonNull<Link>) -> *mut T {
link.byte_add(A::LINK_OFFSET).as_ptr() as *mut T
}
/// unchecked means we don't `assert!((*element_ptr.as_ptr()).is_unlinked());`
///
/// # Safety
/// It is up to the caller to ensure exclusive access to the list and its
/// members.
/// It is up to the caller to ensure the element is not in a list
pub unsafe fn push_front_unchecked(&mut self, element: &mut T) {
let element_ptr = Self::get_link_ptr(element);
// Link up the added element.
(*element_ptr.as_ptr()).set_next(self.head);
(*element_ptr.as_ptr()).set_prev(None);
match self.head {
// If `head` was `None`, the list is empty and `tail` should point
// to the added element.
None => self.tail = Some(element_ptr),
// If `head` is not `None`, point the previous `head` to the added
// element.
Some(head) => (*head.as_ptr()).set_prev(Some(element_ptr)),
}
// Finally point `head` to the added element.
self.head = Some(element_ptr);
}
/// unlinks element from the linked list.
///
/// # Safety
/// It is up to the caller to ensure exclusive access to the list and its
/// members.
/// It is up to the caller to ensure the element is in the list
pub unsafe fn unlink_element(&mut self, element: &T) {
let element_ptr = Self::get_link_ptr(element);
let prev = (*element_ptr.as_ptr()).get_prev();
let next = (*element_ptr.as_ptr()).get_next();
match prev {
// Element is at the head of the list
None => self.head = next,
// Element has elements before it in the list.
Some(prev_ptr) => (*prev_ptr.as_ptr()).set_next(next),
}
match next {
// Element is at the tail of the list
None => self.tail = prev,
// Element has elements after it in the list.
Some(next_ptr) => (*next_ptr.as_ptr()).set_prev(prev),
}
}
/// # Safety
/// It is up to the caller to ensure exclusive access to the list and its
/// members.
pub unsafe fn for_each<E, F: FnMut(&T) -> Result<(), E>>(
&self,
mut callback: F,
) -> Result<(), E> {
let mut cur = self.head;
loop {
let Some(cur_ptr) = cur else {
break;
};
let element = Self::get_element_ptr(cur_ptr);
callback(&*element)?;
cur = (*cur_ptr.as_ptr()).get_next();
}
Ok(())
}
/// Filter iterates over every element in the list calling `callback` on
/// each one. If `callback` returns false, the element will be removed
/// from the list without modifying the element itself. It is safe to
/// add the element to the another linked list within `callback` if it
/// returns false.
///
/// # Safety
/// It is up to the caller to ensure exclusive access to the list and its
/// members.
pub unsafe fn filter<F: FnMut(&mut T) -> bool>(&mut self, mut callback: F) {
let mut cur = self.head;
loop {
let Some(cur_ptr) = cur else {
break;
};
let element = Self::get_element_mut(cur_ptr);
// Cache the next element so that we don't rely on `element` staying
// coherent across calls to `callback`.
let next = (*cur_ptr.as_ptr()).get_next();
if !callback(&mut *element) {
self.unlink_element(&*element);
}
cur = next;
}
}
}
impl<T, A: Adapter> Default for UnsafeList<T, A> {
fn default() -> Self {
Self::new()
}
}