From 4359ef20789f68eccb50f4c1adca0331c3f4b1f6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 6 May 2025 20:24:33 -0700 Subject: [PATCH] More work on vm.Module linking --- src/bun.js/bindings/NodeVMModule.cpp | 9 +- src/bun.js/bindings/NodeVMModule.h | 2 +- .../bindings/NodeVMSourceTextModule.cpp | 150 +++++++++++++----- src/bun.js/bindings/NodeVMSourceTextModule.h | 9 +- src/js/builtins/BunBuiltinNames.h | 3 + src/js/internal/primordials.js | 4 +- src/js/node/vm.ts | 8 +- 7 files changed, 123 insertions(+), 62 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index b67e1297fa..8e530286bc 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -48,13 +48,14 @@ bool NodeVMModule::finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deq return true; } -bool NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject) +JSValue NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject) { if (auto* thisObject = jsDynamicCast(this)) { return thisObject->createModuleRecord(globalObject); } - return true; + ASSERT_NOT_REACHED(); + return JSC::jsUndefined(); } NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) @@ -217,7 +218,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject // } if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return thisObject->link(globalObject, specifiers, moduleNatives); + return JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives)); // return thisObject->link(globalObject, linker); // } else if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { // return thisObject->link(globalObject, specifiers, moduleNatives); @@ -248,7 +249,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(JSC::jsBoolean(thisObject->createModuleRecord(globalObject))); + return JSValue::encode(thisObject->createModuleRecord(globalObject)); } template diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index e911fe394f..665aa0baf4 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -57,7 +57,7 @@ public: bool finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque& stack, unsigned* dfsIndex); // Purposely not virtual. Dispatches to the correct subclass. - bool createModuleRecord(JSC::JSGlobalObject* globalObject); + JSValue createModuleRecord(JSC::JSGlobalObject* globalObject); protected: WTF::String m_identifier; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index a368077c52..6dada1bdf3 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -3,8 +3,8 @@ #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" #include "JSModuleRecord.h" - -#include +#include "ModuleAnalyzer.h" +#include "Parser.h" namespace Bun { using namespace NodeVM; @@ -71,38 +71,127 @@ void NodeVMSourceTextModule::destroy(JSCell* cell) static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); } -bool NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) +JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (m_moduleRecord) { - return false; + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module record already present"_s); + return {}; } - VM& vm = globalObject->vm(); + ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); - // ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); + ParserError parserError; + + std::unique_ptr node = parseRootNode(vm, m_sourceCode, + ImplementationVisibility::Public, + JSParserBuiltinMode::NotBuiltin, + StrictModeLexicallyScopedFeature, + JSParserScriptMode::Module, + SourceParseMode::ModuleAnalyzeMode, + parserError); + + if (parserError.isValid()) { + throwException(globalObject, scope, parserError.toErrorObject(globalObject, m_sourceCode)); + return {}; + } + + RETURN_IF_EXCEPTION(scope, {}); + ASSERT(node != nullptr); + + JSModuleRecord* moduleRecord = nullptr; + + if (auto result = analyzer.analyze(*node)) { + moduleRecord = *result; + } else { + auto [type, message] = result.error(); + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_LINK_FAILURE, message); + return {}; + } - JSModuleRecord* moduleRecord = JSModuleRecord::create(globalObject, vm, globalObject->m_moduleRecordStructure.get(globalObject), Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); m_moduleRecord.set(vm, this, moduleRecord); - - std::println("link synchronousness: {}", int(moduleRecord->link(globalObject, JSC::jsUndefined()))); + m_moduleRequests.clear(); const auto& requests = moduleRecord->requestedModules(); - std::println("requests: {}", requests.size()); - - for (const auto& request : requests) { - std::println("request: {}", request.m_specifier->utf8().data()); + if (requests.isEmpty()) { + return JSC::constructEmptyArray(globalObject, nullptr, 0); } - return true; + JSArray* requestsArray = JSC::constructEmptyArray(globalObject, nullptr, requests.size()); + + const auto& builtinNames = WebCore::clientData(vm)->builtinNames(); + const JSC::Identifier& specifierIdentifier = builtinNames.specifierPublicName(); + const JSC::Identifier& attributesIdentifier = builtinNames.attributesPublicName(); + const JSC::Identifier& hostDefinedImportTypeIdentifier = builtinNames.hostDefinedImportTypePublicName(); + + for (unsigned i = 0; i < requests.size(); ++i) { + const auto& request = requests[i]; + + JSString* specifierValue = JSC::jsString(vm, WTF::String(*request.m_specifier)); + + JSObject* requestObject = constructEmptyObject(globalObject, globalObject->objectPrototype(), 2); + requestObject->putDirect(vm, specifierIdentifier, specifierValue); + + WTF::String attributesTypeString = "unknown"_str; + + if (request.m_attributes) { + JSValue attributesType {}; + switch (request.m_attributes->type()) { + using AttributeType = decltype(request.m_attributes->type()); + using enum AttributeType; + case None: + attributesTypeString = "none"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + case JavaScript: + attributesTypeString = "javascript"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + case WebAssembly: + attributesTypeString = "webassembly"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + case JSON: + attributesTypeString = "json"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + default: + attributesType = JSC::jsNumber(static_cast(request.m_attributes->type())); + break; + } + + WTF::HashMap attributeMap { + { "type"_s, attributesTypeString }, + }; + + JSObject* attributesObject = constructEmptyObject(globalObject, globalObject->objectPrototype(), 1); + attributesObject->putDirect(vm, JSC::Identifier::fromString(vm, "type"_s), attributesType); + if (const String& hostDefinedImportType = request.m_attributes->hostDefinedImportType(); !hostDefinedImportType.isEmpty()) { + attributesObject->putDirect(vm, hostDefinedImportTypeIdentifier, JSC::jsString(vm, hostDefinedImportType)); + attributeMap.set("hostDefinedImportType"_s, hostDefinedImportType); + } + requestObject->putDirect(vm, attributesIdentifier, attributesObject); + addModuleRequest({ WTF::String(*request.m_specifier), WTFMove(attributeMap) }); + } else { + addModuleRequest({ WTF::String(*request.m_specifier), {} }); + requestObject->putDirect(vm, attributesIdentifier, JSC::jsNull()); + } + + requestsArray->putDirectIndex(globalObject, i, requestObject); + } + + return requestsArray; } -EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) +JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) { const unsigned length = specifiers->getArrayLength(); ASSERT(length == moduleNatives->getArrayLength()); if (length == 0) { - return JSC::encodedJSUndefined(); + return JSC::jsUndefined(); } VM& vm = globalObject->vm(); @@ -121,36 +210,9 @@ EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArra m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); } - return JSC::encodedJSUndefined(); + return JSC::jsUndefined(); } -// EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSValue linker) -// { -// VM& vm = globalObject->vm(); -// auto scope = DECLARE_THROW_SCOPE(vm); - -// if (status() != Status::Unlinked) { -// throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module is already linked"_s); -// return {}; -// } - -// status(Status::Linking); - -// JSModuleRecord* moduleRecord = JSModuleRecord::create(globalObject, vm, globalObject->m_moduleRecordStructure.get(globalObject), Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); - -// Synchronousness synchronousness = moduleRecord->link(globalObject, linker); - -// std::println("synchronousness: {}", int(synchronousness)); - -// if (synchronousness == Synchronousness::Sync) { -// status(Status::Linked); -// } - -// m_moduleRecord.set(vm, this, moduleRecord); - -// return JSC::encodedJSUndefined(); -// } - JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 872df10448..4dce4521cd 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -23,18 +23,15 @@ public: [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward(space); }); } + static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); static void destroy(JSC::JSCell* cell); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } - static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); - - bool createModuleRecord(JSGlobalObject* globalObject); - - EncodedJSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); - // EncodedJSValue link(JSGlobalObject* globalObject, JSValue linker); + JSValue createModuleRecord(JSGlobalObject* globalObject); + JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index 39f912a38b..9a2602ea28 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -49,6 +49,7 @@ using namespace JSC; macro(assignToStream) \ macro(associatedReadableByteStreamController) \ macro(atimeMs) \ + macro(attributes) \ macro(autoAllocateChunkSize) \ macro(backpressure) \ macro(backpressureChangePromise) \ @@ -131,6 +132,7 @@ using namespace JSC; macro(headers) \ macro(highWaterMark) \ macro(host) \ + macro(hostDefinedImportType) \ macro(hostname) \ macro(href) \ macro(httpOnly) \ @@ -233,6 +235,7 @@ using namespace JSC; macro(signal) \ macro(sink) \ macro(size) \ + macro(specifier) \ macro(start) \ macro(startAlgorithm) \ macro(startConsumingStream) \ diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index 9c9a79148d..8eb8876333 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -94,7 +94,7 @@ const arrayToSafePromiseIterable = (promises, mapFn) => ), ); const PromiseAll = Promise.all; -const PromiseResolve = Promise.resolve; +const PromiseResolve = Promise.resolve.bind(Promise); const SafePromiseAll = (promises, mapFn) => PromiseAll(arrayToSafePromiseIterable(promises, mapFn)); const SafePromiseAllReturnArrayLike = (promises, mapFn) => new Promise((resolve, reject) => { @@ -107,7 +107,7 @@ const SafePromiseAllReturnArrayLike = (promises, mapFn) => let pendingPromises = length; for (let i = 0; i < length; i++) { const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; - PromisePrototypeThen( + PromisePrototypeThen.$call( PromiseResolve(promise), result => { returnVal[i] = result; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index d117b6a34c..54f5f5f32b 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -19,7 +19,7 @@ const ObjectFreeze = Object.freeze; const ObjectDefineProperty = Object.defineProperty; const ArrayPrototypeMap = Array.prototype.map; const PromisePrototypeThen = Promise.prototype.then; -const PromiseResolve = Promise.resolve; +const PromiseResolve = Promise.resolve.bind(Promise); const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectSetPrototypeOf = Object.setPrototypeOf; @@ -260,9 +260,8 @@ class SourceTextModule extends Module { async [kLink](linker) { this.#statusOverride = "linking"; - console.log({ createModuleRecord: this[kNative].createModuleRecord() }); + const moduleRequests = this[kNative].createModuleRecord(); - const moduleRequests = this[kNative].getModuleRequests(); // Iterates the module requests and links with the linker. // Specifiers should be aligned with the moduleRequests array in order. const specifiers = Array(moduleRequests.length); @@ -275,6 +274,7 @@ class SourceTextModule extends Module { attributes, assert: attributes, }); + const modulePromise = PromisePrototypeThen.$call(PromiseResolve(linkerResult), async mod => { if (!isModule(mod)) { throw $ERR_VM_MODULE_NOT_MODULE(); @@ -303,8 +303,6 @@ class SourceTextModule extends Module { } finally { this.#statusOverride = undefined; } - - // return await this[kNative].link(linker); } get dependencySpecifiers() {