Commit 3926d6cd authored by legendecas's avatar legendecas Committed by V8 LUCI CQ

[builtins] typed array detaching in builtin iterations

%TypedArray.prototype% methods that receive a user callback
fn should not break in the mid-way of the iteration when the
backing array buffer was been detached. Instead, the iteration
should continue with the value set to undefined.

Notably, %TypedArray.prototype%.filter was throwing when the
backing buffer was detached during iteration. This should not
throw now.

Refs: https://github.com/tc39/ecma262/pull/2164
Bug: v8:4895
Change-Id: Ia7fab63264c8148a11f8f123b43c7b3ee0893300
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3066941Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76611}
parent 9cc41406
...@@ -45,13 +45,13 @@ void ArrayBuiltinsAssembler::TypedArrayMapResultGenerator() { ...@@ -45,13 +45,13 @@ void ArrayBuiltinsAssembler::TypedArrayMapResultGenerator() {
// See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map. // See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map.
TNode<Object> ArrayBuiltinsAssembler::TypedArrayMapProcessor( TNode<Object> ArrayBuiltinsAssembler::TypedArrayMapProcessor(
TNode<Object> k_value, TNode<UintPtrT> k) { TNode<Object> k_value, TNode<UintPtrT> k) {
// 8. c. Let mapped_value be ? Call(callbackfn, T, « kValue, k, O »). // 7c. Let mapped_value be ? Call(callbackfn, T, « kValue, k, O »).
TNode<Number> k_number = ChangeUintPtrToTagged(k); TNode<Number> k_number = ChangeUintPtrToTagged(k);
TNode<Object> mapped_value = TNode<Object> mapped_value =
Call(context(), callbackfn(), this_arg(), k_value, k_number, o()); Call(context(), callbackfn(), this_arg(), k_value, k_number, o());
Label fast(this), slow(this), done(this), detached(this, Label::kDeferred); Label fast(this), slow(this), done(this), detached(this, Label::kDeferred);
// 8. d. Perform ? Set(A, Pk, mapped_value, true). // 7d. Perform ? Set(A, Pk, mapped_value, true).
// Since we know that A is a TypedArray, this always ends up in // Since we know that A is a TypedArray, this always ends up in
// #sec-integer-indexed-exotic-objects-set-p-v-receiver and then // #sec-integer-indexed-exotic-objects-set-p-v-receiver and then
// tc39.github.io/ecma262/#sec-integerindexedelementset . // tc39.github.io/ecma262/#sec-integerindexedelementset .
...@@ -59,9 +59,9 @@ TNode<Object> ArrayBuiltinsAssembler::TypedArrayMapProcessor( ...@@ -59,9 +59,9 @@ TNode<Object> ArrayBuiltinsAssembler::TypedArrayMapProcessor(
BIND(&fast); BIND(&fast);
// #sec-integerindexedelementset // #sec-integerindexedelementset
// 5. If arrayTypeName is "BigUint64Array" or "BigInt64Array", let // 2. If arrayTypeName is "BigUint64Array" or "BigInt64Array", let
// numValue be ? ToBigInt(v). // numValue be ? ToBigInt(v).
// 6. Otherwise, let numValue be ? ToNumber(value). // 3. Otherwise, let numValue be ? ToNumber(value).
TNode<Object> num_value; TNode<Object> num_value;
if (source_elements_kind_ == BIGINT64_ELEMENTS || if (source_elements_kind_ == BIGINT64_ELEMENTS ||
source_elements_kind_ == BIGUINT64_ELEMENTS) { source_elements_kind_ == BIGUINT64_ELEMENTS) {
...@@ -175,24 +175,15 @@ void ArrayBuiltinsAssembler::GenerateIteratingTypedArrayBuiltinBody( ...@@ -175,24 +175,15 @@ void ArrayBuiltinsAssembler::GenerateIteratingTypedArrayBuiltinBody(
size_t i = 0; size_t i = 0;
for (auto it = labels.begin(); it != labels.end(); ++i, ++it) { for (auto it = labels.begin(); it != labels.end(); ++i, ++it) {
BIND(&*it); BIND(&*it);
Label done(this);
source_elements_kind_ = static_cast<ElementsKind>(elements_kinds[i]); source_elements_kind_ = static_cast<ElementsKind>(elements_kinds[i]);
// TODO(turbofan): Silently cancelling the loop on buffer detachment is a VisitAllTypedArrayElements(array_buffer, processor, direction, typed_array);
// spec violation. Should go to &throw_detached and throw a TypeError
// instead.
VisitAllTypedArrayElements(array_buffer, processor, &done, direction,
typed_array);
Goto(&done);
// No exception, return success
BIND(&done);
ReturnFromBuiltin(a_.value()); ReturnFromBuiltin(a_.value());
} }
} }
void ArrayBuiltinsAssembler::VisitAllTypedArrayElements( void ArrayBuiltinsAssembler::VisitAllTypedArrayElements(
TNode<JSArrayBuffer> array_buffer, const CallResultProcessor& processor, TNode<JSArrayBuffer> array_buffer, const CallResultProcessor& processor,
Label* detached, ForEachDirection direction, ForEachDirection direction, TNode<JSTypedArray> typed_array) {
TNode<JSTypedArray> typed_array) {
VariableList list({&a_, &k_}, zone()); VariableList list({&a_, &k_}, zone());
TNode<UintPtrT> start = UintPtrConstant(0); TNode<UintPtrT> start = UintPtrConstant(0);
...@@ -208,12 +199,28 @@ void ArrayBuiltinsAssembler::VisitAllTypedArrayElements( ...@@ -208,12 +199,28 @@ void ArrayBuiltinsAssembler::VisitAllTypedArrayElements(
BuildFastLoop<UintPtrT>( BuildFastLoop<UintPtrT>(
list, start, end, list, start, end,
[&](TNode<UintPtrT> index) { [&](TNode<UintPtrT> index) {
GotoIf(IsDetachedBuffer(array_buffer), detached); TVARIABLE(Object, value);
TNode<RawPtrT> data_ptr = LoadJSTypedArrayDataPtr(typed_array); Label detached(this, Label::kDeferred);
TNode<Numeric> value = LoadFixedTypedArrayElementAsTagged( Label process(this);
data_ptr, index, source_elements_kind_); GotoIf(IsDetachedBuffer(array_buffer), &detached);
k_ = index; {
a_ = processor(this, value, index); TNode<RawPtrT> data_ptr = LoadJSTypedArrayDataPtr(typed_array);
value = LoadFixedTypedArrayElementAsTagged(data_ptr, index,
source_elements_kind_);
Goto(&process);
}
BIND(&detached);
{
value = UndefinedConstant();
Goto(&process);
}
BIND(&process);
{
k_ = index;
a_ = processor(this, value.value(), index);
}
}, },
incr, advance_mode); incr, advance_mode);
} }
......
...@@ -104,7 +104,7 @@ class ArrayBuiltinsAssembler : public CodeStubAssembler { ...@@ -104,7 +104,7 @@ class ArrayBuiltinsAssembler : public CodeStubAssembler {
private: private:
void VisitAllTypedArrayElements(TNode<JSArrayBuffer> array_buffer, void VisitAllTypedArrayElements(TNode<JSArrayBuffer> array_buffer,
const CallResultProcessor& processor, const CallResultProcessor& processor,
Label* detached, ForEachDirection direction, ForEachDirection direction,
TNode<JSTypedArray> typed_array); TNode<JSTypedArray> typed_array);
TNode<Object> callbackfn_; TNode<Object> callbackfn_;
......
...@@ -7,24 +7,43 @@ ...@@ -7,24 +7,43 @@
namespace typed_array { namespace typed_array {
const kBuiltinNameEvery: constexpr string = '%TypedArray%.prototype.every'; const kBuiltinNameEvery: constexpr string = '%TypedArray%.prototype.every';
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.every
transitioning macro EveryAllElements(implicit context: Context)( transitioning macro EveryAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, callbackfn: Callable, array: typed_array::AttachedJSTypedArray, callbackfn: Callable,
thisArg: JSAny): Boolean { thisArg: JSAny): Boolean {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 6. Repeat, while k < len
for (let k: uintptr = 0; k < length; k++) { for (let k: uintptr = 0; k < length; k++) {
// BUG(4895): We should throw on detached buffers rather than simply exit. // 6a. Let Pk be ! ToString(𝔽(k)).
witness.Recheck() otherwise break; // There is no need to cast ToString to load elements.
const value: JSAny = witness.Load(k);
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer is detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue,
// 𝔽(k), O »)).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi // TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case. // indices to optimize Convert<Number>(k) for the most common case.
const result = Call( const result = Call(
context, callbackfn, thisArg, value, Convert<Number>(k), context, callbackfn, thisArg, value, Convert<Number>(k),
witness.GetStable()); witness.GetStable());
// 6d. If testResult is false, return false.
if (!ToBoolean(result)) { if (!ToBoolean(result)) {
return False; return False;
} }
// 6e. Set k to k + 1. (done by the loop).
} }
// 7. Return true.
return True; return True;
} }
......
...@@ -38,11 +38,15 @@ transitioning javascript builtin TypedArrayPrototypeFilter( ...@@ -38,11 +38,15 @@ transitioning javascript builtin TypedArrayPrototypeFilter(
// 8. Let captured be 0. // 8. Let captured be 0.
// 9. Repeat, while k < len // 9. Repeat, while k < len
for (let k: uintptr = 0; k < len; k++) { for (let k: uintptr = 0; k < len; k++) {
witness.Recheck() otherwise IsDetached; let value: JSAny;
// a. Let Pk be ! ToString(k). // a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(O, Pk). // b. Let kValue be ? Get(O, Pk).
const value: JSAny = witness.Load(k); try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// c. Let selected be ToBoolean(? Call(callbackfn, T, « kValue, k, O // c. Let selected be ToBoolean(? Call(callbackfn, T, « kValue, k, O
// »)). // »)).
...@@ -57,7 +61,7 @@ transitioning javascript builtin TypedArrayPrototypeFilter( ...@@ -57,7 +61,7 @@ transitioning javascript builtin TypedArrayPrototypeFilter(
// ii. Increase captured by 1. // ii. Increase captured by 1.
if (ToBoolean(selected)) kept.Push(value); if (ToBoolean(selected)) kept.Push(value);
// e.Increase k by 1. // e. Increase k by 1. (done by the loop)
} }
// 10. Let A be ? TypedArraySpeciesCreate(O, captured). // 10. Let A be ? TypedArraySpeciesCreate(O, captured).
......
...@@ -7,24 +7,45 @@ ...@@ -7,24 +7,45 @@
namespace typed_array { namespace typed_array {
const kBuiltinNameFind: constexpr string = '%TypedArray%.prototype.find'; const kBuiltinNameFind: constexpr string = '%TypedArray%.prototype.find';
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.find
transitioning macro FindAllElements(implicit context: Context)( transitioning macro FindAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, callbackfn: Callable, array: typed_array::AttachedJSTypedArray, predicate: Callable,
thisArg: JSAny): JSAny { thisArg: JSAny): JSAny {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 6. Repeat, while k < len
for (let k: uintptr = 0; k < length; k++) { for (let k: uintptr = 0; k < length; k++) {
// BUG(4895): We should throw on detached buffers rather than simply exit. // 6a. Let Pk be ! ToString(𝔽(k)).
witness.Recheck() otherwise break; // There is no need to cast ToString to load elements.
const value: JSAny = witness.Load(k);
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer is detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue,
// 𝔽(k), O »)).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi // TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case. // indices to optimize Convert<Number>(k) for the most common case.
const result = Call( const result = Call(
context, callbackfn, thisArg, value, Convert<Number>(k), context, predicate, thisArg, value, Convert<Number>(k),
witness.GetStable()); witness.GetStable());
// 6d. If testResult is true, return kValue.
if (ToBoolean(result)) { if (ToBoolean(result)) {
return value; return value;
} }
// 6e. Set k to k + 1. (done by the loop).
} }
// 7. Return undefined.
return Undefined; return Undefined;
} }
...@@ -39,9 +60,9 @@ TypedArrayPrototypeFind( ...@@ -39,9 +60,9 @@ TypedArrayPrototypeFind(
otherwise NotTypedArray; otherwise NotTypedArray;
const uarray = typed_array::EnsureAttached(array) otherwise IsDetached; const uarray = typed_array::EnsureAttached(array) otherwise IsDetached;
const callbackfn = Cast<Callable>(arguments[0]) otherwise NotCallable; const predicate = Cast<Callable>(arguments[0]) otherwise NotCallable;
const thisArg = arguments[1]; const thisArg = arguments[1];
return FindAllElements(uarray, callbackfn, thisArg); return FindAllElements(uarray, predicate, thisArg);
} label NotCallable deferred { } label NotCallable deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]); ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]);
} label NotTypedArray deferred { } label NotTypedArray deferred {
......
...@@ -9,19 +9,33 @@ const kBuiltinNameFindIndex: constexpr string = ...@@ -9,19 +9,33 @@ const kBuiltinNameFindIndex: constexpr string =
'%TypedArray%.prototype.findIndex'; '%TypedArray%.prototype.findIndex';
transitioning macro FindIndexAllElements(implicit context: Context)( transitioning macro FindIndexAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, callbackfn: Callable, array: typed_array::AttachedJSTypedArray, predicate: Callable,
thisArg: JSAny): Number { thisArg: JSAny): Number {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 6. Repeat, while k < len
for (let k: uintptr = 0; k < length; k++) { for (let k: uintptr = 0; k < length; k++) {
// BUG(4895): We should throw on detached buffers rather than simply exit. // 6a. Let Pk be ! ToString(𝔽(k)).
witness.Recheck() otherwise break; // There is no need to cast ToString to load elements.
const value: JSAny = witness.Load(k);
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer is detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue,
// 𝔽(k), O »)).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi // TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case. // indices to optimize Convert<Number>(k) for the most common case.
const indexNumber: Number = Convert<Number>(k); const indexNumber: Number = Convert<Number>(k);
const result = Call( const result = Call(
context, callbackfn, thisArg, value, indexNumber, witness.GetStable()); context, predicate, thisArg, value, indexNumber, witness.GetStable());
if (ToBoolean(result)) { if (ToBoolean(result)) {
return indexNumber; return indexNumber;
} }
...@@ -40,9 +54,9 @@ TypedArrayPrototypeFindIndex( ...@@ -40,9 +54,9 @@ TypedArrayPrototypeFindIndex(
otherwise NotTypedArray; otherwise NotTypedArray;
const uarray = typed_array::EnsureAttached(array) otherwise IsDetached; const uarray = typed_array::EnsureAttached(array) otherwise IsDetached;
const callbackfn = Cast<Callable>(arguments[0]) otherwise NotCallable; const predicate = Cast<Callable>(arguments[0]) otherwise NotCallable;
const thisArg = arguments[1]; const thisArg = arguments[1];
return FindIndexAllElements(uarray, callbackfn, thisArg); return FindIndexAllElements(uarray, predicate, thisArg);
} label NotCallable deferred { } label NotCallable deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]); ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]);
} label NotTypedArray deferred { } label NotTypedArray deferred {
......
...@@ -8,56 +8,28 @@ namespace typed_array { ...@@ -8,56 +8,28 @@ namespace typed_array {
const kBuiltinNameFindLast: constexpr string = const kBuiltinNameFindLast: constexpr string =
'%TypedArray%.prototype.findLast'; '%TypedArray%.prototype.findLast';
// Continuation part of
// https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlast
// when array buffer was detached.
transitioning builtin FindLastAllElementsDetachedContinuation(
implicit context: Context)(
array: JSTypedArray, predicate: Callable, thisArg: JSAny,
initialK: Number): JSAny {
// 6. Repeat, while k ≥ 0
for (let k: Number = initialK; k >= 0; k--) {
// 6a. Let Pk be ! ToString(𝔽(k)).
// there is no need to cast ToString to load elements.
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer was detached.
// 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue,
// 𝔽(k), O »)).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case.
const result =
Call(context, predicate, thisArg, Undefined, Convert<Number>(k), array);
// 6d. If testResult is true, return kValue.
if (ToBoolean(result)) {
return Undefined;
}
// 6e. Set k to k - 1. (done by the loop).
}
// 7. Return undefined.
return Undefined;
}
// https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlast // https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlast
transitioning macro FindLastAllElements(implicit context: Context)( transitioning macro FindLastAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, predicate: Callable, array: typed_array::AttachedJSTypedArray, predicate: Callable,
thisArg: JSAny): JSAny labels thisArg: JSAny): JSAny {
Bailout(Number) {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
// 3. Let len be O.[[ArrayLength]]. // 3. Let len be O.[[ArrayLength]].
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 5. Let k be len - 1. // 5. Let k be len - 1.
// 6. Repeat, while k ≥ 0 // 6. Repeat, while k ≥ 0
for (let k: uintptr = length; k-- > 0;) { for (let k: uintptr = length; k-- > 0;) {
witness.Recheck() otherwise goto Bailout(Convert<Number>(k));
// 6a. Let Pk be ! ToString(𝔽(k)). // 6a. Let Pk be ! ToString(𝔽(k)).
// there is no need to cast ToString to load elements. // There is no need to cast ToString to load elements.
// 6b. Let kValue be ! Get(O, Pk). // 6b. Let kValue be ! Get(O, Pk).
const value: JSAny = witness.Load(k); // kValue must be undefined when the buffer was detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, // 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue,
// 𝔽(k), O »)). // 𝔽(k), O »)).
...@@ -94,13 +66,7 @@ TypedArrayPrototypeFindLast( ...@@ -94,13 +66,7 @@ TypedArrayPrototypeFindLast(
// 4. If IsCallable(predicate) is false, throw a TypeError exception. // 4. If IsCallable(predicate) is false, throw a TypeError exception.
const predicate = Cast<Callable>(arguments[0]) otherwise NotCallable; const predicate = Cast<Callable>(arguments[0]) otherwise NotCallable;
const thisArg = arguments[1]; const thisArg = arguments[1];
try { return FindLastAllElements(uarray, predicate, thisArg);
return FindLastAllElements(uarray, predicate, thisArg)
otherwise Bailout;
} label Bailout(k: Number) deferred {
return FindLastAllElementsDetachedContinuation(
uarray, predicate, thisArg, k);
}
} label NotCallable deferred { } label NotCallable deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]); ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]);
} label NotTypedArray deferred { } label NotTypedArray deferred {
......
...@@ -8,57 +8,28 @@ namespace typed_array { ...@@ -8,57 +8,28 @@ namespace typed_array {
const kBuiltinNameFindLastIndex: constexpr string = const kBuiltinNameFindLastIndex: constexpr string =
'%TypedArray%.prototype.findIndexLast'; '%TypedArray%.prototype.findIndexLast';
// Continuation part of
// https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlastindex
// when array buffer was detached.
transitioning builtin FindLastIndexAllElementsDetachedContinuation(
implicit context: Context)(
array: JSTypedArray, predicate: Callable, thisArg: JSAny,
initialK: Number): Number {
// 6. Repeat, while k ≥ 0
for (let k: Number = initialK; k >= 0; k--) {
// 6a. Let Pk be ! ToString(𝔽(k)).
// there is no need to cast ToString to load elements.
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer was detached.
// 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue,
// 𝔽(k), O »)).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case.
const indexNumber: Number = Convert<Number>(k);
const result =
Call(context, predicate, thisArg, Undefined, indexNumber, array);
// 6d. If testResult is true, return 𝔽(k).
if (ToBoolean(result)) {
return indexNumber;
}
// 6e. Set k to k - 1. (done by the loop).
}
// 7. Return -1𝔽.
return -1;
}
// https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlastindex // https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlastindex
transitioning macro FindLastIndexAllElements(implicit context: Context)( transitioning macro FindLastIndexAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, predicate: Callable, array: typed_array::AttachedJSTypedArray, predicate: Callable,
thisArg: JSAny): Number labels thisArg: JSAny): Number {
Bailout(Number) {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
// 3. Let len be O.[[ArrayLength]]. // 3. Let len be O.[[ArrayLength]].
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 5. Let k be len - 1. // 5. Let k be len - 1.
// 6. Repeat, while k ≥ 0 // 6. Repeat, while k ≥ 0
for (let k: uintptr = length; k-- > 0;) { for (let k: uintptr = length; k-- > 0;) {
witness.Recheck() otherwise goto Bailout(Convert<Number>(k));
// 6a. Let Pk be ! ToString(𝔽(k)). // 6a. Let Pk be ! ToString(𝔽(k)).
// there is no need to cast ToString to load elements. // There is no need to cast ToString to load elements.
// 6b. Let kValue be ! Get(O, Pk). // 6b. Let kValue be ! Get(O, Pk).
const value: JSAny = witness.Load(k); // kValue must be undefined when the buffer was detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, // 6c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue,
// 𝔽(k), O »)). // 𝔽(k), O »)).
...@@ -96,13 +67,7 @@ TypedArrayPrototypeFindLastIndex( ...@@ -96,13 +67,7 @@ TypedArrayPrototypeFindLastIndex(
const predicate = Cast<Callable>(arguments[0]) otherwise NotCallable; const predicate = Cast<Callable>(arguments[0]) otherwise NotCallable;
const thisArg = arguments[1]; const thisArg = arguments[1];
try { return FindLastIndexAllElements(uarray, predicate, thisArg);
return FindLastIndexAllElements(uarray, predicate, thisArg)
otherwise Bailout;
} label Bailout(k: Number) deferred {
return FindLastIndexAllElementsDetachedContinuation(
uarray, predicate, thisArg, k);
}
} label NotCallable deferred { } label NotCallable deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]); ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]);
} label NotTypedArray deferred { } label NotTypedArray deferred {
......
...@@ -12,16 +12,33 @@ transitioning macro ForEachAllElements(implicit context: Context)( ...@@ -12,16 +12,33 @@ transitioning macro ForEachAllElements(implicit context: Context)(
thisArg: JSAny): Undefined { thisArg: JSAny): Undefined {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 6. Repeat, while k < len
for (let k: uintptr = 0; k < length; k++) { for (let k: uintptr = 0; k < length; k++) {
// BUG(4895): We should throw on detached buffers rather than simply exit. // 6a. Let Pk be ! ToString(𝔽(k)).
witness.Recheck() otherwise break; // There is no need to cast ToString to load elements.
const value: JSAny = witness.Load(k);
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer is detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi // TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case. // indices to optimize Convert<Number>(k) for the most common case.
Call( Call(
context, callbackfn, thisArg, value, Convert<Number>(k), context, callbackfn, thisArg, value, Convert<Number>(k),
witness.GetStable()); witness.GetStable());
// 6d. Set k to k + 1. (done by the loop).
} }
// 7. Return undefined.
return Undefined; return Undefined;
} }
......
...@@ -12,11 +12,17 @@ transitioning macro ReduceAllElements(implicit context: Context)( ...@@ -12,11 +12,17 @@ transitioning macro ReduceAllElements(implicit context: Context)(
initialValue: JSAny|TheHole): JSAny { initialValue: JSAny|TheHole): JSAny {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
let accumulator = initialValue; let accumulator = initialValue;
for (let k: uintptr = 0; k < length; k++) { for (let k: uintptr = 0; k < length; k++) {
// BUG(4895): We should throw on detached buffers rather than simply exit. let value: JSAny;
witness.Recheck() otherwise break; try {
const value: JSAny = witness.Load(k); witness.Recheck()
otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
typeswitch (accumulator) { typeswitch (accumulator) {
case (TheHole): { case (TheHole): {
accumulator = value; accumulator = value;
......
...@@ -8,6 +8,7 @@ namespace typed_array { ...@@ -8,6 +8,7 @@ namespace typed_array {
const kBuiltinNameReduceRight: constexpr string = const kBuiltinNameReduceRight: constexpr string =
'%TypedArray%.prototype.reduceRight'; '%TypedArray%.prototype.reduceRight';
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.reduceright
transitioning macro ReduceRightAllElements(implicit context: Context)( transitioning macro ReduceRightAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, callbackfn: Callable, array: typed_array::AttachedJSTypedArray, callbackfn: Callable,
initialValue: JSAny|TheHole): JSAny { initialValue: JSAny|TheHole): JSAny {
...@@ -15,9 +16,14 @@ transitioning macro ReduceRightAllElements(implicit context: Context)( ...@@ -15,9 +16,14 @@ transitioning macro ReduceRightAllElements(implicit context: Context)(
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
let accumulator = initialValue; let accumulator = initialValue;
for (let k: uintptr = length; k-- > 0;) { for (let k: uintptr = length; k-- > 0;) {
// BUG(4895): We should throw on detached buffers rather than simply exit. let value: JSAny;
witness.Recheck() otherwise break; try {
const value: JSAny = witness.Load(k); witness.Recheck()
otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
typeswitch (accumulator) { typeswitch (accumulator) {
case (TheHole): { case (TheHole): {
accumulator = value; accumulator = value;
......
...@@ -7,24 +7,45 @@ ...@@ -7,24 +7,45 @@
namespace typed_array { namespace typed_array {
const kBuiltinNameSome: constexpr string = '%TypedArray%.prototype.some'; const kBuiltinNameSome: constexpr string = '%TypedArray%.prototype.some';
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.some
transitioning macro SomeAllElements(implicit context: Context)( transitioning macro SomeAllElements(implicit context: Context)(
array: typed_array::AttachedJSTypedArray, callbackfn: Callable, array: typed_array::AttachedJSTypedArray, callbackfn: Callable,
thisArg: JSAny): Boolean { thisArg: JSAny): Boolean {
let witness = typed_array::NewAttachedJSTypedArrayWitness(array); let witness = typed_array::NewAttachedJSTypedArrayWitness(array);
const length: uintptr = witness.Get().length; const length: uintptr = witness.Get().length;
// 6. Repeat, while k < len
for (let k: uintptr = 0; k < length; k++) { for (let k: uintptr = 0; k < length; k++) {
// BUG(4895): We should throw on detached buffers rather than simply exit. // 6a. Let Pk be ! ToString(𝔽(k)).
witness.Recheck() otherwise break; // There is no need to cast ToString to load elements.
const value: JSAny = witness.Load(k);
// 6b. Let kValue be ! Get(O, Pk).
// kValue must be undefined when the buffer is detached.
let value: JSAny;
try {
witness.Recheck() otherwise goto IsDetached;
value = witness.Load(k);
} label IsDetached deferred {
value = Undefined;
}
// 6c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue,
// 𝔽(k), O »)).
// TODO(v8:4153): Consider versioning this loop for Smi and non-Smi // TODO(v8:4153): Consider versioning this loop for Smi and non-Smi
// indices to optimize Convert<Number>(k) for the most common case. // indices to optimize Convert<Number>(k) for the most common case.
const result = Call( const result = Call(
context, callbackfn, thisArg, value, Convert<Number>(k), context, callbackfn, thisArg, value, Convert<Number>(k),
witness.GetStable()); witness.GetStable());
// 6d. If testResult is true, return true.
if (ToBoolean(result)) { if (ToBoolean(result)) {
return True; return True;
} }
// 6e. Set k to k + 1. (done by the loop).
} }
// 7. Return false.
return False; return False;
} }
...@@ -41,6 +62,7 @@ TypedArrayPrototypeSome( ...@@ -41,6 +62,7 @@ TypedArrayPrototypeSome(
const callbackfn = Cast<Callable>(arguments[0]) otherwise NotCallable; const callbackfn = Cast<Callable>(arguments[0]) otherwise NotCallable;
const thisArg = arguments[1]; const thisArg = arguments[1];
return SomeAllElements(uarray, callbackfn, thisArg); return SomeAllElements(uarray, callbackfn, thisArg);
} label NotCallable deferred { } label NotCallable deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]); ThrowTypeError(MessageTemplate::kCalledNonCallable, arguments[0]);
......
...@@ -82,18 +82,16 @@ function TestTypedArrayForEach(constructor) { ...@@ -82,18 +82,16 @@ function TestTypedArrayForEach(constructor) {
CheckWrapping({}, Object); CheckWrapping({}, Object);
// Detaching the buffer backing the typed array mid-way should // Detaching the buffer backing the typed array mid-way should
// still make .forEach() finish, and the array should keep being // still make .every() finish, and the array should keep being
// empty after detaching it. // empty after detaching it.
count = 0; count = 0;
a = new constructor(3); a = new constructor(3);
result = a.every(function (n, index, array) { result = a.every(function (n, index, array) {
assertFalse(array[index] === undefined); // don't get here if detached
if (count > 0) %ArrayBufferDetach(array.buffer);
array[index] = n + 1;
count++; count++;
return count > 1 ? array[index] === undefined : true; if (count > 1) %ArrayBufferDetach(array.buffer);
return count > 2 ? n === undefined : true;
}); });
assertEquals(2, count); assertEquals(3, count);
assertEquals(true, result); assertEquals(true, result);
CheckTypedArrayIsDetached(a); CheckTypedArrayIsDetached(a);
assertEquals(undefined, a[0]); assertEquals(undefined, a[0]);
......
...@@ -19,10 +19,22 @@ function TestTypedArrayFilter(constructor) { ...@@ -19,10 +19,22 @@ function TestTypedArrayFilter(constructor) {
assertEquals(1, constructor.prototype.filter.length); assertEquals(1, constructor.prototype.filter.length);
// Throw type error if source array is detached while executing a callback // Throw type error if source array is detached while executing a callback
let ta1 = new constructor(10); let ta1 = new constructor(4);
assertThrows(() => let seen = [];
ta1.filter(() => %ArrayBufferDetach(ta1.buffer)) let result = ta1.filter((val, idx) => {
, TypeError); if (idx === 0) {
%ArrayBufferDetach(ta1.buffer);
}
seen.push(val);
return idx < 3;
});
assertArrayEquals(seen, [0, undefined, undefined, undefined]);
// https://tc39.es/ecma262/#sec-setvalueinbuffer
// undefined values should be converted to numerics.
const expectedResult = [Float32Array, Float64Array].includes(constructor) ?
[0, NaN, NaN] :
[0, 0, 0];
assertArrayEquals(result, expectedResult);
// A new typed array should be created after finishing callbacks // A new typed array should be created after finishing callbacks
var speciesCreated = 0; var speciesCreated = 0;
......
...@@ -85,20 +85,18 @@ function TestTypedArrayForEach(constructor) { ...@@ -85,20 +85,18 @@ function TestTypedArrayForEach(constructor) {
assertEquals(42, a[1]); assertEquals(42, a[1]);
// Detaching the buffer backing the typed array mid-way should // Detaching the buffer backing the typed array mid-way should
// still make .forEach() finish, but exiting early due to the missing // still make .forEach() finish, but the first argument of the callback
// elements, and the array should keep being empty after detaching it. // should be undefined value, and the array should keep being empty after
// TODO(dehrenberg): According to the ES6 spec, accessing or testing // detaching it.
// for members on a detached TypedArray should throw, so really this
// should throw in the third iteration. However, this behavior matches
// the Khronos spec.
a = new constructor(3); a = new constructor(3);
count = 0; count = 0;
a.forEach(function (n, index, array) { a.forEach(function (n, index, array) {
if (count > 0) %ArrayBufferDetach(array.buffer); if (count > 0) %ArrayBufferDetach(array.buffer);
if (count > 1) assertTrue(n === undefined);
array[index] = n + 1; array[index] = n + 1;
count++; count++;
}); });
assertEquals(2, count); assertEquals(3, count);
CheckTypedArrayIsDetached(a); CheckTypedArrayIsDetached(a);
assertEquals(undefined, a[0]); assertEquals(undefined, a[0]);
......
...@@ -69,28 +69,8 @@ ...@@ -69,28 +69,8 @@
# https://code.google.com/p/v8/issues/detail?id=10958 # https://code.google.com/p/v8/issues/detail?id=10958
'language/module-code/eval-gtbndng-indirect-faux-assertion': [FAIL], 'language/module-code/eval-gtbndng-indirect-faux-assertion': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=4895
# Some TypedArray methods throw due to the same bug, from Get
'built-ins/TypedArray/prototype/every/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/every/BigInt/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/filter/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/filter/BigInt/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/find/predicate-may-detach-buffer': [FAIL],
'built-ins/TypedArray/prototype/find/BigInt/predicate-may-detach-buffer': [FAIL],
'built-ins/TypedArray/prototype/findIndex/predicate-may-detach-buffer': [FAIL],
'built-ins/TypedArray/prototype/findIndex/BigInt/predicate-may-detach-buffer': [FAIL],
'built-ins/TypedArray/prototype/forEach/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/forEach/BigInt/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/map/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/map/BigInt/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/reduce/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/reduce/BigInt/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/reduceRight/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/reduceRight/BigInt/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/set/array-arg-primitive-toobject': [FAIL], 'built-ins/TypedArray/prototype/set/array-arg-primitive-toobject': [FAIL],
'built-ins/TypedArray/prototype/set/BigInt/array-arg-primitive-toobject': [FAIL], 'built-ins/TypedArray/prototype/set/BigInt/array-arg-primitive-toobject': [FAIL],
'built-ins/TypedArray/prototype/some/callbackfn-detachbuffer': [FAIL],
'built-ins/TypedArray/prototype/some/BigInt/callbackfn-detachbuffer': [FAIL],
# DataView functions should also throw on detached buffers # DataView functions should also throw on detached buffers
'built-ins/DataView/detached-buffer': [FAIL], 'built-ins/DataView/detached-buffer': [FAIL],
'built-ins/DataView/prototype/byteLength/detached-buffer': [FAIL], 'built-ins/DataView/prototype/byteLength/detached-buffer': [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