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

Add tests for no-tear requirement for integer typed arrays #4369

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
154 changes: 154 additions & 0 deletions harness/tearing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
description: >
Helper function for no-tear tests.
defines:
- testNoTear
---*/

/**
* @param TypedArray An integer TypedArray
* @param length Number of elements
* @param test Function to modify the array
* @param setup Optional setup function
*/
function testNoTear(
TypedArray,
length,
test,
setup = undefined,
) {
assert.sameValue(typeof TypedArray, "function");
assert.sameValue(typeof length, "number");
assert.sameValue(typeof test, "function");
if (setup !== undefined) {
assert.sameValue(typeof setup, "function");
}

assert(
TypedArray.BYTES_PER_ELEMENT === 2 ||
TypedArray.BYTES_PER_ELEMENT === 4
);

// Number of loop iterations in no-tear tests.
//
// Larger numbers increase the likelihood that teared writes can be observed
// in non-conformant implementations, but also increase the test duration for
// conformant implementations.
const LOOP_COUNT = 10000;

// Length of the synchronization Int32 TypedArray.
const SYNC_LENGTH = 2;

// Accounting of live agents.
const RUNNING_INDEX = 0;

// Index all agents are waiting on.
const WAIT_INDEX = 1;

// Number of agents to perform concurrent writes.
const NUM_AGENTS = 2;

// Constructor to create the TypedArray object which will be tested.
const TYPED_ARRAY = TypedArray;

// Length of the TypedArray object.
const LENGTH = length;

// Start offset of the TypedArray object within the SharedArrayBuffer. The
// offset shouldn't be word-aligned to make it more likely that non-conformant
// implementations perform tearing, so we add `TYPED_ARRAY.BYTES_PER_ELEMENT`.
const OFFSET = SYNC_LENGTH * Int32Array.BYTES_PER_ELEMENT +
TYPED_ARRAY.BYTES_PER_ELEMENT;

// Total byte length of the SharedArrayBuffer.
const SAB_BYTE_LENGTH = OFFSET + LENGTH * TYPED_ARRAY.BYTES_PER_ELEMENT;

// Byte pattern which will be written into the TypedArray.
const BYTE_PATTERN = TypedArray.BYTES_PER_ELEMENT === 2 ? 0x1111 : 0x1111_1111;

// SharedArrayBuffer passed to agents.
const sab = new SharedArrayBuffer(SAB_BYTE_LENGTH);

// Int32 TypedArray used for cross-agent synchronization.
const sync = new Int32Array(sab, 0, SYNC_LENGTH);

// TypedArray used for testing.
const ta = new TYPED_ARRAY(sab, OFFSET, LENGTH);

// Call the optional setup function, if present.
if (setup !== undefined) {
setup(ta);
}

// Create a new agent.
function createAgent(value) {
// Template function for the agent.
function agent(sab) {
let sync = new Int32Array(sab, 0, SYNC_LENGTH);

// Notify agent has started.
Atomics.add(sync, RUNNING_INDEX, 1);

// Wait until all agents have started.
Atomics.wait(sync, WAIT_INDEX, 0);

// Repeatedly write into the buffer. Uses Atomics.store to ensure JIT
// compilers won't move the store before the loop.
let ta = new TYPED_ARRAY(sab, OFFSET, LENGTH);
for (let i = 0; i < LOOP_COUNT; ++i) {
for (let j = 0; j < LENGTH; ++j) {
Atomics.store(ta, j, VALUE);
}
}

$262.agent.leaving();
}

// Replace all constants in the template.
let source = agent.toString()
.replaceAll("SYNC_LENGTH", SYNC_LENGTH)
.replaceAll("RUNNING_INDEX", RUNNING_INDEX)
.replaceAll("WAIT_INDEX", WAIT_INDEX)
.replaceAll("LOOP_COUNT", LOOP_COUNT)
.replaceAll("TYPED_ARRAY", TYPED_ARRAY.name)
.replaceAll("OFFSET", OFFSET)
.replaceAll("LENGTH", LENGTH)
.replaceAll("VALUE", value);

// Return the agent script source.
return `$262.agent.receiveBroadcast(${source});`;
}

// Create agents.
for (let i = 0; i < NUM_AGENTS; ++i) {
let value = BYTE_PATTERN * (i + 1);
$262.agent.start(createAgent(value));
}

// Broadcast the buffer to all agents.
$262.agent.safeBroadcast(sync);

// Wait until agents are ready.
$262.agent.waitUntil(sync, RUNNING_INDEX, NUM_AGENTS);

// Wake up agents.
let woken = 0;
while ((woken += Atomics.notify(sync, WAIT_INDEX)) < NUM_AGENTS);

// Loop to increase the likelihood of concurrent writes.
for (let i = 0; i < LOOP_COUNT; ++i) {
let r = test(ta);

// Ensure no copied elements were teared.
for (let j = 0; j < LENGTH; ++j) {
let v = r[j];
assert(
v % BYTE_PATTERN === 0,
`i=${i}: r[${j}] = ${v.toString(16)}`
);
}
}
}
86 changes: 86 additions & 0 deletions test/built-ins/TypedArray/prototype/set/no-tear-int16.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-%typedarray%.prototype.set
description: >
No tearing when modifying an Int16Array.
info: |
%TypedArray%.prototype.set ( source [ , offset ] )
...
6. If source is an Object that has a [[TypedArrayName]] internal slot, then
a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source).
...


