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));