Skip to content

Commit

Permalink
Vendor std::cell::OnceCell
Browse files Browse the repository at this point in the history
With recent changes we required the use of std::cell::OnceCell. This
type stabilized with Rust 1.70, which we cannot yet upgrade to while
adhering to our self-imposed minimum supported Rust version policy
(which would only allow us to upgrade once 1.75 is released, but the
most recent release at this point is 1.73).
What's more, some of our consumers were already forced to patch out
constructs that their Rust version does not yet support. For OnceCell
and its pervasive use, this would easily turn into a major effort.
As such, with this change we "vendor" the OnceCell code from the
standard library. It was adjusted to remove compiler specific attributes
and unnecessary functionality but otherwise kept as-is.

Signed-off-by: Daniel Müller <[email protected]>
  • Loading branch information
d-e-s-o committed Nov 8, 2023
1 parent 721418d commit c66f67d
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/file_cache.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::cell::OnceCell;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::path::PathBuf;

use crate::insert_map::InsertMap;
use crate::once::OnceCell;
use crate::util::fstat;
use crate::ErrorExt as _;
use crate::Result;
Expand Down
7 changes: 3 additions & 4 deletions src/inspect/inspector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::cell::OnceCell;
use std::path::Path;
use std::rc::Rc;

Expand All @@ -8,7 +7,7 @@ use crate::elf::ElfBackend;
use crate::elf::ElfParser;
use crate::elf::ElfResolver;
use crate::file_cache::FileCache;
use crate::util::OnceCellExt as _;
use crate::once::OnceCell;
use crate::Result;
use crate::SymResolver;