SetTypedArrayFromTypedArray ( target, targetOffset, source )
...
23. If srcType is targetType, then
...
24. Else,
a. Repeat, while targetByteIndex < limit,
i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, unordered).
ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, unordered).
...


GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )
...
5. If IsSharedArrayBuffer(arrayBuffer) is true, then
...
b. Let rawValue be GetRawBytesFromSharedBlock(block, byteIndex, type,
isTypedArray, order).
...


GetRawBytesFromSharedBlock ( block, byteIndex, type, isTypedArray, order )
...
4. If isTypedArray is true and IsNoTearConfiguration(type, order) is true,
let noTear be true; otherwise let noTear be false.
...
7. Let readEvent be ReadSharedMemory { [[Order]]: order, [[NoTear]]: noTear,
[[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize }.
...


SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )
...
8. If IsSharedArrayBuffer(arrayBuffer) is true, then
...
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]].
...


IsNoTearConfiguration ( type, order )
1. If IsUnclampedIntegerElementType(type) is true, return true.
...


Valid Executions

A candidate execution execution is a valid execution (or simply an execution)
if all of the following are true.
...
- execution has tear free reads.
...

includes: [atomicsHelper.js, tearing.js]
features: [TypedArray, Atomics, SharedArrayBuffer]
---*/

// Tearing was observed in implementations for |length = 1|.
const length = 1;

// Must be a different element type, because the same element case copies the
// contents bytewise.
const source = new Uint16Array(length);

testNoTear(
Int16Array,
length,
ta => { ta.set(source); return ta; }
);
86 changes: 86 additions & 0 deletions test/built-ins/TypedArray/prototype/set/no-tear-int32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-%typedarray%.prototype.set
description: >
No tearing when modifying an Int32Array.
info: |
%TypedArray%.prototype.set ( source [ , offset ] )
...
6. If source is an Object that has a [[TypedArrayName]] internal slot, then
a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source).
...


SetTypedArrayFromTypedArray ( target, targetOffset, source )
...
23. If srcType is targetType, then
...
24. Else,
a. Repeat, while targetByteIndex < limit,
i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, unordered).
ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, unordered).
...


GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )
...
5. If IsSharedArrayBuffer(arrayBuffer) is true, then
...
b. Let rawValue be GetRawBytesFromSharedBlock(block, byteIndex, type,
isTypedArray, order).
...


GetRawBytesFromSharedBlock ( block, byteIndex, type, isTypedArray, order )
...
4. If isTypedArray is true and IsNoTearConfiguration(type, order) is true,
let noTear be true; otherwise let noTear be false.
...
7. Let readEvent be ReadSharedMemory { [[Order]]: order, [[NoTear]]: noTear,
[[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize }.
...


SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )
...
8. If IsSharedArrayBuffer(arrayBuffer) is true, then
...
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]].
...


IsNoTearConfiguration ( type, order )
1. If IsUnclampedIntegerElementType(type) is true, return true.
...


Valid Executions

A candidate execution execution is a valid execution (or simply an execution)
if all of the following are true.
...
- execution has tear free reads.
...

includes: [atomicsHelper.js, tearing.js]
features: [TypedArray, Atomics, SharedArrayBuffer]
---*/

// Tearing was observed in implementations for |length = 1|.
const length = 1;

// Must be a different element type, because the same element case copies the
// contents bytewise.
const source = new Uint32Array(length);

testNoTear(
Int32Array,
length,
ta => { ta.set(source); return ta; }
);
Loading
Loading