Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions oscars/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ thin-vec = { version = "0.2", optional = true }
icu_locale_core = { version = "2.2.0", default-features = false, optional = true }
either = { version = "1.16.0", optional = true }
arrayvec = { version = "0.7.6", optional = true }
typeid = "1.0.3"

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
Expand Down
14 changes: 10 additions & 4 deletions oscars/src/collectors/mark_sweep_branded/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
use core::fmt;
use core::marker::PhantomData;
use core::ops::Deref;
use typeid;

/// A transient pointer to a GC-managed value.
#[derive(Debug)]
Expand Down Expand Up @@ -89,10 +90,15 @@ impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> {
}
}

/// Returns `true` if the inner value is of type `U`.
///
/// Uses `typeid::of::<U>()`, sound even when `U` carries a branded
/// lifetime because it properly handles branded lifetimes. This avoids the `T: 'static` restriction while still
/// giving us a stable, unique identity guarantee.
#[inline]
pub fn is<U: Trace + ?Sized + 'gc>(&self) -> bool {
let actual_type_name = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_name };
actual_type_name == core::any::type_name::<U>()
pub fn is<U: Trace + ?Sized>(&self) -> bool {
let actual_type_id = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_id };
actual_type_id == typeid::of::<U>()
}
Comment thread
shruti2522 marked this conversation as resolved.

#[inline]
Expand Down Expand Up @@ -149,7 +155,7 @@ impl<'gc, T: Trace + ?Sized + 'gc> Deref for Gc<'gc, T> {
}

