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

[rab/gsab] Add rab / gsab support to TA.p.{join,toLocaleString}

Bug: v8:11111
Change-Id: I5cdd26070eb6ddf264e46763a71097e9fb716bf0
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3333924Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78383}
parent b8e2a423
......@@ -55,7 +55,8 @@ LoadJoinElement<array::FastDoubleElements>(
builtin LoadJoinTypedElement<T : type extends ElementsKind>(
context: Context, receiver: JSReceiver, k: uintptr): JSAny {
const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
dcheck(!IsDetachedBuffer(typedArray.buffer));
dcheck(!typed_array::IsJSArrayBufferViewDetachedOrOutOfBoundsBoolean(
typedArray));
return typed_array::LoadFixedTypedArrayElementAsTagged(
typedArray.data_ptr, k, typed_array::KindForArrayType<T>());
}
......@@ -103,7 +104,19 @@ CannotUseSameArrayAccessor<JSTypedArray>(implicit context: Context)(
_loadFn: LoadJoinElementFn, receiver: JSReceiver, _initialMap: Map,
_initialLen: Number): bool {
const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
return IsDetachedBuffer(typedArray.buffer);
// When this is called from toLocaleString(), the underlying buffer might get
// detached / resized (in the case of RAB / GSAB) during iterating the
// elements. When this is called from join(), it can happen only before the
// first element (during parameter conversion). The code below doesn't
// differentiate between these two cases, but does the checks in both cases.
if (IsDetachedBuffer(typedArray.buffer)) {
return true;
}
if (IsVariableLengthJSArrayBufferView(typedArray)) {
// TODO(v8:11111): Add a fast(er) path here.
return true;
}
return false;
}
// Calculates the running total length of the resulting string. If the
......@@ -387,6 +400,28 @@ transitioning ArrayJoin<JSTypedArray>(implicit context: Context)(
loadFn = LoadJoinTypedElement<typed_array::BigUint64Elements>;
} else if (kind == ElementsKind::BIGINT64_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::BigInt64Elements>;
} else if (kind == ElementsKind::RAB_GSAB_UINT8_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Uint8Elements>;
} else if (kind == ElementsKind::RAB_GSAB_INT8_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Int8Elements>;
} else if (kind == ElementsKind::RAB_GSAB_UINT16_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Uint16Elements>;
} else if (kind == ElementsKind::RAB_GSAB_INT16_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Int16Elements>;
} else if (kind == ElementsKind::RAB_GSAB_UINT32_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Uint32Elements>;
} else if (kind == ElementsKind::RAB_GSAB_INT32_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Int32Elements>;
} else if (kind == ElementsKind::RAB_GSAB_FLOAT32_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Float32Elements>;
} else if (kind == ElementsKind::RAB_GSAB_FLOAT64_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Float64Elements>;
} else if (kind == ElementsKind::RAB_GSAB_UINT8_CLAMPED_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::Uint8ClampedElements>;
} else if (kind == ElementsKind::RAB_GSAB_BIGUINT64_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::BigUint64Elements>;
} else if (kind == ElementsKind::RAB_GSAB_BIGINT64_ELEMENTS) {
loadFn = LoadJoinTypedElement<typed_array::BigInt64Elements>;
} else {
unreachable;
}
......@@ -616,12 +651,13 @@ transitioning javascript builtin TypedArrayPrototypeJoin(
// Spec: ValidateTypedArray is applied to the this value prior to evaluating
// the algorithm.
const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
const length = typed_array::ValidateTypedArrayAndGetLength(
context, receiver, '%TypedArray%.prototype.join');
const length = Convert<Number>(typedArray.length);
const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
return CycleProtectedArrayJoin<JSTypedArray>(
false, typedArray, length, separator, Undefined, Undefined);
false, typedArray, Convert<Number>(length), separator, Undefined,
Undefined);
}
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.tolocalestring
......@@ -632,11 +668,11 @@ transitioning javascript builtin TypedArrayPrototypeToLocaleString(
// Spec: ValidateTypedArray is applied to the this value prior to evaluating
// the algorithm.
const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
const length = typed_array::ValidateTypedArrayAndGetLength(
context, receiver, '%TypedArray%.prototype.toLocaleString');
const length = Convert<Number>(typedArray.length);
const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
return CycleProtectedArrayJoin<JSTypedArray>(
true, typedArray, length, ',', locales, options);
true, typedArray, Convert<Number>(length), ',', locales, options);
}
}
......@@ -306,6 +306,16 @@ extern enum ElementsKind extends int32 {
BIGUINT64_ELEMENTS,
BIGINT64_ELEMENTS,
RAB_GSAB_UINT8_ELEMENTS,
RAB_GSAB_INT8_ELEMENTS,
RAB_GSAB_UINT16_ELEMENTS,
RAB_GSAB_INT16_ELEMENTS,
RAB_GSAB_UINT32_ELEMENTS,
RAB_GSAB_INT32_ELEMENTS,
RAB_GSAB_FLOAT32_ELEMENTS,
RAB_GSAB_FLOAT64_ELEMENTS,
RAB_GSAB_UINT8_CLAMPED_ELEMENTS,
RAB_GSAB_BIGUINT64_ELEMENTS,
RAB_GSAB_BIGINT64_ELEMENTS,
// TODO(torque): Allow duplicate enum values.
// FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND,
// FIRST_RAB_GSAB_FIXED_TYPED_ARRAY_ELEMENTS_KIND,
......
......@@ -182,6 +182,8 @@ extern macro TypedArrayBuiltinsAssembler::SetJSTypedArrayOffHeapDataPtr(
JSTypedArray, RawPtr, uintptr): void;
extern macro IsJSArrayBufferViewDetachedOrOutOfBounds(JSArrayBufferView):
never labels DetachedOrOutOfBounds, NotDetachedNorOutOfBounds;
extern macro IsJSArrayBufferViewDetachedOrOutOfBoundsBoolean(JSArrayBufferView):
bool;
// AttachedJSTypedArray guards that the array's buffer is not detached.
transient type AttachedJSTypedArray extends JSTypedArray;
......
......@@ -14024,21 +14024,23 @@ CodeStubAssembler::LoadVariableLengthJSArrayBufferViewByteLength(
}
void CodeStubAssembler::IsJSArrayBufferViewDetachedOrOutOfBounds(
TNode<JSArrayBufferView> array, Label* detached_or_oob,
TNode<JSArrayBufferView> array_buffer_view, Label* detached_or_oob,
Label* not_detached_nor_oob) {
TNode<JSArrayBuffer> buffer = LoadJSArrayBufferViewBuffer(array);
TNode<JSArrayBuffer> buffer = LoadJSArrayBufferViewBuffer(array_buffer_view);
GotoIf(IsDetachedBuffer(buffer), detached_or_oob);
GotoIfNot(IsVariableLengthJSArrayBufferView(array), not_detached_nor_oob);
GotoIfNot(IsVariableLengthJSArrayBufferView(array_buffer_view),
not_detached_nor_oob);
GotoIf(IsSharedArrayBuffer(buffer), not_detached_nor_oob);
{
TNode<UintPtrT> buffer_byte_length = LoadJSArrayBufferByteLength(buffer);
TNode<UintPtrT> array_byte_offset = LoadJSArrayBufferViewByteOffset(array);
TNode<UintPtrT> array_byte_offset =
LoadJSArrayBufferViewByteOffset(array_buffer_view);
Label length_tracking(this), not_length_tracking(this);
Branch(IsLengthTrackingJSArrayBufferView(array), &length_tracking,
&not_length_tracking);
Branch(IsLengthTrackingJSArrayBufferView(array_buffer_view),
&length_tracking, &not_length_tracking);
BIND(&length_tracking);
{
......@@ -14053,7 +14055,7 @@ void CodeStubAssembler::IsJSArrayBufferViewDetachedOrOutOfBounds(
// Check if the backing RAB has shrunk so that the buffer is out of
// bounds.
TNode<UintPtrT> array_byte_length =
LoadJSArrayBufferViewByteLength(array);
LoadJSArrayBufferViewByteLength(array_buffer_view);
Branch(UintPtrGreaterThanOrEqual(
buffer_byte_length,
UintPtrAdd(array_byte_offset, array_byte_length)),
......@@ -14062,6 +14064,29 @@ void CodeStubAssembler::IsJSArrayBufferViewDetachedOrOutOfBounds(
}
}
TNode<BoolT> CodeStubAssembler::IsJSArrayBufferViewDetachedOrOutOfBoundsBoolean(
TNode<JSArrayBufferView> array_buffer_view) {
Label is_detached_or_out_of_bounds(this),
not_detached_nor_out_of_bounds(this), end(this);
TVARIABLE(BoolT, result);
IsJSArrayBufferViewDetachedOrOutOfBounds(array_buffer_view,
&is_detached_or_out_of_bounds,
&not_detached_nor_out_of_bounds);
BIND(&is_detached_or_out_of_bounds);
{
result = BoolConstant(true);
Goto(&end);
}
BIND(&not_detached_nor_out_of_bounds);
{
result = BoolConstant(false);
Goto(&end);
}
BIND(&end);
return result.value();
}
// ES #sec-integerindexedobjectbytelength
TNode<UintPtrT> CodeStubAssembler::LoadVariableLengthJSTypedArrayByteLength(
TNode<Context> context, TNode<JSTypedArray> array,
......
......@@ -3620,10 +3620,13 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<JSArrayBufferView> array, TNode<JSArrayBuffer> buffer,
Label* detached_or_out_of_bounds);
void IsJSArrayBufferViewDetachedOrOutOfBounds(TNode<JSArrayBufferView> array,
Label* detached_or_oob,
void IsJSArrayBufferViewDetachedOrOutOfBounds(
TNode<JSArrayBufferView> array_buffer_view, Label* detached_or_oob,
Label* not_detached_nor_oob);
TNode<BoolT> IsJSArrayBufferViewDetachedOrOutOfBoundsBoolean(
TNode<JSArrayBufferView> array_buffer_view);
TNode<IntPtrT> RabGsabElementsKindToElementByteSize(
TNode<Int32T> elementsKind);
TNode<RawPtrT> LoadJSTypedArrayDataPtr(TNode<JSTypedArray> typed_array);
......
......@@ -2451,3 +2451,146 @@ function TestIterationAndGrow(ta, expected, gsab, grow_after,
assertEquals(-1, lengthTracking.lastIndexOf(NaN));
}
})();
(function JoinToLocaleString() {
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);
// Write some data into the array.
const taWrite = new ctor(gsab);
for (let i = 0; i < 4; ++i) {
WriteToTypedArray(taWrite, i, 2 * i);
}
// Orig. array: [0, 2, 4, 6]
// [0, 2, 4, 6] << fixedLength
// [4, 6] << fixedLengthWithOffset
// [0, 2, 4, 6, ...] << lengthTracking
// [4, 6, ...] << lengthTrackingWithOffset
assertEquals('0,2,4,6', fixedLength.join());
assertEquals('0,2,4,6', fixedLength.toLocaleString());
assertEquals('4,6', fixedLengthWithOffset.join());
assertEquals('4,6', fixedLengthWithOffset.toLocaleString());
assertEquals('0,2,4,6', lengthTracking.join());
assertEquals('0,2,4,6', lengthTracking.toLocaleString());
assertEquals('4,6', lengthTrackingWithOffset.join());
assertEquals('4,6', lengthTrackingWithOffset.toLocaleString());
// Grow.
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
for (let i = 0; i < 6; ++i) {
WriteToTypedArray(taWrite, i, 2 * i);
}
// Orig. array: [0, 2, 4, 6, 8, 10]
// [0, 2, 4, 6] << fixedLength
// [4, 6] << fixedLengthWithOffset
// [0, 2, 4, 6, 8, 10, ...] << lengthTracking
// [4, 6, 8, 10, ...] << lengthTrackingWithOffset
assertEquals('0,2,4,6', fixedLength.join());
assertEquals('0,2,4,6', fixedLength.toLocaleString());
assertEquals('4,6', fixedLengthWithOffset.join());
assertEquals('4,6', fixedLengthWithOffset.toLocaleString());
assertEquals('0,2,4,6,8,10', lengthTracking.join());
assertEquals('0,2,4,6,8,10', lengthTracking.toLocaleString());
assertEquals('4,6,8,10', lengthTrackingWithOffset.join());
assertEquals('4,6,8,10', lengthTrackingWithOffset.toLocaleString());
}
})();
(function JoinParameterConversionGrows() {
// Growing + fixed-length TA.
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);
let evil = { toString: () => {
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
return '.';
}};
assertEquals('0.0.0.0', fixedLength.join(evil));
}
// Growing + length-tracking TA.
for (let ctor of ctors) {
const gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const lengthTracking = new ctor(gsab);
let evil = { toString: () => {
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
return '.';
}};
// We iterate 4 elements, since it was the starting length.
assertEquals('0.0.0.0', lengthTracking.join(evil));
}
})();
(function ToLocaleStringNumberPrototypeToLocaleStringGrows() {
const oldNumberPrototypeToLocaleString = Number.prototype.toLocaleString;
const oldBigIntPrototypeToLocaleString = BigInt.prototype.toLocaleString;
// Growing + fixed-length TA.
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);
let growAfter = 2;
Number.prototype.toLocaleString = function() {
--growAfter;
if (growAfter == 0) {
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
}
return oldNumberPrototypeToLocaleString.call(this);
}
BigInt.prototype.toLocaleString = function() {
--growAfter;
if (growAfter == 0) {
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
}
return oldBigIntPrototypeToLocaleString.call(this);
}
// We iterate 4 elements since it was the starting length. Resizing doesn't
// affect the TA.
assertEquals('0,0,0,0', fixedLength.toLocaleString());
}
// Growing + length-tracking TA.
for (let ctor of ctors) {
const gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const lengthTracking = new ctor(gsab);
let growAfter = 2;
Number.prototype.toLocaleString = function() {
--growAfter;
if (growAfter == 0) {
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
}
return oldNumberPrototypeToLocaleString.call(this);
}
BigInt.prototype.toLocaleString = function() {
--growAfter;
if (growAfter == 0) {
gsab.grow(6 * ctor.BYTES_PER_ELEMENT);
}
return oldBigIntPrototypeToLocaleString.call(this);
}
// We iterate 4 elements since it was the starting length.
assertEquals('0,0,0,0', lengthTracking.toLocaleString());
}
Number.prototype.toLocaleString = oldNumberPrototypeToLocaleString;
BigInt.prototype.toLocaleString = oldBigIntPrototypeToLocaleString;
})();
......@@ -865,3 +865,121 @@ d8.file.execute('test/mjsunit/typedarray-helpers.js');
assertEquals(-1, LastIndexOfHelper(lengthTracking, undefined, evil));
}
})();
(function JoinToLocaleString() {
for (let ctor of ctors) {
const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const fixedLength = new ctor(rab, 0, 4);
const fixedLengthWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT, 2);
const lengthTracking = new ctor(rab, 0);
const lengthTrackingWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT);
%ArrayBufferDetach(rab);
assertThrows(() => { fixedLength.join(); });
assertThrows(() => { fixedLength.toLocaleString(); });
assertThrows(() => { fixedLengthWithOffset.join(); });
assertThrows(() => { fixedLengthWithOffset.toLocaleString(); });
assertThrows(() => { lengthTracking.join(); });
assertThrows(() => { lengthTracking.toLocaleString(); });
assertThrows(() => { lengthTrackingWithOffset.join(); });
assertThrows(() => { lengthTrackingWithOffset.toLocaleString(); });
}
})();
(function JoinParameterConversionDetaches() {
// Detaching + fixed-length TA.
for (let ctor of ctors) {
const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const fixedLength = new ctor(rab, 0, 4);
let evil = { toString: () => {
%ArrayBufferDetach(rab);
return '.';
}};
// We iterate 4 elements, since it was the starting length, but the TA is
// OOB right after parameter conversion, so all elements are converted to
// the empty string.
assertEquals('...', fixedLength.join(evil));
}
// Detaching + length-tracking TA.
for (let ctor of ctors) {
const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const lengthTracking = new ctor(rab);
let evil = { toString: () => {
%ArrayBufferDetach(rab);
return '.';
}};
// We iterate 4 elements, since it was the starting length, but the TA is
// OOB right after parameter conversion, so all elements are converted to
// the empty string.
assertEquals('...', lengthTracking.join(evil));
}
})();
(function ToLocaleStringNumberPrototypeToLocaleStringDetaches() {
const oldNumberPrototypeToLocaleString = Number.prototype.toLocaleString;
const oldBigIntPrototypeToLocaleString = BigInt.prototype.toLocaleString;
// Detaching + fixed-length TA.
for (let ctor of ctors) {
const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const fixedLength = new ctor(rab, 0, 4);
let detachAfter = 2;
Number.prototype.toLocaleString = function() {
--detachAfter;
if (detachAfter == 0) {
%ArrayBufferDetach(rab);
}
return oldNumberPrototypeToLocaleString.call(this);
}
BigInt.prototype.toLocaleString = function() {
--detachAfter;
if (detachAfter == 0) {
%ArrayBufferDetach(rab);
}
return oldBigIntPrototypeToLocaleString.call(this);
}
// We iterate 4 elements, since it was the starting length. The TA goes
// OOB after 2 elements.
assertEquals('0,0,,', fixedLength.toLocaleString());
}
// Detaching + length-tracking TA.
for (let ctor of ctors) {
const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT,
8 * ctor.BYTES_PER_ELEMENT);
const lengthTracking = new ctor(rab);
let detachAfter = 2;
Number.prototype.toLocaleString = function() {
--detachAfter;
if (detachAfter == 0) {
%ArrayBufferDetach(rab);
}
return oldNumberPrototypeToLocaleString.call(this);
}
BigInt.prototype.toLocaleString = function() {
--detachAfter;
if (detachAfter == 0) {
%ArrayBufferDetach(rab);
}
return oldBigIntPrototypeToLocaleString.call(this);
}
// We iterate 4 elements, since it was the starting length. The TA goes
// OOB after 2 elements.
assertEquals('0,0,,', lengthTracking.toLocaleString());
}
Number.prototype.toLocaleString = oldNumberPrototypeToLocaleString;
BigInt.prototype.toLocaleString = oldBigIntPrototypeToLocaleString;
})();
......@@ -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/join/BigInt/return-abrupt-from-this-out-of-bounds': [SKIP],
'built-ins/TypedArray/prototype/join/return-abrupt-from-this-out-of-bounds': [SKIP],
'built-ins/TypedArray/prototype/map/BigInt/return-abrupt-from-this-out-of-bounds': [SKIP],
'built-ins/TypedArray/prototype/map/return-abrupt-from-this-out-of-bounds': [SKIP],
'built-ins/TypedArray/prototype/reverse/BigInt/return-abrupt-from-this-out-of-bounds': [SKIP],
......@@ -294,8 +292,6 @@
'built-ins/TypedArray/prototype/set/typedarray-arg-target-out-of-bounds': [SKIP],
'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],
'built-ins/TypedArray/prototype/toLocaleString/BigInt/return-abrupt-from-this-out-of-bounds': [SKIP],
'built-ins/TypedArray/prototype/toLocaleString/return-abrupt-from-this-out-of-bounds': [SKIP],
'built-ins/TypedArrayConstructors/ctors/typedarray-arg/out-of-bounds-when-species-retrieved-different-type': [FAIL],
'built-ins/TypedArrayConstructors/ctors/typedarray-arg/out-of-bounds-when-species-retrieved-same-type': [FAIL],
'built-ins/Array/prototype/every/callbackfn-resize-arraybuffer': [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