1 // Copyright 2017 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 #include "src/builtins/builtins-iterator-gen.h"
6 #include "src/builtins/growable-fixed-array-gen.h"
7
8 #include "src/builtins/builtins-collections-gen.h"
9 #include "src/builtins/builtins-string-gen.h"
10 #include "src/builtins/builtins-utils-gen.h"
11 #include "src/builtins/builtins.h"
12 #include "src/codegen/code-stub-assembler.h"
13 #include "src/heap/factory-inl.h"
14
15 namespace v8 {
16 namespace internal {
17
18 using IteratorRecord = TorqueStructIteratorRecord;
19
GetIteratorMethod( TNode<Context> context, TNode<Object> object)20 TNode<Object> IteratorBuiltinsAssembler::GetIteratorMethod(
21 TNode<Context> context, TNode<Object> object) {
22 return GetProperty(context, object, factory()->iterator_symbol());
23 }
24
GetIterator(TNode<Context> context, TNode<Object> object)25 IteratorRecord IteratorBuiltinsAssembler::GetIterator(TNode<Context> context,
26 TNode<Object> object) {
27 TNode<Object> method = GetIteratorMethod(context, object);
28 return GetIterator(context, object, method);
29 }
30
GetIterator(TNode<Context> context, TNode<Object> object, TNode<Object> method)31 IteratorRecord IteratorBuiltinsAssembler::GetIterator(TNode<Context> context,
32 TNode<Object> object,
33 TNode<Object> method) {
34 Label if_not_callable(this, Label::kDeferred), if_callable(this);
35 GotoIf(TaggedIsSmi(method), &if_not_callable);
36 Branch(IsCallable(CAST(method)), &if_callable, &if_not_callable);
37
38 BIND(&if_not_callable);
39 CallRuntime(Runtime::kThrowIteratorError, context, object);
40 Unreachable();
41
42 BIND(&if_callable);
43 {
44 TNode<Object> iterator = Call(context, method, object);
45
46 Label get_next(this), if_notobject(this, Label::kDeferred);
47 GotoIf(TaggedIsSmi(iterator), &if_notobject);
48 Branch(IsJSReceiver(CAST(iterator)), &get_next, &if_notobject);
49
50 BIND(&if_notobject);
51 CallRuntime(Runtime::kThrowSymbolIteratorInvalid, context);
52 Unreachable();
53
54 BIND(&get_next);
55 TNode<Object> next =
56 GetProperty(context, iterator, factory()->next_string());
57 return IteratorRecord{TNode<JSReceiver>::UncheckedCast(iterator), next};
58 }
59 }
60
IteratorStep( TNode<Context> context, const IteratorRecord& iterator, Label* if_done, base::Optional<TNode<Map>> fast_iterator_result_map)61 TNode<JSReceiver> IteratorBuiltinsAssembler::IteratorStep(
62 TNode<Context> context, const IteratorRecord& iterator, Label* if_done,
63 base::Optional<TNode<Map>> fast_iterator_result_map) {
64 DCHECK_NOT_NULL(if_done);
65 // 1. a. Let result be ? Invoke(iterator, "next", « »).
66 TNode<Object> result = Call(context, iterator.next, iterator.object);
67
68 // 3. If Type(result) is not Object, throw a TypeError exception.
69 Label if_notobject(this, Label::kDeferred), return_result(this);
70 GotoIf(TaggedIsSmi(result), &if_notobject);
71 TNode<HeapObject> heap_object_result = CAST(result);
72 TNode<Map> result_map = LoadMap(heap_object_result);
73
74 if (fast_iterator_result_map) {
75 // Fast iterator result case:
76 Label if_generic(this);
77
78 // 4. Return result.
79 GotoIfNot(TaggedEqual(result_map, *fast_iterator_result_map), &if_generic);
80
81 // IteratorComplete
82 // 2. Return ToBoolean(? Get(iterResult, "done")).
83 TNode<Object> done =
84 LoadObjectField(heap_object_result, JSIteratorResult::kDoneOffset);
85 BranchIfToBooleanIsTrue(done, if_done, &return_result);
86
87 BIND(&if_generic);
88 }
89
90 // Generic iterator result case:
91 {
92 // 3. If Type(result) is not Object, throw a TypeError exception.
93 GotoIfNot(IsJSReceiverMap(result_map), &if_notobject);
94
95 // IteratorComplete
96 // 2. Return ToBoolean(? Get(iterResult, "done")).
97 TNode<Object> done =
98 GetProperty(context, heap_object_result, factory()->done_string());
99 BranchIfToBooleanIsTrue(done, if_done, &return_result);
100 }
101
102 BIND(&if_notobject);
103 CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result);
104 Unreachable();
105
106 BIND(&return_result);
107 return CAST(heap_object_result);
108 }
109
IteratorValue( TNode<Context> context, TNode<JSReceiver> result, base::Optional<TNode<Map>> fast_iterator_result_map)110 TNode<Object> IteratorBuiltinsAssembler::IteratorValue(
111 TNode<Context> context, TNode<JSReceiver> result,
112 base::Optional<TNode<Map>> fast_iterator_result_map) {
113 Label exit(this);
114 TVARIABLE(Object, var_value);
115 if (fast_iterator_result_map) {
116 // Fast iterator result case:
117 Label if_generic(this);
118 TNode<Map> map = LoadMap(result);
119 GotoIfNot(TaggedEqual(map, *fast_iterator_result_map), &if_generic);
120 var_value = LoadObjectField(result, JSIteratorResult::kValueOffset);
121 Goto(&exit);
122
123 BIND(&if_generic);
124 }
125
126 // Generic iterator result case:
127 var_value = GetProperty(context, result, factory()->value_string());
128 Goto(&exit);
129
130 BIND(&exit);
131 return var_value.value();
132 }
133
IterableToList( TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn)134 TNode<JSArray> IteratorBuiltinsAssembler::IterableToList(
135 TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn) {
136 GrowableFixedArray values(state());
137 FillFixedArrayFromIterable(context, iterable, iterator_fn, &values);
138 return values.ToJSArray(context);
139 }
140
IterableToFixedArray( TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn)141 TNode<FixedArray> IteratorBuiltinsAssembler::IterableToFixedArray(
142 TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn) {
143 GrowableFixedArray values(state());
144 FillFixedArrayFromIterable(context, iterable, iterator_fn, &values);
145 TNode<FixedArray> new_array = values.ToFixedArray();
146 return new_array;
147 }
148
FillFixedArrayFromIterable( TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn, GrowableFixedArray* values)149 void IteratorBuiltinsAssembler::FillFixedArrayFromIterable(
150 TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn,
151 GrowableFixedArray* values) {
152 // 1. Let iteratorRecord be ? GetIterator(items, method).
153 IteratorRecord iterator_record = GetIterator(context, iterable, iterator_fn);
154
155 // 2. Let values be a new empty List.
156
157 // The GrowableFixedArray has already been created. It's ok if we do this step
158 // out of order, since creating an empty List is not observable.
159
160 Label loop_start(this, {values->var_array(), values->var_length(),
161 values->var_capacity()}),
162 done(this);
163 Goto(&loop_start);
164 // 3. Let next be true.
165 // 4. Repeat, while next is not false
166 BIND(&loop_start);
167 {
168 // a. Set next to ? IteratorStep(iteratorRecord).
169 TNode<JSReceiver> next = IteratorStep(context, iterator_record, &done);
170 // b. If next is not false, then
171 // i. Let nextValue be ? IteratorValue(next).
172 TNode<Object> next_value = IteratorValue(context, next);
173 // ii. Append nextValue to the end of the List values.
174 values->Push(next_value);
175 Goto(&loop_start);
176 }
177
178 BIND(&done);
179 }
180
TF_BUILTIN(IterableToList, IteratorBuiltinsAssembler)181 TF_BUILTIN(IterableToList, IteratorBuiltinsAssembler) {
182 auto context = Parameter<Context>(Descriptor::kContext);
183 auto iterable = Parameter<Object>(Descriptor::kIterable);
184 auto iterator_fn = Parameter<Object>(Descriptor::kIteratorFn);
185
186 Return(IterableToList(context, iterable, iterator_fn));
187 }
188
TF_BUILTIN(IterableToFixedArray, IteratorBuiltinsAssembler)189 TF_BUILTIN(IterableToFixedArray, IteratorBuiltinsAssembler) {
190 auto context = Parameter<Context>(Descriptor::kContext);
191 auto iterable = Parameter<Object>(Descriptor::kIterable);
192 auto iterator_fn = Parameter<Object>(Descriptor::kIteratorFn);
193
194 Return(IterableToFixedArray(context, iterable, iterator_fn));
195 }
196
197 #if V8_ENABLE_WEBASSEMBLY
TF_BUILTIN(IterableToFixedArrayForWasm, IteratorBuiltinsAssembler)198 TF_BUILTIN(IterableToFixedArrayForWasm, IteratorBuiltinsAssembler) {
199 auto context = Parameter<Context>(Descriptor::kContext);
200 auto iterable = Parameter<Object>(Descriptor::kIterable);
201 auto expected_length = Parameter<Smi>(Descriptor::kExpectedLength);
202
203 TNode<Object> iterator_fn = GetIteratorMethod(context, iterable);
204 GrowableFixedArray values(state());
205
206 Label done(this);
207
208 FillFixedArrayFromIterable(context, iterable, iterator_fn, &values);
209
210 GotoIf(WordEqual(SmiUntag(expected_length), values.var_length()->value()),
211 &done);
212 Return(CallRuntime(
213 Runtime::kThrowTypeError, context,
214 SmiConstant(MessageTemplate::kWasmTrapMultiReturnLengthMismatch)));
215
216 BIND(&done);
217 Return(values.var_array()->value());
218 }
219 #endif // V8_ENABLE_WEBASSEMBLY
220
StringListFromIterable( TNode<Context> context, TNode<Object> iterable)221 TNode<FixedArray> IteratorBuiltinsAssembler::StringListFromIterable(
222 TNode<Context> context, TNode<Object> iterable) {
223 Label done(this);
224 GrowableFixedArray list(state());
225 // 1. If iterable is undefined, then
226 // a. Return a new empty List.
227 GotoIf(IsUndefined(iterable), &done);
228
229 // 2. Let iteratorRecord be ? GetIterator(items).
230 IteratorRecord iterator_record = GetIterator(context, iterable);
231
232 // 3. Let list be a new empty List.
233
234 Label loop_start(this,
235 {list.var_array(), list.var_length(), list.var_capacity()});
236 Goto(&loop_start);
237 // 4. Let next be true.
238 // 5. Repeat, while next is not false
239 Label if_isnotstringtype(this, Label::kDeferred),
240 if_exception(this, Label::kDeferred);
241 BIND(&loop_start);
242 {
243 // a. Set next to ? IteratorStep(iteratorRecord).
244 TNode<JSReceiver> next = IteratorStep(context, iterator_record, &done);
245 // b. If next is not false, then
246 // i. Let nextValue be ? IteratorValue(next).
247 TNode<Object> next_value = IteratorValue(context, next);
248 // ii. If Type(nextValue) is not String, then
249 GotoIf(TaggedIsSmi(next_value), &if_isnotstringtype);
250 TNode<Uint16T> next_value_type = LoadInstanceType(CAST(next_value));
251 GotoIfNot(IsStringInstanceType(next_value_type), &if_isnotstringtype);
252 // iii. Append nextValue to the end of the List list.
253 list.Push(next_value);
254 Goto(&loop_start);
255 // 5.b.ii
256 BIND(&if_isnotstringtype);
257 {
258 // 1. Let error be ThrowCompletion(a newly created TypeError object).
259 TVARIABLE(Object, var_exception);
260 {
261 compiler::ScopedExceptionHandler handler(this, &if_exception,
262 &var_exception);
263 CallRuntime(Runtime::kThrowTypeError, context,
264 SmiConstant(MessageTemplate::kIterableYieldedNonString),
265 next_value);
266 }
267 Unreachable();
268
269 // 2. Return ? IteratorClose(iteratorRecord, error).
270 BIND(&if_exception);
271 TNode<HeapObject> message = GetPendingMessage();
272 SetPendingMessage(TheHoleConstant());
273 IteratorCloseOnException(context, iterator_record);
274 CallRuntime(Runtime::kReThrowWithMessage, context, var_exception.value(),
275 message);
276 Unreachable();
277 }
278 }
279
280 BIND(&done);
281 // 6. Return list.
282 return list.ToFixedArray();
283 }
284
TF_BUILTIN(StringListFromIterable, IteratorBuiltinsAssembler)285 TF_BUILTIN(StringListFromIterable, IteratorBuiltinsAssembler) {
286 auto context = Parameter<Context>(Descriptor::kContext);
287 auto iterable = Parameter<Object>(Descriptor::kIterable);
288
289 Return(StringListFromIterable(context, iterable));
290 }
291
TF_BUILTIN(StringFixedArrayFromIterable, IteratorBuiltinsAssembler)292 TF_BUILTIN(StringFixedArrayFromIterable, IteratorBuiltinsAssembler) {
293 auto context = Parameter<Context>(Descriptor::kContext);
294 auto iterable = Parameter<Object>(Descriptor::kIterable);
295
296 Return(StringListFromIterable(context, iterable));
297 }
298
299 // This builtin always returns a new JSArray and is thus safe to use even in the
300 // presence of code that may call back into user-JS. This builtin will take the
301 // fast path if the iterable is a fast array and the Array prototype and the
302 // Symbol.iterator is untouched. The fast path skips the iterator and copies the
303 // backing store to the new array. Note that if the array has holes, the holes
304 // will be copied to the new array, which is inconsistent with the behavior of
305 // an actual iteration, where holes should be replaced with undefined (if the
306 // prototype has no elements). To maintain the correct behavior for holey
307 // arrays, use the builtins IterableToList or IterableToListWithSymbolLookup.
TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler)308 TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) {
309 auto context = Parameter<Context>(Descriptor::kContext);
310 auto iterable = Parameter<Object>(Descriptor::kIterable);
311 auto iterator_fn = Parameter<Object>(Descriptor::kIteratorFn);
312
313 Label slow_path(this);
314
315 GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable), &slow_path);
316
317 // The fast path will copy holes to the new array.
318 TailCallBuiltin(Builtin::kCloneFastJSArray, context, iterable);
319
320 BIND(&slow_path);
321 TailCallBuiltin(Builtin::kIterableToList, context, iterable, iterator_fn);
322 }
323
FastIterableToList( TNode<Context> context, TNode<Object> iterable, TVariable<JSArray>* var_result, Label* slow)324 void IteratorBuiltinsAssembler::FastIterableToList(
325 TNode<Context> context, TNode<Object> iterable,
326 TVariable<JSArray>* var_result, Label* slow) {
327 Label done(this), check_string(this), check_map(this), check_set(this);
328
329 // Always call the `next()` builtins when the debugger is
330 // active, to ensure we capture side-effects correctly.
331 GotoIf(IsDebugActive(), slow);
332
333 GotoIfNot(
334 Word32Or(IsFastJSArrayWithNoCustomIteration(context, iterable),
335 IsFastJSArrayForReadWithNoCustomIteration(context, iterable)),
336 &check_string);
337
338 // Fast path for fast JSArray.
339 *var_result = CAST(
340 CallBuiltin(Builtin::kCloneFastJSArrayFillingHoles, context, iterable));
341 Goto(&done);
342
343 BIND(&check_string);
344 {
345 Label string_maybe_fast_call(this);
346 StringBuiltinsAssembler string_assembler(state());
347 string_assembler.BranchIfStringPrimitiveWithNoCustomIteration(
348 iterable, context, &string_maybe_fast_call, &check_map);
349
350 BIND(&string_maybe_fast_call);
351 const TNode<IntPtrT> length = LoadStringLengthAsWord(CAST(iterable));
352 // Use string length as conservative approximation of number of codepoints.
353 GotoIf(
354 IntPtrGreaterThan(length, IntPtrConstant(JSArray::kMaxFastArrayLength)),
355 slow);
356 *var_result = CAST(CallBuiltin(Builtin::kStringToList, context, iterable));
357 Goto(&done);
358 }
359
360 BIND(&check_map);
361 {
362 Label map_fast_call(this);
363 BranchIfIterableWithOriginalKeyOrValueMapIterator(
364 state(), iterable, context, &map_fast_call, &check_set);
365
366 BIND(&map_fast_call);
367 *var_result =
368 CAST(CallBuiltin(Builtin::kMapIteratorToList, context, iterable));
369 Goto(&done);
370 }
371
372 BIND(&check_set);
373 {
374 Label set_fast_call(this);
375 BranchIfIterableWithOriginalValueSetIterator(state(), iterable, context,
376 &set_fast_call, slow);
377
378 BIND(&set_fast_call);
379 *var_result =
380 CAST(CallBuiltin(Builtin::kSetOrSetIteratorToList, context, iterable));
381 Goto(&done);
382 }
383
384 BIND(&done);
385 }
386
FastIterableToList( TNode<Context> context, TNode<Object> iterable, Label* slow)387 TNode<JSArray> IteratorBuiltinsAssembler::FastIterableToList(
388 TNode<Context> context, TNode<Object> iterable, Label* slow) {
389 TVARIABLE(JSArray, var_fast_result);
390 FastIterableToList(context, iterable, &var_fast_result, slow);
391 return var_fast_result.value();
392 }
393
394 // This builtin loads the property Symbol.iterator as the iterator, and has fast
395 // paths for fast arrays, for primitive strings, for sets and set iterators, and
396 // for map iterators. These fast paths will only be taken if Symbol.iterator and
397 // the Iterator prototype are not modified in a way that changes the original
398 // iteration behavior.
399 // * In case of fast holey arrays, holes will be converted to undefined to
400 // reflect iteration semantics. Note that replacement by undefined is only
401 // correct when the NoElements protector is valid.
402 // * In case of map/set iterators, there is an additional requirement that the
403 // iterator is not partially consumed. To be spec-compliant, after spreading
404 // the iterator is set to be exhausted.
TF_BUILTIN(IterableToListWithSymbolLookup, IteratorBuiltinsAssembler)405 TF_BUILTIN(IterableToListWithSymbolLookup, IteratorBuiltinsAssembler) {
406 auto context = Parameter<Context>(Descriptor::kContext);
407 auto iterable = Parameter<Object>(Descriptor::kIterable);
408
409 Label slow_path(this);
410
411 GotoIfForceSlowPath(&slow_path);
412
413 TVARIABLE(JSArray, var_result);
414 FastIterableToList(context, iterable, &var_result, &slow_path);
415 Return(var_result.value());
416
417 BIND(&slow_path);
418 {
419 TNode<Object> iterator_fn = GetIteratorMethod(context, iterable);
420 TailCallBuiltin(Builtin::kIterableToList, context, iterable, iterator_fn);
421 }
422 }
423
TF_BUILTIN(GetIteratorWithFeedbackLazyDeoptContinuation, IteratorBuiltinsAssembler)424 TF_BUILTIN(GetIteratorWithFeedbackLazyDeoptContinuation,
425 IteratorBuiltinsAssembler) {
426 auto context = Parameter<Context>(Descriptor::kContext);
427 auto receiver = Parameter<Object>(Descriptor::kReceiver);
428 // TODO(v8:10047): Use TaggedIndex here once TurboFan supports it.
429 auto call_slot_smi = Parameter<Smi>(Descriptor::kCallSlot);
430 auto feedback = Parameter<FeedbackVector>(Descriptor::kFeedback);
431 auto iterator_method = Parameter<Object>(Descriptor::kResult);
432
433 // Note, that the builtin also expects the call_slot as a Smi.
434 TNode<Object> result =
435 CallBuiltin(Builtin::kCallIteratorWithFeedback, context, receiver,
436 iterator_method, call_slot_smi, feedback);
437 Return(result);
438 }
439
440 // This builtin creates a FixedArray based on an Iterable and doesn't have a
441 // fast path for anything.
TF_BUILTIN(IterableToFixedArrayWithSymbolLookupSlow, IteratorBuiltinsAssembler)442 TF_BUILTIN(IterableToFixedArrayWithSymbolLookupSlow,
443 IteratorBuiltinsAssembler) {
444 auto context = Parameter<Context>(Descriptor::kContext);
445 auto iterable = Parameter<Object>(Descriptor::kIterable);
446
447 TNode<Object> iterator_fn = GetIteratorMethod(context, iterable);
448 TailCallBuiltin(Builtin::kIterableToFixedArray, context, iterable,
449 iterator_fn);
450 }
451
452 } // namespace internal
453 } // namespace v8
454