Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(56)

Side by Side Diff: src/builtins/builtins-async-generator.cc

Issue 2622833002: WIP [esnext] implement async iteration proposal (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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-async.h"
6 #include "src/builtins/builtins-utils.h"
7 #include "src/builtins/builtins.h"
8 #include "src/code-factory.h"
9 #include "src/code-stub-assembler.h"
10 #include "src/frames-inl.h"
11
12 namespace v8 {
13 namespace internal {
14
15 namespace {
16
17 // Describe fields of Context associated with AsyncGeneratorAwait resume
18 // closures.
19 class AwaitContext {
20 public:
21 enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
22 };
23
24 } // anonymous namespace
25
26 // https://tc39.github.io/proposal-async-iteration/
27 // Section #sec-asyncgenerator-prototype-next
28 TF_BUILTIN(AsyncGeneratorPrototypeNext, AsyncBuiltinsAssembler) {
29 Node* const generator = Parameter(0);
30 Node* const value = Parameter(1);
31 Node* const context = Parameter(4);
32 AsyncGeneratorEnqueue(context, generator, value,
33 JSAsyncGeneratorObject::kNext,
34 "[AsyncGenerator].prototype.next");
35 }
36
37 // https://tc39.github.io/proposal-async-iteration/
38 // Section #sec-asyncgenerator-prototype-return
39 TF_BUILTIN(AsyncGeneratorPrototypeReturn, AsyncBuiltinsAssembler) {
40 Node* generator = Parameter(0);
41 Node* value = Parameter(1);
42 Node* context = Parameter(4);
43 AsyncGeneratorEnqueue(context, generator, value,
44 JSAsyncGeneratorObject::kReturn,
45 "[AsyncGenerator].prototype.return");
46 }
47
48 // https://tc39.github.io/proposal-async-iteration/
49 // Section #sec-asyncgenerator-prototype-throw
50 TF_BUILTIN(AsyncGeneratorPrototypeThrow, AsyncBuiltinsAssembler) {
51 Node* generator = Parameter(0);
52 Node* value = Parameter(1);
53 Node* context = Parameter(4);
54 AsyncGeneratorEnqueue(context, generator, value,
55 JSAsyncGeneratorObject::kThrow,
56 "[AsyncGenerator].prototype.throw");
57 }
58
59 TF_BUILTIN(AsyncGeneratorYield, AsyncBuiltinsAssembler) {
60 Node* const generator = Parameter(0);
61 Node* const value = Parameter(1);
62 Node* const context = Parameter(4);
63
64 CSA_ASSERT_JS_ARGC_EQ(this, 1);
65 CSA_SLOW_ASSERT(this,
66 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
67
68 Node* request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
69
70 Node* const promise =
71 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
72
73 // The request Promise must still be pending at this point.
74 CSA_SLOW_ASSERT(this,
75 WordEqual(LoadObjectField(promise, JSPromise::kStatusOffset),
76 SmiConstant(v8::Promise::kPending)));
77
78 // If a yield expression is reached, we must not be awaiting any value.
79 CSA_SLOW_ASSERT(this,
80 IsUndefined(LoadObjectField(
81 request, AsyncGeneratorRequest::kAwaitedPromiseOffset)));
82
83 Node* const iter_result = AllocateJSIteratorResult(context, value, false);
84
85 InternalResolvePromise(context, promise, iter_result);
86
87 AsyncGeneratorResumeNext(context, generator);
88
89 Return(UndefinedConstant());
90 }
91
92 TF_BUILTIN(AsyncGeneratorAwaitResolveClosure, AsyncBuiltinsAssembler) {
93 Node* value = Parameter(1);
94 Node* context = Parameter(4);
95 AsyncGeneratorAwaitResumeClosure(context, value,
96 JSAsyncGeneratorObject::kNext);
97 Return(UndefinedConstant());
98 }
99
100 TF_BUILTIN(AsyncGeneratorAwaitRejectClosure, AsyncBuiltinsAssembler) {
101 Node* value = Parameter(1);
102 Node* context = Parameter(4);
103 AsyncGeneratorAwaitResumeClosure(context, value,
104 JSAsyncGeneratorObject::kThrow);
105 Return(UndefinedConstant());
106 }
107
108 TF_BUILTIN(AsyncGeneratorAwaitUncaught, AsyncBuiltinsAssembler) {
109 const bool kIsCatchable = false;
110 AsyncGeneratorAwait(kIsCatchable);
111 }
112
113 TF_BUILTIN(AsyncGeneratorAwaitCaught, AsyncBuiltinsAssembler) {
114 const bool kIsCatchable = true;
115 AsyncGeneratorAwait(kIsCatchable);
116 }
117
118 // Shared implementation for the 3 Async Iterator protocol methods of Async
119 // Generators.
120 void AsyncBuiltinsAssembler::AsyncGeneratorEnqueue(
121 Node* context, Node* generator, Node* value,
122 JSAsyncGeneratorObject::ResumeMode resume_mode, const char* method_name) {
123 // AsyncGeneratorEnqueue produces a new Promise, and appends it to the list
124 // of async generator requests to be executed. If the generator is not
125 // presently executing, then this method will loop through, processing each
126 // request from front to back.
127 // This loop is resides in AsyncGeneratorResumeNext.
128 Node* promise = AllocateAndInitJSPromise(context);
129
130 Label enqueue(this), if_receiverisincompatible(this, Label::kDeferred);
131
132 GotoIf(TaggedIsSmi(generator), &if_receiverisincompatible);
133 Branch(HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE), &enqueue,
134 &if_receiverisincompatible);
135
136 Bind(&enqueue);
137 {
138 Label done(this);
139 Node* const req =
140 AllocateAsyncGeneratorRequest(resume_mode, value, promise);
141
142 AddAsyncGeneratorRequestToQueue(generator, req);
143
144 // Let state be generator.[[AsyncGeneratorState]]
145 // If state is not "executing", then
146 // Perform AsyncGeneratorResumeNext(Generator)
147 // Check if the {receiver} is running or already closed.
148 Node* continuation =
149 LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset);
150
151 GotoIf(SmiEqual(continuation,
152 SmiConstant(JSAsyncGeneratorObject::kGeneratorExecuting)),
153 &done);
154
155 AsyncGeneratorResumeNext(context, generator, continuation);
156
157 Goto(&done);
158 Bind(&done);
159 Return(promise);
160 }
161
162 Bind(&if_receiverisincompatible);
163 {
164 Node* native_context = LoadNativeContext(context);
165 Node* make_type_error =
166 LoadContextElement(native_context, Context::MAKE_TYPE_ERROR_INDEX);
167 Handle<String> method =
168 factory()->NewStringFromAsciiChecked(method_name, TENURED);
169 Node* error =
170 CallJS(CodeFactory::Call(isolate()), context, make_type_error,
171 UndefinedConstant(),
172 SmiConstant(MessageTemplate::kIncompatibleMethodReceiver),
173 HeapConstant(method), generator);
174
175 CallRuntime(Runtime::kPromiseReject, context, promise, error,
176 TrueConstant());
177 Return(promise);
178 }
179 }
180
181 void AsyncBuiltinsAssembler::AsyncGeneratorResumeNext(Node* context,
182 Node* generator,
183 Node* continuation) {
184 // Loop through each queued request, from first to last, and resuming the
185 // generator. Will not resume generator if resumed with an abrupt completion
186 // such as via the .throw() or .return() methods, or if an Await is
187 // in progress.
188 Variable var_request(this, MachineRepresentation::kTagged);
189 Variable var_continuation(this, MachineRepresentation::kTaggedSigned);
190 var_continuation.Bind(continuation);
191
192 Variable* loop_vars[] = {&var_request, &var_continuation};
193 Label loop(this, arraysize(loop_vars),
194 reinterpret_cast<Variable **>(loop_vars)),
195 done(this);
196
197 var_request.Bind(
198 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
199 Branch(WordEqual(var_request.value(), UndefinedConstant()), &done, &loop);
200 Bind(&loop);
201 {
202 AsyncGeneratorResumeNextStep(context, generator, var_request.value(),
203 var_continuation.value(), &done);
204
205 var_continuation.Bind(LoadObjectField(
206 generator, JSAsyncGeneratorObject::kContinuationOffset));
207 var_request.Bind(
208 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
209
210 // Continue loop if there is another request queued up.
211 Branch(WordEqual(var_request.value(), UndefinedConstant()), &done, &loop);
212 }
213
214 Bind(&done);
215 }
216
217 void AsyncBuiltinsAssembler::AsyncGeneratorResumeNextStep(Node* context,
218 Node* generator,
219 Node* request,
220 Node* continuation,
221 Label* exit_loop) {
222 Variable var_return(this, MachineRepresentation::kTagged);
223 var_return.Bind(UndefinedConstant());
224 CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE));
225
226 Label start(this), loop_next(this);
227
228 // Stop consuming the AsyncGeneratorRequest queue and resuming the generator
229 // if an Await is in progress.
230 Node* awaited =
231 LoadObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset);
232 Branch(IsUndefined(awaited), &start, exit_loop);
233
234 Bind(&start);
235
236 // Let completion be next.[[Completion]]
237 Node* resume_mode =
238 LoadObjectField(request, AsyncGeneratorRequest::kResumeModeOffset);
239 Node* value = LoadObjectField(request, AsyncGeneratorRequest::kValueOffset);
240
241 // If completion is an abrupt completion, then
242 Label if_abruptcompletion(this), check_ifcompleted(this),
243 resume_generator(this), if_return(this, &var_return), if_throw(this);
244 Branch(SmiEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kNext)),
245 &check_ifcompleted, &if_abruptcompletion);
246
247 Bind(&if_abruptcompletion);
248 {
249 Label check_completed(this), if_completed(this);
250
251 // If state is "suspendedStart", then
252 // Set generator.[[AsyncGeneratorState]] to "completed"
253 // Set state to "completed"
254 GotoIf(WordNotEqual(continuation, SmiConstant(0)), &check_completed);
255 StoreObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset,
256 SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed));
257 Goto(&if_completed);
258
259 Bind(&check_completed);
260 Branch(WordEqual(continuation,
261 SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)),
262 &if_completed, &resume_generator);
263
264 Bind(&if_completed);
265 {
266 // If state is "completed", then
267 // If completion.[[Type]] is return, then return
268 // ! AsyncGeneratorResolve(generator, completion.[[Value]], true).
269 // Else, return ! AsyncGeneratorReject(generator,
270 // completion.[[Value]])
271 var_return.Bind(value);
272 Branch(
273 WordEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kReturn)),
274 &if_return, &if_throw);
275 }
276 }
277
278 Bind(&check_ifcompleted);
279 {
280 // Else if state is "completed", then return !
281 // AsyncGeneratorResolve(generator, undefined, true).
282 Branch(WordEqual(continuation,
283 SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)),
284 &if_return, &resume_generator);
285 }
286
287 Bind(&resume_generator);
288 {
289 CSA_ASSERT(this, SmiAboveOrEqual(continuation, SmiConstant(0)));
290 Node* result = CallStub(CodeFactory::ResumeGenerator(isolate()), context,
291 value, generator, resume_mode);
292 Goto(&loop_next);
293 }
294
295 Bind(&if_return);
296 {
297 Node* const request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
298 Node* const promise =
299 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
300 Node* const result =
301 AllocateJSIteratorResult(context, var_return.value(), true);
302 PromiseFulfill(context, promise, result, v8::Promise::kFulfilled);
303 Goto(&loop_next);
304 }
305
306 Bind(&if_throw);
307 {
308 Node* request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
309
310 // Let promiseCapability be next.[[Capability]].
311 Node* promise =
312 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
313
314 // Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
315 CallRuntime(Runtime::kPromiseReject, context, promise, value,
316 TrueConstant());
317 Goto(&loop_next);
318 }
319
320 Bind(&loop_next);
321 }
322
323 Node* AsyncBuiltinsAssembler::AllocateAsyncGeneratorRequest(
324 JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
325 Node* promise) {
326 CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
327 Node* request = Allocate(AsyncGeneratorRequest::kSize);
328 StoreMapNoWriteBarrier(request, Heap::kAsyncGeneratorRequestMapRootIndex);
329 StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kNextOffset,
330 UndefinedConstant());
331 StoreObjectFieldNoWriteBarrier(request,
332 AsyncGeneratorRequest::kResumeModeOffset,
333 SmiConstant(resume_mode));
334 StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kValueOffset,
335 resume_value);
336 StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kPromiseOffset,
337 promise);
338 StoreObjectFieldNoWriteBarrier(request,
339 AsyncGeneratorRequest::kAwaitedPromiseOffset,
340 UndefinedConstant());
341 return request;
342 }
343
344 void AsyncBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
345 Node* context, Node* value,
346 JSAsyncGeneratorObject::ResumeMode resume_mode) {
347 Label done(this);
348 Node* const generator =
349 LoadContextElement(context, AwaitContext::kGeneratorSlot);
350 CSA_SLOW_ASSERT(this,
351 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
352
353 Node* const request =
354 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
355 CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE));
356
357 Node* const promise =
358 LoadObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset);
359 CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
360 CSA_SLOW_ASSERT(
361 this, WordNotEqual(LoadObjectField(promise, JSPromise::kStatusOffset),
362 SmiConstant(v8::Promise::kPending)));
363
364 StoreObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset,
365 UndefinedConstant());
366
367 Node* const continuation =
368 LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset);
369
370 GotoUnless(SmiAboveOrEqual(continuation, SmiConstant(0)), &done);
371 CallStub(CodeFactory::ResumeGenerator(isolate()), context, value, generator,
372 SmiConstant(resume_mode));
373
374 Goto(&done);
375 Bind(&done);
376 AsyncGeneratorResumeNext(context, generator);
377 }
378
379 void AsyncBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
380 Node* generator = Parameter(1);
381 Node* value = Parameter(2);
382 Node* context = Parameter(5);
383
384 CSA_ASSERT_JS_ARGC_EQ(this, 2);
385
386 CSA_SLOW_ASSERT(this,
387 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
388
389 Node* const request =
390 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
391 CSA_SLOW_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
392
393 NodeGenerator1 closure_context = [&](Node* native_context) -> Node* {
394 Node* const context =
395 CreatePromiseContext(native_context, AwaitContext::kLength);
396 StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
397 generator);
398 return context;
399 };
400
401 Node* outer_promise =
402 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
403
404 const int reject_index = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;
405 STATIC_ASSERT((Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN + 1) ==
406 Context::ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN);
407
408 Node* promise = Await(context, generator, value, outer_promise,
409 closure_context, reject_index, is_catchable);
410
411 CSA_SLOW_ASSERT(this,
412 IsUndefined(LoadObjectField(
413 request, AsyncGeneratorRequest::kAwaitedPromiseOffset)));
414 StoreObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset,
415 promise);
416 Return(UndefinedConstant());
417 }
418
419 void AsyncBuiltinsAssembler::AddAsyncGeneratorRequestToQueue(Node* generator,
420 Node* request) {
421 Variable var_current(this, MachineRepresentation::kTagged);
422 Label empty(this), loop(this, &var_current), done(this);
423
424 var_current.Bind(
425 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
426 Branch(IsUndefined(var_current.value()), &empty, &loop);
427
428 Bind(&empty);
429 {
430 StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, request);
431 Goto(&done);
432 }
433
434 Bind(&loop);
435 {
436 Label loop_next(this), next_empty(this);
437 Node* current = var_current.value();
438 Node* next = LoadObjectField(current, AsyncGeneratorRequest::kNextOffset);
439
440 Branch(IsUndefined(next), &next_empty, &loop_next);
441 Bind(&next_empty);
442 {
443 StoreObjectField(current, AsyncGeneratorRequest::kNextOffset, request);
444 Goto(&done);
445 }
446
447 Bind(&loop_next);
448 {
449 var_current.Bind(next);
450 Goto(&loop);
451 }
452 }
453 Bind(&done);
454 }
455
456 Node* AsyncBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
457 Node* generator) {
458 // Removes and returns the first AsyncGeneratorRequest from a
459 // JSAsyncGeneratorObject's queue. Asserts that the queue is not empty.
460 CSA_SLOW_ASSERT(this,
461 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
462
463 Node* request =
464 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
465 CSA_SLOW_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
466
467 Node* next = LoadObjectField(request, AsyncGeneratorRequest::kNextOffset);
468
469 StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
470 return request;
471 }
472
473 } // namespace internal
474 } // namespace v8
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698