Skip to content

Commit 426503f

Browse files
authored
Enable sm test suite and fix ephemeron bug (#4107)
1 parent ce7ea1d commit 426503f

File tree

11 files changed

+237
-121
lines changed

11 files changed

+237
-121
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"program": "${workspaceFolder}/target/debug/boa_tester.exe"
4343
},
4444
"program": "${workspaceFolder}/target/debug/boa_tester",
45-
"args": ["run", "-s", "${input:testPath}", "-vvv"],
45+
"args": ["run", "-s", "${input:testPath}", "-vvv", "-d"],
4646
"sourceLanguages": ["rust"],
4747
"preLaunchTask": "Cargo Build boa_tester"
4848
}

core/engine/src/builtins/array_buffer/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl BufferObject {
158158

159159
/// Gets the mutable buffer data of the object
160160
#[inline]
161+
#[track_caller]
161162
pub(crate) fn as_buffer_mut(
162163
&self,
163164
) -> BufferRefMut<

core/engine/src/builtins/array_buffer/utils.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,19 @@ impl SliceRefMut<'_> {
211211
}
212212
}
213213

214+
/// Gets a subslice of this `SliceRefMut`.
215+
pub(crate) fn subslice<I>(&self, index: I) -> SliceRef<'_>
216+
where
217+
I: SliceIndex<[u8], Output = [u8]> + SliceIndex<[AtomicU8], Output = [AtomicU8]>,
218+
{
219+
match self {
220+
Self::Slice(buffer) => SliceRef::Slice(buffer.get(index).expect("index out of bounds")),
221+
Self::AtomicSlice(buffer) => {
222+
SliceRef::AtomicSlice(buffer.get(index).expect("index out of bounds"))
223+
}
224+
}
225+
}
226+
214227
/// Gets a mutable subslice of this `SliceRefMut`.
215228
pub(crate) fn subslice_mut<I>(&mut self, index: I) -> SliceRefMut<'_>
216229
where
@@ -403,6 +416,34 @@ pub(crate) unsafe fn memcpy(src: BytesConstPtr, dest: BytesMutPtr, count: usize)
403416
}
404417
}
405418

419+
/// Copies `count` bytes from the position `from` to the position `to` in `buffer`, but always
420+
/// copying from left to right.
421+
///
422+
///
423+
/// # Safety
424+
///
425+
/// - `ptr` must be valid from the offset `ptr + from` for `count` reads of bytes.
426+
/// - `ptr` must be valid from the offset `ptr + to` for `count` writes of bytes.
427+
// This looks like a worse version of `memmove`... and it is exactly that...
428+
// but it's the correct behaviour for a weird usage of `%TypedArray%.prototype.slice` so ¯\_(ツ)_/¯.
429+
// Obviously don't use this if you need to implement something that requires a "proper" memmove.
430+
pub(crate) unsafe fn memmove_naive(ptr: BytesMutPtr, from: usize, to: usize, count: usize) {
431+
match ptr {
432+
// SAFETY: The invariants of this operation are ensured by the caller of the function.
433+
BytesMutPtr::Bytes(ptr) => unsafe {
434+
for i in 0..count {
435+
ptr::copy(ptr.add(from + i), ptr.add(to + i), 1);
436+
}
437+
},
438+
// SAFETY: The invariants of this operation are ensured by the caller of the function.
439+
BytesMutPtr::AtomicBytes(ptr) => unsafe {
440+
let src = ptr.add(from);
441+
let dest = ptr.add(to);
442+
copy_shared_to_shared(src, dest, count);
443+
},
444+
}
445+
}
446+
406447
/// Copies `count` bytes from the position `from` to the position `to` in `buffer`.
407448
///
408449
/// # Safety

core/engine/src/builtins/typed_array/builtin.rs

