Skip to content

Commit 2277186

Browse files
author
Suwei Chen
committed
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
1 parent 12912e6 commit 2277186

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1146
-110
lines changed

bin/NativeTests/JsRTApiTest.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,7 @@ namespace JsRTApiTest
17391739
REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
17401740
successTest.mainModule = requestModule;
17411741
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, Success_FIMC) == JsNoError);
1742+
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, Success_FIMC) == JsNoError);
17421743
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, Succes_NMRC) == JsNoError);
17431744

17441745
JsValueRef errorObject = JS_INVALID_REFERENCE;
@@ -1834,6 +1835,7 @@ namespace JsRTApiTest
18341835
REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
18351836
reentrantParseData.mainModule = requestModule;
18361837
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, ReentrantParse_FIMC) == JsNoError);
1838+
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, ReentrantParse_FIMC) == JsNoError);
18371839
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, ReentrantParse_NMRC) == JsNoError);
18381840

18391841
JsValueRef errorObject = JS_INVALID_REFERENCE;
@@ -1913,6 +1915,7 @@ namespace JsRTApiTest
19131915
REQUIRE(JsInitializeModuleRecord(nullptr, specifier, &requestModule) == JsNoError);
19141916
reentrantNoErrorParseData.mainModule = requestModule;
19151917
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleCallback, reentrantNoErrorParse_FIMC) == JsNoError);
1918+
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, reentrantNoErrorParse_FIMC) == JsNoError);
19161919
REQUIRE(JsSetModuleHostInfo(requestModule, JsModuleHostInfo_NotifyModuleReadyCallback, reentrantNoErrorParse_NMRC) == JsNoError);
19171920

19181921
JsValueRef errorObject = JS_INVALID_REFERENCE;

bin/ch/Helpers.cpp

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -139,26 +139,30 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len
139139
//
140140
if (fopen_s(&file, filename, "rb") != 0)
141141
{
142-
#ifdef _WIN32
143-
DWORD lastError = GetLastError();
144-
char16 wszBuff[512];
145-
fprintf(stderr, "Error in opening file '%s' ", filename);
146-
wszBuff[0] = 0;
147-
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
148-
nullptr,
149-
lastError,
150-
0,
151-
wszBuff,
152-
_countof(wszBuff),
153-
nullptr))
142+
if (!HostConfigFlags::flags.AsyncModuleLoadIsEnabled)
154143
{
155-
fwprintf(stderr, _u(": %s"), wszBuff);
156-
}
157-
fwprintf(stderr, _u("\n"));
144+
#ifdef _WIN32
145+
DWORD lastError = GetLastError();
146+
char16 wszBuff[512];
147+
fprintf(stderr, "Error in opening file '%s' ", filename);
148+
wszBuff[0] = 0;
149+
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
150+
nullptr,
151+
lastError,
152+
0,
153+
wszBuff,
154+
_countof(wszBuff),
155+
nullptr))
156+
{
157+
fwprintf(stderr, _u(": %s"), wszBuff);
158+
}
159+
fwprintf(stderr, _u("\n"));
158160
#elif defined(_POSIX_VERSION)
159-
fprintf(stderr, "Error in opening file: ");
160-
perror(filename);
161+
fprintf(stderr, "Error in opening file: ");
162+
perror(filename);
161163
#endif
164+
}
165+
162166
IfFailGo(E_FAIL);
163167
}
164168

