diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/bun.js/bindings/JSNextTickQueue.cpp | 97 | ||||
| -rw-r--r-- | src/bun.js/bindings/JSNextTickQueue.h | 41 | ||||
| -rw-r--r-- | src/bun.js/bindings/Process.cpp | 98 | ||||
| -rw-r--r-- | src/bun.js/bindings/Process.lut.h | 2 | ||||
| -rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 124 | ||||
| -rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 5 | ||||
| -rw-r--r-- | src/bun.js/bindings/bindings.cpp | 3 | ||||
| -rw-r--r-- | src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h | 1 | ||||
| -rw-r--r-- | src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 2 | ||||
| -rw-r--r-- | src/bun.js/event_loop.zig | 15 | ||||
| -rw-r--r-- | src/bun_js.zig | 2 | ||||
| -rw-r--r-- | src/js/builtins/ProcessObjectInternals.ts | 202 | ||||
| -rw-r--r-- | src/js/out/WebCoreJSBuiltins.cpp | 8 | ||||
| -rw-r--r-- | src/js/out/WebCoreJSBuiltins.h | 11 | 
14 files changed, 532 insertions, 79 deletions
| diff --git a/src/bun.js/bindings/JSNextTickQueue.cpp b/src/bun.js/bindings/JSNextTickQueue.cpp new file mode 100644 index 000000000..8916ef6c8 --- /dev/null +++ b/src/bun.js/bindings/JSNextTickQueue.cpp @@ -0,0 +1,97 @@ +#include "root.h" + +#include "JavaScriptCore/JSCJSValueInlines.h" +#include "JavaScriptCore/JSInternalPromise.h" +#include "JavaScriptCore/LazyPropertyInlines.h" +#include <JavaScriptCore/Weak.h> +#include <JavaScriptCore/GetterSetter.h> + +#include "JSNextTickQueue.h" +#include <JavaScriptCore/JSGlobalObject.h> +#include <JavaScriptCore/Structure.h> +#include <JavaScriptCore/JSInternalFieldObjectImplInlines.h> +#include "ExtendedDOMClientIsoSubspaces.h" +#include "ExtendedDOMIsoSubspaces.h" +#include "BunClientData.h" + +namespace Bun { + +using namespace JSC; + +const JSC::ClassInfo JSNextTickQueue::s_info = { "JSNextTickQueue"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNextTickQueue) }; + +template<typename, JSC::SubspaceAccess mode> +JSC::GCClient::IsoSubspace* JSNextTickQueue::subspaceFor(JSC::VM& vm) +{ +    return WebCore::subspaceForImpl<JSNextTickQueue, WebCore::UseCustomHeapCellType::No>( +        vm, +        [](auto& spaces) { return spaces.m_clientSubspaceForJSNextTickQueue.get(); }, +        [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNextTickQueue = std::forward<decltype(space)>(space); }, +        [](auto& spaces) { return spaces.m_subspaceForJSNextTickQueue.get(); }, +        [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNextTickQueue = std::forward<decltype(space)>(space); }); +} + +JSNextTickQueue* JSNextTickQueue::create(VM& vm, Structure* structure) +{ +    JSNextTickQueue* mod = new (NotNull, allocateCell<JSNextTickQueue>(vm)) JSNextTickQueue(vm, structure); +    return mod; +} +Structure* JSNextTickQueue::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ +    return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); +} + +JSNextTickQueue::JSNextTickQueue(VM& vm, Structure* structure) +    : Base(vm, structure) +{ +} + +void JSNextTickQueue::finishCreation(VM& vm) +{ +    Base::finishCreation(vm); +} + +template<typename Visitor> +void JSNextTickQueue::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ +    auto* thisObject = jsCast<JSNextTickQueue*>(cell); +    ASSERT_GC_OBJECT_INHERITS(thisObject, info()); +    Base::visitChildren(thisObject, visitor); +} + +DEFINE_VISIT_CHILDREN(JSNextTickQueue); + +JSNextTickQueue* JSNextTickQueue::create(JSC::JSGlobalObject* globalObject) +{ +    auto& vm = globalObject->vm(); +    auto* obj = create(vm, createStructure(vm, globalObject, jsNull())); +    obj->finishCreation(vm); +    return obj; +} + +bool JSNextTickQueue::isEmpty() +{ +    return !internalField(0) || internalField(0).get().asNumber() == 0; +} + +void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ +    bool mustResetContext = false; +    if (isEmpty()) { +        vm.drainMicrotasks(); +        mustResetContext = true; +    } + +    if (!isEmpty()) { +        if (mustResetContext) { +            globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); +        } +        auto* drainFn = internalField(2).get().getObject(); + +        auto throwScope = DECLARE_THROW_SCOPE(vm); +        MarkedArgumentBuffer drainArgs; +        JSC::call(globalObject, drainFn, drainArgs, "Failed to drain next tick queue"_s); +    } +} + +}
\ No newline at end of file diff --git a/src/bun.js/bindings/JSNextTickQueue.h b/src/bun.js/bindings/JSNextTickQueue.h new file mode 100644 index 000000000..c3bd228cc --- /dev/null +++ b/src/bun.js/bindings/JSNextTickQueue.h @@ -0,0 +1,41 @@ +#include "root.h" +#include "headers-handwritten.h" + +#include "JavaScriptCore/JSCInlines.h" +#include "BunClientData.h" +#include <JavaScriptCore/JSInternalFieldObjectImpl.h> + +namespace Bun { +using namespace JSC; + +class JSNextTickQueue : public JSC::JSInternalFieldObjectImpl<3> { +public: +    static constexpr unsigned numberOfInternalFields = 3; +    using Base = JSC::JSInternalFieldObjectImpl<3>; + +    template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + +    JS_EXPORT_PRIVATE static JSNextTickQueue* create(VM&, Structure*); +    static JSNextTickQueue* create(JSC::JSGlobalObject* globalObject); +    static JSNextTickQueue* createWithInitialValues(VM&, Structure*); +    static Structure* createStructure(VM&, JSGlobalObject*, JSValue); + +    static std::array<JSValue, numberOfInternalFields> initialValues() +    { +        return { { +            jsNumber(-1), +            jsUndefined(), +            jsUndefined(), +        } }; +    } + +    DECLARE_EXPORT_INFO; +    DECLARE_VISIT_CHILDREN; + +    JSNextTickQueue(JSC::VM&, JSC::Structure*); +    void finishCreation(JSC::VM&); + +    bool isEmpty(); +    void drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject); +}; +}
\ No newline at end of file diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp index 5c9c03dd2..252d00075 100644 --- a/src/bun.js/bindings/Process.cpp +++ b/src/bun.js/bindings/Process.cpp @@ -19,6 +19,7 @@  #include <termios.h>  #include <errno.h>  #include <sys/ioctl.h> +#include "JSNextTickQueue.h"  #pragma mark - Node.js Process @@ -160,63 +161,6 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionInternalGetWindowSize,      return JSC::JSValue::encode(jsBoolean(true));  } -JSC_DEFINE_HOST_FUNCTION(Process_functionNextTick, -    (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ -    JSC::VM& vm = globalObject->vm(); -    auto argCount = callFrame->argumentCount(); -    if (argCount == 0) { -        auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); -        JSC::throwTypeError(globalObject, scope, "nextTick requires 1 argument (a function)"_s); -        return JSC::JSValue::encode(JSC::JSValue {}); -    } - -    JSC::JSValue job = callFrame->uncheckedArgument(0); - -    if (!job.isObject() || !job.getObject()->isCallable()) { -        auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); -        JSC::throwTypeError(globalObject, scope, "nextTick expects a function"_s); -        return JSC::JSValue::encode(JSC::JSValue {}); -    } - -    Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject); -    JSC::JSValue asyncContextValue = globalObject->m_asyncContextData.get()->getInternalField(0); - -    switch (callFrame->argumentCount()) { -    case 1: { -        global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, JSC::JSValue {}, JSC::JSValue {}); -        break; -    } -    case 2: { -        global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, callFrame->uncheckedArgument(1), JSC::JSValue {}); -        break; -    } -    case 3: { -        global->queueMicrotask(global->performMicrotaskFunction(), job, asyncContextValue, callFrame->uncheckedArgument(1), callFrame->uncheckedArgument(2)); -        break; -    } -    default: { -        JSC::JSArray* args = JSC::constructEmptyArray(globalObject, nullptr, argCount - 1); -        if (UNLIKELY(!args)) { -            auto scope = DECLARE_THROW_SCOPE(vm); -            throwVMError(globalObject, scope, createOutOfMemoryError(globalObject)); -            return JSC::JSValue::encode(JSC::JSValue {}); -        } - -        for (unsigned i = 1; i < argCount; i++) { -            args->putDirectIndex(globalObject, i - 1, callFrame->uncheckedArgument(i)); -        } - -        global->queueMicrotask( -            global->performMicrotaskVariadicFunction(), job, args, asyncContextValue, JSC::JSValue {}); - -        break; -    } -    } - -    return JSC::JSValue::encode(jsUndefined()); -} -  JSC_DECLARE_HOST_FUNCTION(Process_functionDlopen);  JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen,      (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame)) @@ -279,7 +223,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen,          }      } -    JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject* globalObject, +    JSC::EncodedJSValue (*napi_register_module_v1)(JSC::JSGlobalObject * globalObject,          JSC::EncodedJSValue exports);      napi_register_module_v1 = reinterpret_cast<JSC::EncodedJSValue (*)(JSC::JSGlobalObject*, @@ -1533,6 +1477,42 @@ static JSValue constructMemoryUsage(VM& vm, JSObject* processObject)      return memoryUsage;  } +JSC_DEFINE_HOST_FUNCTION(jsFunctionReportUncaughtException, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ +    JSValue arg0 = callFrame->argument(0); +    Bun__reportUnhandledError(globalObject, JSValue::encode(arg0)); +    return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionDrainMicrotaskQueue, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ +    globalObject->vm().drainMicrotasks(); +    return JSValue::encode(jsUndefined()); +} + +static JSValue constructProcessNextTickFn(VM& vm, JSObject* processObject) +{ +    JSGlobalObject* lexicalGlobalObject = processObject->globalObject(); +    Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(lexicalGlobalObject); +    JSValue nextTickQueueObject; +    if (!globalObject->m_nextTickQueue) { +        Bun::JSNextTickQueue* queue = Bun::JSNextTickQueue::create(globalObject); +        globalObject->m_nextTickQueue.set(vm, globalObject, queue); +        nextTickQueueObject = queue; +    } else { +        nextTickQueueObject = jsCast<Bun::JSNextTickQueue*>(globalObject->m_nextTickQueue.get()); +    } + +    JSC::JSFunction* initializer = JSC::JSFunction::create(vm, processObjectInternalsInitializeNextTickQueueCodeGenerator(vm), lexicalGlobalObject); +    JSC::MarkedArgumentBuffer args; +    args.append(processObject); +    args.append(nextTickQueueObject); +    args.append(JSC::JSFunction::create(vm, globalObject, 1, String(), jsFunctionDrainMicrotaskQueue, ImplementationVisibility::Private)); +    args.append(JSC::JSFunction::create(vm, globalObject, 1, String(), jsFunctionReportUncaughtException, ImplementationVisibility::Private)); + +    return JSC::call(globalObject, initializer, JSC::getCallData(initializer), globalObject->globalThis(), args); +} +  static JSValue constructFeatures(VM& vm, JSObject* processObject)  {      // { @@ -1742,7 +1722,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionKill,    mainModule                       JSBuiltin                                ReadOnly|Builtin|Accessor|Function 0    memoryUsage                      constructMemoryUsage                     PropertyCallback    moduleLoadList                   Process_stubEmptyArray                   PropertyCallback -  nextTick                         Process_functionNextTick                 Function 1 +  nextTick                         constructProcessNextTickFn               PropertyCallback    openStdin                        Process_functionOpenStdin                Function 0    pid                              constructPid                             PropertyCallback    platform                         constructPlatform                        PropertyCallback diff --git a/src/bun.js/bindings/Process.lut.h b/src/bun.js/bindings/Process.lut.h index 3f2d9255d..4086fb19e 100644 --- a/src/bun.js/bindings/Process.lut.h +++ b/src/bun.js/bindings/Process.lut.h @@ -179,7 +179,7 @@ static const struct HashTableValue processObjectTableValues[62] = {     { "mainModule"_s, ((static_cast<unsigned>(PropertyAttribute::ReadOnly|PropertyAttribute::Builtin|PropertyAttribute::Accessor|PropertyAttribute::Function)) & ~PropertyAttribute::Function) | PropertyAttribute::Builtin, NoIntrinsic, { HashTableValue::BuiltinGeneratorType, processObjectMainModuleCodeGenerator, 0 } },     { "memoryUsage"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructMemoryUsage } },     { "moduleLoadList"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, Process_stubEmptyArray } }, -   { "nextTick"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionNextTick, 1 } }, +   { "nextTick"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructProcessNextTickFn } },     { "openStdin"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionOpenStdin, 0 } },     { "pid"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructPid } },     { "platform"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructPlatform } }, diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 7edbd42e6..286084b4d 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -130,6 +130,7 @@  #endif  #include "BunObject.h" +#include "JSNextTickQueue.h"  using namespace Bun; @@ -294,7 +295,7 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c  }  extern "C" void* Bun__getVM(); -extern "C" JSGlobalObject* Bun__getDefaultGlobal(); +extern "C" Zig::GlobalObject* Bun__getDefaultGlobal();  // Error.captureStackTrace may cause computeErrorInfo to be called twice  // Rather than figure out the plumbing in JSC, we just skip the next call @@ -432,6 +433,44 @@ static String computeErrorInfo(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsi      return computeErrorInfoWithoutPrepareStackTrace(vm, stackTrace, line, column, sourceURL, errorInstance);  } +static void resetOnEachMicrotaskTick(JSC::VM& vm, Zig::GlobalObject* globalObject); + +static void checkIfNextTickWasCalledDuringMicrotask(JSC::VM& vm) +{ +    auto* globalObject = Bun__getDefaultGlobal(); +    if (auto nextTickQueueValue = globalObject->m_nextTickQueue.get()) { +        auto* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueueValue); +        resetOnEachMicrotaskTick(vm, globalObject); +        queue->drain(vm, globalObject); +    } +} + +static void cleanupAsyncHooksData(JSC::VM& vm) +{ +    auto* globalObject = Bun__getDefaultGlobal(); +    globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); +    globalObject->asyncHooksNeedsCleanup = false; +    if (!globalObject->m_nextTickQueue) { +        vm.setOnEachMicrotaskTick(&checkIfNextTickWasCalledDuringMicrotask); +        checkIfNextTickWasCalledDuringMicrotask(vm); +    } else { +        vm.setOnEachMicrotaskTick(nullptr); +    } +} + +static void resetOnEachMicrotaskTick(JSC::VM& vm, Zig::GlobalObject* globalObject) +{ +    if (globalObject->asyncHooksNeedsCleanup) { +        vm.setOnEachMicrotaskTick(&cleanupAsyncHooksData); +    } else { +        if (globalObject->m_nextTickQueue) { +            vm.setOnEachMicrotaskTick(nullptr); +        } else { +            vm.setOnEachMicrotaskTick(&checkIfNextTickWasCalledDuringMicrotask); +        } +    } +} +  extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client, int32_t executionContextId, bool miniMode, void* worker_ptr)  {      auto heapSize = miniMode ? JSC::HeapType::Small : JSC::HeapType::Large; @@ -479,6 +518,16 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(void* console_client,      JSC::gcProtect(globalObject); +    vm.setOnEachMicrotaskTick([](JSC::VM& vm) -> void { +        auto* globalObject = Bun__getDefaultGlobal(); +        if (auto nextTickQueue = globalObject->m_nextTickQueue.get()) { +            resetOnEachMicrotaskTick(vm, globalObject); +            Bun::JSNextTickQueue* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueue); +            queue->drain(vm, globalObject); +            return; +        } +    }); +      vm.ref();      return globalObject;  } @@ -1115,6 +1164,17 @@ JSC_DEFINE_HOST_FUNCTION(functionSetTimeout,          return JSC::JSValue::encode(JSC::JSValue {});      } +#ifdef BUN_DEBUG +    /** View the file name of the JS file that called this function +     * from a debugger */ +    SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); +    const char* fileName = sourceOrigin.string().utf8().data(); +    static const char* lastFileName = nullptr; +    if (lastFileName != fileName) { +        lastFileName = fileName; +    } +#endif +      return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));  } @@ -1166,6 +1226,17 @@ JSC_DEFINE_HOST_FUNCTION(functionSetInterval,          return JSC::JSValue::encode(JSC::JSValue {});      } +#ifdef BUN_DEBUG +    /** View the file name of the JS file that called this function +     * from a debugger */ +    SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); +    const char* fileName = sourceOrigin.string().utf8().data(); +    static const char* lastFileName = nullptr; +    if (lastFileName != fileName) { +        lastFileName = fileName; +    } +#endif +      return Bun__Timer__setInterval(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments));  } @@ -1182,6 +1253,17 @@ JSC_DEFINE_HOST_FUNCTION(functionClearInterval,      JSC::JSValue num = callFrame->argument(0); +#ifdef BUN_DEBUG +    /** View the file name of the JS file that called this function +     * from a debugger */ +    SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); +    const char* fileName = sourceOrigin.string().utf8().data(); +    static const char* lastFileName = nullptr; +    if (lastFileName != fileName) { +        lastFileName = fileName; +    } +#endif +      return Bun__Timer__clearInterval(globalObject, JSC::JSValue::encode(num));  } @@ -1198,6 +1280,17 @@ JSC_DEFINE_HOST_FUNCTION(functionClearTimeout,      JSC::JSValue num = callFrame->argument(0); +#ifdef BUN_DEBUG +    /** View the file name of the JS file that called this function +     * from a debugger */ +    SourceOrigin sourceOrigin = callFrame->callerSourceOrigin(vm); +    const char* fileName = sourceOrigin.string().utf8().data(); +    static const char* lastFileName = nullptr; +    if (lastFileName != fileName) { +        lastFileName = fileName; +    } +#endif +      return Bun__Timer__clearTimeout(globalObject, JSC::JSValue::encode(num));  } @@ -1393,12 +1486,6 @@ JSC_DEFINE_HOST_FUNCTION(functionCallback, (JSC::JSGlobalObject * globalObject,      return JSC::JSValue::encode(JSC::call(globalObject, callback, callData, JSC::jsUndefined(), JSC::MarkedArgumentBuffer()));  } -static void cleanupAsyncHooksData(JSC::VM& vm) -{ -    vm.setOnEachMicrotaskTick(nullptr); -    Bun__getDefaultGlobal()->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); -} -  // $lazy("async_hooks").cleanupLater  JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))  { @@ -1406,7 +1493,9 @@ JSC_DEFINE_HOST_FUNCTION(asyncHooksCleanupLater, (JSC::JSGlobalObject * globalOb      // - nobody else uses setOnEachMicrotaskTick      // - this is called by js if we set async context in a way we may not clear it      // - AsyncLocalStorage.prototype.run cleans up after itself and does not call this cb -    globalObject->vm().setOnEachMicrotaskTick(&cleanupAsyncHooksData); +    auto* global = jsCast<Zig::GlobalObject*>(globalObject); +    global->asyncHooksNeedsCleanup = true; +    resetOnEachMicrotaskTick(globalObject->vm(), global);      return JSC::JSValue::encode(JSC::jsUndefined());  } @@ -3955,6 +4044,23 @@ extern "C" bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* g  #endif  } +void GlobalObject::drainMicrotasks() +{ +    auto& vm = this->vm(); +    if (auto nextTickQueue = this->m_nextTickQueue.get()) { +        Bun::JSNextTickQueue* queue = jsCast<Bun::JSNextTickQueue*>(nextTickQueue); +        queue->drain(vm, this); +        return; +    } + +    vm.drainMicrotasks(); +} + +extern "C" void JSC__JSGlobalObject__drainMicrotasks(Zig::GlobalObject* globalObject) +{ +    globalObject->drainMicrotasks(); +} +  template<typename Visitor>  void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)  { @@ -4008,6 +4114,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)      visitor.append(thisObject->m_JSWebSocketSetterValue);      visitor.append(thisObject->m_JSWorkerSetterValue); +    visitor.append(thisObject->m_nextTickQueue); +      thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor);      thisObject->m_JSBufferListClassStructure.visit(visitor);      thisObject->m_JSFFIFunctionStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 029f90132..e622016de 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -296,6 +296,8 @@ public:          return m_processEnvObject.getInitializedOnMainThread(this);      } +    void drainMicrotasks(); +      void handleRejectedPromises();      void initGeneratedLazyClasses(); @@ -363,6 +365,8 @@ public:          return func;      } +    bool asyncHooksNeedsCleanup = false; +      /**       * WARNING: You must update visitChildrenImpl() if you add a new field.       * @@ -381,6 +385,7 @@ public:      mutable WriteBarrier<JSFunction> m_readableStreamToText;      mutable WriteBarrier<JSFunction> m_readableStreamToFormData; +    mutable WriteBarrier<Unknown> m_nextTickQueue;      mutable WriteBarrier<Unknown> m_BunCommonJSModuleValue;      mutable WriteBarrier<Unknown> m_JSBroadcastChannelSetterValue;      mutable WriteBarrier<Unknown> m_JSBufferSetterValue; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index ca072f2b1..6413e0470 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2527,7 +2527,6 @@ JSC__JSInternalPromise*  JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject,      const BunString* arg1)  { -    globalObject->vm().drainMicrotasks();      auto name = Bun::toWTFString(*arg1);      name.impl()->ref(); @@ -2546,9 +2545,7 @@ JSC__JSModuleLoader__loadAndEvaluateModule(JSC__JSGlobalObject* globalObject,                  JSC::JSInternalPromise::rejectedPromise(globalObject, callFrame->argument(0)));          }); -    globalObject->vm().drainMicrotasks();      auto result = promise->then(globalObject, resolverFunction, rejecterFunction); -    globalObject->vm().drainMicrotasks();      // if (promise->status(globalObject->vm()) ==      // JSC::JSPromise::Status::Fulfilled) { diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 4c09df6a5..e21a62bf8 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -42,6 +42,7 @@ public:      std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject;      std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInternalModuleRegistry;      std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBunInspectorConnection; +    std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNextTickQueue;  #include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"      /* --- bun --- */ diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 2b834cf3c..806aa4454 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -42,7 +42,7 @@ public:      std::unique_ptr<IsoSubspace> m_subspaceForProcessObject;      std::unique_ptr<IsoSubspace> m_subspaceForInternalModuleRegistry;      std::unique_ptr<IsoSubspace> m_subspaceForBunInspectorConnection; - +    std::unique_ptr<IsoSubspace> m_subspaceForJSNextTickQueue;  #include "ZigGeneratedClasses+DOMIsoSubspaces.h"      /*-- BUN --*/ diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 640a9276c..f1367c239 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -529,14 +529,14 @@ pub const EventLoop = struct {              this.virtual_machine.event_loop_handle.?.tick();          }      } - -    pub fn drainMicrotasksWithVM(this: *EventLoop, vm: *JSC.VM) void { -        vm.drainMicrotasks(); +    extern fn JSC__JSGlobalObject__drainMicrotasks(*JSC.JSGlobalObject) void; +    fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalObject) void { +        JSC__JSGlobalObject__drainMicrotasks(globalObject);          this.drainDeferredTasks();      }      pub fn drainMicrotasks(this: *EventLoop) void { -        this.drainMicrotasksWithVM(this.global.vm()); +        this.drainMicrotasksWithGlobal(this.global);      }      pub fn ensureAliveForOneTick(this: *EventLoop) void { @@ -666,7 +666,7 @@ pub const EventLoop = struct {              }              global_vm.releaseWeakRefs(); -            this.drainMicrotasksWithVM(global_vm); +            this.drainMicrotasksWithGlobal(global);          }          this.tasks.head = if (this.tasks.count == 0) 0 else this.tasks.head; @@ -824,13 +824,14 @@ pub const EventLoop = struct {          this.processGCTimer(); -        var global_vm = ctx.global.vm(); +        var global = ctx.global; +        var global_vm = global.vm();          while (true) {              while (this.tickWithCount() > 0) : (this.global.handleRejectedPromises()) {                  this.tickConcurrent();              } else {                  global_vm.releaseWeakRefs(); -                this.drainMicrotasksWithVM(global_vm); +                this.drainMicrotasksWithGlobal(global);                  this.tickConcurrent();                  if (this.tasks.count > 0) continue;              } diff --git a/src/bun_js.zig b/src/bun_js.zig index 46942b849..0605c57a1 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -241,6 +241,8 @@ pub const Run = struct {      pub fn start(this: *Run) void {          var vm = this.vm;          vm.hot_reload = this.ctx.debug.hot_reload; +        vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose; +          if (this.ctx.debug.hot_reload != .none) {              JSC.HotReloader.enableHotModuleReloading(vm);          } diff --git a/src/js/builtins/ProcessObjectInternals.ts b/src/js/builtins/ProcessObjectInternals.ts index e6c04c90f..2bb8648df 100644 --- a/src/js/builtins/ProcessObjectInternals.ts +++ b/src/js/builtins/ProcessObjectInternals.ts @@ -1,4 +1,5 @@  /* + * Copyright Joyent, Inc. and other Node contributors.   * Copyright 2023 Codeblog Corp. All rights reserved.   *   * Redistribution and use in source and binary forms, with or without @@ -199,3 +200,204 @@ export function getStdinStream(fd) {    return stream;  } + +export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksFn, reportUncaughtExceptionFn) { +  var queue; +  var process; +  var nextTickQueue = nextTickQueue; +  var drainMicrotasks = drainMicrotasksFn; +  var reportUncaughtException = reportUncaughtExceptionFn; + +  function validateFunction(cb) { +    if (typeof cb !== "function") { +      const err = new TypeError(`The "callback" argument must be of type "function". Received type ${typeof cb}`); +      err.code = "ERR_INVALID_ARG_TYPE"; +      throw err; +    } +  } + +  var setup; +  setup = () => { +    queue = (function createQueue() { +      // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. +      const kSize = 2048; +      const kMask = kSize - 1; + +      // The FixedQueue is implemented as a singly-linked list of fixed-size +      // circular buffers. It looks something like this: +      // +      //  head                                                       tail +      //    |                                                          | +      //    v                                                          v +      // +-----------+ <-----\       +-----------+ <------\         +-----------+ +      // |  [null]   |        \----- |   next    |         \------- |   next    | +      // +-----------+               +-----------+                  +-----------+ +      // |   item    | <-- bottom    |   item    | <-- bottom       |  [empty]  | +      // |   item    |               |   item    |                  |  [empty]  | +      // |   item    |               |   item    |                  |  [empty]  | +      // |   item    |               |   item    |                  |  [empty]  | +      // |   item    |               |   item    |       bottom --> |   item    | +      // |   item    |               |   item    |                  |   item    | +      // |    ...    |               |    ...    |                  |    ...    | +      // |   item    |               |   item    |                  |   item    | +      // |   item    |               |   item    |                  |   item    | +      // |  [empty]  | <-- top       |   item    |                  |   item    | +      // |  [empty]  |               |   item    |                  |   item    | +      // |  [empty]  |               |  [empty]  | <-- top  top --> |  [empty]  | +      // +-----------+               +-----------+                  +-----------+ +      // +      // Or, if there is only one circular buffer, it looks something +      // like either of these: +      // +      //  head   tail                                 head   tail +      //    |     |                                     |     | +      //    v     v                                     v     v +      // +-----------+                               +-----------+ +      // |  [null]   |                               |  [null]   | +      // +-----------+                               +-----------+ +      // |  [empty]  |                               |   item    | +      // |  [empty]  |                               |   item    | +      // |   item    | <-- bottom            top --> |  [empty]  | +      // |   item    |                               |  [empty]  | +      // |  [empty]  | <-- top            bottom --> |   item    | +      // |  [empty]  |                               |   item    | +      // +-----------+                               +-----------+ +      // +      // Adding a value means moving `top` forward by one, removing means +      // moving `bottom` forward by one. After reaching the end, the queue +      // wraps around. +      // +      // When `top === bottom` the current queue is empty and when +      // `top + 1 === bottom` it's full. This wastes a single space of storage +      // but allows much quicker checks. + +      class FixedCircularBuffer { +        constructor() { +          this.bottom = 0; +          this.top = 0; +          this.list = $newArrayWithSize(kSize); +          this.next = null; +        } + +        isEmpty() { +          return this.top === this.bottom; +        } + +        isFull() { +          return ((this.top + 1) & kMask) === this.bottom; +        } + +        push(data) { +          this.list[this.top] = data; +          this.top = (this.top + 1) & kMask; +        } + +        shift() { +          var { list, bottom } = this; +          const nextItem = list[bottom]; +          if (nextItem === undefined) return null; +          list[bottom] = undefined; +          this.bottom = (bottom + 1) & kMask; +          return nextItem; +        } +      } + +      class FixedQueue { +        constructor() { +          this.head = this.tail = new FixedCircularBuffer(); +        } + +        isEmpty() { +          return this.head.isEmpty(); +        } + +        push(data) { +          if (this.head.isFull()) { +            // Head is full: Creates a new queue, sets the old queue's `.next` to it, +            // and sets it as the new main queue. +            this.head = this.head.next = new FixedCircularBuffer(); +          } +          this.head.push(data); +        } + +        shift() { +          const tail = this.tail; +          const next = tail.shift(); +          if (tail.isEmpty() && tail.next !== null) { +            // If there is another queue, it forms the new tail. +            this.tail = tail.next; +            tail.next = null; +          } +          return next; +        } +      } + +      return new FixedQueue(); +    })(); + +    function processTicksAndRejections() { +      var tock; +      do { +        while ((tock = queue.shift()) !== null) { +          var callback = tock.callback; +          var args = tock.args; +          var frame = tock.frame; +          var restore = $getInternalField($asyncContext, 0); +          $putInternalField($asyncContext, 0, frame); +          try { +            if (args === undefined) { +              callback(); +            } else { +              switch (args.length) { +                case 1: +                  callback(args[0]); +                  break; +                case 2: +                  callback(args[0], args[1]); +                  break; +                case 3: +                  callback(args[0], args[1], args[2]); +                  break; +                case 4: +                  callback(args[0], args[1], args[2], args[3]); +                  break; +                default: +                  callback(...args); +                  break; +              } +            } +          } catch (e) { +            reportUncaughtException(e); +          } finally { +            $putInternalField($asyncContext, 0, restore); +          } +        } + +        drainMicrotasks(); +      } while (!queue.isEmpty()); +    } + +    $putInternalField(nextTickQueue, 0, 0); +    $putInternalField(nextTickQueue, 1, queue); +    $putInternalField(nextTickQueue, 2, processTicksAndRejections); +    setup = undefined; +  }; + +  function nextTick(cb, args) { +    validateFunction(cb); +    if (setup) { +      setup(); +      process = globalThis.process; +    } +    if (process._exiting) return; + +    queue.push({ +      callback: cb, +      args: $argumentCount() > 1 ? Array.prototype.slice.$call(arguments, 1) : undefined, +      frame: $getInternalField($asyncContext, 0), +    }); +    $putInternalField(nextTickQueue, 0, 1); +  } + +  return nextTick; +} diff --git a/src/js/out/WebCoreJSBuiltins.cpp b/src/js/out/WebCoreJSBuiltins.cpp index 1003fd522..d767a3c39 100644 --- a/src/js/out/WebCoreJSBuiltins.cpp +++ b/src/js/out/WebCoreJSBuiltins.cpp @@ -658,6 +658,14 @@ const int s_processObjectInternalsGetStdinStreamCodeLength = 1386;  static const JSC::Intrinsic s_processObjectInternalsGetStdinStreamCodeIntrinsic = JSC::NoIntrinsic;  const char* const s_processObjectInternalsGetStdinStreamCode = "(function (fd){\"use strict\";var reader,readerRef;function ref(){reader\?\?=@Bun.stdin.stream().getReader(),readerRef\?\?=setInterval(()=>{},1<<30)}function unref(){if(readerRef)clearInterval(readerRef),readerRef=@undefined;if(reader)reader.cancel(),reader=@undefined}const stream=new((@getInternalField(@internalModuleRegistry,44))||(@createInternalModuleById(44))).ReadStream(fd),originalOn=stream.on;stream.on=function(event,listener){if(event===\"readable\")ref();return originalOn.call(this,event,listener)},stream.fd=fd;const originalPause=stream.pause;stream.pause=function(){return unref(),originalPause.call(this)};const originalResume=stream.resume;stream.resume=function(){return ref(),originalResume.call(this)};async function internalRead(stream2){try{var done,value;const read=reader\?.readMany();if(@isPromise(read))({done,value}=await read);else({done,value}=read);if(!done){stream2.push(value[0]);const length=value.length;for(let i=1;i<length;i++)stream2.push(value[i])}else stream2.emit(\"end\"),stream2.pause()}catch(err){stream2.destroy(err)}}return stream._read=function(size){internalRead(this)},stream.on(\"resume\",()=>{ref(),stream._undestroy()}),stream._readableState.reading=!1,stream.on(\"pause\",()=>{process.nextTick(()=>{if(!stream.readableFlowing)stream._readableState.reading=!1})}),stream.on(\"close\",()=>{process.nextTick(()=>{stream.destroy(),unref()})}),stream})\n"; +// initializeNextTickQueue +const JSC::ConstructAbility s_processObjectInternalsInitializeNextTickQueueCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_processObjectInternalsInitializeNextTickQueueCodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_processObjectInternalsInitializeNextTickQueueCodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_processObjectInternalsInitializeNextTickQueueCodeLength = 2336; +static const JSC::Intrinsic s_processObjectInternalsInitializeNextTickQueueCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_processObjectInternalsInitializeNextTickQueueCode = "(function (process,nextTickQueue,drainMicrotasksFn,reportUncaughtExceptionFn){\"use strict\";var queue,process,nextTickQueue=nextTickQueue,drainMicrotasks=drainMicrotasksFn,reportUncaughtException=reportUncaughtExceptionFn;function validateFunction(cb){if(typeof cb!==\"function\"){const err=@makeTypeError(`The \"callback\" argument must be of type \"function\". Received type ${typeof cb}`);throw err.code=\"ERR_INVALID_ARG_TYPE\",err}}var setup=()=>{queue=function createQueue(){class FixedCircularBuffer{constructor(){this.bottom=0,this.top=0,this.list=@newArrayWithSize(2048),this.next=null}isEmpty(){return this.top===this.bottom}isFull(){return(this.top+1&2047)===this.bottom}push(data){this.list[this.top]=data,this.top=this.top+1&2047}shift(){var{list,bottom}=this;const nextItem=list[bottom];if(nextItem===@undefined)return null;return list[bottom]=@undefined,this.bottom=bottom+1&2047,nextItem}}class FixedQueue{constructor(){this.head=this.tail=new FixedCircularBuffer}isEmpty(){return this.head.isEmpty()}push(data){if(this.head.isFull())this.head=this.head.next=new FixedCircularBuffer;this.head.push(data)}shift(){const tail=this.tail,next=tail.shift();if(tail.isEmpty()&&tail.next!==null)this.tail=tail.next,tail.next=null;return next}}return new FixedQueue}();function processTicksAndRejections(){var tock;do{while((tock=queue.shift())!==null){var{callback,args,frame}=tock,restore=@getInternalField(@asyncContext,0);@putInternalField(@asyncContext,0,frame);try{if(args===@undefined)callback();else switch(args.length){case 1:callback(args[0]);break;case 2:callback(args[0],args[1]);break;case 3:callback(args[0],args[1],args[2]);break;case 4:callback(args[0],args[1],args[2],args[3]);break;default:callback(...args);break}}catch(e){reportUncaughtException(e)}finally{@putInternalField(@asyncContext,0,restore)}}drainMicrotasks()}while(!queue.isEmpty())}@putInternalField(nextTickQueue,0,0),@putInternalField(nextTickQueue,1,queue),@putInternalField(nextTickQueue,2,processTicksAndRejections),setup=@undefined};function nextTick(cb,args){if(validateFunction(cb),setup)setup(),process=globalThis.process;if(process._exiting)return;queue.push({callback:cb,args:@argumentCount()>1\?@Array.prototype.slice.@call(arguments,1):@undefined,frame:@getInternalField(@asyncContext,0)}),@putInternalField(nextTickQueue,0,1)}return nextTick})\n"; +  #define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \  JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \  {\ diff --git a/src/js/out/WebCoreJSBuiltins.h b/src/js/out/WebCoreJSBuiltins.h index cf28fa82a..4fc91dbd9 100644 --- a/src/js/out/WebCoreJSBuiltins.h +++ b/src/js/out/WebCoreJSBuiltins.h @@ -1179,20 +1179,31 @@ extern const JSC::ConstructAbility s_processObjectInternalsGetStdinStreamCodeCon  extern const JSC::ConstructorKind s_processObjectInternalsGetStdinStreamCodeConstructorKind;  extern const JSC::ImplementationVisibility s_processObjectInternalsGetStdinStreamCodeImplementationVisibility; +// initializeNextTickQueue +#define WEBCORE_BUILTIN_PROCESSOBJECTINTERNALS_INITIALIZENEXTTICKQUEUE 1 +extern const char* const s_processObjectInternalsInitializeNextTickQueueCode; +extern const int s_processObjectInternalsInitializeNextTickQueueCodeLength; +extern const JSC::ConstructAbility s_processObjectInternalsInitializeNextTickQueueCodeConstructAbility; +extern const JSC::ConstructorKind s_processObjectInternalsInitializeNextTickQueueCodeConstructorKind; +extern const JSC::ImplementationVisibility s_processObjectInternalsInitializeNextTickQueueCodeImplementationVisibility; +  #define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_DATA(macro) \      macro(binding, processObjectInternalsBinding, 1) \      macro(getStdioWriteStream, processObjectInternalsGetStdioWriteStream, 1) \      macro(getStdinStream, processObjectInternalsGetStdinStream, 1) \ +    macro(initializeNextTickQueue, processObjectInternalsInitializeNextTickQueue, 4) \  #define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_CODE(macro) \      macro(processObjectInternalsBindingCode, binding, ASCIILiteral(), s_processObjectInternalsBindingCodeLength) \      macro(processObjectInternalsGetStdioWriteStreamCode, getStdioWriteStream, ASCIILiteral(), s_processObjectInternalsGetStdioWriteStreamCodeLength) \      macro(processObjectInternalsGetStdinStreamCode, getStdinStream, ASCIILiteral(), s_processObjectInternalsGetStdinStreamCodeLength) \ +    macro(processObjectInternalsInitializeNextTickQueueCode, initializeNextTickQueue, ASCIILiteral(), s_processObjectInternalsInitializeNextTickQueueCodeLength) \  #define WEBCORE_FOREACH_PROCESSOBJECTINTERNALS_BUILTIN_FUNCTION_NAME(macro) \      macro(binding) \      macro(getStdioWriteStream) \      macro(getStdinStream) \ +    macro(initializeNextTickQueue) \  #define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \      JSC::FunctionExecutable* codeName##Generator(JSC::VM&); | 