Expand Down Expand Up @@ -84,15 +83,15 @@ impl Inspector {
let (file, cell) = self.elf_cache.entry(path)?;
let resolver = if let Some(data) = cell.get() {
if debug_info {
data.dwarf.get_or_try_init_(|| {
data.dwarf.get_or_try_init(|| {
// SANITY: We *know* a `ResolverData` object is present and
// given that we are initializing the `dwarf` part
// of it, the `elf` part *must* be present.
let parser = data.elf.get().unwrap().parser().clone();
self.elf_resolver_from_parser(path, parser, true)
})?
} else {
data.elf.get_or_try_init_(|| {
data.elf.get_or_try_init(|| {
// SANITY: We *know* a `ResolverData` object is present and
// given that we are initializing the `elf` part of
// it, the `dwarf` part *must* be present.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ mod ksym;
mod maps;
mod mmap;
pub mod normalize;
mod once;
mod resolver;
pub mod symbolize;
mod util;
Expand Down
194 changes: 194 additions & 0 deletions src/once.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
//! A copy of std::once::OnceCell.
// TODO: Remove this module once our minimum supported Rust version is greater
// 1.70 and/or `OnceCell::get_or_try_init` is stable.

use std::cell::UnsafeCell;
use std::convert::Infallible;
use std::fmt;
use std::hint::unreachable_unchecked;

/// A cell which can be written to only once.
///
/// This allows obtaining a shared `&T` reference to its inner value without copying or replacing
/// it (unlike [`Cell`]), and without runtime borrow checks (unlike [`RefCell`]). However,
/// only immutable references can be obtained unless one has a mutable reference to the cell
/// itself.
///
/// For a thread-safe version of this struct, see [`std::sync::OnceLock`].
///
/// [`RefCell`]: crate::cell::RefCell
/// [`Cell`]: crate::cell::Cell
/// [`std::sync::OnceLock`]: ../../std/sync/struct.OnceLock.html
pub struct OnceCell<T> {
// Invariant: written to at most once.
inner: UnsafeCell<Option<T>>,
}

impl<T> OnceCell<T> {
/// Creates a new empty cell.
#[inline]
#[must_use]
pub const fn new() -> OnceCell<T> {
OnceCell {
inner: UnsafeCell::new(None),
}
}

/// Gets the reference to the underlying value.
///
/// Returns `None` if the cell is empty.
#[inline]
pub fn get(&self) -> Option<&T> {
// SAFETY: Safe due to `inner`'s invariant
unsafe { &*self.inner.get() }.as_ref()
}

/// Sets the contents of the cell to `value`.
///
/// # Errors
///
/// This method returns `Ok(())` if the cell was empty and `Err(value)` if
/// it was full.
#[inline]
pub fn set(&self, value: T) -> Result<(), T> {
match self.try_insert(value) {
Ok(_) => Ok(()),
Err((_, value)) => Err(value),
}
}

/// Sets the contents of the cell to `value` if the cell was empty, then
/// returns a reference to it.
///
/// # Errors
///
/// This method returns `Ok(&value)` if the cell was empty and
/// `Err(&current_value, value)` if it was full.
#[inline]
pub fn try_insert(&self, value: T) -> Result<&T, (&T, T)> {
if let Some(old) = self.get() {
return Err((old, value));
}

// SAFETY: This is the only place where we set the slot, no races
// due to reentrancy/concurrency are possible, and we've
// checked that slot is currently `None`, so this write
// maintains the `inner`'s invariant.
let slot = unsafe { &mut *self.inner.get() };
Ok(slot.insert(value))
}

/// Gets the contents of the cell, initializing it with `f`
/// if the cell was empty.
///
/// # Panics
///
/// If `f` panics, the panic is propagated to the caller, and the cell
/// remains uninitialized.
///
/// It is an error to reentrantly initialize the cell from `f`. Doing
/// so results in a panic.
#[inline]
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
match self.get_or_try_init(|| Ok::<T, Infallible>(f())) {
Ok(val) => val,
Err(_) => unsafe { unreachable_unchecked() },
}
}

/// Gets the contents of the cell, initializing it with `f` if
/// the cell was empty. If the cell was empty and `f` failed, an
/// error is returned.
///
/// # Panics
///
/// If `f` panics, the panic is propagated to the caller, and the cell
/// remains uninitialized.
///
/// It is an error to reentrantly initialize the cell from `f`. Doing
/// so results in a panic.
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if let Some(val) = self.get() {
return Ok(val);
}
/// Avoid inlining the initialization closure into the common path that fetches
/// the already initialized value
#[cold]
fn outlined_call<F, T, E>(f: F) -> Result<T, E>
where
F: FnOnce() -> Result<T, E>,
{
f()
}
let val = outlined_call(f)?;
// Note that *some* forms of reentrant initialization might lead to
// UB (see `reentrant_init` test). I believe that just removing this
// `panic`, while keeping `try_insert` would be sound, but it seems
// better to panic, rather than to silently use an old value.
if let Ok(val) = self.try_insert(val) {
Ok(val)
} else {
panic!("reentrant init")
}
}
}

impl<T> Default for OnceCell<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}

impl<T: fmt::Debug> fmt::Debug for OnceCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_tuple("OnceCell");
match self.get() {
Some(v) => d.field(v),
None => d.field(&format_args!("<uninit>")),
};
d.finish()
}
}

impl<T: Clone> Clone for OnceCell<T> {
#[inline]
fn clone(&self) -> OnceCell<T> {
let res = OnceCell::new();
if let Some(value) = self.get() {
match res.set(value.clone()) {
Ok(()) => (),
Err(_) => unreachable!(),
}
}
res
}
}

impl<T: PartialEq> PartialEq for OnceCell<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}

impl<T: Eq> Eq for OnceCell<T> {}

impl<T> From<T> for OnceCell<T> {
/// Creates a new `OnceCell<T>` which already contains the given `value`.
#[inline]
fn from(value: T) -> Self {
OnceCell {
inner: UnsafeCell::new(Some(value)),
}
}
}

// Just like for `Cell<T>` this isn't needed, but results in nicer error messages.
//impl<T> !Sync for OnceCell<T> {}
9 changes: 4 additions & 5 deletions src/symbolize/symbolizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use crate::normalize::normalize_sorted_user_addrs_with_entries;
use crate::normalize::Handler as _;
use crate::util;
use crate::util::uname_release;
use crate::util::OnceCellExt as _;
use crate::zip;
use crate::Addr;
use crate::Error;
Expand Down Expand Up @@ -397,7 +396,7 @@ impl Symbolizer {

fn elf_resolver<'slf>(&'slf self, path: &Path) -> Result<&'slf Rc<ElfResolver>> {
let (file, cell) = self.elf_cache.entry(path)?;
let resolver = cell.get_or_try_init_(|| self.create_elf_resolver(path, file))?;
let resolver = cell.get_or_try_init(|| self.create_elf_resolver(path, file))?;
Ok(resolver)
}

Expand All @@ -408,7 +407,7 @@ impl Symbolizer {

fn gsym_resolver<'slf>(&'slf self, path: &Path) -> Result<&'slf Rc<GsymResolver<'static>>> {
let (file, cell) = self.gsym_cache.entry(path)?;
let resolver = cell.get_or_try_init_(|| self.create_gsym_resolver(path, file))?;
let resolver = cell.get_or_try_init(|| self.create_gsym_resolver(path, file))?;
Ok(resolver)
}

Expand Down Expand Up @@ -460,7 +459,7 @@ impl Symbolizer {
file_off: u64,
) -> Result<Option<(&'slf Rc<ElfResolver>, Addr)>> {
let (file, cell) = self.apk_cache.entry(path)?;
let (apk, resolvers) = cell.get_or_try_init_(|| {
let (apk, resolvers) = cell.get_or_try_init(|| {
let apk = zip::Archive::with_mmap(Mmap::builder().map(file)?)?;
let resolvers = InsertMap::new();
Result::<_, Error>::Ok((apk, resolvers))
Expand Down Expand Up @@ -572,7 +571,7 @@ impl Symbolizer {

fn ksym_resolver<'slf>(&'slf self, path: &Path) -> Result<&'slf Rc<KSymResolver>> {
let (file, cell) = self.ksym_cache.entry(path)?;
let resolver = cell.get_or_try_init_(|| self.create_ksym_resolver(path, file))?;
let resolver = cell.get_or_try_init(|| self.create_ksym_resolver(path, file))?;
Ok(resolver)
}

Expand Down
23 changes: 0 additions & 23 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::cell::OnceCell;
use std::cmp::Ordering;
use std::ffi::CStr;
use std::ffi::CString;
Expand All @@ -12,28 +11,6 @@ use std::ptr::NonNull;
use std::slice;


// TODO: Remove once `OnceCell::get_or_try_init()` is stable.
pub(crate) trait OnceCellExt<T> {
fn get_or_try_init_<F, E>(&self, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>;
}

impl<T> OnceCellExt<T> for OnceCell<T> {
fn get_or_try_init_<F, E>(&self, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if let Some(value) = self.get() {
Ok(value)
} else {
let value = f()?;
Ok(self.get_or_init(|| value))
}
}
}


/// Reorder elements of `array` based on index information in `indices`.
fn reorder<T, U>(array: &mut [T], indices: Vec<(U, usize)>) {
debug_assert_eq!(array.len(), indices.len());
Expand Down

0 comments on commit c66f67d

Please sign in to comment.