Recursive module evaluation support (sync-only for now) + namespace object fixes

This commit is contained in:
Kai Tamkun
2025-05-13 19:12:29 -07:00
parent d12d0b9483
commit e2d5bfad85
4 changed files with 45 additions and 15 deletions

View File

@@ -88,6 +88,28 @@ NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObjec
return nullptr;
}
JSModuleNamespaceObject* NodeVMModule::namespaceObject(JSC::JSGlobalObject* globalObject)
{
JSModuleNamespaceObject* object = m_namespaceObject.get();
if (object) {
return object;
}
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(this)) {
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
object = thisObject->moduleRecord(globalObject)->getModuleNamespace(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (object) {
namespaceObject(vm, object);
}
} else {
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("NodeVMModule::namespaceObject called on an unsupported module type (%s)", info()->className.characters());
}
return object;
}
JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier);
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode);
JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus);
@@ -179,7 +201,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalO
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto* thisObject = jsCast<NodeVMSourceTextModule*>(callFrame->thisValue());
return JSValue::encode(thisObject->namespace_());
return JSValue::encode(thisObject->namespaceObject(globalObject));
}
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
@@ -292,7 +314,7 @@ void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
ASSERT_GC_OBJECT_INHERITS(vmModule, info());
Base::visitChildren(vmModule, visitor);
visitor.append(vmModule->m_namespace);
visitor.append(vmModule->m_namespaceObject);
visitor.append(vmModule->m_context);
auto moduleNatives = vmModule->m_resolveCache.values();

View File

@@ -2,7 +2,8 @@
#include "NodeVM.h"
#include "JavaScriptCore/AbstractModuleRecord.h"
#include "AbstractModuleRecord.h"
#include "JSModuleNamespaceObject.h"
namespace Bun {
@@ -49,8 +50,8 @@ public:
Status status() const { return m_status; }
void status(Status value) { m_status = value; }
JSObject* namespace_() const { return m_namespace.get(); }
void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); }
JSModuleNamespaceObject* namespaceObject(JSC::JSGlobalObject* globalObject);
void namespaceObject(JSC::VM& vm, JSModuleNamespaceObject* value) { m_namespaceObject.set(vm, this, value); }
const WTF::Vector<NodeVMModuleRequest>& moduleRequests() const { return m_moduleRequests; }
void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); }
@@ -67,7 +68,7 @@ public:
protected:
WTF::String m_identifier;
Status m_status = Status::Unlinked;
mutable WriteBarrier<JSObject> m_namespace;
mutable WriteBarrier<JSModuleNamespaceObject> m_namespaceObject;
mutable WriteBarrier<JSObject> m_context;
WTF::Vector<NodeVMModuleRequest> m_moduleRequests;
mutable WTF::HashMap<WTF::String, WriteBarrier<JSObject>> m_resolveCache;

View File

@@ -2,8 +2,8 @@
#include "ErrorCode.h"
#include "JSDOMExceptionHandling.h"
#include "JSModuleLoader.h"
#include "JSModuleRecord.h"
#include "JSPromise.h"
#include "JSSourceCode.h"
#include "ModuleAnalyzer.h"
#include "Parser.h"
@@ -228,7 +228,7 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec
WTF::String specifier = specifierValue.toWTFString(globalObject);
JSObject* moduleNative = moduleNativeValue.getObject();
auto* resolvedRecord = jsCast<NodeVMModule*>(moduleNative)->moduleRecord(globalObject);
AbstractModuleRecord* resolvedRecord = jsCast<NodeVMModule*>(moduleNative)->moduleRecord(globalObject);
record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord);
m_resolveCache.set(WTFMove(specifier), WriteBarrier<JSObject> { vm, this, moduleNative });
@@ -244,11 +244,9 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec
RETURN_IF_EXCEPTION(scope, {});
if (sync == Synchronousness::Async) {
ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking");
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking");
}
RETURN_IF_EXCEPTION(scope, {});
status(Status::Linked);
return JSC::jsUndefined();
}
@@ -273,7 +271,19 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t
}
auto run = [&] {
// TODO(@heimskr): top-level await support
status(Status::Evaluating);
for (const auto& request : record->requestedModules()) {
if (auto iter = m_resolveCache.find(WTF::String(*request.m_specifier)); iter != m_resolveCache.end()) {
if (auto* dependency = jsDynamicCast<NodeVMSourceTextModule*>(iter->value.get())) {
if (dependency->status() == Status::Linked) {
JSValue dependencyResult = dependency->evaluate(globalObject, timeout, breakOnSigint);
RELEASE_ASSERT_WITH_MESSAGE(jsDynamicCast<JSC::JSPromise*>(dependencyResult) == nullptr, "TODO(@heimskr): implement async support for node:vm SourceTextModule dependencies");
}
}
}
}
result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast<int32_t>(JSGenerator::ResumeMode::NormalMode)));
};

View File

@@ -9,7 +9,6 @@ const {
validateInt32,
validateBuffer,
validateFunction,
validateInternalField,
} = require("internal/validators");
const util = require("node:util");
@@ -190,7 +189,6 @@ class Module {
}
[util.inspect.custom](depth, options) {
validateInternalField(this, kNative, "Module");
if (typeof depth === "number" && depth < 0) return this;
const constructor = getConstructorOf(this) || Module;
@@ -301,7 +299,6 @@ class SourceTextModule extends Module {
const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises);
this[kNative].link(specifiers, moduleNatives, 0);
} catch (e) {
console.error("linking error:", e);
this.#error = e;
throw e;
} finally {