diff --git a/API/hermes/hermes.cpp b/API/hermes/hermes.cpp index a641c5dc678..e6e6821ada3 100644 --- a/API/hermes/hermes.cpp +++ b/API/hermes/hermes.cpp @@ -197,6 +197,7 @@ class HermesRuntimeImpl final : public HermesRuntime, compileFlags_.enableGenerator = runtimeConfig.getEnableGenerator(); compileFlags_.enableES6BlockScoping = runtimeConfig.getES6BlockScoping(); + compileFlags_.enableAsyncGenerators = runtimeConfig.getEnableAsyncGenerators(); compileFlags_.emitAsyncBreakCheck = runtimeConfig.getAsyncBreakCheckInEval(); runtime_.addCustomRootsFunction( diff --git a/include/hermes/AST/AsyncGenerator.h b/include/hermes/AST/AsyncGenerator.h new file mode 100644 index 00000000000..b8839d0a910 --- /dev/null +++ b/include/hermes/AST/AsyncGenerator.h @@ -0,0 +1,14 @@ +#ifndef HERMES_AST_ASYNCGENERATORS_H +#define HERMES_AST_ASYNCGENERATORS_H + +#include "hermes/AST/ESTree.h" + +namespace hermes { + +/// Recursively transforms the ESTree Node tree such that async generators +/// are converted into generators +ESTree::Node* transformAsyncGenerators(Context &context, ESTree::Node *node); + +} // namespace hermes + +#endif diff --git a/include/hermes/AST/Context.h b/include/hermes/AST/Context.h index a604a0c02f8..a92b01fa4da 100644 --- a/include/hermes/AST/Context.h +++ b/include/hermes/AST/Context.h @@ -242,6 +242,9 @@ class Context { /// Whether to parse TypeScript syntax. bool parseTS_{false}; + /// Whether to enable support for async generators + bool enableAsyncGenerators_{false}; + /// Whether to enable support for ES6 block scoping. /// TODO: This is intended to provide a temporary way to configure block /// scoping until we have debugger support for it. @@ -433,6 +436,14 @@ class Context { return parseTS_; } + void setEnableAsyncGenerators(bool enableAsyncGenerators) { + enableAsyncGenerators_ = enableAsyncGenerators; + } + + bool getEnableAsyncGenerators() const { + return enableAsyncGenerators_; + } + void setEnableES6BlockScoping(bool enableES6BlockScoping) { enableES6BlockScoping_ = enableES6BlockScoping; } diff --git a/include/hermes/AST/TransformAST.h b/include/hermes/AST/TransformAST.h new file mode 100644 index 00000000000..55db15ee621 --- /dev/null +++ b/include/hermes/AST/TransformAST.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "hermes/AST/Context.h" +#include "hermes/AST/ESTree.h" + +namespace hermes { + +/// General purpose AST transformation which will be applied before running +/// semantic resolution in the compiler pipeline. +/// Allows adding functionality/transforms in a general way directly to the AST +/// in a way that works for lazy compilation, debugger eval, etc. +/// +/// \return the transformed node, which should be used for the remainder of +/// compilation. On failure, report an error and return nullptr. +/// The returned Node must be the same kind as the original \p root. +ESTree::Node *transformASTForCompilation(Context &context, ESTree::Node *root); + +} // namespace hermes diff --git a/include/hermes/AST/TransformationsBase.h b/include/hermes/AST/TransformationsBase.h new file mode 100644 index 00000000000..03920dbc45e --- /dev/null +++ b/include/hermes/AST/TransformationsBase.h @@ -0,0 +1,146 @@ +#include "hermes/AST/RecursiveVisitor.h" +#include "hermes/Parser/JSLexer.h" +#include "llvh/ADT/StringRef.h" + +namespace { +using namespace hermes; + +/// Mutable vector that helps dealing with arrays of nodes safely. +/// Once done with the vector, it can create an ESTree::NodeList +/// representation which is used by the ESTree API in several places. +class NodeVector { + public: + using Storage = llvh::SmallVector; + + NodeVector() = default; + NodeVector(std::initializer_list nodes) { + for (auto &node : nodes) { + _storage.push_back(node); + } + } + + NodeVector(ESTree::NodeList &list) { + for (auto &node : list) { + _storage.push_back(&node); + } + } + + ~NodeVector() = default; + + size_t size() const { + return _storage.size(); + } + + Storage::const_iterator begin() const { + return _storage.begin(); + } + + Storage::const_iterator end() const { + return _storage.end(); + } + + void append(ESTree::Node *node) { + _storage.emplace_back(node); + } + + void prepend(ESTree::Node *node) { + _storage.insert(_storage.begin(), node); + } + + ESTree::NodeList toNodeList() const { + ESTree::NodeList nodeList; + for (auto &node : _storage) { + nodeList.push_back(*node); + } + return nodeList; + } + + private: + Storage _storage; +}; + +class TransformationsBase + : public ESTree::RecursionDepthTracker { + public: + static constexpr bool kEnableNodeListMutation = true; + + TransformationsBase(Context &context) + : context_(context), + identVar_(context.getIdentifier("var").getUnderlyingPointer()) {} + + void recursionDepthExceeded(ESTree::Node *n) { + context_.getSourceErrorManager().error( + n->getEndLoc(), "Too many nested expressions/statements/declarations"); + } + + protected: + Context &context_; + UniqueString *const identVar_; + + void doCopyLocation(ESTree::Node *src, ESTree::Node *dest) { + if (src != nullptr) { + dest->setStartLoc(src->getStartLoc()); + dest->setEndLoc(src->getEndLoc()); + dest->setDebugLoc(src->getDebugLoc()); + } + } + + template + T *copyLocation(ESTree::Node *src, T *dest) { + doCopyLocation(src, dest); + return dest; + } + + template + T *createTransformedNode(ESTree::Node *src, Args &&...args) { + auto *node = new (context_) T(std::forward(args)...); + return copyLocation(src, node); + } + + ESTree::IdentifierNode *makeIdentifierNode( + ESTree::Node *srcNode, + UniqueString *name) { + return createTransformedNode( + srcNode, name, nullptr, false); + } + + ESTree::IdentifierNode *makeIdentifierNode( + ESTree::Node *srcNode, + llvh::StringRef name) { + return makeIdentifierNode( + srcNode, context_.getIdentifier(name).getUnderlyingPointer()); + } + + ESTree::Node *makeSingleVarDecl( + ESTree::Node *srcNode, + ESTree::Node *identifier, + ESTree::Node *value) { + auto *variableDeclarator = + createTransformedNode( + srcNode, value, identifier); + ESTree::NodeList variableList; + variableList.push_back(*variableDeclarator); + return createTransformedNode( + srcNode, identVar_, std::move(variableList)); + } + + ESTree::Node *makeHermesInternalCall( + ESTree::Node *srcNode, + llvh::StringRef methodName, + const NodeVector ¶meters) { + auto hermesInternalIdentifier = getHermesInternalIdentifier(srcNode); + auto methodIdentifier = makeIdentifierNode(srcNode, methodName); + + auto *getPropertyNode = createTransformedNode( + srcNode, hermesInternalIdentifier, methodIdentifier, false); + return createTransformedNode( + srcNode, getPropertyNode, nullptr, parameters.toNodeList()); + } + + virtual ESTree::Node *getHermesInternalIdentifier(ESTree::Node *srcNode) { + return nullptr; + }; + + virtual ~TransformationsBase() = default; +}; +} // namespace diff --git a/include/hermes/BCGen/HBC/HBC.h b/include/hermes/BCGen/HBC/HBC.h index 56a595413c2..ea8bf4eee29 100644 --- a/include/hermes/BCGen/HBC/HBC.h +++ b/include/hermes/BCGen/HBC/HBC.h @@ -68,6 +68,8 @@ struct CompileFlags { bool enableGenerator{true}; /// Enable ES6 block scoping support bool enableES6BlockScoping{false}; + /// Enable async generators support + bool enableAsyncGenerators{false}; /// Define the output format of the generated bytecode. For instance, whether /// the bytecode is intended for execution or serialisation. OutputFormatKind format{Execute}; diff --git a/include/hermes/CompilerDriver/CompilerDriver.h b/include/hermes/CompilerDriver/CompilerDriver.h index 80bb5361bdb..185a3d6b975 100644 --- a/include/hermes/CompilerDriver/CompilerDriver.h +++ b/include/hermes/CompilerDriver/CompilerDriver.h @@ -102,5 +102,6 @@ extern llvh::cl::opt EmitAsyncBreakCheck; extern llvh::cl::list InputFilenames; extern llvh::cl::opt OptimizedEval; extern llvh::cl::opt PrintCompilerTiming; +extern llvh::cl::opt EnableAsyncGenerators; } // namespace cl #endif diff --git a/include/hermes/FrontEndDefs/Builtins.def b/include/hermes/FrontEndDefs/Builtins.def index 16634c59855..fb704798a6c 100644 --- a/include/hermes/FrontEndDefs/Builtins.def +++ b/include/hermes/FrontEndDefs/Builtins.def @@ -156,6 +156,7 @@ PRIVATE_BUILTIN(functionPrototypeCall) JS_BUILTIN(spawnAsync) MARK_FIRST_JS_BUILTIN(spawnAsync) +JS_BUILTIN(makeAsyncIterator) #undef NORMAL_OBJECT #undef NORMAL_METHOD diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index 28ead8b1bfb..3e0f340208e 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -164,6 +164,7 @@ NATIVE_FUNCTION(hermesInternalIsProxy) NATIVE_FUNCTION(hermesInternalHasPromise) NATIVE_FUNCTION(hermesInternalTTIReached) NATIVE_FUNCTION(hermesInternalTTRCReached) +NATIVE_FUNCTION(hermesInternalHasAsyncGenerators) NATIVE_FUNCTION(hermesInternalSetPromiseRejectionTrackingHook) NATIVE_FUNCTION(hermesInternalEnablePromiseRejectionTracker) diff --git a/include/hermes/VM/PredefinedStrings.def b/include/hermes/VM/PredefinedStrings.def index ba3b0d49235..1ea8cb38409 100644 --- a/include/hermes/VM/PredefinedStrings.def +++ b/include/hermes/VM/PredefinedStrings.def @@ -430,6 +430,7 @@ STR(HermesInternal, "HermesInternal") STR(detachArrayBuffer, "detachArrayBuffer") STR(createHeapSnapshot, "createHeapSnapshot") STR(hasPromise, "hasPromise") +STR(hasAsyncGenerators, "hasAsyncGenerators") STR(useEngineQueue, "useEngineQueue") STR(enqueueJob, "enqueueJob") STR(drainJobs, "drainJobs") @@ -463,6 +464,7 @@ STR(fileName, "fileName") STR(setPromiseRejectionTrackingHook, "setPromiseRejectionTrackingHook") STR(enablePromiseRejectionTracker, "enablePromiseRejectionTracker") STR(spawnAsync, "spawnAsync") /* NOLINT */ +STR(makeAsyncIterator, "makeAsyncIterator") /* NOLINT */ STR(SHBuiltin, "$SHBuiltin") diff --git a/include/hermes/VM/Runtime.h b/include/hermes/VM/Runtime.h index bc669557580..c2cfd354c62 100644 --- a/include/hermes/VM/Runtime.h +++ b/include/hermes/VM/Runtime.h @@ -902,6 +902,10 @@ class Runtime : public RuntimeBase, public HandleRootOwner { return hasES6Proxy_; } + bool hasAsyncGenerators() const { + return hasAsyncGenerators_; + } + bool hasES6BlockScoping() const { return hasES6BlockScoping_; } @@ -1143,6 +1147,9 @@ class Runtime : public RuntimeBase, public HandleRootOwner { /// Set to true if we should enable ES6 Proxy. const bool hasES6Proxy_; + /// Set to true if we should enable async generators. + const bool hasAsyncGenerators_; + /// Set to true if we should enable ES6 block scoping. const bool hasES6BlockScoping_; diff --git a/lib/AST/AsyncGenerator.cpp b/lib/AST/AsyncGenerator.cpp new file mode 100644 index 00000000000..75810406572 --- /dev/null +++ b/lib/AST/AsyncGenerator.cpp @@ -0,0 +1,180 @@ +#include "hermes/AST/AsyncGenerator.h" +#include "hermes/AST/TransformationsBase.h" + +namespace hermes { + +/** + * Transforms JS async generators to generators according to babel + * transformation + * https://babeljs.io/docs/babel-plugin-transform-async-generator-functions + * + * An async generator: + * + * async function* userFunction(arguments) { + * // body containing await calls + * } + * + * is transformed into: + * + * let userFunction = (() => { + * let _ref = _wrapAsyncGenerator(function* (arguments) { // body + * containing await calls }); return function userFunction() { return + * _ref.apply(this, arguments); + * }; + * })(); + * + * and an await call inside the async generator body: + * await 1; + * + * is transformed into: + * yield _awaitAsyncGenerator(1); + * + * where _awaitAsyncGenerator and _wrapAsyncGenerator are helper functions + * defined in 04-AsyncIterator.js + * + */ +class AsyncGenerator : public TransformationsBase { + public: + AsyncGenerator(Context &context) : TransformationsBase(context) {} + + void visit(ESTree::FunctionDeclarationNode *funcDecl, ESTree::Node **ppNode) { + if (funcDecl->_async && funcDecl->_generator) { + recurseFunctionBody(funcDecl->_body, true); + auto *refFunc = transformAsyncGeneratorFunction( + funcDecl, funcDecl->_params, funcDecl->_body); + + *ppNode = createTransformedNode( + funcDecl, + funcDecl->_id, + std::move(funcDecl->_params), + refFunc, + nullptr, + nullptr, + nullptr, + false, + false); + } else { + recurseFunctionBody(funcDecl->_body, false); + } + } + + void visit(ESTree::FunctionExpressionNode *funcExpr, ESTree::Node **ppNode) { + if (funcExpr->_async && funcExpr->_generator) { + recurseFunctionBody(funcExpr->_body, true); + auto *refFunc = transformAsyncGeneratorFunction( + funcExpr, funcExpr->_params, funcExpr->_body); + + *ppNode = createTransformedNode( + funcExpr, + nullptr, + std::move(funcExpr->_params), + refFunc, + nullptr, + nullptr, + nullptr, + false, + false); + } else { + recurseFunctionBody(funcExpr->_body, false); + } + } + + void visit(ESTree::AwaitExpressionNode *awaitExpr, ESTree::Node **ppNode) { + if (!insideAsyncGenerator) { + return; + } + + auto *awaitCall = makeHermesInternalCall( + awaitExpr, "_awaitAsyncGenerator", NodeVector{awaitExpr->_argument}); + *ppNode = createTransformedNode( + awaitExpr, awaitCall, false); + } + + void visit(ESTree::YieldExpressionNode *yieldExpr, ESTree::Node **ppNode) { + if (!insideAsyncGenerator) { + return; + } + + auto awaitExpr = + llvh::dyn_cast(yieldExpr->_argument); + if (awaitExpr) { + visit(awaitExpr->_argument); + *ppNode = createTransformedNode( + yieldExpr, awaitExpr->_argument, false); + } + } + + void visit(ESTree::Node *node) { + visitESTreeChildren(*this, node); + } + + private: + void recurseFunctionBody(ESTree::Node *body, bool insideAsyncGenerator) { + // recursively transforms all nested async generators and async expressions + bool oldInsideAsyncGenerator = this->insideAsyncGenerator; + this->insideAsyncGenerator = insideAsyncGenerator; + visitESTreeChildren(*this, body); + this->insideAsyncGenerator = oldInsideAsyncGenerator; + } + + ESTree::Node *transformAsyncGeneratorFunction( + ESTree::Node *funcNode, + ESTree::NodeList ¶ms, + ESTree::Node *body) { + auto *refFunc = createTransformedNode( + funcNode, + nullptr, // id is null for anonymous function + std::move(params), // params + body, + nullptr, // typeParameters + nullptr, // returnType + nullptr, // predicate + true, // generator + false); // async + + auto *wrappedRef = isBodyEmpty(body) ? refFunc : makeHermesInternalCall( + funcNode, "_wrapAsyncGenerator", NodeVector{refFunc}); + + // Create a function body that calls apply on the wrapped function + auto appliedBody = createTransformedNode( + funcNode, + NodeVector{ + createTransformedNode( + funcNode, + createTransformedNode( + funcNode, + createTransformedNode( + funcNode, + wrappedRef, + makeIdentifierNode(funcNode, "apply"), + false), + nullptr, + NodeVector{ + createTransformedNode( + funcNode), + makeIdentifierNode(funcNode, "arguments")} + .toNodeList()))} + .toNodeList()); + + return appliedBody; + } + + bool isBodyEmpty(ESTree::Node *body) { + auto *blockStmt = llvh::dyn_cast(body); + return blockStmt && blockStmt->_body.empty(); + } + + ESTree::Node *getHermesInternalIdentifier(ESTree::Node *srcNode) override { + return makeIdentifierNode(srcNode, "HermesAsyncIteratorsInternal"); + }; + + bool insideAsyncGenerator = false; +}; + +ESTree::Node* transformAsyncGenerators(Context &context, ESTree::Node *node) { + AsyncGenerator transformer(context); + visitESTreeNode(transformer, node, nullptr); + return node; +} + +} // namespace hermes diff --git a/lib/AST/CMakeLists.txt b/lib/AST/CMakeLists.txt index 158239c8cfa..b9714e11176 100644 --- a/lib/AST/CMakeLists.txt +++ b/lib/AST/CMakeLists.txt @@ -6,10 +6,12 @@ add_hermes_library(hermesAST ASTBuilder.cpp ASTUtils.cpp + AsyncGenerator.cpp ESTree.cpp ESTreeJSONDumper.cpp CommonJS.cpp TS2Flow.cpp + TransformAST.cpp Context.cpp NativeContext.cpp LINK_OBJLIBS diff --git a/lib/AST/TransformAST.cpp b/lib/AST/TransformAST.cpp new file mode 100644 index 00000000000..741f78964b6 --- /dev/null +++ b/lib/AST/TransformAST.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "hermes/AST/TransformAST.h" +#include "hermes/AST/AsyncGenerator.h" + +namespace hermes { + +ESTree::Node *transformASTForCompilation(Context &context, ESTree::Node *root) { + if (context.getEnableAsyncGenerators()) { + root = transformAsyncGenerators(context, root); + } + return root; +} + +} // namespace hermes diff --git a/lib/BCGen/HBC/BCProviderFromSrc.cpp b/lib/BCGen/HBC/BCProviderFromSrc.cpp index b1c38fc4168..e4f7790c928 100644 --- a/lib/BCGen/HBC/BCProviderFromSrc.cpp +++ b/lib/BCGen/HBC/BCProviderFromSrc.cpp @@ -7,6 +7,7 @@ #include "hermes/BCGen/HBC/BCProviderFromSrc.h" +#include "hermes/AST/TransformAST.h" #include "hermes/BCGen/HBC/HBC.h" #include "hermes/IR/IR.h" #include "hermes/IRGen/IRGen.h" @@ -132,6 +133,7 @@ BCProviderFromSrc::create( context->setStrictMode(compileFlags.strict); context->setEnableEval(true); context->setEnableES6BlockScoping(compileFlags.enableES6BlockScoping); + context->setEnableAsyncGenerators(compileFlags.enableAsyncGenerators); context->setPreemptiveFunctionCompilationThreshold( compileFlags.preemptiveFunctionCompilationThreshold); context->setPreemptiveFileCompilationThreshold( @@ -205,6 +207,12 @@ BCProviderFromSrc::create( parser.registerMagicURLs(); } + if (!parsed) + return {nullptr, getErrorString()}; + + parsed = llvh::cast( + hermes::transformASTForCompilation(*context, *parsed)); + if (!parsed || !hermes::sema::resolveAST(*context, *semCtx, *parsed, declFileList)) { return {nullptr, getErrorString()}; diff --git a/lib/BCGen/HBC/HBC.cpp b/lib/BCGen/HBC/HBC.cpp index 6ca71e5d0ce..9f022c38d40 100644 --- a/lib/BCGen/HBC/HBC.cpp +++ b/lib/BCGen/HBC/HBC.cpp @@ -8,6 +8,7 @@ #include "hermes/BCGen/HBC/HBC.h" #include "BytecodeGenerator.h" +#include "hermes/AST/TransformAST.h" #include "hermes/BCGen/HBC/BCProviderFromSrc.h" #include "hermes/IRGen/IRGen.h" #include "hermes/Optimizer/PassManager/Pipeline.h" @@ -169,6 +170,14 @@ static void compileLazyFunctionWorker(void *argPtr) { sema::SemContext *semCtx = provider->getSemCtx(); assert(semCtx && "missing semantic data to compile"); + if (!optParsed) { + data->success = false; + data->error = outputManager.getErrorString(); + return; + } + + optParsed = hermes::transformASTForCompilation(context, *optParsed); + // A non-null home object means the parent function context could reference // super. bool parentHadSuperBinding = lazyDataInst->getHomeObject(); @@ -264,6 +273,7 @@ static void compileEvalWorker(void *argPtr) { context.setEmitAsyncBreakCheck(data->compileFlags.emitAsyncBreakCheck); context.setEnableES6BlockScoping(data->compileFlags.enableES6BlockScoping); + context.setEnableAsyncGenerators(data->compileFlags.enableAsyncGenerators); context.setDebugInfoSetting( data->compileFlags.debug ? DebugInfoSetting::ALL : DebugInfoSetting::THROWING); @@ -306,6 +316,11 @@ static void compileEvalWorker(void *argPtr) { parser.setStrictMode(data->compileFlags.strict); auto optParsed = parser.parse(); + if (!optParsed) { + data->success = false; + data->error = outputManager.getErrorString(); + return; + } if (optParsed && parserMode != parser::LazyParse) { parser.registerMagicURLs(); @@ -317,6 +332,9 @@ static void compileEvalWorker(void *argPtr) { std::shared_ptr semCtx = std::make_shared(context, provider->shareSemCtx()); + optParsed = llvh::cast( + hermes::transformASTForCompilation(context, *optParsed)); + // A non-null home object means the parent function context could reference // super. bool parentHadSuperBinding = evalDataInst->getHomeObject() != nullptr; diff --git a/lib/CompilerDriver/CompilerDriver.cpp b/lib/CompilerDriver/CompilerDriver.cpp index 8b81223d330..4e3d1405b48 100644 --- a/lib/CompilerDriver/CompilerDriver.cpp +++ b/lib/CompilerDriver/CompilerDriver.cpp @@ -12,6 +12,7 @@ #include "hermes/AST/Context.h" #include "hermes/AST/ESTreeJSONDumper.h" #include "hermes/AST/TS2Flow.h" +#include "hermes/AST/TransformAST.h" #include "hermes/AST2JS/AST2JS.h" #include "hermes/BCGen/HBC/BytecodeDisassembler.h" #include "hermes/BCGen/HBC/HBC.h" @@ -331,6 +332,13 @@ opt ES6BlockScoping( Hidden, cat(CompilerCategory)); +opt EnableAsyncGenerators( + "Xasync-generators", + init(false), + desc("Enable support for async generators"), + Hidden, + cat(CompilerCategory)); + opt MetroRequireOpt( "Xmetro-require", init(true), @@ -868,6 +876,10 @@ ESTree::NodePtr parseJS( } #endif + parsedAST = hermes::transformASTForCompilation(*context, parsedAST); + if (!parsedAST) + return nullptr; + // If we are executing in typed mode and not script, then wrap the program. if (shouldWrapInIIFE) { parsedAST = wrapInIIFE(context, llvh::cast(parsedAST)); @@ -1125,6 +1137,7 @@ std::shared_ptr createContext( context->setStrictMode((!cl::NonStrictMode && cl::StrictMode) || cl::Typed); context->setEnableEval(cl::EnableEval); context->setEnableES6BlockScoping(cl::ES6BlockScoping); + context->setEnableAsyncGenerators(cl::EnableAsyncGenerators); context->setMetroRequireOpt(cl::MetroRequireOpt); context->getSourceErrorManager().setOutputOptions(guessErrorOutputOptions()); diff --git a/lib/IRGen/ESTreeIRGen-stmt.cpp b/lib/IRGen/ESTreeIRGen-stmt.cpp index a34c699c381..bc2a9b98393 100644 --- a/lib/IRGen/ESTreeIRGen-stmt.cpp +++ b/lib/IRGen/ESTreeIRGen-stmt.cpp @@ -54,7 +54,7 @@ void ESTreeIRGen::genStatement(ESTree::Node *stmt) { } if (auto *FOS = llvh::dyn_cast(stmt)) { - return genForOfStatement(FOS); + return FOS->_await ? genAsyncForOfStatement(FOS) : genForOfStatement(FOS); } if (auto *Ret = llvh::dyn_cast(stmt)) { @@ -978,6 +978,78 @@ void ESTreeIRGen::genForOfStatement(ESTree::ForOfStatementNode *forOfStmt) { Builder.setInsertionBlock(exitBlock); } +void ESTreeIRGen::genAsyncForOfStatement( + ESTree::ForOfStatementNode *forOfStmt) { + emitScopeDeclarations(forOfStmt->getScope()); + + auto *function = Builder.getInsertionBlock()->getParent(); + auto *getNextBlock = Builder.createBasicBlock(function); + auto *bodyBlock = Builder.createBasicBlock(function); + auto *exitBlock = Builder.createBasicBlock(function); + + // Initialize the goto labels. + curFunction()->initLabel(forOfStmt, exitBlock, getNextBlock); + + auto *exprValue = genExpression(forOfStmt->_right); + const IteratorRecordSlow iteratorRecord = emitGetAsyncIteratorSlow(exprValue); + + Builder.createBranchInst(getNextBlock); + + // Attempt to retrieve the next value. If iteration is complete, finish the + // loop. This stays outside the SurroundingTry below because exceptions in + // `.next()` should not call `.return()` on the iterator. + Builder.setInsertionBlock(getNextBlock); + auto *nextResult = genYieldOrAwaitExpr(emitIteratorNextSlow(iteratorRecord)); + auto *done = emitIteratorCompleteSlow(nextResult); + Builder.createCondBranchInst(done, exitBlock, bodyBlock); + + Builder.setInsertionBlock(bodyBlock); + auto *nextValue = emitIteratorValueSlow(nextResult); + + emitTryCatchScaffolding( + getNextBlock, + // emitBody. + [this, forOfStmt, nextValue, &iteratorRecord, getNextBlock]( + BasicBlock *catchBlock) { + // Generate IR for the body of Try + SurroundingTry thisTry{ + curFunction(), + forOfStmt, + catchBlock, + {}, + [this, &iteratorRecord, getNextBlock]( + ESTree::Node *, + ControlFlowChange cfc, + BasicBlock *continueTarget) { + // Only emit the iteratorClose if this is a + // 'break' or if the target of the control flow + // change is outside the current loop. If + // continuing the existing loop, do not close + // the iterator. + if (cfc == ControlFlowChange::Break || + continueTarget != getNextBlock) + emitIteratorCloseSlow(iteratorRecord, false); + }}; + + // Note: obtaining the value is not protected, but storing it is. + createLRef(forOfStmt->_left, false).emitStore(nextValue); + + genStatement(forOfStmt->_body); + Builder.setLocation(SourceErrorManager::convertEndToLocation( + forOfStmt->_body->getSourceRange())); + }, + // emitNormalCleanup. + []() {}, + // emitHandler. + [this, &iteratorRecord](BasicBlock *) { + auto *catchReg = Builder.createCatchInst(); + emitIteratorCloseSlow(iteratorRecord, true); + Builder.createThrowInst(catchReg); + }); + + Builder.setInsertionBlock(exitBlock); +} + void ESTreeIRGen::genForOfFastArrayStatement( ESTree::ForOfStatementNode *forOfStmt, flow::ArrayType *type) { diff --git a/lib/IRGen/ESTreeIRGen.cpp b/lib/IRGen/ESTreeIRGen.cpp index c82f6137f40..b2fd637334d 100644 --- a/lib/IRGen/ESTreeIRGen.cpp +++ b/lib/IRGen/ESTreeIRGen.cpp @@ -570,6 +570,12 @@ Value *ESTreeIRGen::emitIteratorSymbol() { "iterator"); } +Value *ESTreeIRGen::emitAsyncIteratorSymbol() { + return Builder.createLoadPropertyInst( + Builder.createGetBuiltinClosureInst(BuiltinMethod::globalThis_Symbol), + "asyncIterator"); +} + ESTreeIRGen::IteratorRecordSlow ESTreeIRGen::emitGetIteratorSlow(Value *obj) { auto *method = Builder.createLoadPropertyInst(obj, emitIteratorSymbol()); auto *iterator = Builder.createCallInst( @@ -581,6 +587,21 @@ ESTreeIRGen::IteratorRecordSlow ESTreeIRGen::emitGetIteratorSlow(Value *obj) { return {iterator, nextMethod}; } +ESTreeIRGen::IteratorRecordSlow ESTreeIRGen::emitGetAsyncIteratorSlow( + Value *obj) { + auto *makeAsyncIterator = Builder.createGetBuiltinClosureInst( + BuiltinMethod::HermesBuiltin_makeAsyncIterator); + + auto *iterator = Builder.createCallInst( + makeAsyncIterator, + Builder.getLiteralUndefined(), + Builder.getLiteralUndefined(), + {obj}); + auto *nextMethod = Builder.createLoadPropertyInst(iterator, "next"); + + return {iterator, nextMethod}; +} + Value *ESTreeIRGen::emitIteratorNextSlow(IteratorRecordSlow iteratorRecord) { auto *nextResult = Builder.createCallInst( iteratorRecord.nextMethod, diff --git a/lib/IRGen/ESTreeIRGen.h b/lib/IRGen/ESTreeIRGen.h index bd6118291d1..32fce6fe7be 100644 --- a/lib/IRGen/ESTreeIRGen.h +++ b/lib/IRGen/ESTreeIRGen.h @@ -683,6 +683,7 @@ class ESTreeIRGen { void genForOfFastArrayStatement( ESTree::ForOfStatementNode *forOfStmt, flow::ArrayType *type); + void genAsyncForOfStatement(ESTree::ForOfStatementNode *forOfStmt); void genWhileLoop(ESTree::WhileStatementNode *loop); void genDoWhileLoop(ESTree::DoWhileStatementNode *loop); @@ -1425,6 +1426,9 @@ class ESTreeIRGen { /// \return the internal value @@iterator Value *emitIteratorSymbol(); + /// \return the internal value @@asyncIterator + Value *emitAsyncIteratorSymbol(); + /// IteratorRecord as defined in ES2018 7.4.1 GetIterator struct IteratorRecordSlow { Value *iterator; @@ -1443,6 +1447,15 @@ class ESTreeIRGen { /// \return (iterator, nextMethod) IteratorRecordSlow emitGetIteratorSlow(Value *obj); + /// Call obj[@@asyncIterator], which should return an async iterator, + /// and return the iterator itself and its \c next() method. + /// + /// NOTE: This API is slow and should only be used if it is necessary to + /// provide a value to the `next()` method on the iterator. + /// + /// \return (iterator, nextMethod) + IteratorRecordSlow emitGetAsyncIteratorSlow(Value *obj); + /// ES2018 7.4.2 IteratorNext /// https://www.ecma-international.org/ecma-262/9.0/index.html#sec-iteratornext /// diff --git a/lib/InternalJavaScript/04-AsyncIterator.js b/lib/InternalJavaScript/04-AsyncIterator.js new file mode 100644 index 00000000000..b2edea3b5d6 --- /dev/null +++ b/lib/InternalJavaScript/04-AsyncIterator.js @@ -0,0 +1,160 @@ +function initAsyncIterators() { + var HermesPromise = globalThis.Promise; + + /** + * Creates an asynchronous iterator for the given iterable. + * + * This function checks if the iterable already has an async iterator. + * If it does, it returns the existing async iterator. + * If not, it constructs an async iterator from the iterable's synchronous iterator. + * The constructed async iterator resolves any promise values within its methods. + * + * @param {Iterable} iterable - The iterable object for which to create an async iterator. + * @returns {AsyncIterator} - An asynchronous iterator for the given iterable. + */ + function _makeAsyncIterator(iterable) { + if (iterable[Symbol.asyncIterator]) { + return iterable[Symbol.asyncIterator](); + } + + var syncIterator = iterable[Symbol.iterator](); + async function handleResult(result) { + if (result.done) { + return result; + } + var value = result.value; + var resolvedValue = value instanceof HermesPromise ? await value : value; + return { done: false, value: resolvedValue }; + } + + return { + async next() { + var result = syncIterator.next(); + return handleResult(result); + }, + async return(value) { + var result = typeof syncIterator.return === 'function' ? syncIterator.return(value) : { done: true, value }; + return handleResult(result); + }, + async throw(error) { + var result = typeof syncIterator.throw === 'function' ? syncIterator.throw(error) : { done: true, value: error }; + return handleResult(result); + } + }; + } + + // Register as "makeAsyncIterator" + internalBytecodeResult.makeAsyncIterator = _makeAsyncIterator; +} + +function initAsyncGenerators() { + var HermesPromise = globalThis.Promise; + + // https://github.com/babel/babel/blob/dcfa42b933299644c4e78168cb4678ecbfae67ed/packages/babel-runtime-corejs3/helpers/esm/wrapAsyncGenerator.js + function OverloadYield(value, kind) { + this.value = value, this.kind = kind; + } + + /** + * Implements an asynchronous generator to handle async iteration. + * + * This function manages state transitions (next, throw, return) + * through promises to mimic JavaScript's native async generator behavior. + * https://github.com/babel/babel/blob/dcfa42b933299644c4e78168cb4678ecbfae67ed/packages/babel-runtime-corejs3/helpers/esm/awaitAsyncGenerator.js + */ + function AsyncGenerator(generatorFunction) { + var currentPromise, resolveQueue; + + function resume(key, argument) { + try { + var nextIteration = generatorFunction[key](argument), + iterationValue = nextIteration.value, + isOverloadYield = iterationValue instanceof OverloadYield; + HermesPromise.resolve(isOverloadYield ? iterationValue.value : iterationValue).then(function (resolvedValue) { + if (isOverloadYield) { + var nextKey = "return" === key ? "return" : "next"; + if (!iterationValue.kind || resolvedValue.done) { + return resume(nextKey, resolvedValue); + } + resolvedValue = generatorFunction[nextKey](resolvedValue).value; + } + conclude(nextIteration.done ? "return" : "normal", resolvedValue); + }, function (error) { + resume("throw", error); + }); + } catch (error) { + conclude("throw", error); + } + } + + function conclude(type, result) { + switch (type) { + case "return": + currentPromise.resolve({ value: result, done: true }); + break; + case "throw": + currentPromise.reject(result); + break; + default: + currentPromise.resolve({ value: result, done: false }); + } + (currentPromise = currentPromise.next) ? resume(currentPromise.key, currentPromise.arg) : resolveQueue = null; + } + + this._invoke = function (key, argument) { + return new HermesPromise(function (resolve, reject) { + var promiseCapability = { + key: key, + arg: argument, + resolve: resolve, + reject: reject, + next: null + }; + resolveQueue ? resolveQueue = resolveQueue.next = promiseCapability : (currentPromise = resolveQueue = promiseCapability, resume(key, argument)); + }); + }; + + if (typeof generatorFunction["return"] !== "function") { + this["return"] = void 0; + } + } + + AsyncGenerator.prototype[Symbol.asyncIterator] = function () { + return this; + }; + AsyncGenerator.prototype.next = function (value) { + return this._invoke("next", value); + }; + AsyncGenerator.prototype["throw"] = function (error) { + return this._invoke("throw", error); + }; + AsyncGenerator.prototype["return"] = function (value) { + return this._invoke("return", value); + }; + + // https://github.com/babel/babel/blob/dcfa42b933299644c4e78168cb4678ecbfae67ed/packages/babel-runtime-corejs3/helpers/esm/wrapAsyncGenerator.js + function _wrapAsyncGenerator(generatorFunction) { + return function () { + return new AsyncGenerator(generatorFunction.apply(this, arguments)); + }; + } + + // https://github.com/babel/babel/blob/dcfa42b933299644c4e78168cb4678ecbfae67ed/packages/babel-runtime-corejs3/helpers/esm/awaitAsyncGenerator.js + function _awaitAsyncGenerator(value) { + return new OverloadYield(value, 0); + } + + // Since async generators require an AST transformation, using internalBytecodeResult is not feasible. + var HermesAsyncIteratorsInternal = { + _wrapAsyncGenerator, + _awaitAsyncGenerator, + }; + Object.freeze(HermesAsyncIteratorsInternal); + globalThis.HermesAsyncIteratorsInternal = HermesAsyncIteratorsInternal; +} + +initAsyncIterators(); + +if (HermesInternal?.hasAsyncGenerators?.()) { + initAsyncGenerators(); +} diff --git a/lib/Sema/SemResolve.cpp b/lib/Sema/SemResolve.cpp index 7a1234cfd31..2e347b0d1ab 100644 --- a/lib/Sema/SemResolve.cpp +++ b/lib/Sema/SemResolve.cpp @@ -162,6 +162,7 @@ bool resolveAST( flow::FlowContext *flowContext, ESTree::ProgramNode *root, const DeclarationFileListTy &ambientDecls) { + PerfSection validation("Resolving JavaScript global AST"); // Resolve the entire AST. DeclCollectorMapTy declCollectorMap{}; diff --git a/lib/Sema/SemanticResolver.cpp b/lib/Sema/SemanticResolver.cpp index 528327e711a..885378bf8b6 100644 --- a/lib/Sema/SemanticResolver.cpp +++ b/lib/Sema/SemanticResolver.cpp @@ -515,8 +515,6 @@ void SemanticResolver::visit(ESTree::ForInStatementNode *node) { } void SemanticResolver::visit(ESTree::ForOfStatementNode *node) { - if (compile_ && node->_await) - sm_.error(node->getStartLoc(), "for await is not supported"); visitForInOf(node, node, node->_left, node->_right, node->_body); } diff --git a/lib/VM/JSLib/HermesInternal.cpp b/lib/VM/JSLib/HermesInternal.cpp index 90ca7c45cd2..fcc47554f4e 100644 --- a/lib/VM/JSLib/HermesInternal.cpp +++ b/lib/VM/JSLib/HermesInternal.cpp @@ -377,6 +377,11 @@ hermesInternalHasPromise(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeBoolValue(true); } +CallResult +hermesInternalHasAsyncGenerators(void *, Runtime &runtime, NativeArgs args) { + return HermesValue::encodeBoolValue(runtime.hasAsyncGenerators()); +} + CallResult hermesInternalUseEngineQueue(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeBoolValue(runtime.hasMicrotaskQueue()); @@ -757,6 +762,7 @@ Handle createHermesInternalObject( // present by the VM internals even under a security-sensitive environment // where HermesInternal might be explicitly disabled. defineInternMethod(P::hasPromise, hermesInternalHasPromise); + defineInternMethod(P::hasAsyncGenerators, hermesInternalHasAsyncGenerators); defineInternMethod(P::enqueueJob, hermesInternalEnqueueJob); defineInternMethod( P::setPromiseRejectionTrackingHook, diff --git a/lib/VM/JSLib/eval.cpp b/lib/VM/JSLib/eval.cpp index 890a32c8077..73001676418 100644 --- a/lib/VM/JSLib/eval.cpp +++ b/lib/VM/JSLib/eval.cpp @@ -42,6 +42,7 @@ CallResult evalInEnvironment( compileFlags.lazy = utf8code.size() >= compileFlags.preemptiveFileCompilationThreshold; compileFlags.enableES6BlockScoping = runtime.hasES6BlockScoping(); + compileFlags.enableAsyncGenerators = runtime.hasAsyncGenerators(); compileFlags.requireSingleFunction = singleFunction; #ifdef HERMES_ENABLE_DEBUGGER // Required to allow stepping and examining local variables in eval'd code diff --git a/lib/VM/Runtime.cpp b/lib/VM/Runtime.cpp index 705e9ed12c3..04c2bc993e4 100644 --- a/lib/VM/Runtime.cpp +++ b/lib/VM/Runtime.cpp @@ -283,6 +283,7 @@ Runtime::Runtime( runtimeConfig.getVMExperimentFlags()), jitContext_(runtimeConfig.getEnableJIT()), hasES6Proxy_(runtimeConfig.getES6Proxy()), + hasAsyncGenerators_(runtimeConfig.getEnableAsyncGenerators()), hasES6BlockScoping_(runtimeConfig.getES6BlockScoping()), hasIntl_(runtimeConfig.getIntl()), hasMicrotaskQueue_(runtimeConfig.getMicrotaskQueue()), diff --git a/public/hermes/Public/RuntimeConfig.h b/public/hermes/Public/RuntimeConfig.h index 0de34d5a355..87766bf67c6 100644 --- a/public/hermes/Public/RuntimeConfig.h +++ b/public/hermes/Public/RuntimeConfig.h @@ -69,6 +69,9 @@ class PinnedHermesValue; /* Support for ES6 block scoping. */ \ F(constexpr, bool, ES6BlockScoping, false) \ \ + /* Support for async generators. */ \ + F(constexpr, bool, EnableAsyncGenerators, false) \ + \ /* Support for ECMA-402 Intl APIs. */ \ F(constexpr, bool, Intl, true) \ \ diff --git a/test/hermes/async-generators-throw.js b/test/hermes/async-generators-throw.js new file mode 100644 index 00000000000..02a6bb660ad --- /dev/null +++ b/test/hermes/async-generators-throw.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes -Xasync-generators %s | %FileCheck --match-full-lines %s +// RUN: %hermes -Xasync-generators -O0 %s | %FileCheck --match-full-lines %s +// RUN: %hermes -Xasync-generators -lazy %s | %FileCheck --match-full-lines %s + + +async function* errorHandlingGenerator() { + yield 1; + throw new Error('Async generator threw!'); + yield 2; +} + +// Test error propagation with the async generator +(async function testErrorPropagation() { + try { + for await (const value of errorHandlingGenerator()) { + print(value); + } + } catch (error) { + print('Caught error:', error.message); + } +})(); +//CHECK: Caught error: Async generator threw! diff --git a/test/hermes/async-generators.js b/test/hermes/async-generators.js new file mode 100644 index 00000000000..3a2faa4b475 --- /dev/null +++ b/test/hermes/async-generators.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes -Xasync-generators %s | %FileCheck --match-full-lines %s +// RUN: %hermes -Xasync-generators -O0 %s | %FileCheck --match-full-lines %s +// RUN: %hermes -Xasync-generators -lazy %s | %FileCheck --match-full-lines %s + +async function* mixedValuesGenerator() { + yield 1; + yield Promise.resolve(2); + yield new Promise((resolve) => resolve(3)); +} + +// Test async generator with for await...of loop +(async function testMixedValuesGenerator() { + let sum = 0; + for await (const value of mixedValuesGenerator()) { + sum += value; + } + print(sum); +})(); +//CHECK: 6 diff --git a/test/hermes/for-await-of-async-iterator.js b/test/hermes/for-await-of-async-iterator.js new file mode 100644 index 00000000000..a472c6f3b5a --- /dev/null +++ b/test/hermes/for-await-of-async-iterator.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes %s | %FileCheck --match-full-lines %s +// RUN: %hermes -O0 %s | %FileCheck --match-full-lines %s + +// Custom async iterable using Symbol.asyncIterator +const asyncIterable = { + [Symbol.asyncIterator]: function() { + let i = 1; + const max = 3; + return { + next: function() { + if (i <= max) { + return Promise.resolve({ value: i++, done: false }); + } else { + return Promise.resolve({ done: true }); + } + } + }; + } +}; + +// Test for await of loop with custom async iterable +(async function testCustomAsyncIterable() { + sum = 0; + for await (const value of asyncIterable) { + sum += value; + } + print(sum); +})(); + +//CHECK: 6 \ No newline at end of file diff --git a/test/hermes/for-await-of-return.js b/test/hermes/for-await-of-return.js new file mode 100644 index 00000000000..11ae252727d --- /dev/null +++ b/test/hermes/for-await-of-return.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes %s | %FileCheck --match-full-lines %s +// RUN: %hermes -O0 %s | %FileCheck --match-full-lines %s +// RUN: %hermes -lazy %s | %FileCheck --match-full-lines %s + +// Custom async iterable using Symbol.asyncIterator +const asyncIterable = { + [Symbol.asyncIterator]: function() { + return { + next: function() { + return Promise.resolve({ value:0, done: false }); + }, + return: function() { + print('Cleanup performed'); + return Promise.resolve({ done: true }); + } + }; + } +}; + +// Using break and triggering return() in for await...of loop +(async function testBreakWithReturn() { + for await (const value of asyncIterable) { + if (value === 0) { + break; + } + } +})(); + +//CHECK: Cleanup performed \ No newline at end of file diff --git a/test/hermes/for-await-of-sync-iterator.js b/test/hermes/for-await-of-sync-iterator.js new file mode 100644 index 00000000000..a0c836396a3 --- /dev/null +++ b/test/hermes/for-await-of-sync-iterator.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes %s | %FileCheck --match-full-lines %s +// RUN: %hermes -O0 %s | %FileCheck --match-full-lines %s +// RUN: %hermes -lazy %s | %FileCheck --match-full-lines %s + +// For await of loop should work with a sync iterable +const syncIterable = [1,2,3]; + +(async function testCustomSyncIterable() { + sum = 0; + for await (const value of syncIterable) { + sum += value; + } + print(sum); +})(); + +//CHECK: 6 \ No newline at end of file diff --git a/test/hermes/hermes-internal.js b/test/hermes/hermes-internal.js index 22c9468b0cc..990228e4e93 100644 --- a/test/hermes/hermes-internal.js +++ b/test/hermes/hermes-internal.js @@ -16,11 +16,12 @@ // concat // hasPromise +// hasAsyncGenerators // setPromiseRejectionTrackingHook // enablePromiseRejectionTracker // enqueueJob // useEngineQueue -var SAFE_FIELDS_COUNT = 6; +var SAFE_FIELDS_COUNT = 7; // Check that we can disable unsafe fields of HermesInternal. print(Object.getOwnPropertyNames(HermesInternal).length) diff --git a/tools/hermes/hermes.cpp b/tools/hermes/hermes.cpp index 5bcf130c446..4b65924dd48 100644 --- a/tools/hermes/hermes.cpp +++ b/tools/hermes/hermes.cpp @@ -111,6 +111,7 @@ static int executeHBCBytecodeFromCL( .withMaxNumRegisters(flags.MaxNumRegisters) .withEnableJIT(flags.DumpJITCode || flags.EnableJIT || flags.ForceJIT) .withEnableEval(cl::EnableEval) + .withEnableAsyncGenerators(cl::EnableAsyncGenerators) .withVerifyEvalIR(cl::VerifyIR) .withOptimizedEval(cl::OptimizedEval) .withAsyncBreakCheckInEval(cl::EmitAsyncBreakCheck) @@ -178,6 +179,7 @@ static vm::RuntimeConfig getReplRuntimeConfig() { .build()) .withVMExperimentFlags(flags.VMExperimentFlags) .withES6Proxy(flags.ES6Proxy) + .withEnableAsyncGenerators(cl::EnableAsyncGenerators) .withIntl(flags.Intl) .withMicrotaskQueue(flags.MicrotaskQueue) .withEnableHermesInternal(flags.EnableHermesInternal) diff --git a/tools/shermes/shermes.cpp b/tools/shermes/shermes.cpp index 8bd84260785..36c768c4d7a 100644 --- a/tools/shermes/shermes.cpp +++ b/tools/shermes/shermes.cpp @@ -13,6 +13,7 @@ #include "hermes/AST/ESTreeJSONDumper.h" #include "hermes/AST/NativeContext.h" #include "hermes/AST/TS2Flow.h" +#include "hermes/AST/TransformAST.h" #include "hermes/IR/IRVerifier.h" #include "hermes/IRGen/IRGen.h" #include "hermes/Optimizer/PassManager/PassManager.h" @@ -325,6 +326,13 @@ cl::opt ES6BlockScoping{ llvh::cl::init(false), llvh::cl::cat(CompilerCategory)}; +cl::opt EnableAsyncGenerators{ + "Xasync-generators", + llvh::cl::Hidden, + llvh::cl::desc("Enable support for async generators"), + llvh::cl::init(false), + llvh::cl::cat(CompilerCategory)}; + cl::opt MetroRequireOpt( "Xmetro-require", llvh::cl::init(true), @@ -647,6 +655,7 @@ std::shared_ptr createContext() { context->setStrictMode(cli::Typed || cli::StrictMode); context->setEnableEval(cli::EnableEval); context->setEnableES6BlockScoping(cli::ES6BlockScoping); + context->setEnableAsyncGenerators(cli::EnableAsyncGenerators); context->getSourceErrorManager().setOutputOptions(guessErrorOutputOptions()); setWarningsAreErrorsFromFlags(context->getSourceErrorManager()); @@ -805,6 +814,11 @@ ESTree::NodePtr parseJS( } #endif + parsedAST = llvh::cast( + hermes::transformASTForCompilation(*context, parsedAST)); + if (!parsedAST) + return nullptr; + // If we are executing in typed mode and not script, then wrap the program. if (shouldWrapInIIFE) { parsedAST = wrapInIIFE(context, parsedAST); diff --git a/utils/testsuite/hermes.py b/utils/testsuite/hermes.py index ac4b9059792..6cfa7f4fb01 100644 --- a/utils/testsuite/hermes.py +++ b/utils/testsuite/hermes.py @@ -19,6 +19,7 @@ "-fno-static-builtins", "-Xes6-block-scoping", "-Xenable-tdz", + "-Xasync-generators", ] ES6_ARGS = ["-Xes6-proxy"] EXTRA_RUN_ARGS = ["-Xhermes-internal-test-methods"]