diff --git a/bin/ch/WScriptJsrt.cpp b/bin/ch/WScriptJsrt.cpp index 4bfb7c2f86e..1eff3ab27b3 100644 --- a/bin/ch/WScriptJsrt.cpp +++ b/bin/ch/WScriptJsrt.cpp @@ -600,30 +600,6 @@ JsValueRef WScriptJsrt::LoadScriptHelper(JsValueRef callee, bool isConstructCall return returnValue; } -JsErrorCode WScriptJsrt::InitializeModuleInfo(JsModuleRecord moduleRecord) -{ - JsErrorCode errorCode = JsNoError; - errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule); - - if (errorCode == JsNoError) - { - errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, (void*)WScriptJsrt::FetchImportedModuleFromScript); - - if (errorCode == JsNoError) - { - errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback); - - if (errorCode == JsNoError) - { - errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_InitializeImportMetaCallback, (void*)WScriptJsrt::InitializeImportMetaCallback); - } - } - } - - IfJsrtErrorFailLogAndRetErrorCode(errorCode); - return JsNoError; -} - void WScriptJsrt::GetDir(LPCSTR fullPathNarrow, std::string *fullDirNarrow) { char fileDrive[_MAX_DRIVE]; @@ -668,10 +644,6 @@ JsErrorCode WScriptJsrt::LoadModuleFromString(LPCSTR fileName, LPCSTR fileConten nullptr, specifier, &requestModule); } if (errorCode == JsNoError) - { - errorCode = InitializeModuleInfo(requestModule); - } - if (errorCode == JsNoError) { if (fullName) { @@ -1220,7 +1192,11 @@ bool WScriptJsrt::Initialize() IfJsrtErrorFail(CreatePropertyIdFromString("console", &consoleName), false); IfJsrtErrorFail(ChakraRTInterface::JsSetProperty(global, consoleName, console, true), false); - IfJsrtErrorFail(InitializeModuleInfo(nullptr), false); + IfJsrtErrorFail(ChakraRTInterface::JsSetModuleHostInfo(nullptr, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule), false); + IfJsrtErrorFail(ChakraRTInterface::JsSetModuleHostInfo(nullptr, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, (void*)WScriptJsrt::FetchImportedModuleFromScript), false); + IfJsrtErrorFail(ChakraRTInterface::JsSetModuleHostInfo(nullptr, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback), false); + IfJsrtErrorFail(ChakraRTInterface::JsSetModuleHostInfo(nullptr, JsModuleHostInfo_InitializeImportMetaCallback, (void*)WScriptJsrt::InitializeImportMetaCallback), false); + IfJsrtErrorFail(ChakraRTInterface::JsSetModuleHostInfo(nullptr, JsModuleHostInfo_ReportModuleCompletionCallback, (void*)WScriptJsrt::ReportModuleCompletionCallback), false); // When the command-line argument `-Test262` is set, // WScript will have the extra support API below and $262 will be @@ -1787,11 +1763,15 @@ JsValueRef __stdcall WScriptJsrt::GetProxyPropertiesCallback(JsValueRef callee, return returnValue; } -bool WScriptJsrt::PrintException(LPCSTR fileName, JsErrorCode jsErrorCode) +bool WScriptJsrt::PrintException(LPCSTR fileName, JsErrorCode jsErrorCode, JsValueRef exception) { LPCWSTR errorTypeString = ConvertErrorCodeToMessage(jsErrorCode); - JsValueRef exception; - ChakraRTInterface::JsGetAndClearException(&exception); + + if (exception == nullptr) + { + ChakraRTInterface::JsGetAndClearException(&exception); + } + if (HostConfigFlags::flags.MuteHostErrorMsgIsEnabled) { return false; @@ -1992,21 +1972,7 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName) errorCode = ChakraRTInterface::JsModuleEvaluation(moduleRecord, &result); if (errorCode != JsNoError) { - if (moduleErrMap[moduleRecord] == RootModule) - { - PrintException(fileName, errorCode); - } - else - { - bool hasException = false; - ChakraRTInterface::JsHasException(&hasException); - if (hasException) - { - JsValueRef exception; - ChakraRTInterface::JsGetAndClearException(&exception); - exception; //unusued - } - } + PrintException(fileName, errorCode); // this should not be called } } } @@ -2039,6 +2005,17 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName) return errorCode; } +JsErrorCode WScriptJsrt::ReportModuleCompletionCallback(JsModuleRecord module, JsValueRef exception) +{ + if (exception != nullptr) + { + JsValueRef specifier = JS_INVALID_REFERENCE; + ChakraRTInterface::JsGetModuleHostInfo(module, JsModuleHostInfo_Url, &specifier); + PrintException(AutoString(specifier).GetString(), JsErrorCode::JsErrorScriptException, exception); + } + return JsNoError; +} + JsErrorCode WScriptJsrt::FetchImportedModuleHelper(JsModuleRecord referencingModule, JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord, LPCSTR refdir) { @@ -2070,7 +2047,6 @@ JsErrorCode WScriptJsrt::FetchImportedModuleHelper(JsModuleRecord referencingMod if (errorCode == JsNoError) { GetDir(fullPath, &moduleDirMap[moduleRecord]); - InitializeModuleInfo(moduleRecord); std::string pathKey = std::string(fullPath); moduleRecordMap[pathKey] = moduleRecord; moduleErrMap[moduleRecord] = ImportedModule; @@ -2112,12 +2088,11 @@ JsErrorCode WScriptJsrt::FetchImportedModuleFromScript(_In_ JsSourceContext dwRe return FetchImportedModuleHelper(nullptr, specifier, dependentModuleRecord); } -// Callback from chakraCore when the module resolution is finished, either successfuly or unsuccessfully. +// Callback from chakraCore when the module resolution is finished, either successfully or unsuccessfully. JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar) { - if (exceptionVar != nullptr) + if (exceptionVar != nullptr && HostConfigFlags::flags.TraceHostCallbackIsEnabled) { - ChakraRTInterface::JsSetException(exceptionVar); JsValueRef specifier = JS_INVALID_REFERENCE; ChakraRTInterface::JsGetModuleHostInfo(referencingModule, JsModuleHostInfo_Url, &specifier); AutoString fileName; @@ -2125,19 +2100,10 @@ JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord refer { fileName.Initialize(specifier); } - - if (HostConfigFlags::flags.TraceHostCallbackIsEnabled) - { - wprintf(_u("NotifyModuleReadyCallback(exception) %S\n"), fileName.GetString()); - } - - // No need to print - just consume the exception - JsValueRef exception; - ChakraRTInterface::JsGetAndClearException(&exception); - exception; // unused + wprintf(_u("NotifyModuleReadyCallback(exception) %S\n"), fileName.GetString()); } - if (exceptionVar != nullptr || moduleErrMap[referencingModule] != ErroredModule) + if (moduleErrMap[referencingModule] != ErroredModule) { WScriptJsrt::ModuleMessage* moduleMessage = WScriptJsrt::ModuleMessage::Create(referencingModule, nullptr); diff --git a/bin/ch/WScriptJsrt.h b/bin/ch/WScriptJsrt.h index 0c106a7cbf3..fba543d7c85 100644 --- a/bin/ch/WScriptJsrt.h +++ b/bin/ch/WScriptJsrt.h @@ -65,6 +65,7 @@ class WScriptJsrt static JsErrorCode FetchImportedModule(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); static JsErrorCode FetchImportedModuleFromScript(_In_ DWORD_PTR dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar); + static JsErrorCode ReportModuleCompletionCallback(JsModuleRecord module, JsValueRef exception); static JsErrorCode CALLBACK InitializeImportMetaCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef importMetaVar); static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState); static void CALLBACK PromiseRejectionTrackerCallback(JsValueRef promise, JsValueRef reason, bool handled, void *callbackState); @@ -101,7 +102,7 @@ class WScriptJsrt static void CALLBACK JsContextBeforeCollectCallback(JsRef contextRef, void *data); #endif - static bool PrintException(LPCSTR fileName, JsErrorCode jsErrorCode); + static bool PrintException(LPCSTR fileName, JsErrorCode jsErrorCode, JsValueRef exception = nullptr); static JsValueRef LoadScript(JsValueRef callee, LPCSTR fileName, LPCSTR fileContent, LPCSTR scriptInjectType, bool isSourceModule, JsFinalizeCallback finalizeCallback, bool isFile); static DWORD_PTR GetNextSourceContext(); static JsValueRef LoadScriptFileHelper(JsValueRef callee, JsValueRef *arguments, unsigned short argumentCount, bool isSourceModule); @@ -129,7 +130,6 @@ class WScriptJsrt static JsValueRef CALLBACK EmptyCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsErrorCode CALLBACK LoadModuleFromString(LPCSTR fileName, LPCSTR fileContent, LPCSTR fullName = nullptr, bool isFile = false); - static JsErrorCode CALLBACK InitializeModuleInfo(JsModuleRecord moduleRecord); static JsValueRef CALLBACK LoadBinaryFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); static JsValueRef CALLBACK LoadTextFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState); diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index 535e151a7ff..88e76eeab27 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -693,6 +693,7 @@ PHASE(All) #define DEFAULT_CONFIG_ESImportMeta (true) #define DEFAULT_CONFIG_ESExportNsAs (true) #define DEFAULT_CONFIG_ES2018AsyncIteration (true) +#define DEFAULT_CONFIG_ESTopLevelAwait (true) #define DEFAULT_CONFIG_ESSharedArrayBuffer (false) @@ -1159,6 +1160,7 @@ FLAGPR (Boolean, ES6, ES6RegExSticky , "Enable ES6 RegEx stick FLAGPR (Boolean, ES6, ES2018RegExDotAll , "Enable ES2018 RegEx dotAll flag" , DEFAULT_CONFIG_ES2018RegExDotAll) FLAGPR (Boolean, ES6, ESExportNsAs , "Enable ES experimental export * as name" , DEFAULT_CONFIG_ESExportNsAs) FLAGPR (Boolean, ES6, ES2018AsyncIteration , "Enable ES2018 Async Iteration" , DEFAULT_CONFIG_ES2018AsyncIteration) +FLAGPR (Boolean, ES6, ESTopLevelAwait , "Enable Top Level Await in modules" , DEFAULT_CONFIG_ESTopLevelAwait) #ifndef COMPILE_DISABLE_ES6RegExPrototypeProperties #define COMPILE_DISABLE_ES6RegExPrototypeProperties 0 diff --git a/lib/Jsrt/ChakraCore.h b/lib/Jsrt/ChakraCore.h index 2d42c3b7ee6..39b18c93b9a 100644 --- a/lib/Jsrt/ChakraCore.h +++ b/lib/Jsrt/ChakraCore.h @@ -115,7 +115,11 @@ typedef enum JsModuleHostInfoKind /// /// Callback to allow host to initialize import.meta object properties. /// - JsModuleHostInfo_InitializeImportMetaCallback = 0x7 + JsModuleHostInfo_InitializeImportMetaCallback = 0x7, + /// + /// Callback to report module completion or exception thrown when evaluating a module. + /// + JsModuleHostInfo_ReportModuleCompletionCallback = 0x8 } JsModuleHostInfoKind; /// @@ -207,6 +211,27 @@ typedef JsErrorCode(CHAKRA_CALLBACK * NotifyModuleReadyCallback)(_In_opt_ JsModu /// typedef JsErrorCode(CHAKRA_CALLBACK * InitializeImportMetaCallback)(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef importMetaVar); +/// +/// User implemented callback to report completion of module execution. +/// +/// +/// This callback is used to report the completion of module execution and to report any runtime exceptions. +/// Note it is not used for dynamicly imported modules import() as the reuslt from those are handled with a +/// promise. +/// If this callback is not set and a module produces an exception: +/// a) a purely synchronous module tree with an exception will set the exception on the runtime +/// (this is not done if this callback is set) +/// b) an exception in an asynchronous module tree will not be reported directly. +/// +/// However in all cases the exception will be set on the JsModuleRecord. +/// +/// The root module that has completed either with an exception or normally. +/// The exception object which was thrown or nullptr if the module had a normal completion. +/// +/// Returns a JsErrorCode: JsNoError if successful. +/// +typedef JsErrorCode(CHAKRA_CALLBACK * ReportModuleCompletionCallback)(_In_ JsModuleRecord module, _In_opt_ JsValueRef exception); + /// /// A structure containing information about a native function callback. /// diff --git a/lib/Jsrt/Core/JsrtContextCore.cpp b/lib/Jsrt/Core/JsrtContextCore.cpp index a999fb6cd76..59fffac697a 100644 --- a/lib/Jsrt/Core/JsrtContextCore.cpp +++ b/lib/Jsrt/Core/JsrtContextCore.cpp @@ -176,6 +176,23 @@ HRESULT ChakraCoreHostScriptContext::InitializeImportMeta(Js::ModuleRecordBase* return E_INVALIDARG; } +bool ChakraCoreHostScriptContext::ReportModuleCompletion(Js::ModuleRecordBase* module, Js::Var exception) +{ + if (reportModuleCompletionCallback == nullptr) + { + return false; + } + { + AUTO_NO_EXCEPTION_REGION; + JsErrorCode errorCode = reportModuleCompletionCallback(module, exception); + if (errorCode == JsNoError) + { + return true; + } + } + return false; +} + ChakraCoreStreamWriter::~ChakraCoreStreamWriter() { HeapDelete(m_serializerCore); diff --git a/lib/Jsrt/Core/JsrtContextCore.h b/lib/Jsrt/Core/JsrtContextCore.h index a28b494fccf..a520352d517 100644 --- a/lib/Jsrt/Core/JsrtContextCore.h +++ b/lib/Jsrt/Core/JsrtContextCore.h @@ -104,7 +104,8 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext fetchImportedModuleCallback(nullptr), fetchImportedModuleFromScriptCallback(nullptr), notifyModuleReadyCallback(nullptr), - initializeImportMetaCallback(nullptr) + initializeImportMetaCallback(nullptr), + reportModuleCompletionCallback(nullptr) { } ~ChakraCoreHostScriptContext() @@ -253,6 +254,7 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) override; HRESULT InitializeImportMeta(Js::ModuleRecordBase* referencingModule, Js::Var importMetaObject) override; + bool ReportModuleCompletion(Js::ModuleRecordBase* module, Js::Var exception) override; void SetNotifyModuleReadyCallback(NotifyModuleReadyCallback notifyCallback) { this->notifyModuleReadyCallback = notifyCallback; } NotifyModuleReadyCallback GetNotifyModuleReadyCallback() const { return this->notifyModuleReadyCallback; } @@ -263,9 +265,12 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext void SetFetchImportedModuleFromScriptCallback(FetchImportedModuleFromScriptCallBack fetchCallback) { this->fetchImportedModuleFromScriptCallback = fetchCallback; } FetchImportedModuleFromScriptCallBack GetFetchImportedModuleFromScriptCallback() const { return this->fetchImportedModuleFromScriptCallback; } - void SetInitializeImportMetaCallback(InitializeImportMetaCallback finalizeCallback) { this->initializeImportMetaCallback = finalizeCallback; } + void SetInitializeImportMetaCallback(InitializeImportMetaCallback initializeCallback) { this->initializeImportMetaCallback = initializeCallback; } InitializeImportMetaCallback GetInitializeImportMetaCallback() const { return this->initializeImportMetaCallback; } + void SetReportModuleCompletionCallback(ReportModuleCompletionCallback processCallback) { this->reportModuleCompletionCallback = processCallback; } + ReportModuleCompletionCallback GetReportModuleCompletionCallback() const { return this->reportModuleCompletionCallback; } + #if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM) void EnsureParentInfo(Js::ScriptContext* scriptContext = NULL) override { @@ -281,4 +286,5 @@ class ChakraCoreHostScriptContext sealed : public HostScriptContext FetchImportedModuleFromScriptCallBack fetchImportedModuleFromScriptCallback; NotifyModuleReadyCallback notifyModuleReadyCallback; InitializeImportMetaCallback initializeImportMetaCallback; + ReportModuleCompletionCallback reportModuleCompletionCallback; }; diff --git a/lib/Jsrt/Core/JsrtCore.cpp b/lib/Jsrt/Core/JsrtCore.cpp index 6f9abd9d31c..d5b43414bfb 100644 --- a/lib/Jsrt/Core/JsrtCore.cpp +++ b/lib/Jsrt/Core/JsrtCore.cpp @@ -149,10 +149,9 @@ JsSetModuleHostInfo( Js::SourceTextModuleRecord* moduleRecord; if (!Js::SourceTextModuleRecord::Is(requestModule)) { - if (moduleHostInfo != JsModuleHostInfo_FetchImportedModuleCallback && - moduleHostInfo != JsModuleHostInfo_FetchImportedModuleFromScriptCallback && - moduleHostInfo != JsModuleHostInfo_NotifyModuleReadyCallback && - moduleHostInfo != JsModuleHostInfo_InitializeImportMetaCallback) + if (moduleHostInfo == JsModuleHostInfo_Exception || + moduleHostInfo == JsModuleHostInfo_HostDefined || + moduleHostInfo == JsModuleHostInfo_Url) { return JsErrorInvalidArgument; } @@ -188,6 +187,9 @@ JsSetModuleHostInfo( case JsModuleHostInfo_InitializeImportMetaCallback: currentContext->GetHostScriptContext()->SetInitializeImportMetaCallback(reinterpret_cast(hostInfo)); break; + case JsModuleHostInfo_ReportModuleCompletionCallback: + currentContext->GetHostScriptContext()->SetReportModuleCompletionCallback(reinterpret_cast(hostInfo)); + break; case JsModuleHostInfo_Url: moduleRecord->SetSpecifier(hostInfo); break; @@ -238,6 +240,9 @@ JsGetModuleHostInfo( case JsModuleHostInfo_InitializeImportMetaCallback: *hostInfo = reinterpret_cast(currentContext->GetHostScriptContext()->GetInitializeImportMetaCallback()); break; + case JsModuleHostInfo_ReportModuleCompletionCallback: + *hostInfo = reinterpret_cast(currentContext->GetHostScriptContext()->GetReportModuleCompletionCallback()); + break; case JsModuleHostInfo_Url: *hostInfo = reinterpret_cast(moduleRecord->GetSpecifier()); break; diff --git a/lib/Parser/Parse.cpp b/lib/Parser/Parse.cpp index d77edbfa2e2..0ffc80e9dc6 100644 --- a/lib/Parser/Parse.cpp +++ b/lib/Parser/Parse.cpp @@ -2727,6 +2727,17 @@ bool Parser::IsTopLevelModuleFunc() return curFunc->nop == knopFncDecl && curFunc->IsModule(); } +void Parser::MakeModuleAsync() +{ + Assert(IsTopLevelModuleFunc()); + if (!m_scriptContext->GetConfig()->IsESTopLevelAwaitEnabled()) + { + Error(ERRExperimental); + } + ParseNodeFnc * curFunc = GetCurrentFunctionNode(); + curFunc->SetIsAsync(); +} + template ParseNodePtr Parser::ParseImportCall() { this->GetScanner()->Scan(); @@ -8873,7 +8884,14 @@ ParseNodePtr Parser::ParseExpr(int oplMin, // is not a grammar production outside of async functions. // // Further, await expressions are disallowed within parameter scopes. - Error(ERRBadAwait); + if (IsTopLevelModuleFunc()) + { + MakeModuleAsync(); + } + else + { + Error(ERRBadAwait); + } } } @@ -10282,7 +10300,14 @@ ParseNodePtr Parser::ParseStatement() { if (!this->GetScanner()->AwaitIsKeywordRegion()) { - Error(ERRBadAwait); // for await () in a non-async function + if (IsTopLevelModuleFunc()) + { + MakeModuleAsync(); + } + else + { + Error(ERRBadAwait); // for await () in a non-async function + } } if (!m_scriptContext->GetConfig()->IsES2018AsyncIterationEnabled()) { diff --git a/lib/Parser/Parse.h b/lib/Parser/Parse.h index b134393280e..84970c93673 100644 --- a/lib/Parser/Parse.h +++ b/lib/Parser/Parse.h @@ -975,6 +975,7 @@ class Parser void CheckIfImportOrExportStatementValidHere(); bool IsTopLevelModuleFunc(); + void MakeModuleAsync(); template ParseNodePtr ParseImport(); template void ParseImportClause(ModuleImportOrExportEntryList* importEntryList, bool parsingAfterComma = false); diff --git a/lib/Runtime/Base/ScriptContext.h b/lib/Runtime/Base/ScriptContext.h index d066b4801d4..4cf07238719 100644 --- a/lib/Runtime/Base/ScriptContext.h +++ b/lib/Runtime/Base/ScriptContext.h @@ -185,6 +185,7 @@ class HostScriptContext virtual HRESULT FetchImportedModuleFromScript(DWORD_PTR dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) = 0; virtual HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) = 0; virtual HRESULT InitializeImportMeta(Js::ModuleRecordBase* referencingModule, Js::Var importMetaObject) = 0; + virtual bool ReportModuleCompletion(Js::ModuleRecordBase* module, Js::Var exception) = 0; virtual HRESULT ThrowIfFailed(HRESULT hr) = 0; diff --git a/lib/Runtime/Base/ThreadConfigFlagsList.h b/lib/Runtime/Base/ThreadConfigFlagsList.h index 036a4ecea35..9e079c340fa 100644 --- a/lib/Runtime/Base/ThreadConfigFlagsList.h +++ b/lib/Runtime/Base/ThreadConfigFlagsList.h @@ -51,6 +51,7 @@ FLAG_RELEASE(IsES2018AsyncIterationEnabled, ES2018AsyncIteration) #ifdef ENABLE_TEST_HOOKS FLAG_RELEASE(Force32BitByteCode, Force32BitByteCode) #endif +FLAG_RELEASE(IsESTopLevelAwaitEnabled, ESTopLevelAwait) #ifdef ENABLE_PROJECTION FLAG(AreWinRTDelegatesInterfaces, WinRTDelegateInterfaces) FLAG_RELEASE(IsWinRTAdaptiveAppsEnabled, WinRTAdaptiveApps) diff --git a/lib/Runtime/Language/SourceTextModuleRecord.cpp b/lib/Runtime/Language/SourceTextModuleRecord.cpp index 4b9c62c57d6..010885f89f4 100644 --- a/lib/Runtime/Language/SourceTextModuleRecord.cpp +++ b/lib/Runtime/Language/SourceTextModuleRecord.cpp @@ -35,11 +35,6 @@ namespace Js hostDefined(nullptr), exportedNames(nullptr), resolvedExportMap(nullptr), - wasParsed(false), - wasDeclarationInitialized(false), - parentsNotified(false), - isRootModule(false), - hadNotifyHostReady(false), localExportSlots(nullptr), moduleId(InvalidModuleIndex), localSlotCount(InvalidSlotCount), @@ -109,7 +104,7 @@ namespace Js // Mark module as root module if it currently has no parents // Note, if there are circular imports it may gain parents later - if (parentModuleList == nullptr) + if (parentModuleList == nullptr && promise == nullptr) { SetIsRootModule(); } @@ -374,7 +369,7 @@ namespace Js OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentsAsNeeded\n")); NotifyParentsAsNeeded(); - if (!WasDeclarationInitialized() && isRootModule) + if (!WasDeclarationInitialized() && (isRootModule || promise != nullptr)) { // TODO: move this as a promise call? if parser is called from a different thread // We'll need to call the bytecode gen in the main thread as we are accessing GC. @@ -766,6 +761,11 @@ namespace Js { this->parentModuleList->Add(parentRecord); } + + if (IsEvaluating()) + { + parentRecord->IncrementAwaited(); + } } } @@ -781,25 +781,27 @@ namespace Js } bool result = true; confirmedReady = true; - EnsureChildModuleSet(GetScriptContext()); - childrenModuleSet->EachValue([&](SourceTextModuleRecord* childModuleRecord) { - if (childModuleRecord->ParentsNotified()) - { - return false; - } - else - { - if (childModuleRecord->ConfirmChildrenParsed()) + if (childrenModuleSet != nullptr) + { + childrenModuleSet->EachValue([&](SourceTextModuleRecord* childModuleRecord) { + if (childModuleRecord->ParentsNotified()) { return false; } else { - result = false; - return true; + if (childModuleRecord->ConfirmChildrenParsed()) + { + return false; + } + else + { + result = false; + return true; + } } - } - }); + }); + } confirmedReady = false; return result; } @@ -873,7 +875,7 @@ namespace Js { // zero out fields is more a defense in depth as those fields are not needed anymore Assert(wasParsed); - Assert(wasEvaluated); + Assert(evaluating); Assert(wasDeclarationInitialized); // Debugger can reparse the source and generate the byte code again. Don't cleanup the // helper information for now. @@ -928,7 +930,7 @@ namespace Js void SourceTextModuleRecord::GenerateRootFunction() { - // On cyclic dependency, we may endup generating the root function twice + // On cyclic dependency, we may end up generating the root function twice // so make sure we don't if (this->rootFunction != nullptr) { @@ -971,6 +973,18 @@ namespace Js } } + static bool ReportModuleCompletion(SourceTextModuleRecord* module, Var exception) + { + bool hasCallback = false; + ScriptContext* scriptContext = module->GetScriptContext(); + OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ReportModuleCompletion %s\n"), module->GetSpecifierSz()); + LEAVE_SCRIPT_IF_ACTIVE(scriptContext, + { + hasCallback = scriptContext->GetHostScriptContext()->ReportModuleCompletion(module, exception); + }); + return hasCallback; + } + bool SourceTextModuleRecord::ModuleEvaluationPrepass() { if (this->errorObject != nullptr) @@ -983,6 +997,10 @@ namespace Js SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this, false); return false; } + if (isRootModule && ReportModuleCompletion(this, errorObject)) + { + return false; + } else { JavascriptExceptionOperators::Throw(errorObject, this->scriptContext); @@ -1001,55 +1019,34 @@ namespace Js } #endif - JavascriptExceptionObject *exception = nullptr; - - try + if (childrenModuleSet != nullptr) { - if (childrenModuleSet != nullptr) + childrenModuleSet->EachValue([=](SourceTextModuleRecord* childModuleRecord) { - childrenModuleSet->EachValue([=](SourceTextModuleRecord* childModuleRecord) + if (!childModuleRecord->WasEvaluationPrepassed()) { - if (!childModuleRecord->WasEvaluationPrepassed()) - { - childModuleRecord->ModuleEvaluationPrepass(); - } - - // if child module was evaluated before and threw need to re-throw now - // if child module has been dynamically imported and has exception need to throw - if (childModuleRecord->GetErrorObject() != nullptr) - { - this->ReleaseParserResourcesForHierarchy(); + childModuleRecord->ModuleEvaluationPrepass(); + } - JavascriptExceptionOperators::Throw(childModuleRecord->GetErrorObject(), this->scriptContext); - } - }); - } + // if child module was evaluated before and threw need to re-throw now + // if child module has been dynamically imported and has exception need to throw + if (childModuleRecord->GetErrorObject() != nullptr) + { + this->ReleaseParserResourcesForHierarchy(); - AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_JavascriptException)); - BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext()) - { - Arguments outArgs(CallInfo(CallFlags_Value, 0), nullptr); - this->generator = VarTo(rootFunction->CallRootFunction(outArgs, scriptContext, true)); - } - END_SAFE_REENTRANT_CALL - } - catch (const Js::JavascriptException &err) - { - exception = err.GetAndClear(); - Var errorObject = exception->GetThrownObject(scriptContext); - AssertOrFailFastMsg(errorObject != nullptr, "ModuleEvaluation: null error object thrown from root function"); - this->errorObject = errorObject; - if (this->promise != nullptr) - { - ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext, this, false); - return false; - } + JavascriptExceptionOperators::Throw(childModuleRecord->GetErrorObject(), this->scriptContext); + } + }); } - if (exception != nullptr) + AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_JavascriptException)); + BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext()) { - JavascriptExceptionOperators::DoThrowCheckClone(exception, scriptContext); + Arguments outArgs(CallInfo(CallFlags_Value, 0), nullptr); + this->generator = VarTo(rootFunction->CallRootFunction(outArgs, scriptContext, true)); } + END_SAFE_REENTRANT_CALL + return true; } @@ -1058,27 +1055,24 @@ namespace Js { OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ModuleEvaluation(%s)\n"), this->GetSpecifierSz()); - if (!scriptContext->GetConfig()->IsES6ModuleEnabled() || WasEvaluated()) + if (WasEvaluated() || IsEvaluating() || !scriptContext->GetConfig()->IsES6ModuleEnabled()) { return nullptr; } - if (!WasEvaluationPrepassed()) + try { - if (!ModuleEvaluationPrepass()) + if (!WasEvaluationPrepassed()) { - return scriptContext->GetLibrary()->GetUndefined(); + if (!ModuleEvaluationPrepass()) + { + return scriptContext->GetLibrary()->GetUndefined(); + } } - } - - Assert(this->errorObject == nullptr); - SetWasEvaluated(); - JavascriptExceptionObject *exception = nullptr; - Var ret = nullptr; - - try - { + Assert(this->errorObject == nullptr); + SetEvaluating(true); + if (requestedModuleList != nullptr) { requestedModuleList->Reverse(); @@ -1097,42 +1091,203 @@ namespace Js } }); } - CleanupBeforeExecution(); - - JavascriptGenerator* gen = static_cast (generator); - - AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_JavascriptException)); - BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext()) + if (awaitedModules == 0) { - ret = gen->CallGenerator(scriptContext->GetLibrary()->GetUndefined(), ResumeYieldKind::Normal); - ret = JavascriptOperators::GetProperty(VarTo(ret), PropertyIds::value, scriptContext); + FinishModuleEvaluation(true); + } + else + { + if (this->parentModuleList != nullptr) + { + parentModuleList->Map([=](uint i, SourceTextModuleRecord* parentModule) + { + parentModule->IncrementAwaited(); + }); + } } - END_SAFE_REENTRANT_CALL } catch (const Js::JavascriptException &err) { - exception = err.GetAndClear(); + if (!WasEvaluated()) + { + SetWasEvaluated(); + } + SetEvaluating(false); + JavascriptExceptionObject *exception = err.GetAndClear(); Var errorObject = exception->GetThrownObject(scriptContext); AssertOrFailFastMsg(errorObject != nullptr, "ModuleEvaluation: null error object thrown from root function"); this->errorObject = errorObject; if (this->promise != nullptr) { ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext, this, false); - return scriptContext->GetLibrary()->GetUndefined(); + } + if (isRootModule && !ReportModuleCompletion(this, errorObject)) + { + JavascriptExceptionOperators::DoThrowCheckClone(exception, scriptContext); } } - if (exception != nullptr) + return scriptContext->GetLibrary()->GetUndefined(); + } + + void SourceTextModuleRecord::FinishModuleEvaluation(bool shouldIncrementAwait) + { + if (WasEvaluated()) { - JavascriptExceptionOperators::DoThrowCheckClone(exception, scriptContext); + return; } - if (this->promise != nullptr) + CleanupBeforeExecution(); + + JavascriptGenerator* gen = static_cast (generator); + + AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_JavascriptException)); + BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext()) + { + if (gen->IsAsyncModule()) + { + JavascriptPromise* prom = JavascriptAsyncFunction::BeginAsyncFunctionExecution(this->generator); + if (this->parentModuleList != nullptr && shouldIncrementAwait) + { + parentModuleList->Map([=](uint i, SourceTextModuleRecord* parentModule) + { + parentModule->IncrementAwaited(); + }); + } + auto* fulfilled = scriptContext->GetLibrary()->CreateAsyncModuleCallbackFunction(EntryAsyncModuleFulfilled, this); + auto* rejected = scriptContext->GetLibrary()->CreateAsyncModuleCallbackFunction(EntryAsyncModuleRejected, this); + auto* unused = JavascriptPromise::UnusedPromiseCapability(scriptContext); + JavascriptPromise::PerformPromiseThen(prom, unused, fulfilled, rejected, scriptContext); + } + else + { + SetWasEvaluated(); + SetEvaluating(false); + gen->CallGenerator(scriptContext->GetLibrary()->GetUndefined(), ResumeYieldKind::Normal); + if (this->parentModuleList != nullptr && !shouldIncrementAwait) + { + parentModuleList->Map([=](uint i, SourceTextModuleRecord* parentModule) + { + if (parentModule->DecrementAwaited()) + { + if(parentModule->IsEvaluating()) + { + parentModule->FinishModuleEvaluation(false); + } + } + }); + } + if (this->promise != nullptr) + { + ResolveOrRejectDynamicImportPromise(true, GetNamespace(), scriptContext, this, false); + } + if (isRootModule) + { + ReportModuleCompletion(this, nullptr); + } + } + } + END_SAFE_REENTRANT_CALL + } + + void SourceTextModuleRecord::DecrementAndEvaluateIfNothingAwaited() + { + if (DecrementAwaited()) + { + JavascriptExceptionObject *exception = nullptr; + try + { + FinishModuleEvaluation(false); + } + catch (const Js::JavascriptException &err) + { + if (!WasEvaluated()) + { + SetWasEvaluated(); + } + SetEvaluating(false); + exception = err.GetAndClear(); + Var errorObject = exception->GetThrownObject(scriptContext); + AssertOrFailFastMsg(errorObject != nullptr, "ModuleEvaluation: null error object thrown from root function"); + + PropogateRejection(errorObject); + return; + } + } + } + + Var SourceTextModuleRecord::EntryAsyncModuleFulfilled( + RecyclableObject* function, + CallInfo callInfo, ...) + { + SourceTextModuleRecord* module = VarTo(function)->module; + module->SetEvaluating(false); + module->SetWasEvaluated(); + if (module->parentModuleList != nullptr) + { + module->parentModuleList->Map([=](uint i, SourceTextModuleRecord* parentModule) + { + parentModule->DecrementAndEvaluateIfNothingAwaited(); + }); + } + + if (module->GetPromise() != nullptr) + { + ResolveOrRejectDynamicImportPromise(true, module->GetNamespace(), module->scriptContext, module, false); + } + else if (module->GetIsRootModule()) + { + ReportModuleCompletion(module, nullptr); + } + + return function->GetLibrary()->GetUndefined(); + } + + Var SourceTextModuleRecord::EntryAsyncModuleRejected( + RecyclableObject* function, + CallInfo callInfo, ...) + { + SourceTextModuleRecord* module = VarTo(function)->module; + + PROBE_STACK(module->scriptContext, Js::Constants::MinStackDefault); + ARGUMENTS(args, callInfo); + + Assert (args.Info.Count > 1); + module->PropogateRejection(args[1]); + + return function->GetLibrary()->GetUndefined(); + } + + void SourceTextModuleRecord::PropogateRejection(Var reason) + { + SetEvaluating(false); + if (wasEvaluated) + { + return; + } + + SetWasEvaluated(); + + this->errorObject = reason; + if (this->parentModuleList != nullptr) { - SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, this->GetNamespace(), this->GetScriptContext(), this, false); + parentModuleList->Map([=](uint i, SourceTextModuleRecord* parentModule) + { + if (parentModule->GetErrorObject() == nullptr) + { + parentModule->PropogateRejection(reason); + } + }); } - return ret; + if (this->promise != nullptr) + { + ResolveOrRejectDynamicImportPromise(false, reason, scriptContext, this, false); + } + if (isRootModule) + { + ReportModuleCompletion(this, errorObject); + } } HRESULT SourceTextModuleRecord::OnHostException(void* errorVar) @@ -1387,6 +1542,10 @@ namespace Js if (moduleRecord != nullptr) { moduleRecord->SetPromise(nullptr); + if (!moduleRecord->WasEvaluated()) + { + moduleRecord->SetWasEvaluated(); + } if (useReturn) { return JavascriptPromise::CreatePassThroughPromise(promise, scriptContext); @@ -1415,4 +1574,14 @@ namespace Js return this->importMetaObject; } + + template<> + bool VarIsImpl(RecyclableObject* obj) + { + return VarIs(obj) && ( + VirtualTableInfo::HasVirtualTable(obj) || + VirtualTableInfo>::HasVirtualTable(obj) + ); + } + } diff --git a/lib/Runtime/Language/SourceTextModuleRecord.h b/lib/Runtime/Language/SourceTextModuleRecord.h index 9a16eaf615f..1cd3e10fd29 100644 --- a/lib/Runtime/Language/SourceTextModuleRecord.h +++ b/lib/Runtime/Language/SourceTextModuleRecord.h @@ -35,7 +35,10 @@ namespace Js bool ModuleDeclarationInstantiation() override; void GenerateRootFunction(); Var ModuleEvaluation() override; + void FinishModuleEvaluation(bool shouldIncrementAwait); bool ModuleEvaluationPrepass(); + void DecrementAndEvaluateIfNothingAwaited(); + void PropogateRejection(Var reason); virtual ModuleNamespace* GetNamespace(); virtual void SetNamespace(ModuleNamespace* moduleNamespace); @@ -64,9 +67,14 @@ namespace Js void SetWasParsed() { wasParsed = true; } bool WasEvaluationPrepassed() const { return wasPrepassed; } void SetEvaluationPrepassed() { wasPrepassed = true; } + bool IsEvaluating() const { return evaluating; } + void SetEvaluating(bool status) { evaluating = status; } + void IncrementAwaited() { ++awaitedModules; } + bool DecrementAwaited() { return (--awaitedModules) == 0; } bool WasDeclarationInitialized() const { return wasDeclarationInitialized; } void SetWasDeclarationInitialized() { wasDeclarationInitialized = true; } void SetIsRootModule() { isRootModule = true; } + bool GetIsRootModule() { return isRootModule; } JavascriptPromise *GetPromise() { return this->promise; } void SetPromise(JavascriptPromise *value) { this->promise = value; } @@ -101,6 +109,14 @@ namespace Js static SourceTextModuleRecord* Create(ScriptContext* scriptContext); + static Var EntryAsyncModuleFulfilled( + RecyclableObject* function, + CallInfo callInfo, ...); + + static Var EntryAsyncModuleRejected( + RecyclableObject* function, + CallInfo callInfo, ...); + uint GetLocalExportSlotIndexByExportName(PropertyId exportNameId); uint GetLocalExportSlotIndexByLocalName(PropertyId localNameId); Field(Var)* GetLocalExportSlots() const { return localExportSlots; } @@ -122,17 +138,17 @@ namespace Js const static uint InvalidSlotCount = 0xffffffff; const static uint InvalidSlotIndex = 0xffffffff; // TODO: move non-GC fields out to avoid false reference? - // This is the parsed tree resulted from compilation. Field(bool) confirmedReady = false; Field(bool) notifying = false; Field(bool) wasPrepassed = false; - Field(bool) wasParsed; - Field(bool) wasDeclarationInitialized; - Field(bool) parentsNotified; - Field(bool) isRootModule; - Field(bool) hadNotifyHostReady; + Field(bool) wasParsed = false; + Field(bool) wasDeclarationInitialized = false; + Field(bool) parentsNotified = false; + Field(bool) isRootModule = false; + Field(bool) hadNotifyHostReady = false; + Field(bool) evaluating = false; Field(JavascriptGenerator*) generator; - Field(ParseNodeProg *) parseTree; + Field(ParseNodeProg *) parseTree; // This is the parsed tree resulted from compilation. Field(Utf8SourceInfo*) pSourceInfo; Field(uint) sourceIndex; FieldNoBarrier(Parser*) parser; // we'll need to keep the parser around till we are done with bytecode gen. @@ -161,6 +177,9 @@ namespace Js Field(uint) localExportCount; Field(uint) moduleId; + // for Top level Await + Field(uint) awaitedModules; + Field(ModuleNameRecord) namespaceRecord; Field(JavascriptPromise*) promise; @@ -190,4 +209,25 @@ namespace Js uint moduleId; Field(Var)* localExportSlotsAddr; }; + + class AsyncModuleCallbackFunction : public RuntimeFunction + { + protected: + DEFINE_VTABLE_CTOR(AsyncModuleCallbackFunction, RuntimeFunction); + DEFINE_MARSHAL_OBJECT_TO_SCRIPT_CONTEXT(AsyncModuleCallbackFunction); + + public: + AsyncModuleCallbackFunction( + DynamicType* type, + FunctionInfo* functionInfo, + SourceTextModuleRecord* module) : + RuntimeFunction(type, functionInfo), + module(module) {} + + Field(SourceTextModuleRecord*) module; + }; + + template<> + bool VarIsImpl(RecyclableObject* obj); + } diff --git a/lib/Runtime/Library/JavascriptAsyncFunction.cpp b/lib/Runtime/Library/JavascriptAsyncFunction.cpp index 6fa4dbba8ca..c6d2ff83562 100644 --- a/lib/Runtime/Library/JavascriptAsyncFunction.cpp +++ b/lib/Runtime/Library/JavascriptAsyncFunction.cpp @@ -48,6 +48,14 @@ Var JavascriptAsyncFunction::EntryAsyncFunctionImplementation( auto* asyncFn = VarTo(function); auto* scriptFn = asyncFn->GetGeneratorVirtualScriptFunction(); auto* generator = library->CreateGenerator(args, scriptFn, library->GetNull()); + + return BeginAsyncFunctionExecution(generator); +} + +JavascriptPromise* JavascriptAsyncFunction::BeginAsyncFunctionExecution(JavascriptGenerator* generator) +{ + auto* library = generator->GetLibrary(); + auto* scriptContext = generator->GetScriptContext(); auto* promise = library->CreatePromise(); auto* stepFn = library->CreateAsyncSpawnStepFunction( diff --git a/lib/Runtime/Library/JavascriptAsyncFunction.h b/lib/Runtime/Library/JavascriptAsyncFunction.h index 3a0fc76f8fc..9e6f7fc281f 100644 --- a/lib/Runtime/Library/JavascriptAsyncFunction.h +++ b/lib/Runtime/Library/JavascriptAsyncFunction.h @@ -29,6 +29,9 @@ class JavascriptAsyncFunction : public JavascriptGeneratorFunction RecyclableObject* function, CallInfo callInfo, ...); + static JavascriptPromise* BeginAsyncFunctionExecution( + JavascriptGenerator* generator); + static Var EntryAsyncSpawnExecutorFunction( RecyclableObject* function, CallInfo callInfo, ...); diff --git a/lib/Runtime/Library/JavascriptGenerator.cpp b/lib/Runtime/Library/JavascriptGenerator.cpp index 51338a29f20..d7fd07c0333 100644 --- a/lib/Runtime/Library/JavascriptGenerator.cpp +++ b/lib/Runtime/Library/JavascriptGenerator.cpp @@ -318,6 +318,12 @@ Var JavascriptGenerator::EntryThrow(RecyclableObject* function, CallInfo callInf return generator->CallGenerator(input, ResumeYieldKind::Throw); } +bool JavascriptGenerator::IsAsyncModule() const +{ + FunctionProxy* proxy = this->scriptFunction->GetFunctionProxy(); + return proxy->IsModule() && proxy->IsAsync(); +} + #ifdef ENABLE_DEBUG_CONFIG_OPTIONS void JavascriptGenerator::OutputBailInTrace(JavascriptGenerator* generator) { diff --git a/lib/Runtime/Library/JavascriptGenerator.h b/lib/Runtime/Library/JavascriptGenerator.h index 480dc4d1cfc..ec0cb9291df 100644 --- a/lib/Runtime/Library/JavascriptGenerator.h +++ b/lib/Runtime/Library/JavascriptGenerator.h @@ -82,6 +82,8 @@ class JavascriptGenerator : public DynamicObject bool IsSuspended() const { return this->state == GeneratorState::Suspended; } bool IsCompleted() const { return this->state == GeneratorState::Completed; } + bool IsAsyncModule() const; + void SetSuspendedStart() { Assert( diff --git a/lib/Runtime/Library/JavascriptGeneratorFunction.cpp b/lib/Runtime/Library/JavascriptGeneratorFunction.cpp index 9cb5da9f49f..db4f852bf61 100644 --- a/lib/Runtime/Library/JavascriptGeneratorFunction.cpp +++ b/lib/Runtime/Library/JavascriptGeneratorFunction.cpp @@ -65,7 +65,7 @@ using namespace Js; JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(scriptFunction, EtwTrace::GetFunctionId(functionProxy))); JavascriptGeneratorFunction* genFunc = nullptr; - if (functionProxy->IsAsync()) + if (functionProxy->IsAsync() && !functionProxy->IsModule()) { if (functionProxy->IsGenerator()) { diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp index 271245059db..779c90b3945 100644 --- a/lib/Runtime/Library/JavascriptLibrary.cpp +++ b/lib/Runtime/Library/JavascriptLibrary.cpp @@ -6505,6 +6505,30 @@ namespace Js generator); } + RuntimeFunction* JavascriptLibrary::CreateAsyncModuleCallbackFunction( + JavascriptMethod entryPoint, + SourceTextModuleRecord* module) + { + Assert(scriptContext->GetConfig()->IsES2018AsyncIterationEnabled()); + + auto* functionInfo = RecyclerNew(GetRecycler(), FunctionInfo, entryPoint); + + auto* type = DynamicType::New( + scriptContext, + TypeIds_Function, + functionPrototype, + entryPoint, + GetDeferredAnonymousFunctionTypeHandler()); + + return RecyclerNewEnumClass( + GetRecycler(), + EnumFunctionClass, + AsyncModuleCallbackFunction, + type, + functionInfo, + module); + } + JavascriptAsyncGeneratorFunction* JavascriptLibrary::CreateAsyncGeneratorFunction(JavascriptMethod entryPoint, GeneratorVirtualScriptFunction* scriptFunction) { Assert(scriptContext->GetConfig()->IsES2018AsyncIterationEnabled()); diff --git a/lib/Runtime/Library/JavascriptLibrary.h b/lib/Runtime/Library/JavascriptLibrary.h index 911f7d3a31c..adaa77880e6 100644 --- a/lib/Runtime/Library/JavascriptLibrary.h +++ b/lib/Runtime/Library/JavascriptLibrary.h @@ -1034,6 +1034,7 @@ namespace Js JavascriptGeneratorFunction* CreateGeneratorFunction(JavascriptMethod entryPoint, bool isAnonymousFunction); JavascriptAsyncGeneratorFunction* CreateAsyncGeneratorFunction(JavascriptMethod entryPoint, GeneratorVirtualScriptFunction* scriptFunction); AsyncGeneratorCallbackFunction* CreateAsyncGeneratorCallbackFunction(JavascriptMethod entryPoint, JavascriptAsyncGenerator* generator); + RuntimeFunction* CreateAsyncModuleCallbackFunction(JavascriptMethod entryPoint, SourceTextModuleRecord* module); JavascriptAsyncFunction* CreateAsyncFunction(JavascriptMethod entryPoint, GeneratorVirtualScriptFunction* scriptFunction); JavascriptAsyncFunction* CreateAsyncFunction(JavascriptMethod entryPoint, bool isAnonymousFunction); JavascriptAsyncSpawnStepFunction* CreateAsyncSpawnStepFunction(JavascriptMethod entryPoint, JavascriptGenerator* generator, Var argument, Var resolve = nullptr, Var reject = nullptr, bool isReject = false); diff --git a/test/es6module/bug_OS12095746.baseline b/test/es6module/bug_OS12095746.baseline index 7b430333be1..988dfd6b58e 100644 --- a/test/es6module/bug_OS12095746.baseline +++ b/test/es6module/bug_OS12095746.baseline @@ -1,6 +1,3 @@ -NotifyModuleReadyCallback(exception) bug_OS12095746_mod0.js -NotifyModuleReadyCallback(exception) bug_OS12095746_mod1.js mod0 catch:Syntax error mod1 catch:Unexpected identifier after numeric literal -NotifyModuleReadyCallback(exception) bug_OS12095746_mod2.js mod2 catch:Unexpected identifier after numeric literal diff --git a/test/es6module/rlexe.xml b/test/es6module/rlexe.xml index c60b2ce0140..5fca9ac93b3 100644 --- a/test/es6module/rlexe.xml +++ b/test/es6module/rlexe.xml @@ -211,4 +211,11 @@ exclude_jshost + + + top-level-await.js + -ESDynamicImport -ESTopLevelAwait -module + exclude_jshost + + diff --git a/test/es6module/top-level-await.js b/test/es6module/top-level-await.js new file mode 100644 index 00000000000..f15e64ee947 --- /dev/null +++ b/test/es6module/top-level-await.js @@ -0,0 +1,280 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +const promises = []; + +let resolveOne, resolveTwo; +let counter = 0; +const firstPromise = new Promise(r => { resolveOne = r}); +const secondPromise = new Promise(r => { resolveTwo = function(a) + { + if (counter > 0) throw new Error("should only be called once"); + ++ counter; + r(a); + }}); +export default resolveOne; +export {resolveTwo}; + +// async tests without a baseline by using an array of promises +function AddPromise(test, promise, result, shouldFail = false) +{ + const resultPromise = shouldFail ? + promise.then( + ()=>{throw new Error(`${test} failed - promise should have been rejected`);}, + (x)=>{if (result !== x) {throw new Error(`${test} failed - ${JSON.stringify(x)} should equal ${JSON.stringify(result)}`);}} + ) : + promise.then( + (x)=>{if (result !== x) {throw new Error(`${test} failed - ${JSON.stringify(x)} should equal ${JSON.stringify(result)}`);}}, + (x)=>{throw new Error(`${test} failed - ${x}`);} + ); + promises.push(resultPromise); +} + +const tests = [ + { + name : "Load a single async module", + body(test) { + WScript.RegisterModuleSource("Single1", ` + export default 5; + await 0; + `); + AddPromise(test, import("Single1").then(x => x.default), 5, false); + } + }, + { + name : "Syntax in an async module", + body(test) { + WScript.RegisterModuleSource("Syntax1", '(await 0) = 1;'); + WScript.RegisterModuleSource("Syntax2", 'await = 1;'); + WScript.RegisterModuleSource("Syntax3", 'await import "Syntax1"'); + WScript.RegisterModuleSource("Syntax4", 'await + 5;'); + WScript.RegisterModuleSource("Syntax5", 'import await "bar";'); + WScript.RegisterModuleSource("Syntax6", 'await 0; function bar () { await 0; }'); + WScript.RegisterModuleSource("Syntax7", 'await 0; yield 5;'); + WScript.RegisterModuleSource("Syntax8", 'function foo() { await 0; }'); + WScript.RegisterModuleSource("Syntax9", 'function* foo() { await 0; }'); + + AddPromise(test, import("Syntax1").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax2").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax3").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax4").then(x => x.default), undefined, false); + AddPromise(test, import("Syntax5").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax6").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax7").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax8").catch(x => (x instanceof SyntaxError)), true, false); + AddPromise(test, import("Syntax9").catch(x => (x instanceof SyntaxError)), true, false); + } + }, + { + name : "Basic Async load order", + body(test) { + WScript.RegisterModuleSource("LoadOrder1", 'export default [];'); + WScript.RegisterModuleSource("LoadOrder2", ` + import arr from 'LoadOrder1'; + export {arr as default}; + arr.push("LO2a1"); + await 0; + arr.push("LO2a2"); + await 0; + await 0; + arr.push("LO2a3"); + await 0; + await 0; + await 0; + arr.push("LO2a4"); + await 0; + arr.push("LO2a5"); + await 0; + await 0; + await 0; + arr.push("LO2a6"); + `); + WScript.RegisterModuleSource("LoadOrder3", ` + import arr from 'LoadOrder1'; + export {arr as default}; + arr.push("LO3a1"); + await 0; + arr.push("LO3a2"); + `); + WScript.RegisterModuleSource("LoadOrder4", ` + import arr from 'LoadOrder2'; + import 'LoadOrder3' + export {arr as default}; + arr.push("LO4"); + `); + WScript.RegisterModuleSource("LoadOrder5", ` + import arr from 'LoadOrder3'; + export {arr as default}; + arr.push("LO5a1"); + await 0; + arr.push("LO5a2"); + `); + WScript.RegisterModuleSource("LoadOrder6", ` + import arr from 'LoadOrder5'; + export {arr as default}; + arr.push("LO6"); + `); + WScript.RegisterModuleSource("LoadOrder7", ` + import arr from 'LoadOrder6'; + import 'LoadOrder4'; + export {arr as default}; + arr.push("LO7"); + `); + import('LoadOrder4'); + AddPromise(test, import('LoadOrder1').then(() => import('LoadOrder7').then(x => x.default.toString())), + "LO2a1,LO3a1,LO2a2,LO3a2,LO2a3,LO5a1,LO2a4,LO5a2,LO2a5,LO6,LO2a6,LO4,LO7", false); + } + }, + { + name : "Cyclic load order", + body(test) { + WScript.RegisterModuleSource("CyclicOrder1", 'export default [];'); + WScript.RegisterModuleSource("CyclicOrder2", ` + import arr from 'CyclicOrder1'; + import {testFunc} from 'CyclicOrder4'; + testFunc(); + arr.push("CO2a1"); + await 0; + arr.push("CO2a2"); + `); + WScript.RegisterModuleSource("CyclicOrder3", ` + import arr from 'CyclicOrder1'; + import 'CyclicOrder2'; + arr.push("CO3a1"); + await 0; + arr.push("CO3a2"); + `); + WScript.RegisterModuleSource("CyclicOrder4", ` + import arr from 'CyclicOrder1'; + export {arr as default}; + export function testFunc () { arr.push("testFunc");} + import 'CyclicOrder3'; + arr.push("CO4a1"); + await 0; + arr.push("CO4a2"); + `); + AddPromise(test, import('CyclicOrder4').then(x => x.default.toString()), "testFunc,CO2a1,CO2a2,CO3a1,CO3a2,CO4a1,CO4a2", false); + } + }, + { + name : "Double Cyclic load order", + body(test) { + WScript.RegisterModuleSource('CyclicDouble1', 'export default [];'); + WScript.RegisterModuleSource('CyclicDouble2', ` + import arr from 'CyclicDouble1'; + import 'CyclicDouble4'; + arr.push("CD2a1"); + await 0; + arr.push("CD2a2"); + `); + WScript.RegisterModuleSource('CyclicDouble3', ` + import arr from 'CyclicDouble1'; + import 'CyclicDouble2' + arr.push("CD3a1"); + await 0; + arr.push("CD3a2"); + `); + WScript.RegisterModuleSource('CyclicDouble4', ` + import arr from 'CyclicDouble1'; + import 'CyclicDouble3' + arr.push("CD4a1"); + await 0; + arr.push("CD4a2"); + `); + WScript.RegisterModuleSource('CyclicDouble5', ` + import arr from 'CyclicDouble1'; + arr.push("CD5"); + `); + WScript.RegisterModuleSource('CyclicDouble6', ` + import arr from 'CyclicDouble1'; + import 'CyclicDouble5' + import 'CyclicDouble3' + export {arr as default}; + arr.push("CD6a1"); + await 0; + arr.push("CD6a2"); + `); + import ('CyclicDouble4'); + AddPromise(test, import ('CyclicDouble1').then(() => import('CyclicDouble6').then(x => x.default.toString())), "CD2a1,CD2a2,CD3a1,CD3a2,CD5,CD4a1,CD6a1,CD4a2,CD6a2", false); + } + }, + { + name : "Rejections", + body (test) { + WScript.RegisterModuleSource("Rejection1", 'await 0; throw new Error("rejection")'); + WScript.RegisterModuleSource("Rejection2", 'import "Rejection1"'); + + AddPromise(test, import('Rejection1').catch(x => (x instanceof Error && x.message === "rejection")), true, false); + AddPromise(test, import('Rejection2').catch(x => (x instanceof Error && x.message === "rejection")), true, false); + + WScript.RegisterModuleSource("CyclicRejection1", 'export default [];'); + WScript.RegisterModuleSource("CyclicRejection2", ` + import arr from 'CyclicRejection1'; + import 'CyclicRejection3'; + arr.push("CR1a1"); + await 0; + arr.push("CR1a2"); + `); + WScript.RegisterModuleSource("CyclicRejection3", ` + import arr from 'CyclicRejection1'; + import 'CyclicRejection2'; + arr.push("CR2a1"); + await Promise.reject('REJECTED'); + arr.push("CR2a2"); + `); + + AddPromise(test, import('CyclicRejection3'), "REJECTED", true); + AddPromise(test, import('CyclicRejection3').catch(() => import('CyclicRejection1').then(x => x.default.toString())), "CR1a1,CR1a2,CR2a1", false); + } + }, + { + name : "Test static loading", + body (test) { + WScript.RegisterModuleSource("Static", ` + import resolve from 'top-level-await.js'; + await 0; + resolve("static pass"); + `); + WScript.LoadScriptFile("Static", "module"); + AddPromise(test, firstPromise, "static pass", false); + } + }, + { + name : "Root and dynamic", + body (test) { + WScript.RegisterModuleSource("Dynamic Root", ` + export let bar = 0; + import {resolveTwo} from 'top-level-await.js'; + await 0; + resolveTwo("other pass"); + `); + WScript.LoadScriptFile("Dynamic Root", "module"); + AddPromise(test, import("Dynamic Root").then(x => x.bar), 0, false); + AddPromise(test, secondPromise, "other pass", false); + } + }, + { + name : "For-await-of in module", + body (test) { + WScript.RegisterModuleSource("for-await-of", ` + let out = 0; + for await (const bar of [2,3,4]) { + out += bar; + } + export default out; + `); + AddPromise(test, import('for-await-of').then (x => x.default), 9, false); + } + } +]; + + +for(let i = 0; i < tests.length; ++i) +{ + const test = tests[i]; + test.body(`Test ${i + 1}: ${test.name}`); +} + +Promise.all(promises).then(x => print("pass"), x => print (x));