Commit d782fd1d authored by Marja Hölttä's avatar Marja Hölttä Committed by V8 LUCI CQ

[rab/gsab] RAB/GSAB support for TA.p.sort

Bug: v8:11111
Change-Id: Id6eafbd3a70cd8edd552d06942517ffaf413f568
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3497815Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79494}
parent a9d185b4
......@@ -183,9 +183,7 @@ BUILTIN(TypedArrayPrototypeFill) {
}
if (V8_UNLIKELY(array->IsVariableLength())) {
bool out_of_bounds = false;
array->GetLengthOrOutOfBounds(out_of_bounds);
if (out_of_bounds) {
if (array->IsOutOfBounds()) {
const MessageTemplate message = MessageTemplate::kDetachedOperation;
Handle<String> operation =
isolate->factory()->NewStringFromAsciiChecked(method_name);
......@@ -259,12 +257,8 @@ BUILTIN(TypedArrayPrototypeIndexOf) {
if (V8_UNLIKELY(array->WasDetached())) return Smi::FromInt(-1);
if (V8_UNLIKELY(array->IsVariableLength())) {
bool out_of_bounds = false;
array->GetLengthOrOutOfBounds(out_of_bounds);
if (out_of_bounds) {
return Smi::FromInt(-1);
}
if (V8_UNLIKELY(array->IsVariableLength() && array->IsOutOfBounds())) {
return Smi::FromInt(-1);
}
Handle<Object> search_element = args.atOrUndefined(isolate, 1);
......@@ -300,12 +294,8 @@ BUILTIN(TypedArrayPrototypeLastIndexOf) {
if (index < 0) return Smi::FromInt(-1);
if (V8_UNLIKELY(array->WasDetached())) return Smi::FromInt(-1);
if (V8_UNLIKELY(array->IsVariableLength())) {
bool out_of_bounds = false;
array->GetLengthOrOutOfBounds(out_of_bounds);
if (out_of_bounds) {
return Smi::FromInt(-1);
}
if (V8_UNLIKELY(array->IsVariableLength() && array->IsOutOfBounds())) {
return Smi::FromInt(-1);
}
Handle<Object> search_element = args.atOrUndefined(isolate, 1);
......
......@@ -16,14 +16,24 @@ transitioning macro CallCompare(
const v: Number = ToNumber_Inline(Call(context, comparefn, Undefined, a, b));
// b. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if (IsDetachedBuffer(array.buffer)) {
// c. Let getBufferByteLength be
// MakeIdempotentArrayBufferByteLengthGetter(SeqCst).
// d. If IsIntegerIndexedObjectOutOfBounds(obj, getBufferByteLength) is true,
// throw a TypeError exception.
// TODO(v8:11111): Update this, depending on how
// https://github.com/tc39/ecma262/pull/2646#issuecomment-1067456576 gets
// resolved.
try {
LoadJSTypedArrayLengthAndCheckDetached(array)
otherwise DetachedOrOutOfBounds;
} label DetachedOrOutOfBounds {
ThrowTypeError(MessageTemplate::kDetachedOperation, kBuiltinNameSort);
}
// c. If v is NaN, return +0.
// e. If v is NaN, return +0.
if (NumberIsNaN(v)) return 0;
// d. return v.
// f. return v.
return v;
}
......@@ -99,12 +109,10 @@ transitioning javascript builtin TypedArrayPrototypeSort(
const obj: JSAny = receiver;
// 3. Let buffer be ? ValidateTypedArray(obj).
// ValidateTypedArray currently returns the array, not the ViewBuffer.
const array: JSTypedArray =
ValidateTypedArray(context, obj, kBuiltinNameSort);
// 4. Let len be obj.[[ArrayLength]].
const len: uintptr = array.length;
// 4. Let len be IntegerIndexedObjectLength(obj).
let len: uintptr =
ValidateTypedArrayAndGetLength(context, obj, kBuiltinNameSort);
const array: JSTypedArray = UnsafeCast<JSTypedArray>(obj);
// Arrays of length 1 or less are considered sorted.
if (len < 2) return array;
......@@ -141,6 +149,19 @@ transitioning javascript builtin TypedArrayPrototypeSort(
TypedArrayMergeSort(work2, 0, len, work1, array, comparefn);
// Reload the length; it's possible the backing ArrayBuffer has been resized.
// It cannot be OOB here though, since we've checked it as part of the
// comparison function.
// TODO(v8:11111): Update this, depending on how
// https://github.com/tc39/ecma262/pull/2646#issuecomment-1067456576 gets
// resolved.
const newLen =
LoadJSTypedArrayLengthAndCheckDetached(array) otherwise unreachable;
if (newLen < len) {
len = newLen;
}
// work1 contains the sorted numbers. Write them back.
for (let i: uintptr = 0; i < len; ++i) {
accessor.StoreNumeric(
......
......@@ -3471,11 +3471,7 @@ class TypedElementsAccessor
JSTypedArray typed_array = JSTypedArray::cast(*receiver);
DCHECK(!typed_array.WasDetached());
#if DEBUG
bool out_of_bounds = false;
typed_array.GetLengthOrOutOfBounds(out_of_bounds);
DCHECK(!out_of_bounds);
#endif
DCHECK(!typed_array.IsOutOfBounds());
ElementType typed_search_value;
......
......@@ -235,6 +235,16 @@ size_t JSTypedArray::GetLength() const {
return GetLengthOrOutOfBounds(out_of_bounds);
}
size_t JSTypedArray::GetByteLength() const {
return GetLength() * element_size();
}
bool JSTypedArray::IsOutOfBounds() const {
bool out_of_bounds = false;
GetLengthOrOutOfBounds(out_of_bounds);
return out_of_bounds;
}
size_t JSTypedArray::length() const {
DCHECK(!is_length_tracking());
DCHECK(!is_backed_by_rab());
......@@ -347,15 +357,11 @@ MaybeHandle<JSTypedArray> JSTypedArray::Validate(Isolate* isolate,
THROW_NEW_ERROR(isolate, NewTypeError(message, operation), JSTypedArray);
}
if (V8_UNLIKELY(array->IsVariableLength())) {
bool out_of_bounds = false;
array->GetLengthOrOutOfBounds(out_of_bounds);
if (out_of_bounds) {
const MessageTemplate message = MessageTemplate::kDetachedOperation;
Handle<String> operation =
isolate->factory()->NewStringFromAsciiChecked(method_name);
THROW_NEW_ERROR(isolate, NewTypeError(message, operation), JSTypedArray);
}
if (V8_UNLIKELY(array->IsVariableLength() && array->IsOutOfBounds())) {
const MessageTemplate message = MessageTemplate::kDetachedOperation;
Handle<String> operation =
isolate->factory()->NewStringFromAsciiChecked(method_name);
THROW_NEW_ERROR(isolate, NewTypeError(message, operation), JSTypedArray);
}
// spec describes to return `buffer`, but it may disrupt current
......
......@@ -296,6 +296,8 @@ class JSTypedArray
inline size_t GetLengthOrOutOfBounds(bool& out_of_bounds) const;
inline size_t GetLength() const;
inline size_t GetByteLength() const;
inline bool IsOutOfBounds() const;
static size_t LengthTrackingGsabBackedTypedArrayLength(Isolate* isolate,
Address raw_array);
......
......@@ -90,6 +90,7 @@ RUNTIME_FUNCTION(Runtime_TypedArraySortFast) {
// Validation is handled in the Torque builtin.
Handle<JSTypedArray> array = args.at<JSTypedArray>(0);
DCHECK(!array->WasDetached());
DCHECK(!array->IsOutOfBounds());
#if MULTI_MAPPED_ALLOCATOR_AVAILABLE
if (FLAG_multi_mapped_mock_allocator) {
......@@ -99,7 +100,7 @@ RUNTIME_FUNCTION(Runtime_TypedArraySortFast) {
}
#endif
size_t length = array->length();
size_t length = array->GetLength();
DCHECK_LT(1, length);
// In case of a SAB, the data is copied into temporary memory, as
......@@ -113,7 +114,7 @@ RUNTIME_FUNCTION(Runtime_TypedArraySortFast) {
std::vector<uint8_t> offheap_copy;
void* data_copy_ptr = nullptr;
if (copy_data) {
const size_t bytes = array->byte_length();
const size_t bytes = array->GetByteLength();
if (bytes <= static_cast<unsigned>(
ByteArray::LengthFor(kMaxRegularHeapObjectSize))) {
array_copy = isolate->factory()->NewByteArray(static_cast<int>(bytes));
......@@ -162,7 +163,7 @@ RUNTIME_FUNCTION(Runtime_TypedArraySortFast) {
if (copy_data) {
DCHECK_NOT_NULL(data_copy_ptr);
DCHECK_NE(array_copy.is_null(), offheap_copy.empty());
const size_t bytes = array->byte_length();
const size_t bytes = array->GetByteLength();
base::Relaxed_Memcpy(static_cast<base::Atomic8*>(array->DataPtr()),
static_cast<base::Atomic8*>(data_copy_ptr), bytes);
}
......
......@@ -3364,3 +3364,204 @@ function TestIterationAndGrow(ta, expected, gsab, grow_after,
assertEquals([0, 2, 4, 6], ToNumbers(lengthTracking.subarray(evil)));
}
})();
(function SortWithDefaultComparison() {
for (let ctor of ctors) {
const gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const fixedLength = new ctor(gsab, 0, 4);
const fixedLengthWithOffset = new ctor(gsab, 2 * ctor.BYTES_PER_ELEMENT, 2);
const lengthTracking = new ctor(gsab, 0);
const lengthTrackingWithOffset = new ctor(gsab, 2 * ctor.BYTES_PER_ELEMENT);
const taFull = new ctor(gsab, 0);
function WriteUnsortedData() {
// Write some data into the array.
for (let i = 0; i < taFull.length; ++i) {
WriteToTypedArray(taFull, i, 10 - 2 * i);
}
}
// Orig. array: [10, 8, 6, 4]
// [10, 8, 6, 4] << fixedLength
// [6, 4] << fixedLengthWithOffset
// [10, 8, 6, 4, ...] << lengthTracking
// [6, 4, ...] << lengthTrackingWithOffset
WriteUnsortedData();
fixedLength.sort();
assertEquals([4, 6, 8, 10], ToNumbers(taFull));
WriteUnsortedData();
fixedLengthWithOffset.sort();
assertEquals([10, 8, 4, 6], ToNumbers(taFull));
WriteUnsortedData();
lengthTracking.sort();
assertEquals([4, 6, 8, 10], ToNumbers(taFull));
WriteUnsortedData();
lengthTrackingWithOffset.sort();
assertEquals([10, 8, 4, 6], ToNumbers(taFull));
// Grow.
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
// Orig. array: [10, 8, 6, 4, 2, 0]
// [10, 8, 6, 4] << fixedLength
// [6, 4] << fixedLengthWithOffset
// [10, 8, 6, 4, 2, 0, ...] << lengthTracking
// [6, 4, 2, 0, ...] << lengthTrackingWithOffset
WriteUnsortedData();
fixedLength.sort();
assertEquals([4, 6, 8, 10, 2, 0], ToNumbers(taFull));
WriteUnsortedData();
fixedLengthWithOffset.sort();
assertEquals([10, 8, 4, 6, 2, 0], ToNumbers(taFull));
WriteUnsortedData();
lengthTracking.sort();
assertEquals([0, 2, 4, 6, 8, 10], ToNumbers(taFull));
WriteUnsortedData();
lengthTrackingWithOffset.sort();
assertEquals([10, 8, 0, 2, 4, 6], ToNumbers(taFull));
}
})();
(function SortWithCustomComparison() {
for (let ctor of ctors) {
const gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const fixedLength = new ctor(gsab, 0, 4);
const fixedLengthWithOffset = new ctor(gsab, 2 * ctor.BYTES_PER_ELEMENT, 2);
const lengthTracking = new ctor(gsab, 0);
const lengthTrackingWithOffset = new ctor(gsab, 2 * ctor.BYTES_PER_ELEMENT);
const taFull = new ctor(gsab, 0);
function WriteUnsortedData() {
// Write some data into the array.
for (let i = 0; i < taFull.length; ++i) {
WriteToTypedArray(taFull, i, 10 - i);
}
}
function CustomComparison(a, b) {
// Sort all odd numbers before even numbers.
a = Number(a);
b = Number(b);
if (a % 2 == 1 && b % 2 == 0) {
return -1;
}
if (a % 2 == 0 && b % 2 == 1) {
return 1;
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
// Orig. array: [10, 9, 8, 7]
// [10, 9, 8, 7] << fixedLength
// [8, 7] << fixedLengthWithOffset
// [10, 9, 8, 7, ...] << lengthTracking
// [8, 7, ...] << lengthTrackingWithOffset
WriteUnsortedData();
fixedLength.sort(CustomComparison);
assertEquals([7, 9, 8, 10], ToNumbers(taFull));
WriteUnsortedData();
fixedLengthWithOffset.sort(CustomComparison);
assertEquals([10, 9, 7, 8], ToNumbers(taFull));
WriteUnsortedData();
lengthTracking.sort(CustomComparison);
assertEquals([7, 9, 8, 10], ToNumbers(taFull));
WriteUnsortedData();
lengthTrackingWithOffset.sort(CustomComparison);
assertEquals([10, 9, 7, 8], ToNumbers(taFull));
// Grow.
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
// Orig. array: [10, 9, 8, 7, 6, 5]
// [10, 9, 8, 7] << fixedLength
// [8, 7] << fixedLengthWithOffset
// [10, 9, 8, 7, 6, 5, ...] << lengthTracking
// [8, 7, 6, 5, ...] << lengthTrackingWithOffset
WriteUnsortedData();
fixedLength.sort(CustomComparison);
assertEquals([7, 9, 8, 10, 6, 5], ToNumbers(taFull));
WriteUnsortedData();
fixedLengthWithOffset.sort(CustomComparison);
assertEquals([10, 9, 7, 8, 6, 5], ToNumbers(taFull));
WriteUnsortedData();
lengthTracking.sort(CustomComparison);
assertEquals([5, 7, 9, 6, 8, 10], ToNumbers(taFull));
WriteUnsortedData();
lengthTrackingWithOffset.sort(CustomComparison);
assertEquals([10, 9, 5, 7, 6, 8], ToNumbers(taFull));
}
})();
(function SortCallbackGrows() {
function WriteUnsortedData(taFull) {
for (let i = 0; i < taFull.length; ++i) {
WriteToTypedArray(taFull, i, 10 - i);
}
}
let gsab;
let growTo;
function CustomComparison(a, b) {
gsab.grow(growTo);
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
// Fixed length TA.
for (let ctor of ctors) {
gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
growTo = 6 * ctor.BYTES_PER_ELEMENT;
const fixedLength = new ctor(gsab, 0, 4);
const taFull = new ctor(gsab, 0);
WriteUnsortedData(taFull);
fixedLength.sort(CustomComparison);
// Growing doesn't affect the sorting.
assertEquals([7, 8, 9, 10, 0, 0], ToNumbers(taFull));
}
// Length-tracking TA.
for (let ctor of ctors) {
gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
growTo = 6 * ctor.BYTES_PER_ELEMENT;
const lengthTracking = new ctor(gsab, 0);
const taFull = new ctor(gsab, 0);
WriteUnsortedData(taFull);
lengthTracking.sort(CustomComparison);
// Growing doesn't affect the sorting. Only the elements that were part of
// the original TA are sorted.
assertEquals([7, 8, 9, 10, 0, 0], ToNumbers(taFull));
}
})();
......@@ -1444,3 +1444,39 @@ d8.file.execute('test/mjsunit/typedarray-helpers.js');
assertThrows(() => { fixedLength.subarray(0, evil); }, TypeError);
}
})();
(function SortCallbackDetaches() {
function WriteUnsortedData(taFull) {
for (let i = 0; i < taFull.length; ++i) {
WriteToTypedArray(taFull, i, 10 - i);
}
}
let rab;
function CustomComparison(a, b) {
%ArrayBufferDetach(rab);
return 0;
}
// Fixed length TA.
for (let ctor of ctors) {
rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const fixedLength = new ctor(rab, 0, 4);
const taFull = new ctor(rab, 0);
WriteUnsortedData(taFull);
assertThrows(() => { fixedLength.sort(CustomComparison); });
}
// Length-tracking TA.
for (let ctor of ctors) {
rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const lengthTracking = new ctor(rab, 0);
const taFull = new ctor(rab, 0);
WriteUnsortedData(taFull);
assertThrows(() => { lengthTracking.sort(CustomComparison); });
}
})();
......@@ -283,8 +283,6 @@
# https://bugs.chromium.org/p/v8/issues/detail?id=11111
'built-ins/ArrayBuffer/prototype/transfer/*': [FAIL],
'built-ins/ArrayBuffer/prototype/transfer/this-is-sharedarraybuffer': [PASS],
'built-ins/TypedArray/prototype/sort/BigInt/return-abrupt-from-this-out-of-bounds': [FAIL],
'built-ins/TypedArray/prototype/sort/return-abrupt-from-this-out-of-bounds': [FAIL],
# See also https://github.com/tc39/test262/issues/3380
'built-ins/TypedArray/prototype/map/callbackfn-resize': [FAIL],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment