Skip to content
73 changes: 73 additions & 0 deletions nova_vm/src/ecmascript/abstract_operations/type_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
//! The BigInt type has no implicit conversions in the ECMAScript language;
//! programmers must call BigInt explicitly to convert values from other types.

use num_bigint::Sign;

use crate::engine::context::GcScope;
use crate::{
ecmascript::{
Expand Down Expand Up @@ -707,6 +709,77 @@ pub(crate) fn string_to_big_int(_agent: &mut Agent, _argument: String) -> Option
todo!("string_to_big_int: Implement BigInts")
}

/// ### [7.1.15 ToBigInt64 ( argument )](https://tc39.es/ecma262/#sec-tobigint64)
///
/// The abstract operation ToBigInt64 takes argument argument (an ECMAScript
/// language value) and returns either a normal completion containing a BigInt
/// or a throw completion. It converts argument to one of 2**64 BigInt values
/// in the inclusive interval from ℤ(-2**63) to ℤ(2**63 - 1).
#[inline(always)]
pub(crate) fn to_big_int64(
agent: &mut Agent,
gc: GcScope<'_, '_>,
argument: Value,
) -> JsResult<i64> {
// 1. Let n be ? ToBigInt(argument).
let n = to_big_int(agent, gc, argument)?;

// 2. Let int64bit be ℝ(n) modulo 2**64.
match n {
BigInt::BigInt(heap_big_int) => {
// 3. If int64bit ≥ 2**63, return ℤ(int64bit - 2**64); otherwise return ℤ(int64bit).
let big_int = &agent[heap_big_int].data;
let int64bit = big_int.iter_u64_digits().next().unwrap_or(0);
let int64bit = if big_int.sign() == Sign::Minus {
u64::MAX - int64bit + 1
} else {
int64bit
};
let int64bit = i64::from_ne_bytes(int64bit.to_ne_bytes());
Ok(int64bit)
}
BigInt::SmallBigInt(small_big_int) => {
let int64bit = small_big_int.into_i64();
Ok(int64bit)
}
}
}

/// ### [7.1.16 ToBigUint64 ( argument )](https://tc39.es/ecma262/#sec-tobiguint64)
///
/// The abstract operation ToBigUint64 takes argument argument (an ECMAScript
/// language value) and returns either a normal completion containing a BigInt
/// or a throw completion. It converts argument to one of 2**64 BigInt values
/// in the inclusive interval from 0ℤ to ℤ(2**64 - 1).
#[inline(always)]
pub(crate) fn to_big_uint64(
agent: &mut Agent,
gc: GcScope<'_, '_>,
argument: Value,
) -> JsResult<u64> {
// 1. Let n be ? ToBigInt(argument).
let n = to_big_int(agent, gc, argument)?;

// 2. Let int64bit be ℝ(n) modulo 2**64.
match n {
BigInt::BigInt(heap_big_int) => {
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7d82adfe85f7d0ed44ab37a7b2cdf092
let big_int = &agent[heap_big_int].data;
let int64bit = big_int.iter_u64_digits().next().unwrap_or(0);
let int64bit = if big_int.sign() == Sign::Minus {
u64::MAX - int64bit + 1
} else {
int64bit
};
Ok(int64bit)
}
BigInt::SmallBigInt(small_big_int) => {
let int64bit = small_big_int.into_i64();
Ok(int64bit as u64)
}
}
}

/// ### [7.1.17 ToString ( argument )](https://tc39.es/ecma262/#sec-tostring)
pub(crate) fn to_string(
agent: &mut Agent,
Expand Down
4 changes: 2 additions & 2 deletions nova_vm/src/ecmascript/builtins/array_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ use crate::{
};

pub(crate) use abstract_operations::{
allocate_array_buffer, array_buffer_byte_length, is_detached_buffer,
is_fixed_length_array_buffer, Ordering,
allocate_array_buffer, array_buffer_byte_length, get_value_from_buffer, is_detached_buffer,
is_fixed_length_array_buffer, set_value_in_buffer, Ordering,
};
pub use data::ArrayBufferHeapData;
use std::ops::{Index, IndexMut};
Expand Down
95 changes: 74 additions & 21 deletions nova_vm/src/ecmascript/builtins/array_buffer/abstract_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use super::{ArrayBuffer, ArrayBufferHeapData};
use crate::ecmascript::types::Viewable;
use crate::engine::context::GcScope;
use crate::{
ecmascript::{
Expand Down Expand Up @@ -314,7 +315,11 @@ pub(crate) const fn is_no_tear_configuration(r#type: (), order: Ordering) -> boo
/// The abstract operation RawBytesToNumeric takes arguments type (a
/// TypedArray element type), rawBytes (a List of byte values), and
/// isLittleEndian (a Boolean) and returns a Number or a BigInt.
pub(crate) const fn raw_bytes_to_numeric(_type: (), _raw_bytes: &[u8], _is_little_endian: bool) {
pub(crate) fn raw_bytes_to_numeric<T: Viewable>(
agent: &mut Agent,
raw_bytes: T,
is_little_endian: bool,
) -> Value {
// 1. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
// 2. If isLittleEndian is false, reverse the order of the elements of rawBytes.
// 3. If type is FLOAT32, then
Expand All @@ -331,6 +336,11 @@ pub(crate) const fn raw_bytes_to_numeric(_type: (), _raw_bytes: &[u8], _is_littl
// a. Let intValue be the byte elements of rawBytes concatenated and interpreted as a bit string encoding of a binary little-endian two's complement number of bit length elementSize × 8.
// 7. If IsBigIntElementType(type) is true, return the BigInt value that corresponds to intValue.
// 8. Otherwise, return the Number value that corresponds to intValue.
if is_little_endian {
raw_bytes.into_le_value(agent)
} else {
raw_bytes.into_be_value(agent)
}
}

/// ### [25.1.3.14 GetRawBytesFromSharedBlock ( block, byteIndex, type, isTypedArray, order )](https://tc39.es/ecma262/#sec-getrawbytesfromsharedblock)
Expand Down Expand Up @@ -366,39 +376,57 @@ pub(crate) fn get_raw_bytes_from_shared_block(
/// integer), type (a TypedArray element type), isTypedArray (a Boolean),
/// and order (SEQ-CST or UNORDERED) and optional argument isLittleEndian
/// (a Boolean) and returns a Number or a BigInt.
pub(crate) fn get_value_from_buffer(
_array_buffer: ArrayBuffer,
_byte_index: u32,
_type: (),
pub(crate) fn get_value_from_buffer<T: Viewable>(
agent: &mut Agent,
array_buffer: ArrayBuffer,
byte_index: usize,
_is_typed_array: bool,
_order: Ordering,
_is_little_endian: Option<bool>,
) {
is_little_endian: Option<bool>,
) -> Value {
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
debug_assert!(!array_buffer.is_detached(agent));
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
// 3. Let block be arrayBuffer.[[ArrayBufferData]].
// 4. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
// 3. Let block be arrayBuffer.[[ArrayBufferData]].
let block = agent[array_buffer].get_data_block();
// 5. If IsSharedArrayBuffer(arrayBuffer) is true, then
// a. Assert: block is a Shared Data Block.
// b. Let rawValue be GetRawBytesFromSharedBlock(block, byteIndex, type, isTypedArray, order).
// 6. Else,
// a. Let rawValue be a List whose elements are bytes from block at indices in the interval from byteIndex (inclusive) to byteIndex + elementSize (exclusive).
// 7. Assert: The number of elements in rawValue is elementSize.
// 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
let is_little_endian = is_little_endian.unwrap_or({
#[cfg(target_endian = "little")]
{
true
}
#[cfg(target_endian = "big")]
{
false
}
});

// 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian).
raw_bytes_to_numeric::<T>(
agent,
block.get_offset_by_byte::<T>(byte_index).unwrap(),
is_little_endian,
)
}

/// ### [25.1.3.16 NumericToRawBytes ( type, value, isLittleEndian )](https://tc39.es/ecma262/#sec-numerictorawbytes)
///
/// The abstract operation NumericToRawBytes takes arguments type (a
/// TypedArray element type), value (a Number or a BigInt), and
/// isLittleEndian (a Boolean) and returns a List of byte values.
pub(crate) fn numeric_to_raw_bytes(
_array_buffer: ArrayBuffer,
_type: (),
_value: Number,
_is_little_endian: bool,
) {
pub(crate) fn numeric_to_raw_bytes<T: Viewable>(
agent: &mut Agent,
gc: GcScope<'_, '_>,
value: Value,
is_little_endian: bool,
) -> T {
// 1. If type is FLOAT32, then
// a. Let rawBytes be a List whose elements are the 4 bytes that are the result of converting value to IEEE 754-2019 binary32 format using roundTiesToEven mode. The bytes are arranged in little endian order. If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.
// 2. Else if type is FLOAT64, then
Expand All @@ -413,6 +441,11 @@ pub(crate) fn numeric_to_raw_bytes(
// i. Let rawBytes be a List whose elements are the n-byte binary two's complement encoding of intValue. The bytes are ordered in little endian order.
// 4. If isLittleEndian is false, reverse the order of the elements of rawBytes.
// 5. Return rawBytes.
if is_little_endian {
T::from_le_value(agent, gc, value)
} else {
T::from_be_value(agent, gc, value)
}
}

/// ### [25.1.3.17 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order \[ , isLittleEndian \] )](https://tc39.es/ecma262/#sec-setvalueinbuffer)
Expand All @@ -422,29 +455,49 @@ pub(crate) fn numeric_to_raw_bytes(
/// type (a TypedArray element type), value (a Number or a BigInt),
/// isTypedArray (a Boolean), and order (SEQ-CST, UNORDERED, or INIT) and
/// optional argument isLittleEndian (a Boolean) and returns UNUSED.
pub(crate) fn set_value_in_buffer(
_array_buffer: ArrayBuffer,
_byte_index: u32,
_type: (),
_value: Value,
#[allow(clippy::too_many_arguments)]
pub(crate) fn set_value_in_buffer<T: Viewable>(
agent: &mut Agent,
gc: GcScope<'_, '_>,
array_buffer: ArrayBuffer,
byte_index: usize,
value: Value,
_is_typed_array: bool,
_order: Ordering,
_is_little_endian: Option<bool>,
is_little_endian: Option<bool>,
) {
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
debug_assert!(!array_buffer.is_detached(agent));
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
// 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number.
// 4. Let block be arrayBuffer.[[ArrayBufferData]].

// 5. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
// 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
let is_little_endian = is_little_endian.unwrap_or({
#[cfg(target_endian = "little")]
{
true
}
#[cfg(target_endian = "big")]
{
false
}
});

// 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
let raw_bytes = numeric_to_raw_bytes::<T>(agent, gc, value, is_little_endian);
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
// a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
// b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
// c. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false.
// d. Append WriteSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize, [[Payload]]: rawBytes } to eventsRecord.[[EventList]].
// 9. Else,

// 4. Let block be arrayBuffer.[[ArrayBufferData]].
let block = agent[array_buffer].get_data_block_mut();

// a. Store the individual bytes of rawBytes into block, starting at block[byteIndex].
block.set_offset_by_byte::<T>(byte_index, raw_bytes);
// 10. Return UNUSED.
}

Expand Down
Loading