Lines changed: 80 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use num_traits::Zero;
99
use super::{
1010
object::typed_array_set_element, ContentType, TypedArray, TypedArrayKind, TypedArrayMarker,
1111
};
12-
use crate::value::JsVariant;
12+
use crate::{builtins::array_buffer::utils::memmove_naive, value::JsVariant};
1313
use crate::{
1414
builtins::{
1515
array::{find_via_predicate, ArrayIterator, Direction},
@@ -2048,8 +2048,8 @@ impl BuiltinTypedArray {
20482048

20492049
// a. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
20502050
// b. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
2051-
let src_buf_borrow = src_borrow.data.viewed_array_buffer().as_buffer();
2052-
let Some(src_buf) = src_buf_borrow
2051+
let mut src_buf_borrow = src_borrow.data.viewed_array_buffer().as_buffer_mut();
2052+
let Some(mut src_buf) = src_buf_borrow
20532053
.bytes(Ordering::SeqCst)
20542054
.filter(|s| !src_borrow.data.is_out_of_bounds(s.len()))
20552055
else {
@@ -2067,41 +2067,88 @@ impl BuiltinTypedArray {
20672067
// f. Let targetType be TypedArrayElementType(A).
20682068
let target_type = target_borrow.data.kind();
20692069

2070+
if src_type != target_type {
2071+
// h. Else,
2072+
drop(src_buf_borrow);
2073+
drop((src_borrow, target_borrow));
2074+
2075+
// i. Let n be 0.
2076+
// ii. Let k be startIndex.
2077+
// iii. Repeat, while k < endIndex,
2078+
let src = src.upcast();
2079+
let target = target.upcast();
2080+
for (n, k) in (start_index..end_index).enumerate() {
2081+
// 1. Let Pk be ! ToString(𝔽(k)).
2082+
// 2. Let kValue be ! Get(O, Pk).
2083+
let k_value = src.get(k, context).expect("Get cannot fail here");
2084+
2085+
// 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true).
2086+
target
2087+
.set(n, k_value, true, context)
2088+
.expect("Set cannot fail here");
2089+
2090+
// 4. Set k to k + 1.
2091+
// 5. Set n to n + 1.
2092+
}
2093+
2094+
// 15. Return A.
2095+
return Ok(target.into());
2096+
}
2097+
20702098
// g. If srcType is targetType, then
2071-
if src_type == target_type {
2072-
{
2073-
let byte_count = count * src_type.element_size() as usize;
2099+
{
2100+
let byte_count = count * src_type.element_size() as usize;
20742101

2075-
// i. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data.
2076-
// ii. Let srcBuffer be O.[[ViewedArrayBuffer]].
2077-
// iii. Let targetBuffer be A.[[ViewedArrayBuffer]].
2078-
let target_borrow = target_borrow;
2079-
let mut target_buf = target_borrow.data.viewed_array_buffer().as_buffer_mut();
2080-
let mut target_buf = target_buf
2081-
.bytes_with_len(byte_count)
2082-
.expect("newly created array cannot be detached");
2102+
// i. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data.
2103+
// ii. Let srcBuffer be O.[[ViewedArrayBuffer]].
2104+
// iii. Let targetBuffer be A.[[ViewedArrayBuffer]].
2105+
2106+
// iv. Let elementSize be TypedArrayElementSize(O).
2107+
let element_size = src_type.element_size();
20832108

2084-
// iv. Let elementSize be TypedArrayElementSize(O).
2085-
let element_size = src_type.element_size();
2109+
// v. Let srcByteOffset be O.[[ByteOffset]].
2110+
let src_byte_offset = src_borrow.data.byte_offset();
20862111

2087-
// v. Let srcByteOffset be O.[[ByteOffset]].
2088-
let src_byte_offset = src_borrow.data.byte_offset();
2112+
// vi. Let srcByteIndex be (startIndex × elementSize) + srcByteOffset.
2113+
let src_byte_index = (start_index * element_size + src_byte_offset) as usize;
20892114

2090-
// vi. Let srcByteIndex be (startIndex × elementSize) + srcByteOffset.
2091-
let src_byte_index = (start_index * element_size + src_byte_offset) as usize;
2115+
// vii. Let targetByteIndex be A.[[ByteOffset]].
2116+
let target_byte_index = target_borrow.data.byte_offset() as usize;
20922117

2093-
// vii. Let targetByteIndex be A.[[ByteOffset]].
2094-
let target_byte_index = target_borrow.data.byte_offset() as usize;
2118+
// viii. Let endByteIndex be targetByteIndex + (countBytes × elementSize).
2119+
// Not needed by the impl.
20952120

2096-
// viii. Let endByteIndex be targetByteIndex + (countBytes × elementSize).
2097-
// Not needed by the impl.
2121+
// ix. Repeat, while targetByteIndex < endByteIndex,
2122+
// 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, uint8, true, unordered).
2123+
// 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, uint8, value, true, unordered).
2124+
// 3. Set srcByteIndex to srcByteIndex + 1.
2125+
// 4. Set targetByteIndex to targetByteIndex + 1.
2126+
if BufferObject::equals(
2127+
src_borrow.data.viewed_array_buffer(),
2128+
target_borrow.data.viewed_array_buffer(),
2129+
) {
2130+
// cannot borrow the target mutably (overlapping bytes), but we can move the data instead.
20982131

2099-
// ix. Repeat, while targetByteIndex < endByteIndex,
2100-
// 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, uint8, true, unordered).
2101-
// 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, uint8, value, true, unordered).
2102-
// 3. Set srcByteIndex to srcByteIndex + 1.
2103-
// 4. Set targetByteIndex to targetByteIndex + 1.
2132+
#[cfg(debug_assertions)]
2133+
{
2134+
assert!(src_buf.subslice_mut(src_byte_index..).len() >= byte_count);
2135+
assert!(src_buf.subslice_mut(target_byte_index..).len() >= byte_count);
2136+
}
21042137

2138+
// SAFETY: All previous checks put the copied bytes at least within the bounds of `src_buf`.
2139+
unsafe {
2140+
memmove_naive(
2141+
src_buf.as_ptr(),
2142+
src_byte_index,
2143+
target_byte_index,
2144+
byte_count,
2145+
);
2146+
}
2147+
} else {
2148+
let mut target_buf = target_borrow.data.viewed_array_buffer().as_buffer_mut();
2149+
let mut target_buf = target_buf
2150+
.bytes(Ordering::SeqCst)
2151+
.expect("newly created array cannot be detached");
21052152
let src = src_buf.subslice(src_byte_index..);
21062153
let mut target = target_buf.subslice_mut(target_byte_index..);
21072154

@@ -2118,36 +2165,11 @@ impl BuiltinTypedArray {
21182165
memcpy(src.as_ptr(), target.as_ptr(), byte_count);
21192166
}
21202167
}
2121-
2122-
// 15. Return A.
2123-
Ok(target.upcast().into())
2124-
} else {
2125-
// h. Else,
2126-
drop(src_buf_borrow);
2127-
drop((src_borrow, target_borrow));
2128-
2129-
// i. Let n be 0.
2130-
// ii. Let k be startIndex.
2131-
// iii. Repeat, while k < endIndex,
2132-
let src = src.upcast();
2133-
let target = target.upcast();
2134-
for (n, k) in (start_index..end_index).enumerate() {
2135-
// 1. Let Pk be ! ToString(𝔽(k)).
2136-
// 2. Let kValue be ! Get(O, Pk).
2137-
let k_value = src.get(k, context).expect("Get cannot fail here");
2138-
2139-
// 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true).
2140-
target
2141-
.set(n, k_value, true, context)
2142-
.expect("Set cannot fail here");
2143-
2144-
// 4. Set k to k + 1.
2145-
// 5. Set n to n + 1.
2146-
}
2147-
2148-
// 15. Return A.
2149-
Ok(target.into())
21502168
}
2169+
2170+
drop(target_borrow);
2171+
// 15. Return A.
2172+
Ok(target.upcast().into())
21512173
}
21522174

21532175
/// `%TypedArray%.prototype.some ( callbackfn [ , thisArg ] )`

core/engine/src/environments/runtime/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ impl Context {
506506
/// # Panics
507507
///
508508
/// Panics if the environment or binding index are out of range.
509+
#[track_caller]
509510
pub(crate) fn get_binding(&mut self, locator: &BindingLocator) -> JsResult<Option<JsValue>> {
510511
match locator.scope() {
511512
BindingLocatorScope::GlobalObject => {

core/gc/src/lib.rs

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -325,23 +325,9 @@ impl Collector {
325325
if node_ref.is_rooted() {
326326
tracer.enqueue(*node);
327327

328-
while let Some(node) = tracer.next() {
329-
// SAFETY: the gc heap object should be alive if there is a root.
330-
let node_ref = unsafe { node.as_ref() };
331-
332-
if !node_ref.header.is_marked() {
333-
node_ref.header.mark();
334-
335-
// SAFETY: if `GcBox::trace_inner()` has been called, then,
336-
// this box must have been deemed as reachable via tracing
337-
// from a root, which by extension means that value has not
338-
// been dropped either.
339-
340-
let trace_fn = node_ref.trace_fn();
341-
342-
// SAFETY: The function pointer is appropriate for this node type because we extract it from it's VTable.
343-
unsafe { trace_fn(node, tracer) }
344-
}
328+
// SAFETY: all nodes must be valid as this phase cannot drop any node.
329+
unsafe {
330+
tracer.trace_until_empty();
345331
}
346332
} else if !node_ref.is_marked() {
347333
strong_dead.push(*node);
@@ -378,14 +364,9 @@ impl Collector {
378364
pending_ephemerons.push(*eph);
379365
}
380366

381-
while let Some(node) = tracer.next() {
382-
// SAFETY: node must be valid as this phase cannot drop any node.
383-
let trace_fn = unsafe { node.as_ref() }.trace_fn();
384-
385-
// SAFETY: The function pointer is appropriate for this node type because we extract it from it's VTable.
386-
unsafe {
387-
trace_fn(node, tracer);
388-
}
367+
// SAFETY: all nodes must be valid as this phase cannot drop any node.
368+
unsafe {
369+
tracer.trace_until_empty();
389370
}
390371
}
391372

@@ -397,14 +378,9 @@ impl Collector {
397378
// SAFETY: The garbage collector ensures that all nodes are valid.
398379
unsafe { node_ref.trace(tracer) };
399380

400-
while let Some(node) = tracer.next() {
401-
// SAFETY: node must be valid as this phase cannot drop any node.
402-
let trace_fn = unsafe { node.as_ref() }.trace_fn();
403-
404-
// SAFETY: The function pointer is appropriate for this node type because we extract it from it's VTable.
405-
unsafe {
406-
trace_fn(node, tracer);
407-
}
381+
// SAFETY: all nodes must be valid as this phase cannot drop any node.
382+
unsafe {
383+
tracer.trace_until_empty();
408384
}
409385
}
410386

@@ -419,14 +395,9 @@ impl Collector {
419395
// SAFETY: the garbage collector ensures `eph_ref` always points to valid data.
420396
let is_key_marked = unsafe { !eph_ref.trace(tracer) };
421397

422-
while let Some(node) = tracer.next() {
423-
// SAFETY: node must be valid as this phase cannot drop any node.
424-
let trace_fn = unsafe { node.as_ref() }.trace_fn();
425-
426-
// SAFETY: The function pointer is appropriate for this node type because we extract it from it's VTable.
427-
unsafe {
428-
trace_fn(node, tracer);
429-
}
398+
// SAFETY: all nodes must be valid as this phase cannot drop any node.
399+
unsafe {
400+
tracer.trace_until_empty();
430401
}
431402

432403
is_key_marked

core/gc/src/test/weak.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use std::{cell::Cell, rc::Rc};
22

33
use super::run_test;
44
use crate::{
5-
force_collect, test::Harness, Ephemeron, Finalize, Gc, GcBox, GcRefCell, Trace, WeakGc,
5+
force_collect, internals::EphemeronBox, test::Harness, Ephemeron, Finalize, Gc, GcBox,
6+
GcRefCell, Trace, WeakGc,
67
};
78

89
#[test]
@@ -290,3 +291,44 @@ fn eph_gc_finalizer() {
290291
assert_eq!(val.inner.get(), 1);
291292
});
292293
}
294+
295+
#[test]
296+
fn eph_strong_self_reference() {
297+
type Inner = GcRefCell<(Option<TestCell>, Option<TestCell>)>;
298+
#[derive(Trace, Finalize, Clone)]
299+
struct TestCell {
300+
inner: Gc<Inner>,
301+
}
302+
run_test(|| {
303+
let root = TestCell {
304+
inner: Gc::new(GcRefCell::new((None, None))),
305+
};
306+
let root_size = size_of::<GcBox<Inner>>();
307+
308+
Harness::assert_exact_bytes_allocated(root_size);
309+
310+
let watched = Gc::new(0);
311+
let watched_size = size_of::<GcBox<i32>>();
312+
313+
{
314+
let eph = Ephemeron::new(&watched, root.clone());
315+
let eph_size = size_of::<EphemeronBox<Gc<i32>, TestCell>>();
316+
317+
root.inner.borrow_mut().0 = Some(root.clone());
318+
root.inner.borrow_mut().1 = Some(root.clone());
319+
320+
force_collect();
321+
322+
assert!(eph.value().is_some());
323+
Harness::assert_exact_bytes_allocated(root_size + eph_size + watched_size);
324+
}
325+
326+
force_collect();
327+
328+
drop(watched);
329+
330+
force_collect();
331+
332+
Harness::assert_exact_bytes_allocated(root_size);
333+
});
334+
}

0 commit comments

Comments
 (0)