• 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) {
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) {
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) {
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        // pg.moveVreg(node, nextResult, this.resumeVal);
212        iter.callMethodwithValue(this.resumeVal);
213
214        this.await(node);
215        pg.throwIfNotObject(node, this.resumeVal);
216
217        iter.iteratorComplete(this.resumeVal);
218        pg.jumpIfTrue(node, iterCompletion);
219
220        pg.loadAccumulator(node, this.resumeVal);
221
222        iter.iteratorValue(this.resumeVal);
223        this.await(node);
224
225        pg.generatorYield(node, this.funcObj);
226        pg.storeConst(node, done, CacheList.False);
227        pg.asyncgeneratorresolve(node, this.funcObj, this.resumeVal, done);
228
229        this.resumeGenerator(node);
230
231        pg.loadAccumulatorInt(node, ResumeMode.RETURN);
232        pg.condition(node, ts.SyntaxKind.EqualsEqualsToken, this.resumeType, loopStart);
233
234        pg.loadAccumulator(node, this.resumeVal);
235        pg.asyncFunctionAwaitUncaught(node, this.funcObj);
236
237        pg.suspendGenerator(node, this.funcObj);
238        this.resumeGenerator(node);
239
240        pg.loadAccumulatorInt(node, ResumeMode.THROW);
241        pg.condition(node, ts.SyntaxKind.EqualsEqualsToken, this.resumeType, returnCompletion);
242
243        pg.branch(node, loopStart);
244
245        pg.label(node, iterCompletion);
246
247        pg.loadAccumulator(node, exitReturn);
248        pg.jumpIfFalse(node, normalOrThrowCompletion);
249
250        iter.iteratorValue(this.resumeVal);
251
252        this.compiler.compileFinallyBeforeCFC(
253            undefined,
254            ControlFlowChange.Break,
255            undefined
256        )
257
258        this.directReturn(node);
259
260        pg.label(node, normalOrThrowCompletion);
261        iter.iteratorValue(this.resumeVal);
262
263        this.resumeVal = receivedValue;
264
265        pg.freeTemps(method, iterator, nextResult, value, done, nextMethod, exitReturn);
266    }
267
268    private handleAsyncYieldResume(node: ts.Node) {
269        let pg = this.pg;
270        let notRet = new Label();
271        let normalCompletion = new Label();
272        let notThrow = new Label();
273
274        // 27.6.3.8.8.a If resumptionValue.[[Type]] is not return
275        pg.loadAccumulatorInt(node, ResumeMode.RETURN);
276        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, notRet);
277
278        // 27.6.3.8.8.b Let awaited be Await(resumptionValue.[[Value]])
279        pg.loadAccumulator(node, this.resumeVal);
280        pg.asyncFunctionAwaitUncaught(node, this.funcObj);
281        pg.suspendGenerator(node, this.funcObj);
282        this.resumeGenerator(node);
283
284        // 27.6.3.8.8.c. If awaited.[[Type]] is throw, return Completion(awaited)
285        pg.loadAccumulatorInt(node, ResumeMode.THROW);
286        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, normalCompletion);
287        pg.loadAccumulator(node, this.resumeVal);
288        pg.throw(node);
289
290        pg.label(node, normalCompletion);
291        // 27.6.3.8.8.d. Assert: awaited.[[Type]] is normal.
292        // 27.6.3.8.8.e. Return Completion { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }
293
294        // if there are finally blocks, should implement these at first.
295        this.compiler.compileFinallyBeforeCFC(
296            undefined,
297            ControlFlowChange.Break,
298            undefined
299        );
300        pg.loadAccumulator(node, this.resumeVal);
301        this.directReturn(node);
302
303        pg.label(node, notRet);
304        // 27.6.3.8.8.a return Completion(resumptionValue)
305        pg.loadAccumulatorInt(node, ResumeMode.THROW);
306        pg.condition(node, ts.SyntaxKind.EqualsEqualsEqualsToken, this.resumeType, notThrow);
307        pg.loadAccumulator(node, this.resumeVal);
308        pg.throw(node);
309
310        pg.label(node, notThrow);
311        pg.loadAccumulator(node, this.resumeVal);
312    }
313
314    private handleMode(node: ts.Node) {
315        let pandaGen = this.pg;
316
317        pandaGen.getResumeMode(node, this.funcObj);
318        pandaGen.storeAccumulator(node, this.resumeType);
319
320        // .throw(value)
321        pandaGen.loadAccumulatorInt(node, ResumeMode.THROW);
322
323        let notThrowLabel = new Label();
324
325        pandaGen.condition(node, ts.SyntaxKind.EqualsEqualsToken, this.resumeType, notThrowLabel);
326        pandaGen.loadAccumulator(node, this.resumeVal);
327        pandaGen.throw(node);
328
329        // .next(value)
330        pandaGen.label(node, notThrowLabel);
331        pandaGen.loadAccumulator(node, this.resumeVal);
332    }
333
334    resolve(node: ts.Node | NodeKind, value: VReg) {
335        let pandaGen = this.pg;
336        pandaGen.asyncgeneratorresolve(node, this.funcObj, value, getVregisterCache(pandaGen, CacheList.True));
337    }
338
339    cleanUp(node: ts.Node) {
340        let pandaGen = this.pg;
341        pandaGen.label(node, this.endLabel);
342        // catch
343        pandaGen.storeAccumulator(node, this.resumeVal);
344        pandaGen.generatorComplete(node, this.funcObj);
345        pandaGen.loadAccumulator(node, this.resumeVal);
346        pandaGen.asyncgeneratorreject(node, this.funcObj); // exception is in acc
347        pandaGen.return(NodeKind.Invalid);
348        this.pg.freeTemps(this.funcObj, this.resumeVal, this.resumeType);
349        new CatchTable(pandaGen, this.endLabel, new LabelPair(this.beginLabel, this.endLabel));
350    }
351}
352