Sfoglia il codice sorgente

Dynamic Module Import

This PR adds support for import() dynamic module import sematic.

Per https://github.com/tc39/proposal-dynamic-import:

"A call to import(specifier) returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself.

"Here specifier will be interpreted the same way as in an import declaration (i.e., the same strings will work in both places). However, while specifier is a string it is not necessarily a string literal; thus code like import(`./language-packs/${navigator.language}.js`) will work—something impossible to accomplish with the usual import declarations.

"import() is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code."

This PR includes following changes:
	- Update parser and bytecode generator to support import() sematic in module and in script
	- Add new bytecode 'ImportCall'
	- Add runtime function for import() that:
		○ Uses caller from stack to look up module record or source context that are associated with the module or script from which 'import()' is called
		○ Requests host to load target module source file (gets module record in return)
		○ Creates promise unless the module record has one
		○ Resolves/rejects promise if appropriates
		○ Returns promise
	- Add new host callback ('FetchImportedModuleFromScript') for fetching imported module from script (accepts source context)
	- Add 'promiseCapability' field to module record class
	- Update SourceTextModuleRecord's methods to accept callback from host and to handle dynamically imported module and its promise capability
	- Update exception checks and assertions to cover new usage scenario of importing and evaluating module code with active script
Add unit tests for dynamic import functionality
Suwei Chen 8 anni fa
parent
commit
2277186a9e
41 ha cambiato i file con 1146 aggiunte e 110 eliminazioni
  1. 3 0
      bin/NativeTests/JsRTApiTest.cpp
  2. 21 17
      bin/ch/Helpers.cpp
  3. 1 0
      bin/ch/HostConfigFlagsList.h
  4. 59 14
      bin/ch/WScriptJsrt.cpp
  5. 4 0
      bin/ch/WScriptJsrt.h
  6. 2 0
      lib/Backend/JnHelperMethodList.h
  7. 14 0
      lib/Backend/Lower.cpp
  8. 2 5
      lib/Common/ConfigFlagsList.h
  9. 6 0
      lib/Common/Memory/ArenaAllocator.h
  10. 17 1
      lib/Jsrt/ChakraCore.h
  11. 20 1
      lib/Jsrt/Core/JsrtContextCore.cpp
  12. 7 0
      lib/Jsrt/Core/JsrtContextCore.h
  13. 6 0
      lib/Jsrt/Core/JsrtCore.cpp
  14. 1 0
      lib/Jsrt/JsrtInternal.h
  15. 44 7
      lib/Parser/Parse.cpp
  16. 2 1
      lib/Parser/Parse.h
  17. 1 0
      lib/Parser/ptlist.h
  18. 6 1
      lib/Runtime/Base/FunctionBody.cpp
  19. 1 0
      lib/Runtime/Base/FunctionBody.h
  20. 1 0
      lib/Runtime/Base/ScriptContext.h
  21. 24 0
      lib/Runtime/Base/ThreadContext.h
  22. 9 0
      lib/Runtime/ByteCode/ByteCodeEmitter.cpp
  23. 2 0
      lib/Runtime/ByteCode/OpCodes.h
  24. 1 0
      lib/Runtime/Language/InterpreterHandler.inl
  25. 5 0
      lib/Runtime/Language/InterpreterStackFrame.cpp
  26. 1 0
      lib/Runtime/Language/InterpreterStackFrame.h
  27. 106 0
      lib/Runtime/Language/JavascriptOperators.cpp
  28. 1 0
      lib/Runtime/Language/JavascriptOperators.h
  29. 193 46
      lib/Runtime/Language/SourceTextModuleRecord.cpp
  30. 8 6
      lib/Runtime/Language/SourceTextModuleRecord.h
  31. 12 0
      lib/Runtime/Library/JavascriptPromise.cpp
  32. 2 0
      lib/Runtime/Library/JavascriptPromise.h
  33. 20 4
      test/UnitTestFramework/UnitTestFramework.js
  34. 4 0
      test/es6/ModuleCircularBar.js
  35. 1 1
      test/es6/ModuleCircularFoo.js
  36. 7 2
      test/es6/ModuleComplexExports.js
  37. 372 0
      test/es6/dynamic-module-functionality.js
  38. 142 0
      test/es6/dynamic-module-import-specifier.js
  39. 2 2
      test/es6/es6_stable.baseline
  40. 2 2
      test/es6/es6_stable.enable_disable.baseline
  41. 14 0
      test/es6/rlexe.xml

+ 3 - 0
bin/NativeTests/JsRTApiTest.cpp

@@ -1739,6 +1739,7 @@ namespace JsRTApiTest
         REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
         successTest.mainModule = requestModule;
         REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, Success_FIMC) == JsNoError);
+        REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, Success_FIMC) == JsNoError);
         REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, Succes_NMRC) == JsNoError);
 
         JsValueRef errorObject = JS_INVALID_REFERENCE;
@@ -1834,6 +1835,7 @@ namespace JsRTApiTest
         REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
         reentrantParseData.mainModule = requestModule;
         REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, ReentrantParse_FIMC) == JsNoError);
+        REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, ReentrantParse_FIMC) == JsNoError);
         REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, ReentrantParse_NMRC) == JsNoError);
 
         JsValueRef errorObject = JS_INVALID_REFERENCE;
@@ -1913,6 +1915,7 @@ namespace JsRTApiTest
         REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
         reentrantNoErrorParseData.mainModule = requestModule;
         REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, reentrantNoErrorParse_FIMC) == JsNoError);
+        REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, reentrantNoErrorParse_FIMC) == JsNoError);
         REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, reentrantNoErrorParse_NMRC) == JsNoError);
 
         JsValueRef errorObject = JS_INVALID_REFERENCE;

+ 21 - 17
bin/ch/Helpers.cpp

@@ -139,26 +139,30 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len
     //
     if (fopen_s(&file, filename, "rb") != 0)
     {
-#ifdef _WIN32
-        DWORD lastError = GetLastError();
-        char16 wszBuff[512];
-        fprintf(stderr, "Error in opening file '%s' ", filename);
-        wszBuff[0] = 0;
-        if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
-            nullptr,
-            lastError,
-            0,
-            wszBuff,
-            _countof(wszBuff),
-            nullptr))
+        if (!HostConfigFlags::flags.AsyncModuleLoadIsEnabled)
         {
-            fwprintf(stderr, _u(": %s"), wszBuff);
-        }
-        fwprintf(stderr, _u("\n"));
+#ifdef _WIN32
+            DWORD lastError = GetLastError();
+            char16 wszBuff[512];
+            fprintf(stderr, "Error in opening file '%s' ", filename);
+            wszBuff[0] = 0;
+            if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+                nullptr,
+                lastError,
+                0,
+                wszBuff,
+                _countof(wszBuff),
+                nullptr))
+            {
+                fwprintf(stderr, _u(": %s"), wszBuff);
+            }
+            fwprintf(stderr, _u("\n"));
 #elif defined(_POSIX_VERSION)
-        fprintf(stderr, "Error in opening file: ");
-        perror(filename);
+            fprintf(stderr, "Error in opening file: ");
+            perror(filename);
 #endif
+        }
+
         IfFailGo(E_FAIL);
     }
 

+ 1 - 0
bin/ch/HostConfigFlagsList.h

@@ -11,5 +11,6 @@ FLAG(int,  InspectMaxStringLength,          "Max string length to dump in locals
 FLAG(BSTR, Serialized,                      "If source is UTF8, deserializes from bytecode file", NULL)
 FLAG(bool, OOPJIT,                          "Run JIT in a separate process", false)
 FLAG(bool, EnsureCloseJITServer,            "JIT process will be force closed when ch is terminated", true)
+FLAG(bool, AsyncModuleLoad,                 "Silence host error output for module load failures to enable promise testing", false)
 #undef FLAG
 #endif

+ 59 - 14
bin/ch/WScriptJsrt.cpp

@@ -304,14 +304,22 @@ JsErrorCode WScriptJsrt::InitializeModuleInfo(JsValueRef specifier, JsModuleReco
 {
     JsErrorCode errorCode = JsNoError;
     errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule);
+
     if (errorCode == JsNoError)
     {
-        errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback);
-    }
-    if (errorCode == JsNoError)
-    {
-        errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier);
+        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_HostDefined, specifier);
+            }
+        }
     }
+
     IfJsrtErrorFailLogAndRetErrorCode(errorCode);
     return JsNoError;
 }
@@ -351,9 +359,10 @@ JsErrorCode WScriptJsrt::LoadModuleFromString(LPCSTR fileName, LPCSTR fileConten
     JsValueRef errorObject = JS_INVALID_REFERENCE;
 
     // ParseModuleSource is sync, while additional fetch & evaluation are async.
+    unsigned int fileContentLength = (fileContent == nullptr) ? 0 : (unsigned int)strlen(fileContent);
     errorCode = ChakraRTInterface::JsParseModuleSource(requestModule, dwSourceCookie, (LPBYTE)fileContent,
-        (unsigned int)strlen(fileContent), JsParseModuleSourceFlags_DataIsUTF8, &errorObject);
-    if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE)
+        fileContentLength, JsParseModuleSourceFlags_DataIsUTF8, &errorObject);
+    if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE && fileContent != nullptr)
     {
         ChakraRTInterface::JsSetException(errorObject);
         return errorCode;
@@ -875,10 +884,24 @@ bool WScriptJsrt::Initialize()
     IfJsrtErrorFail(CreatePropertyIdFromString("console", &consoleName), false);
     IfJsrtErrorFail(ChakraRTInterface::JsSetProperty(global, consoleName, console, true), false);
 
+    IfJsrtErrorFail(InitializeModuleCallbacks(), false);
+
 Error:
     return hr == S_OK;
 }
 
+JsErrorCode WScriptJsrt::InitializeModuleCallbacks()
+{
+    JsModuleRecord moduleRecord = JS_INVALID_REFERENCE;
+    JsErrorCode errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, nullptr, &moduleRecord);
+    if (errorCode == JsNoError)
+    {
+        errorCode = InitializeModuleInfo(nullptr, moduleRecord);
+    }
+
+    return errorCode;
+}
+
 bool WScriptJsrt::Uninitialize()
 {
     // moduleRecordMap is a global std::map, its destructor may access overrided
@@ -1217,7 +1240,12 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName)
             hr = Helpers::LoadScriptFromFile(specifierStr.GetString(), fileContent);
             if (FAILED(hr))
             {
-                fprintf(stderr, "Couldn't load file.\n");
+                if (!HostConfigFlags::flags.AsyncModuleLoadIsEnabled)
+                {
+                    fprintf(stderr, "Couldn't load file.\n");
+                }
+
+                LoadScript(nullptr, specifierStr.GetString(), nullptr, "module", true, WScriptJsrt::FinalizeFree);
             }
             else
             {
@@ -1228,12 +1256,8 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName)
     return errorCode;
 }
 
