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