impl<T: Trace + ?Sized> Finalize for Gc<'_, T> {}
unsafe impl<T: Trace + ?Sized> Trace for Gc<'_, T> {
unsafe impl<'gc, T: Trace + ?Sized + 'gc> Trace for Gc<'gc, T> {
unsafe fn trace(&self, tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {
tracer.mark(self);
}
Expand Down
17 changes: 12 additions & 5 deletions oscars/src/collectors/mark_sweep_branded/gc_box.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! The heap header wrapping every GC-managed value.

use core::any::TypeId;
use core::cell::Cell;
use core::ptr::NonNull;

Expand Down Expand Up @@ -32,8 +33,12 @@ pub struct GcBox<T: ?Sized> {
pub(crate) drop_fn: DropFn,
/// Allocation ID used to validate weak pointers.
pub(crate) alloc_id: usize,
/// Type name of the underlying value
pub(crate) type_name: &'static str,
/// Unique identifier for the concrete type `T`.
///
/// Stored as `typeid::of::<T>()`. This safely erases branded lifetimes
/// (eg. `'gc`) without requiring `T: 'static`, giving us a stable
/// unique identity guarantee for sound downcasting.
pub(crate) type_id: TypeId,
/// The user value.
pub(crate) value: T,
}
Expand All @@ -42,15 +47,17 @@ impl<T: ?Sized> GcBox<T> {
pub(crate) const FREED_ALLOC_ID: usize = usize::MAX;
}

impl<T> GcBox<T> {
/// Create a [`GcBox`] for `value`, `color` starts as [`GcColor::White`]
impl<T: Trace> GcBox<T> {
/// Create a [`GcBox`] for `value`, `color` starts as [`GcColor::White`].
///
/// Requires `T: Trace` for the `TypeId`.
pub(crate) fn new(value: T, trace_fn: TraceFn, drop_fn: DropFn, alloc_id: usize) -> Self {
Self {
color: Cell::new(GcColor::White),
trace_fn,
drop_fn,
alloc_id,
type_name: core::any::type_name::<T>(),
type_id: typeid::of::<T>(),
value,
}
}
Expand Down
6 changes: 6 additions & 0 deletions oscars/src/collectors/mark_sweep_branded/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ pub struct Collector {
pub(crate) ephemerons: RefCell<Vec<EphemeronEntry>>,
}

impl Default for Collector {
fn default() -> Self {
Self::new()
}
}

impl Collector {
pub fn new() -> Self {
Self {
Expand Down
51 changes: 32 additions & 19 deletions oscars/src/collectors/mark_sweep_branded/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl<'a> Tracer<'a> {
}
}

unsafe impl<T: ?Sized> Trace for &T {
unsafe impl<T: Trace + ?Sized> Trace for &T {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {}
}
Expand All @@ -149,7 +149,6 @@ empty_trace![
bool,
isize,
usize,
str,
i8,
u8,
i16,
Expand Down Expand Up @@ -180,6 +179,12 @@ empty_trace![
core::sync::atomic::AtomicUsize,
];

// str is a DST; we cannot allocate it directly. Use String as the Sized proxy.
unsafe impl Trace for str {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {}
}

unsafe impl<T: Trace, const N: usize> Trace for [T; N] {
unsafe fn trace(&self, tracer: &mut Tracer) {
for v in self.iter() {
Expand All @@ -188,6 +193,17 @@ unsafe impl<T: Trace, const N: usize> Trace for [T; N] {
}
}

// Slices [T] cannot be allocated directly in the GC.
unsafe impl<T: Trace> Trace for [T] {
#[inline]
unsafe fn trace(&self, tracer: &mut Tracer) {
for v in self {
v.trace(tracer);
}
}
}

// Box<T> where T: ?Sized. Box is always Sized even for DST contents.
unsafe impl<T: Trace + ?Sized> Trace for Box<T> {
unsafe fn trace(&self, tracer: &mut Tracer) {
(**self).trace(tracer);
Expand Down Expand Up @@ -244,20 +260,12 @@ unsafe impl<T: Trace> Trace for LinkedList<T> {
}
}

unsafe impl<T> Trace for PhantomData<T> {
// PhantomData<T> doesn't trace T, so T need not implement Trace.
unsafe impl<T: 'static> Trace for PhantomData<T> {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {}
}

unsafe impl<T: Trace> Trace for [T] {
#[inline]
unsafe fn trace(&self, tracer: &mut Tracer) {
for v in self {
v.trace(tracer);
}
}
}

unsafe impl Trace for core::any::TypeId {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {}
Expand All @@ -281,10 +289,11 @@ unsafe impl<T: Trace> Trace for OnceCell<T> {
}
}

unsafe impl<T: ToOwned + Trace + ?Sized> Trace for Cow<'static, T>
unsafe impl<T: ToOwned + Trace + ?Sized + 'static> Trace for Cow<'static, T>
where
T::Owned: Trace,
{
// T is already 'static so we can use it directly as the proxy.
unsafe fn trace(&self, tracer: &mut Tracer) {
if let Cow::Owned(v) = self {
v.trace(tracer);
Expand Down Expand Up @@ -380,25 +389,29 @@ unsafe impl<A: Trace, B: Trace, C: Trace, D: Trace, E: Trace, F: Trace, G: Trace
// Rc and Arc do not contain Gc pointers (they use reference counting, not GC).
// If you need to store Gc pointers inside Rc/Arc, wrap them in a GC-allocated
// struct instead.
unsafe impl<T: ?Sized> Trace for rust_alloc::rc::Rc<T> {
// Rc/Arc are reference-counted, not GC-traced. They cannot contain live Gc
// pointers (that would create a cycle the GC cannot see).
unsafe impl<T: ?Sized + 'static> Trace for rust_alloc::rc::Rc<T> {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {}
}

unsafe impl<T: ?Sized> Trace for rust_alloc::sync::Arc<T> {
unsafe impl<T: ?Sized + 'static> Trace for rust_alloc::sync::Arc<T> {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {}
}

unsafe impl<K, V: Trace> Trace for BTreeMap<K, V> {
// K is not traced (BTreeMap keys are immutable).
unsafe impl<K: 'static, V: Trace> Trace for BTreeMap<K, V> {
unsafe fn trace(&self, tracer: &mut Tracer) {
for v in self.values() {
v.trace(tracer);
}
}
}

unsafe impl<T> Trace for BTreeSet<T> {
// BTreeSet keys are never traced.
unsafe impl<T: 'static> Trace for BTreeSet<T> {
#[inline]
unsafe fn trace(&self, _tracer: &mut Tracer) {
// BTreeSet keys are immutable and cannot contain Gc pointers
Expand Down Expand Up @@ -470,7 +483,7 @@ unsafe impl Trace for std::time::SystemTime {
}

#[cfg(feature = "std")]
unsafe impl<K: Trace, V: Trace, S> Trace for std::collections::HashMap<K, V, S> {
unsafe impl<K: Trace, V: Trace, S: 'static> Trace for std::collections::HashMap<K, V, S> {
Comment thread
nekevss marked this conversation as resolved.
#[inline]
unsafe fn trace(&self, tracer: &mut Tracer) {
for (k, v) in self {
Expand All @@ -481,7 +494,7 @@ unsafe impl<K: Trace, V: Trace, S> Trace for std::collections::HashMap<K, V, S>
}

#[cfg(feature = "std")]
unsafe impl<T: Trace, S> Trace for std::collections::HashSet<T, S> {
unsafe impl<T: Trace, S: 'static> Trace for std::collections::HashSet<T, S> {
#[inline]
unsafe fn trace(&self, tracer: &mut Tracer) {
for v in self {
Expand Down
3 changes: 1 addition & 2 deletions oscars/src/collectors/mark_sweep_branded/weak.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ impl<'id, T: Trace + ?Sized> WeakGc<'id, T> {

/// Returns `true` if the referenced value is still alive.
pub fn is_upgradable(&self) -> bool {
let is_valid = unsafe { (*self.ptr.as_ptr().as_ptr()).0.alloc_id == self.alloc_id };
is_valid
unsafe { (*self.ptr.as_ptr().as_ptr()).0.alloc_id == self.alloc_id }
}
}

Expand Down
11 changes: 8 additions & 3 deletions oscars/src/collectors/null_collector_branded/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,14 @@ impl<T: Trace + ?Sized> DerefMut for GcRefMut<'_, T> {
}
}

impl<'a, T: Trace + ?Sized> GcRef<'a, T> {
pub fn clone(orig: &GcRef<'a, T>) -> GcRef<'a, T> {
GcRef(Ref::clone(&orig.0))
impl<'a, T: Trace + ?Sized> Clone for GcRef<'a, T> {
#[inline]
fn clone(&self) -> Self {
GcRef(Ref::clone(&self.0))
}
}

impl<'a, T: Trace + ?Sized> GcRef<'a, T> {
pub fn map<U: Trace + ?Sized, F>(orig: GcRef<'a, T>, f: F) -> GcRef<'a, U>
where
F: FnOnce(&T) -> &U,
Expand Down Expand Up @@ -130,6 +134,7 @@ impl<'a, T: Trace + ?Sized> GcRefMut<'a, T> {
impl<T: Trace + ?Sized> Finalize for GcRefCell<T> {}

unsafe impl<T: Trace + ?Sized> Trace for GcRefCell<T> {
// GcRefCell<'gc, T> is branded by T's lifetime. Map to the static form.
#[inline]
unsafe fn trace(&self, tracer: &mut Tracer) {
// SAFETY: We only access the inner value for tracing and do not mutate it.
Expand Down
13 changes: 9 additions & 4 deletions oscars/src/collectors/null_collector_branded/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,15 @@ impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> {
}
}

/// Returns `true` if the inner value is of type `U`.
///
/// Uses `typeid::of::<U>()`, sound even when `U` carries a branded
/// lifetime because it properly handles branded lifetimes. This avoids the `T: 'static` restriction while still
/// giving us a stable, unique identity guarantee
#[inline]
pub fn is<U: Trace + ?Sized + 'gc>(&self) -> bool {
let actual_type_name = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_name };
actual_type_name == core::any::type_name::<U>()
pub fn is<U: Trace + ?Sized>(&self) -> bool {
let actual_type_id = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_id };
actual_type_id == typeid::of::<U>()
}

#[inline]
Expand Down Expand Up @@ -141,7 +146,7 @@ impl<'gc, T: Trace + ?Sized + 'gc> Deref for Gc<'gc, T> {

impl<T: Trace + ?Sized> Finalize for Gc<'_, T> {}

unsafe impl<T: Trace + ?Sized> Trace for Gc<'_, T> {
unsafe impl<'gc, T: Trace + ?Sized + 'gc> Trace for Gc<'gc, T> {
unsafe fn trace(&self, tracer: &mut crate::collectors::null_collector_branded::trace::Tracer) {
tracer.mark(self);
}
Expand Down
18 changes: 13 additions & 5 deletions oscars/src/collectors/null_collector_branded/gc_box.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use core::any::TypeId;
use core::ptr::NonNull;

use crate::alloc::mempool3::PoolAllocator;
use crate::collectors::null_collector_branded::trace::Trace;

pub type DropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>);

Expand All @@ -10,18 +12,24 @@ pub type DropFn = unsafe fn(&mut PoolAllocator<'static>, NonNull<u8>);
pub struct GcBox<T: ?Sized> {
/// Type erased finalize and free fn
pub(crate) drop_fn: DropFn,
/// Type name of the underlying value
pub(crate) type_name: &'static str,
/// Unique identifier for the concrete type `T`.
///
/// Stored as `typeid::of::<T>()`. This safely erases branded lifetimes
/// (eg. `'gc`) without requiring `T: 'static`, giving us a stable
/// unique identity guarantee for sound downcasting.
pub(crate) type_id: TypeId,
/// User value
pub(crate) value: T,
}

impl<T> GcBox<T> {
/// Create a [`GcBox`] for `value`
impl<T: Trace> GcBox<T> {
/// Create a [`GcBox`] for `value`.
///
/// Requires `T: Trace` for the `TypeId`.
pub(crate) fn new(value: T, drop_fn: DropFn) -> Self {
Self {
drop_fn,
type_name: core::any::type_name::<T>(),
type_id: typeid::of::<T>(),
value,
}
}
Expand Down
12 changes: 12 additions & 0 deletions oscars/src/collectors/null_collector_branded/mutation_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,24 @@ impl<'id, 'gc> MutationContext<'id, 'gc> {
/// **Note**: This is a temporary workaround to keep `boa_engine` working.
/// It breaks the normal safety rules of the collector, and should only be
/// used to support older code that relies on `Default`
///
/// # Safety
///
/// `Gc<'gc, T>` and `MutationContext` are both `!Send`, so neither can escape
/// the thread that created them. `Collector::drop` only runs at thread exit,
/// after which no `Gc` on this thread can be accessed. The raw pointer reborrow
/// below is therefore sound ,the reference cannot outlive the TLS slot.
Comment thread
shruti2522 marked this conversation as resolved.
#[cfg(feature = "std")]
pub fn global() -> Self {
std::thread_local! {
static COLLECTOR: crate::collectors::null_collector_branded::Collector = crate::collectors::null_collector_branded::Collector::new();
}
COLLECTOR.with(|c| {
// SAFETY: `Gc` and `MutationContext` are `!Send`, so they cannot escape
// this thread. `COLLECTOR` is a thread-local whose destructor only runs
// at thread exit, after all thread-local values are inaccessible.
// Therefore, `c` remains valid for at least as long as any `MutationContext`
// or `Gc` that could possibly reference it.
let ptr = c as *const crate::collectors::null_collector_branded::Collector;
Self {
collector: unsafe { &*ptr },
Expand Down
Loading