bin/ch/HostConfigFlagsList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ FLAG(int, InspectMaxStringLength, "Max string length to dump in locals
1111
FLAG(BSTR, Serialized, "If source is UTF8, deserializes from bytecode file", NULL)
1212
FLAG(bool, OOPJIT, "Run JIT in a separate process", false)
1313
FLAG(bool, EnsureCloseJITServer, "JIT process will be force closed when ch is terminated", true)
14+
FLAG(bool, AsyncModuleLoad, "Silence host error output for module load failures to enable promise testing", false)
1415
#undef FLAG
1516
#endif

bin/ch/WScriptJsrt.cpp

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,22 @@ JsErrorCode WScriptJsrt::InitializeModuleInfo(JsValueRef specifier, JsModuleReco
304304
{
305305
JsErrorCode errorCode = JsNoError;
306306
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleCallback, (void*)WScriptJsrt::FetchImportedModule);
307+
307308
if (errorCode == JsNoError)
308309
{
309-
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback);
310-
}
311-
if (errorCode == JsNoError)
312-
{
313-
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier);
310+
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_FetchImportedModuleFromScriptCallback, (void*)WScriptJsrt::FetchImportedModuleFromScript);
311+
312+
if (errorCode == JsNoError)
313+
{
314+
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_NotifyModuleReadyCallback, (void*)WScriptJsrt::NotifyModuleReadyCallback);
315+
316+
if (errorCode == JsNoError)
317+
{
318+
errorCode = ChakraRTInterface::JsSetModuleHostInfo(moduleRecord, JsModuleHostInfo_HostDefined, specifier);
319+
}
320+
}
314321
}
322+
315323
IfJsrtErrorFailLogAndRetErrorCode(errorCode);
316324
return JsNoError;
317325
}
@@ -351,9 +359,10 @@ JsErrorCode WScriptJsrt::LoadModuleFromString(LPCSTR fileName, LPCSTR fileConten
351359
JsValueRef errorObject = JS_INVALID_REFERENCE;
352360

353361
// ParseModuleSource is sync, while additional fetch & evaluation are async.
362+
unsigned int fileContentLength = (fileContent == nullptr) ? 0 : (unsigned int)strlen(fileContent);
354363
errorCode = ChakraRTInterface::JsParseModuleSource(requestModule, dwSourceCookie, (LPBYTE)fileContent,
355-
(unsigned int)strlen(fileContent), JsParseModuleSourceFlags_DataIsUTF8, &errorObject);
356-
if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE)
364+
fileContentLength, JsParseModuleSourceFlags_DataIsUTF8, &errorObject);
365+
if ((errorCode != JsNoError) && errorObject != JS_INVALID_REFERENCE && fileContent != nullptr)
357366
{
358367
ChakraRTInterface::JsSetException(errorObject);
359368
return errorCode;
@@ -875,10 +884,24 @@ bool WScriptJsrt::Initialize()
875884
IfJsrtErrorFail(CreatePropertyIdFromString("console", &consoleName), false);
876885
IfJsrtErrorFail(ChakraRTInterface::JsSetProperty(global, consoleName, console, true), false);
877886

887+
IfJsrtErrorFail(InitializeModuleCallbacks(), false);
888+
878889
Error:
879890
return hr == S_OK;
880891
}
881892

893+
JsErrorCode WScriptJsrt::InitializeModuleCallbacks()
894+
{
895+
JsModuleRecord moduleRecord = JS_INVALID_REFERENCE;
896+
JsErrorCode errorCode = ChakraRTInterface::JsInitializeModuleRecord(nullptr, nullptr, &moduleRecord);
897+
if (errorCode == JsNoError)
898+
{
899+
errorCode = InitializeModuleInfo(nullptr, moduleRecord);
900+
}
901+
902+
return errorCode;
903+
}
904+
882905
bool WScriptJsrt::Uninitialize()
883906
{
884907
// moduleRecordMap is a global std::map, its destructor may access overrided
@@ -1217,7 +1240,12 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName)
12171240
hr = Helpers::LoadScriptFromFile(specifierStr.GetString(), fileContent);
12181241
if (FAILED(hr))
12191242
{
1220-
fprintf(stderr, "Couldn't load file.\n");
1243+
if (!HostConfigFlags::flags.AsyncModuleLoadIsEnabled)
1244+
{
1245+
fprintf(stderr, "Couldn't load file.\n");
1246+
}
1247+
1248+
LoadScript(nullptr, specifierStr.GetString(), nullptr, "module", true, WScriptJsrt::FinalizeFree);
12211249
}
12221250
else
12231251
{
@@ -1228,12 +1256,8 @@ HRESULT WScriptJsrt::ModuleMessage::Call(LPCSTR fileName)
12281256
return errorCode;
12291257
}
12301258

1231-
// Callback from chakracore to fetch dependent module. In the test harness,
1232-
// we are not doing any translation, just treat the specifier as fileName.
1233-
// While this call will come back directly from ParseModuleSource, the additional
1234-
// task are treated as Promise that will be executed later.
1235-
JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule,
1236-
_In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
1259+
JsErrorCode WScriptJsrt::FetchImportedModuleHelper(JsModuleRecord referencingModule,
1260+
JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord)
12371261
{
12381262
JsModuleRecord moduleRecord = JS_INVALID_REFERENCE;
12391263
AutoString specifierStr;
@@ -1267,6 +1291,27 @@ JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModu
12671291
return errorCode;
12681292
}
12691293

