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

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

Issue 2622833002: WIP [esnext] implement async iteration proposal (Closed)
Patch Set: Make (most of) for-await-of work (no IteratorClose yet..) 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);
jgruber 2017/01/13 13:34:39 I just recently landed CreateIterResultObject, per
caitp 2017/01/13 14:36:04 Sgtm
caitp 2017/01/17 19:23:11 Done.
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
96 AsyncGeneratorAwaitResumeClosure(context, value,
97 JSAsyncGeneratorObject::kNext);
98 Return(UndefinedConstant());
99 }
100
101 TF_BUILTIN(AsyncGeneratorAwaitRejectClosure, AsyncBuiltinsAssembler) {
102 Node* value = Parameter(1);
103 Node* context = Parameter(4);
104
105 AsyncGeneratorAwaitResumeClosure(context, value,
106 JSAsyncGeneratorObject::kThrow);
107 Return(UndefinedConstant());
108 }
109
110 TF_BUILTIN(AsyncGeneratorAwaitUncaught, AsyncBuiltinsAssembler) {
111 const bool kIsCatchable = false;
112 AsyncGeneratorAwait(kIsCatchable);
113 }
114
115 TF_BUILTIN(AsyncGeneratorAwaitCaught, AsyncBuiltinsAssembler) {
116 const bool kIsCatchable = true;
117 AsyncGeneratorAwait(kIsCatchable);
118 }
119
120 // Shared implementation for the 3 Async Iterator protocol methods of Async
121 // Generators.
122 void AsyncBuiltinsAssembler::AsyncGeneratorEnqueue(
123 Node* context, Node* generator, Node* value,
124 JSAsyncGeneratorObject::ResumeMode resume_mode, const char* method_name) {
125 // AsyncGeneratorEnqueue produces a new Promise, and appends it to the list
126 // of async generator requests to be executed. If the generator is not
127 // presently executing, then this method will loop through, processing each
128 // request from front to back.
129 // This loop is resides in AsyncGeneratorResumeNext.
jgruber 2017/01/13 13:34:42 Nit: is resides
130 Node* promise = AllocateAndInitJSPromise(context);
131
132 Label enqueue(this), if_receiverisincompatible(this, Label::kDeferred);
133
134 GotoIf(TaggedIsSmi(generator), &if_receiverisincompatible);
135 Branch(HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE), &enqueue,
136 &if_receiverisincompatible);
137
138 Bind(&enqueue);
139 {
140 Label done(this);
141 Node* const req =
142 AllocateAsyncGeneratorRequest(resume_mode, value, promise);
143
144 AddAsyncGeneratorRequestToQueue(generator, req);
145
146 // Let state be generator.[[AsyncGeneratorState]]
147 // If state is not "executing", then
148 // Perform AsyncGeneratorResumeNext(Generator)
149 // Check if the {receiver} is running or already closed.
150 Node* continuation =
151 LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset);
152
153 GotoIf(SmiEqual(continuation,
154 SmiConstant(JSAsyncGeneratorObject::kGeneratorExecuting)),
155 &done);
156
157 AsyncGeneratorResumeNext(context, generator, continuation);
158
159 Goto(&done);
160 Bind(&done);
161 Return(promise);
162 }
163
164 Bind(&if_receiverisincompatible);
165 {
166 Node* native_context = LoadNativeContext(context);
167 Node* make_type_error =
168 LoadContextElement(native_context, Context::MAKE_TYPE_ERROR_INDEX);
169 Handle<String> method =
170 factory()->NewStringFromAsciiChecked(method_name, TENURED);
171 Node* error =
172 CallJS(CodeFactory::Call(isolate()), context, make_type_error,
173 UndefinedConstant(),
174 SmiConstant(MessageTemplate::kIncompatibleMethodReceiver),
175 HeapConstant(method), generator);
176
177 CallRuntime(Runtime::kPromiseReject, context, promise, error,
178 TrueConstant());
179 Return(promise);
180 }
181 }
182
183 void AsyncBuiltinsAssembler::AsyncGeneratorResumeNext(Node* context,
184 Node* generator,
185 Node* continuation) {
186 // Loop through each queued request, from first to last, and resuming the
187 // generator. Will not resume generator if resumed with an abrupt completion
188 // such as via the .throw() or .return() methods, or if an Await is
189 // in progress.
190 Variable var_request(this, MachineRepresentation::kTagged);
191 Variable var_continuation(this, MachineRepresentation::kTaggedSigned);
192 var_continuation.Bind(continuation);
193
194 Variable* loop_vars[] = {&var_request, &var_continuation};
195 Label loop(this, arraysize(loop_vars),
196 reinterpret_cast<Variable **>(loop_vars)),
197 done(this);
198
199 var_request.Bind(
200 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
201 Branch(WordEqual(var_request.value(), UndefinedConstant()), &done, &loop);
202 Bind(&loop);
203 {
204 AsyncGeneratorResumeNextStep(context, generator, var_request.value(),
205 var_continuation.value(), &done);
206
207 var_continuation.Bind(LoadObjectField(
208 generator, JSAsyncGeneratorObject::kContinuationOffset));
209 var_request.Bind(
210 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
211
212 // Continue loop if there is another request queued up.
213 Branch(WordEqual(var_request.value(), UndefinedConstant()), &done, &loop);
214 }
215
216 Bind(&done);
217 }
218
219 void AsyncBuiltinsAssembler::AsyncGeneratorResumeNextStep(Node* context,
220 Node* generator,
221 Node* request,
222 Node* continuation,
223 Label* exit_loop) {
224 Variable var_return(this, MachineRepresentation::kTagged);
225 var_return.Bind(UndefinedConstant());
226 CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE));
227
228 Label start(this), loop_next(this);
229
230 // Stop consuming the AsyncGeneratorRequest queue and resuming the generator
231 // if an Await is in progress.
232 Node* awaited =
233 LoadObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset);
234 Branch(IsUndefined(awaited), &start, exit_loop);
235
236 Bind(&start);
237
238 // Let completion be next.[[Completion]]
239 Node* resume_mode =
240 LoadObjectField(request, AsyncGeneratorRequest::kResumeModeOffset);
241 Node* value = LoadObjectField(request, AsyncGeneratorRequest::kValueOffset);
242
243 // If completion is an abrupt completion, then
244 Label if_abruptcompletion(this), check_ifcompleted(this),
245 resume_generator(this), if_return(this, &var_return), if_throw(this);
246 Branch(SmiEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kNext)),
247 &check_ifcompleted, &if_abruptcompletion);
248
249 Bind(&if_abruptcompletion);
250 {
251 Label check_completed(this), if_completed(this);
252
253 // If state is "suspendedStart", then
254 // Set generator.[[AsyncGeneratorState]] to "completed"
255 // Set state to "completed"
256 GotoIf(WordNotEqual(continuation, SmiConstant(0)), &check_completed);
257 StoreObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset,
jgruber 2017/01/13 13:34:36 I guess we could use the NoWriteBarrier variant he
caitp 2017/01/17 19:23:11 Done.
258 SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed));
259 Goto(&if_completed);
260
261 Bind(&check_completed);
262 Branch(WordEqual(continuation,
jgruber 2017/01/13 13:34:38 SmiEqual here and at other smi comparisons.
caitp 2017/01/17 19:23:11 Done.
263 SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)),
264 &if_completed, &resume_generator);
265
266 Bind(&if_completed);
267 {
268 // If state is "completed", then
269 // If completion.[[Type]] is return, then return
270 // ! AsyncGeneratorResolve(generator, completion.[[Value]], true).
271 // Else, return ! AsyncGeneratorReject(generator,
272 // completion.[[Value]])
273 var_return.Bind(value);
274 Branch(
275 WordEqual(resume_mode, SmiConstant(JSAsyncGeneratorObject::kReturn)),
276 &if_return, &if_throw);
277 }
278 }
279
280 Bind(&check_ifcompleted);
281 {
282 // Else if state is "completed", then return !
283 // AsyncGeneratorResolve(generator, undefined, true).
284 Branch(WordEqual(continuation,
285 SmiConstant(JSAsyncGeneratorObject::kGeneratorClosed)),
286 &if_return, &resume_generator);
287 }
288
289 Bind(&resume_generator);
290 {
291 CSA_ASSERT(this, SmiAboveOrEqual(continuation, SmiConstant(0)));
292 Node* result = CallStub(CodeFactory::ResumeGenerator(isolate()), context,
293 value, generator, resume_mode);
294 Goto(&loop_next);
295 }
296
297 Bind(&if_return);
298 {
299 Node* const request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
300 Node* const promise =
301 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
302 Node* const result =
303 AllocateJSIteratorResult(context, var_return.value(), true);
304 PromiseFulfill(context, promise, result, v8::Promise::kFulfilled);
305 Goto(&loop_next);
306 }
307
308 Bind(&if_throw);
309 {
310 Node* request = TakeFirstAsyncGeneratorRequestFromQueue(generator);
311
312 // Let promiseCapability be next.[[Capability]].
313 Node* promise =
314 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
315
316 // Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
317 CallRuntime(Runtime::kPromiseReject, context, promise, value,
318 TrueConstant());
319 Goto(&loop_next);
320 }
321
322 Bind(&loop_next);
323 }
324
325 Node* AsyncBuiltinsAssembler::AllocateAsyncGeneratorRequest(
326 JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
327 Node* promise) {
328 CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
329 Node* request = Allocate(AsyncGeneratorRequest::kSize);
330 StoreMapNoWriteBarrier(request, Heap::kAsyncGeneratorRequestMapRootIndex);
331 StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kNextOffset,
332 UndefinedConstant());
333 StoreObjectFieldNoWriteBarrier(request,
334 AsyncGeneratorRequest::kResumeModeOffset,
335 SmiConstant(resume_mode));
336 StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kValueOffset,
337 resume_value);
338 StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kPromiseOffset,
339 promise);
340 StoreObjectFieldNoWriteBarrier(request,
341 AsyncGeneratorRequest::kAwaitedPromiseOffset,
342 UndefinedConstant());
343 return request;
344 }
345
346 void AsyncBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
347 Node* context, Node* value,
348 JSAsyncGeneratorObject::ResumeMode resume_mode) {
349 Label done(this);
350 Node* const generator =
351 LoadContextElement(context, AwaitContext::kGeneratorSlot);
352 CSA_SLOW_ASSERT(this,
353 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
354
355 Node* const request =
356 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
357 CSA_SLOW_ASSERT(this, HasInstanceType(request, ASYNC_GENERATOR_REQUEST_TYPE));
358
359 Node* const promise =
360 LoadObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset);
361 CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
362 CSA_SLOW_ASSERT(
363 this, WordNotEqual(LoadObjectField(promise, JSPromise::kStatusOffset),
364 SmiConstant(v8::Promise::kPending)));
365
366 StoreObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset,
jgruber 2017/01/13 13:34:36 NoWriteBarrier
caitp 2017/01/13 14:36:04 Acknowledged.
caitp 2017/01/17 19:23:11 and done
367 UndefinedConstant());
368
369 Node* const continuation =
370 LoadObjectField(generator, JSAsyncGeneratorObject::kContinuationOffset);
371
372 GotoUnless(SmiAboveOrEqual(continuation, SmiConstant(0)), &done);
jgruber 2017/01/13 13:34:35 Isn't an unsigned '>= 0' comparison always true?
caitp 2017/01/13 14:36:04 If the generator is executing or closed, no.
jgruber 2017/01/13 15:46:40 My point was that there is no way to make SmiAbove
caitp 2017/01/17 19:23:11 I understand now, per offline discussion. Changed
373
374 CallStub(CodeFactory::ResumeGenerator(isolate()), context, value, generator,
375 SmiConstant(resume_mode));
376
377 Goto(&done);
378 Bind(&done);
379 AsyncGeneratorResumeNext(context, generator);
380 }
381
382 void AsyncBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable) {
383 Node* generator = Parameter(1);
384 Node* value = Parameter(2);
385 Node* context = Parameter(5);
386
387 CSA_ASSERT_JS_ARGC_EQ(this, 2);
388
389 CSA_SLOW_ASSERT(this,
390 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
391
392 Node* const request =
393 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
394 CSA_SLOW_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
395
396 NodeGenerator1 closure_context = [&](Node* native_context) -> Node* {
397 Node* const context =
398 CreatePromiseContext(native_context, AwaitContext::kLength);
399 StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
400 generator);
401 return context;
402 };
403
404 Node* outer_promise =
405 LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
406
407 const int reject_index = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;
408 STATIC_ASSERT((Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN + 1) ==
409 Context::ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN);
410
411 Node* promise = Await(context, generator, value, outer_promise,
412 closure_context, reject_index, is_catchable);
413
414 CSA_SLOW_ASSERT(this,
415 IsUndefined(LoadObjectField(
416 request, AsyncGeneratorRequest::kAwaitedPromiseOffset)));
417 StoreObjectField(request, AsyncGeneratorRequest::kAwaitedPromiseOffset,
418 promise);
419 Return(UndefinedConstant());
420 }
421
422 void AsyncBuiltinsAssembler::AddAsyncGeneratorRequestToQueue(Node* generator,
423 Node* request) {
424 Variable var_current(this, MachineRepresentation::kTagged);
425 Label empty(this), loop(this, &var_current), done(this);
426
427 var_current.Bind(
428 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
429 Branch(IsUndefined(var_current.value()), &empty, &loop);
430
431 Bind(&empty);
432 {
433 StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, request);
434 Goto(&done);
435 }
436
437 Bind(&loop);
438 {
439 Label loop_next(this), next_empty(this);
440 Node* current = var_current.value();
441 Node* next = LoadObjectField(current, AsyncGeneratorRequest::kNextOffset);
442
443 Branch(IsUndefined(next), &next_empty, &loop_next);
444 Bind(&next_empty);
445 {
446 StoreObjectField(current, AsyncGeneratorRequest::kNextOffset, request);
447 Goto(&done);
448 }
449
450 Bind(&loop_next);
451 {
452 var_current.Bind(next);
453 Goto(&loop);
454 }
455 }
456 Bind(&done);
457 }
458
459 Node* AsyncBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
460 Node* generator) {
461 // Removes and returns the first AsyncGeneratorRequest from a
462 // JSAsyncGeneratorObject's queue. Asserts that the queue is not empty.
463 CSA_SLOW_ASSERT(this,
464 HasInstanceType(generator, JS_ASYNC_GENERATOR_OBJECT_TYPE));
465
466 Node* request =
467 LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
468 CSA_SLOW_ASSERT(this, WordNotEqual(request, UndefinedConstant()));
469
470 Node* next = LoadObjectField(request, AsyncGeneratorRequest::kNextOffset);
471
472 StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
473 return request;
474 }
475
476 } // namespace internal
477 } // namespace v8
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698