Skip to content

Commit

Permalink
feat(ecmascript): BoundFunction lifetime (#493)
Browse files Browse the repository at this point in the history
  • Loading branch information
aapoalas authored Jan 1, 2025
1 parent 1430fa4 commit 5a698d7
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 111 deletions.
70 changes: 24 additions & 46 deletions nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,6 @@ impl IntegerOrInfinity {
}

/// ### [7.1.5 ToIntegerOrInfinity ( argument )](https://tc39.es/ecma262/#sec-tointegerorinfinity)
// TODO: Should we add another [`Value`] newtype for IntegerOrInfinity?
pub(crate) fn to_integer_or_infinity(
agent: &mut Agent,
argument: Value,
Expand All @@ -489,30 +488,42 @@ pub(crate) fn to_integer_or_infinity(
return Ok(int);
}
// 1. Let number be ? ToNumber(argument).
let number = to_number(agent, argument, gc.reborrow())?.unbind();
let number = to_number(agent, argument, gc.reborrow())?
.unbind()
.bind(gc.nogc());

Ok(to_integer_or_infinity_number(agent, number, gc.nogc()))
}

/// ### [7.1.5 ToIntegerOrInfinity ( argument )](https://tc39.es/ecma262/#sec-tointegerorinfinity)
///
/// This implements steps from 2 onwards.
pub(crate) fn to_integer_or_infinity_number(
agent: &Agent,
number: Number,
gc: NoGcScope,
) -> IntegerOrInfinity {
// Fast path: The value might've been eg. parsed into an integer.
if let Number::Integer(int) = number {
let int = IntegerOrInfinity(int.into_i64());
return Ok(int);
return int;
}

let gc = gc.into_nogc();
let number = number.bind(gc);

// 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0.
if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) {
return Ok(IntegerOrInfinity(0));
return IntegerOrInfinity(0);
}

// 3. If number is +∞𝔽, return +∞.
if number.is_pos_infinity(agent) {
return Ok(IntegerOrInfinity::POS_INFINITY);
return IntegerOrInfinity::POS_INFINITY;
}

// 4. If number is -∞𝔽, return -∞.
if number.is_neg_infinity(agent) {
return Ok(IntegerOrInfinity::NEG_INFINITY);
return IntegerOrInfinity::NEG_INFINITY;
}

// 5. Return truncate(ℝ(number)).
Expand All @@ -526,11 +537,10 @@ pub(crate) fn to_integer_or_infinity(
} else {
number
};
Ok(IntegerOrInfinity(number))
IntegerOrInfinity(number)
}

/// ### [7.1.5 ToIntegerOrInfinity ( argument )](https://tc39.es/ecma262/#sec-tointegerorinfinity)
// TODO: Should we add another [`Value`] newtype for IntegerOrInfinity?
//
// This version of the abstract operation attempts to convert the argument
// Value into an integer or infinity without calling any JavaScript code. If
Expand All @@ -540,57 +550,25 @@ pub(crate) fn try_to_integer_or_infinity(
agent: &mut Agent,
argument: Value,
gc: NoGcScope<'_, '_>,
) -> Option<JsResult<IntegerOrInfinity>> {
) -> TryResult<JsResult<IntegerOrInfinity>> {
// Fast path: A safe integer is already an integer.
if let Value::Integer(int) = argument {
let int = IntegerOrInfinity(int.into_i64());
return Some(Ok(int));
return TryResult::Continue(Ok(int));
}
// 1. Let number be ? ToNumber(argument).
let Ok(argument) = Primitive::try_from(argument) else {
// Converting to Number would require calling into JavaScript code.
return None;
return TryResult::Break(());
};
let number = match to_number_primitive(agent, argument, gc) {
Ok(number) => number,
Err(err) => {
return Some(Err(err));
return TryResult::Continue(Err(err));
}
};

// Fast path: The value might've been eg. parsed into an integer.
if let Number::Integer(int) = number {
let int = IntegerOrInfinity(int.into_i64());
return Some(Ok(int));
}

// 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0.
if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) {
return Some(Ok(IntegerOrInfinity(0)));
}

