use crate::runtime::threads::{
LocalThreadAccessError, LocalThreadState, SharedThreadInfo, ShortThreadId,
};
use arbitrary_int::prelude::*;
use cfg_if::cfg_if;
use core::ffi::c_void;
use core::fmt::{Debug, Formatter};
use core::marker::PhantomPinned;
use core::ptr::NonNull;
use core::sync::atomic::AtomicU32;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
mod threads;
pub(crate) trait UnwindPolicy {
const MAY_UNWIND: bool;
fn maybe_abort_unwind<R>(func: impl FnOnce() -> R) -> R;
}
pub(crate) struct MayPanic;
impl UnwindPolicy for MayPanic {
const MAY_UNWIND: bool = cfg!(panic = "unwind");
#[inline(always)]
fn maybe_abort_unwind<R>(func: impl FnOnce() -> R) -> R {
func()
}
}
pub(crate) struct NeverPanic;
impl UnwindPolicy for NeverPanic {
const MAY_UNWIND: bool = false;
#[inline(always)]
fn maybe_abort_unwind<R>(func: impl FnOnce() -> R) -> R {
nounwind::abort_unwind(func)
}
}
#[derive(Debug, Clone, thiserror::Error, Eq, PartialEq)]
#[doc(hidden)]
pub enum BiasedCountError {
#[error("Wrong thread cannot access biased count")]
WrongThread,
#[error("Reference is no longer biased")]
NotBiased,
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("Imprecise reference count due to biased thread (lower bound is {lower_bound})")]
pub struct ImpreciseRefCountError {
pub(crate) lower_bound: usize,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ZeroReferenceCountError;
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct StrongDecrementResult {
pub should_drop: bool,
}
#[repr(C)]
pub struct RawBrcHeader {
shared_word: AtomicUsize,
biased_word: AtomicU32,
marker: PhantomPinned,
}
impl RawBrcHeader {
pub const NEEDS_DROP: bool = false;
#[inline]
pub unsafe fn init() -> Self {
let this_id = match LocalThreadState::existing_short_id() {
Ok(short_id) => Some(short_id),
Err(LocalThreadAccessError::Dead | LocalThreadAccessError::IdOverflow(_)) => {
None
}
Err(LocalThreadAccessError::Uninitialized) => {
LocalThreadState::init_tid()
}
};
match this_id {
None => RawBrcHeader {
shared_word: AtomicUsize::new(
SharedWord {
shared_count: SharedCount::new(1),
merged: true,
queued: false,
}
.to_raw(),
),
biased_word: AtomicU32::new(BiasedWord::UNOWNED.to_raw()),
marker: PhantomPinned,
},
Some(this_id) => RawBrcHeader {
biased_word: AtomicU32::new(
BiasedWord {
biased_count: BiasedCount::new(1),
owner_id: Some(this_id),
}
.to_raw(),
),
shared_word: AtomicUsize::new(
SharedWord {
queued: false,
merged: false,
shared_count: SharedCount::new(0),
}
.to_raw(),
),
marker: PhantomPinned,
},
}
}
#[inline]
pub fn strong_count(
&self,
shared_load_order: Ordering,
) -> Result<usize, ImpreciseRefCountError> {
let this_thread_id = LocalThreadState::existing_short_id().ok();
let shared_word = SharedWord::from_raw(self.shared_word.load(shared_load_order));
let biased_word = BiasedWord::from_raw(self.biased_word.load(Ordering::Relaxed));
if shared_word.merged {
let res = shared_word.shared_count.value();
if res < 0 {
unsafe {
if cfg!(debug_assertions) {
undefined_behavior::negative_refcnt_merge();
} else {
core::hint::unreachable_unchecked()
}
}
}
#[expect(
clippy::cast_sign_loss,
reason = "Reference count should be nonnegative for merged threads"
)]
Ok(res as usize)
} else if biased_word.owner_id == this_thread_id {
const {
assert!(
BiasedCount::BITS + 1 < usize::BITS as usize,
"biased count should be at least one bit smaller than usize"
);
assert!(
SharedCount::BITS + 1 < usize::BITS as usize,
"biased count should be at least one bit smaller than usize"
);
}
let biased_count = biased_word.biased_count.value() as usize;
let shared_count = shared_word.shared_count.value();
let sum_count: usize = {
if cfg!(debug_assertions) {
biased_count
.checked_add_signed(shared_count)
.unwrap_or_else(|| undefined_behavior::strong_count_arith_overflow())
} else {
biased_count.wrapping_add_signed(shared_count)
}
};
Ok(sum_count)
} else {
#[expect(clippy::cast_sign_loss, reason = "Ensured nonnegative before casting")]
Err(ImpreciseRefCountError {
lower_bound: shared_word.shared_count.value().max(0) as usize,
})
}
}
#[inline]
pub fn is_definitely_not_unique(&self) -> bool {
let biased_word = BiasedWord::from_raw(self.biased_word.load(Ordering::Relaxed));
let this_thread_id = LocalThreadState::existing_short_id().ok();
biased_word.owner_id.is_some() && biased_word.owner_id != this_thread_id
}
#[inline]
pub fn is_unique(&self) -> bool {
match self.strong_count(Ordering::Acquire) {
Ok(count) => count == 1,
Err(ImpreciseRefCountError { .. }) => false, }
}
#[inline]
pub fn biased_count(&self) -> Result<usize, BiasedCountError> {
let this_thread_id = LocalThreadState::existing_short_id().ok();
let biased_word = BiasedWord::from_raw(self.biased_word.load(Ordering::Relaxed));
if biased_word.owner_id.is_none() {
Err(BiasedCountError::NotBiased)
} else if this_thread_id == biased_word.owner_id {
Ok(biased_word.biased_count.value() as usize)
} else {
Err(BiasedCountError::WrongThread)
}
}
#[inline]
pub fn shared_count(&self) -> isize {
SharedWord::from_raw(self.shared_word.load(Ordering::Acquire))
.shared_count
.value()
}
#[inline]
fn attempt_biased_increment(&self) -> Result<(), FastIncrementFailure> {
let biased_word = BiasedWord::from_raw(self.biased_word.load(Ordering::Relaxed));
let incremented_counter = biased_word
.biased_count
.checked_add(BiasedCount::new(1))
.ok_or(FastIncrementFailure)?;
let this_id = LocalThreadState::existing_short_id().map_err(|_| FastIncrementFailure)?;
if biased_word.owner_id == Some(this_id) {
if cfg!(debug_assertions) && biased_word.biased_count.value() == 0 {
undefined_behavior::biased_count_zero_and_owned();
}
self.biased_word.store(
BiasedWord {
biased_count: incremented_counter,
..biased_word
}
.to_raw(),
Ordering::Relaxed,
);
Ok(())
} else {
Err(FastIncrementFailure)
}
}
#[inline]
pub fn increment_strong(&self) {
if self.attempt_biased_increment().is_err() {
self.increment_strong_shared();
}
}
#[cold]
#[inline]
pub fn increment_strong_shared(&self) {
let new_word = SharedWord::from_raw(self.shared_word.fetch_add(1, Ordering::Relaxed));
if new_word.shared_count > SharedWord::OVERFLOW_THRESHOLD {
fatal_errors::shared_refcnt_overflow();
}
}
#[inline]
pub fn increment_strong_unless_zero(&self) -> Result<(), ZeroReferenceCountError> {
if self.attempt_biased_increment().is_ok() {
Ok(())
} else {
match self
.shared_word
.fetch_update(Ordering::AcqRel, Ordering::Relaxed, |old_shared| {
let old_shared = SharedWord::from_raw(old_shared);
let is_dead = old_shared.merged && old_shared.shared_count.value() == 0;
if cfg!(debug_assertions)
&& old_shared.merged
&& old_shared.shared_count.value() < 0
{
undefined_behavior::negative_refcnt_merge();
}
if is_dead {
None
} else {
Some(
SharedWord {
shared_count: old_shared
.shared_count
.checked_add(SharedCount::new(1))
.unwrap_or_else(|| fatal_errors::shared_refcnt_overflow()),
..old_shared
}
.to_raw(),
)
}
}) {
Ok(_) => Ok(()),
Err(_) => Err(ZeroReferenceCountError),
}
}
}
#[inline]
pub unsafe fn decrement_strong<D: DropInfo>(
this: *const Self,
drop: D,
) -> StrongDecrementResult {
match unsafe { (*this).decrement_biased() } {
Ok(res) => {
res
}
Err(FastDecrementFailure) => {
unsafe { Self::decrement_shared(this, drop) }
}
}
}
#[inline]
unsafe fn decrement_biased(&self) -> Result<StrongDecrementResult, FastDecrementFailure> {
let biased_word = BiasedWord::from_raw(self.biased_word.load(Ordering::Relaxed));
let owner_id = biased_word.owner_id.ok_or(FastDecrementFailure)?;
let this_id = match LocalThreadState::existing_short_id() {
Ok(short_id) => short_id,
Err(
LocalThreadAccessError::Uninitialized
| LocalThreadAccessError::IdOverflow(_)
| LocalThreadAccessError::Dead,
) => {
return Err(FastDecrementFailure);
}
};
if this_id == owner_id {
debug_assert_ne!(biased_word.biased_count.value(), 0);
let new_biased_count = unsafe {
BiasedCount::new_unchecked(biased_word.biased_count.value().unchecked_sub(1))
};
if new_biased_count.value() > 0 {
self.biased_word.store(
BiasedWord {
biased_count: new_biased_count,
..biased_word
}
.to_raw(),
Ordering::Relaxed,
);
Ok(StrongDecrementResult { should_drop: false })
} else {
unsafe { Ok(self.decrement_biased_slow()) }
}
} else {
Err(FastDecrementFailure)
}
}
#[cold]
#[inline(never)] unsafe fn decrement_biased_slow(&self) -> StrongDecrementResult {
let old_shared = SharedWord::from_raw(
self.shared_word
.fetch_or(SharedWord::MERGED_BIT, Ordering::AcqRel),
);
debug_assert!(!old_shared.merged);
let new_shared = SharedWord {
merged: true,
..old_shared
};
debug_assert!(new_shared.shared_count.value() >= 0);
self.biased_word
.store(BiasedWord::UNOWNED.to_raw(), Ordering::Relaxed);
StrongDecrementResult {
should_drop: new_shared.shared_count.value() == 0,
}
}
#[cold]
#[inline(never)]
unsafe fn decrement_shared<D: DropInfo>(this: *const Self, drop: D) -> StrongDecrementResult {
let shared_word = unsafe { &(*this).shared_word };
let mut old = SharedWord::from_raw(shared_word.load(Ordering::Relaxed));
let mut new: SharedWord;
loop {
new = SharedWord {
shared_count: old
.shared_count
.checked_sub(SharedCount::new(1))
.unwrap_or_else(|| fatal_errors::shared_refcnt_underflow()),
..old
};
if new.shared_count.value() < 0 {
new.queued = true;
}
match shared_word.compare_exchange_weak(
old.to_raw(),
new.to_raw(),
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(x) => old = SharedWord::from_raw(x),
}
}
let should_drop: bool;
debug_assert!(!new.merged || new.shared_count.value() >= 0);
if old.queued != new.queued {
unsafe { Self::decrement_shared_do_queue(this, drop) }
should_drop = false; } else {
should_drop = new.merged && new.shared_count.value() == 0;
}
StrongDecrementResult { should_drop }
}
#[cold]
#[inline(never)]
unsafe fn decrement_shared_do_queue<D: DropInfo>(this: *const Self, drop: D) {
let biased_word =
BiasedWord::from_raw(unsafe { &(*this).biased_word }.load(Ordering::Relaxed));
let owner_id = biased_word
.owner_id
.unwrap_or_else(|| undefined_behavior::negative_refcnt_no_owner());
let drop = drop.erase();
unsafe {
SharedThreadInfo::get_by_id(owner_id)
.unwrap_or_else(|| undefined_behavior::owner_undefined_state())
.queue_object(QueuedObject {
header_ptr: NonNull::new_unchecked(this.cast_mut()),
drop: drop.clone(),
});
}
}
}
unsafe impl Send for RawBrcHeader {}
unsafe impl Sync for RawBrcHeader {}
#[derive(Debug)]
struct FastIncrementFailure;
#[derive(Debug)]
struct FastDecrementFailure;
impl Debug for RawBrcHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let RawBrcHeader {
marker: _,
biased_word,
shared_word,
} = self;
f.debug_struct("RawBrcHeader")
.field(
"biased_word",
&BiasedWord::from_raw(biased_word.load(Ordering::Relaxed)),
)
.field(
"shared_word",
&SharedWord::from_raw(shared_word.load(Ordering::Relaxed)),
)
.finish()
}
}
pub trait DropInfo: Copy {
fn value_offset(&self) -> isize;
fn erased_context(&self) -> ErasedDestructorContext;
unsafe fn erased_dealloc(
header_ptr: NonNull<RawBrcHeader>,
ctx: ErasedDestructorContext,
value_offset: isize,
);
#[inline]
unsafe fn dealloc(&self, header_ptr: NonNull<RawBrcHeader>) {
unsafe { Self::erased_dealloc(header_ptr, self.erased_context(), self.value_offset()) }
}
#[inline]
fn erase(&self) -> ErasedDropInfo {
ErasedDropInfo {
value_offset: self.value_offset(),
erased_ctx: self.erased_context(),
erased_func: Self::erased_dealloc,
}
}
}
#[derive(Clone)]
pub struct ErasedDropInfo {
value_offset: isize,
erased_ctx: ErasedDestructorContext,
erased_func: unsafe fn(NonNull<RawBrcHeader>, ErasedDestructorContext, isize),
}
impl ErasedDropInfo {
#[inline]
pub unsafe fn dealloc(&self, header_ptr: NonNull<RawBrcHeader>) {
unsafe { (self.erased_func)(header_ptr, self.erased_ctx, self.value_offset) }
}
}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct ErasedDestructorContext(pub *mut c_void);
type BiasedCount = u20;
#[derive(Copy, Clone, Debug)]
struct BiasedWord {
owner_id: Option<ShortThreadId>,
biased_count: BiasedCount,
}
impl BiasedWord {
const BITS: usize = 32;
const UNOWNED: BiasedWord = BiasedWord {
owner_id: None,
biased_count: BiasedCount::ZERO,
};
#[inline]
fn to_raw(self) -> u32 {
const {
assert!(ShortThreadId::BITS as usize + BiasedCount::BITS == Self::BITS);
}
((self.biased_count.value()) << ShortThreadId::BITS)
| (self
.owner_id
.map_or(0, |value| value.value().value() as u32))
}
#[inline]
fn from_raw(raw: u32) -> Self {
BiasedWord {
owner_id: ShortThreadId::new(arbitrary_int::u12::masked_new(raw)),
biased_count: BiasedCount::masked_new(raw >> ShortThreadId::BITS),
}
}
}
cfg_if! {
if #[cfg(target_pointer_width = "32")] {
type SharedCountInner = i30;
} else if #[cfg(target_pointer_width = "64")] {
type SharedCountInner = i62;
} else {
compile_error!("unsupported pointer width");
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct SharedCount(SharedCountInner);
impl SharedCount {
pub const BITS: usize = {
assert!(SharedCountInner::BITS <= usize::BITS as usize);
SharedCountInner::BITS
};
pub const MAX: Self = Self(SharedCountInner::MAX);
#[inline]
pub unsafe fn new_unchecked(val: isize) -> Self {
unsafe { Self(SharedCountInner::new_unchecked(val as _)) }
}
#[inline]
pub const fn new(val: isize) -> Self {
Self(SharedCountInner::new(val as _))
}
#[inline]
#[expect(clippy::cast_possible_truncation, reason = "known to fit in isize")]
pub const fn value(self) -> isize {
self.0.value() as isize
}
#[inline]
#[expect(clippy::cast_possible_truncation, reason = "known to fit in usize")]
pub const fn to_bits(self) -> usize {
self.0.to_bits() as usize
}
#[inline]
pub fn masked_new(value: usize) -> Self {
Self(SharedCountInner::masked_new(value as u64))
}
#[inline]
pub fn checked_sub(self, other: Self) -> Option<Self> {
self.0.checked_sub(other.0).map(Self)
}
#[inline]
pub fn checked_add(self, other: Self) -> Option<Self> {
self.0.checked_add(other.0).map(Self)
}
}
#[derive(Copy, Clone, Debug)]
struct SharedWord {
shared_count: SharedCount,
merged: bool,
queued: bool,
}
impl SharedWord {
const BITS: usize = SharedCount::BITS + 2;
const MERGED_BIT: usize = 1 << SharedCount::BITS;
const QUEUED_BIT: usize = 1 << (SharedCount::BITS + 1);
const OVERFLOW_THRESHOLD: SharedCount = SharedCount::new(SharedCount::MAX.value() / 2);
#[inline]
fn to_raw(self) -> usize {
const {
assert!(usize::BITS as usize == Self::BITS);
}
self.shared_count.to_bits()
| ((self.merged as usize) << SharedCount::BITS)
| ((self.queued as usize) << (SharedCount::BITS + 1))
}
#[inline]
fn from_raw(raw: usize) -> Self {
SharedWord {
shared_count: SharedCount::masked_new(raw),
merged: (raw & Self::MERGED_BIT) != 0,
queued: (raw & Self::QUEUED_BIT) != 0,
}
}
}
#[derive(Clone)]
pub(super) struct QueuedObject {
pub header_ptr: NonNull<RawBrcHeader>,
pub drop: ErasedDropInfo,
}
unsafe impl Send for QueuedObject {}
unsafe impl Sync for QueuedObject {}
#[cold]
pub(super) unsafe fn explicit_merge(biased_tid: ShortThreadId, object: QueuedObject) {
let header = unsafe { object.header_ptr.as_ref() };
let biased = BiasedWord::from_raw(header.biased_word.load(Ordering::Relaxed));
if biased.owner_id != Some(biased_tid) {
undefined_behavior::explicit_merge_bad_id();
}
let mut old_word = SharedWord::from_raw(header.shared_word.load(Ordering::Relaxed));
let mut new_word: SharedWord;
loop {
assert!(!old_word.merged);
let biased_count: SharedCount;
{
const {
assert!(
BiasedCount::MAX.value() as i64 <= SharedCount::MAX.value() as i64,
"BiasedCount doesn't fit in a SharedCount"
);
}
#[expect(clippy::cast_possible_wrap, reason = "checked above")]
unsafe {
biased_count = SharedCount::new_unchecked(biased.biased_count.value() as isize);
}
}
new_word = SharedWord {
shared_count: old_word
.shared_count
.checked_add(biased_count)
.unwrap_or_else(|| fatal_errors::merged_refcnt_overflow()),
merged: true,
..old_word
};
match header.shared_word.compare_exchange_weak(
old_word.to_raw(),
new_word.to_raw(),
Ordering::AcqRel,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual_value) => {
old_word = SharedWord::from_raw(actual_value);
continue;
}
}
}
header
.biased_word
.store(BiasedWord::UNOWNED.to_raw(), Ordering::Relaxed);
match new_word.shared_count.value().cmp(&0) {
core::cmp::Ordering::Less => {
undefined_behavior::negative_refcnt_merge()
}
core::cmp::Ordering::Equal => {
unsafe { object.drop.dealloc(object.header_ptr) }
}
core::cmp::Ordering::Greater => {
}
}
}
#[inline]
pub fn collect() {
if LocalThreadState::currently_needs_collect() {
LocalThreadState::collect_slow::<MayPanic>();
}
}
#[inline]
pub fn collect_nounwind() {
if LocalThreadState::currently_needs_collect() {
LocalThreadState::collect_slow::<NeverPanic>();
}
}
#[inline]
pub(crate) fn collect_implicit() {
if cfg!(any(
feature = "implicit-collect-disable",
biasedrc_no_implicit_collect
)) {
} else {
collect_nounwind();
}
}
#[cold]
pub fn collect_force() {
let _ = LocalThreadState::with_current(LocalThreadState::collect_force);
}
pub(crate) mod fatal_errors {
macro_rules! fatal_error {
($name:ident => $fmt:expr $(, $($arg:tt)*)?) => {
#[cold]
#[inline(never)]
pub(crate) fn $name() -> ! {
nounwind::panic_nounwind!(concat!("biasedrc: ", $fmt) $(, $($arg)*)*)
}
};
}
fatal_error!(shared_refcnt_underflow => "Reference count underflow for shared counter");
fatal_error!(shared_refcnt_overflow => "Reference count overflow for a shared counter");
fatal_error!(merged_refcnt_overflow => "Merged reference counts overflow the shared counter");
fatal_error!(weak_refcnt_overflow => "Weak reference counts overflowed its counter");
}
#[cfg_attr(not(debug_assertions), allow(unused))]
mod undefined_behavior {
macro_rules! undefined_behavior {
($name:ident => $fmt:expr $(, $($arg:tt)*)?) => {
#[cold]
#[inline(never)]
pub(crate) fn $name() -> ! {
nounwind::panic_nounwind!(concat!("biasedrc encountered undefined behavior: ", $fmt) $(, $($arg)*)*)
}
};
}
undefined_behavior!(negative_refcnt_merge => "Negative reference count after merging counter");
undefined_behavior!(explicit_merge_bad_id => "The `explicit_merge` function is called with bad tid");
undefined_behavior!(negative_refcnt_no_owner => "Negative reference count but no biased thread");
undefined_behavior!(owner_undefined_state => "Biased thread has undefined state, but still owns objects");
undefined_behavior!(strong_count_arith_overflow => "Computing the strong_count either overflowed (impossible) or underflowed (UB) a counter");
undefined_behavior!(biased_count_zero_and_owned => "Reference is biased towards a particular thread, but has a zero refcount");
}