Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find roots when running GC rather than runtime #3109

Merged
merged 9 commits into from
Jul 26, 2023
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
18 changes: 10 additions & 8 deletions boa_engine/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1064,19 +1064,21 @@ impl Array {
// d. Let lowerExists be ? HasProperty(O, lowerP).
let lower_exists = o.has_property(lower, context)?;
// e. If lowerExists is true, then
let mut lower_value = JsValue::undefined();
if lower_exists {
let lower_value = if lower_exists {
// i. Let lowerValue be ? Get(O, lowerP).
lower_value = o.get(lower, context)?;
}
o.get(lower, context)?
} else {
JsValue::undefined()
};
// f. Let upperExists be ? HasProperty(O, upperP).
let upper_exists = o.has_property(upper, context)?;
// g. If upperExists is true, then
let mut upper_value = JsValue::undefined();
if upper_exists {
let upper_value = if upper_exists {
// i. Let upperValue be ? Get(O, upperP).
upper_value = o.get(upper, context)?;
}
o.get(upper, context)?
} else {
JsValue::undefined()
};
match (lower_exists, upper_exists) {
// h. If lowerExists is true and upperExists is true, then
(true, true) => {
Expand Down
3 changes: 2 additions & 1 deletion boa_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_possible_wrap
clippy::cast_possible_wrap,
clippy::must_use_candidate,
)]

extern crate static_assertions as sa;
Expand Down
10 changes: 1 addition & 9 deletions boa_engine/src/native_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ where
/// to use. All other closures can also be stored in a `NativeFunction`, albeit by using an `unsafe`
/// API, but note that passing closures implicitly capturing traceable types could cause
/// **Undefined Behaviour**.
#[derive(Clone)]
#[derive(Clone, Finalize)]
pub struct NativeFunction {
inner: Inner,
}
Expand All @@ -80,14 +80,6 @@ enum Inner {
Closure(Gc<dyn TraceableClosure>),
}

impl Finalize for NativeFunction {
fn finalize(&self) {
if let Inner::Closure(c) = &self.inner {
c.finalize();
}
}
}

// Manual implementation because deriving `Trace` triggers the `single_use_lifetimes` lint.
// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe.
unsafe impl Trace for NativeFunction {
Expand Down
84 changes: 11 additions & 73 deletions boa_gc/src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,53 +26,35 @@ enum BorrowState {
Unused,
}

const ROOT: usize = 1;
const WRITING: usize = !1;
const WRITING: usize = !0;
const UNUSED: usize = 0;

/// The base borrowflag init is rooted, and has no outstanding borrows.
const BORROWFLAG_INIT: BorrowFlag = BorrowFlag(ROOT);
const BORROWFLAG_INIT: BorrowFlag = BorrowFlag(UNUSED);

impl BorrowFlag {
/// Check the current `BorrowState` of `BorrowFlag`.
const fn borrowed(self) -> BorrowState {
match self.0 & !ROOT {
match self.0 {
UNUSED => BorrowState::Unused,
WRITING => BorrowState::Writing,
_ => BorrowState::Reading,
}
}

/// Check whether the borrow bit is flagged.
const fn rooted(self) -> bool {
self.0 & ROOT > 0
}

/// Set the `BorrowFlag`'s state to writing.
const fn set_writing(self) -> Self {
// Set every bit other than the root bit, which is preserved
Self(self.0 | WRITING)
}

/// Remove the root flag on `BorrowFlag`
const fn set_unused(self) -> Self {
// Clear every bit other than the root bit, which is preserved
Self(self.0 & ROOT)
}

/// Increments the counter for a new borrow.
///
/// # Panic
/// - This method will panic if the current `BorrowState` is writing.
/// - This method will panic after incrementing if the borrow count overflows.
fn add_reading(self) -> Self {
assert!(self.borrowed() != BorrowState::Writing);
// Add 1 to the integer starting at the second binary digit. As our
// borrowstate is not writing, we know that overflow cannot happen, so
// this is equivalent to the following, more complicated, expression:
//
// BorrowFlag((self.0 & ROOT) | (((self.0 >> 1) + 1) << 1))
let flags = Self(self.0 + 0b10);
let flags = Self(self.0 + 1);

// This will fail if the borrow count overflows, which shouldn't happen,
// but let's be safe
Expand All @@ -88,26 +70,13 @@ impl BorrowFlag {
/// - This method will panic if the current `BorrowState` is not reading.
fn sub_reading(self) -> Self {
assert!(self.borrowed() == BorrowState::Reading);
// Subtract 1 from the integer starting at the second binary digit. As
// our borrowstate is not writing or unused, we know that overflow or
// undeflow cannot happen, so this is equivalent to the following, more
// complicated, expression:
//
// BorrowFlag((self.0 & ROOT) | (((self.0 >> 1) - 1) << 1))
Self(self.0 - 0b10)
}

/// Set the root flag on the `BorrowFlag`.
fn set_rooted(self, rooted: bool) -> Self {
// Preserve the non-root bits
Self((self.0 & !ROOT) | (usize::from(rooted)))
Self(self.0 - 1)
}
}

impl Debug for BorrowFlag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BorrowFlag")
.field("Rooted", &self.rooted())
.field("State", &self.borrowed())
.finish()
}
Expand Down Expand Up @@ -214,12 +183,6 @@ impl<T: Trace + ?Sized> GcRefCell<T> {
// SAFETY: This is safe as the value is rooted if it was not previously rooted,
// so it cannot be dropped.
unsafe {
// Force the val_ref's contents to be rooted for the duration of the
// mutable borrow
if !self.flags.get().rooted() {
(*self.cell.get()).root();
}

Ok(GcRefMut {
gc_cell: self,
value: &mut *self.cell.get(),
Expand Down Expand Up @@ -263,25 +226,11 @@ unsafe impl<T: Trace + ?Sized> Trace for GcRefCell<T> {
}
}

unsafe fn root(&self) {
assert!(!self.flags.get().rooted(), "Can't root a GcCell twice!");
self.flags.set(self.flags.get().set_rooted(true));

match self.flags.get().borrowed() {
BorrowState::Writing => (),
// SAFETY: Please see GcCell's Trace impl Safety note.
_ => unsafe { (*self.cell.get()).root() },
}
}

unsafe fn unroot(&self) {
assert!(self.flags.get().rooted(), "Can't unroot a GcCell twice!");
self.flags.set(self.flags.get().set_rooted(false));

fn trace_non_roots(&self) {
match self.flags.get().borrowed() {
BorrowState::Writing => (),
// SAFETY: Please see GcCell's Trace impl Safety note.
_ => unsafe { (*self.cell.get()).unroot() },
_ => unsafe { (*self.cell.get()).trace_non_roots() },
}
}

Expand Down Expand Up @@ -407,12 +356,12 @@ impl<T: ?Sized + Display> Display for GcRef<'_, T> {
}

/// A wrapper type for a mutably borrowed value from a `GcCell<T>`.
pub struct GcRefMut<'a, T: Trace + ?Sized + 'static, U: ?Sized = T> {
pub struct GcRefMut<'a, T: ?Sized + 'static, U: ?Sized = T> {
pub(crate) gc_cell: &'a GcRefCell<T>,
pub(crate) value: &'a mut U,
}

impl<'a, T: Trace + ?Sized, U: ?Sized> GcRefMut<'a, T, U> {
impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> {
/// Makes a new `GcCellRefMut` for a component of the borrowed data, e.g., an enum
/// variant.
///
Expand Down Expand Up @@ -457,21 +406,10 @@ impl<T: Trace + ?Sized, U: ?Sized> DerefMut for GcRefMut<'_, T, U> {
}
}

impl<T: Trace + ?Sized, U: ?Sized> Drop for GcRefMut<'_, T, U> {
impl<T: ?Sized, U: ?Sized> Drop for GcRefMut<'_, T, U> {
fn drop(&mut self) {
debug_assert!(self.gc_cell.flags.get().borrowed() == BorrowState::Writing);
// Restore the rooted state of the GcCell's contents to the state of the GcCell.
// During the lifetime of the GcCellRefMut, the GcCell's contents are rooted.
if !self.gc_cell.flags.get().rooted() {
// SAFETY: If `GcCell` is no longer rooted, then unroot it. This should be safe
// as the internal `GcBox` should be guaranteed to have at least 1 root.
unsafe {
(*self.gc_cell.cell.get()).unroot();
}
}
self.gc_cell
.flags
.set(self.gc_cell.flags.get().set_unused());
self.gc_cell.flags.set(BorrowFlag(UNUSED));
}
}

Expand Down
Loading