// 3. If number is +∞𝔽, return +∞.
if number.is_pos_infinity(agent) {
return Some(Ok(IntegerOrInfinity::POS_INFINITY));
}

// 4. If number is -∞𝔽, return -∞.
if number.is_neg_infinity(agent) {
return Some(Ok(IntegerOrInfinity::NEG_INFINITY));
}

// 5. Return truncate(ℝ(number)).
let number = number.into_f64(agent).trunc() as i64;
// Note: Make sure converting the f64 didn't take us to our sentinel
// values.
let number = if number == i64::MAX {
i64::MAX - 1
} else if number == i64::MIN {
i64::MIN + 1
} else {
number
};
Some(Ok(IntegerOrInfinity(number)))
TryResult::Continue(Ok(to_integer_or_infinity_number(agent, number, gc)))
}

/// ### [7.1.6 ToInt32 ( argument )](https://tc39.es/ecma262/#sec-toint32)
Expand Down
104 changes: 78 additions & 26 deletions nova_vm/src/ecmascript/builtins/bound_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::ops::{Index, IndexMut};

use crate::ecmascript::types::{function_try_get, function_try_has_property, function_try_set};
use crate::engine::context::{GcScope, NoGcScope};
use crate::engine::TryResult;
use crate::engine::rootable::{HeapRootData, HeapRootRef, Rootable};
use crate::engine::{Scoped, TryResult};
use crate::{
ecmascript::{
abstract_operations::{
Expand All @@ -33,9 +34,37 @@ use super::ArgumentsList;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct BoundFunction(BoundFunctionIndex);
pub struct BoundFunction<'a>(BoundFunctionIndex<'a>);

impl BoundFunction<'_> {
/// Unbind this BoundFunction from its current lifetime. This is necessary to use
/// the BoundFunction as a parameter in a call that can perform garbage
/// collection.
pub fn unbind(self) -> BoundFunction<'static> {
unsafe { std::mem::transmute::<BoundFunction, BoundFunction<'static>>(self) }
}

// Bind this BoundFunction to the garbage collection lifetime. This enables Rust's
// borrow checker to verify that your BoundFunctions cannot not be invalidated by
// garbage collection being performed.
//
// This function is best called with the form
// ```rs
// let number = number.bind(&gc);
// ```
// to make sure that the unbound BoundFunction cannot be used after binding.
pub const fn bind<'gc>(self, _: NoGcScope<'gc, '_>) -> BoundFunction<'gc> {
unsafe { std::mem::transmute::<BoundFunction, BoundFunction<'gc>>(self) }
}

pub fn scope<'scope>(
self,
agent: &mut Agent,
gc: NoGcScope<'_, 'scope>,
) -> Scoped<'scope, BoundFunction<'static>> {
Scoped::new(agent, self.unbind(), gc)
}

impl BoundFunction {
pub(crate) const fn _def() -> Self {
BoundFunction(BoundFunctionIndex::from_u32_index(0))
}
Expand All @@ -51,21 +80,21 @@ impl BoundFunction {
}
}

impl IntoValue for BoundFunction {
impl IntoValue for BoundFunction<'_> {
fn into_value(self) -> Value {
Value::BoundFunction(self)
Value::BoundFunction(self.unbind())
}
}

impl IntoObject for BoundFunction {
impl IntoObject for BoundFunction<'_> {
fn into_object(self) -> Object {
Object::BoundFunction(self)
Object::BoundFunction(self.unbind())
}
}

impl IntoFunction for BoundFunction {
impl IntoFunction for BoundFunction<'_> {
fn into_function(self) -> Function {
Function::BoundFunction(self)
Function::BoundFunction(self.unbind())
}
}

Expand All @@ -76,13 +105,13 @@ impl IntoFunction for BoundFunction {
/// boundArgs (a List of ECMAScript language values) and returns either a
/// normal completion containing a function object or a throw completion. It is
/// used to specify the creation of new bound function exotic objects.
pub(crate) fn bound_function_create(
pub(crate) fn bound_function_create<'a>(
agent: &mut Agent,
target_function: Function,
bound_this: Value,
bound_args: &[Value],
mut gc: GcScope<'_, '_>,
) -> JsResult<BoundFunction> {
mut gc: GcScope<'a, '_>,
) -> JsResult<BoundFunction<'a>> {
// 1. Let proto be ? targetFunction.[[GetPrototypeOf]]().
let proto = target_function.internal_get_prototype_of(agent, gc.reborrow())?;
// 2. Let internalSlotsList be the list-concatenation of « [[Prototype]],
Expand Down Expand Up @@ -119,7 +148,7 @@ pub(crate) fn bound_function_create(
Ok(obj)
}

impl FunctionInternalProperties for BoundFunction {
impl FunctionInternalProperties for BoundFunction<'_> {
fn get_name(self, agent: &Agent) -> String<'static> {
agent[self].name.unwrap_or(String::EMPTY_STRING)
}
Expand All @@ -129,7 +158,7 @@ impl FunctionInternalProperties for BoundFunction {
}
}

impl InternalSlots for BoundFunction {
impl InternalSlots for BoundFunction<'_> {
const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::Function;

#[inline(always)]
Expand All @@ -149,7 +178,7 @@ impl InternalSlots for BoundFunction {
}
}

impl InternalMethods for BoundFunction {
impl InternalMethods for BoundFunction<'_> {
fn try_get_own_property(
self,
agent: &mut Agent,
Expand Down Expand Up @@ -342,48 +371,71 @@ impl InternalMethods for BoundFunction {
}
}

impl Index<BoundFunction> for Agent {
impl<'a> Index<BoundFunction<'a>> for Agent {
type Output = BoundFunctionHeapData;

fn index(&self, index: BoundFunction) -> &Self::Output {
fn index(&self, index: BoundFunction<'a>) -> &Self::Output {
&self.heap.bound_functions[index]
}
}

impl IndexMut<BoundFunction> for Agent {
fn index_mut(&mut self, index: BoundFunction) -> &mut Self::Output {
impl<'a> IndexMut<BoundFunction<'a>> for Agent {
fn index_mut(&mut self, index: BoundFunction<'a>) -> &mut Self::Output {
&mut self.heap.bound_functions[index]
}
}

impl Index<BoundFunction> for Vec<Option<BoundFunctionHeapData>> {
impl<'a> Index<BoundFunction<'a>> for Vec<Option<BoundFunctionHeapData>> {
type Output = BoundFunctionHeapData;

fn index(&self, index: BoundFunction) -> &Self::Output {
fn index(&self, index: BoundFunction<'a>) -> &Self::Output {
self.get(index.get_index())
.expect("BoundFunction out of bounds")
.as_ref()
.expect("BoundFunction slot empty")
}
}

impl IndexMut<BoundFunction> for Vec<Option<BoundFunctionHeapData>> {
fn index_mut(&mut self, index: BoundFunction) -> &mut Self::Output {
impl<'a> IndexMut<BoundFunction<'a>> for Vec<Option<BoundFunctionHeapData>> {
fn index_mut(&mut self, index: BoundFunction<'a>) -> &mut Self::Output {
self.get_mut(index.get_index())
.expect("BoundFunction out of bounds")
.as_mut()
.expect("BoundFunction slot empty")
}
}

impl CreateHeapData<BoundFunctionHeapData, BoundFunction> for Heap {
fn create(&mut self, data: BoundFunctionHeapData) -> BoundFunction {
impl CreateHeapData<BoundFunctionHeapData, BoundFunction<'static>> for Heap {
fn create(&mut self, data: BoundFunctionHeapData) -> BoundFunction<'static> {
self.bound_functions.push(Some(data));
BoundFunction(BoundFunctionIndex::last(&self.bound_functions))
}
}

impl HeapMarkAndSweep for BoundFunction {
impl Rootable for BoundFunction<'_> {
type RootRepr = HeapRootRef;

fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
Err(HeapRootData::BoundFunction(value.unbind()))
}

fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
Err(*value)
}

fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
heap_ref
}

fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
match heap_data {
HeapRootData::BoundFunction(d) => Some(d),
_ => None,
}
}
}

impl HeapMarkAndSweep for BoundFunction<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.bound_functions.push(*self);
}
Expand Down
Loading

0 comments on commit 5a698d7

Please sign in to comment.