-// Callback from chakracore to fetch dependent module. In the test harness,
-// we are not doing any translation, just treat the specifier as fileName.
-// While this call will come back directly from ParseModuleSource, the additional
-// task are treated as Promise that will be executed later.
-JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule,
-    _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
+JsErrorCode WScriptJsrt::FetchImportedModuleHelper(JsModuleRecord referencingModule,
+    JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord)
 {
     JsModuleRecord moduleRecord = JS_INVALID_REFERENCE;
     AutoString specifierStr;
@@ -1267,6 +1291,27 @@ JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModu
     return errorCode;
 }
 
+// Callback from chakracore to fetch dependent module. In the test harness,
+// we are not doing any translation, just treat the specifier as fileName.
+// While this call will come back directly from ParseModuleSource, the additional
+// task are treated as Promise that will be executed later.
+JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule,
+    _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
+{
+    return FetchImportedModuleHelper(referencingModule, specifier, dependentModuleRecord);
+}
+
+// Callback from chakracore to fetch module dynamically during runtime. In the test harness,
+// we are not doing any translation, just treat the specifier as fileName.
+// While this call will come back directly from runtime script or module code, the additional
+// task can be scheduled asynchronously that executed later.
+JsErrorCode WScriptJsrt::FetchImportedModuleFromScript(_In_ JsSourceContext dwReferencingSourceContext,
+    _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
+{
+    // ch.exe assumes all imported source files are located at .
+    return FetchImportedModuleHelper(nullptr, specifier, dependentModuleRecord);
+}
+
 // Callback from chakraCore when the module resolution is finished, either successfuly or unsuccessfully.
 JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar)
 {

+ 4 - 0
bin/ch/WScriptJsrt.h

@@ -53,7 +53,9 @@ public:
     static void PushMessage(MessageBase *message) { messageQueue->InsertSorted(message); }
 
     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 InitializeModuleCallbacks();
     static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState);
 
     static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode)
@@ -116,6 +118,8 @@ private:
     static JsValueRef CALLBACK LoadTextFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
     static JsValueRef CALLBACK FlagCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
 
+    static JsErrorCode FetchImportedModuleHelper(JsModuleRecord referencingModule, JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord);
+
     static MessageQueue *messageQueue;
     static DWORD_PTR sourceContext;
     static std::map<std::string, JsModuleRecord> moduleRecordMap;

+ 2 - 0
lib/Backend/JnHelperMethodList.h

@@ -507,6 +507,8 @@ HELPERCALL(SetHomeObj,          Js::JavascriptOperators::OP_SetHomeObj,
 HELPERCALL(LdHomeObjProto,      Js::JavascriptOperators::OP_LdHomeObjProto,     0)
 HELPERCALL(LdFuncObjProto,      Js::JavascriptOperators::OP_LdFuncObjProto,     0)
 
+HELPERCALL(ImportCall,          Js::JavascriptOperators::OP_ImportCall,         0)
+
 HELPERCALL(ResumeYield,   Js::JavascriptOperators::OP_ResumeYield,   AttrCanThrow)
 
 #include "ExternalHelperMethodList.h"

+ 14 - 0
lib/Backend/Lower.cpp

@@ -2844,6 +2844,20 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
             break;
         }
 
+        case Js::OpCode::ImportCall:
+        {
+            IR::Opnd *src1Opnd = instr->UnlinkSrc1();
+            IR::Opnd *functionObjOpnd = nullptr;
+            m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
+
+            LoadScriptContext(instr);
+            m_lowererMD.LoadHelperArgument(instr, src1Opnd);
+            m_lowererMD.LoadHelperArgument(instr, functionObjOpnd);
+            m_lowererMD.ChangeToHelperCall(instr, IR::HelperImportCall);
+
+            break;
+        }
+
         case Js::OpCode::SetComputedNameVar:
         {
             IR::Opnd *src2Opnd = instr->UnlinkSrc2();

+ 2 - 5
lib/Common/ConfigFlagsList.h

@@ -532,7 +532,7 @@ PHASE(All)
     // If ES6Module needs to be disabled by compile flag, DEFAULT_CONFIG_ES6Module should be false
     #define DEFAULT_CONFIG_ES6Module               (false)
 #else
-    #define DEFAULT_CONFIG_ES6Module               (false)
+    #define DEFAULT_CONFIG_ES6Module               (true)
 #endif
 #define DEFAULT_CONFIG_ES6Object               (true)
 #define DEFAULT_CONFIG_ES6Number               (true)
@@ -998,10 +998,7 @@ FLAGPR           (Boolean, ES6, ES7TrailingComma       , "Enable ES7 trailing co
 FLAGPR           (Boolean, ES6, ES6IsConcatSpreadable  , "Enable ES6 isConcatSpreadable Symbol"                     , DEFAULT_CONFIG_ES6IsConcatSpreadable)
 FLAGPR           (Boolean, ES6, ES6Math                , "Enable ES6 Math extensions"                               , DEFAULT_CONFIG_ES6Math)
 
-#ifndef COMPILE_DISABLE_ES6Module
-    #define COMPILE_DISABLE_ES6Module 0
-#endif
-FLAGPR_REGOVR_EXP(Boolean, ES6, ES6Module              , "Enable ES6 Modules"                                       , DEFAULT_CONFIG_ES6Module)
+FLAGPR           (Boolean, ES6, ES6Module              , "Enable ES6 Modules"                                       , DEFAULT_CONFIG_ES6Module)
 FLAGPR           (Boolean, ES6, ES6Object              , "Enable ES6 Object extensions"                             , DEFAULT_CONFIG_ES6Object)
 FLAGPR           (Boolean, ES6, ES6Number              , "Enable ES6 Number extensions"                             , DEFAULT_CONFIG_ES6Number)
 FLAGPR           (Boolean, ES6, ES6ObjectLiterals      , "Enable ES6 Object literal extensions"                     , DEFAULT_CONFIG_ES6ObjectLiterals)

+ 6 - 0
lib/Common/Memory/ArenaAllocator.h

@@ -21,6 +21,12 @@ namespace Memory
 #define Adelete(alloc, obj) AllocatorDelete(ArenaAllocator, alloc, obj)
 #define AdeletePlus(alloc, size, obj) AllocatorDeletePlus(ArenaAllocator, alloc, size, obj)
 #define AdeleteArray(alloc, count, obj) AllocatorDeleteArray(ArenaAllocator, alloc, count, obj)
+#define AdeleteUnlessNull(alloc, obj) \
+    if (obj != nullptr) \
+    { \
+        Adelete(alloc, obj); \
+        obj = nullptr; \
+    }
 
 
 #define AnewNoThrow(alloc,T,...) AllocatorNewNoThrow(ArenaAllocator, alloc, T, __VA_ARGS__)

+ 17 - 1
lib/Jsrt/ChakraCore.h

@@ -38,7 +38,8 @@ typedef enum JsModuleHostInfoKind
     JsModuleHostInfo_Exception = 0x01,
     JsModuleHostInfo_HostDefined = 0x02,
     JsModuleHostInfo_NotifyModuleReadyCallback = 0x3,
-    JsModuleHostInfo_FetchImportedModuleCallback = 0x4
+    JsModuleHostInfo_FetchImportedModuleCallback = 0x4,
+    JsModuleHostInfo_FetchImportedModuleFromScriptCallback = 0x5
 } JsModuleHostInfoKind;
 
 /// <summary>
@@ -70,6 +71,21 @@ typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleCallBack)(_In_ JsModule
 /// <returns>
 ///     true if the operation succeeded, false otherwise.
 /// </returns>
+typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleFromScriptCallBack)(_In_ JsSourceContext dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
+
+/// <summary>
+///     User implemented callback to get notification when the module is ready.
+/// </summary>
+/// <remarks>
+/// Notify the host after ModuleDeclarationInstantiation step (15.2.1.1.6.4) is finished. If there was error in the process, exceptionVar
+/// holds the exception. Otherwise the referencingModule is ready and the host should schedule execution afterwards.
+/// </remarks>
+/// <param name="dwReferencingSourceContext">The referencing script that calls import()</param>
+/// <param name="exceptionVar">If nullptr, the module is successfully initialized and host should queue the execution job
+///                           otherwise it's the exception object.</param>
+/// <returns>
+///     true if the operation succeeded, false otherwise.
+/// </returns>
 typedef JsErrorCode(CHAKRA_CALLBACK * NotifyModuleReadyCallback)(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar);
 
 /// <summary>

+ 20 - 1
lib/Jsrt/Core/JsrtContextCore.cpp

@@ -101,6 +101,25 @@ void JsrtContextCore::OnScriptLoad(Js::JavascriptFunction * scriptFunction, Js::
 }
 
 HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord)
