• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import ts from "typescript";
17import { CacheList, getVregisterCache } from "../base/vregisterCache";
18import { Compiler, ControlFlowChange } from "../compiler";
19import {
20    Label,
21    VReg
22} from "../irnodes";
23import { PandaGen } from "../pandagen";
24import { NodeKind } from "../debuginfo";
25import { CatchTable, LabelPair } from "../statement/tryStatement";
26import { FunctionBuilder } from "./functionBuilder";
27import { Iterator } from "../base/iterator";
28import { IteratorType } from "../statement/forOfStatement";
29
30enum ResumeMode {
31    RETURN,
32    THROW,
33    NEXT
34};
35
36export enum AsyncGeneratorState {
37    UNDEFINED,
38    SUSPENDSTART,
39    SUSPENDYIELD,
40    EXECUTING,
41    COMPLETED,
42    AWAITING_RETURN
43}
44
45export class AsyncGeneratorFunctionBuilder extends FunctionBuilder {
46    private compiler: Compiler;
47
48    constructor(pandaGen: PandaGen, compiler: Compiler) {
49        super(pandaGen);
50        this.funcObj = pandaGen.getTemp();
51        this.resumeVal = pandaGen.getTemp();
52        this.resumeType = pandaGen.getTemp();
53        this.beginLabel = new Label();
54        this.endLabel = new Label();
55        this.compiler = compiler;
56    }
57
58    prepare(node: ts.Node): void {
59        let pg = this.pg;
60
61        // backend handle funcobj, frontend set undefined
62        pg.createAsyncGeneratorObj(node, getVregisterCache(pg, CacheList.FUNC));
63        pg.storeAccumulator(node, this.funcObj);
64
65        pg.label(node, this.beginLabel);
66        pg.loadAccumulator(node, getVregisterCache(pg, CacheList.UNDEFINED));
67        pg.suspendGenerator(node, this.funcObj);
68        pg.resumeGenerator(node, this.funcObj);
69        pg.storeAccumulator(node, this.resumeVal);
70    }
71
72    await(node: ts.Node): void {
73        // value is in acc
74        this.functionAwait(node);
75        this.handleMode(node);
76    }
77
78    directReturn(node: ts.Node | NodeKind): void {
79        let pg = this.pg;
80        pg.storeAccumulator(node, this.resumeVal);
81        pg.generatorComplete(node, this.funcObj);
82        pg.asyncgeneratorresolve(node, this.funcObj, this.resumeVal, getVregisterCache(pg, CacheList.TRUE));
83        pg.return(node);
84    }
85
86    explicitReturn(node: ts.Node | NodeKind, empty ? : boolean): void {
87        let pg = this.pg;
88        if (!empty) {
89            pg.asyncFunctionAwaitUncaught(node, this.funcObj);
90            pg.suspendGenerator(node, this.funcObj);
91            this.resumeGenerator(node);
92        }
93        pg.generatorComplete(node, this.funcObj);
94        pg.asyncgeneratorresolve(node, this.funcObj, this.resumeVal, getVregisterCache(pg, CacheList.TRUE));
95        pg.return(node);
96    }
97
98    implicitReturn(node: ts.Node | NodeKind): void {
99        this.pg.loadAccumulator(node, getVregisterCache(this.pg, CacheList.UNDEFINED));
100        this.directReturn(node);
101    }
102
103    yield(node: ts.Node): void {
104        let pg = this.pg;
105
106        // 27.6.3.8.5 Set value to ? Await(value).
107        // vallue is in acc
108        this.await(node);
109        pg.storeAccumulator(node, this.resumeVal);
110
111        // 27.6.3.8.6 Set generator.[[AsyncGeneratorState]] to suspendedYield.
112        pg.generatorYield(node, this.funcObj);
113        /** 27.6.3.8.7 Remove genContext from the execution context stack and restore the execution context that
114         *  is at the top of the execution context stack as the running execution context.
115         *  27.6.3.8.9 Return ! AsyncGeneratorResolve(generator, value, false).
116         */
117        pg.asyncgeneratorresolve(node, this.funcObj, this.resumeVal, getVregisterCache(pg, CacheList.FALSE));
118        this.resumeGenerator(node);
119
120        this.handleAsyncYieldResume(node);
121    }
122
123    yieldStar(node: ts.Node): void {
124        let pg = this.pg;
125        let method = pg.getTemp();
126        let iterator = pg.getTemp();
127        let nextResult = pg.getTemp();
128        let value = pg.getTemp();
129        let done = pg.getTemp();
130        let nextMethod = pg.getTemp();
131        let exitReturn = pg.getTemp();
132
133        // 4. Let iteratorRecord be ? GetIterator(value, generatorKind)
134        let iter: Iterator = new Iterator({iterator: iterator, nextMethod: method}, done, value, pg,
135                                          node, IteratorType.Async, this);
136        iter.getIterator();
137
138        let receivedValue = this.resumeVal;
139        this.resumeVal = nextResult;
140
141        // 6. Let received be NormalCompletion(undefined)
142        pg.storeConst(node, this.resumeVal, CacheList.UNDEFINED);
143        pg.loadAccumulatorInt(node, ResumeMode.NEXT);
144        pg.storeAccumulator(node, this.resumeType);
145        pg.moveVreg(node, nextMethod, iter.method());
146
147        let loopStart = new Label();
148        let throwCompletion = new Label();
149        let returnCompletion = new Label();
150        let callMethod = new Label();
151        let normalOrThrowCompletion = new Label();
152        let iterCompletion = new Label();
153        // 7. Repeat
154        pg.label(node, loopStart);
155        pg.storeConst(node, exitReturn, CacheList.FALSE);
156
157        // a. If received.[[Type]] is normal, then
158        pg.loadAccumulatorInt(node, ResumeMode.NEXT);
159        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, throwCompletion);
160        pg.moveVreg(node, iter.method(), nextMethod);
161        pg.branch(node, callMethod);
162
163        // b. Else if received.[[Type]] is throw, then
164        pg.label(node, throwCompletion);
165        pg.loadAccumulatorInt(node, ResumeMode.THROW);
166        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, returnCompletion);
167
168        // i. Let throw be ? GetMethod(iterator, "throw")
169        iter.getMethod("throw");
170
171        // ii. If throw is not undefined, then
172        pg.branchIfNotUndefined(node, callMethod);
173
174        // iii. Else,
175        // 1. NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop. But first we
176        // need to give iterator a chance to clean up.
177        // 2. Let closeCompletion be Completion { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
178        // 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion).
179        // 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion).
180        iter.close()
181        // 5. NOTE: The next step throws a TypeError to indicate that there was a yield* protocol violation: iterator does
182        // not have a throw method.
183        // 6. Throw a TypeError exception.
184        pg.throwThrowNotExist(node);
185
186        // c. Else,
187        // i. Assert: received.[[Type]] is return
188        pg.label(node, returnCompletion);
189        pg.storeConst(node, exitReturn, CacheList.TRUE);
190        // ii. Let return be ? GetMethod(iterator, "return").
191        iter.getMethod("return");
192
193        // iii. If return is undefined, then
194        pg.branchIfNotUndefined(node, callMethod);
195
196        this.compiler.compileFinallyBeforeCFC(
197            undefined,
198            ControlFlowChange.Break,
199            undefined
200        );
201
202        pg.loadAccumulator(node, this.resumeVal);
203        this.await(node);
204
205        this.directReturn(node);
206
207        pg.label(node, callMethod);
208        // i. Let innerResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « received.[[Value]] »).
209        // 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »).
210        // iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »).
211        iter.callMethodwithValue(this.resumeVal);
212
213        this.await(node);
214        pg.throwIfNotObject(node, this.resumeVal);
215
216        iter.iteratorComplete(this.resumeVal);
217        pg.jumpIfTrue(node, iterCompletion);
218
219        pg.loadAccumulator(node, this.resumeVal);
220
221        iter.iteratorValue(this.resumeVal);
222        this.await(node);
223
224        pg.generatorYield(node, this.funcObj);
225        pg.storeConst(node, done, CacheList.FALSE);
226        pg.asyncgeneratorresolve(node, this.funcObj, this.resumeVal, done);
227
228        this.resumeGenerator(node);
229
230        pg.loadAccumulatorInt(node, ResumeMode.RETURN);
231        pg.condition(node, ts.SyntaxKind.EqualsEqualsToken, this.resumeType, loopStart);
232
233        pg.loadAccumulator(node, this.resumeVal);
234        pg.asyncFunctionAwaitUncaught(node, this.funcObj);
235
236        pg.suspendGenerator(node, this.funcObj);
237        this.resumeGenerator(node);
238
239        pg.loadAccumulatorInt(node, ResumeMode.THROW);
240        pg.condition(node, ts.SyntaxKind.EqualsEqualsToken, this.resumeType, returnCompletion);
241
242        pg.branch(node, loopStart);
243
244        pg.label(node, iterCompletion);
245
246        pg.loadAccumulator(node, exitReturn);
247        pg.jumpIfFalse(node, normalOrThrowCompletion);
248
249        iter.iteratorValue(this.resumeVal);
250
251        this.compiler.compileFinallyBeforeCFC(
252            undefined,
253            ControlFlowChange.Break,
254            undefined
255        )
256
257        this.directReturn(node);
258
259        pg.label(node, normalOrThrowCompletion);
260        iter.iteratorValue(this.resumeVal);
261
262        this.resumeVal = receivedValue;
263
264        pg.freeTemps(method, iterator, nextResult, value, done, nextMethod, exitReturn);
265    }
266
267    private handleAsyncYieldResume(node: ts.Node): void {
268        let pg = this.pg;
269        let notRet = new Label();
270        let normalCompletion = new Label();
271        let notThrow = new Label();
272
273        // 27.6.3.8.8.a If resumptionValue.[[Type]] is not return
274        pg.loadAccumulatorInt(node, ResumeMode.RETURN);
275        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, notRet);
276
277        // 27.6.3.8.8.b Let awaited be Await(resumptionValue.[[Value]])
278        pg.loadAccumulator(node, this.resumeVal);
279        pg.asyncFunctionAwaitUncaught(node, this.funcObj);
280        pg.suspendGenerator(node, this.funcObj);
281        this.resumeGenerator(node);
282
283        // 27.6.3.8.8.c. If awaited.[[Type]] is throw, return Completion(awaited)
284        pg.loadAccumulatorInt(node, ResumeMode.THROW);
285        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, normalCompletion);
286        pg.loadAccumulator(node, this.resumeVal);
287        pg.throw(node);
288
289        pg.label(node, normalCompletion);
290        // 27.6.3.8.8.d. Assert: awaited.[[Type]] is normal.
291        // 27.6.3.8.8.e. Return Completion { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }
292
293        // if there are finally blocks, should implement these at first.
294        this.compiler.compileFinallyBeforeCFC(
295            undefined,
296            ControlFlowChange.Break,
297            undefined
298        );
299        pg.loadAccumulator(node, this.resumeVal);
300        this.directReturn(node);
301
302        pg.label(node, notRet);
303        // 27.6.3.8.8.a return Completion(resumptionValue)
304        pg.loadAccumulatorInt(node, ResumeMode.THROW);
305        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, notThrow);
306        pg.loadAccumulator(node, this.resumeVal);
307        pg.throw(node);
308
309        pg.label(node, notThrow);
310        pg.loadAccumulator(node, this.resumeVal);
311    }
312
313    private handleMode(node: ts.Node): void {
314        let pandaGen = this.pg;
315
316        pandaGen.getResumeMode(node, this.funcObj);
317        pandaGen.storeAccumulator(node, this.resumeType);
318
319        // .throw(value)
320        pandaGen.loadAccumulatorInt(node, ResumeMode.THROW);
321
322        let notThrowLabel = new Label();
323
324        pandaGen.condition(node, ts.SyntaxKind.EqualsEqualsToken, this.resumeType, notThrowLabel);
325        pandaGen.loadAccumulator(node, this.resumeVal);
326        pandaGen.throw(node);
327
328        // .next(value)
329        pandaGen.label(node, notThrowLabel);
330        pandaGen.loadAccumulator(node, this.resumeVal);
331    }
332
333    resolve(node: ts.Node | NodeKind, value: VReg): void {
334        let pandaGen = this.pg;
335        pandaGen.asyncgeneratorresolve(node, this.funcObj, value, getVregisterCache(pandaGen, CacheList.TRUE));
336    }
337
338    cleanUp(node: ts.Node): void {
339        let pandaGen = this.pg;
340        pandaGen.label(node, this.endLabel);
341        // catch
342        pandaGen.storeAccumulator(node, this.resumeVal);
343        pandaGen.generatorComplete(node, this.funcObj);
344        pandaGen.loadAccumulator(node, this.resumeVal);
345        pandaGen.asyncgeneratorreject(node, this.funcObj); // exception is in acc
346        pandaGen.return(NodeKind.INVALID);
347        this.pg.freeTemps(this.funcObj, this.resumeVal, this.resumeType);
348        new CatchTable(pandaGen, this.endLabel, new LabelPair(this.beginLabel, this.endLabel));
349    }
350}
351