diff --git a/BUILD.bazel b/BUILD.bazel index 97c4b39bf4..f87154bc19 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -781,6 +781,7 @@ filegroup( "src/builtins/array-slice.tq", "src/builtins/array-some.tq", "src/builtins/array-splice.tq", + "src/builtins/array-to-reversed.tq", "src/builtins/array-unshift.tq", "src/builtins/array.tq", "src/builtins/arraybuffer.tq", @@ -880,6 +881,7 @@ filegroup( "src/builtins/typed-array-some.tq", "src/builtins/typed-array-sort.tq", "src/builtins/typed-array-subarray.tq", + "src/builtins/typed-array-to-reversed.tq", "src/builtins/typed-array-values.tq", "src/builtins/typed-array.tq", "src/builtins/weak-ref.tq", diff --git a/BUILD.gn b/BUILD.gn index 4e575eb024..a0ce44c8c1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1683,6 +1683,7 @@ torque_files = [ "src/builtins/array-slice.tq", "src/builtins/array-some.tq", "src/builtins/array-splice.tq", + "src/builtins/array-to-reversed.tq", "src/builtins/array-unshift.tq", "src/builtins/array.tq", "src/builtins/arraybuffer.tq", @@ -1782,6 +1783,7 @@ torque_files = [ "src/builtins/typed-array-some.tq", "src/builtins/typed-array-sort.tq", "src/builtins/typed-array-subarray.tq", + "src/builtins/typed-array-to-reversed.tq", "src/builtins/typed-array-values.tq", "src/builtins/typed-array.tq", "src/builtins/weak-ref.tq", diff --git a/src/builtins/array-to-reversed.tq b/src/builtins/array-to-reversed.tq new file mode 100644 index 0000000000..f4a41decbd --- /dev/null +++ b/src/builtins/array-to-reversed.tq @@ -0,0 +1,102 @@ +// 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 { +macro FastPackedArrayToReversed( + implicit context: Context)( + kind: constexpr ElementsKind, elements: FixedArrayBase, + length: Smi): JSArray { + // 3. Let A be ? ArrayCreate(๐”ฝ(len)). + const copy: FixedArrayBase = + AllocateFixedArray(kind, SmiUntag(length), AllocationFlag::kNone); + + // 4. Let k be 0. + let k: Smi = 0; + + // 5. Repeat, while k < len, + while (k < length) { + // a. Let from be ! ToString(๐”ฝ(len - k - 1)). + // b. Let Pk be ! ToString(๐”ฝ(k)). + const from = length - k - 1; + + // c. Let fromValue be ? Get(O, from). + const fromValue: T = LoadElement(elements, from); + + // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). + StoreElement(copy, k, fromValue); + + // e. Set k to k + 1. + ++k; + } + + // 6. Return A. + const map: Map = LoadJSArrayElementsMap(kind, LoadNativeContext(context)); + return NewJSArray(map, copy); +} + +macro TryFastPackedArrayToReversed(implicit context: Context)(receiver: JSAny): + JSArray labels Slow { + const array: FastJSArray = Cast(receiver) otherwise Slow; + + const kind: ElementsKind = array.map.elements_kind; + if (kind == ElementsKind::PACKED_SMI_ELEMENTS) { + return FastPackedArrayToReversed( + ElementsKind::PACKED_SMI_ELEMENTS, array.elements, array.length); + } + if (kind == ElementsKind::PACKED_ELEMENTS) { + return FastPackedArrayToReversed( + ElementsKind::PACKED_ELEMENTS, array.elements, array.length); + } + if (kind == ElementsKind::PACKED_DOUBLE_ELEMENTS) { + return FastPackedArrayToReversed( + ElementsKind::PACKED_DOUBLE_ELEMENTS, array.elements, array.length); + } + + goto Slow; +} + +transitioning macro GenericArrayToReversed( + context: Context, receiver: JSAny): JSAny { + // 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); + + // 3. Let A be ? ArrayCreate(๐”ฝ(len)). + const copy = ArrayCreate(len); + + // 4. Let k be 0. + let k: Number = 0; + + // 5. Repeat, while k < len, + while (k < len) { + // a. Let from be ! ToString(๐”ฝ(len - k - 1)). + // b. Let Pk be ! ToString(๐”ฝ(k)). + const from: Number = len - k - 1; + + // c. Let fromValue be ? Get(object, from). + const fromValue = GetProperty(object, from); + + // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). + FastCreateDataProperty(copy, k, fromValue); + + // e. Set k to k + 1. + ++k; + } + + // 6. Return A. + return copy; +} + +// https://tc39.es/proposal-change-array-by-copy/#sec-array.prototype.toReversed +transitioning javascript builtin ArrayPrototypeToReversed( + js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { + try { + return TryFastPackedArrayToReversed(receiver) otherwise Slow; + } label Slow { + return GenericArrayToReversed(context, receiver); + } +} +} diff --git a/src/builtins/typed-array-createtypedarray.tq b/src/builtins/typed-array-createtypedarray.tq index 7bbe48d2e4..a5d28ef620 100644 --- a/src/builtins/typed-array-createtypedarray.tq +++ b/src/builtins/typed-array-createtypedarray.tq @@ -475,4 +475,15 @@ transitioning macro TypedArraySpeciesCreateByBuffer(implicit context: Context)( Convert(newLength)); return typedArray; } + +transitioning macro TypedArrayCreateSameType(implicit context: Context)( + exemplar: JSTypedArray, newLength: uintptr): JSTypedArray { + const constructor = GetDefaultConstructor(exemplar); + const typedArray = CreateTypedArray( + context, constructor, constructor, Convert(newLength), Undefined, + Undefined); + dcheck(!IsDetachedBuffer(typedArray.buffer)); + dcheck(exemplar.elements_kind == typedArray.elements_kind); + return typedArray; +} } diff --git a/src/builtins/typed-array-to-reversed.tq b/src/builtins/typed-array-to-reversed.tq new file mode 100644 index 0000000000..d812e60a9f --- /dev/null +++ b/src/builtins/typed-array-to-reversed.tq @@ -0,0 +1,43 @@ +// 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 { +// https://tc39.es/proposal-change-array-by-copy/#sec-%typedarray%.prototype.toReversed +transitioning javascript builtin TypedArrayPrototypeToReversed( + js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + // 3. Let length be O.[[ArrayLength]]. + const len = ValidateTypedArrayAndGetLength( + context, receiver, '%TypedArray%.prototype.toReversed'); + const src: JSTypedArray = UnsafeCast(receiver); + + // 4. Let A be ? TypedArrayCreateSameType(O, ยซ ๐”ฝ(length) ยป). + const copy = TypedArrayCreateSameType(src, len); + const accessor: TypedArrayAccessor = + GetTypedArrayAccessor(copy.elements_kind); + + // 5. Let k be 0. + let k: uintptr = 0; + + // 6. Repeat, while k < length, + while (k < len) { + // a. Let from be ! ToString(๐”ฝ(length - k - 1)). + // b. Let Pk be ! ToString(๐”ฝ(k)). + const from = len - k - 1; + + // c. Let fromValue be ! Get(O, from). + const fromValue = accessor.LoadNumeric(src, from); + + // d. Perform ! Set(A, Pk, kValue, true). + accessor.StoreNumeric(context, copy, k, fromValue); + + // e. Set k to k + 1. + ++k; + } + + // 7. Return A. + return copy; +} +} diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h index 43bc05e212..505804fc67 100644 --- a/src/flags/flag-definitions.h +++ b/src/flags/flag-definitions.h @@ -307,7 +307,8 @@ DEFINE_BOOL(harmony_shipping, true, "enable all shipped harmony features") V(harmony_import_assertions, "harmony import assertions") \ V(harmony_temporal, "Temporal") \ V(harmony_shadow_realm, "harmony ShadowRealm") \ - V(harmony_struct, "harmony structs and shared structs") + V(harmony_struct, "harmony structs and shared structs") \ + V(harmony_change_array_by_copy, "harmony change-Array-by-copy") #ifdef V8_INTL_SUPPORT #define HARMONY_INPROGRESS(V) \ diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc index 1a6f36ea23..4a8ce55879 100644 --- a/src/init/bootstrapper.cc +++ b/src/init/bootstrapper.cc @@ -4486,6 +4486,34 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_best_fit_matcher) #undef EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE +void Genesis::InitializeGlobal_harmony_change_array_by_copy() { + if (!FLAG_harmony_change_array_by_copy) return; + + { + Handle array_function(native_context()->array_function(), + isolate()); + Handle array_prototype( + JSObject::cast(array_function->instance_prototype()), isolate()); + + SimpleInstallFunction(isolate_, array_prototype, "toReversed", + Builtin::kArrayPrototypeToReversed, 0, true); + + Handle unscopables = Handle::cast( + JSObject::GetProperty(isolate(), array_prototype, + isolate()->factory()->unscopables_symbol()) + .ToHandleChecked()); + + InstallTrueValuedProperty(isolate_, unscopables, "toReversed"); + } + + { + Handle prototype(native_context()->typed_array_prototype(), + isolate()); + SimpleInstallFunction(isolate_, prototype, "toReversed", + Builtin::kTypedArrayPrototypeToReversed, 0, true); + } +} + void Genesis::InitializeGlobal_harmony_shadow_realm() { if (!FLAG_harmony_shadow_realm) return; Factory* factory = isolate()->factory(); diff --git a/test/mjsunit/harmony/array-to-reversed.js b/test/mjsunit/harmony/array-to-reversed.js new file mode 100644 index 0000000000..cd70a5f682 --- /dev/null +++ b/test/mjsunit/harmony/array-to-reversed.js @@ -0,0 +1,107 @@ +// 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 + +assertEquals(0, Array.prototype.toReversed.length); +assertEquals("toReversed", Array.prototype.toReversed.name); + +(function TestSmiPacked() { + let a = [1,2,3,4]; + let r = a.toReversed(); + assertEquals([1,2,3,4], a); + assertEquals([4,3,2,1], r); + assertFalse(a === r); +})(); + +(function TestDoublePacked() { + let a = [1.1,2.2,3.3,4.4]; + let r = a.toReversed(); + assertEquals([1.1,2.2,3.3,4.4], a); + assertEquals([4.4,3.3,2.2,1.1], r); + assertFalse(a === r); +})(); + +(function TestPacked() { + let a = [true,false,1,42.42]; + let r = a.toReversed(); + assertEquals([true,false,1,42.42], a); + assertEquals([42.42,1,false,true], r); + assertFalse(a === r); +})(); + +(function TestGeneric() { + let a = { length: 4, + get "0"() { return "hello"; }, + get "1"() { return "cursed"; }, + get "2"() { return "java"; }, + get "3"() { return "script" } }; + let r = Array.prototype.toReversed.call(a); + assertEquals("hello", a[0]); + assertEquals(["script","java","cursed","hello"], r); + assertFalse(a === r); + assertTrue(Array.isArray(r)); + assertEquals(Array, r.constructor); +})(); + +(function TestReadOrder() { + let order = []; + let a = { length: 4, + get "0"() { order.push("4th"); return "4th"; }, + get "1"() { order.push("3rd"); return "3rd"; }, + get "2"() { order.push("2nd"); return "2nd"; }, + get "3"() { order.push("1st"); return "1st"; } }; + let r = Array.prototype.toReversed.call(a); + assertEquals(["1st","2nd","3rd","4th"], r); + assertEquals(["1st","2nd","3rd","4th"], order); +})(); + +(function TestTooBig() { + let a = { length: Math.pow(2, 32) }; + assertThrows(() => Array.prototype.toReversed.call(a), RangeError); +})(); + +(function TestNoSpecies() { + class MyArray extends Array { + static get [Symbol.species]() { return MyArray; } + } + assertEquals(Array, (new MyArray()).toReversed().constructor); +})(); + +// All tests after this have an invalidated elements-on-prototype protector. +(function TestNoHoles() { + let a = [,,,,]; + Array.prototype[3] = "on proto"; + let r = a.toReversed(); + assertEquals([,,,,], a); + assertEquals(["on proto",undefined,undefined,undefined], r); + assertTrue(r.hasOwnProperty('0')); + assertTrue(r.hasOwnProperty('1')); + assertTrue(r.hasOwnProperty('2')); + assertTrue(r.hasOwnProperty('3')); +})(); + +(function TestMidIterationShenanigans() { + let a = { length: 4, + "0": 1, + get "1"() { a.length = 1; return 2; }, + "2": 3, + "3": 4, + __proto__: Array.prototype }; + // The length is cached before iteration, so mid-iteration resizing does not + // affect the copied array length. + let r = a.toReversed(); + assertEquals(1, a.length); + assertEquals([4,3,2,1], r); + + // Values can be changed mid-iteration. + a = { length: 4, + "0": 1, + get "1"() { a[0] = "poof"; return 2; }, + "2": 3, + "3": 4, + __proto__: Array.prototype }; + r = a.toReversed(); + assertEquals([4,3,2,"poof"], r); +})(); diff --git a/test/mjsunit/harmony/typedarray-to-reversed.js b/test/mjsunit/harmony/typedarray-to-reversed.js new file mode 100644 index 0000000000..e536d862af --- /dev/null +++ b/test/mjsunit/harmony/typedarray-to-reversed.js @@ -0,0 +1,35 @@ +// Copyright 2018 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 + +d8.file.execute('test/mjsunit/typedarray-helpers.js'); + +(function TestSurface() { + for (let TA of ctors) { + assertEquals(TA.prototype.toReversed.length, 0); + assertEquals(TA.prototype.toReversed.name, "toReversed"); + } +})(); + +(function TestBasic() { + for (let TA of ctors) { + let a = new TA(4); + for (let i = 0; i < 4; i++) { + a[i] = i + ""; + } + let r = a.toReversed(); + for (let i = 0; i < 4; i++) { + assertEquals(a[i], r[4-i-1]); + } + assertFalse(a === r); + } +})(); + +(function TestNoSpecies() { + class MyUint8Array extends Uint8Array { + static get [Symbol.species]() { return MyUint8Array; } + } + assertEquals(Uint8Array, (new MyUint8Array()).toReversed().constructor); +})(); diff --git a/tools/v8heapconst.py b/tools/v8heapconst.py index f5df89b4e8..3dc14f4e34 100644 --- a/tools/v8heapconst.py +++ b/tools/v8heapconst.py @@ -536,30 +536,30 @@ KNOWN_OBJECTS = { ("old_space", 0x04b35): "StringSplitCache", ("old_space", 0x04f3d): "RegExpMultipleCache", ("old_space", 0x05345): "BuiltinsConstantsTable", - ("old_space", 0x05779): "AsyncFunctionAwaitRejectSharedFun", - ("old_space", 0x0579d): "AsyncFunctionAwaitResolveSharedFun", - ("old_space", 0x057c1): "AsyncGeneratorAwaitRejectSharedFun", - ("old_space", 0x057e5): "AsyncGeneratorAwaitResolveSharedFun", - ("old_space", 0x05809): "AsyncGeneratorYieldResolveSharedFun", - ("old_space", 0x0582d): "AsyncGeneratorReturnResolveSharedFun", - ("old_space", 0x05851): "AsyncGeneratorReturnClosedRejectSharedFun", - ("old_space", 0x05875): "AsyncGeneratorReturnClosedResolveSharedFun", - ("old_space", 0x05899): "AsyncIteratorValueUnwrapSharedFun", - ("old_space", 0x058bd): "PromiseAllResolveElementSharedFun", - ("old_space", 0x058e1): "PromiseAllSettledResolveElementSharedFun", - ("old_space", 0x05905): "PromiseAllSettledRejectElementSharedFun", - ("old_space", 0x05929): "PromiseAnyRejectElementSharedFun", - ("old_space", 0x0594d): "PromiseCapabilityDefaultRejectSharedFun", - ("old_space", 0x05971): "PromiseCapabilityDefaultResolveSharedFun", - ("old_space", 0x05995): "PromiseCatchFinallySharedFun", - ("old_space", 0x059b9): "PromiseGetCapabilitiesExecutorSharedFun", - ("old_space", 0x059dd): "PromiseThenFinallySharedFun", - ("old_space", 0x05a01): "PromiseThrowerFinallySharedFun", - ("old_space", 0x05a25): "PromiseValueThunkFinallySharedFun", - ("old_space", 0x05a49): "ProxyRevokeSharedFun", - ("old_space", 0x05a6d): "ShadowRealmImportValueFulfilledSFI", - ("old_space", 0x05a91): "SourceTextModuleExecuteAsyncModuleFulfilledSFI", - ("old_space", 0x05ab5): "SourceTextModuleExecuteAsyncModuleRejectedSFI", + ("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", } # Lower 32 bits of first page addresses for various heap spaces.