From bfca627dfa7b944b407747ac2d8adfacc262af1c Mon Sep 17 00:00:00 2001 From: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Date: Fri, 9 Aug 2024 00:33:49 -0700 Subject: [PATCH] fix `node:vm` and DOMJIT (#13185) --- src/bun.js/bindings/BunGlobalScope.cpp | 50 ++++++ src/bun.js/bindings/BunGlobalScope.h | 45 +++++ src/bun.js/bindings/BunWorkerGlobalScope.cpp | 8 +- src/bun.js/bindings/BunWorkerGlobalScope.h | 14 +- src/bun.js/bindings/NodeVM.cpp | 22 +-- src/bun.js/bindings/NodeVM.h | 14 ++ src/bun.js/bindings/ZigGlobalObject.cpp | 31 +--- src/bun.js/bindings/ZigGlobalObject.h | 32 +++- src/bun.js/bindings/webcore/JSPerformance.cpp | 9 +- src/bun.js/bindings/webcore/JSTextEncoder.cpp | 156 +++++++++--------- test/js/bun/jsc/domjit.test.ts | 55 +++++- 11 files changed, 291 insertions(+), 145 deletions(-) create mode 100644 src/bun.js/bindings/BunGlobalScope.cpp create mode 100644 src/bun.js/bindings/BunGlobalScope.h diff --git a/src/bun.js/bindings/BunGlobalScope.cpp b/src/bun.js/bindings/BunGlobalScope.cpp new file mode 100644 index 0000000000..5594cb8309 --- /dev/null +++ b/src/bun.js/bindings/BunGlobalScope.cpp @@ -0,0 +1,50 @@ + +#include "root.h" +#include "ZigGlobalObject.h" +#include "BunGlobalScope.h" +#include "JavaScriptCore/VM.h" +#include "JavaScriptCore/VMTraps.h" +#include "JavaScriptCore/VMTrapsInlines.h" +#include "JavaScriptCore/LazyClassStructure.h" +#include "JavaScriptCore/LazyClassStructureInlines.h" +#include "BunClientData.h" + +namespace Bun { + +using namespace JSC; + +void GlobalScope::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + m_encodeIntoObjectStructure.initLater( + [](const JSC::LazyProperty::Initializer& init) { + auto& vm = init.vm; + auto& globalObject = *init.owner; + Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), 2); + PropertyOffset offset; + auto clientData = WebCore::clientData(vm); + structure = Structure::addPropertyTransition(vm, structure, clientData->builtinNames().readPublicName(), 0, offset); + RELEASE_ASSERT(offset == 0); + structure = Structure::addPropertyTransition(vm, structure, clientData->builtinNames().writtenPublicName(), 0, offset); + RELEASE_ASSERT(offset == 1); + init.set(structure); + }); +} + +DEFINE_VISIT_CHILDREN(GlobalScope); + +template +void GlobalScope::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + GlobalScope* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + thisObject->m_encodeIntoObjectStructure.visit(visitor); +} + +const JSC::ClassInfo GlobalScope::s_info = { "GlobalScope"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(GlobalScope) }; + +} diff --git a/src/bun.js/bindings/BunGlobalScope.h b/src/bun.js/bindings/BunGlobalScope.h new file mode 100644 index 0000000000..1fc6f4b1ce --- /dev/null +++ b/src/bun.js/bindings/BunGlobalScope.h @@ -0,0 +1,45 @@ +#pragma once + +#include "root.h" +#include "JavaScriptCore/JSGlobalObject.h" + +namespace Bun { + +using namespace JSC; + +class GlobalScope : public JSC::JSGlobalObject { + using Base = JSC::JSGlobalObject; + +protected: + void finishCreation(JSC::VM& vm); + +public: + GlobalScope(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + GlobalScope(JSC::VM& vm, JSC::Structure* structure, const JSC::GlobalObjectMethodTable* methodTable) + : Base(vm, structure, methodTable) + { + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + JSC::Structure* encodeIntoObjectStructure() const { return m_encodeIntoObjectStructure.getInitializedOnMainThread(this); } + + /** + * WARNING: You must update visitChildrenImpl() if you add a new field. + * + * That informs the garbage collector that these fields exist. If you don't + * do that, the garbage collector will not know about these fields and will + * not trace them. This will lead to crashes and very strange behavior at runtime. + * + * For example, if you don't add the queueMicrotask functions to visitChildrenImpl(), + * those callbacks will eventually never be called anymore. But it'll work the first time! + */ + LazyProperty m_encodeIntoObjectStructure; +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWorkerGlobalScope.cpp b/src/bun.js/bindings/BunWorkerGlobalScope.cpp index f781116338..3bac99269b 100644 --- a/src/bun.js/bindings/BunWorkerGlobalScope.cpp +++ b/src/bun.js/bindings/BunWorkerGlobalScope.cpp @@ -5,17 +5,17 @@ namespace WebCore { -WTF_MAKE_ISO_ALLOCATED_IMPL(GlobalScope); +WTF_MAKE_ISO_ALLOCATED_IMPL(WorkerGlobalScope); -MessagePortChannelProvider& GlobalScope::messagePortChannelProvider() +MessagePortChannelProvider& WorkerGlobalScope::messagePortChannelProvider() { return *reinterpret_cast(&MessagePortChannelProviderImpl::singleton()); } -void GlobalScope::onDidChangeListenerImpl(EventTarget& self, const AtomString& eventType, OnDidChangeListenerKind kind) +void WorkerGlobalScope::onDidChangeListenerImpl(EventTarget& self, const AtomString& eventType, OnDidChangeListenerKind kind) { if (eventType == eventNames().messageEvent) { - auto& global = static_cast(self); + auto& global = static_cast(self); switch (kind) { case Add: if (global.m_messageEventCount == 0) { diff --git a/src/bun.js/bindings/BunWorkerGlobalScope.h b/src/bun.js/bindings/BunWorkerGlobalScope.h index e1ac3636c6..eb9a2bac8a 100644 --- a/src/bun.js/bindings/BunWorkerGlobalScope.h +++ b/src/bun.js/bindings/BunWorkerGlobalScope.h @@ -15,15 +15,15 @@ namespace WebCore { class MessagePortChannelProvider; class MessagePortChannelProviderImpl; -class GlobalScope : public RefCounted, public EventTargetWithInlineData { - WTF_MAKE_ISO_ALLOCATED(GlobalScope); +class WorkerGlobalScope : public RefCounted, public EventTargetWithInlineData { + WTF_MAKE_ISO_ALLOCATED(WorkerGlobalScope); - uint32_t m_messageEventCount{0}; + uint32_t m_messageEventCount { 0 }; static void onDidChangeListenerImpl(EventTarget&, const AtomString&, OnDidChangeListenerKind); public: - GlobalScope(ScriptExecutionContext* context) + WorkerGlobalScope(ScriptExecutionContext* context) : EventTargetWithInlineData() , m_context(context) { @@ -33,12 +33,12 @@ public: using RefCounted::deref; using RefCounted::ref; - static Ref create(ScriptExecutionContext* context) + static Ref create(ScriptExecutionContext* context) { - return adoptRef(*new GlobalScope(context)); + return adoptRef(*new WorkerGlobalScope(context)); } - ~GlobalScope() = default; + ~WorkerGlobalScope() = default; EventTargetInterface eventTargetInterface() const final { return EventTargetInterface::DOMWindowEventTargetInterfaceType; } ScriptExecutionContext* scriptExecutionContext() const final { return m_context; } diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 923b530200..a6ea55e2cc 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -26,10 +26,6 @@ #include "JSBuffer.h" #include -#include "DOMJITIDLConvert.h" -#include "DOMJITIDLType.h" -#include "DOMJITIDLTypeFilter.h" -#include "DOMJITHelpers.h" #include #include @@ -272,8 +268,8 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject auto* zigGlobal = reinterpret_cast(globalObject); JSObject* context = asObject(contextObjectValue); - auto* targetContext = JSC::JSGlobalObject::create( - vm, zigGlobal->globalObjectStructure()); + auto* targetContext = NodeVMGlobalObject::create( + vm, zigGlobal->NodeVMGlobalObjectStructure()); auto* executable = JSC::DirectEvalExecutable::create( targetContext, source, NoLexicallyScopedFeatures, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None, @@ -394,8 +390,8 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, auto* zigGlobal = reinterpret_cast(globalObject); JSObject* context = asObject(contextObjectValue); - auto* targetContext = JSC::JSGlobalObject::create( - vm, zigGlobal->globalObjectStructure()); + auto* targetContext = NodeVMGlobalObject::create( + vm, zigGlobal->NodeVMGlobalObjectStructure()); // auto proxyStructure = JSGlobalProxy::createStructure(vm, globalObject, JSC::jsNull()); // auto proxy = JSGlobalProxy::create(vm, proxyStructure); @@ -446,6 +442,11 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, return JSValue::encode(jsString(vm, url)); } +Structure* createNodeVMGlobalObjectStructure(JSC::VM& vm) +{ + return NodeVMGlobalObject::createStructure(vm, jsNull()); +} + JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); @@ -462,8 +463,8 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, } JSObject* context = asObject(contextArg); auto* zigGlobalObject = reinterpret_cast(globalObject); - auto* targetContext = JSC::JSGlobalObject::create( - vm, zigGlobalObject->globalObjectStructure()); + auto* targetContext = NodeVMGlobalObject::create( + vm, zigGlobalObject->NodeVMGlobalObjectStructure()); auto proxyStructure = zigGlobalObject->globalProxyStructure(); auto proxy = JSGlobalProxy::create(vm, proxyStructure); @@ -536,6 +537,7 @@ static const struct HashTableValue scriptPrototypeTableValues[] = { const ClassInfo NodeVMScriptPrototype::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptPrototype) }; const ClassInfo NodeVMScript::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScript) }; const ClassInfo NodeVMScriptConstructor::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptConstructor) }; +const ClassInfo NodeVMGlobalObject::s_info = { "NodeVMGlobalObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMGlobalObject) }; DEFINE_VISIT_CHILDREN(NodeVMScript); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index becaf7b4b7..4cee946713 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -12,6 +12,8 @@ namespace WebCore { +Structure* createNodeVMGlobalObjectStructure(JSC::VM&); + class NodeVMScriptConstructor final : public JSC::InternalFunction { public: using Base = JSC::InternalFunction; @@ -76,6 +78,18 @@ private: void finishCreation(JSC::VM&); }; +class NodeVMGlobalObject final : public Bun::GlobalScope { + using Base = Bun::GlobalScope; + +public: + NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + DECLARE_INFO; +}; + JSC_DECLARE_HOST_FUNCTION(vmModule_createContext); JSC_DECLARE_HOST_FUNCTION(vmModule_isContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 99e79bf339..d183cde4b8 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -58,7 +58,6 @@ #include "AddEventListenerOptions.h" #include "AsyncContextFrame.h" #include "BunClientData.h" -#include "BunClientData.h" #include "BunObject.h" #include "BunPlugin.h" #include "BunProcess.h" @@ -1050,14 +1049,14 @@ const JSC::GlobalObjectMethodTable EvalGlobalObject::s_globalObjectMethodTable = }; GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure, const JSC::GlobalObjectMethodTable* methodTable) - : JSC::JSGlobalObject(vm, structure, methodTable) + : Base(vm, structure, methodTable) , m_bunVM(Bun__getVM()) , m_constructors(makeUnique()) , m_world(WebCore::DOMWrapperWorld::create(vm, WebCore::DOMWrapperWorld::Type::Normal)) , m_worldIsNormal(true) , m_builtinInternalFunctions(vm) , m_scriptExecutionContext(new WebCore::ScriptExecutionContext(&vm, this)) - , globalEventScope(*new Bun::GlobalScope(m_scriptExecutionContext)) + , globalEventScope(*new Bun::WorkerGlobalScope(m_scriptExecutionContext)) { // m_scriptExecutionContext = globalEventScope.m_context; mockModule = Bun::JSMockModule::create(this); @@ -1067,14 +1066,14 @@ GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure, const JSC::Gl } GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure, WebCore::ScriptExecutionContextIdentifier contextId, const JSC::GlobalObjectMethodTable* methodTable) - : JSC::JSGlobalObject(vm, structure, methodTable) + : Base(vm, structure, methodTable) , m_bunVM(Bun__getVM()) , m_constructors(makeUnique()) , m_world(WebCore::DOMWrapperWorld::create(vm, WebCore::DOMWrapperWorld::Type::Normal)) , m_worldIsNormal(true) , m_builtinInternalFunctions(vm) , m_scriptExecutionContext(new WebCore::ScriptExecutionContext(&vm, this, contextId)) - , globalEventScope(*new Bun::GlobalScope(m_scriptExecutionContext)) + , globalEventScope(*new Bun::WorkerGlobalScope(m_scriptExecutionContext)) { // m_scriptExecutionContext = globalEventScope.m_context; mockModule = Bun::JSMockModule::create(this); @@ -2833,10 +2832,9 @@ void GlobalObject::finishCreation(VM& vm) Bun::NapiPrototype::createStructure(init.vm, init.owner, init.owner->objectPrototype())); }); - m_cachedGlobalObjectStructure.initLater( + m_cachedNodeVMGlobalObjectStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { - init.set( - JSC::JSGlobalObject::createStructure(init.vm, JSC::jsNull())); + init.set(WebCore::createNodeVMGlobalObjectStructure(init.vm)); }); m_cachedGlobalProxyStructure.initLater( @@ -2935,20 +2933,6 @@ void GlobalObject::finishCreation(VM& vm) init.set(registry); }); - m_encodeIntoObjectStructure.initLater( - [](const JSC::LazyProperty::Initializer& init) { - auto& vm = init.vm; - auto& globalObject = *init.owner; - Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), 2); - PropertyOffset offset; - auto clientData = WebCore::clientData(vm); - structure = Structure::addPropertyTransition(vm, structure, clientData->builtinNames().readPublicName(), 0, offset); - RELEASE_ASSERT(offset == 0); - structure = Structure::addPropertyTransition(vm, structure, clientData->builtinNames().writtenPublicName(), 0, offset); - RELEASE_ASSERT(offset == 1); - init.set(structure); - }); - m_requireFunctionUnbound.initLater( [](const JSC::LazyProperty::Initializer& init) { init.set( @@ -3527,12 +3511,11 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_asyncBoundFunctionStructure.visit(visitor); thisObject->m_bunObject.visit(visitor); - thisObject->m_cachedGlobalObjectStructure.visit(visitor); + thisObject->m_cachedNodeVMGlobalObjectStructure.visit(visitor); thisObject->m_cachedGlobalProxyStructure.visit(visitor); thisObject->m_callSiteStructure.visit(visitor); thisObject->m_commonJSModuleObjectStructure.visit(visitor); thisObject->m_cryptoObject.visit(visitor); - thisObject->m_encodeIntoObjectStructure.visit(visitor); thisObject->m_errorConstructorPrepareStackTraceInternalValue.visit(visitor); thisObject->m_esmRegistryMap.visit(visitor); thisObject->m_importMetaObjectStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 8ce91abd3b..440a071d7e 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -21,7 +21,7 @@ class ScriptExecutionContext; class DOMGuardedObject; class EventLoopTask; class DOMWrapperWorld; -class GlobalScope; +class WorkerGlobalScope; class SubtleCrypto; class EventTarget; class Performance; @@ -44,9 +44,10 @@ class InternalModuleRegistry; #include "WebCoreJSBuiltins.h" #include "headers-handwritten.h" #include "BunCommonStrings.h" +#include "BunGlobalScope.h" namespace WebCore { -class GlobalScope; +class WorkerGlobalScope; class SubtleCrypto; class EventTarget; } @@ -68,8 +69,8 @@ using DOMGuardedObjectSet = HashSet; #define ZIG_GLOBAL_OBJECT_DEFINED -class GlobalObject : public JSC::JSGlobalObject { - using Base = JSC::JSGlobalObject; +class GlobalObject : public Bun::GlobalScope { + using Base = Bun::GlobalScope; // Move this to the front for better cache locality. void* m_bunVM; @@ -242,7 +243,6 @@ public: JSC::JSMap* readableStreamNativeMap() const { return m_lazyReadableStreamPrototypeMap.getInitializedOnMainThread(this); } JSC::JSMap* requireMap() const { return m_requireMap.getInitializedOnMainThread(this); } JSC::JSMap* esmRegistryMap() const { return m_esmRegistryMap.getInitializedOnMainThread(this); } - JSC::Structure* encodeIntoObjectStructure() const { return m_encodeIntoObjectStructure.getInitializedOnMainThread(this); } JSC::Structure* callSiteStructure() const { return m_callSiteStructure.getInitializedOnMainThread(this); } @@ -264,7 +264,7 @@ public: JSObject* lazyRequireCacheObject() const { return m_lazyRequireCacheObject.getInitializedOnMainThread(this); } - Structure* globalObjectStructure() const { return m_cachedGlobalObjectStructure.getInitializedOnMainThread(this); } + Structure* NodeVMGlobalObjectStructure() const { return m_cachedNodeVMGlobalObjectStructure.getInitializedOnMainThread(this); } Structure* globalProxyStructure() const { return m_cachedGlobalProxyStructure.getInitializedOnMainThread(this); } JSObject* lazyTestModuleObject() const { return m_lazyTestModuleObject.getInitializedOnMainThread(this); } JSObject* lazyPreloadTestModuleObject() const { return m_lazyPreloadTestModuleObject.getInitializedOnMainThread(this); } @@ -314,7 +314,7 @@ public: WebCore::EventTarget& eventTarget(); WebCore::ScriptExecutionContext* m_scriptExecutionContext; - Bun::GlobalScope& globalEventScope; + Bun::WorkerGlobalScope& globalEventScope; void resetOnEachMicrotaskTick(); @@ -531,7 +531,6 @@ public: LazyProperty m_lazyReadableStreamPrototypeMap; LazyProperty m_requireMap; LazyProperty m_esmRegistryMap; - LazyProperty m_encodeIntoObjectStructure; LazyProperty m_JSArrayBufferControllerPrototype; LazyProperty m_JSHTTPSResponseControllerPrototype; LazyProperty m_JSFileSinkControllerPrototype; @@ -543,7 +542,7 @@ public: LazyProperty m_lazyTestModuleObject; LazyProperty m_lazyPreloadTestModuleObject; LazyProperty m_testMatcherUtilsObject; - LazyProperty m_cachedGlobalObjectStructure; + LazyProperty m_cachedNodeVMGlobalObjectStructure; LazyProperty m_cachedGlobalProxyStructure; LazyProperty m_commonJSModuleObjectStructure; LazyProperty m_JSSocketAddressStructure; @@ -593,6 +592,21 @@ namespace Bun { String formatStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, const WTF::String& name, const WTF::String& message, OrdinalNumber& line, OrdinalNumber& column, WTF::String& sourceURL, Vector& stackTrace, JSC::JSObject* errorInstance); +ALWAYS_INLINE void* vm(Zig::GlobalObject* globalObject) +{ + return globalObject->bunVM(); +} + +ALWAYS_INLINE void* vm(JSC::VM& vm) +{ + return WebCore::clientData(vm)->bunVM; +} + +ALWAYS_INLINE void* vm(JSC::JSGlobalObject* lexicalGlobalObject) +{ + return WebCore::clientData(lexicalGlobalObject->vm())->bunVM; +} + } #ifndef RENAMED_JSDOM_GLOBAL_OBJECT diff --git a/src/bun.js/bindings/webcore/JSPerformance.cpp b/src/bun.js/bindings/webcore/JSPerformance.cpp index d866a4e58e..fb301cd730 100644 --- a/src/bun.js/bindings/webcore/JSPerformance.cpp +++ b/src/bun.js/bindings/webcore/JSPerformance.cpp @@ -113,11 +113,10 @@ static JSC_DECLARE_HOST_FUNCTION(functionPerformanceNow); static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(functionPerformanceNowWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject*, JSPerformance*)); } -static inline JSC::EncodedJSValue functionPerformanceNowBody(JSGlobalObject* globalObject) +static inline JSC::EncodedJSValue functionPerformanceNowBody(VM& vm) { - auto* global = reinterpret_cast(globalObject); // nanoseconds to seconds - double time = static_cast(Bun__readOriginTimer(global->bunVM())); + double time = static_cast(Bun__readOriginTimer(Bun::vm(vm))); double result = time / 1000000.0; // https://github.com/oven-sh/bun/issues/5604 @@ -126,7 +125,7 @@ static inline JSC::EncodedJSValue functionPerformanceNowBody(JSGlobalObject* glo JSC_DEFINE_HOST_FUNCTION(functionPerformanceNow, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - return functionPerformanceNowBody(globalObject); + return functionPerformanceNowBody(globalObject->vm()); } JSC_DEFINE_JIT_OPERATION(functionPerformanceNowWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSPerformance* castedThis)) @@ -136,7 +135,7 @@ JSC_DEFINE_JIT_OPERATION(functionPerformanceNowWithoutTypeCheck, JSC::EncodedJSV CallFrame* callFrame = DECLARE_CALL_FRAME(vm); IGNORE_WARNINGS_END JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); - return { functionPerformanceNowBody(lexicalGlobalObject) }; + return { functionPerformanceNowBody(vm) }; } // -- end copied -- diff --git a/src/bun.js/bindings/webcore/JSTextEncoder.cpp b/src/bun.js/bindings/webcore/JSTextEncoder.cpp index 0cee5a0c67..933014df07 100644 --- a/src/bun.js/bindings/webcore/JSTextEncoder.cpp +++ b/src/bun.js/bindings/webcore/JSTextEncoder.cpp @@ -73,10 +73,10 @@ extern "C" size_t TextEncoder__encodeInto8(const LChar* stringPtr, size_t string extern "C" size_t TextEncoder__encodeInto16(const UChar* stringPtr, size_t stringLen, void* ptr, size_t len); extern "C" JSC::EncodedJSValue TextEncoder__encodeRopeString(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSString* str); -// extern "C" { -// static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsTextEncoderEncodeWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject*, JSTextEncoder*, DOMJIT::IDLJSArgumentType)); -// static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSTextEncoder* castedThis, DOMJIT::IDLJSArgumentType source, DOMJIT::IDLJSArgumentType destination)); -// } +extern "C" { +static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsTextEncoderEncodeWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject*, JSTextEncoder*, DOMJIT::IDLJSArgumentType)); +static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSTextEncoder* castedThis, DOMJIT::IDLJSArgumentType source, DOMJIT::IDLJSArgumentType destination)); +} template<> TextEncoder::EncodeIntoResult convertDictionary(JSGlobalObject& lexicalGlobalObject, JSValue value) { @@ -214,97 +214,90 @@ template<> void JSTextEncoderDOMConstructor::initializeProperties(VM& vm, JSDOMG putDirect(vm, vm.propertyNames->prototype, JSTextEncoder::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); } -// static const JSC::DOMJIT::Signature DOMJITSignatureForJSTextEncoderEncodeWithoutTypeCheck( -// jsTextEncoderEncodeWithoutTypeCheck, -// JSTextEncoder::info(), -// // https://github.com/oven-sh/bun/issues/9226 -// // It's not totally clear what the correct side effects are for this function, so we just make it conservative for now. -// JSC::DOMJIT::Effect {}, -// DOMJIT::IDLResultTypeFilter::value, -// DOMJIT::IDLArgumentTypeFilter::value); +static const JSC::DOMJIT::Signature DOMJITSignatureForJSTextEncoderEncodeWithoutTypeCheck( + jsTextEncoderEncodeWithoutTypeCheck, + JSTextEncoder::info(), + // https://github.com/oven-sh/bun/issues/9226 + // It's not totally clear what the correct side effects are for this function, so we just make it conservative for now. + JSC::DOMJIT::Effect {}, + DOMJIT::IDLResultTypeFilter::value, + DOMJIT::IDLArgumentTypeFilter::value); -// static const JSC::DOMJIT::Signature DOMJITSignatureForJSTextEncoderEncodeIntoWithoutTypeCheck( -// jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeCheck, -// JSTextEncoder::info(), +static const JSC::DOMJIT::Signature DOMJITSignatureForJSTextEncoderEncodeIntoWithoutTypeCheck( + jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeCheck, + JSTextEncoder::info(), -// JSC::DOMJIT::Effect {}, -// // JSC::DOMJIT::Effect::forReadWriteKinds(encodeIntoRead, encodeIntoWrite), -// DOMJIT::IDLResultTypeFilter::value, -// DOMJIT::IDLArgumentTypeFilter::value, -// DOMJIT::IDLArgumentTypeFilter::value); + JSC::DOMJIT::Effect {}, + // JSC::DOMJIT::Effect::forReadWriteKinds(encodeIntoRead, encodeIntoWrite), + DOMJIT::IDLResultTypeFilter::value, + DOMJIT::IDLArgumentTypeFilter::value, + DOMJIT::IDLArgumentTypeFilter::value); /* Hash table for prototype */ static const HashTableValue JSTextEncoderPrototypeTableValues[] = { { "constructor"_s, static_cast(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsTextEncoderConstructor, 0 } }, { "encoding"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, jsTextEncoder_encoding, 0 } }, - // TODO: bring these back after fix issue with globalObject pointer argument in `encodeInto` - // REPRO: - // 1. bun create docusaurus - // 2. bun ./node_modules/.bin/docusaurus build --no-minify - // https://github.com/oven-sh/bun/issues/12335 - // { "encode"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DOMJITFunction), NoIntrinsic, { HashTableValue::DOMJITFunctionType, jsTextEncoderPrototypeFunction_encode, &DOMJITSignatureForJSTextEncoderEncodeWithoutTypeCheck } }, - // { "encodeInto"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DOMJITFunction), NoIntrinsic, { HashTableValue::DOMJITFunctionType, jsTextEncoderPrototypeFunction_encodeInto, &DOMJITSignatureForJSTextEncoderEncodeIntoWithoutTypeCheck } }, - { "encode"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsTextEncoderPrototypeFunction_encode, 1 } }, - { "encodeInto"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsTextEncoderPrototypeFunction_encodeInto, 2 } }, + { "encode"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DOMJITFunction), NoIntrinsic, { HashTableValue::DOMJITFunctionType, jsTextEncoderPrototypeFunction_encode, &DOMJITSignatureForJSTextEncoderEncodeWithoutTypeCheck } }, + { "encodeInto"_s, static_cast(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DOMJITFunction), NoIntrinsic, { HashTableValue::DOMJITFunctionType, jsTextEncoderPrototypeFunction_encodeInto, &DOMJITSignatureForJSTextEncoderEncodeIntoWithoutTypeCheck } }, }; -// JSC_DEFINE_JIT_OPERATION(jsTextEncoderEncodeWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSTextEncoder* castedThis, DOMJIT::IDLJSArgumentType input)) -// { -// VM& vm = JSC::getVM(lexicalGlobalObject); -// IGNORE_WARNINGS_BEGIN("frame-address") -// CallFrame* callFrame = DECLARE_CALL_FRAME(vm); -// IGNORE_WARNINGS_END -// JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); -// auto throwScope = DECLARE_THROW_SCOPE(vm); -// JSC::EncodedJSValue res; -// String str; -// if (input->is8Bit()) { -// if (input->isRope()) { -// GCDeferralContext gcDeferralContext(vm); -// auto encodedValue = TextEncoder__encodeRopeString(lexicalGlobalObject, input); -// if (!JSC::JSValue::decode(encodedValue).isUndefined()) { -// RELEASE_AND_RETURN(throwScope, { encodedValue }); -// } -// } +JSC_DEFINE_JIT_OPERATION(jsTextEncoderEncodeWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSTextEncoder* castedThis, DOMJIT::IDLJSArgumentType input)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + IGNORE_WARNINGS_BEGIN("frame-address") + CallFrame* callFrame = DECLARE_CALL_FRAME(vm); + IGNORE_WARNINGS_END + JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSC::EncodedJSValue res; + String str; + if (input->is8Bit()) { + if (input->isRope()) { + GCDeferralContext gcDeferralContext(vm); + auto encodedValue = TextEncoder__encodeRopeString(lexicalGlobalObject, input); + if (!JSC::JSValue::decode(encodedValue).isUndefined()) { + RELEASE_AND_RETURN(throwScope, { encodedValue }); + } + } -// str = input->value(lexicalGlobalObject); -// res = TextEncoder__encode8(lexicalGlobalObject, str.span8().data(), str.length()); -// } else { -// str = input->value(lexicalGlobalObject); -// res = TextEncoder__encode16(lexicalGlobalObject, str.span16().data(), str.length()); -// } + str = input->value(lexicalGlobalObject); + res = TextEncoder__encode8(lexicalGlobalObject, str.span8().data(), str.length()); + } else { + str = input->value(lexicalGlobalObject); + res = TextEncoder__encode16(lexicalGlobalObject, str.span16().data(), str.length()); + } -// if (UNLIKELY(JSC::JSValue::decode(res).isObject() && JSC::JSValue::decode(res).getObject()->isErrorInstance())) { -// throwScope.throwException(lexicalGlobalObject, JSC::JSValue::decode(res)); -// return { encodedJSValue() }; -// } + if (UNLIKELY(JSC::JSValue::decode(res).isObject() && JSC::JSValue::decode(res).getObject()->isErrorInstance())) { + throwScope.throwException(lexicalGlobalObject, JSC::JSValue::decode(res)); + return { encodedJSValue() }; + } -// RELEASE_AND_RETURN(throwScope, { res }); -// } + RELEASE_AND_RETURN(throwScope, { res }); +} -// JSC_DEFINE_JIT_OPERATION(jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSTextEncoder* castedThis, DOMJIT::IDLJSArgumentType sourceStr, DOMJIT::IDLJSArgumentType destination)) -// { -// VM& vm = JSC::getVM(lexicalGlobalObject); -// IGNORE_WARNINGS_BEGIN("frame-address") -// CallFrame* callFrame = DECLARE_CALL_FRAME(vm); -// IGNORE_WARNINGS_END -// JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); -// String source = sourceStr->value(lexicalGlobalObject); -// size_t res = 0; -// if (!source.is8Bit()) { -// res = TextEncoder__encodeInto16(source.span16().data(), source.length(), destination->vector(), destination->byteLength()); -// } else { -// res = TextEncoder__encodeInto8(source.span8().data(), source.length(), destination->vector(), destination->byteLength()); -// } +JSC_DEFINE_JIT_OPERATION(jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSTextEncoder* castedThis, DOMJIT::IDLJSArgumentType sourceStr, DOMJIT::IDLJSArgumentType destination)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + IGNORE_WARNINGS_BEGIN("frame-address") + CallFrame* callFrame = DECLARE_CALL_FRAME(vm); + IGNORE_WARNINGS_END + JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); + String source = sourceStr->value(lexicalGlobalObject); + size_t res = 0; + if (!source.is8Bit()) { + res = TextEncoder__encodeInto16(source.span16().data(), source.length(), destination->vector(), destination->byteLength()); + } else { + res = TextEncoder__encodeInto8(source.span8().data(), source.length(), destination->vector(), destination->byteLength()); + } -// Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); -// auto* result = JSC::constructEmptyObject(vm, globalObject->encodeIntoObjectStructure()); -// result->putDirectOffset(vm, 0, JSC::jsNumber(static_cast(res))); -// result->putDirectOffset(vm, 1, JSC::jsNumber(static_cast(res >> 32))); + Bun::GlobalScope* globalScope = reinterpret_cast(lexicalGlobalObject); + auto* result = JSC::constructEmptyObject(vm, globalScope->encodeIntoObjectStructure()); + result->putDirectOffset(vm, 0, JSC::jsNumber(static_cast(res))); + result->putDirectOffset(vm, 1, JSC::jsNumber(static_cast(res >> 32))); -// return { JSValue::encode(result) }; -// } + return { JSValue::encode(result) }; +} const ClassInfo JSTextEncoderPrototype::s_info = { "TextEncoder"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTextEncoderPrototype) }; @@ -440,9 +433,8 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeIntoBody( res = TextEncoder__encodeInto8(span.data(), span.size(), destination->vector(), destination->byteLength()); } - Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); - - auto* result = JSC::constructEmptyObject(vm, globalObject->encodeIntoObjectStructure()); + Bun::GlobalScope* globalScope = reinterpret_cast(lexicalGlobalObject); + auto* result = JSC::constructEmptyObject(vm, globalScope->encodeIntoObjectStructure()); result->putDirectOffset(vm, 0, JSC::jsNumber(static_cast(res))); result->putDirectOffset(vm, 1, JSC::jsNumber(static_cast(res >> 32))); diff --git a/test/js/bun/jsc/domjit.test.ts b/test/js/bun/jsc/domjit.test.ts index fe38b3cc91..cb090b578d 100644 --- a/test/js/bun/jsc/domjit.test.ts +++ b/test/js/bun/jsc/domjit.test.ts @@ -5,11 +5,13 @@ import { describe, test, expect } from "bun:test"; import crypto from "crypto"; import { statSync } from "fs"; import { read, ptr } from "bun:ffi"; +import vm from "node:vm"; const dirStats = statSync(import.meta.dir); const buffer = new BigInt64Array(16); describe("DOMJIT", () => { + const buf = new Uint8Array(4); for (let iter of [1000, 10000, 100000, 1000000]) { test("Buffer.alloc", () => { for (let i = 0; i < iter; i++) { @@ -43,13 +45,13 @@ describe("DOMJIT", () => { }); test("TextEncoder.encodeInto", () => { for (let i = 0; i < iter; i++) { - new TextEncoder().encodeInto("test", new Uint8Array(4)); + new TextEncoder().encodeInto("test", buf); } expect(true).toBe(true); }); test("Crypto.timingSafeEqual", () => { for (let i = 0; i < iter; i++) { - crypto.timingSafeEqual(new Uint8Array(4), new Uint8Array(4)); + crypto.timingSafeEqual(buf, buf); } expect(true).toBe(true); }); @@ -61,13 +63,13 @@ describe("DOMJIT", () => { }); test("Crypto.getRandomValues", () => { for (let i = 0; i < iter; i++) { - crypto.getRandomValues(new Uint8Array(4)); + crypto.getRandomValues(buf); } expect(true).toBe(true); }); test("TextDecoder.decode", () => { for (let i = 0; i < iter; i++) { - new TextDecoder().decode(new Uint8Array(4)); + new TextDecoder().decode(buf); } expect(true).toBe(true); }); @@ -100,4 +102,49 @@ describe("DOMJIT", () => { expect(true).toBe(true); }); } + + describe("in NodeVM", () => { + const code = ` + const buf = new Uint8Array(4); + for (let iter of [100000]) { + for (let i = 0; i < iter; i++) { + performance.now(); + } + for (let i = 0; i < iter; i++) { + new TextEncoder().encode("test"); + } + for (let i = 0; i < iter; i++) { + new TextEncoder().encodeInto("test", buf); + } + for (let i = 0; i < iter; i++) { + crypto.timingSafeEqual(buf, buf); + } + for (let i = 0; i < iter; i++) { + crypto.randomUUID(); + } + for (let i = 0; i < iter; i++) { + crypto.getRandomValues(buf); + } + for (let i = 0; i < iter; i++) { + new TextDecoder().decode(buf); + } + for (let i = 0; i < iter; i++) { + dirStats.isSymbolicLink(); + dirStats.isSocket(); + dirStats.isFile(); + dirStats.isFIFO(); + dirStats.isDirectory(); + dirStats.isCharacterDevice(); + dirStats.isBlockDevice(); + } + } + "success";`; + test("Script.runInNewContext", () => { + const script = new vm.Script(code); + expect(script.runInNewContext({ crypto, performance, TextEncoder, TextDecoder, dirStats })).toBe("success"); + }, 20_000); + test("vm.runInNewContext", () => { + expect(vm.runInNewContext(code, { crypto, performance, TextEncoder, TextDecoder, dirStats })).toBe("success"); + }, 20_000); + }); });