Skip to content

Commit b51053d

Browse files
expatdannoCommit Bot
authored andcommitted
Reland: [builtins] Implement Array.prototype.slice in Torque
In the process: - add volatile types for FastJSArray and remove the length_fast accessor from JSArray with the application of more rigorous typing. - add micro benchmarks for testing all the interesting slice cases Also update a few assorted places in .tq code to make them more idiomatic. The original version of this patch had an overly agressive assert that has been loosened. TBR=jgruber@chromium.org Change-Id: I56870862f4b124d1b38372daa326182a526c874c Reviewed-on: https://chromium-review.googlesource.com/c/1291375 Reviewed-by: Daniel Clifford <danno@chromium.org> Commit-Queue: Daniel Clifford <danno@chromium.org> Cr-Commit-Position: refs/heads/master@{#56829}
1 parent 0039b34 commit b51053d

18 files changed

Lines changed: 502 additions & 59 deletions

BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@ torque_files = [
917917
"src/builtins/array-join.tq",
918918
"src/builtins/array-lastindexof.tq",
919919
"src/builtins/array-reverse.tq",
920+
"src/builtins/array-slice.tq",
920921
"src/builtins/array-splice.tq",
921922
"src/builtins/array-unshift.tq",
922923
"src/builtins/typed-array.tq",

benchmarks/micro/slice-perf.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2018 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
const kIterations = 1000000;
6+
const kIterationShort = 10000;
7+
const kArraySize = 64;
8+
9+
let smi_array = [];
10+
for (let i = 0; i < kArraySize; ++i) smi_array[i] = Math.floor(Math.random() * 100);
11+
12+
let start = performance.now();
13+
for (let x = 0; x < kIterations; ++x) {
14+
smi_array.slice(0);
15+
}
16+
let stop = performance.now();
17+
print("smi_array copy: " + (Math.floor((stop - start)*10)/10) + " ms");
18+
19+
start = performance.now();
20+
for (let x = 0; x < kIterations; ++x) {
21+
smi_array.slice(x % kArraySize);
22+
}
23+
stop = performance.now();
24+
print("smi_array: " + (Math.floor((stop - start)*10)/10) + " ms");
25+
26+
let double_array = [];
27+
for (let i = 0; i < kArraySize; ++i) double_array[i] = Math.random() * 100;
28+
start = performance.now();
29+
for (let x = 0; x < kIterations; ++x) {
30+
double_array.slice(x % kArraySize);
31+
}
32+
stop = performance.now();
33+
print("double_array: " + (Math.floor((stop - start)*10)/10) + " ms");
34+
35+
let object_array = [];
36+
for (let i = 0; i < kArraySize; ++i) object_array[i] = new Object();
37+
start = performance.now();
38+
for (let x = 0; x < kIterations; ++x) {
39+
object_array.slice(x % kArraySize);
40+
}
41+
stop = performance.now();
42+
print("object_array: " + (Math.floor((stop - start)*10)/10) + " ms");
43+
44+
let dictionary_array = [];
45+
for (let i = 0; i < kArraySize; ++i) dictionary_array[i] = new Object();
46+
dictionary_array[100000] = new Object();
47+
start = performance.now();
48+
for (let x = 0; x < kIterationShort; ++x) {
49+
dictionary_array.slice(x % kArraySize);
50+
}
51+
stop = performance.now();
52+
print("dictionary: " + (Math.floor((stop - start)*10)/10) + " ms");
53+
54+
let arguments_array;
55+
function sloppy() {
56+
arguments_array = arguments;
57+
}
58+
sloppy.apply(null, smi_array);
59+
start = performance.now();
60+
for (let x = 0; x < kIterations; ++x) {
61+
let r = Array.prototype.slice.call(arguments_array, x % kArraySize);
62+
}
63+
stop = performance.now();
64+
print("arguments_array (sloppy): " + (Math.floor((stop - start)*10)/10) + " ms");
65+
66+
function sloppy2 (a) {
67+
arguments_array = arguments;
68+
}
69+
sloppy2.apply(null, smi_array);
70+
start = performance.now();
71+
for (let x = 0; x < kIterations; ++x) {
72+
Array.prototype.slice.call(arguments_array, x % kArraySize);
73+
}
74+
stop = performance.now();
75+
print("arguments_array (fast aliased): " + (Math.floor((stop - start)*10)/10) + " ms");
76+
77+
delete arguments_array[5];
78+
start = performance.now();
79+
for (let x = 0; x < kIterationShort; ++x) {
80+
Array.prototype.slice.call(arguments_array, x % kArraySize);
81+
}
82+
stop = performance.now();
83+
print("arguments_array (slow aliased): " + (Math.floor((stop - start)*10)/10) + " ms");

src/bootstrapper.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,8 +1739,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
17391739
Builtins::kArrayPrototypeShift, 0, false);
17401740
SimpleInstallFunction(isolate_, proto, "unshift",
17411741
Builtins::kArrayPrototypeUnshift, 1, false);
1742-
SimpleInstallFunction(isolate_, proto, "slice",
1743-
Builtins::kArrayPrototypeSlice, 2, false);
1742+
SimpleInstallFunction(isolate_, proto, "slice", Builtins::kArraySlice, 2,
1743+
false);
17441744
SimpleInstallFunction(isolate_, proto, "sort",
17451745
Builtins::kArrayPrototypeSort, 1, false);
17461746
SimpleInstallFunction(isolate_, proto, "splice", Builtins::kArraySplice, 2,

src/builtins/array-lastindexof.tq

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,8 @@ module array {
7575
context: Context, receiver: JSReceiver, searchElement: Object,
7676
from: Number): Object
7777
labels Slow {
78-
EnsureFastJSArray(context, receiver) otherwise Slow;
79-
const array: JSArray = UnsafeCast<JSArray>(receiver);
80-
81-
const length: Smi = array.length_fast;
78+
const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
79+
const length: Smi = array.length;
8280
if (length == 0) return SmiConstant(-1);
8381

8482
const fromSmi: Smi = Cast<Smi>(from) otherwise Slow;

src/builtins/array-reverse.tq

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,22 @@ module array {
143143
return object;
144144
}
145145

146-
macro TryFastPackedArrayReverse(receiver: Object) labels Slow {
147-
const array: JSArray = Cast<JSArray>(receiver) otherwise Slow;
146+
macro TryFastPackedArrayReverse(implicit context: Context)(receiver: Object)
147+
labels Slow {
148+
const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
148149

149150
const kind: ElementsKind = array.map.elements_kind;
150151
if (kind == PACKED_SMI_ELEMENTS) {
151152
EnsureWriteableFastElements(array);
152153
FastPackedArrayReverse<FastPackedSmiElements, Smi>(
153-
array.elements, array.length_fast);
154+
array.elements, array.length);
154155
} else if (kind == PACKED_ELEMENTS) {
155156
EnsureWriteableFastElements(array);
156157
FastPackedArrayReverse<FastPackedObjectElements, Object>(
157-
array.elements, array.length_fast);
158+
array.elements, array.length);
158159
} else if (kind == PACKED_DOUBLE_ELEMENTS) {
159160
FastPackedArrayReverse<FastPackedDoubleElements, float64>(
160-
array.elements, array.length_fast);
161+
array.elements, array.length);
161162
} else {
162163
goto Slow;
163164
}

src/builtins/array-slice.tq

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright 2018 the V8 project authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
module array {
6+
macro HandleSimpleArgumentsSlice(
7+
context: Context, args: JSArgumentsObjectWithLength, start: Smi,
8+
count: Smi): JSArray
9+
labels Bailout {
10+
const end: Smi = start + count;
11+
const sourceElements: FixedArray =
12+
Cast<FixedArray>(args.elements) otherwise Bailout;
13+
if (SmiAbove(end, sourceElements.length)) goto Bailout;
14+
15+
const arrayMap: Map = LoadJSArrayElementsMap(HOLEY_ELEMENTS, context);
16+
const result: JSArray =
17+
AllocateJSArray(HOLEY_ELEMENTS, arrayMap, count, count);
18+
const newElements: FixedArray =
19+
Cast<FixedArray>(result.elements) otherwise Bailout;
20+
CopyElements(
21+
PACKED_ELEMENTS, newElements, Convert<intptr>(0), sourceElements,
22+
Convert<intptr>(start), Convert<intptr>(count));
23+
return result;
24+
}
25+
26+
macro HandleFastAliasedSloppyArgumentsSlice(
27+
context: Context, args: JSArgumentsObjectWithLength, start: Smi,
28+
count: Smi): JSArray
29+
labels Bailout {
30+
const sloppyElements: SloppyArgumentsElements =
31+
Cast<SloppyArgumentsElements>(args.elements) otherwise Bailout;
32+
const sloppyElementsLength: Smi = sloppyElements.length;
33+
const parameterMapLength: Smi =
34+
sloppyElementsLength - kSloppyArgumentsParameterMapStart;
35+
36+
// Check to make sure that the extraction will not access outside the
37+
// defined arguments
38+
const end: Smi = start + count;
39+
const unmappedElements: FixedArray =
40+
Cast<FixedArray>(sloppyElements[kSloppyArgumentsArgumentsIndex])
41+
otherwise Bailout;
42+
const unmappedElementsLength: Smi = unmappedElements.length;
43+
if (SmiAbove(end, unmappedElementsLength)) goto Bailout;
44+
45+
const argumentsContext: Context =
46+
UnsafeCast<Context>(sloppyElements[kSloppyArgumentsContextIndex]);
47+
48+
const arrayMap: Map = LoadJSArrayElementsMap(HOLEY_ELEMENTS, context);
49+
const result: JSArray =
50+
AllocateJSArray(HOLEY_ELEMENTS, arrayMap, count, count);
51+
52+
let indexOut: Smi = 0;
53+
const resultElements: FixedArray = UnsafeCast<FixedArray>(result.elements);
54+
const to: Smi = SmiMin(parameterMapLength, end);
55+
56+
// Fill in the part of the result that map to context-mapped parameters.
57+
for (let current: Smi = start; current < to; ++current) {
58+
const e: Object =
59+
sloppyElements[current + kSloppyArgumentsParameterMapStart];
60+
const newElement: Object = e != Hole ?
61+
argumentsContext[UnsafeCast<Smi>(e)] :
62+
unmappedElements[current];
63+
StoreFixedArrayElementSmi(
64+
resultElements, indexOut++, newElement, SKIP_WRITE_BARRIER);
65+
}
66+
67+
// Fill in the rest of the result that contains the unmapped parameters
68+
// above the formal parameters.
69+
const unmappedFrom: Smi = SmiMin(SmiMax(parameterMapLength, start), end);
70+
const restCount: Smi = end - unmappedFrom;
71+
CopyElements(
72+
PACKED_ELEMENTS, resultElements, Convert<intptr>(indexOut),
73+
unmappedElements, Convert<intptr>(unmappedFrom),
74+
Convert<intptr>(restCount));
75+
return result;
76+
}
77+
78+
macro HandleFastSlice(
79+
context: Context, o: Object, startNumber: Number, countNumber: Number):
80+
JSArray
81+
labels Bailout {
82+
const start: Smi = Cast<Smi>(startNumber) otherwise Bailout;
83+
const count: Smi = Cast<Smi>(countNumber) otherwise Bailout;
84+
assert(start >= 0);
85+
86+
// If the resulting array doesn't fit in new space, use the slow path.
87+
if (count >= kMaxNewSpaceFixedArrayElements) goto Bailout;
88+
89+
typeswitch (o) {
90+
case (a: FastJSArrayForCopy): {
91+
// It's possible to modify the array length from a valueOf
92+
// callback between the original array length read and this
93+
// point. That can change the length of the array backing store,
94+
// in the worst case, making it smaller than the region that needs
95+
// to be copied out. Therefore, re-check the length before calling
96+
// the appropriate fast path. See regress-785804.js
97+
if (SmiAbove(start + count, a.length)) goto Bailout;
98+
return ExtractFastJSArray(context, a, start, count);
99+
}
100+
case (a: JSArgumentsObjectWithLength): {
101+
const nativeContext: NativeContext = LoadNativeContext(context);
102+
const map: Map = a.map;
103+
if (IsFastAliasedArgumentsMap(map)) {
104+
return HandleFastAliasedSloppyArgumentsSlice(context, a, start, count)
105+
otherwise Bailout;
106+
} else if (IsStrictArgumentsMap(map) || IsSloppyArgumentsMap(map)) {
107+
return HandleSimpleArgumentsSlice(context, a, start, count)
108+
otherwise Bailout;
109+
}
110+
}
111+
case (Object): {
112+
}
113+
}
114+
goto Bailout;
115+
}
116+
117+
// https://tc39.github.io/ecma262/#sec-array.prototype.slice
118+
javascript builtin ArraySlice(
119+
context: Context, receiver: Object, ...arguments): Object {
120+
// Handle array cloning case if the receiver is a fast array.
121+
if (arguments.length == 0) {
122+
typeswitch (receiver) {
123+
case (a: FastJSArrayForCopy): {
124+
return CloneFastJSArray(context, a);
125+
}
126+
case (Object): {
127+
}
128+
}
129+
}
130+
131+
// 1. Let O be ? ToObject(this value).
132+
const o: JSReceiver = ToObject_Inline(context, receiver);
133+
134+
// 2. Let len be ? ToLength(? Get(O, "length")).
135+
const len: Number = GetLengthProperty(o);
136+
137+
// 3. Let relativeStart be ? ToInteger(start).
138+
const start: Object = arguments[0];
139+
const relativeStart: Number = ToInteger_Inline(context, start);
140+
141+
// 4. If relativeStart < 0, let k be max((len + relativeStart), 0);
142+
// else let k be min(relativeStart, len).
143+
let k: Number = relativeStart < 0 ? Max((len + relativeStart), 0) :
144+
Min(relativeStart, len);
145+
146+
// 5. If end is undefined, let relativeEnd be len;
147+
// else let relativeEnd be ? ToInteger(end).
148+
const end: Object = arguments[1];
149+
const relativeEnd: Number =
150+
end == Undefined ? len : ToInteger_Inline(context, end);
151+
152+
// 6. If relativeEnd < 0, let final be max((len + relativeEnd), 0);
153+
// else let final be min(relativeEnd, len).
154+
const final: Number =
155+
relativeEnd < 0 ? Max((len + relativeEnd), 0) : Min(relativeEnd, len);
156+
157+
// 7. Let count be max(final - k, 0).
158+
const count: Number = Max(final - k, 0);
159+
160+
assert(0 <= k);
161+
assert(k <= len);
162+
assert(0 <= final);
163+
assert(final <= len);
164+
assert(0 <= count);
165+
assert(count <= len);
166+
167+
try {
168+
return HandleFastSlice(context, o, k, count) otherwise Slow;
169+
}
170+
label Slow {}
171+
172+
// 8. Let A be ? ArraySpeciesCreate(O, count).
173+
const a: JSReceiver = ArraySpeciesCreate(context, o, count);
174+
175+
// 9. Let n be 0.
176+
let n: Number = 0;
177+
178+
// 10. Repeat, while k < final
179+
while (k < final) {
180+
// a. Let Pk be ! ToString(k).
181+
let pK: Number = k;
182+
183+
// b. Let kPresent be ? HasProperty(O, Pk).
184+
const fromPresent: Boolean = HasProperty(o, pK);
185+
186+
// c. If kPresent is true, then
187+
if (fromPresent == True) {
188+
// i. Let kValue be ? Get(O, Pk).
189+
const kValue: Object = GetProperty(o, pK);
190+
191+
// ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(n), kValue).
192+
CreateDataProperty(a, n, kValue);
193+
}
194+
195+
// d. Increase k by 1.
196+
k++;
197+
198+
// e. Increase n by 1.
199+
n++;
200+
}
201+
202+
// 11. Perform ? Set(A, "length", n, true).
203+
SetProperty(a, kLengthString, n);
204+
205+
// 12. Return A.
206+
return a;
207+
}
208+
}

src/builtins/array-unshift.tq

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ module array {
99
context: Context, receiver: Object, arguments: constexpr Arguments):
1010
never
1111
labels Slow {
12-
EnsureFastJSArray(context, receiver) otherwise Slow;
13-
const array: JSArray = UnsafeCast<JSArray>(receiver);
12+
const array: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
1413
EnsureWriteableFastElements(array);
1514

1615
const map: Map = array.map;

src/builtins/array.tq

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module array {
2424
// extract FixedArrays and don't have to worry about FixedDoubleArrays.
2525
assert(IsFastSmiOrTaggedElementsKind(array.map.elements_kind));
2626

27-
const length: Smi = array.length_fast;
27+
const length: Smi = Cast<Smi>(array.length) otherwise unreachable;
2828
array.elements =
2929
ExtractFixedArray(elements, 0, length, length, kFixedArrays);
3030
assert(array.elements.map != kCOWMap);

0 commit comments

Comments
 (0)