+{
+    return FetchImportedModuleHelper(
+        [=](Js::JavascriptString *specifierVar, JsModuleRecord *dependentRecord) -> JsErrorCode
+        {
+            return fetchImportedModuleCallback(referencingModule, specifierVar, dependentRecord);
+        }, specifier, dependentModuleRecord);
+}
+
+HRESULT ChakraCoreHostScriptContext::FetchImportedModuleFromScript(JsSourceContext dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord)
+{
+    return FetchImportedModuleHelper(
+        [=](Js::JavascriptString *specifierVar, JsModuleRecord *dependentRecord) -> JsErrorCode
+    {
+        return fetchImportedModuleFromScriptCallback(dwReferencingSourceContext, specifierVar, dependentRecord);
+    }, specifier, dependentModuleRecord);
+}
+
+template<typename Fn>
+HRESULT ChakraCoreHostScriptContext::FetchImportedModuleHelper(Fn fetch, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord)
 {
     if (fetchImportedModuleCallback == nullptr)
     {
@@ -110,7 +129,7 @@ HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* r
     JsModuleRecord dependentRecord = JS_INVALID_REFERENCE;
     {
         AUTO_NO_EXCEPTION_REGION;
-        JsErrorCode errorCode = fetchImportedModuleCallback(referencingModule, specifierVar, &dependentRecord);
+        JsErrorCode errorCode = fetch(specifierVar, &dependentRecord);
         if (errorCode == JsNoError)
         {
             *dependentModuleRecord = static_cast<Js::ModuleRecordBase*>(dependentRecord);

+ 7 - 0
lib/Jsrt/Core/JsrtContextCore.h

@@ -168,6 +168,7 @@ public:
     }
 
     HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override;
+    HRESULT FetchImportedModuleFromScript(JsSourceContext dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) override;
 
     HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) override;
 
@@ -177,6 +178,9 @@ public:
     void SetFetchImportedModuleCallback(FetchImportedModuleCallBack fetchCallback) { this->fetchImportedModuleCallback = fetchCallback ; }
     FetchImportedModuleCallBack GetFetchImportedModuleCallback() const { return this->fetchImportedModuleCallback; }
 
+    void SetFetchImportedModuleFromScriptCallback(FetchImportedModuleFromScriptCallBack fetchCallback) { this->fetchImportedModuleFromScriptCallback = fetchCallback; }
+    FetchImportedModuleFromScriptCallBack GetFetchImportedModuleFromScriptCallback() const { return this->fetchImportedModuleFromScriptCallback; }
+
 #if DBG_DUMP || defined(PROFILE_EXEC) || defined(PROFILE_MEM)
     void EnsureParentInfo(Js::ScriptContext* scriptContext = NULL) override
     {
@@ -186,6 +190,9 @@ public:
 #endif
 
 private:
+    template<typename Fn>
+    HRESULT FetchImportedModuleHelper(Fn fetch, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord);
     FetchImportedModuleCallBack fetchImportedModuleCallback;
+    FetchImportedModuleFromScriptCallBack fetchImportedModuleFromScriptCallback;
     NotifyModuleReadyCallback notifyModuleReadyCallback;
 };

+ 6 - 0
lib/Jsrt/Core/JsrtCore.cpp

@@ -162,6 +162,9 @@ JsSetModuleHostInfo(
         case JsModuleHostInfo_FetchImportedModuleCallback:
             currentContext->GetHostScriptContext()->SetFetchImportedModuleCallback(reinterpret_cast<FetchImportedModuleCallBack>(hostInfo));
             break;
+        case JsModuleHostInfo_FetchImportedModuleFromScriptCallback:
+            currentContext->GetHostScriptContext()->SetFetchImportedModuleFromScriptCallback(reinterpret_cast<FetchImportedModuleFromScriptCallBack>(hostInfo));
+            break;
         case JsModuleHostInfo_NotifyModuleReadyCallback:
             currentContext->GetHostScriptContext()->SetNotifyModuleReadyCallback(reinterpret_cast<NotifyModuleReadyCallback>(hostInfo));
             break;
@@ -203,6 +206,9 @@ JsGetModuleHostInfo(
         case JsModuleHostInfo_FetchImportedModuleCallback:
             *hostInfo = reinterpret_cast<void*>(currentContext->GetHostScriptContext()->GetFetchImportedModuleCallback());
             break;
+        case JsModuleHostInfo_FetchImportedModuleFromScriptCallback:
+            *hostInfo = reinterpret_cast<void*>(currentContext->GetHostScriptContext()->GetFetchImportedModuleFromScriptCallback());
+            break;
         case JsModuleHostInfo_NotifyModuleReadyCallback:
             *hostInfo = reinterpret_cast<void*>(currentContext->GetHostScriptContext()->GetNotifyModuleReadyCallback());
             break;

+ 1 - 0
lib/Jsrt/JsrtInternal.h

@@ -369,6 +369,7 @@ JsErrorCode SetContextAPIWrapper(JsrtContext* newContext, Fn fn)
         return JsErrorOutOfMemory;
     }
     CATCH_OTHER_EXCEPTIONS(errorCode)
+    AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_StackOverflow));
     JsrtContext::TrySetCurrent(oldContext);
     return errorCode;
 }

+ 44 - 7
lib/Parser/Parse.cpp

@@ -2435,12 +2435,37 @@ bool Parser::IsImportOrExportStatementValidHere()
         && this->m_tryCatchOrFinallyDepth == 0;
 }
 
+template<bool buildAST> ParseNodePtr Parser::ParseImportCall()
+{
+    m_pscan->Scan();
+    ParseNodePtr specifier = ParseExpr<buildAST>(koplCma, nullptr, /* fAllowIn */FALSE, /* fAllowEllipsis */FALSE);
+    if (m_token.tk != tkRParen)
+    {
+        Error(ERRnoRparen);
+    }
+
+    m_pscan->Scan();
+    return CreateCallNode(knopCall, CreateNodeWithScanner<knopImport>(), specifier);
+}
+
 template<bool buildAST>
-ParseNodePtr Parser::ParseImportDeclaration()
+ParseNodePtr Parser::ParseImport()
 {
     Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled());
     Assert(m_token.tk == tkIMPORT);
 
+    RestorePoint parsedImport;
+    m_pscan->Capture(&parsedImport);
+    m_pscan->Scan();
+
+    // import()
+    if (m_token.tk == tkLParen)
+    {
+        return ParseImportCall<buildAST>();
+    }
+
+    m_pscan->SeekTo(parsedImport);
+
     if (!IsImportOrExportStatementValidHere())
     {
         Error(ERRInvalidModuleImportOrExport);
@@ -3227,6 +3252,23 @@ LFunction :
         }
         break;
 
+    case tkIMPORT:
+        if (m_scriptContext->GetConfig()->IsES6ModuleEnabled())
+        {
+            m_pscan->Scan();
+            if (m_token.tk == tkLParen)
+            {
+                return ParseImportCall<buildAST>();
+            }
+
+            Error(ERRnoLparen);
+        }
+        else
+        {
+            goto LUnknown;
+        }
+        break;
+
     case tkCASE:
     {
         if (!m_doingFastScan)
@@ -10112,12 +10154,7 @@ LGetJumpStatement:
         goto LNeedTerminator;
 
     case tkIMPORT:
-        if (!(m_grfscr & fscrIsModuleCode))
-        {
-            goto LDefaultToken;
-        }
-
-        pnode = ParseImportDeclaration<buildAST>();
+        pnode = ParseImport<buildAST>();
 
         goto LNeedTerminator;
 

+ 2 - 1
lib/Parser/Parse.h

@@ -861,8 +861,9 @@ private:
 
     bool IsImportOrExportStatementValidHere();
 
-    template<bool buildAST> ParseNodePtr ParseImportDeclaration();
+    template<bool buildAST> ParseNodePtr ParseImport();
     template<bool buildAST> void ParseImportClause(ModuleImportOrExportEntryList* importEntryList, bool parsingAfterComma = false);
+    template<bool buildAST> ParseNodePtr ParseImportCall();
 
     template<bool buildAST> ParseNodePtr ParseExportDeclaration();
     template<bool buildAST> ParseNodePtr ParseDefaultExportClause();

+ 1 - 0
lib/Parser/ptlist.h

@@ -23,6 +23,7 @@ PTNODE(knopNone       , "<none>"           , Nop      , None        , fnopNone
 ***************************************************************************/
 PTNODE(knopName       , "name"             , Nop      , Pid         , fnopLeaf              , "NameExpr"                       )
 PTNODE(knopInt        , "int const"        , Nop      , Int         , fnopLeaf|fnopConst    , "NumberLit"                      )
+PTNODE(knopImport     , "import"           , Nop      , None        , fnopLeaf              , "ImportExpr"                      )
 PTNODE(knopFlt        , "flt const"        , Nop      , Flt         , fnopLeaf|fnopConst    , "NumberLit"                      )
 PTNODE(knopStr        , "str const"        , Nop      , Pid         , fnopLeaf|fnopConst    , "StringLit"                      )
 PTNODE(knopRegExp     , "reg expr"         , Nop      , Pid         , fnopLeaf|fnopConst    , "RegExprLit"                     )

+ 6 - 1
lib/Runtime/Base/FunctionBody.cpp

@@ -203,6 +203,11 @@ namespace Js
         scriptContext->GetDebugContext()->RegisterFunction(this, pszTitle);
     }
 
+    bool ParseableFunctionInfo::IsES6ModuleCode() const
+    {
+        return (GetGrfscr() & fscrIsModuleCode) == fscrIsModuleCode;
+    }
+
     // Given an offset into the source buffer, determine if the end of this SourceInfo
     // lies after the given offset.
     bool
@@ -3171,7 +3176,7 @@ namespace Js
 
     Js::RootObjectBase * FunctionBody::LoadRootObject() const
     {
-        if ((this->GetGrfscr() & fscrIsModuleCode) == fscrIsModuleCode || this->GetModuleID() == kmodGlobal)
+        if (this->IsES6ModuleCode() || this->GetModuleID() == kmodGlobal)
         {
             return JavascriptOperators::OP_LdRoot(this->GetScriptContext());
         }

+ 1 - 0
lib/Runtime/Base/FunctionBody.h

@@ -2094,6 +2094,7 @@ namespace Js
         DeferredFunctionStub *GetDeferredStubs() const { return static_cast<DeferredFunctionStub *>(this->GetAuxPtr(AuxPointerType::DeferredStubs)); }
         void SetDeferredStubs(DeferredFunctionStub *stub) { this->SetAuxPtr(AuxPointerType::DeferredStubs, stub); }
         void RegisterFuncToDiag(ScriptContext * scriptContext, char16 const * pszTitle);
+        bool IsES6ModuleCode() const;
 
     protected:
         static HRESULT MapDeferredReparseError(HRESULT& hrParse, const CompileScriptException& se);

+ 1 - 0
lib/Runtime/Base/ScriptContext.h

@@ -153,6 +153,7 @@ public:
     virtual HRESULT EnqueuePromiseTask(Js::Var varTask) = 0;
 
     virtual HRESULT FetchImportedModule(Js::ModuleRecordBase* referencingModule, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) = 0;
+    virtual HRESULT FetchImportedModuleFromScript(DWORD_PTR dwReferencingSourceContext, LPCOLESTR specifier, Js::ModuleRecordBase** dependentModuleRecord) = 0;
     virtual HRESULT NotifyHostAboutModuleReady(Js::ModuleRecordBase* referencingModule, Js::Var exceptionVar) = 0;
 
     Js::ScriptContext* GetScriptContext() { return scriptContext; }

+ 24 - 0
lib/Runtime/Base/ThreadContext.h

@@ -133,6 +133,30 @@ extern "C" void* MarkerForExternalDebugStep();
                 }\
         }
 
+#define LEAVE_SCRIPT_IF(scriptContext, condition, block) \
+        if (condition) \
+        { \
+            BEGIN_LEAVE_SCRIPT(scriptContext); \
+            block \
+            END_LEAVE_SCRIPT(scriptContext); \
+        } \
+        else \
+        { \
+            block \
+        }
+
+#define ENTER_SCRIPT_IF(scriptContext, doCleanup, isCallRoot, hasCaller, condition, block) \
+        if (condition) \
+        { \
+            BEGIN_ENTER_SCRIPT(scriptContext, doCleanup, isCallRoot, hasCaller); \
+            block \
+            END_ENTER_SCRIPT(scriptContext, doCleanup, isCallRoot, hasCaller); \
+        } \
+        else \
+        { \
+            block \
+        }
+
 #define BEGIN_LEAVE_SCRIPT(scriptContext) \
         LEAVE_SCRIPT_START_EX(scriptContext, /* stackProbe */ true, /* leaveForHost */ true, /* isFPUControlRestoreNeeded */ false)
 

+ 9 - 0
lib/Runtime/ByteCode/ByteCodeEmitter.cpp