1294+
// Callback from chakracore to fetch dependent module. In the test harness,
1295+
// we are not doing any translation, just treat the specifier as fileName.
1296+
// While this call will come back directly from ParseModuleSource, the additional
1297+
// task are treated as Promise that will be executed later.
1298+
JsErrorCode WScriptJsrt::FetchImportedModule(_In_ JsModuleRecord referencingModule,
1299+
_In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
1300+
{
1301+
return FetchImportedModuleHelper(referencingModule, specifier, dependentModuleRecord);
1302+
}
1303+
1304+
// Callback from chakracore to fetch module dynamically during runtime. In the test harness,
1305+
// we are not doing any translation, just treat the specifier as fileName.
1306+
// While this call will come back directly from runtime script or module code, the additional
1307+
// task can be scheduled asynchronously that executed later.
1308+
JsErrorCode WScriptJsrt::FetchImportedModuleFromScript(_In_ JsSourceContext dwReferencingSourceContext,
1309+
_In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord)
1310+
{
1311+
// ch.exe assumes all imported source files are located at .
1312+
return FetchImportedModuleHelper(nullptr, specifier, dependentModuleRecord);
1313+
}
1314+
12701315
// Callback from chakraCore when the module resolution is finished, either successfuly or unsuccessfully.
12711316
JsErrorCode WScriptJsrt::NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar)
12721317
{

bin/ch/WScriptJsrt.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ class WScriptJsrt
5353
static void PushMessage(MessageBase *message) { messageQueue->InsertSorted(message); }
5454

5555
static JsErrorCode FetchImportedModule(_In_ JsModuleRecord referencingModule, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
56+
static JsErrorCode FetchImportedModuleFromScript(_In_ DWORD_PTR dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
5657
static JsErrorCode NotifyModuleReadyCallback(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar);
58+
static JsErrorCode InitializeModuleCallbacks();
5759
static void CALLBACK PromiseContinuationCallback(JsValueRef task, void *callbackState);
5860

5961
static LPCWSTR ConvertErrorCodeToMessage(JsErrorCode errorCode)
@@ -116,6 +118,8 @@ class WScriptJsrt
116118
static JsValueRef CALLBACK LoadTextFileCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
117119
static JsValueRef CALLBACK FlagCallback(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState);
118120

121+
static JsErrorCode FetchImportedModuleHelper(JsModuleRecord referencingModule, JsValueRef specifier, __out JsModuleRecord* dependentModuleRecord);
122+
119123
static MessageQueue *messageQueue;
120124
static DWORD_PTR sourceContext;
121125
static std::map<std::string, JsModuleRecord> moduleRecordMap;

lib/Backend/JnHelperMethodList.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ HELPERCALL(SetHomeObj, Js::JavascriptOperators::OP_SetHomeObj,
507507
HELPERCALL(LdHomeObjProto, Js::JavascriptOperators::OP_LdHomeObjProto, 0)
508508
HELPERCALL(LdFuncObjProto, Js::JavascriptOperators::OP_LdFuncObjProto, 0)
509509

510+
HELPERCALL(ImportCall, Js::JavascriptOperators::OP_ImportCall, 0)
511+
510512
HELPERCALL(ResumeYield, Js::JavascriptOperators::OP_ResumeYield, AttrCanThrow)
511513

512514
#include "ExternalHelperMethodList.h"

lib/Backend/Lower.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2844,6 +2844,20 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
28442844
break;
28452845
}
28462846

2847+
case Js::OpCode::ImportCall:
2848+
{
2849+
IR::Opnd *src1Opnd = instr->UnlinkSrc1();
2850+
IR::Opnd *functionObjOpnd = nullptr;
2851+
m_lowererMD.LoadFunctionObjectOpnd(instr, functionObjOpnd);
2852+
2853+
LoadScriptContext(instr);
2854+
m_lowererMD.LoadHelperArgument(instr, src1Opnd);
2855+
m_lowererMD.LoadHelperArgument(instr, functionObjOpnd);
2856+
m_lowererMD.ChangeToHelperCall(instr, IR::HelperImportCall);
2857+
2858+
break;
2859+
}
2860+
28472861
case Js::OpCode::SetComputedNameVar:
28482862
{
28492863
IR::Opnd *src2Opnd = instr->UnlinkSrc2();

lib/Common/ConfigFlagsList.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ PHASE(All)
532532
// If ES6Module needs to be disabled by compile flag, DEFAULT_CONFIG_ES6Module should be false
533533
#define DEFAULT_CONFIG_ES6Module (false)
534534
#else
535-
#define DEFAULT_CONFIG_ES6Module (false)
535+
#define DEFAULT_CONFIG_ES6Module (true)
536536
#endif
537537
#define DEFAULT_CONFIG_ES6Object (true)
538538
#define DEFAULT_CONFIG_ES6Number (true)
@@ -998,10 +998,7 @@ FLAGPR (Boolean, ES6, ES7TrailingComma , "Enable ES7 trailing co
998998
FLAGPR (Boolean, ES6, ES6IsConcatSpreadable , "Enable ES6 isConcatSpreadable Symbol" , DEFAULT_CONFIG_ES6IsConcatSpreadable)
999999
FLAGPR (Boolean, ES6, ES6Math , "Enable ES6 Math extensions" , DEFAULT_CONFIG_ES6Math)
10001000

1001-
#ifndef COMPILE_DISABLE_ES6Module
1002-
#define COMPILE_DISABLE_ES6Module 0
1003-
#endif
1004-
FLAGPR_REGOVR_EXP(Boolean, ES6, ES6Module , "Enable ES6 Modules" , DEFAULT_CONFIG_ES6Module)
1001+
FLAGPR (Boolean, ES6, ES6Module , "Enable ES6 Modules" , DEFAULT_CONFIG_ES6Module)
10051002
FLAGPR (Boolean, ES6, ES6Object , "Enable ES6 Object extensions" , DEFAULT_CONFIG_ES6Object)
10061003
FLAGPR (Boolean, ES6, ES6Number , "Enable ES6 Number extensions" , DEFAULT_CONFIG_ES6Number)
10071004
FLAGPR (Boolean, ES6, ES6ObjectLiterals , "Enable ES6 Object literal extensions" , DEFAULT_CONFIG_ES6ObjectLiterals)

lib/Common/Memory/ArenaAllocator.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ namespace Memory
2121
#define Adelete(alloc, obj) AllocatorDelete(ArenaAllocator, alloc, obj)
2222
#define AdeletePlus(alloc, size, obj) AllocatorDeletePlus(ArenaAllocator, alloc, size, obj)
2323
#define AdeleteArray(alloc, count, obj) AllocatorDeleteArray(ArenaAllocator, alloc, count, obj)
24+
#define AdeleteUnlessNull(alloc, obj) \
25+
if (obj != nullptr) \
26+
{ \
27+
Adelete(alloc, obj); \
28+
obj = nullptr; \
29+
}
2430

2531

2632
#define AnewNoThrow(alloc,T,...) AllocatorNewNoThrow(ArenaAllocator, alloc, T, __VA_ARGS__)

lib/Jsrt/ChakraCore.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ typedef enum JsModuleHostInfoKind
3838
JsModuleHostInfo_Exception = 0x01,
3939
JsModuleHostInfo_HostDefined = 0x02,
4040
JsModuleHostInfo_NotifyModuleReadyCallback = 0x3,
41-
JsModuleHostInfo_FetchImportedModuleCallback = 0x4
41+
JsModuleHostInfo_FetchImportedModuleCallback = 0x4,
42+
JsModuleHostInfo_FetchImportedModuleFromScriptCallback = 0x5
4243
} JsModuleHostInfoKind;
4344

4445
/// <summary>
@@ -70,6 +71,21 @@ typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleCallBack)(_In_ JsModule
7071
/// <returns>
7172
/// true if the operation succeeded, false otherwise.
7273
/// </returns>
74+
typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleFromScriptCallBack)(_In_ JsSourceContext dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord);
75+
76+
/// <summary>
77+
/// User implemented callback to get notification when the module is ready.
78+
/// </summary>
79+
/// <remarks>
80+
/// Notify the host after ModuleDeclarationInstantiation step (15.2.1.1.6.4) is finished. If there was error in the process, exceptionVar
81+
/// holds the exception. Otherwise the referencingModule is ready and the host should schedule execution afterwards.
82+
/// </remarks>
83+
/// <param name="dwReferencingSourceContext">The referencing script that calls import()</param>
84+
/// <param name="exceptionVar">If nullptr, the module is successfully initialized and host should queue the execution job
85+
/// otherwise it's the exception object.</param>
86+
/// <returns>
87+
/// true if the operation succeeded, false otherwise.
88+
/// </returns>
7389
typedef JsErrorCode(CHAKRA_CALLBACK * NotifyModuleReadyCallback)(_In_opt_ JsModuleRecord referencingModule, _In_opt_ JsValueRef exceptionVar);
7490

7591
/// <summary>

0 commit comments

Comments
 (0)