v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
builtins-async-iterator-gen.cc
Go to the documentation of this file.
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 <optional>
6
12
13namespace v8 {
14namespace internal {
15
17
18namespace {
19class AsyncFromSyncBuiltinsAssembler : public AsyncBuiltinsAssembler {
20 public:
21 // The 'next' and 'return' take an optional value parameter, and the 'throw'
22 // method take an optional reason parameter.
23 static const int kValueOrReasonArg = 0;
24
25 explicit AsyncFromSyncBuiltinsAssembler(compiler::CodeAssemblerState* state)
26 : AsyncBuiltinsAssembler(state) {}
27
28 using UndefinedMethodHandler = std::function<void(
29 const TNode<NativeContext> native_context, const TNode<JSPromise> promise,
30 const TNode<JSReceiver> sync_iterator, Label* if_exception)>;
31 using SyncIteratorNodeGenerator =
32 std::function<TNode<Object>(TNode<JSReceiver>)>;
33 enum CloseOnRejectionOption { kDoNotCloseOnRejection, kCloseOnRejection };
34 void Generate_AsyncFromSyncIteratorMethod(
35 CodeStubArguments* args, const TNode<Context> context,
36 const TNode<Object> iterator, const TNode<Object> sent_value,
37 const SyncIteratorNodeGenerator& get_method,
38 const UndefinedMethodHandler& if_method_undefined,
39 const char* operation_name, CloseOnRejectionOption close_on_rejection,
40 Label::Type reject_label_type = Label::kDeferred,
41 std::optional<TNode<Object>> initial_exception_value = std::nullopt);
42
43 void Generate_AsyncFromSyncIteratorMethod(
44 CodeStubArguments* args, const TNode<Context> context,
45 const TNode<Object> iterator, const TNode<Object> sent_value,
46 Handle<String> name, const UndefinedMethodHandler& if_method_undefined,
47 const char* operation_name, CloseOnRejectionOption close_on_rejection,
48 Label::Type reject_label_type = Label::kDeferred,
49 std::optional<TNode<Object>> initial_exception_value = std::nullopt) {
50 auto get_method = [=, this](const TNode<JSReceiver> sync_iterator) {
51 return GetProperty(context, sync_iterator, name);
52 };
53 return Generate_AsyncFromSyncIteratorMethod(
54 args, context, iterator, sent_value, get_method, if_method_undefined,
55 operation_name, close_on_rejection, reject_label_type,
56 initial_exception_value);
57 }
58
59 // Load "value" and "done" from an iterator result object. If an exception
60 // is thrown at any point, jumps to the `if_exception` label with exception
61 // stored in `var_exception`.
62 //
63 // Returns a Pair of Nodes, whose first element is the value of the "value"
64 // property, and whose second element is the value of the "done" property,
65 // converted to a Boolean if needed.
66 std::pair<TNode<Object>, TNode<Boolean>> LoadIteratorResult(
67 const TNode<Context> context, const TNode<NativeContext> native_context,
68 const TNode<JSAny> iter_result, Label* if_exception,
69 TVariable<Object>* var_exception);
70
71 // Synthetic Context for the AsyncFromSyncIterator rejection closure that
72 // closes the underlying sync iterator.
73 struct AsyncFromSyncIteratorCloseSyncAndRethrowContext {
74 enum Fields { kSyncIterator = Context::MIN_CONTEXT_SLOTS, kLength };
75 };
76
77 TNode<JSFunction> CreateAsyncFromSyncIteratorCloseSyncAndRethrowClosure(
78 TNode<NativeContext> native_context, TNode<JSReceiver> sync_iterator);
79
80 TNode<Context> AllocateAsyncFromSyncIteratorCloseSyncAndRethrowContext(
81 TNode<NativeContext> native_context, TNode<JSReceiver> sync_iterator);
82};
83
84// This implements common steps found in various AsyncFromSyncIterator prototype
85// methods followed by ES#sec-asyncfromsynciteratorcontinuation. The differences
86// between the various prototype methods are handled by the get_method and
87// if_method_undefined callbacks.
88void AsyncFromSyncBuiltinsAssembler::Generate_AsyncFromSyncIteratorMethod(
89 CodeStubArguments* args, const TNode<Context> context,
90 const TNode<Object> iterator, const TNode<Object> sent_value,
91 const SyncIteratorNodeGenerator& get_method,
92 const UndefinedMethodHandler& if_method_undefined,
93 const char* operation_name, CloseOnRejectionOption close_on_rejection,
94 Label::Type reject_label_type,
95 std::optional<TNode<Object>> initial_exception_value) {
96 const TNode<NativeContext> native_context = LoadNativeContext(context);
97 const TNode<JSPromise> promise = NewJSPromise(context);
98
100 Object, var_exception,
101 initial_exception_value ? *initial_exception_value : UndefinedConstant());
102 Label maybe_close_sync_then_reject_promise(this, reject_label_type);
103 Label maybe_close_sync_if_not_done_then_reject_promise(this,
104 reject_label_type);
105
106 // At this time %AsyncFromSyncIterator% does not escape to user code, and so
107 // cannot be called with an incompatible receiver.
108 CSA_CHECK(this,
109 HasInstanceType(CAST(iterator), JS_ASYNC_FROM_SYNC_ITERATOR_TYPE));
110 TNode<JSAsyncFromSyncIterator> async_iterator = CAST(iterator);
111 const TNode<JSReceiver> sync_iterator = LoadObjectField<JSReceiver>(
112 async_iterator, JSAsyncFromSyncIterator::kSyncIteratorOffset);
113
114 TNode<Object> method = get_method(sync_iterator);
115
116 if (if_method_undefined) {
117 Label if_isnotundefined(this);
118
119 GotoIfNot(IsNullOrUndefined(method), &if_isnotundefined);
120 if_method_undefined(native_context, promise, sync_iterator,
121 &maybe_close_sync_then_reject_promise);
122
123 BIND(&if_isnotundefined);
124 }
125
126 TVARIABLE(JSAny, iter_result);
127 {
128 Label has_sent_value(this), no_sent_value(this), merge(this);
129 ScopedExceptionHandler handler(this, &maybe_close_sync_then_reject_promise,
130 &var_exception);
131 Branch(IntPtrGreaterThan(args->GetLengthWithoutReceiver(),
132 IntPtrConstant(kValueOrReasonArg)),
133 &has_sent_value, &no_sent_value);
134 BIND(&has_sent_value);
135 {
136 iter_result = Call(context, method, sync_iterator, sent_value);
137 Goto(&merge);
138 }
139 BIND(&no_sent_value);
140 {
141 iter_result = Call(context, method, sync_iterator);
142 Goto(&merge);
143 }
144 BIND(&merge);
145 }
146
147 TNode<Object> value;
148 TNode<Boolean> done;
149 std::tie(value, done) =
150 LoadIteratorResult(context, native_context, iter_result.value(),
151 &maybe_close_sync_then_reject_promise, &var_exception);
152
153 const TNode<JSFunction> promise_fun =
154 CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX));
155 CSA_DCHECK(this, IsConstructor(promise_fun));
156
157 // 6. Let valueWrapper be PromiseResolve(%Promise%, « value »).
158 // IfAbruptRejectPromise(valueWrapper, promiseCapability).
159 TNode<Object> value_wrapper;
160 {
161 ScopedExceptionHandler handler(
162 this, &maybe_close_sync_if_not_done_then_reject_promise,
163 &var_exception);
164 value_wrapper = CallBuiltin(Builtin::kPromiseResolve, native_context,
165 promise_fun, value);
166 }
167
168 // 10. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »).
169 const TNode<JSFunction> on_fulfilled =
170 CreateUnwrapClosure(native_context, done);
171
172 // 12. If done is true, or if closeOnRejection is false, then
173 // a. Let onRejected be undefined.
174 // 13. Else,
175 // [...]
176 // b. Let onRejected be CreateBuiltinFunction(closeIterator, 1, "", « »).
177 TNode<Object> on_rejected;
178 if (close_on_rejection == kCloseOnRejection) {
179 on_rejected = Select<Object>(
180 IsTrue(done), [=, this] { return UndefinedConstant(); },
181 [=, this] {
182 return CreateAsyncFromSyncIteratorCloseSyncAndRethrowClosure(
183 native_context, sync_iterator);
184 });
185 } else {
186 on_rejected = UndefinedConstant();
187 }
188
189 // 14. Perform ! PerformPromiseThen(valueWrapper,
190 // onFulfilled, onRejected, promiseCapability).
191 args->PopAndReturn(CallBuiltin<JSAny>(Builtin::kPerformPromiseThen, context,
192 value_wrapper, on_fulfilled,
193 on_rejected, promise));
194
195 Label reject_promise(this);
196 BIND(&maybe_close_sync_if_not_done_then_reject_promise);
197 {
198 if (close_on_rejection == kCloseOnRejection) {
199 GotoIf(IsFalse(done), &maybe_close_sync_then_reject_promise);
200 }
201 Goto(&reject_promise);
202 }
203 BIND(&maybe_close_sync_then_reject_promise);
204 {
205 if (close_on_rejection == kCloseOnRejection) {
206 // 7. If valueWrapper is an abrupt completion, done is false, and
207 // closeOnRejection is true, then
208 // a. Set valueWrapper to Completion(IteratorClose(syncIteratorRecord,
209 // valueWrapper)).
210 TorqueStructIteratorRecord sync_iterator_record = {sync_iterator, {}};
211 IteratorCloseOnException(context, sync_iterator_record.object);
212 }
213 Goto(&reject_promise);
214 }
215 BIND(&reject_promise);
216 {
217 const TNode<Object> exception = var_exception.value();
218 CallBuiltin(Builtin::kRejectPromise, context, promise, exception,
219 TrueConstant());
220 args->PopAndReturn(promise);
221 }
222}
223
224std::pair<TNode<Object>, TNode<Boolean>>
225AsyncFromSyncBuiltinsAssembler::LoadIteratorResult(
226 const TNode<Context> context, const TNode<NativeContext> native_context,
227 const TNode<JSAny> iter_result, Label* if_exception,
228 TVariable<Object>* var_exception) {
229 Label if_fastpath(this), if_slowpath(this), merge(this), to_boolean(this),
230 done(this), if_notanobject(this, Label::kDeferred);
231 GotoIf(TaggedIsSmi(iter_result), &if_notanobject);
232
233 const TNode<Map> iter_result_map = LoadMap(CAST(iter_result));
234 GotoIfNot(JSAnyIsNotPrimitiveMap(iter_result_map), &if_notanobject);
235
236 const TNode<Object> fast_iter_result_map =
237 LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
238
239 TVARIABLE(Object, var_value);
240 TVARIABLE(Object, var_done);
241 Branch(TaggedEqual(iter_result_map, fast_iter_result_map), &if_fastpath,
242 &if_slowpath);
243
244 BIND(&if_fastpath);
245 {
246 TNode<JSObject> fast_iter_result = CAST(iter_result);
247 var_done = LoadObjectField(fast_iter_result, JSIteratorResult::kDoneOffset);
248 var_value =
249 LoadObjectField(fast_iter_result, JSIteratorResult::kValueOffset);
250 Goto(&merge);
251 }
252
253 BIND(&if_slowpath);
254 {
255 ScopedExceptionHandler handler(this, if_exception, var_exception);
256
257 // Let nextDone be IteratorComplete(nextResult).
258 // IfAbruptRejectPromise(nextDone, promiseCapability).
259 const TNode<Object> iter_result_done =
260 GetProperty(context, iter_result, factory()->done_string());
261
262 // Let nextValue be IteratorValue(nextResult).
263 // IfAbruptRejectPromise(nextValue, promiseCapability).
264 const TNode<Object> iter_result_value =
265 GetProperty(context, iter_result, factory()->value_string());
266
267 var_value = iter_result_value;
268 var_done = iter_result_done;
269 Goto(&merge);
270 }
271
272 BIND(&if_notanobject);
273 {
274 // Sync iterator result is not an object --- Produce a TypeError and jump
275 // to the `if_exception` path.
276 const TNode<Object> error = MakeTypeError(
277 MessageTemplate::kIteratorResultNotAnObject, context, iter_result);
278 *var_exception = error;
279 Goto(if_exception);
280 }
281
282 BIND(&merge);
283 // Ensure `iterResult.done` is a Boolean.
284 GotoIf(TaggedIsSmi(var_done.value()), &to_boolean);
285 Branch(IsBoolean(CAST(var_done.value())), &done, &to_boolean);
286
287 BIND(&to_boolean);
288 {
289 const TNode<Object> result =
290 CallBuiltin(Builtin::kToBoolean, context, var_done.value());
291 var_done = result;
292 Goto(&done);
293 }
294
295 BIND(&done);
296 return std::make_pair(var_value.value(), CAST(var_done.value()));
297}
298
299TNode<JSFunction> AsyncFromSyncBuiltinsAssembler::
300 CreateAsyncFromSyncIteratorCloseSyncAndRethrowClosure(
301 TNode<NativeContext> native_context, TNode<JSReceiver> sync_iterator) {
302 const TNode<Context> closure_context =
303 AllocateAsyncFromSyncIteratorCloseSyncAndRethrowContext(native_context,
304 sync_iterator);
305 return AllocateRootFunctionWithContext(
306 RootIndex::kAsyncFromSyncIteratorCloseSyncAndRethrowSharedFun,
307 closure_context, native_context);
308}
309
310TNode<Context> AsyncFromSyncBuiltinsAssembler::
311 AllocateAsyncFromSyncIteratorCloseSyncAndRethrowContext(
312 TNode<NativeContext> native_context, TNode<JSReceiver> sync_iterator) {
313 TNode<Context> context = AllocateSyntheticFunctionContext(
314 native_context, AsyncFromSyncIteratorCloseSyncAndRethrowContext::kLength);
315 StoreContextElementNoWriteBarrier(
316 context, AsyncFromSyncIteratorCloseSyncAndRethrowContext::kSyncIterator,
317 sync_iterator);
318 return context;
319}
320
321} // namespace
322
323// ES#sec-%asyncfromsynciteratorprototype%.next
324TF_BUILTIN(AsyncFromSyncIteratorPrototypeNext, AsyncFromSyncBuiltinsAssembler) {
325 TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
326 UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
327 CodeStubArguments args(this, argc);
328
329 const TNode<Object> iterator = args.GetReceiver();
330 const TNode<Object> value = args.GetOptionalArgumentValue(kValueOrReasonArg);
331 const auto context = Parameter<Context>(Descriptor::kContext);
332
333 auto get_method = [=, this](const TNode<JSReceiver> unused) {
334 return LoadObjectField(CAST(iterator),
335 JSAsyncFromSyncIterator::kNextOffset);
336 };
337 Generate_AsyncFromSyncIteratorMethod(
338 &args, context, iterator, value, get_method, UndefinedMethodHandler(),
339 "[Async-from-Sync Iterator].prototype.next", kCloseOnRejection);
340}
341
342// ES#sec-%asyncfromsynciteratorprototype%.return
343TF_BUILTIN(AsyncFromSyncIteratorPrototypeReturn,
344 AsyncFromSyncBuiltinsAssembler) {
345 TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
346 UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
347 CodeStubArguments args(this, argc);
348
349 const TNode<Object> iterator = args.GetReceiver();
350 const TNode<Object> value = args.GetOptionalArgumentValue(kValueOrReasonArg);
351 const auto context = Parameter<Context>(Descriptor::kContext);
352
353 auto if_return_undefined = [=, this, &args](
355 const TNode<JSPromise> promise,
356 const TNode<JSReceiver> sync_iterator,
357 Label* if_exception) {
358 // If return is undefined, then
359 // Let iterResult be ! CreateIterResultObject(value, true)
360 const TNode<Object> iter_result = CallBuiltin(
361 Builtin::kCreateIterResultObject, context, value, TrueConstant());
362
363 // Perform ! Call(promiseCapability.[[Resolve]], undefined, « iterResult »).
364 // IfAbruptRejectPromise(nextDone, promiseCapability).
365 // Return promiseCapability.[[Promise]].
366 CallBuiltin(Builtin::kResolvePromise, context, promise, iter_result);
367 args.PopAndReturn(promise);
368 };
369
370 Generate_AsyncFromSyncIteratorMethod(
371 &args, context, iterator, value, factory()->return_string(),
372 if_return_undefined, "[Async-from-Sync Iterator].prototype.return",
373 kDoNotCloseOnRejection);
374}
375
376// ES#sec-%asyncfromsynciteratorprototype%.throw
377TF_BUILTIN(AsyncFromSyncIteratorPrototypeThrow,
378 AsyncFromSyncBuiltinsAssembler) {
379 TNode<IntPtrT> argc = ChangeInt32ToIntPtr(
380 UncheckedParameter<Int32T>(Descriptor::kJSActualArgumentsCount));
381 CodeStubArguments args(this, argc);
382
383 const TNode<Object> iterator = args.GetReceiver();
384 const TNode<Object> reason = args.GetOptionalArgumentValue(kValueOrReasonArg);
385 const auto context = Parameter<Context>(Descriptor::kContext);
386
387 // 8. If throw is undefined, then
388 auto if_throw_undefined =
389 [=, this, &args](const TNode<NativeContext> native_context,
390 const TNode<JSPromise> promise,
391 const TNode<JSReceiver> sync_iterator,
392 Label* if_exception) {
393 // a. NOTE: If syncIterator does not have a `throw` method, close it to
394 // give it a chance to clean up before we reject the capability.
395 // b. Let closeCompletion be NormalCompletion(~empty~).
396 // c. Let result be Completion(IteratorClose(syncIteratorRecord,
397 // closeCompletion)).
398 TVARIABLE(Object, var_reject_value);
399 Label done(this);
400 {
401 ScopedExceptionHandler handler(this, &done, &var_reject_value);
402 TorqueStructIteratorRecord sync_iterator_record = {sync_iterator, {}};
403 IteratorClose(context, sync_iterator_record);
404
405 // d. IfAbruptRejectPromise(result, promiseCapability).
406 // (Done below)
407 }
408
409 // e. NOTE: The next step throws a *TypeError* to indicate that there
410 // was a protocol violation: syncIterator does not have a `throw`
411 // method.
412 // f. NOTE: If closing syncIterator does not throw then the result of
413 // that operation is ignored, even if it yields a rejected promise.
414 // g. Perform ! Call(promiseCapability.[[Reject]], *undefined*, « a
415 // newly created *TypeError* object »).
416 var_reject_value =
417 MakeTypeError(MessageTemplate::kThrowMethodMissing, context);
418 Goto(&done);
419 BIND(&done);
420 CallBuiltin(Builtin::kRejectPromise, context, promise,
421 var_reject_value.value(), TrueConstant());
422 args.PopAndReturn(promise);
423 };
424
425 Generate_AsyncFromSyncIteratorMethod(
426 &args, context, iterator, reason, factory()->throw_string(),
427 if_throw_undefined, "[Async-from-Sync Iterator].prototype.throw",
428 kCloseOnRejection, Label::kNonDeferred, reason);
429}
430
431TF_BUILTIN(AsyncFromSyncIteratorCloseSyncAndRethrow,
432 AsyncFromSyncBuiltinsAssembler) {
433 // #sec-asyncfromsynciteratorcontinuation
434 //
435 // 13. [...]
436 // a. Let closeIterator be a new Abstract Closure with parameters (error)
437 // that captures syncIteratorRecord and performs the following steps
438 // when called:
439 // i. Return ? IteratorClose(syncIteratorRecord,
440 // ThrowCompletion(error)).
441
442 auto error = Parameter<Object>(Descriptor::kError);
443 auto context = Parameter<Context>(Descriptor::kContext);
444
445 const TNode<JSReceiver> sync_iterator = CAST(LoadContextElement(
446 context, AsyncFromSyncIteratorCloseSyncAndRethrowContext::kSyncIterator));
447 // iterator.next field is not used by IteratorCloseOnException.
448 TorqueStructIteratorRecord sync_iterator_record = {sync_iterator, {}};
449 IteratorCloseOnException(context, sync_iterator_record.object);
450 Return(CallRuntime(Runtime::kReThrow, context, error));
451}
452
454
455} // namespace internal
456} // namespace v8
#define BIND(label)
#define TVARIABLE(...)
#define CSA_DCHECK(csa,...)
#define CSA_CHECK(csa, x)
static const int kValueOrReasonArg
#define TF_BUILTIN(Name, AssemblerBase)
#define CAST(x)
base::Vector< const DirectHandle< Object > > args
Definition execution.cc:74
TNode< Context > context
ZoneVector< RpoNumber > & result
MovableLabel handler
TNode< Oddball > UndefinedConstant(JSGraph *jsgraph)
compiler::TypedCodeAssemblerVariable< T > TVariable
bool IsNullOrUndefined(Tagged< Object > obj, Isolate *isolate)
!IsContextMap !IsContextMap native_context
Definition map-inl.h:877
Symbol method
std::unique_ptr< ValueMirror > value