@@ -10803,6 +10803,15 @@ void Emit(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *func
         {
             byteCodeGenerator->EmitSuperCall(funcInfo, pnode, fReturnValue);
         }
+        else if (pnode->sxCall.pnodeTarget->nop == knopImport)
+        {
+            ParseNodePtr args = pnode->sxCall.pnodeArgs;
+            Assert(CountArguments(args) == 2); // import() takes one argument
+            Emit(args, byteCodeGenerator, funcInfo, false);
+            funcInfo->ReleaseLoc(args);
+            funcInfo->AcquireLoc(pnode);
+            byteCodeGenerator->Writer()->Reg2(Js::OpCode::ImportCall, pnode->location, args->location);
+        }
         else
         {
             if (pnode->sxCall.isApplyCall && funcInfo->GetApplyEnclosesArgs())

+ 2 - 0
lib/Runtime/ByteCode/OpCodes.h

@@ -734,6 +734,8 @@ MACRO_EXTEND_WMS(       LdHomeObjProto,     Reg2,           OpSideEffect)
 MACRO_EXTEND_WMS(       LdFuncObjProto,     Reg2,           OpSideEffect)
 MACRO_EXTEND_WMS(       SetHomeObj,         Reg2,           OpSideEffect)
 
+MACRO_EXTEND_WMS(       ImportCall,         Reg2,           OpSideEffect)
+
 MACRO_BACKEND_ONLY(     BrFncCachedScopeEq, Reg2,           None)
 MACRO_BACKEND_ONLY(     BrFncCachedScopeNeq,Reg2,           None)
 

+ 1 - 0
lib/Runtime/Language/InterpreterHandler.inl

@@ -300,6 +300,7 @@ EXDEF2_WMS(XXtoA1Mem,               ScopedLdHomeObj,            OP_ScopedLdHomeO
 EXDEF2_WMS(XXtoA1Mem,               ScopedLdFuncObj,            OP_ScopedLdFuncObj)
 EXDEF2_WMS(A1toA1Mem,               LdHomeObjProto,             JavascriptOperators::OP_LdHomeObjProto)
 EXDEF2_WMS(A1toA1Mem,               LdFuncObjProto,             JavascriptOperators::OP_LdFuncObjProto)
+EXDEF2_WMS(A1toA1Mem,               ImportCall,                 OP_ImportCall)
 EXDEF2_WMS(A2toXX,                  SetHomeObj,                 JavascriptOperators::OP_SetHomeObj)
   DEF2_WMS(A1toA1Mem,               StrictLdThis,               JavascriptOperators::OP_StrictGetThis)
   DEF2_WMS(A1I1toA1Mem,             ProfiledLdThis,             PROFILEDOP(OP_ProfiledLdThis, JavascriptOperators::OP_GetThisNoFastPath))

+ 5 - 0
lib/Runtime/Language/InterpreterStackFrame.cpp

@@ -7555,6 +7555,11 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip)
         return JavascriptOperators::OP_ScopedLdFuncObj(function, scriptContext);
     }
 
+    Var InterpreterStackFrame::OP_ImportCall(Var specifier, ScriptContext *scriptContext)
+    {
+        return JavascriptOperators::OP_ImportCall(function, specifier, scriptContext);
+    }
+
     void InterpreterStackFrame::ValidateRegValue(Var value, bool allowStackVar, bool allowStackVarOnDisabledStackNestedFunc) const
     {
 #if DBG

+ 1 - 0
lib/Runtime/Language/InterpreterStackFrame.h

@@ -727,6 +727,7 @@ namespace Js
         template <LayoutSize layoutSize,bool profiled> const byte * OP_ProfiledLoopBodyStart(const byte *ip);
         template <typename T> void OP_ApplyArgs(const unaligned OpLayoutT_Reg5<T> * playout);
         template <class T> void OP_EmitTmpRegCount(const unaligned OpLayoutT_Unsigned1<T> * ip);
+        Var OP_ImportCall(Var specifier, ScriptContext *scriptContext);
 
         HeapArgumentsObject * CreateEmptyHeapArgumentsObject(ScriptContext* scriptContext);
         void TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParam, bool isScopeObjRestored);

+ 106 - 0
lib/Runtime/Language/JavascriptOperators.cpp

@@ -9639,6 +9639,112 @@ CommonNumber:
         return superCtor;
     }
 
+    Var JavascriptOperators::OP_ImportCall(__in JavascriptFunction *function, __in Var specifier, __in ScriptContext* scriptContext)
+    {
+        ModuleRecordBase *moduleRecordBase = nullptr;
+        SourceTextModuleRecord *moduleRecord = nullptr;
+
+        FunctionBody* parentFuncBody = function->GetFunctionBody();
+        JavascriptString *specifierString = nullptr;
+
+        try
+        {
+            specifierString = JavascriptConversion::ToString(specifier, scriptContext);
+        }
+        catch (const JavascriptException &err)
+        {
+            Var errorObject = err.GetAndClear()->GetThrownObject(scriptContext);
+            AssertMsg(errorObject != nullptr, "OP_ImportCall: null error object thrown by ToString(specifier)");
+            if (errorObject != nullptr)
+            {
+                return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext);
+            }
+
+            Throw::InternalError();
+        }
+
+        DWORD_PTR dwReferencingSourceContext = parentFuncBody->GetHostSourceContext();
+        if (!parentFuncBody->IsES6ModuleCode() && dwReferencingSourceContext == Js::Constants::NoHostSourceContext)
+        {
+            // import() called from eval
+            if (parentFuncBody->GetUtf8SourceInfo()->GetCallerUtf8SourceInfo() == nullptr)
+            {
+                JavascriptError *error = scriptContext->GetLibrary()->CreateError();
+                JavascriptError::SetErrorMessageProperties(error, E_FAIL, _u("Unable to locate active script or module that calls import()"), scriptContext);
+                return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext);
+            }
+
+            dwReferencingSourceContext = parentFuncBody->GetUtf8SourceInfo()->GetCallerUtf8SourceInfo()->GetSourceContextInfo()->dwHostSourceContext;
+
+            if (dwReferencingSourceContext == Js::Constants::NoHostSourceContext)
+            {
+                // Walk the call stack if caller function is neither module code nor having host source context
+
+                JavascriptFunction* caller = nullptr;
+                Js::JavascriptStackWalker walker(scriptContext);
+                walker.GetCaller(&caller);
+
+                do
+                {
+                    if (walker.GetCaller(&caller) && caller != nullptr && caller->IsScriptFunction())
+                    {
+                        parentFuncBody = caller->GetFunctionBody();
+                        dwReferencingSourceContext = parentFuncBody->GetHostSourceContext();
+                    }
+                    else
+                    {
+                        JavascriptError *error = scriptContext->GetLibrary()->CreateError();
+                        JavascriptError::SetErrorMessageProperties(error, E_FAIL, _u("Unable to locate active script or module that calls import()"), scriptContext);
+                        return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext);
+                    }
+
+                } while (!parentFuncBody->IsES6ModuleCode() && dwReferencingSourceContext == Js::Constants::NoHostSourceContext);
+            }
+        }
+
+        LPCOLESTR moduleName = specifierString->GetSz();
+        HRESULT hr = 0;
+
+        if (parentFuncBody->IsES6ModuleCode())
+        {
+            SourceTextModuleRecord *referenceModuleRecord = parentFuncBody->GetScriptContext()->GetLibrary()->GetModuleRecord(parentFuncBody->GetModuleID());
+            BEGIN_LEAVE_SCRIPT(scriptContext);
+            BEGIN_TRANSLATE_TO_HRESULT(static_cast<ExceptionType>(ExceptionType_OutOfMemory | ExceptionType_StackOverflow));
+            hr = scriptContext->GetHostScriptContext()->FetchImportedModule(referenceModuleRecord, moduleName, &moduleRecordBase);
+            END_TRANSLATE_EXCEPTION_TO_HRESULT(hr);
+            END_LEAVE_SCRIPT(scriptContext);
+        }
+        else
+        {
+            Assert(dwReferencingSourceContext != Js::Constants::NoHostSourceContext);
+            BEGIN_LEAVE_SCRIPT(scriptContext);
+            BEGIN_TRANSLATE_TO_HRESULT(static_cast<ExceptionType>(ExceptionType_OutOfMemory | ExceptionType_StackOverflow));
+            hr = scriptContext->GetHostScriptContext()->FetchImportedModuleFromScript(dwReferencingSourceContext, moduleName, &moduleRecordBase);
+            END_TRANSLATE_EXCEPTION_TO_HRESULT(hr);
+            END_LEAVE_SCRIPT(scriptContext);
+        }
+
+        if (FAILED(hr))
+        {
+            Js::JavascriptError *error = scriptContext->GetLibrary()->CreateURIError();
+            JavascriptError::SetErrorMessageProperties(error, hr, moduleName, scriptContext);
+            return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext);
+        }
+
+        moduleRecord = SourceTextModuleRecord::FromHost(moduleRecordBase);
+
+        if (moduleRecord->GetErrorObject() != nullptr)
+        {
+            return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, moduleRecord->GetErrorObject(), scriptContext, moduleRecord);
+        }
+        else if (moduleRecord->WasEvaluated())
+        {
+            return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, moduleRecord->GetNamespace(), scriptContext, moduleRecord);
+        }
+
+        return moduleRecord->PostProcessDynamicModuleImport();
+    }
+
     Var JavascriptOperators::ScopedLdHomeObjFuncObjHelper(Var scriptFunction, Js::PropertyId propertyId, ScriptContext * scriptContext)
     {
         ScriptFunction *instance = ScriptFunction::FromVar(scriptFunction);

+ 1 - 0
lib/Runtime/Language/JavascriptOperators.h

@@ -567,6 +567,7 @@ namespace Js
         static Var ScopedLdHomeObjFuncObjHelper(Var scriptFunction, Js::PropertyId propertyId, ScriptContext * scriptContext);
         static Var OP_LdHomeObjProto(Var aRight, ScriptContext* scriptContext);
         static Var OP_LdFuncObjProto(Var aRight, ScriptContext* scriptContext);
+        static Var OP_ImportCall(__in JavascriptFunction *function, __in Var specifier, __in ScriptContext* scriptContext);
 
         static Var OP_ResumeYield(ResumeYieldData* yieldData, RecyclableObject* iterator);
 

+ 193 - 46
lib/Runtime/Language/SourceTextModuleRecord.cpp

@@ -7,6 +7,7 @@
 #include "Types/SimpleDictionaryPropertyDescriptor.h"
 #include "Types/SimpleDictionaryTypeHandler.h"
 #include "ModuleNamespace.h"
+#include "Library/JavascriptPromise.h"
 
 namespace Js
 {
@@ -36,15 +37,14 @@ namespace Js
         resolvedExportMap(nullptr),
         wasParsed(false),
         wasDeclarationInitialized(false),
-#if DBG
         parentsNotified(false),
-#endif
         isRootModule(false),
         hadNotifyHostReady(false),
         localExportSlots(nullptr),
         numPendingChildrenModule(0),
         moduleId(InvalidModuleIndex),
         localSlotCount(InvalidSlotCount),
+        promise(nullptr),
         localExportCount(0)
     {
         namespaceRecord.module = this;
@@ -74,15 +74,11 @@ namespace Js
         localExportRecordList = nullptr;
         indirectExportRecordList = nullptr;
         starExportRecordList = nullptr;
-        childrenModuleSet = nullptr;
         parentModuleList = nullptr;
         if (!isShutdown)
         {
-            if (parser != nullptr)
-            {
-                AllocatorDelete(ArenaAllocator, scriptContext->GeneralAllocator(), parser);
-                parser = nullptr;
-            }
+            AdeleteUnlessNull(scriptContext->GeneralAllocator(), parser);
+            AdeleteUnlessNull(scriptContext->GeneralAllocator(), childrenModuleSet);
         }
     }
 
@@ -91,6 +87,7 @@ namespace Js
         OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ParseSource(%s)\n"), this->GetSpecifierSz());
         Assert(!wasParsed);
         Assert(parser == nullptr);
+        Assert(exceptionVar != nullptr);
         HRESULT hr = NOERROR;
         ScriptContext* scriptContext = GetScriptContext();
         CompileScriptException se;
@@ -98,6 +95,9 @@ namespace Js
         *exceptionVar = nullptr;
         if (!scriptContext->GetConfig()->IsES6ModuleEnabled())
         {
+            JavascriptError *pError = scriptContext->GetLibrary()->CreateError();
+            JavascriptError::SetErrorMessageProperties(pError, hr, _u("ES6Module not supported"), scriptContext);
+            *exceptionVar = pError;
             return E_NOTIMPL;
         }
         // Host indicates that the current module failed to load.
@@ -105,8 +105,8 @@ namespace Js
         {
             Assert(sourceLength == 0);
             hr = E_FAIL;
-            JavascriptError *pError = scriptContext->GetLibrary()->CreateError();
-            JavascriptError::SetErrorMessageProperties(pError, hr, _u("host failed to download module"), scriptContext);
+            JavascriptError *pError = scriptContext->GetLibrary()->CreateURIError();
+            JavascriptError::SetErrorMessageProperties(pError, hr, this->GetSpecifierSz(), scriptContext);
             *exceptionVar = pError;
         }
         else
@@ -114,7 +114,7 @@ namespace Js
             try
             {
                 AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_StackOverflow));
-                this->parser = (Parser*)AllocatorNew(ArenaAllocator, allocator, Parser, scriptContext);
+                this->parser = Anew(allocator, Parser, scriptContext);
                 srcInfo->moduleID = moduleId;
 
                 LoadScriptFlag loadScriptFlag = (LoadScriptFlag)(LoadScriptFlag_Expression | LoadScriptFlag_Module |
@@ -166,7 +166,7 @@ namespace Js
             if (this->parser)
             {
                 this->parseTree = nullptr;
-                AllocatorDelete(ArenaAllocator, allocator, this->parser);
+                Adelete(allocator, this->parser);
                 this->parser = nullptr;
             }
             if (this->errorObject == nullptr)
@@ -175,6 +175,11 @@ namespace Js
             }
 
             OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded\n"));
+            if (this->promise != nullptr)
+            {
+                SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this);
+            }
+
             NotifyParentsAsNeeded();
         }
         return hr;
