Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b37be4a
feat(ecmascript): Implement `DataView` constructor
eliassjogreen Oct 17, 2024
6fe043a
chore: Update expectations
eliassjogreen Oct 17, 2024
e99f07a
chore: update test262 to same as main branch
eliassjogreen Oct 17, 2024
8063b23
chore: fmt
eliassjogreen Oct 17, 2024
e58c5d6
chore: update expectations
eliassjogreen Oct 17, 2024
2785dcd
fix: Move large `byte_offset`s and `byte_length`s into a side-table o…
eliassjogreen Oct 19, 2024
af3ad8e
Merge branch 'main' into feat/ecmascript-data-view-constructor
eliassjogreen Oct 19, 2024
8e060e6
fix: `HeapMarkAndSweep` impl for `DataView`
eliassjogreen Oct 19, 2024
15de646
fix: Bad merge stuff
eliassjogreen Oct 19, 2024
9750404
fix: Use an `if let` instead
eliassjogreen Oct 19, 2024
1249e52
fix: Doc fmt
eliassjogreen Oct 19, 2024
138834c
fix: Doc fmt
eliassjogreen Oct 19, 2024
d4c82cb
fix: nitpicks etc
eliassjogreen Oct 19, 2024
e450d3a
Merge branch 'main' into feat/ecmascript-data-view-constructor
eliassjogreen Oct 20, 2024
2e48888
fix: Merge fmt and clippy
eliassjogreen Oct 20, 2024
7d52b51
Merge branch 'main' into feat/ecmascript-data-view-constructor
eliassjogreen Oct 20, 2024
771eda5
feat: An initial implementation of GC sweeping for associated data vi…
eliassjogreen Oct 20, 2024
a1c26f5
fix: Building with no features
eliassjogreen Oct 20, 2024
d2cf3f4
fix: rewrite `sweep_data_view_side_table_values` in style of `sweep_h…
eliassjogreen Oct 24, 2024
df37e3a
chore: fmt
eliassjogreen Oct 24, 2024
9e6671e
fix: Make `sweep_side_table_values` generic
eliassjogreen Oct 25, 2024
4ce383e
feat: Generic `IntoBaseIndex<T>` trait and `sweep_side_table_values` fn
eliassjogreen Oct 28, 2024
4157348
Merge branch 'main' into feat/ecmascript-data-view-constructor
eliassjogreen Oct 28, 2024
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
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
pub(crate) mod arguments;
mod array;
#[cfg(feature = "array-buffer")]
mod array_buffer;
pub mod array_buffer;
pub mod bound_function;
mod builtin_constructor;
mod builtin_function;
Expand Down
3 changes: 2 additions & 1 deletion nova_vm/src/ecmascript/builtins/array_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use crate::{
};

pub(crate) use abstract_operations::{
allocate_array_buffer, is_detached_buffer, is_fixed_length_array_buffer,
allocate_array_buffer, array_buffer_byte_length, is_detached_buffer,
is_fixed_length_array_buffer, Ordering,
};
pub use data::ArrayBufferHeapData;
use std::ops::{Index, IndexMut};
Expand Down
45 changes: 44 additions & 1 deletion nova_vm/src/ecmascript/builtins/data_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use std::ops::{Index, IndexMut};

use data::{DataViewByteLength, DataViewByteOffset};

use crate::{
ecmascript::{
execution::{Agent, ProtoIntrinsics},
Expand All @@ -16,13 +18,43 @@ use crate::{

use self::data::DataViewHeapData;

use super::ArrayBuffer;

pub(crate) mod abstract_operations;
pub mod data;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct DataView(pub(crate) DataViewIndex);

impl DataView {
#[inline]
pub fn byte_length(self, agent: &Agent) -> Option<usize> {
let byte_length = agent[self].byte_length;
if byte_length == DataViewByteLength::heap() {
Some(*agent.heap.data_view_byte_lengths.get(&self).unwrap())
} else if byte_length == DataViewByteLength::auto() {
None
} else {
Some(byte_length.0 as usize)
}
}

#[inline]
pub fn byte_offset(self, agent: &Agent) -> usize {
let byte_offset = agent[self].byte_offset;
if byte_offset == DataViewByteOffset::heap() {
*agent.heap.data_view_byte_offsets.get(&self).unwrap()
} else {
byte_offset.0 as usize
}
}

#[inline]
pub fn get_viewed_array_buffer(self, agent: &Agent) -> ArrayBuffer {
agent[self].viewed_array_buffer
}

pub(crate) const fn _def() -> Self {
Self(DataViewIndex::from_u32_index(0))
}
Expand Down Expand Up @@ -62,6 +94,17 @@ impl From<DataView> for Object {
}
}

impl TryFrom<Object> for DataView {
type Error = ();

fn try_from(value: Object) -> Result<Self, Self::Error> {
match value {
Object::DataView(data) => Ok(data),
_ => Err(()),
}
}
}

impl Index<DataView> for Agent {
type Output = DataViewHeapData;

Expand Down
145 changes: 145 additions & 0 deletions nova_vm/src/ecmascript/builtins/data_view/abstract_operations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use crate::ecmascript::{
builtins::array_buffer::{array_buffer_byte_length, is_fixed_length_array_buffer, Ordering},
execution::Agent,
};

use super::DataView;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ByteLength(pub usize);

impl ByteLength {
pub fn value(value: usize) -> Self {
Self(value)
}

pub fn detached() -> Self {
Self(usize::MAX)
}

pub fn is_detached(&self) -> bool {
*self == Self::detached()
}
}

/// ### [25.3.1.1 DataView With Buffer Witness Records](https://tc39.es/ecma262/#sec-dataview-with-buffer-witness-records)
///
/// A DataView With Buffer Witness Record is a Record value used to encapsulate
/// a DataView along with a cached byte length of the viewed buffer. It is used
/// to help ensure there is a single shared memory read event of the byte
/// length data block when the viewed buffer is a growable SharedArrayBuffers.
#[derive(Debug, Clone)]
pub(crate) struct DataViewWithBufferWitnessRecord {
/// ### [\[\[Object\]\]](https://tc39.es/ecma262/#table-dataview-with-buffer-witness-record-fields)
object: DataView,
/// ### [\[\[CachedBufferByteLength\]\]](https://tc39.es/ecma262/#table-dataview-with-buffer-witness-record-fields)
cached_buffer_byte_length: ByteLength,
}

/// [25.3.1.2 MakeDataViewWithBufferWitnessRecord ( obj, order )](https://tc39.es/ecma262/#sec-makedataviewwithbufferwitnessrecord)
///
/// The abstract operation MakeDataViewWithBufferWitnessRecord takes arguments
/// obj (a DataView) and order (seq-cst or unordered) and returns a DataView
/// With Buffer Witness Record.
pub(crate) fn make_data_view_with_buffer_witness_record(
agent: &Agent,
obj: DataView,
order: Ordering,
) -> DataViewWithBufferWitnessRecord {
let buffer = obj.get_viewed_array_buffer(agent);
let byte_length = if buffer.is_detached(agent) {
ByteLength::detached()
} else {
ByteLength::value(array_buffer_byte_length(agent, buffer, order) as usize)
};
DataViewWithBufferWitnessRecord {
object: obj,
cached_buffer_byte_length: byte_length,
}
}

/// [25.3.1.3 GetViewByteLength ( viewRecord )](https://tc39.es/ecma262/#sec-getviewbytelength)
///
/// The abstract operation GetViewByteLength takes argument viewRecord
/// (a DataView With Buffer Witness Record) and returns a non-negative integer.
pub(crate) fn get_view_byte_length(
agent: &Agent,
view_record: &DataViewWithBufferWitnessRecord,
) -> i64 {
// 1. Assert: IsViewOutOfBounds(viewRecord) is false.
assert!(!is_view_out_of_bounds(agent, view_record));

// 2. Let view be viewRecord.[[Object]].
let view = view_record.object;

// 3. If view.[[ByteLength]] is not auto, return view.[[ByteLength]].
if let Some(byte_length) = view.byte_length(agent) {
return byte_length as i64;
}

// NOTE: This assert seems to not be guarding anything important, so it's
// debug only. See https://github.com/trynova/nova/pull/447#discussion_r1805708906
// 4. Assert: IsFixedLengthArrayBuffer(view.[[ViewedArrayBuffer]]) is false.
debug_assert!(!is_fixed_length_array_buffer(
agent,
view.get_viewed_array_buffer(agent)
));

// 5. Let byteOffset be view.[[ByteOffset]].
let byte_offset = view.byte_offset(agent);

// 6. Let byteLength be viewRecord.[[CachedBufferByteLength]].
// 7. Assert: byteLength is not detached.
assert!(!view_record.cached_buffer_byte_length.is_detached());
let byte_length = view_record.cached_buffer_byte_length.0;

// 8. Return byteLength - byteOffset.
(byte_length - byte_offset) as i64
}

/// [25.3.1.4 IsViewOutOfBounds ( viewRecord )](https://tc39.es/ecma262/#sec-isviewoutofbounds)
///
/// The abstract operation IsViewOutOfBounds takes argument viewRecord
/// (a DataView With Buffer Witness Record) and returns a Boolean.
pub(crate) fn is_view_out_of_bounds(
agent: &Agent,
view_record: &DataViewWithBufferWitnessRecord,
) -> bool {
// 1. Let view be viewRecord.[[Object]].
let view = view_record.object;
let ab = view.get_viewed_array_buffer(agent);

// 2. Let bufferByteLength be viewRecord.[[CachedBufferByteLength]].
let buffer_byte_length = view_record.cached_buffer_byte_length;

// 3. Assert: IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached.
assert!(ab.is_detached(agent) == buffer_byte_length.is_detached());

// 4. If bufferByteLength is detached, return true.
if buffer_byte_length.is_detached() {
return true;
}
let buffer_byte_length = buffer_byte_length.0;

// 5. Let byteOffsetStart be view.[[ByteOffset]].
let byte_offset_start = view.byte_offset(agent);

// 6. If view.[[ByteLength]] is auto, then
let byte_offset_end = if let Some(byte_length) = view.byte_length(agent) {
// 7. Else,
// a. Let byteOffsetEnd be byteOffsetStart + view.[[ByteLength]].
byte_offset_start + byte_length
} else {
// a. Let byteOffsetEnd be bufferByteLength.
buffer_byte_length
};

// 8. If byteOffsetStart > bufferByteLength or byteOffsetEnd > bufferByteLength, return true.
if byte_offset_start > buffer_byte_length || byte_offset_end > buffer_byte_length {
return true;
}

// 9. NOTE: 0-length DataViews are not considered out-of-bounds.
// 10. Return false.
false
}
134 changes: 132 additions & 2 deletions nova_vm/src/ecmascript/builtins/data_view/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,151 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use crate::{
ecmascript::types::OrdinaryObject,
ecmascript::{builtins::ArrayBuffer, types::OrdinaryObject},
heap::{CompactionLists, HeapMarkAndSweep, WorkQueues},
};

#[derive(Debug, Clone, Default)]
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) struct DataViewByteLength(pub u32);

impl DataViewByteLength {
pub fn value(value: u32) -> Self {
Self(value)
}

/// A sentinel value of `u32::MAX - 1` means that the byte length is stored in an
/// associated map in the heap. This will most likely be a very rare case,
/// only applicable for 4GB+ buffers.
pub fn heap() -> Self {
Self(u32::MAX - 1)
}

/// A sentinel value of `u32::MAX` means that the byte length is the
/// `AUTO` value used in the spec.
pub fn auto() -> Self {
Self(u32::MAX)
}
}

impl Default for DataViewByteLength {
fn default() -> Self {
Self::auto()
}
}

impl From<Option<usize>> for DataViewByteLength {
fn from(value: Option<usize>) -> Self {
match value {
Some(value) => {
if value >= Self::heap().0 as usize {
Self::heap()
} else {
Self::value(value as u32)
}
}
None => Self::auto(),
}
}
}

impl HeapMarkAndSweep for DataViewByteLength {
fn mark_values(&self, _queues: &mut WorkQueues) {
if *self == Self::heap() {
todo!()
}
}

fn sweep_values(&mut self, _compactions: &CompactionLists) {
if *self == Self::heap() {
todo!()
}
}
}

#[repr(transparent)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) struct DataViewByteOffset(pub u32);

impl DataViewByteOffset {
pub fn value(value: u32) -> Self {
Self(value)
}

/// A sentinel value of `u32::MAX` means that the byte offset is stored in
/// an associated map in the heap. This will most likely be a very rare
/// case, only applicable for 4GB+ buffers.
pub fn heap() -> Self {
Self(u32::MAX)
}
}

impl Default for DataViewByteOffset {
fn default() -> Self {
Self::value(0)
}
}

impl From<usize> for DataViewByteOffset {
fn from(value: usize) -> Self {
if value >= Self::heap().0 as usize {
Self::heap()
} else {
Self::value(value as u32)
}
}
}

impl HeapMarkAndSweep for DataViewByteOffset {
fn mark_values(&self, _queues: &mut WorkQueues) {
if *self == Self::heap() {
todo!()
}
}

fn sweep_values(&mut self, _compactions: &CompactionLists) {
if *self == Self::heap() {
todo!()
}
}
}

#[derive(Debug, Clone)]
pub struct DataViewHeapData {
pub(crate) object_index: Option<OrdinaryObject>,
// TODO: Add a helper function for a u32::MAX value which signifies an a under-construction value:
// See https://github.com/trynova/nova/pull/447#discussion_r1806247107 for reference.
/// ### [\[\[ViewedArrayBuffer\]\]](https://tc39.es/ecma262/#sec-properties-of-dataview-instances)
pub(crate) viewed_array_buffer: ArrayBuffer,
/// ### [\[\[ByteLength\]\]](https://tc39.es/ecma262/#sec-properties-of-dataview-instances)
pub(crate) byte_length: DataViewByteLength,
/// ### [\[\[ByteOffset\]\]](https://tc39.es/ecma262/#sec-properties-of-dataview-instances)
pub(crate) byte_offset: DataViewByteOffset,
}

impl Default for DataViewHeapData {
fn default() -> Self {
Self {
object_index: None,
viewed_array_buffer: ArrayBuffer::_def(),
byte_length: DataViewByteLength::default(),
byte_offset: DataViewByteOffset::default(),
}
}
}

impl HeapMarkAndSweep for DataViewHeapData {
fn mark_values(&self, queues: &mut WorkQueues) {
self.object_index.mark_values(queues);
self.viewed_array_buffer.mark_values(queues);
self.byte_length.mark_values(queues);
self.byte_offset.mark_values(queues);
}

fn sweep_values(&mut self, compactions: &CompactionLists) {
self.object_index.sweep_values(compactions);
self.viewed_array_buffer.sweep_values(compactions);
self.byte_length.sweep_values(compactions);
self.byte_offset.sweep_values(compactions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl ArrayBufferConstructor {
let Some(new_target) = new_target else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Constructor ArrayBuffe requires 'new'",
"Constructor ArrayBuffer requires 'new'",
));
};
// 2. Let byteLength be ? ToIndex(length).
Expand Down
Loading