Commit 0f510c4a authored by Shu-yu Guo's avatar Shu-yu Guo Committed by V8 LUCI CQ

[change-array-by-copy] Implement with

Bug: v8:12764
Change-Id: I67b9b0e4f3c7ca6a2719c234b7f7605f07f86b28
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3671760Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80874}
parent 5c445ebb
......@@ -783,6 +783,7 @@ filegroup(
"src/builtins/array-splice.tq",
"src/builtins/array-to-reversed.tq",
"src/builtins/array-unshift.tq",
"src/builtins/array-with.tq",
"src/builtins/array.tq",
"src/builtins/arraybuffer.tq",
"src/builtins/base.tq",
......@@ -883,6 +884,7 @@ filegroup(
"src/builtins/typed-array-subarray.tq",
"src/builtins/typed-array-to-reversed.tq",
"src/builtins/typed-array-values.tq",
"src/builtins/typed-array-with.tq",
"src/builtins/typed-array.tq",
"src/builtins/weak-ref.tq",
"src/ic/handler-configuration.tq",
......
......@@ -1683,6 +1683,7 @@ torque_files = [
"src/builtins/array-splice.tq",
"src/builtins/array-to-reversed.tq",
"src/builtins/array-unshift.tq",
"src/builtins/array-with.tq",
"src/builtins/array.tq",
"src/builtins/arraybuffer.tq",
"src/builtins/base.tq",
......@@ -1783,6 +1784,7 @@ torque_files = [
"src/builtins/typed-array-subarray.tq",
"src/builtins/typed-array-to-reversed.tq",
"src/builtins/typed-array-values.tq",
"src/builtins/typed-array-with.tq",
"src/builtins/typed-array.tq",
"src/builtins/weak-ref.tq",
"src/ic/handler-configuration.tq",
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
namespace array {
transitioning macro TryFastPackedArrayWith(implicit context: Context)(
receiver: JSReceiver, len: Number, actualIndex: Number,
value: JSAny): JSArray labels Slow {
// Array#with does not preserve holes and always creates packed Arrays. Holes
// in the source array-like are treated like any other element and the value
// is computed with Get. So, there are only fast paths for packed elements.
const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
if (IsFastPackedElementsKind(array.map.elements_kind)) {
const lenSmi = Cast<Smi>(len) otherwise Slow;
// It is possible that the index coercion shrunk the source array, in which
// case go to the slow case.
if (lenSmi > array.length) goto Slow;
// Steps 7-9 done by copying and overriding the value at index.
const copy = ExtractFastJSArray(context, array, 0, lenSmi);
FastCreateDataProperty(copy, actualIndex, value);
// 10. Return A.
return copy;
}
goto Slow;
}
transitioning macro GenericArrayWith(
context: Context, receiver: JSReceiver, len: Number, actualIndex: Number,
value: JSAny): JSArray {
// 7. Let A be ? ArrayCreate(𝔽(len)).
const copy = ArrayCreate(len);
// 8. Let k be 0.
let k: Number = 0;
// 9. Repeat, while k < len,
while (k < len) {
// a. Let Pk be ! ToString(𝔽(k)).
// b. If k is actualIndex, let fromValue be value.
// c. Else, let fromValue be ? Get(O, Pk).
const fromValue = k == actualIndex ? value : GetProperty(receiver, k);
// d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue).
FastCreateDataProperty(copy, k, fromValue);
// e. Set k to k + 1.
++k;
}
// 10. Return A.
return copy;
}
// https://tc39.es/proposal-change-array-by-copy/#sec-array.prototype.with
transitioning javascript builtin ArrayPrototypeWith(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
const index = arguments[0];
const value = arguments[1];
// 1. Let O be ? ToObject(this value).
const object: JSReceiver = ToObject_Inline(context, receiver);
// 2. Let len be ? LengthOfArrayLike(O).
const len: Number = GetLengthProperty(object);
try {
// 3. Let relativeIndex be ? ToIntegerOrInfinity(index).
const relativeIndex = ToInteger_Inline(index);
// 4. If relativeIndex ≥ 0, let actualIndex be relativeIndex.
// 5. Else, let actualIndex be len + relativeIndex.
// 6. If actualIndex ≥ len or actualIndex < 0, throw a RangeError exception.
const actualIndex =
ConvertRelativeIndex(relativeIndex, len) otherwise OutOfBounds,
OutOfBounds;
try {
return TryFastPackedArrayWith(object, len, actualIndex, value)
otherwise Slow;
} label Slow {
return GenericArrayWith(context, object, len, actualIndex, value);
}
} label OutOfBounds deferred {
ThrowRangeError(MessageTemplate::kInvalid, 'index', index);
}
}
}
......@@ -360,9 +360,11 @@ const kBigIntMaxLength: constexpr intptr generates 'BigInt::kMaxLength';
extern enum MessageTemplate {
kAllPromisesRejected,
kInvalid,
kInvalidArrayBufferLength,
kInvalidArrayLength,
kInvalidIndex,
kInvalidTypedArrayIndex,
kNotConstructor,
kNotGeneric,
kCalledNonCallable,
......@@ -644,6 +646,8 @@ transitioning macro ToInteger_Inline(implicit context: Context)(input: JSAny):
}
}
extern macro ToBigInt(Context, JSAny): BigInt;
extern enum BigIntHandling extends int32
constexpr 'CodeStubAssembler::BigIntHandling' { kConvertToNumber, kThrow }
......@@ -683,6 +687,8 @@ extern macro ThrowRangeError(implicit context: Context)(
constexpr MessageTemplate): never;
extern macro ThrowRangeError(implicit context: Context)(
constexpr MessageTemplate, Object): never;
extern macro ThrowRangeError(implicit context: Context)(
constexpr MessageTemplate, Object, Object): never;
extern macro ThrowTypeError(implicit context: Context)(
constexpr MessageTemplate): never;
extern macro ThrowTypeError(implicit context: Context)(
......@@ -828,6 +834,7 @@ extern macro IsElementsKindInRange(
ElementsKind, constexpr ElementsKind, constexpr ElementsKind): bool;
extern macro IsFastElementsKind(constexpr ElementsKind): constexpr bool;
extern macro IsFastPackedElementsKind(constexpr ElementsKind): constexpr bool;
extern macro IsDoubleElementsKind(constexpr ElementsKind): constexpr bool;
extern macro GetNonRabGsabElementsKind(ElementsKind): ElementsKind;
......@@ -1306,6 +1313,7 @@ extern macro BasicLoadNumberDictionaryElement(NumberDictionary, intptr): JSAny
labels NotData, IfHole;
extern macro IsFastElementsKind(ElementsKind): bool;
extern macro IsFastPackedElementsKind(ElementsKind): bool;
extern macro IsDoubleElementsKind(ElementsKind): bool;
extern macro IsFastSmiOrTaggedElementsKind(ElementsKind): bool;
extern macro IsFastSmiElementsKind(ElementsKind): bool;
......
......@@ -536,7 +536,6 @@ transitioning javascript builtin DataViewPrototypeGetBigInt64(
}
extern macro ToNumber(Context, JSAny): Number;
extern macro ToBigInt(Context, JSAny): BigInt;
extern macro TruncateFloat64ToWord32(float64): uint32;
extern macro DataViewBuiltinsAssembler::StoreWord8(
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
namespace typed_array {
const kBuiltinNameWith: constexpr string = '%TypedArray%.prototype.with';
// https://tc39.es/proposal-change-array-by-copy/#sec-%typedarray%.prototype.with
transitioning javascript builtin TypedArrayPrototypeWith(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
const index = arguments[0];
let value: JSAny = arguments[1];
try {
// 1. Let O be the this value.
// 2. Perform ? ValidateTypedArray(O).
// 3. Let len be O.[[ArrayLength]].
const array: JSTypedArray =
Cast<JSTypedArray>(receiver) otherwise NotTypedArray;
let attachedArrayAndLength = EnsureAttachedAndReadLength(array)
otherwise IsDetachedOrOutOfBounds;
const originalLength = attachedArrayAndLength.length;
// TODO(v8:12764): This change is waiting on upstream
// https://github.com/tc39/proposal-change-array-by-copy/issues/85
if (IsBigInt64ElementsKind(array.elements_kind)) {
// 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value).
value = ToBigInt(context, value);
} else {
// 5. Else, set value to ? ToNumber(value).
value = ToNumber_Inline(value);
}
// 6. Let relativeIndex be ? ToIntegerOrInfinity(index).
const relativeIndex = ToInteger_Inline(index);
// 7. If relativeIndex ≥ 0, let actualIndex be relativeIndex.
// 8. Else, let actualIndex be len + relativeIndex.
const actualIndex: uintptr = ConvertRelativeIndex(
relativeIndex, originalLength) otherwise IndexOutOfBounds,
IndexOutOfBounds;
// 9. If ! IsValidIntegerIndex(O, 𝔽(actualIndex)) is false, throw a
// RangeError exception.
attachedArrayAndLength = EnsureAttachedAndReadLength(array)
otherwise IndexOutOfBounds;
if (actualIndex >= attachedArrayAndLength.length) goto IndexOutOfBounds;
// 10. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »).
const copy = TypedArrayCreateSameType(array, originalLength);
const fastCopyableLength =
UintPtrMin(originalLength, attachedArrayAndLength.length);
// Steps 11-12's copy loop implemented by memmove.
const info = GetTypedArrayElementsInfo(copy);
const countBytes: uintptr =
info.CalculateByteLength(fastCopyableLength) otherwise unreachable;
// TypedArrayCreateSameType always use built-in constructors, and so cannot
// cause the source TypedArray to become detached or OOB.
const srcPtr: RawPtr = array.data_ptr;
if (IsSharedArrayBuffer(array.buffer)) {
CallCRelaxedMemmove(copy.data_ptr, srcPtr, countBytes);
} else {
CallCMemmove(copy.data_ptr, srcPtr, countBytes);
}
// b. If k is actualIndex, then
// i. Perform ? Set(A, Pk, value, true).
const accessor: TypedArrayAccessor =
GetTypedArrayAccessor(copy.elements_kind);
accessor.StoreJSAny(context, copy, actualIndex, value)
otherwise unreachable;
// Fill the remainder with undefined, in case of resize during parameter
// conversion. This is not the same as doing nothing because:
// - Undefined convert to NaN, which is observable when stored into
// Float32 and Float64Arrays
// - Undefined cannot convert to BigInt and throws
let k: uintptr = fastCopyableLength;
while (k < copy.length) {
accessor.StoreJSAny(context, copy, k, Undefined) otherwise unreachable;
++k;
}
// 11. Return A.
return copy;
} label IndexOutOfBounds deferred {
ThrowRangeError(MessageTemplate::kInvalidTypedArrayIndex);
} label NotTypedArray deferred {
ThrowTypeError(MessageTemplate::kNotTypedArray, kBuiltinNameWith);
} label IsDetachedOrOutOfBounds deferred {
ThrowTypeError(MessageTemplate::kDetachedOperation, kBuiltinNameWith);
}
}
}
......@@ -298,7 +298,7 @@ builtin StoreTypedElementNumeric<T : type extends ElementsKind>(
return kStoreSucceded;
}
// Returns True on sucess or False if the typedArrays was detached.
// Returns True on success or False if the typedArrays was detached.
builtin StoreTypedElementJSAny<T : type extends ElementsKind>(
context: Context, typedArray: JSTypedArray, index: uintptr,
value: JSAny): Smi {
......
......@@ -14492,6 +14492,18 @@ TNode<BoolT> CodeStubAssembler::IsFastElementsKind(
Int32Constant(LAST_FAST_ELEMENTS_KIND));
}
TNode<BoolT> CodeStubAssembler::IsFastPackedElementsKind(
TNode<Int32T> elements_kind) {
static_assert(FIRST_ELEMENTS_KIND == FIRST_FAST_ELEMENTS_KIND);
// ElementsKind values that are even are packed. See
// internal::IsFastPackedElementsKind.
static_assert((~PACKED_SMI_ELEMENTS & 1) == 1);
static_assert((~PACKED_ELEMENTS & 1) == 1);
static_assert((~PACKED_DOUBLE_ELEMENTS & 1) == 1);
return Word32And(IsNotSetWord32(elements_kind, 1),
IsFastElementsKind(elements_kind));
}
TNode<BoolT> CodeStubAssembler::IsFastOrNonExtensibleOrSealedElementsKind(
TNode<Int32T> elements_kind) {
static_assert(FIRST_ELEMENTS_KIND == FIRST_FAST_ELEMENTS_KIND);
......
......@@ -2719,6 +2719,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
bool IsFastElementsKind(ElementsKind kind) {
return v8::internal::IsFastElementsKind(kind);
}
TNode<BoolT> IsFastPackedElementsKind(TNode<Int32T> elements_kind);
bool IsFastPackedElementsKind(ElementsKind kind) {
return v8::internal::IsFastPackedElementsKind(kind);
}
TNode<BoolT> IsFastOrNonExtensibleOrSealedElementsKind(
TNode<Int32T> elements_kind);
......
......@@ -4497,6 +4497,8 @@ void Genesis::InitializeGlobal_harmony_change_array_by_copy() {
SimpleInstallFunction(isolate_, array_prototype, "toReversed",
Builtin::kArrayPrototypeToReversed, 0, true);
SimpleInstallFunction(isolate_, array_prototype, "with",
Builtin::kArrayPrototypeWith, 2, true);
Handle<JSObject> unscopables = Handle<JSObject>::cast(
JSObject::GetProperty(isolate(), array_prototype,
......@@ -4511,6 +4513,8 @@ void Genesis::InitializeGlobal_harmony_change_array_by_copy() {
isolate());
SimpleInstallFunction(isolate_, prototype, "toReversed",
Builtin::kTypedArrayPrototypeToReversed, 0, true);
SimpleInstallFunction(isolate_, prototype, "with",
Builtin::kTypedArrayPrototypeWith, 2, true);
}
}
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-change-array-by-copy
"use strict";
assertEquals(2, Array.prototype.with.length);
assertEquals("with", Array.prototype.with.name);
const values = [1, 1.1, true, false, undefined, null, "foo", {}];
(function TestSmiPacked() {
let a = [1,2,3,4];
for (let v of values) {
let b = a.with(1, v);
assertEquals([1,2,3,4], a);
assertEquals([1,v,3,4], b);
assertFalse(a === b);
let c = a.with(-1, v);
assertEquals([1,2,3,4], a);
assertEquals([1,2,3,v], c);
assertFalse(a === c);
}
})();
(function TestDoublePacked() {
let a = [1.1,2.2,3.3,4.4];
for (let v of values) {
let b = a.with(1, v);
assertEquals([1.1,2.2,3.3,4.4], a);
assertEquals([1.1,v,3.3,4.4], b);
assertFalse(a === b);
let c = a.with(-1, v);
assertEquals([1.1,2.2,3.3,4.4], a);
assertEquals([1.1,2.2,3.3,v], c);
assertFalse(a === c);
}
})();
(function TestPacked() {
let a = [true,false,1,42.42];
for (let v of values) {
let b = a.with(1, v);
assertEquals([true,false,1,42.42], a);
assertEquals([true,v,1,42.42], b);
assertFalse(a === b);
let c = a.with(-1, v);
assertEquals([true,false,1,42.42], a);
assertEquals([true,false,1,v], c);
assertFalse(a === c);
}
})();
(function TestGeneric() {
let a = { length: 4,
get "0"() { return "hello"; },
get "1"() { return "cursed"; },
get "2"() { return "java"; },
get "3"() { return "script" } };
for (let v of values) {
let b = Array.prototype.with.call(a, 1, v);
assertEquals("cursed", a[1]);
assertEquals(["hello",v,"java","script"], b);
assertFalse(a === b);
assertTrue(Array.isArray(b));
assertEquals(Array, b.constructor);
let c = Array.prototype.with.call(a, -1, v);
assertEquals("script", a[a.length-1]);
assertEquals(["hello","cursed","java",v], c);
assertFalse(a === c);
assertTrue(Array.isArray(c));
assertEquals(Array, c.constructor);
}
})();
(function TestTooBig() {
let a = { length: Math.pow(2, 32) };
assertThrows(() => Array.prototype.with.call(a, 1, 42), RangeError);
})();
(function TestNoSpecies() {
class MyArray extends Array {
static get [Symbol.species]() { return MyArray; }
}
let a = new MyArray();
a.length = 4;
assertEquals(Array, a.with(1,42).constructor);
})();
(function TestOutOfBounds() {
let a = [1,2,3,4];
assertThrows(() => { a.with(a.length, 42); }, RangeError);
assertThrows(() => { a.with(-a.length - 1, 42); }, RangeError);
})();
(function TestIndexShenanigans() {
let a = [1,2,3,4];
// The index can resize and do other shenanigans since it's coerced before
// iteration.
let b = a.with({ valueOf() { a.length = 2; return 1; } }, 42);
assertEquals([1,2], a);
assertEquals([1,42,undefined,undefined], b);
// Holey.
a = [1,2,3,4,,,];
let c = a.with({ valueOf() { a.length = 2; return 1; } }, 42);
assertEquals([1,2], a);
assertEquals([1,42,undefined,undefined,undefined,undefined], c);
a = [1,2,3,4];
let d = a.with({ valueOf() { a[2] = "barf"; return 1; } }, 42);
assertEquals([1,2,"barf",4], a);
assertEquals([1,42,"barf",4], d);
})();
(function TestValuePassthrough() {
let a = [1,2,3,4];
// The value isn't used in any meaningful sense by with.
a.with(1, { valueOf() { assertUnreachable(); } });
})();
// All tests after this have an invalidated elements-on-prototype protector.
(function TestNoHoles() {
let a = [,,,,];
Array.prototype[3] = "on proto";
for (let v of values) {
let b = a.with(1,v);
assertEquals([undefined,v,undefined,"on proto"], b);
assertTrue(b.hasOwnProperty('0'));
assertTrue(b.hasOwnProperty('1'));
assertTrue(b.hasOwnProperty('2'));
assertTrue(b.hasOwnProperty('3'));
}
})();
This diff is collapsed.
......@@ -536,30 +536,30 @@ KNOWN_OBJECTS = {
("old_space", 0x04b35): "StringSplitCache",
("old_space", 0x04f3d): "RegExpMultipleCache",
("old_space", 0x05345): "BuiltinsConstantsTable",
("old_space", 0x0577d): "AsyncFunctionAwaitRejectSharedFun",
("old_space", 0x057a1): "AsyncFunctionAwaitResolveSharedFun",
("old_space", 0x057c5): "AsyncGeneratorAwaitRejectSharedFun",
("old_space", 0x057e9): "AsyncGeneratorAwaitResolveSharedFun",
("old_space", 0x0580d): "AsyncGeneratorYieldResolveSharedFun",
("old_space", 0x05831): "AsyncGeneratorReturnResolveSharedFun",
("old_space", 0x05855): "AsyncGeneratorReturnClosedRejectSharedFun",
("old_space", 0x05879): "AsyncGeneratorReturnClosedResolveSharedFun",
("old_space", 0x0589d): "AsyncIteratorValueUnwrapSharedFun",
("old_space", 0x058c1): "PromiseAllResolveElementSharedFun",
("old_space", 0x058e5): "PromiseAllSettledResolveElementSharedFun",
("old_space", 0x05909): "PromiseAllSettledRejectElementSharedFun",
("old_space", 0x0592d): "PromiseAnyRejectElementSharedFun",
("old_space", 0x05951): "PromiseCapabilityDefaultRejectSharedFun",
("old_space", 0x05975): "PromiseCapabilityDefaultResolveSharedFun",
("old_space", 0x05999): "PromiseCatchFinallySharedFun",
("old_space", 0x059bd): "PromiseGetCapabilitiesExecutorSharedFun",
("old_space", 0x059e1): "PromiseThenFinallySharedFun",
("old_space", 0x05a05): "PromiseThrowerFinallySharedFun",
("old_space", 0x05a29): "PromiseValueThunkFinallySharedFun",
("old_space", 0x05a4d): "ProxyRevokeSharedFun",
("old_space", 0x05a71): "ShadowRealmImportValueFulfilledSFI",
("old_space", 0x05a95): "SourceTextModuleExecuteAsyncModuleFulfilledSFI",
("old_space", 0x05ab9): "SourceTextModuleExecuteAsyncModuleRejectedSFI",
("old_space", 0x05781): "AsyncFunctionAwaitRejectSharedFun",
("old_space", 0x057a5): "AsyncFunctionAwaitResolveSharedFun",
("old_space", 0x057c9): "AsyncGeneratorAwaitRejectSharedFun",
("old_space", 0x057ed): "AsyncGeneratorAwaitResolveSharedFun",
("old_space", 0x05811): "AsyncGeneratorYieldResolveSharedFun",
("old_space", 0x05835): "AsyncGeneratorReturnResolveSharedFun",
("old_space", 0x05859): "AsyncGeneratorReturnClosedRejectSharedFun",
("old_space", 0x0587d): "AsyncGeneratorReturnClosedResolveSharedFun",
("old_space", 0x058a1): "AsyncIteratorValueUnwrapSharedFun",
("old_space", 0x058c5): "PromiseAllResolveElementSharedFun",
("old_space", 0x058e9): "PromiseAllSettledResolveElementSharedFun",
("old_space", 0x0590d): "PromiseAllSettledRejectElementSharedFun",
("old_space", 0x05931): "PromiseAnyRejectElementSharedFun",
("old_space", 0x05955): "PromiseCapabilityDefaultRejectSharedFun",
("old_space", 0x05979): "PromiseCapabilityDefaultResolveSharedFun",
("old_space", 0x0599d): "PromiseCatchFinallySharedFun",
("old_space", 0x059c1): "PromiseGetCapabilitiesExecutorSharedFun",
("old_space", 0x059e5): "PromiseThenFinallySharedFun",
("old_space", 0x05a09): "PromiseThrowerFinallySharedFun",
("old_space", 0x05a2d): "PromiseValueThunkFinallySharedFun",
("old_space", 0x05a51): "ProxyRevokeSharedFun",
("old_space", 0x05a75): "ShadowRealmImportValueFulfilledSFI",
("old_space", 0x05a99): "SourceTextModuleExecuteAsyncModuleFulfilledSFI",
("old_space", 0x05abd): "SourceTextModuleExecuteAsyncModuleRejectedSFI",
}
# Lower 32 bits of first page addresses for various heap spaces.
......
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