@@ -190,9 +195,7 @@ namespace Js
                 parentModule->OnChildModuleReady(this, this->errorObject);
             });
         }
-#if DBG
         SetParentsNotified();
-#endif
     }
 
     void SourceTextModuleRecord::ImportModuleListsFromParser()
@@ -220,6 +223,55 @@ namespace Js
         return hr;
     }
 
+    Var SourceTextModuleRecord::PostProcessDynamicModuleImport()
+    {
+        JavascriptPromise *promise = this->GetPromise();
+        ScriptContext* scriptContext = GetScriptContext();
+        AnalysisAssert(scriptContext != nullptr);
+        if (promise == nullptr)
+        {
+            promise = JavascriptPromise::CreateEnginePromise(scriptContext);
+            this->SetPromise(promise);
+        }
+
+        if (this->ParentsNotified())
+        {
+            HRESULT hr = NOERROR;
+            if (!WasDeclarationInitialized())
+            {
+                ModuleDeclarationInstantiation();
+
+                if (this->errorObject != nullptr)
+                {
+                    SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, scriptContext, this);
+                }
+                else
+                {
+                    if (!hadNotifyHostReady && !WasEvaluated())
+                    {
+                        bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive();
+
+                        LEAVE_SCRIPT_IF(scriptContext, isScriptActive,
+                        {
+                            hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject);
+                        });
+
+                        hadNotifyHostReady = true;
+                    }
+                }
+            }
+
+            if (FAILED(hr))
+            {
+                Js::JavascriptError * error = scriptContext->GetLibrary()->CreateURIError();
+                JavascriptError::SetErrorMessageProperties(error, hr, this->GetSpecifierSz(), scriptContext);
+                return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext, this);
+            }
+        }
+
+        return this->promise;
+    }
+
     HRESULT SourceTextModuleRecord::PrepareForModuleDeclarationInitialization()
     {
         OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("PrepareForModuleDeclarationInitialization(%s)\n"), this->GetSpecifierSz());
@@ -230,17 +282,22 @@ namespace Js
             OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentsAsNeeded\n"));
             NotifyParentsAsNeeded();
 
-            if (!WasDeclarationInitialized() && isRootModule)
+            if (this->promise != nullptr || (!WasDeclarationInitialized() && isRootModule))
             {
                 // 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.
                 ScriptContext* scriptContext = GetScriptContext();
-                Assert(!scriptContext->GetThreadContext()->IsScriptActive());
+                bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive();
+                Assert(!isScriptActive || this->promise != nullptr);
 
                 ModuleDeclarationInstantiation();
-                if (!hadNotifyHostReady)
+                if (!hadNotifyHostReady && !WasEvaluated())
                 {
-                    hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject);
+                    LEAVE_SCRIPT_IF(scriptContext, isScriptActive,
+                    {
+                        hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject);
+                    });
+
                     hadNotifyHostReady = true;
                 }
             }
@@ -268,9 +325,21 @@ namespace Js
 
             OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded (childException)\n"), this->GetSpecifierSz());
             NotifyParentsAsNeeded();
-            if (isRootModule && !hadNotifyHostReady)
+
+            bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive();
+
+            if (this->promise != nullptr)
+            {
+                SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this);
+            }
+
+            if (this->promise != nullptr || (isRootModule && !hadNotifyHostReady))
             {
-                hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject);
+                LEAVE_SCRIPT_IF(scriptContext, isScriptActive,
+                {
+                    hr = scriptContext->GetHostScriptContext()->NotifyHostAboutModuleReady(this, this->errorObject);
+                });
+
                 hadNotifyHostReady = true;
             }
         }
