Skip to content

Commit 0a6126f

Browse files
trossimel-scfacebook-github-bot
authored andcommitted
Add for await of (#1559)
Summary: This PR introduces support for the `for await of` syntax through IR. In detail: - The helper function `_makeAsyncIterator` is used to create an async iterator for a given iterable. It checks if an async iterator method is available; if not, it creates an async iterator from a synchronous iterator by wrapping its results in promises. - Added a `genAsyncForOfStatement` which handles the for await of loop by iterating over the async iterator object. It follows the same rules described [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) Pull Request resolved: #1559 Test Plan: - Added 3 unit tests to cover the main behavior of `for await of` loop Reviewed By: tmikov Differential Revision: D72175189 Pulled By: avp fbshipit-source-id: 20933dbd7f19fab48550b64db5255ecaf0612b8e
1 parent 5768b2a commit 0a6126f

17 files changed

+531
-11
lines changed

include/hermes/FrontEndDefs/Builtins.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ PRIVATE_BUILTIN(functionPrototypeCall)
156156

157157
JS_BUILTIN(spawnAsync)
158158
MARK_FIRST_JS_BUILTIN(spawnAsync)
159+
JS_BUILTIN(makeAsyncIterator)
159160

160161
#undef NORMAL_OBJECT
161162
#undef NORMAL_METHOD

include/hermes/VM/PredefinedStrings.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ STR(keyFor, "keyFor")
417417
STR(hasInstance, "hasInstance")
418418
STR(isConcatSpreadable, "isConcatSpreadable")
419419
STR(iterator, "iterator")
420+
STR(asyncIterator, "asyncIterator")
420421
STR(toPrimitive, "toPrimitive")
421422
STR(toStringTag, "toStringTag")
422423

@@ -473,6 +474,7 @@ STR(fileName, "fileName")
473474
STR(setPromiseRejectionTrackingHook, "setPromiseRejectionTrackingHook")
474475
STR(enablePromiseRejectionTracker, "enablePromiseRejectionTracker")
475476
STR(spawnAsync, "spawnAsync") /* NOLINT */
477+
STR(makeAsyncIterator, "makeAsyncIterator") /* NOLINT */
476478

477479
STR(SHBuiltin, "$SHBuiltin")
478480

@@ -488,6 +490,7 @@ STR(squareObjectNull, "[object Null]")
488490
STR(squareObjectGlobal, "[object global]")
489491
STR(squareSymbolHasInstance, "[Symbol.hasInstance]")
490492
STR(squareSymbolIterator, "[Symbol.iterator]")
493+
STR(squareSymbolAsyncIterator, "[Symbol.asyncIterator]")
491494
STR(squareSymbolToPrimitive, "[Symbol.toPrimitive]")
492495
STR(squareSymbolToStringTag, "[Symbol.toStringTag]")
493496
STR(squareSymbolMatch, "[Symbol.match]")

include/hermes/VM/PredefinedSymbols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
SYM(SymbolHasInstance, "Symbol.hasInstance")
1717
SYM(SymbolIterator, "Symbol.iterator")
18+
SYM(SymbolAsyncIterator, "Symbol.asyncIterator")
1819
SYM(SymbolIsConcatSpreadable, "Symbol.isConcatSpreadable")
1920
SYM(SymbolToPrimitive, "Symbol.toPrimitive")
2021
SYM(SymbolToStringTag, "Symbol.toStringTag")

lib/IRGen/ESTreeIRGen-expr.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,7 +1997,7 @@ Value *ESTreeIRGen::genYieldStarExpr(ESTree::YieldExpressionNode *Y) {
19971997
// emitNormalCleanup.
19981998
[]() {},
19991999
// emitHandler.
2000-
[this, exitBlock, result, &iteratorRecord, &resumeGenTryStartBB](
2000+
[this, exitBlock, result, &iteratorRecord, &resumeGenTryStartBB, Y](
20012001
BasicBlock *getNextBlock) {
20022002
auto *catchReg = Builder.createCatchInst();
20032003

@@ -2052,7 +2052,7 @@ Value *ESTreeIRGen::genYieldStarExpr(ESTree::YieldExpressionNode *Y) {
20522052
// going to terminate the yield* loop. But first we need to give
20532053
// iterator a chance to clean up.
20542054
Builder.setInsertionBlock(noThrowMethodBB);
2055-
emitIteratorCloseSlow(iteratorRecord, false);
2055+
emitIteratorCloseSlow(Y, iteratorRecord, false);
20562056
Builder.createThrowTypeErrorInst(Builder.getLiteralString(
20572057
"yield* delegate must have a .throw() method"));
20582058
});

lib/IRGen/ESTreeIRGen-stmt.cpp

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void ESTreeIRGen::genStatement(ESTree::Node *stmt) {
5454
}
5555

5656
if (auto *FOS = llvh::dyn_cast<ESTree::ForOfStatementNode>(stmt)) {
57-
return genForOfStatement(FOS);
57+
return FOS->_await ? genAsyncForOfStatement(FOS) : genForOfStatement(FOS);
5858
}
5959

6060
if (auto *Ret = llvh::dyn_cast<ESTree::ReturnStatementNode>(stmt)) {
@@ -974,6 +974,104 @@ void ESTreeIRGen::genForOfStatement(ESTree::ForOfStatementNode *forOfStmt) {
974974
Builder.setInsertionBlock(exitBlock);
975975
}
976976

977+
void ESTreeIRGen::genAsyncForOfStatement(
978+
ESTree::ForOfStatementNode *forOfStmt) {
979+
auto *outerScope = curFunction()->curScope();
980+
981+
// If block scoping is enabled, check if anything in the loop might capture,
982+
// so we know whether to create inner scopes for the loop.
983+
bool createInnerScopes = Mod->getContext().getEnableES6BlockScoping() &&
984+
!treeDoesNotCapture(forOfStmt);
985+
986+
// Create an inner scope for the loop init if needed and set it as the top
987+
// scope. All loop variables will be declared in this scope.
988+
if (createInnerScopes) {
989+
auto *initScope = Builder.createCreateScopeInst(
990+
curFunction()->getOrCreateInnerVariableScope(forOfStmt), outerScope);
991+
curFunction()->setCurScope(initScope);
992+
}
993+
994+
emitScopeDeclarations(forOfStmt->getScope());
995+
996+
auto *function = Builder.getInsertionBlock()->getParent();
997+
auto *getNextBlock = Builder.createBasicBlock(function);
998+
auto *bodyBlock = Builder.createBasicBlock(function);
999+
auto *exitBlock = Builder.createBasicBlock(function);
1000+
1001+
// Initialize the goto labels.
1002+
curFunction()->initLabel(forOfStmt, exitBlock, getNextBlock);
1003+
1004+
auto *exprValue = genExpression(forOfStmt->_right);
1005+
const IteratorRecordSlow iteratorRecord = emitGetAsyncIteratorSlow(exprValue);
1006+
1007+
Builder.createBranchInst(getNextBlock);
1008+
1009+
// Attempt to retrieve the next value. If iteration is complete, finish the
1010+
// loop. This stays outside the SurroundingTry below because exceptions in
1011+
// `.next()` should not call `.return()` on the iterator.
1012+
Builder.setInsertionBlock(getNextBlock);
1013+
auto *nextResult = genYieldOrAwaitExpr(emitIteratorNextSlow(iteratorRecord));
1014+
auto *done = emitIteratorCompleteSlow(nextResult);
1015+
Builder.createCondBranchInst(done, exitBlock, bodyBlock);
1016+
1017+
Builder.setInsertionBlock(bodyBlock);
1018+
1019+
// Create a scope for the body if needed.
1020+
if (createInnerScopes) {
1021+
auto *innerScope = Builder.createCreateScopeInst(
1022+
curFunction()->curScope()->getVariableScope(), outerScope);
1023+
curFunction()->setCurScope(innerScope);
1024+
}
1025+
1026+
auto *nextValue = emitIteratorValueSlow(nextResult);
1027+
1028+
emitTryCatchScaffolding(
1029+
getNextBlock,
1030+
// emitBody.
1031+
[this, forOfStmt, nextValue, &iteratorRecord, getNextBlock](
1032+
BasicBlock *catchBlock) {
1033+
// Generate IR for the body of Try
1034+
SurroundingTry thisTry{
1035+
curFunction(),
1036+
forOfStmt,
1037+
catchBlock,
1038+
{},
1039+
[this, &iteratorRecord, getNextBlock, forOfStmt](
1040+
ESTree::Node *,
1041+
ControlFlowChange cfc,
1042+
BasicBlock *continueTarget) {
1043+
// Only emit the iteratorClose if this is a
1044+
// 'break' or if the target of the control flow
1045+
// change is outside the current loop. If
1046+
// continuing the existing loop, do not close
1047+
// the iterator.
1048+
if (cfc == ControlFlowChange::Break ||
1049+
continueTarget != getNextBlock)
1050+
emitAsyncIteratorCloseSlow(forOfStmt, iteratorRecord, false);
1051+
}};
1052+
1053+
// Note: obtaining the value is not protected, but storing it is.
1054+
createLRef(forOfStmt->_left, false).emitStore(nextValue);
1055+
1056+
genStatement(forOfStmt->_body);
1057+
Builder.setLocation(SourceErrorManager::convertEndToLocation(
1058+
forOfStmt->_body->getSourceRange()));
1059+
},
1060+
// emitNormalCleanup.
1061+
[]() {},
1062+
// emitHandler.
1063+
[this, &iteratorRecord, forOfStmt](BasicBlock *) {
1064+
auto *catchReg = Builder.createCatchInst();
1065+
emitAsyncIteratorCloseSlow(forOfStmt, iteratorRecord, true);
1066+
Builder.createThrowInst(catchReg);
1067+
});
1068+
1069+
// Restore the outer scope for subsequent code.
1070+
curFunction()->setCurScope(outerScope);
1071+
1072+
Builder.setInsertionBlock(exitBlock);
1073+
}
1074+
9771075
void ESTreeIRGen::genForOfFastArrayStatement(
9781076
ESTree::ForOfStatementNode *forOfStmt,
9791077
flow::ArrayType *type) {

lib/IRGen/ESTreeIRGen.cpp

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,12 @@ Value *ESTreeIRGen::emitIteratorSymbol() {
583583
"iterator");
584584
}
585585

586+
Value *ESTreeIRGen::emitAsyncIteratorSymbol() {
587+
return Builder.createLoadPropertyInst(
588+
Builder.createGetBuiltinClosureInst(BuiltinMethod::globalThis_Symbol),
589+
"asyncIterator");
590+
}
591+
586592
ESTreeIRGen::IteratorRecordSlow ESTreeIRGen::emitGetIteratorSlow(Value *obj) {
587593
auto *method = Builder.createLoadPropertyInst(obj, emitIteratorSymbol());
588594
auto *iterator = Builder.createCallInst(
@@ -594,6 +600,21 @@ ESTreeIRGen::IteratorRecordSlow ESTreeIRGen::emitGetIteratorSlow(Value *obj) {
594600
return {iterator, nextMethod};
595601
}
596602

603+
ESTreeIRGen::IteratorRecordSlow ESTreeIRGen::emitGetAsyncIteratorSlow(
604+
Value *obj) {
605+
auto *makeAsyncIterator = Builder.createGetBuiltinClosureInst(
606+
BuiltinMethod::HermesBuiltin_makeAsyncIterator);
607+
608+
auto *iterator = Builder.createCallInst(
609+
makeAsyncIterator,
610+
Builder.getLiteralUndefined(),
611+
Builder.getLiteralUndefined(),
612+
{obj});
613+
auto *nextMethod = Builder.createLoadPropertyInst(iterator, "next");
614+
615+
return {iterator, nextMethod};
616+
}
617+
597618
Value *ESTreeIRGen::emitIteratorNextSlow(IteratorRecordSlow iteratorRecord) {
598619
auto *nextResult = Builder.createCallInst(
599620
iteratorRecord.nextMethod,
@@ -612,9 +633,11 @@ Value *ESTreeIRGen::emitIteratorValueSlow(Value *iterResult) {
612633
return Builder.createLoadPropertyInst(iterResult, "value");
613634
}
614635

615-
void ESTreeIRGen::emitIteratorCloseSlow(
636+
void ESTreeIRGen::_emitIteratorCloseImpl(
637+
ESTree::Node *astNode,
616638
hermes::irgen::ESTreeIRGen::IteratorRecordSlow iteratorRecord,
617-
bool ignoreInnerException) {
639+
bool ignoreInnerException,
640+
bool isAsyncIterator) {
618641
auto *haveReturn = Builder.createBasicBlock(Builder.getFunction());
619642
auto *noReturn = Builder.createBasicBlock(Builder.getFunction());
620643

@@ -632,12 +655,24 @@ void ESTreeIRGen::emitIteratorCloseSlow(
632655
emitTryCatchScaffolding(
633656
noReturn,
634657
// emitBody.
635-
[this, returnMethod, &iteratorRecord](BasicBlock * /*catchBlock*/) {
636-
Builder.createCallInst(
658+
[this, returnMethod, &iteratorRecord, isAsyncIterator, astNode](
659+
BasicBlock *catchBlock) {
660+
SurroundingTry thisTry{
661+
curFunction(),
662+
astNode,
663+
catchBlock,
664+
{},
665+
[](ESTree::Node *,
666+
ControlFlowChange cfc,
667+
BasicBlock *continueTarget) {}};
668+
669+
auto *callResult = Builder.createCallInst(
637670
returnMethod,
638671
/* newTarget */ Builder.getLiteralUndefined(),
639672
iteratorRecord.iterator,
640673
{});
674+
if (isAsyncIterator)
675+
genYieldOrAwaitExpr(callResult);
641676
},
642677
// emitNormalCleanup.
643678
[]() {},
@@ -648,18 +683,34 @@ void ESTreeIRGen::emitIteratorCloseSlow(
648683
Builder.createBranchInst(nextBlock);
649684
});
650685
} else {
651-
auto *innerResult = Builder.createCallInst(
686+
auto *callResult = Builder.createCallInst(
652687
returnMethod,
653688
/* newTarget */ Builder.getLiteralUndefined(),
654689
iteratorRecord.iterator,
655690
{});
656-
emitEnsureObject(innerResult, "iterator.return() did not return an object");
691+
emitEnsureObject(
692+
isAsyncIterator ? genYieldOrAwaitExpr(callResult) : callResult,
693+
"iterator.return() did not return an object");
657694
Builder.createBranchInst(noReturn);
658695
}
659696

660697
Builder.setInsertionBlock(noReturn);
661698
}
662699

700+
void ESTreeIRGen::emitIteratorCloseSlow(
701+
ESTree::Node *astNode,
702+
hermes::irgen::ESTreeIRGen::IteratorRecordSlow iteratorRecord,
703+
bool ignoreInnerException) {
704+
_emitIteratorCloseImpl(astNode, iteratorRecord, ignoreInnerException, false);
705+
}
706+
707+
void ESTreeIRGen::emitAsyncIteratorCloseSlow(
708+
ESTree::Node *astNode,
709+
hermes::irgen::ESTreeIRGen::IteratorRecordSlow iteratorRecord,
710+
bool ignoreInnerException) {
711+
_emitIteratorCloseImpl(astNode, iteratorRecord, ignoreInnerException, true);
712+
}
713+
663714
ESTreeIRGen::IteratorRecord ESTreeIRGen::emitGetIterator(Value *obj) {
664715
// Each of these will be modified by "next", so we use a stack storage.
665716
auto *iterStorage = Builder.createAllocStackInst(

lib/IRGen/ESTreeIRGen.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ class ESTreeIRGen {
704704
void genForOfFastArrayStatement(
705705
ESTree::ForOfStatementNode *forOfStmt,
706706
flow::ArrayType *type);
707+
void genAsyncForOfStatement(ESTree::ForOfStatementNode *forOfStmt);
707708
void genWhileLoop(ESTree::WhileStatementNode *loop);
708709
void genDoWhileLoop(ESTree::DoWhileStatementNode *loop);
709710

@@ -1460,6 +1461,9 @@ class ESTreeIRGen {
14601461
/// \return the internal value @@iterator
14611462
Value *emitIteratorSymbol();
14621463

1464+
/// \return the internal value @@asyncIterator
1465+
Value *emitAsyncIteratorSymbol();
1466+
14631467
/// IteratorRecord as defined in ES2018 7.4.1 GetIterator
14641468
struct IteratorRecordSlow {
14651469
Value *iterator;
@@ -1478,6 +1482,15 @@ class ESTreeIRGen {
14781482
/// \return (iterator, nextMethod)
14791483
IteratorRecordSlow emitGetIteratorSlow(Value *obj);
14801484

1485+
/// Call obj[@@asyncIterator], which should return an async iterator,
1486+
/// and return the iterator itself and its \c next() method.
1487+
///
1488+
/// NOTE: This API is slow and should only be used if it is necessary to
1489+
/// provide a value to the `next()` method on the iterator.
1490+
///
1491+
/// \return (iterator, nextMethod)
1492+
IteratorRecordSlow emitGetAsyncIteratorSlow(Value *obj);
1493+
14811494
/// ES2018 7.4.2 IteratorNext
14821495
/// https://www.ecma-international.org/ecma-262/9.0/index.html#sec-iteratornext
14831496
///
@@ -1499,13 +1512,35 @@ class ESTreeIRGen {
14991512
/// \return \c iterResult.value
15001513
Value *emitIteratorValueSlow(Value *iterResult);
15011514

1515+
/// A helper called only from \c emitIteratorCloseSlow or
1516+
/// emitAsyncIteratorCloseSlow. It performs the actual work.
1517+
void _emitIteratorCloseImpl(
1518+
ESTree::Node *astNode,
1519+
IteratorRecordSlow iteratorRecord,
1520+
bool ignoreInnerException,
1521+
bool isAsyncIterator);
1522+
15021523
/// ES2018 7.4.6 IteratorClose
15031524
/// https://www.ecma-international.org/ecma-262/9.0/index.html#sec-iteratorclose
15041525
///
1526+
/// \param astNode the caller AST node
15051527
/// \param ignoreInnerException if set, exceptions thrown by the \c
15061528
/// iterator.return() method will be ignored and its result will not be
15071529
/// checked whether it is an object.
15081530
void emitIteratorCloseSlow(
1531+
ESTree::Node *astNode,
1532+
IteratorRecordSlow iteratorRecord,
1533+
bool ignoreInnerException);
1534+
1535+
/// ES2018 7.4.7 AsyncIteratorClose
1536+
/// https://www.ecma-international.org/ecma-262/9.0/index.html#sec-asynciteratorclose
1537+
///
1538+
/// \param astNode the caller AST node
1539+
/// \param ignoreInnerException if set, exceptions thrown by the \c
1540+
/// async iterator.return() method will be ignored and its result will not
1541+
/// be checked whether it is an object.
1542+
void emitAsyncIteratorCloseSlow(
1543+
ESTree::Node *astNode,
15091544
IteratorRecordSlow iteratorRecord,
15101545
bool ignoreInnerException);
15111546

0 commit comments

Comments
 (0)