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