@@ -311,7 +380,7 @@ namespace Js
         ArenaAllocator* allocator = scriptContext->GeneralAllocator();
         if (exportStarSet == nullptr)
         {
-            exportStarSet = (ExportModuleRecordList*)AllocatorNew(ArenaAllocator, allocator, ExportModuleRecordList, allocator);
+            exportStarSet = Anew(allocator, ExportModuleRecordList, allocator);
         }
         if (exportStarSet->Has(this))
         {
@@ -321,7 +390,7 @@ namespace Js
         ExportedNames* tempExportedNames = nullptr;
         if (this->localExportRecordList != nullptr)
         {
-            tempExportedNames = (ExportedNames*)AllocatorNew(ArenaAllocator, allocator, ExportedNames, allocator);
+            tempExportedNames = Anew(allocator, ExportedNames, allocator);
             this->localExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) {
                 PropertyId exportNameId = EnsurePropertyIdForIdentifier(exportEntry.exportName);
                 tempExportedNames->Prepend(exportNameId);
@@ -331,7 +400,7 @@ namespace Js
         {
             if (tempExportedNames == nullptr)
             {
-                tempExportedNames = (ExportedNames*)AllocatorNew(ArenaAllocator, allocator, ExportedNames, allocator);
+                tempExportedNames = Anew(allocator, ExportedNames, allocator);
             }
             this->indirectExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) {
                 PropertyId exportedNameId = EnsurePropertyIdForIdentifier(exportEntry.exportName);
@@ -342,7 +411,7 @@ namespace Js
         {
             if (tempExportedNames == nullptr)
             {
-                tempExportedNames = (ExportedNames*)AllocatorNew(ArenaAllocator, allocator, ExportedNames, allocator);
+                tempExportedNames = Anew(allocator, ExportedNames, allocator);
             }
             this->starExportRecordList->Map([=](ModuleImportOrExportEntry exportEntry) {
                 Assert(exportEntry.moduleRequest != nullptr);
@@ -409,7 +478,7 @@ namespace Js
         ArenaAllocator* allocator = scriptContext->GeneralAllocator();
         if (resolvedExportMap == nullptr)
         {
-            resolvedExportMap = AllocatorNew(ArenaAllocator, allocator, ResolvedExportMap, allocator);
+            resolvedExportMap = Anew(allocator, ResolvedExportMap, allocator);
         }
         if (resolvedExportMap->TryGetReference(exportName, exportRecord))
         {
@@ -418,11 +487,11 @@ namespace Js
         // TODO: use per-call/loop allocator?
         if (exportStarSet == nullptr)
         {
-            exportStarSet = (ExportModuleRecordList*)AllocatorNew(ArenaAllocator, allocator, ExportModuleRecordList, allocator);
+            exportStarSet = Anew(allocator, ExportModuleRecordList, allocator);
         }
         if (resolveSet == nullptr)
         {
-            resolveSet = (ResolveSet*)AllocatorNew(ArenaAllocator, allocator, ResolveSet, allocator);
+            resolveSet = Anew(allocator, ResolveSet, allocator);
         }
 
         *exportRecord = nullptr;
@@ -586,7 +655,7 @@ namespace Js
     void SourceTextModuleRecord::SetParent(SourceTextModuleRecord* parentRecord, LPCOLESTR moduleName)
     {
         Assert(parentRecord != nullptr);
-        Assert(parentRecord->childrenModuleSet != nullptr);
+        parentRecord->EnsureChildModuleSet(GetScriptContext());
         if (!parentRecord->childrenModuleSet->ContainsKey(moduleName))
         {
             parentRecord->childrenModuleSet->AddNew(moduleName, this);
@@ -609,6 +678,15 @@ namespace Js
         }
     }
 
+    void SourceTextModuleRecord::EnsureChildModuleSet(ScriptContext *scriptContext)
+    {
+        if (nullptr == this->childrenModuleSet)
+        {
+            ArenaAllocator* allocator = scriptContext->GeneralAllocator();
+            this->childrenModuleSet = Anew(allocator, ChildModuleRecordSet, allocator);
+        }
+    }
+
     HRESULT SourceTextModuleRecord::ResolveExternalModuleDependencies()
     {
         OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("ResolveExternalModuleDependencies(%s)\n"), this->GetSpecifierSz());
@@ -618,11 +696,7 @@ namespace Js
         HRESULT hr = NOERROR;
         if (requestedModuleList != nullptr)
         {
-            if (nullptr == childrenModuleSet)
-            {
-                ArenaAllocator* allocator = scriptContext->GeneralAllocator();
-                childrenModuleSet = (ChildModuleRecordSet*)AllocatorNew(ArenaAllocator, allocator, ChildModuleRecordSet, allocator);
-            }
+            EnsureChildModuleSet(scriptContext);
             requestedModuleList->MapUntil([&](IdentPtr specifier) {
                 ModuleRecordBase* moduleRecordBase = nullptr;
                 SourceTextModuleRecord* moduleRecord = nullptr;
@@ -739,19 +813,23 @@ namespace Js
         {
             return nullptr;
         }
-        Assert(this->errorObject == nullptr);
+
         if (this->errorObject != nullptr)
         {
-            JavascriptExceptionOperators::Throw(errorObject, scriptContext);
+            if (this->promise != nullptr)
+            {
+                SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->errorObject, this->scriptContext, this);
+                return scriptContext->GetLibrary()->GetUndefined();
+            }
+            else
+            {
+                JavascriptExceptionOperators::Throw(errorObject, this->scriptContext);
+            }
         }
-        Assert(!WasEvaluated());
+
+        Assert(this->errorObject == nullptr);
         SetWasEvaluated();
-        // we shouldn't evaluate if there are existing failure. This is defense in depth as the host shouldn't
-        // call into evaluation if there was previous failure on the module.
-        if (this->errorObject)
-        {
-            return this->errorObject;
-        }
+
         if (childrenModuleSet != nullptr)
         {
             childrenModuleSet->EachValue([=](SourceTextModuleRecord* childModuleRecord)
@@ -765,7 +843,41 @@ namespace Js
         CleanupBeforeExecution();
 
         Arguments outArgs(CallInfo(CallFlags_Value, 0), nullptr);
-        return rootFunction->CallRootFunction(outArgs, scriptContext, true);
+
+        Var ret = nullptr;
+        JavascriptExceptionObject *exception = nullptr;
+        try
+        {
+            AUTO_NESTED_HANDLED_EXCEPTION_TYPE((ExceptionType)(ExceptionType_OutOfMemory | ExceptionType_JavascriptException));
+            ENTER_SCRIPT_IF(scriptContext, true, false, false, !scriptContext->GetThreadContext()->IsScriptActive(),
+            {
+                ret = rootFunction->CallRootFunction(outArgs, scriptContext, true);
+            });
+        }
+        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);
+                return scriptContext->GetLibrary()->GetUndefined();
+            }
+        }
+
+        if (exception != nullptr)
+        {
+            JavascriptExceptionOperators::DoThrowCheckClone(exception, scriptContext);
+        }
+
+        if (this->promise != nullptr)
+        {
+            SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(true, this->GetNamespace(), this->GetScriptContext(), this);
+        }
+
+        return ret;
     }
 
     HRESULT SourceTextModuleRecord::OnHostException(void* errorVar)
@@ -836,9 +948,9 @@ namespace Js
             if (localExportRecordList != nullptr)
             {
                 ArenaAllocator* allocator = scriptContext->GeneralAllocator();
-                localExportMapByExportName = AllocatorNew(ArenaAllocator, allocator, LocalExportMap, allocator);
-                localExportMapByLocalName = AllocatorNew(ArenaAllocator, allocator, LocalExportMap, allocator);
-                localExportIndexList = AllocatorNew(ArenaAllocator, allocator, LocalExportIndexList, allocator);
+                localExportMapByExportName = Anew(allocator, LocalExportMap, allocator);
+                localExportMapByLocalName = Anew(allocator, LocalExportMap, allocator);
+                localExportIndexList = Anew(allocator, LocalExportIndexList, allocator);
                 localExportRecordList->Map([&](ModuleImportOrExportEntry exportEntry)
                 {
                     Assert(exportEntry.moduleRequest == nullptr);
@@ -983,4 +1095,39 @@ namespace Js
         }
         return slotIndex;
     }
+
+    // static
+    Var SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(bool isResolve, Var value, ScriptContext *scriptContext, SourceTextModuleRecord *moduleRecord)
+    {
+        bool isScriptActive = scriptContext->GetThreadContext()->IsScriptActive();
+        JavascriptPromise *promise = nullptr;
+        if (moduleRecord != nullptr)
+        {
+            promise = moduleRecord->GetPromise();
+        }
+
+        if (promise == nullptr)
+        {
+            promise = JavascriptPromise::CreateEnginePromise(scriptContext);
+        }
+
+        ENTER_SCRIPT_IF(scriptContext, true, false, false, !isScriptActive,
+        {
+            if (isResolve)
+            {
+                promise->Resolve(value, scriptContext);
+            }
+            else
+            {
+                promise->Reject(value, scriptContext);
+            }
+        });
+
+        if (moduleRecord != nullptr)
+        {
+            moduleRecord->SetPromise(nullptr);
+        }
+
+        return promise;
+    }
 }

+ 8 - 6
lib/Runtime/Language/SourceTextModuleRecord.h

@@ -41,6 +41,7 @@ namespace Js
         void Mark(Recycler * recycler) override { return; }
 
         HRESULT ResolveExternalModuleDependencies();
+        void EnsureChildModuleSet(ScriptContext *scriptContext);
 
         void* GetHostDefined() const { return hostDefined; }
         void SetHostDefined(void* hostObj) { hostDefined = hostObj; }
@@ -55,11 +56,9 @@ namespace Js
         void SetWasParsed() { wasParsed = true; }
         bool WasDeclarationInitialized() const { return wasDeclarationInitialized; }
         void SetWasDeclarationInitialized() { wasDeclarationInitialized = true; }
-#if DBG
-        bool ParentsNotified() const { return parentsNotified; }
-        void SetParentsNotified() { parentsNotified = true; }
-#endif
         void SetIsRootModule() { isRootModule = true; }
+        JavascriptPromise *GetPromise() { return this->promise; }
+        void SetPromise(JavascriptPromise *value) { this->promise = value; }
 
         void SetImportRecordList(ModuleImportOrExportEntryList* importList) { importRecordList = importList; }
         void SetLocalExportRecordList(ModuleImportOrExportEntryList* localExports) { localExportRecordList = localExports; }
@@ -103,6 +102,8 @@ namespace Js
 
         void SetParent(SourceTextModuleRecord* parentRecord, LPCOLESTR moduleName);
         Utf8SourceInfo* GetSourceInfo() { return this->pSourceInfo; }
+        static Var ResolveOrRejectDynamicImportPromise(bool isResolve, Var value, ScriptContext *scriptContext, SourceTextModuleRecord *mr = nullptr);
+        Var PostProcessDynamicModuleImport();
 
     private:
         const static uint InvalidModuleIndex = 0xffffffff;
@@ -112,9 +113,7 @@ namespace Js
         // This is the parsed tree resulted from compilation. 
         Field(bool) wasParsed;
         Field(bool) wasDeclarationInitialized;
-#if DBG
         Field(bool) parentsNotified;
-#endif
         Field(bool) isRootModule;
         Field(bool) hadNotifyHostReady;
         Field(ParseNodePtr) parseTree;
@@ -148,6 +147,7 @@ namespace Js
         Field(uint) moduleId;
 
         Field(ModuleNameRecord) namespaceRecord;
+        Field(JavascriptPromise*) promise;
 
         HRESULT PostParseProcess();
         HRESULT PrepareForModuleDeclarationInitialization();
@@ -158,6 +158,8 @@ namespace Js
         void InitializeLocalImports();
         void InitializeLocalExports();
         void InitializeIndirectExports();
+        bool ParentsNotified() const { return parentsNotified; }
+        void SetParentsNotified() { parentsNotified = true; }
         PropertyId EnsurePropertyIdForIdentifier(IdentPtr pid);
         LocalExportMap* GetLocalExportMap() const { return localExportMapByExportName; }
         LocalExportIndexList* GetLocalExportIndexList() const { return localExportIndexList; }

+ 12 - 0
lib/Runtime/Library/JavascriptPromise.cpp

@@ -1863,4 +1863,16 @@ namespace Js
 
         return args[0];
     }
+
+    //static
+    JavascriptPromise* JavascriptPromise::CreateEnginePromise(ScriptContext *scriptContext)
+    {
+        JavascriptPromiseResolveOrRejectFunction *resolve = nullptr;
+        JavascriptPromiseResolveOrRejectFunction *reject = nullptr;
+
+        JavascriptPromise *promise = scriptContext->GetLibrary()->CreatePromise();
+        JavascriptPromise::InitializePromise(promise, &resolve, &reject, scriptContext);
+
+        return promise;
+    }
 } // namespace Js

+ 2 - 0
lib/Runtime/Library/JavascriptPromise.h

@@ -442,6 +442,8 @@ namespace Js
         static Var TryCallResolveOrRejectHandler(Var handler, Var value, ScriptContext* scriptContext);
         static Var TryRejectWithExceptionObject(JavascriptExceptionObject* exceptionObject, Var handler, ScriptContext* scriptContext);
 
+        static JavascriptPromise* CreateEnginePromise(ScriptContext *scriptContext);
+
         Var Resolve(Var resolution, ScriptContext* scriptContext);
         Var Reject(Var resolution, ScriptContext* scriptContext);
 

+ 20 - 4
test/UnitTestFramework/UnitTestFramework.js

@@ -253,6 +253,7 @@ var testRunner = function testRunner() {
                 ++passedTestCount;
             } else {
                 helpers.writeln("FAILED");
+                testRunner.asyncTestErr(testIndex, "RUN FAILED");
             }
             ++executedTestCount;
         },
@@ -268,7 +269,7 @@ var testRunner = function testRunner() {
             asyncTest.resolve[testIndex][testCount] = 0;
         },
 
-        prepareAsyncCode: function prepareAsyncCode(source, shouldFail) {
+        prepareAsyncCode: function prepareAsyncCode(source, shouldFail, explicitAsyncTestExit) {
             var testIndex = asyncTest.testIndex;
             if (typeof shouldFail == "undefined" || shouldFail == false) {
                 _hasAsyncTestPromise = true;
@@ -277,14 +278,29 @@ var testRunner = function testRunner() {
                     asyncTest.resolve[testIndex].push(resolve);
                 });
                 asyncTest.promise[testIndex].push(promise);
-                return `testRunner.asyncTestBegin(${testIndex}, ${testCount}); ${source};\ntestRunner.asyncTestEnd(${testIndex}, ${testCount});`;
+                return explicitAsyncTestExit ?
+                    `
+                    var _asyncEnter = ()=>{ testRunner.asyncTestBegin(${testIndex}, ${testCount}); };
+                    var _asyncExit = ()=>{ testRunner.asyncTestEnd(${testIndex}, ${testCount}); };
+                    _asyncEnter();
+                    ${source};
+                    `  :
+                    `
+                    testRunner.asyncTestBegin(${testIndex}, ${testCount});
+                    ${source};
+                    testRunner.asyncTestEnd(${testIndex}, ${testCount});
+                    `;
             } else {
                 return source;
             }
         },
 
-        LoadModule : function LoadModule(source, context, shouldFail) {
-            return WScript.LoadModule(testRunner.prepareAsyncCode(source, shouldFail), context);
+        LoadModule : function LoadModule(source, context, shouldFail, explicitAsyncTestExit) {
+            return WScript.LoadModule(testRunner.prepareAsyncCode(source, shouldFail, explicitAsyncTestExit), context);
+        },
+
+        LoadScript : function LoadScript(source, context, shouldFail, explicitAsyncTestExit) {
+            return WScript.LoadScript(testRunner.prepareAsyncCode(source, shouldFail, explicitAsyncTestExit));
         }
     };
     return testRunner;

+ 4 - 0
test/es6/ModuleCircularBar.js

@@ -12,3 +12,7 @@ export function increment() {
     counter++;
 }
 export var counter = 0;
+
+export function reset() {
+    counter = 0;
+}

+ 1 - 1
test/es6/ModuleCircularFoo.js

@@ -12,4 +12,4 @@ export function circular_foo() {
         return counter;
     }
 }
-export { circular_bar as rexportbar } from "ModuleCircularBar.js"
+export { circular_bar as rexportbar, reset } from "ModuleCircularBar.js"

+ 7 - 2
test/es6/ModuleComplexExports.js

@@ -41,13 +41,18 @@ export { genfoo as genfoo2, genbar, genbar as genbar2 };
 
 export default function () { return 'default'; };
 
-var mutatingExportTarget = function() { return 'before'; };
+var mutatingExportTarget;
+function resetMutatingExportTarget() {
+    mutatingExportTarget = function() { return 'before'; };
+    return 'ok';
+}
 function changeMutatingExportTarget() {
     mutatingExportTarget = function() { return 'after'; };
     return 'ok';
 }
+resetMutatingExportTarget();
 
-export { mutatingExportTarget as target, changeMutatingExportTarget as changeTarget };
+export { mutatingExportTarget as target, changeMutatingExportTarget as changeTarget, resetMutatingExportTarget as reset};
 
 var exportedAsKeyword = 'ModuleComplexExports';
 export { exportedAsKeyword as export };

+ 372 - 0
test/es6/dynamic-module-functionality.js

@@ -0,0 +1,372 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+// ES6 Module functionality tests -- verifies functionality of import and export statements
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+function testScript(source, message, shouldFail = false, explicitAsync = false) {
+    message += " (script)";
+    let testfunc = () => testRunner.LoadScript(source, undefined, shouldFail, explicitAsync);
+
+    if (shouldFail) {
+        let caught = false;
+
+        assert.throws(testfunc, SyntaxErrr, message);
+        assert.isTrue(caught, `Expected error not thrown: ${message}`);
+    } else {
+        assert.doesNotThrow(testfunc, message);
+    }
+}
+
+function testModuleScript(source, message, shouldFail = false, explicitAsync = false) {
+    message += " (module)";
+    let testfunc = () => testRunner.LoadModule(source, 'samethread', shouldFail, explicitAsync);
+
+    if (shouldFail) {
+        let caught = false;
+
+        // We can't use assert.throws here because the SyntaxError used to construct the thrown error
+        // is from a different context so it won't be strictly equal to our SyntaxError.
+        try {
+            testfunc();
+        } catch(e) {
+            caught = true;
+
+            // Compare toString output of SyntaxError and other context SyntaxError constructor.
+            assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message);
+        }
+
+        assert.isTrue(caught, `Expected error not thrown: ${message}`);
+    } else {
+        assert.doesNotThrow(testfunc, message);
+    }
+}
+
+function testDynamicImport(importFunc, thenFunc, catchFunc, _asyncEnter, _asyncExit) {
+    var promise = importFunc();
+    assert.isTrue(promise instanceof Promise);
+    promise.then((v)=>{
+        _asyncEnter();
+        thenFunc(v);
+        _asyncExit();
+    }).catch((err)=>{
+        _asyncEnter();
+        catchFunc(err);
+        _asyncExit();
+    });
+}
+
+var tests = [
+    {
+        name: "Runtime evaluation of module specifier",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>{
+                        var getName = ()=>{ return 'ModuleSimpleExport'; };
+                        return import( getName() + '.js');
+                    },
+                    (v)=>{
+                        assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
+                )`;
+            testScript(functionBody, "Test importing a simple exported function", false, true);
+            testModuleScript(functionBody, "Test importing a simple exported function", false, true);
+        }
+    },
+    {
+        name: "Validate a simple module export",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>{ return import('ModuleSimpleExport.js'); },
+                    (v)=>{ assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
+                )`;
+            testScript(functionBody, "Test importing a simple exported function", false, true);
+            testModuleScript(functionBody, "Test importing a simple exported function", false, true);
+        }
+    },
+    {
+        name: "Validate importing from multiple modules",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexExports.js'),
+                    (v)=>{
+                        assert.areEqual('foo', v.foo2(), 'Failed to import foo2 from ModuleComplexExports.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
+                )`;
+            testScript(functionBody, "Test importing from multiple modules", false, true);
+            testModuleScript(functionBody, "Test importing from multiple modules", false, true);
+        }
+    },
+    {
+        name: "Validate a variety of more complex exports",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexExports.js'),
+                    (v)=>{
+                        assert.areEqual('foo', v.foo(), 'Failed to import foo from ModuleComplexExports.js');
+                        assert.areEqual('foo', v.foo2(), 'Failed to import foo2 from ModuleComplexExports.js');
+                        assert.areEqual('bar', v.bar(), 'Failed to import bar from ModuleComplexExports.js');
+                        assert.areEqual('bar', v.bar2(), 'Failed to import bar2 from ModuleComplexExports.js');
+                        assert.areEqual('let2', v.let2, 'Failed to import let2 from ModuleComplexExports.js');
+                        assert.areEqual('let3', v.let3, 'Failed to import let3 from ModuleComplexExports.js');
+                        assert.areEqual('let2', v.let4, 'Failed to import let4 from ModuleComplexExports.js');
+                        assert.areEqual('let3', v.let5, 'Failed to import let5 from ModuleComplexExports.js');
+                        assert.areEqual('const2', v.const2, 'Failed to import const2 from ModuleComplexExports.js');
+                        assert.areEqual('const3', v.const3, 'Failed to import const3 from ModuleComplexExports.js');
+                        assert.areEqual('const2', v.const4, 'Failed to import const4 from ModuleComplexExports.js');
+                        assert.areEqual('const3', v.const5, 'Failed to import const5 from ModuleComplexExports.js');
+                        assert.areEqual('var2', v.var2, 'Failed to import var2 from ModuleComplexExports.js');
+                        assert.areEqual('var3', v.var3, 'Failed to import var3 from ModuleComplexExports.js');
+                        assert.areEqual('var2', v.var4, 'Failed to import var4 from ModuleComplexExports.js');
+                        assert.areEqual('var3', v.var5, 'Failed to import var5 from ModuleComplexExports.js');
+                        assert.areEqual('class2', v.class2.static_member(), 'Failed to import class2 from ModuleComplexExports.js');
+                        assert.areEqual('class2', new v.class2().member(), 'Failed to create intance of class2 from ModuleComplexExports.js');
+                        assert.areEqual('class2', v.class3.static_member(), 'Failed to import class3 from ModuleComplexExports.js');
+                        assert.areEqual('class2', new v.class3().member(), 'Failed to create intance of class3 from ModuleComplexExports.js');
+                        assert.areEqual('class4', v.class4.static_member(), 'Failed to import class4 from ModuleComplexExports.js');
+                        assert.areEqual('class4', new v.class4().member(), 'Failed to create intance of class4 from ModuleComplexExports.js');
+                        assert.areEqual('class4', v.class5.static_member(), 'Failed to import class4 from ModuleComplexExports.js');
+                        assert.areEqual('class4', new v.class5().member(), 'Failed to create intance of class4 from ModuleComplexExports.js');
+                        assert.areEqual('default', v.default(), 'Failed to import default from ModuleComplexExports.js');
+                        assert.areEqual('ModuleComplexExports', v.function, 'Failed to import v.function from ModuleComplexExports.js');
+                        assert.areEqual('ModuleComplexExports', v.export, 'Failed to import v.export from ModuleComplexExports.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Test importing a variety of exports", false, true);
+            testModuleScript(functionBody, "Test importing a variety of exports", false, true);
+        }
+    },
+    {
+        name: "Exporting module changes exported value",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexExports.js'),
+                    (v)=>{
+                        v.reset();
+                        assert.areEqual('before', v.target(), 'Failed to import target from ModuleComplexExports.js');
+                        assert.areEqual('ok', v.changeTarget(), 'Failed to import changeTarget from ModuleComplexExports.js');
+                        assert.areEqual('after', v.target(), 'changeTarget failed to change export value');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Changing exported value", false, true);
+            testModuleScript(functionBody, "Changing exported value", false, true);
+        }
+    },
+    {
+        name: "Simple re-export forwards import to correct slot",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleSimpleReexport.js'),
+                    (v)=>{
+                        assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleReexport.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Simple re-export from one module to another", false, true);
+            testModuleScript(functionBody, "Simple re-export from one module to another", false, true);
+        }
+    },
+    {
+        name: "Renamed re-export and dynamic import",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexReexports.js'),
+                    (v)=>{
+                        assert.areEqual('bar', v.ModuleComplexReexports_foo(), 'Failed to import ModuleComplexReexports_foo from ModuleComplexReexports.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Rename already renamed re-export", false, true);
+            testModuleScript(functionBody, "Rename already renamed re-export", false, true);
+        }
+    },
+    {
+        name: "Explicit export/import to default binding",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleDefaultExport1.js'),
+                    (v)=>{
+                        assert.areEqual('ModuleDefaultExport1', v.default(), 'Failed to import default from ModuleDefaultExport1.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Explicitly export and import a local name to the default binding", false, true);
+            testModuleScript(functionBody, "Explicitly export and import a local name to the default binding", false, true);
+        }
+    },
+    {
+        name: "Explicit import of default binding",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleDefaultExport2.js'),
+                    (v)=>{
+                        assert.areEqual('ModuleDefaultExport2', v.default(), 'Failed to import default from ModuleDefaultExport2.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Explicitly import the default export binding", false, true);
+            testModuleScript(functionBody, "Explicitly import the default export binding", false, true);
+        }
+    },
+    {
+        name: "Exporting module changes value of default export",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleDefaultExport3.js'),
+                    (v)=>{
+                        assert.areEqual(2, v.default, 'Failed to import default from ModuleDefaultExport3.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Exported value incorrectly bound", false, true);
+            testModuleScript(functionBody, "Exported value incorrectly bound", false, true);
+
+            functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleDefaultExport4.js'),
+                    (v)=>{
+                        assert.areEqual(1, v.default, 'Failed to import default from ModuleDefaultExport4.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Exported value incorrectly bound", false, true);
+            testModuleScript(functionBody, "Exported value incorrectly bound", false, true);
+        }
+    },
+    {
+        name: "Import bindings used in a nested function",
+        body: function () {
+            let functionBody =
+                `function test(func) {
+                    assert.areEqual('ModuleDefaultExport2', func(), 'Failed to import default from ModuleDefaultExport2.js');
+                }
+                testDynamicImport(
+                    ()=>import('ModuleDefaultExport2.js'),
+                    (v)=>test(v.default),
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Failed to find imported name correctly in nested function", false, true);
+            testModuleScript(functionBody, "Failed to find imported name correctly in nested function", false, true);
+        }
+    },
+    {
+        name: "Exported name may be any keyword",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexExports.js'),
+                    (v)=>{
+                        assert.areEqual('ModuleComplexExports', v.export, 'Failed to import export from ModuleDefaultExport2.js');
+                        assert.areEqual('ModuleComplexExports', v.function, 'Failed to import function from ModuleDefaultExport2.js');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false, true);
+            testModuleScript(functionBody, "Exported name may be a keyword (import binding must be binding identifier)", false, true);
+        }
+    },
+    {
+        name: "Odd case of 'export { as as as }; import { as as as };'",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexExports.js'),
+                    (v)=>{
+                        assert.areEqual('as', v.as(), 'String "as" is not reserved word');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+            testScript(functionBody, "Test 'import { as as as}'", false, true);
+            testModuleScript(functionBody, "Test 'import { as as as}'", false, true);
+        }
+    },
+    {
+        name: "Typeof a module export",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleDefaultExport2.js'),
+                    (v)=>{
+                        assert.areEqual('function', typeof v.default, 'typeof default export from ModuleDefaultExport2.js is function');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+
+            testScript(functionBody, "Typeof a module export", false, true);
+            testModuleScript(functionBody, "Typeof a module export", false, true);
+        }
+    },
+    {
+        name: "Circular module dependency",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleCircularFoo.js'),
+                    (v)=>{
+                        v.reset();
+                        assert.areEqual(2, v.circular_foo(), 'This function calls between both modules in the circular dependency incrementing a counter in each');
+                        assert.areEqual(4, v.rexportbar(), 'Second call originates in the other module but still increments the counter twice');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+
+            testScript(functionBody, "Circular module dependency", false, true);
+            testModuleScript(functionBody, "Circular module dependency", false, true);
+
+        }
+    },
+    {
+        name: "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>import('ModuleComplexReexports.js'),
+                    (v)=>{
+                        assert.areEqual('foo', v.foo(), 'Simple implicit re-export');
+                        assert.areEqual('foo', v.baz(), 'Renamed export imported and renamed during implicit re-export');
+                        assert.areEqual('foo', v.localfoo(), 'Export renamed as import and implicitly re-exported');
+                        assert.areEqual('foo', v.bar(), 'Renamed export renamed as import and renamed again during implicit re-exported');
+                        assert.areEqual('foo', v.localfoo2(), 'Renamed export renamed as import and implicitly re-exported');
+                        assert.areEqual('foo', v.bar2(), 'Renamed export renamed as import and renamed again during implicit re-export');
+                    },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit);
+                `;
+
+            testScript(functionBody, "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", false, true);
+            testModuleScript(functionBody, "Implicitly re-exporting an import binding (import { foo } from ''; export { foo };)", false, true);
+        }
+    },
+    {
+        name: "Validate a simple module export inside eval()",
+        body: function () {
+            let functionBody =
+                `testDynamicImport(
+                    ()=>{ return eval("import('ModuleSimpleExport.js')"); },
+                    (v)=>{ assert.areEqual('ModuleSimpleExport', v.ModuleSimpleExport_foo(), 'Failed to import ModuleSimpleExport_foo from ModuleSimpleExport.js'); },
+                    (err)=>{ assert.fail(err.message); }, _asyncEnter, _asyncExit
+                )`;
+            testScript(functionBody, "Test importing a simple exported function", false, true);
+            testModuleScript(functionBody, "Test importing a simple exported function", false, true);
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 142 - 0
test/es6/dynamic-module-import-specifier.js

@@ -0,0 +1,142 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+// ES6 Module functionality tests -- verifies functionality of import and export statements
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+function testScript(source, message, shouldFail = false, explicitAsync = false) {
+    message += " (script)";
+    let testfunc = () => testRunner.LoadScript(source, undefined, shouldFail, explicitAsync);
+
+    if (shouldFail) {
+        let caught = false;
+
+        assert.throws(testfunc, SyntaxErrr, message);
+        assert.isTrue(caught, `Expected error not thrown: ${message}`);
+    } else {
+        assert.doesNotThrow(testfunc, message);
+    }
+}
+
+function testModuleScript(source, message, shouldFail = false, explicitAsync = false) {
+    message += " (module)";
+    let testfunc = () => testRunner.LoadModule(source, 'samethread', shouldFail, explicitAsync);
+
+    if (shouldFail) {
+        let caught = false;
+
+        // We can't use assert.throws here because the SyntaxError used to construct the thrown error
+        // is from a different context so it won't be strictly equal to our SyntaxError.
+        try {
+            testfunc();
+        } catch(e) {
+            caught = true;
+
+            // Compare toString output of SyntaxError and other context SyntaxError constructor.
+            assert.areEqual(e.constructor.toString(), SyntaxError.toString(), message);
+        }
+
+        assert.isTrue(caught, `Expected error not thrown: ${message}`);
+    } else {
+        assert.doesNotThrow(testfunc, message);
+    }
+}
+
+function testDynamicImport(importFunc, thenFunc, catchFunc, _asyncEnter, _asyncExit) {
+    var promise = importFunc();
+    assert.isTrue(promise instanceof Promise);
+    promise.then((v)=>{
+        _asyncEnter();
+        thenFunc(v);
+        _asyncExit();
+    }).catch((err)=>{
+        _asyncEnter();
+        catchFunc(err);
+        _asyncExit();
+    });
+}
+
+var tests = [
+    {
+        name: "Valid cases for import()",
+        body: function () {
+                let functionBody =
+                    `
+                    assert.doesNotThrow(function () { eval("import(undefined)"); }, "undefined");
+                    assert.doesNotThrow(function () { eval("import(null)"); }, "null");
+                    assert.doesNotThrow(function () { eval("import(true)"); }, "boolean - true");
+                    assert.doesNotThrow(function () { eval("import(false)"); }, "boolean - false");
+                    assert.doesNotThrow(function () { eval("import(1234567890)"); }, "number");
+                    assert.doesNotThrow(function () { eval("import('abc789cde')"); }, "string literal");
+                    assert.doesNotThrow(function () { eval("import('number' + 100 + 0.4 * 18)"); }, "expression");
+                    assert.doesNotThrow(function () { eval("import(import(true))"); }, "nested import");
+                    assert.doesNotThrow(function () { eval("import(import(Infinity) + import(undefined))"); }, "nested import expression");
+                    `;
+
+                testScript(functionBody, "Test importing a simple exported function");
+                testModuleScript(functionBody, "Test importing a simple exported function");
+        }
+    },
+    {
+        name: "Syntax errors for import() call",
+        body: function () {
+                let functionBody =
+                    `
+                    assert.throws(function () { eval("import()"); }, SyntaxError, "no argument");
+                    assert.throws(function () { eval("import(1, 2)"); }, SyntaxError, "more than one arguments");
+                    assert.throws(function () { eval("import('abc.js', 'def.js')"); }, SyntaxError, "more than one argument - case 2");
+                    assert.throws(function () { eval("import(...['abc.js', 'def.js'])"); }, SyntaxError, "spread argument");
+                    `;
+
+                testScript(functionBody, "Test importing a simple exported function");
+                testModuleScript(functionBody, "Test importing a simple exported function");
+        }
+    },
+    {
+        name: "Module specifier that are not string",
+        body: function () {
+            var testNonStringSpecifier = function (specifier, expectedType, expectedErrMsg, message) {
+                if (typeof message === "undefined" ) {
+                    message = specifier;
+                }
+
+                let functionBody =
+                    `testDynamicImport(
+                        ()=>{
+                            return import(${specifier});
+                        },
+                        (v)=>{
+                            assert.fail('Expected: promise rejected; actual: promise resolved: ' + '${message}');
+                        },
+                        (err)=>{
+                            assert.isTrue(err instanceof Error, '${message}');
+                            assert.areEqual(${expectedType}, err.constructor, '${message}');
+                            assert.areEqual("${expectedErrMsg}", err.message, '${message}');
+                        }, _asyncEnter, _asyncExit
+                    )`;
+
+                testScript(functionBody, "Test importing a simple exported function", false, true);
+                testModuleScript(functionBody, "Test importing a simple exported function", false, true);
+            };
+
+            testNonStringSpecifier("undefined", "URIError", "undefined");
+            testNonStringSpecifier("null", "URIError", "null");
+            testNonStringSpecifier("true", "URIError", "true");
+            testNonStringSpecifier("false", "URIError", "false");
+            testNonStringSpecifier("NaN", "URIError", "NaN");
+            testNonStringSpecifier("+0", "URIError", "0");
+            testNonStringSpecifier("-0", "URIError", "0");
+            testNonStringSpecifier("-12345", "URIError", "-12345");
+            testNonStringSpecifier("1/0", "URIError", "Infinity");
+            testNonStringSpecifier("1.123456789012345678901", "URIError", "1.1234567890123457");
+            testNonStringSpecifier("-1.123456789012345678901", "URIError", "-1.1234567890123457");
+            testNonStringSpecifier('Symbol("abc")', "TypeError", "Object doesn't support property or method 'ToString'");
+            testNonStringSpecifier("{}", "URIError", "[object Object]");
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 2 - 2
test/es6/es6_stable.baseline

@@ -31,8 +31,8 @@ FLAG ES6 = 1 - setting child flag ES6IsConcatSpreadable = 1
 FLAG ES6IsConcatSpreadable = 1
 FLAG ES6 = 1 - setting child flag ES6Math = 1
 FLAG ES6Math = 1
-FLAG ES6 = 1 - setting child flag ES6Module = 0
-FLAG ES6Module = 0
+FLAG ES6 = 1 - setting child flag ES6Module = 1
+FLAG ES6Module = 1
 FLAG ES6 = 1 - setting child flag ES6Object = 1
 FLAG ES6Object = 1
 FLAG ES6 = 1 - setting child flag ES6Number = 1

+ 2 - 2
test/es6/es6_stable.enable_disable.baseline

@@ -31,8 +31,8 @@ FLAG ES6 = 1 - setting child flag ES6IsConcatSpreadable = 1
 FLAG ES6IsConcatSpreadable = 1
 FLAG ES6 = 1 - setting child flag ES6Math = 1
 FLAG ES6Math = 1
-FLAG ES6 = 1 - setting child flag ES6Module = 0
-FLAG ES6Module = 0
+FLAG ES6 = 1 - setting child flag ES6Module = 1
+FLAG ES6Module = 1
 FLAG ES6 = 1 - setting child flag ES6Object = 1
 FLAG ES6Object = 1
 FLAG ES6 = 1 - setting child flag ES6Number = 1

+ 14 - 0
test/es6/rlexe.xml

@@ -1361,6 +1361,20 @@
         <tags>exclude_dynapogo,exclude_sanitize_address</tags>
     </default>
 </test>
+<test>
+    <default>
+        <files>dynamic-module-functionality.js</files>
+        <compile-flags>-ES6Module -args summary -endargs</compile-flags>
+        <tags>exclude_sanitize_address</tags>
+    </default>
+</test>
+<test>
+    <default>
+        <files>dynamic-module-import-specifier.js</files>
+        <compile-flags>-AsyncModuleLoad -ES6Module -args summary -endargs</compile-flags>
+        <tags>exclude_sanitize_address</tags>
+    </default>
+</test>
 <test>
     <default>
         <files>module-syntax.js</files>