Initial intrusive list implementation
Change-Id: I08974c2fd6eb8a2bfe5221251faa3820f38361b6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/maize/+/258633
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Reviewed-by: Travis Geiselbrecht <travisg@google.com>
Commit-Queue: Erik Gilling <konkers@google.com>
diff --git a/lib/list/BUILD.bazel b/lib/list/BUILD.bazel
new file mode 100644
index 0000000..4748110
--- /dev/null
+++ b/lib/list/BUILD.bazel
@@ -0,0 +1,32 @@
+# 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.
+
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+package(default_visibility = ["//visibility:public"])
+
+rust_library(
+ name = "list",
+ srcs = ["list.rs"],
+)
+
+rust_test(
+ name = "list_test",
+ crate = ":list",
+ use_libtest_harness = False,
+ deps = [
+ "//lib/unittest",
+ "//target:linker_script",
+ ],
+)
diff --git a/lib/list/list.rs b/lib/list/list.rs
new file mode 100644
index 0000000..9e7be6f
--- /dev/null
+++ b/lib/list/list.rs
@@ -0,0 +1,557 @@
+// 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.
+ ///
+ /// Precondition: element is in the link list.
+ 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()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use core::mem::offset_of;
+
+ use super::*;
+ use unittest::test;
+
+ struct TestMember {
+ value: u32,
+ link: Link,
+ }
+
+ struct TestAdapter {}
+ impl Adapter for TestAdapter {
+ const LINK_OFFSET: usize = offset_of!(TestMember, link);
+ }
+
+ unsafe fn validate_list(
+ list: &UnsafeList<TestMember, TestAdapter>,
+ expected_values: &[u32],
+ ) -> unittest::Result<()> {
+ let mut index = 0;
+ list.for_each(|element| {
+ unittest::assert_eq!(element.value, expected_values[index]);
+ index += 1;
+ Ok(())
+ })?;
+
+ unittest::assert_eq!(index, expected_values.len());
+ Ok(())
+ }
+
+ #[test]
+ fn new_link_is_not_linked() -> unittest::Result<()> {
+ let link = Link::new();
+ unittest::assert_false!(link.is_linked());
+ unittest::assert_true!(link.is_unlinked());
+ Ok(())
+ }
+
+ #[test]
+ fn new_list_is_empty() -> unittest::Result<()> {
+ let list = UnsafeList::<TestMember, TestAdapter>::new();
+ unittest::assert_true!(unsafe { list.is_empty() });
+ Ok(())
+ }
+
+ #[test]
+ fn push_front_adds_in_correct_order() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unittest::assert_false!(unsafe { list.is_empty() });
+
+ unsafe { validate_list(&list, &[1, 2]) }
+ }
+
+ #[test]
+ fn unlink_removes_head_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.unlink_element(&element1) };
+
+ unsafe { validate_list(&list, &[2, 3]) }
+ }
+
+ #[test]
+ fn unlink_removes_tail_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.unlink_element(&element3) };
+
+ unsafe { validate_list(&list, &[1, 2]) }
+ }
+
+ #[test]
+ fn unlink_removes_middle_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.unlink_element(&element2) };
+
+ unsafe { validate_list(&list, &[1, 3]) }
+ }
+
+ #[test]
+ fn filter_removes_nothing_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.filter(|_| true) };
+
+ unsafe { validate_list(&list, &[1, 2, 3]) }
+ }
+
+ #[test]
+ fn filter_removes_everything_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.filter(|_| false) };
+
+ unsafe { validate_list(&list, &[]) }
+ }
+
+ #[test]
+ fn filter_removes_head_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.filter(|element| element.value != 1) };
+
+ unsafe { validate_list(&list, &[2, 3]) }
+ }
+
+ #[test]
+ fn filter_removes_middle_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.filter(|element| element.value != 2) };
+
+ unsafe { validate_list(&list, &[1, 3]) }
+ }
+
+ #[test]
+ fn filter_removes_tail_correctly() -> unittest::Result<()> {
+ let mut element1 = TestMember {
+ value: 1,
+ link: Link::new(),
+ };
+ let mut element2 = TestMember {
+ value: 2,
+ link: Link::new(),
+ };
+ let mut element3 = TestMember {
+ value: 3,
+ link: Link::new(),
+ };
+
+ let mut list = UnsafeList::<TestMember, TestAdapter>::new();
+ unsafe { list.push_front_unchecked(&mut element3) };
+ unsafe { list.push_front_unchecked(&mut element2) };
+ unsafe { list.push_front_unchecked(&mut element1) };
+
+ unsafe { list.filter(|element| element.value != 3) };
+
+ unsafe { validate_list(&list, &[1, 2]) }
+ }
+}