Compare commits

...

9 Commits

Author SHA1 Message Date
Jarred Sumner
7f06208915 WIP rewrite IO for Bun.file() 2023-12-05 06:54:58 -08:00
Jarred Sumner
3223a77158 Add missing header 2023-12-04 16:16:16 -08:00
Jarred Sumner
e55a53ee0f Reduce number of headers exposed for JSStringDecoder 2023-12-04 15:56:50 -08:00
Jarred Sumner
e1ff68cbfa Undo other new.target changes 2023-12-04 15:55:00 -08:00
Jarred Sumner
9e916bc2cc Align stream constructor more with node.js 2023-12-04 15:54:39 -08:00
Jarred Sumner
5535a5ce76 Faster napi 2023-12-04 15:41:21 -08:00
Jarred Sumner
88c1e4df72 Disable for streams 2023-12-04 03:36:25 -08:00
Jarred Sumner
367cccc35c Fix bug with syntax highlighter 2023-12-02 23:58:32 -08:00
Jarred Sumner
ca18ef068e Use new.target instead of instanceof 2023-12-02 18:59:48 -08:00
21 changed files with 1580 additions and 4103 deletions

View File

@@ -9,6 +9,10 @@
#include "JSDOMConvertEnumeration.h"
#include <JavaScriptCore/JSArrayBufferView.h>
#include "BunClientData.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/LazyClassStructure.h>
#include <JavaScriptCore/LazyClassStructureInlines.h>
#include <JavaScriptCore/VMTrapsInlines.h>
namespace WebCore {
@@ -22,6 +26,66 @@ static JSC_DECLARE_CUSTOM_GETTER(jsStringDecoder_lastChar);
static JSC_DECLARE_CUSTOM_GETTER(jsStringDecoder_lastNeed);
static JSC_DECLARE_CUSTOM_GETTER(jsStringDecoder_lastTotal);
class JSStringDecoderPrototype : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSStringDecoderPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSStringDecoderPrototype* ptr = new (NotNull, JSC::allocateCell<JSStringDecoderPrototype>(vm)) JSStringDecoderPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSStringDecoderPrototype, Base);
return &vm.plainObjectSpace();
}
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());
}
private:
JSStringDecoderPrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
};
class JSStringDecoderConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static JSStringDecoderConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSStringDecoderPrototype* prototype);
static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr bool needsDestruction = false;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype);
// Must be defined for each specialization class.
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*);
DECLARE_EXPORT_INFO;
private:
JSStringDecoderConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, call, construct)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype);
};
static inline JSC::EncodedJSValue jsStringDecoderCast(JSGlobalObject* globalObject, JSValue stringDecoderValue)
{
if (LIKELY(jsDynamicCast<JSStringDecoder*>(stringDecoderValue)))
@@ -507,7 +571,7 @@ void JSStringDecoderConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* glo
JSStringDecoderConstructor* JSStringDecoderConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSStringDecoderPrototype* prototype)
{
JSStringDecoderConstructor* ptr = new (NotNull, JSC::allocateCell<JSStringDecoderConstructor>(vm)) JSStringDecoderConstructor(vm, structure, construct);
JSStringDecoderConstructor* ptr = new (NotNull, JSC::allocateCell<JSStringDecoderConstructor>(vm)) JSStringDecoderConstructor(vm, structure);
ptr->finishCreation(vm, globalObject, prototype);
return ptr;
}
@@ -550,6 +614,11 @@ JSC::EncodedJSValue JSStringDecoderConstructor::construct(JSC::JSGlobalObject* l
return JSC::JSValue::encode(jsObject);
}
JSC::EncodedJSValue JSStringDecoderConstructor::call(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame)
{
return JSC::JSValue::encode(jsUndefined());
}
void JSStringDecoderConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype)
{
putDirect(vm, vm.propertyNames->length, jsNumber(1), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
@@ -561,4 +630,24 @@ void JSStringDecoderConstructor::initializeProperties(VM& vm, JSC::JSGlobalObjec
const ClassInfo JSStringDecoderConstructor::s_info = { "StringDecoder"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStringDecoderConstructor) };
void setJSStringDecoderLazyClassStructure(JSC::LazyClassStructure& lazy)
{
lazy.initLater(
[](LazyClassStructure::Initializer& init) {
auto* prototype = JSStringDecoderPrototype::create(
init.vm, init.global, JSStringDecoderPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()));
auto* structure = JSStringDecoder::createStructure(init.vm, init.global, prototype);
auto* constructor = JSStringDecoderConstructor::create(
init.vm, init.global, JSStringDecoderConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
});
}
void JSStringDecoder::destroy(JSCell* cell)
{
static_cast<JSStringDecoder*>(cell)->JSStringDecoder::~JSStringDecoder();
}
} // namespace Zig

View File

@@ -46,7 +46,7 @@ public:
}
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
static void destroy(JSCell*) {}
static void destroy(JSCell*);
JSC::JSValue write(JSC::VM&, JSC::JSGlobalObject*, uint8_t*, uint32_t);
JSC::JSValue end(JSC::VM&, JSC::JSGlobalObject*, uint8_t*, uint32_t);
@@ -63,63 +63,6 @@ private:
BufferEncodingType m_encoding;
};
class JSStringDecoderPrototype : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSStringDecoderPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSStringDecoderPrototype* ptr = new (NotNull, JSC::allocateCell<JSStringDecoderPrototype>(vm)) JSStringDecoderPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSStringDecoderPrototype, Base);
return &vm.plainObjectSpace();
}
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());
}
private:
JSStringDecoderPrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
};
class JSStringDecoderConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static JSStringDecoderConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSStringDecoderPrototype* prototype);
static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr bool needsDestruction = false;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype);
// Must be defined for each specialization class.
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
DECLARE_EXPORT_INFO;
private:
JSStringDecoderConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction)
: Base(vm, structure, nativeFunction, nativeFunction)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype);
};
void setJSStringDecoderLazyClassStructure(JSC::LazyClassStructure&);
}

View File

@@ -137,7 +137,7 @@
#include "BunObject.h"
#include "JSNextTickQueue.h"
#include "NodeHTTP.h"
#include "napi_external.h"
using namespace Bun;
extern "C" JSC__JSValue Bun__NodeUtil__jsParseArgs(JSC::JSGlobalObject*, JSC::CallFrame*);
@@ -3061,6 +3061,22 @@ void GlobalObject::finishCreation(VM& vm)
this->initGeneratedLazyClasses();
m_NapiExternalStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
auto& global = *reinterpret_cast<Zig::GlobalObject*>(init.owner);
init.set(
Bun::NapiExternal::createStructure(init.vm, init.owner, init.owner->objectPrototype()));
});
m_NapiPrototypeStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
auto& global = *reinterpret_cast<Zig::GlobalObject*>(init.owner);
init.set(
Bun::NapiPrototype::createStructure(init.vm, init.owner, init.owner->objectPrototype()));
});
m_cachedGlobalObjectStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
auto& global = *reinterpret_cast<Zig::GlobalObject*>(init.owner);
@@ -3305,17 +3321,7 @@ void GlobalObject::finishCreation(VM& vm)
init.setStructure(structure);
});
m_JSStringDecoderClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
auto* prototype = JSStringDecoderPrototype::create(
init.vm, init.global, JSStringDecoderPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()));
auto* structure = JSStringDecoder::createStructure(init.vm, init.global, prototype);
auto* constructor = JSStringDecoderConstructor::create(
init.vm, init.global, JSStringDecoderConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
});
setJSStringDecoderLazyClassStructure(m_JSStringDecoderClassStructure);
m_JSReadableStateClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
@@ -3931,6 +3937,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_JSSocketAddressStructure.visit(visitor);
thisObject->m_cachedGlobalObjectStructure.visit(visitor);
thisObject->m_cachedGlobalProxyStructure.visit(visitor);
thisObject->m_NapiExternalStructure.visit(visitor);
thisObject->m_NapiPrototypeStructure.visit(visitor);
thisObject->mockModule.mockFunctionStructure.visit(visitor);
thisObject->mockModule.mockResultStructure.visit(visitor);

View File

@@ -258,6 +258,9 @@ public:
JSWeakMap* vmModuleContextMap() { return m_vmModuleContextMap.getInitializedOnMainThread(this); }
Structure* NapiExternalStructure() { return m_NapiExternalStructure.getInitializedOnMainThread(this); }
Structure* NapiPrototypeStructure() { return m_NapiPrototypeStructure.getInitializedOnMainThread(this); }
bool hasProcessObject() const { return m_processObject.isInitialized(); }
JSC::JSObject* processObject() { return m_processObject.getInitializedOnMainThread(this); }
@@ -515,6 +518,8 @@ public:
LazyProperty<JSGlobalObject, Structure> m_asyncBoundFunctionStructure;
LazyProperty<JSGlobalObject, JSC::JSObject> m_JSDOMFileConstructor;
LazyProperty<JSGlobalObject, Structure> m_JSCryptoKey;
LazyProperty<JSGlobalObject, Structure> m_NapiExternalStructure;
LazyProperty<JSGlobalObject, Structure> m_NapiPrototypeStructure;
LazyProperty<JSGlobalObject, JSObject> m_bunObject;
LazyProperty<JSGlobalObject, JSObject> m_cryptoObject;

View File

@@ -59,6 +59,26 @@
using namespace JSC;
using namespace Zig;
#define NAPI_VERBOSE 0
#if !OS(WINDOWS)
#if NAPI_VERBOSE
#include <stdio.h>
#define NAPI_PREMABLE \
printf("[napi] %s:%d:%s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
#else
#endif
#ifndef NAPI_PREMABLE
#define NAPI_PREMABLE
#endif
#endif
namespace Napi {
JSC::SourceCode generateSourceCode(WTF::String keyString, JSC::VM& vm, JSC::JSObject* object, JSC::JSGlobalObject* globalObject)
@@ -161,17 +181,8 @@ void NapiRef::clear()
// namespace Napi {
// class Reference
// }
#define StackAllocatedCallFramePointerTag 62
typedef struct StackAllocatedCallFrame {
void* dataPtr;
JSC::EncodedJSValue thisValue;
// this is "bar" in:
// set foo(bar)
JSC::EncodedJSValue argument1;
} StackAllocatedCallFrame;
extern "C" Zig::GlobalObject*
Bun__getDefaultGlobal();
extern "C" Zig::GlobalObject* Bun__getDefaultGlobal();
WTF_MAKE_ISO_ALLOCATED_IMPL(NapiRef);
@@ -314,6 +325,8 @@ static void defineNapiProperty(Zig::GlobalObject* globalObject, JSC::JSObject* t
extern "C" napi_status napi_set_property(napi_env env, napi_value target,
napi_value key, napi_value value)
{
NAPI_PREMABLE
if (UNLIKELY(!env || !target || !key)) {
return napi_invalid_arg;
}
@@ -338,6 +351,8 @@ extern "C" napi_status napi_set_property(napi_env env, napi_value target,
extern "C" napi_status napi_has_property(napi_env env, napi_value object,
napi_value key, bool* result)
{
NAPI_PREMABLE
if (UNLIKELY(!object || !env)) {
return napi_invalid_arg;
}
@@ -360,6 +375,8 @@ extern "C" napi_status napi_has_property(napi_env env, napi_value object,
extern "C" napi_status napi_get_property(napi_env env, napi_value object,
napi_value key, napi_value* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
auto& vm = globalObject->vm();
@@ -382,6 +399,8 @@ extern "C" napi_status napi_get_property(napi_env env, napi_value object,
extern "C" napi_status napi_delete_property(napi_env env, napi_value object,
napi_value key, bool* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
auto& vm = globalObject->vm();
@@ -401,6 +420,8 @@ extern "C" napi_status napi_delete_property(napi_env env, napi_value object,
extern "C" napi_status napi_has_own_property(napi_env env, napi_value object,
napi_value key, bool* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
auto& vm = globalObject->vm();
@@ -422,6 +443,8 @@ extern "C" napi_status napi_set_named_property(napi_env env, napi_value object,
const char* utf8name,
napi_value value)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
auto target = toJS(object).getObject();
auto& vm = globalObject->vm();
@@ -452,6 +475,8 @@ extern "C" napi_status napi_create_arraybuffer(napi_env env,
napi_value* result)
{
NAPI_PREMABLE
JSC::JSGlobalObject* globalObject = toJS(env);
if (UNLIKELY(!globalObject || !result)) {
return napi_invalid_arg;
@@ -491,6 +516,7 @@ extern "C" napi_status napi_has_named_property(napi_env env, napi_value object,
const char* utf8name,
bool* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
auto& vm = globalObject->vm();
@@ -513,6 +539,7 @@ extern "C" napi_status napi_get_named_property(napi_env env, napi_value object,
const char* utf8name,
napi_value* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
auto& vm = globalObject->vm();
@@ -671,6 +698,8 @@ extern "C" napi_status napi_wrap(napi_env env,
napi_ref* result)
{
NAPI_PREMABLE
JSValue value = toJS(js_object);
if (!value || value.isUndefinedOrNull()) {
return napi_object_expected;
@@ -723,6 +752,8 @@ extern "C" napi_status napi_wrap(napi_env env,
extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object,
void** result)
{
NAPI_PREMABLE
JSValue value = toJS(js_object);
if (!value || value.isUndefinedOrNull()) {
return napi_object_expected;
@@ -760,6 +791,8 @@ extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object,
extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object,
void** result)
{
NAPI_PREMABLE
JSValue value = toJS(js_object);
if (!value.isObject()) {
@@ -787,6 +820,7 @@ extern "C" napi_status napi_create_function(napi_env env, const char* utf8name,
size_t length, napi_callback cb,
void* data, napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -831,6 +865,8 @@ extern "C" napi_status napi_get_cb_info(
napi_value* this_arg, // [out] Receives the JS 'this' arg for the call
void** data)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
auto inputArgsCount = argc == nullptr ? 0 : *argc;
JSC::CallFrame* callFrame = reinterpret_cast<JSC::CallFrame*>(cbinfo);
@@ -936,6 +972,7 @@ extern "C" napi_status napi_throw_error(napi_env env,
const char* code,
const char* msg)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -951,7 +988,7 @@ extern "C" napi_status napi_create_reference(napi_env env, napi_value value,
uint32_t initial_refcount,
napi_ref* result)
{
NAPI_PREMABLE
JSC::JSValue val = toJS(value);
if (!val || !val.isObject()) {
@@ -991,7 +1028,7 @@ extern "C" napi_status napi_create_reference(napi_env env, napi_value value,
extern "C" void napi_set_ref(NapiRef* ref, JSC__JSValue val_)
{
NAPI_PREMABLE
JSC::JSValue val = JSC::JSValue::decode(val_);
if (val) {
ref->strongRef.set(ref->globalObject->vm(), val);
@@ -1006,6 +1043,7 @@ extern "C" napi_status napi_add_finalizer(napi_env env, napi_value js_object,
void* finalize_hint,
napi_ref* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1025,6 +1063,7 @@ extern "C" napi_status napi_add_finalizer(napi_env env, napi_value js_object,
extern "C" napi_status napi_reference_unref(napi_env env, napi_ref ref,
uint32_t* result)
{
NAPI_PREMABLE
NapiRef* napiRef = toJS(ref);
napiRef->unref();
*result = napiRef->refCount;
@@ -1037,6 +1076,7 @@ extern "C" napi_status napi_reference_unref(napi_env env, napi_ref ref,
extern "C" napi_status napi_get_reference_value(napi_env env, napi_ref ref,
napi_value* result)
{
NAPI_PREMABLE
NapiRef* napiRef = toJS(ref);
*result = toNapi(napiRef->value());
@@ -1045,12 +1085,14 @@ extern "C" napi_status napi_get_reference_value(napi_env env, napi_ref ref,
extern "C" JSC__JSValue napi_get_reference_value_internal(NapiRef* napiRef)
{
NAPI_PREMABLE
return JSC::JSValue::encode(napiRef->value());
}
extern "C" napi_status napi_reference_ref(napi_env env, napi_ref ref,
uint32_t* result)
{
NAPI_PREMABLE
NapiRef* napiRef = toJS(ref);
napiRef->ref();
*result = napiRef->refCount;
@@ -1059,6 +1101,7 @@ extern "C" napi_status napi_reference_ref(napi_env env, napi_ref ref,
extern "C" napi_status napi_delete_reference(napi_env env, napi_ref ref)
{
NAPI_PREMABLE
NapiRef* napiRef = toJS(ref);
napiRef->~NapiRef();
return napi_ok;
@@ -1066,6 +1109,7 @@ extern "C" napi_status napi_delete_reference(napi_env env, napi_ref ref)
extern "C" void napi_delete_reference_internal(napi_ref ref)
{
NAPI_PREMABLE
NapiRef* napiRef = toJS(ref);
delete napiRef;
}
@@ -1074,6 +1118,7 @@ extern "C" napi_status napi_is_detached_arraybuffer(napi_env env,
napi_value arraybuffer,
bool* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1091,6 +1136,7 @@ extern "C" napi_status napi_is_detached_arraybuffer(napi_env env,
extern "C" napi_status napi_detach_arraybuffer(napi_env env,
napi_value arraybuffer)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1114,6 +1160,7 @@ extern "C" napi_status napi_adjust_external_memory(napi_env env,
int64_t change_in_bytes,
int64_t* adjusted_value)
{
NAPI_PREMABLE
if (change_in_bytes > 0) {
toJS(env)->vm().heap.deprecatedReportExtraMemory(change_in_bytes);
}
@@ -1123,6 +1170,7 @@ extern "C" napi_status napi_adjust_external_memory(napi_env env,
extern "C" napi_status napi_is_exception_pending(napi_env env, bool* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
*result = globalObject->vm().exceptionForInspection() != nullptr;
return napi_ok;
@@ -1130,6 +1178,7 @@ extern "C" napi_status napi_is_exception_pending(napi_env env, bool* result)
extern "C" napi_status napi_get_and_clear_last_exception(napi_env env,
napi_value* result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
*result = toNapi(JSC::JSValue(globalObject->vm().lastException()));
globalObject->vm().clearLastException();
@@ -1139,6 +1188,7 @@ extern "C" napi_status napi_get_and_clear_last_exception(napi_env env,
extern "C" napi_status napi_fatal_exception(napi_env env,
napi_value err)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
JSC::JSValue value = toJS(err);
JSC::JSObject* obj = value.getObject();
@@ -1153,6 +1203,7 @@ extern "C" napi_status napi_fatal_exception(napi_env env,
extern "C" napi_status napi_throw(napi_env env, napi_value error)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
@@ -1171,6 +1222,7 @@ extern "C" napi_status node_api_symbol_for(napi_env env,
const char* utf8description,
size_t length, napi_value* result)
{
NAPI_PREMABLE
auto* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
if (UNLIKELY(!result || !utf8description)) {
@@ -1188,6 +1240,7 @@ extern "C" napi_status node_api_create_syntax_error(napi_env env,
napi_value msg,
napi_value* result)
{
NAPI_PREMABLE
if (UNLIKELY(!result)) {
return napi_invalid_arg;
}
@@ -1209,6 +1262,8 @@ extern "C" napi_status node_api_throw_syntax_error(napi_env env,
const char* code,
const char* msg)
{
NAPI_PREMABLE
auto message = msg ? WTF::String::fromUTF8(msg) : String();
auto globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1225,6 +1280,7 @@ extern "C" napi_status node_api_throw_syntax_error(napi_env env,
extern "C" napi_status napi_throw_type_error(napi_env env, const char* code,
const char* msg)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1259,6 +1315,7 @@ extern "C" napi_status napi_create_error(napi_env env, napi_value code,
napi_value msg,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1266,6 +1323,10 @@ extern "C" napi_status napi_create_error(napi_env env, napi_value code,
JSC::JSValue messageValue = toJS(msg);
WTF::String message = messageValue.toWTFString(globalObject);
if (message.isEmpty()) {
message = "Error"_s;
}
auto* error = JSC::createError(globalObject, message);
if (codeValue) {
error->putDirect(vm, WebCore::builtinNames(vm).codePublicName(), codeValue, 0);
@@ -1277,6 +1338,7 @@ extern "C" napi_status napi_create_error(napi_env env, napi_value code,
extern "C" napi_status napi_throw_range_error(napi_env env, const char* code,
const char* msg)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1290,6 +1352,7 @@ extern "C" napi_status napi_throw_range_error(napi_env env, const char* code,
extern "C" napi_status napi_object_freeze(napi_env env, napi_value object_value)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1310,6 +1373,7 @@ extern "C" napi_status napi_object_freeze(napi_env env, napi_value object_value)
}
extern "C" napi_status napi_object_seal(napi_env env, napi_value object_value)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
@@ -1331,6 +1395,8 @@ extern "C" napi_status napi_object_seal(napi_env env, napi_value object_value)
extern "C" napi_status napi_get_global(napi_env env, napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
*result = reinterpret_cast<napi_value>(globalObject->globalThis());
@@ -1341,6 +1407,7 @@ extern "C" napi_status napi_create_range_error(napi_env env, napi_value code,
napi_value msg,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1359,6 +1426,7 @@ extern "C" napi_status napi_get_new_target(napi_env env,
napi_callback_info cbinfo,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
// handle:
@@ -1379,6 +1447,7 @@ extern "C" napi_status napi_create_dataview(napi_env env, size_t length,
size_t byte_offset,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
@@ -1541,7 +1610,7 @@ void NapiClass::finishCreation(VM& vm, NativeExecutable* executable, unsigned le
prototypePropertyCount += property.attributes & napi_static ? 0 : 1;
}
NapiPrototype* prototype = NapiPrototype::create(vm, globalObject);
NapiPrototype* prototype = NapiPrototype::create(vm, globalObject->NapiPrototypeStructure());
auto throwScope = DECLARE_THROW_SCOPE(vm);
@@ -1599,6 +1668,7 @@ static napi_extended_error_info last_error_info;
extern "C" napi_status
napi_get_last_error_info(napi_env env, const napi_extended_error_info** result)
{
NAPI_PREMABLE
auto globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
auto lastException = vm.lastException();
@@ -1633,6 +1703,8 @@ extern "C" napi_status napi_define_class(napi_env env,
const napi_property_descriptor* properties,
napi_value* result)
{
NAPI_PREMABLE
if (utf8name == nullptr) {
return napi_invalid_arg;
}
@@ -1657,6 +1729,7 @@ extern "C" napi_status napi_define_class(napi_env env,
extern "C" napi_status napi_coerce_to_string(napi_env env, napi_value value,
napi_value* result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr || value == nullptr || env == nullptr)) {
return napi_invalid_arg;
}
@@ -1684,7 +1757,7 @@ extern "C" napi_status napi_coerce_to_string(napi_env env, napi_value value,
extern "C" napi_status napi_get_property_names(napi_env env, napi_value object,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1714,6 +1787,8 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length,
void* finalize_hint,
napi_value* result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr)) {
return napi_invalid_arg;
}
@@ -1737,6 +1812,8 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length,
extern "C" napi_status napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length,
napi_finalize finalize_cb, void* finalize_hint, napi_value* result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr)) {
return napi_invalid_arg;
}
@@ -1762,6 +1839,8 @@ extern "C" napi_status napi_get_value_string_utf8(napi_env env,
size_t bufsize,
size_t* writtenPtr)
{
NAPI_PREMABLE
JSGlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -1823,6 +1902,8 @@ extern "C" napi_status napi_get_value_string_utf8(napi_env env,
extern "C" napi_status napi_get_element(napi_env env, napi_value objectValue,
uint32_t index, napi_value* result)
{
NAPI_PREMABLE
JSValue jsValue = toJS(objectValue);
if (UNLIKELY(!env || !jsValue || !jsValue.isObject())) {
return napi_invalid_arg;
@@ -1844,6 +1925,8 @@ extern "C" napi_status napi_get_element(napi_env env, napi_value objectValue,
extern "C" napi_status napi_create_object(napi_env env, napi_value* result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr || env == nullptr)) {
return napi_invalid_arg;
}
@@ -1851,7 +1934,7 @@ extern "C" napi_status napi_create_object(napi_env env, napi_value* result)
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
JSValue value = JSValue(NapiPrototype::create(vm, globalObject));
JSValue value = JSValue(NapiPrototype::create(vm, globalObject->NapiPrototypeStructure()));
*result = toNapi(value);
JSC::EnsureStillAliveScope ensureStillAlive(value);
@@ -1863,6 +1946,7 @@ extern "C" napi_status napi_create_external(napi_env env, void* data,
void* finalize_hint,
napi_value* result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr)) {
return napi_invalid_arg;
}
@@ -1870,9 +1954,9 @@ extern "C" napi_status napi_create_external(napi_env env, void* data,
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
auto* structure = Bun::NapiExternal::createStructure(vm, globalObject, globalObject->objectPrototype());
JSValue value = JSValue(Bun::NapiExternal::create(vm, structure, data, finalize_hint, finalize_cb));
auto* structure = globalObject->NapiExternalStructure();
JSValue value = Bun::NapiExternal::create(vm, structure, data, finalize_hint, reinterpret_cast<void*>(finalize_cb));
JSC::EnsureStillAliveScope ensureStillAlive(value);
*result = toNapi(value);
return napi_ok;
}
@@ -1880,6 +1964,8 @@ extern "C" napi_status napi_create_external(napi_env env, void* data,
extern "C" napi_status napi_typeof(napi_env env, napi_value val,
napi_valuetype* result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr))
return napi_invalid_arg;
@@ -1940,6 +2026,8 @@ extern "C" napi_status napi_typeof(napi_env env, napi_value val,
*result = napi_object;
return napi_ok;
}
break;
}
}
}
@@ -1973,6 +2061,8 @@ extern "C" napi_status napi_get_value_bigint_words(napi_env env,
size_t* word_count,
uint64_t* words)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::JSValue jsValue = toJS(value);
@@ -2012,6 +2102,8 @@ extern "C" napi_status napi_get_value_bigint_words(napi_env env,
extern "C" napi_status napi_get_value_external(napi_env env, napi_value value,
void** result)
{
NAPI_PREMABLE
if (UNLIKELY(result == nullptr)) {
return napi_invalid_arg;
}
@@ -2029,6 +2121,8 @@ extern "C" napi_status napi_get_value_external(napi_env env, napi_value value,
extern "C" napi_status napi_get_instance_data(napi_env env,
void** data)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
if (UNLIKELY(data == nullptr)) {
return napi_invalid_arg;
@@ -2043,6 +2137,8 @@ extern "C" napi_status napi_set_instance_data(napi_env env,
napi_finalize finalize_cb,
void* finalize_hint)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
if (data)
globalObject->napiInstanceData = data;
@@ -2059,6 +2155,8 @@ extern "C" napi_status napi_create_bigint_words(napi_env env,
const uint64_t* words,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
auto* bigint = JSC::JSBigInt::tryCreateWithLength(vm, word_count);
@@ -2085,6 +2183,8 @@ extern "C" napi_status napi_create_bigint_words(napi_env env,
extern "C" napi_status napi_create_symbol(napi_env env, napi_value description,
napi_value* result)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -2118,6 +2218,8 @@ extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi,
const napi_value* argv,
napi_value* result_ptr)
{
NAPI_PREMABLE
Zig::GlobalObject* globalObject = toJS(env);
JSC::VM& vm = globalObject->vm();
@@ -2139,14 +2241,21 @@ extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi,
}
JSC::JSValue thisValue = toJS(recv_napi);
auto scope = DECLARE_THROW_SCOPE(vm);
if (thisValue.isEmpty()) {
thisValue = JSC::jsUndefined();
}
JSC::JSValue result = call(globalObject, funcValue, callData, thisValue, args);
if (result_ptr) {
*result_ptr = toNapi(result);
if (result.isEmpty()) {
*result_ptr = toNapi(JSC::jsUndefined());
} else {
*result_ptr = toNapi(result);
}
}
return napi_ok;
}
RETURN_IF_EXCEPTION(scope, napi_generic_failure);
RELEASE_AND_RETURN(scope, napi_ok);
}

View File

@@ -192,19 +192,13 @@ public:
DECLARE_INFO;
static NapiPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure)
static NapiPrototype* create(VM& vm, Structure* structure)
{
NapiPrototype* footprint = new (NotNull, allocateCell<NapiPrototype>(vm)) NapiPrototype(vm, structure);
footprint->finishCreation(vm);
return footprint;
}
static NapiPrototype* create(VM& vm, JSGlobalObject* globalObject)
{
Structure* structure = createStructure(vm, globalObject, globalObject->objectPrototype());
return create(vm, globalObject, structure);
}
static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
ASSERT(globalObject);

View File

@@ -6,7 +6,7 @@ namespace Bun {
NapiExternal::~NapiExternal()
{
if (finalizer) {
finalizer(toNapi(globalObject()), m_value, m_finalizerHint);
reinterpret_cast<napi_finalize>(finalizer)(toNapi(globalObject()), m_value, m_finalizerHint);
}
}

View File

@@ -6,7 +6,6 @@
#include "BunBuiltinNames.h"
#include "BunClientData.h"
#include "node_api.h"
namespace Bun {
@@ -48,14 +47,33 @@ public:
JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
static NapiExternal* create(JSC::VM& vm, JSC::Structure* structure, void* value, void* finalizer_hint, napi_finalize finalizer)
static NapiExternal* create(JSC::VM& vm, JSC::Structure* structure, void* value, void* finalizer_hint, void* finalizer)
{
NapiExternal* accessor = new (NotNull, JSC::allocateCell<NapiExternal>(vm)) NapiExternal(vm, structure);
accessor->finishCreation(vm, value, finalizer_hint, finalizer);
#if BUN_DEBUG
if (auto* callFrame = vm.topCallFrame) {
auto origin = callFrame->callerSourceOrigin(vm);
accessor->sourceOriginURL = origin.string();
std::unique_ptr<Vector<StackFrame>> stackTrace = makeUnique<Vector<StackFrame>>();
vm.interpreter.getStackTrace(accessor, *stackTrace, 0, 20);
if (!stackTrace->isEmpty()) {
for (auto& frame : *stackTrace) {
if (frame.hasLineAndColumnInfo()) {
frame.computeLineAndColumn(accessor->sourceOriginLine, accessor->sourceOriginColumn);
break;
}
}
}
}
#endif
return accessor;
}
void finishCreation(JSC::VM& vm, void* value, void* finalizer_hint, napi_finalize finalizer)
void finishCreation(JSC::VM& vm, void* value, void* finalizer_hint, void* finalizer)
{
Base::finishCreation(vm);
m_value = value;
@@ -69,7 +87,13 @@ public:
void* m_value;
void* m_finalizerHint;
napi_finalize finalizer;
void* finalizer;
#if BUN_DEBUG
String sourceOriginURL = String();
unsigned sourceOriginLine = 0;
unsigned sourceOriginColumn = 0;
#endif
};
} // namespace Zig

View File

@@ -1545,18 +1545,14 @@ pub const Blob = struct {
const State = @This();
/// This is a workaround for some versions of IO uring returning
/// EAGAIN when reading a file opened with O_NONBLOCK. Since io_uring waits, we don't need to wait.
const non_block_without_io_uring = if (Environment.isLinux) 0 else std.os.O.NONBLOCK;
const __opener_flags = non_block_without_io_uring | std.os.O.CLOEXEC;
const __opener_flags = std.os.O.NONBLOCK | std.os.O.CLOEXEC;
const open_flags_ = if (@hasDecl(This, "open_flags"))
This.open_flags | __opener_flags
else
std.os.O.RDONLY | __opener_flags;
pub fn getFdMac(this: *This) bun.FileDescriptor {
pub fn getFdImpl(this: *This) bun.FileDescriptor {
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
var path_string = if (@hasField(This, "file_store"))
this.file_store.pathlike.path
@@ -1602,11 +1598,7 @@ pub const Blob = struct {
}
}
if (comptime Environment.isMac) {
Callback(this, this.getFdMac());
} else {
this.getFdLinux(Callback);
}
Callback(this, this.getFdImpl());
}
const WrappedOpenCallback = *const fn (*State, *http.NetworkThread.Completion, AsyncIO.OpenError!bun.FileDescriptor) void;
@@ -1663,57 +1655,18 @@ pub const Blob = struct {
}
}.onOpen;
}
pub fn getFdLinux(this: *This, comptime callback: OpenCallback) void {
var aio = &AsyncIO.global;
var path_string = if (@hasField(This, "file_store"))
this.file_store.pathlike.path
else
this.file_blob.store.?.data.file.pathlike.path;
var holder = bun.default_allocator.create(State) catch unreachable;
holder.* = .{
.context = this,
};
var path_buffer = bun.default_allocator.dupeZ(u8, path_string.slice()) catch unreachable;
aio.open(
*State,
holder,
comptime OpenCallbackWrapper(callback),
&holder.open_completion,
path_buffer,
open_flags_,
JSC.Node.default_permission,
);
}
};
}
pub fn FileCloserMixin(comptime This: type) type {
return struct {
const Closer = @This();
close_completion: AsyncIO.Completion = undefined,
pub fn doClose(this: *This) void {
const fd = this.opened_fd;
std.debug.assert(fd != null_fd);
var aio = &AsyncIO.global;
var closer = bun.default_allocator.create(Closer) catch unreachable;
aio.close(
*Closer,
closer,
onClose,
&closer.close_completion,
fd,
);
this.opened_fd = null_fd;
}
pub fn onClose(closer: *Closer, _: *http.NetworkThread.Completion, _: AsyncIO.CloseError!void) void {
bun.default_allocator.destroy(closer);
_ = bun.sys.close(fd);
}
};
}

View File

@@ -277,7 +277,7 @@ pub const fmt = struct {
var remain = text;
var prev_keyword: ?Keyword = null;
while (remain.len > 0) {
outer: while (remain.len > 0) {
if (js_lexer.isIdentifierStart(remain[0])) {
var i: usize = 1;
@@ -351,24 +351,56 @@ pub const fmt = struct {
prev_keyword = null;
var i: usize = 1;
for (remain[i..]) |c| {
if (c == char) {
if (i < remain.len) {
i += 1;
}
break;
} else if (c == '\\') {
i += 1;
if (i < remain.len) {
i += 1;
}
} else {
if (i < remain.len) {
i += 1;
while (i < remain.len and remain[i] != char) {
if (comptime char == '`') {
if (remain[i] == '$' and i + 1 < remain.len and remain[i + 1] == '{') {
const curly_start = i;
i += 2;
while (i < remain.len and remain[i] != '}') {
if (remain[i] == '\\') {
i += 1;
}
i += 1;
}
try writer.print(Output.prettyFmt("<r><green>{s}<r>", true), .{remain[0..curly_start]});
try writer.writeAll("${");
const curly_remain = QuickAndDirtyJavaScriptSyntaxHighlighter{
.text = remain[curly_start + 2 .. i],
.enable_colors = this.enable_colors,
.limited = false,
};
if (curly_remain.text.len > 0) {
try curly_remain.format("", .{}, writer);
}
if (i < remain.len and remain[i] == '}') {
i += 1;
}
try writer.writeAll("}");
remain = remain[i..];
i = 0;
if (remain.len > 0 and remain[0] == char) {
try writer.writeAll(Output.prettyFmt("<r><green>`<r>", true));
remain = remain[1..];
continue :outer;
}
continue;
}
}
if (i + 1 < remain.len and remain[i] == '\\') {
i += 1;
}
i += 1;
}
// Include the trailing quote, if any
i += @as(usize, @intFromBool(i > 1 and i < remain.len and remain[i] == char));
try writer.print(Output.prettyFmt("<r><green>{s}<r>", true), .{remain[0..i]});
remain = remain[i..];
},

379
src/io/heap.zig Normal file
View File

@@ -0,0 +1,379 @@
const std = @import("std");
const assert = std.debug.assert;
/// An intrusive heap implementation backed by a pairing heap[1] implementation.
///
/// Why? Intrusive data structures require the element type to hold the metadata
/// required for the structure, rather than an additional container structure.
/// There are numerous pros/cons that are documented well by Boost[2]. For Zig,
/// I think the primary benefits are making data structures allocation free
/// (rather, shifting allocation up to the consumer which can choose how they
/// want the memory to be available). There are various costs to this such as
/// the costs of pointer chasing, larger memory overhead, requiring the element
/// type to be aware of its container, etc. But for certain use cases an intrusive
/// data structure can yield much better performance.
///
/// Usage notes:
/// - The element T is expected to have a field "heap" of type InstrusiveHeapField.
/// See the tests for a full example of how to set this.
/// - You can easily make this a min or max heap by inverting the result of
/// "less" below.
///
/// [1]: https://en.wikipedia.org/wiki/Pairing_heap
/// [2]: https://www.boost.org/doc/libs/1_64_0/doc/html/intrusive/intrusive_vs_nontrusive.html
pub fn Intrusive(
comptime T: type,
comptime Context: type,
comptime less: *const fn (ctx: Context, a: *T, b: *T) bool,
) type {
return struct {
const Self = @This();
root: ?*T = null,
context: Context,
/// Insert a new element v into the heap. An element v can only
/// be a member of a single heap at any given time. When compiled
/// with runtime-safety, assertions will help verify this property.
pub fn insert(self: *Self, v: *T) void {
self.root = if (self.root) |root| self.meld(v, root) else v;
}
/// Look at the next minimum value but do not remove it.
pub fn peek(self: *Self) ?*T {
return self.root;
}
/// Delete the minimum value from the heap and return it.
pub fn deleteMin(self: *Self) ?*T {
const root = self.root orelse return null;
self.root = if (root.heap.child) |child|
self.combine_siblings(child)
else
null;
// Clear pointers with runtime safety so we can verify on
// insert that values aren't incorrectly being set multiple times.
root.heap = .{};
return root;
}
/// Remove the value v from the heap.
pub fn remove(self: *Self, v: *T) void {
// If v doesn't have a previous value, this must be the root
// element. If it is NOT the root element, v can't be in this
// heap and we trigger an assertion failure.
const prev = v.heap.prev orelse {
assert(self.root.? == v);
_ = self.deleteMin();
return;
};
// Detach "v" from the tree and clean up any links so it
// is as if this node never nexisted. The previous value
// must point to the proper next value and the pointers
// must all be cleaned up.
if (v.heap.next) |next| next.heap.prev = prev;
if (prev.heap.child == v)
prev.heap.child = v.heap.next
else
prev.heap.next = v.heap.next;
v.heap.prev = null;
v.heap.next = null;
// If we have children, then we need to merge them back in.
const child = v.heap.child orelse return;
v.heap.child = null;
const x = self.combine_siblings(child);
self.root = self.meld(x, self.root.?);
}
/// Meld (union) two heaps together. This isn't a generalized
/// union. It assumes that a.heap.next is null so this is only
/// meant in specific scenarios in the pairing heap where meld
/// is expected.
///
/// For example, when melding a new value "v" with an existing
/// root "root", "v" must always be the first param.
fn meld(self: *Self, a: *T, b: *T) *T {
assert(a.heap.next == null);
if (less(self.context, a, b)) {
// B points back to A
b.heap.prev = a;
// If B has siblings, then A inherits B's siblings
// and B's immediate sibling must point back to A to
// maintain the doubly linked list.
if (b.heap.next) |b_next| {
a.heap.next = b_next;
b_next.heap.prev = a;
b.heap.next = null;
}
// If A has a child, then B becomes the leftmost sibling
// of that child.
if (a.heap.child) |a_child| {
b.heap.next = a_child;
a_child.heap.prev = b;
}
// B becomes the leftmost child of A
a.heap.child = b;
return a;
}
// Replace A with B in the tree. Any of B's children
// become siblings of A. A becomes the leftmost child of B.
// A points back to B
b.heap.prev = a.heap.prev;
a.heap.prev = b;
if (b.heap.child) |b_child| {
a.heap.next = b_child;
b_child.heap.prev = a;
}
b.heap.child = a;
return b;
}
/// Combine the siblings of the leftmost value "left" into a single
/// new rooted with the minimum value.
fn combine_siblings(self: *Self, left: *T) *T {
left.heap.prev = null;
// Merge pairs right
var root: *T = root: {
var a: *T = left;
while (true) {
var b = a.heap.next orelse break :root a;
a.heap.next = null;
b = self.meld(a, b);
a = b.heap.next orelse break :root b;
}
};
// Merge pairs left
while (true) {
var b = root.heap.prev orelse return root;
b.heap.next = null;
root = self.meld(b, root);
}
}
};
}
/// The state that is required for IntrusiveHeap element types. This
/// should be set as the "heap" field in the type T.
pub fn IntrusiveField(comptime T: type) type {
return struct {
child: ?*T = null,
prev: ?*T = null,
next: ?*T = null,
};
}
test "heap" {
const Elem = struct {
const Self = @This();
value: usize = 0,
heap: IntrusiveField(Self) = .{},
};
const Heap = Intrusive(Elem, void, (struct {
fn less(ctx: void, a: *Elem, b: *Elem) bool {
_ = ctx;
return a.value < b.value;
}
}).less);
var a: Elem = .{ .value = 12 };
var b: Elem = .{ .value = 24 };
var c: Elem = .{ .value = 7 };
var d: Elem = .{ .value = 9 };
var h: Heap = .{ .context = {} };
h.insert(&a);
h.insert(&b);
h.insert(&c);
h.insert(&d);
h.remove(&d);
const testing = std.testing;
try testing.expect(h.deleteMin().?.value == 7);
try testing.expect(h.deleteMin().?.value == 12);
try testing.expect(h.deleteMin().?.value == 24);
try testing.expect(h.deleteMin() == null);
}
test "heap remove root" {
const Elem = struct {
const Self = @This();
value: usize = 0,
heap: IntrusiveField(Self) = .{},
};
const Heap = Intrusive(Elem, void, (struct {
fn less(ctx: void, a: *Elem, b: *Elem) bool {
_ = ctx;
return a.value < b.value;
}
}).less);
var a: Elem = .{ .value = 12 };
var b: Elem = .{ .value = 24 };
var h: Heap = .{ .context = {} };
h.insert(&a);
h.insert(&b);
h.remove(&a);
const testing = std.testing;
try testing.expect(h.deleteMin().?.value == 24);
try testing.expect(h.deleteMin() == null);
}
test "heap remove with children" {
const Elem = struct {
const Self = @This();
value: usize = 0,
heap: IntrusiveField(Self) = .{},
};
const Heap = Intrusive(Elem, void, (struct {
fn less(ctx: void, a: *Elem, b: *Elem) bool {
_ = ctx;
return a.value < b.value;
}
}).less);
var a: Elem = .{ .value = 36 };
var b: Elem = .{ .value = 24 };
var c: Elem = .{ .value = 12 };
var h: Heap = .{ .context = {} };
h.insert(&a);
h.insert(&b);
h.insert(&c);
h.remove(&b);
const testing = std.testing;
try testing.expect(h.deleteMin().?.value == 12);
try testing.expect(h.deleteMin().?.value == 36);
try testing.expect(h.deleteMin() == null);
}
test "heap equal values" {
const testing = std.testing;
const Elem = struct {
const Self = @This();
value: usize = 0,
heap: IntrusiveField(Self) = .{},
};
const Heap = Intrusive(Elem, void, (struct {
fn less(ctx: void, a: *Elem, b: *Elem) bool {
_ = ctx;
return a.value < b.value;
}
}).less);
var a: Elem = .{ .value = 1 };
var b: Elem = .{ .value = 2 };
var c: Elem = .{ .value = 3 };
var d: Elem = .{ .value = 4 };
var h: Heap = .{ .context = {} };
h.insert(&a);
h.insert(&b);
h.insert(&c);
h.insert(&d);
try testing.expect(h.deleteMin().?.value == 1);
try testing.expect(h.deleteMin().?.value == 2);
try testing.expect(h.deleteMin().?.value == 3);
try testing.expect(h.deleteMin().?.value == 4);
try testing.expect(h.deleteMin() == null);
}
test "heap: million values" {
const testing = std.testing;
const alloc = testing.allocator;
const Elem = struct {
const Self = @This();
value: usize = 0,
heap: IntrusiveField(Self) = .{},
};
const Heap = Intrusive(Elem, void, (struct {
fn less(ctx: void, a: *Elem, b: *Elem) bool {
_ = ctx;
return a.value < b.value;
}
}).less);
const NUM_TIMERS: usize = 1000 * 1000;
var elems = try alloc.alloc(Elem, NUM_TIMERS);
defer alloc.free(elems);
var i: usize = 0;
var value: usize = 0;
while (i < NUM_TIMERS) : (i += 1) {
if (i % 100 == 0) value += 1;
elems[i] = .{ .value = value };
}
var h: Heap = .{ .context = {} };
for (elems) |*elem| {
h.insert(elem);
}
var count: usize = 0;
var last: usize = 0;
while (h.deleteMin()) |elem| {
count += 1;
try testing.expect(elem.value >= last);
last = elem.value;
}
try testing.expect(h.deleteMin() == null);
try testing.expect(count == NUM_TIMERS);
}
test "heap: dangling next pointer" {
const testing = std.testing;
const Elem = struct {
const Self = @This();
value: usize = 0,
heap: IntrusiveField(Self) = .{},
};
const Heap = Intrusive(Elem, void, (struct {
fn less(ctx: void, a: *Elem, b: *Elem) bool {
_ = ctx;
return a.value < b.value;
}
}).less);
var a: Elem = .{ .value = 2 };
var b: Elem = .{ .value = 4 };
var c: Elem = .{ .value = 5 };
var d: Elem = .{ .value = 1 };
var e: Elem = .{ .value = 3 };
var h: Heap = .{ .context = {} };
h.insert(&a);
h.insert(&b);
h.insert(&c);
h.insert(&d);
h.insert(&e);
try testing.expect(h.deleteMin().?.value == 1);
try testing.expect(h.deleteMin().?.value == 2);
try testing.expect(h.deleteMin().?.value == 3);
try testing.expect(h.deleteMin().?.value == 4);
try testing.expect(h.deleteMin().?.value == 5);
try testing.expect(h.deleteMin() == null);
}

708
src/io/io.zig Normal file
View File

@@ -0,0 +1,708 @@
const bun = @import("root").bun;
const std = @import("std");
const sys = bun.sys;
const linux = std.os.linux;
const Environment = bun.Environment;
const heap = @import("./heap.zig");
const JSC = bun.JSC;
const log = bun.Output.scoped(.loop, false);
const TimerHeap = heap.Intrusive(Timer, void, Timer.less);
const os = std.os;
const assert = std.debug.assert;
pub const Loop = struct {
pending: Request.Queue = .{},
waker: bun.Async.Waker,
timers: TimerHeap = .{ .context = {} },
cached_now: os.timespec = .{
.tv_nsec = 0,
.tv_sec = 0,
},
active: usize = 0,
var loop: Loop = undefined;
pub fn schedule(this: *Loop, request: *Request) void {
this.pending.push(request);
this.waker.wake();
}
pub fn tickEpoll(this: *Loop) void {
if (comptime !Environment.isLinux) {
@compileError("not implemented");
}
while (true) {
// Process pending requests
{
var pending_batch = this.pending.popBatch();
var pending = pending_batch.iterator();
while (pending.next()) |request| {
switch (request.callback(request)) {
.readable => |readable| {
switch (readable.poll.registerWithFd(this, .poll_readable, true, @intCast(readable.fd))) {
.err => |err| {
readable.onError(request, err);
},
.result => {
this.active += 1;
},
}
},
.writable => |writable| {
switch (writable.poll.registerWithFd(this, .poll_writable, true, @intCast(writable.fd))) {
.err => |err| {
writable.onError(request, err);
},
.result => {
this.active += 1;
},
}
},
.close => |close| {
switch (close.poll.unregisterWithFd(this, @intCast(close.fd))) {
.result, .err => {
this.active -= 1;
close.onDone(request);
},
}
},
.timer => |timer| {
while (true) {
switch (timer.state) {
.PENDING => {
timer.state = .ACTIVE;
if (Timer.less({}, timer, &.{ .next = this.cached_now })) {
if (timer.fire() == .rearm) {
if (timer.reset) |reset| {
timer.next = reset;
timer.reset = null;
continue;
}
}
break;
}
this.timers.insert(timer);
},
.ACTIVE => {
@panic("timer is already active");
},
.CANCELLED => {
timer.deinit();
break;
},
.FIRED => {
@panic("timer has already fired");
},
}
break;
}
},
}
}
}
this.drainExpiredTimers();
// Determine our next timeout based on the timers
const timeout: i32 = if (this.active == 0) 0 else timeout: {
const t = this.timers.peek() orelse break :timeout -1;
// Determine the time in milliseconds.
const ms_now = @as(u64, @intCast(this.cached_now.tv_sec)) * std.time.ms_per_s +
@as(u64, @intCast(this.cached_now.tv_nsec)) / std.time.ns_per_ms;
const ms_next = @as(u64, @intCast(t.next.tv_sec)) * std.time.ms_per_s +
@as(u64, @intCast(t.next.tv_nsec)) / std.time.ns_per_ms;
break :timeout @as(i32, @intCast(ms_next -| ms_now));
};
var events: [EventType]256 = undefined;
const rc = linux.epoll_wait(
this.fd(),
&events,
@intCast(events.len),
timeout,
);
switch (std.os.linux.getErrno(rc)) {
.INTR => continue,
.SUCCESS => {},
else => |e| bun.Output.panic("epoll_wait: {s}", .{@tagName(e)}),
}
this.update_now();
const current_events: []std.os.linux.epoll_event = events[0..rc];
for (current_events) |event| {
const pollable: Pollable = Pollable.from(event.data.u64);
Poll.onUpdateEpoll(pollable.poll(), pollable.tag(), event.events);
}
}
}
pub fn tickKqueue(this: *Loop) void {
if (comptime !Environment.isMac) {
@compileError("not implemented");
}
while (true) {
var stack_fallback = std.heap.stackFallback(@sizeOf([256]EventType), bun.default_allocator);
var events_list: std.ArrayList(EventType) = std.ArrayList(EventType).initCapacity(stack_fallback.allocator, 256) catch unreachable;
defer events_list.deinit();
// Process pending requests
{
var pending_batch = this.pending.popBatch();
var pending = pending_batch.iterator();
events_list.ensureUnusedCapacity(pending.batch.count) catch bun.outOfMemory();
while (pending.next()) |request| {
switch (request.callback(request)) {
.readable => |readable| {
const i = events_list.items.len;
assert(i + 1 <= events_list.capacity);
events_list.items.len += 1;
Poll.Flags.applyKQueue(
.readable,
readable.tag,
readable.poll,
readable.fd,
&events_list.items.ptr[i],
);
},
.writable => |writable| {
const i = events_list.items.len;
assert(i + 1 <= events_list.capacity);
events_list.items.len += 1;
Poll.Flags.applyKQueue(
.writable,
writable.tag,
writable.poll,
writable.fd,
&events_list.items.ptr[i],
);
},
.close => |close| {
const i = events_list.items.len;
assert(i + 1 <= events_list.capacity);
events_list.items.len += 1;
Poll.Flags.applyKQueue(
.close,
close.tag,
close.poll,
close.fd,
&events_list.items.ptr[i],
);
},
.timer => |timer| {
while (true) {
switch (timer.state) {
.PENDING => {
timer.state = .ACTIVE;
if (Timer.less({}, timer, &.{ .next = this.cached_now })) {
if (timer.fire() == .rearm) {
if (timer.reset) |reset| {
timer.next = reset;
timer.reset = null;
continue;
}
}
break;
}
this.timers.insert(timer);
},
.ACTIVE => {
@panic("timer is already active");
},
.CANCELLED => {
timer.deinit();
break;
},
.FIRED => {
@panic("timer has already fired");
},
}
break;
}
},
}
}
}
this.drainExpiredTimers();
const change_count = events_list.items.len;
// Determine our next timeout based on the timers
const timeout: ?std.os.timespec = timeout: {
const t = this.timers.peek() orelse break :timeout null;
var out: std.os.timespec = undefined;
out.tv_sec = t.next.tv_sec -| this.cached_now.tv_sec;
out.tv_nsec = t.next.tv_nsec -| this.cached_now.tv_nsec;
break :timeout out;
};
const rc = os.system.kevent64(
this.fd(),
events_list.items.ptr,
@intCast(change_count),
// The same array may be used for the changelist and eventlist.
events_list.items.ptr,
// we set 0 here so that if we get an error on
// registration, it becomes errno
@intCast(events_list.capacity),
0,
if (timeout) |*t| t else null,
);
switch (std.c.getErrno(rc)) {
.INTR => continue,
.SUCCESS => {},
else => |e| bun.Output.panic("kevent64 failed: {s}", .{@tagName(e)}),
}
this.update_now();
assert(rc <= events_list.capacity);
const current_events: []std.os.darwin.kevent64_s = events_list.items.ptr[0..rc];
for (current_events) |event| {
const pollable: Pollable = Pollable.from(event.udata);
Poll.onUpdateKQueue(pollable.poll(), pollable.tag(), event);
}
}
}
fn drainExpiredTimers(this: *Loop) void {
const now = Timer{ .next = this.cached_now };
// Run our expired timers
while (this.timers.peek()) |t| {
if (!Timer.less({}, t, &now)) break;
// Remove the timer
assert(this.timers.deleteMin().? == t);
// Mark completion as done
t.state = .FIRED;
switch (t.fire()) {
.disarm => {},
.rearm => |new| {
t.next = new;
t.reset = null;
t.state = .ACTIVE;
this.timers.insert(t);
},
}
}
}
fn update_now(this: *Loop) void {
if (comptime Environment.isLinux) {
const rc = linux.clock_gettime(linux.CLOCK.MONOTONIC, &this.cached_now);
assert(rc == 0);
} else if (comptime Environment.isMac) {
std.os.clock_gettime(std.os.CLOCK.MONOTONIC, &this.cached_now) catch {};
} else {
@compileError("TODO: implement poll for this platform");
}
}
};
const EventType = if (Environment.isLinux) linux.epoll_event else std.os.system.kevent64_s;
pub const Request = struct {
next: ?*Request = null,
callback: *const fn (*Request) Action,
pub const Queue = bun.UnboundedQueue(Request, .next);
};
pub const Action = union(enum) {
readable: FileAction,
writable: FileAction,
close: CloseAction,
timer: *Timer,
pub const FileAction = struct {
fd: bun.FileDescriptor,
poll: *Poll,
ctx: *anyopaque,
tag: Pollable.Tag,
onError: *const fn (*anyopaque, sys.Error) void,
};
pub const CloseAction = struct {
fd: bun.FileDescriptor,
poll: *Poll,
ctx: *anyopaque,
tag: Pollable.Tag,
onDone: *const fn (*anyopaque) void,
};
};
const Pollable = struct {
value: bun.TaggedPointer,
const Tag = enum(bun.TaggedPointer.Tag) {};
pub fn init(t: Tag, p: *Poll) Pollable {
return Pollable{
.value = bun.TaggedPointer.init(p, @intFromEnum(t)),
};
}
pub fn poll(this: Pollable) *Poll {
return this.value.get(Poll);
}
pub fn tag(this: Pollable) Tag {
return @enumFromInt(this.value.data);
}
pub fn ptr(this: Pollable) *anyopaque {
return this.value.to();
}
};
pub const Timer = struct {
/// The absolute time to fire this timer next.
next: os.timespec,
/// Only used internally. If this is non-null and timer is
/// CANCELLED, then the timer is rearmed automatically with this
/// as the next time. The callback will not be called on the
/// cancellation.
reset: ?os.timespec = null,
/// Internal heap fields.
heap: heap.IntrusiveField(Timer) = .{},
state: State = .PENDING,
pub const State = enum {
/// The timer is waiting to be enabled.
PENDING,
/// The timer is active and will fire at the next time.
ACTIVE,
/// The timer has been cancelled and will not fire.
CANCELLED,
/// The timer has fired and the callback has been called.
FIRED,
};
fn less(_: void, a: *const Timer, b: *const Timer) bool {
return a.ns() < b.ns();
}
/// Returns the nanoseconds of this timer. Note that maxInt(u64) ns is
/// 584 years so if we get any overflows we just use maxInt(u64). If
/// any software is running in 584 years waiting on this timer...
/// shame on me I guess... but I'll be dead.
fn ns(self: *const Timer) u64 {
assert(self.next.tv_sec >= 0);
assert(self.next.tv_nsec >= 0);
const max = std.math.maxInt(u64);
const s_ns = std.math.mul(
u64,
@as(u64, @intCast(self.next.tv_sec)),
std.time.ns_per_s,
) catch return max;
return std.math.add(u64, s_ns, @as(u64, @intCast(self.next.tv_nsec))) catch
return max;
}
};
pub const Poll = struct {
flags: Flags,
pub const Flags = enum {
// What are we asking the event loop about?
/// Poll for readable events
poll_readable,
/// Poll for writable events
poll_writable,
/// Poll for process-related events
poll_process,
/// Poll for machport events
poll_machport,
// What did the event loop tell us?
readable,
writable,
process,
eof,
hup,
machport,
// What is the type of file descriptor?
fifo,
tty,
one_shot,
needs_rearm,
closed,
nonblocking,
was_ever_registered,
ignore_updates,
cancelled,
pub const Set = std.EnumSet(Flags);
pub const Struct = std.enums.EnumFieldStruct(Flags, bool, false);
pub fn fromKQueueEvent(kqueue_event: std.os.system.kevent64_s) Flags.Set {
var flags = Flags.Set{};
if (kqueue_event.filter == std.os.system.EVFILT_READ) {
flags.insert(Flags.readable);
log("readable", .{});
if (kqueue_event.flags & std.os.system.EV_EOF != 0) {
flags.insert(Flags.hup);
log("hup", .{});
}
} else if (kqueue_event.filter == std.os.system.EVFILT_WRITE) {
flags.insert(Flags.writable);
log("writable", .{});
if (kqueue_event.flags & std.os.system.EV_EOF != 0) {
flags.insert(Flags.hup);
log("hup", .{});
}
} else if (kqueue_event.filter == std.os.system.EVFILT_PROC) {
log("proc", .{});
flags.insert(Flags.process);
} else if (kqueue_event.filter == std.os.system.EVFILT_MACHPORT) {
log("machport", .{});
flags.insert(Flags.machport);
}
return flags;
}
pub fn fromEpollEvent(epoll: std.os.linux.epoll_event) Flags.Set {
var flags = Flags.Set{};
if (epoll.events & std.os.linux.EPOLL.IN != 0) {
flags.insert(Flags.readable);
log("readable", .{});
}
if (epoll.events & std.os.linux.EPOLL.OUT != 0) {
flags.insert(Flags.writable);
log("writable", .{});
}
if (epoll.events & std.os.linux.EPOLL.ERR != 0) {
flags.insert(Flags.eof);
log("eof", .{});
}
if (epoll.events & std.os.linux.EPOLL.HUP != 0) {
flags.insert(Flags.hup);
log("hup", .{});
}
return flags;
}
pub fn applyKQueue(
comptime action: @Type(.EnumLiteral),
tag: Pollable.Tag,
poll: *Poll,
fd: bun.FileDescriptor,
kqueue_event: *std.os.system.kevent64_s,
) void {
defer {
switch (comptime action) {
.readable => poll.flags.insert(Flags.poll_readable),
.writable => poll.flags.insert(Flags.poll_writable),
.cancel => {
if (poll.flags.contains(Flags.poll_readable)) {
poll.flags.remove(Flags.poll_readable);
} else if (poll.flags.contains(Flags.poll_writable)) {
poll.flags.remove(Flags.poll_writable);
} else {
unreachable;
}
},
else => unreachable,
}
if (comptime Environment.allow_assert and action != .cancel) {
generation_number += 1;
poll.generation_number = generation_number;
}
}
kqueue_event.* = switch (comptime action) {
.readable => .{
.ident = @as(u64, @intCast(fd)),
.filter = std.os.system.EVFILT_READ,
.data = 0,
.fflags = 0,
.udata = @intFromPtr(Pollable.init(tag, this).ptr()),
.flags = std.c.EV_ADD | one_shot_flag,
.ext = .{ generation_number, 0 },
},
.writable => .{
.ident = @as(u64, @intCast(fd)),
.filter = std.os.system.EVFILT_WRITE,
.data = 0,
.fflags = 0,
.udata = @intFromPtr(Pollable.init(tag, this).ptr()),
.flags = std.c.EV_ADD | one_shot_flag,
.ext = .{ generation_number, 0 },
},
.cancel => if (poll.flags.contains(.poll_readable)) .{
.ident = @as(u64, @intCast(fd)),
.filter = std.os.system.EVFILT_READ,
.data = 0,
.fflags = 0,
.udata = @intFromPtr(Pollable.init(tag, this).ptr()),
.flags = std.c.EV_DELETE,
.ext = .{ poll.generation_number, 0 },
} else if (poll.flags.contains(.poll_writable)) .{
.ident = @as(u64, @intCast(fd)),
.filter = std.os.system.EVFILT_WRITE,
.data = 0,
.fflags = 0,
.udata = @intFromPtr(Pollable.init(tag, this).ptr()),
.flags = std.c.EV_DELETE,
.ext = .{ poll.generation_number, 0 },
} else unreachable,
else => unreachable,
};
}
};
pub fn unregisterWithFd(this: *Poll, fd: u64) JSC.Maybe(void) {}
pub fn onUpdateKQueue(
this: *Poll,
event: std.os.system.kevent64_s,
) JSC.Maybe(void) {}
pub fn onUpdateEpoll(
this: *Poll,
event: linux.epoll_event,
) JSC.Maybe(void) {}
pub fn registerForEpoll(this: *Poll, loop: *Loop, flag: Flags, one_shot: bool, fd: u64) JSC.Maybe(void) {
const watcher_fd = loop.fd;
log("register: {s} ({d})", .{ @tagName(flag), fd });
std.debug.assert(fd != bun.invalid_fd);
if (one_shot) {
this.flags.insert(.one_shot);
}
if (comptime Environment.isLinux) {
const one_shot_flag: u32 = if (!this.flags.contains(.one_shot)) 0 else linux.EPOLL.ONESHOT;
const flags: u32 = switch (flag) {
.process,
.readable,
=> linux.EPOLL.IN | linux.EPOLL.HUP | one_shot_flag,
.writable => linux.EPOLL.OUT | linux.EPOLL.HUP | linux.EPOLL.ERR | one_shot_flag,
else => unreachable,
};
var event = linux.epoll_event{ .events = flags, .data = .{ .u64 = @intFromPtr(Pollable.init(this).ptr()) } };
var op: u32 = if (this.isRegistered() or this.flags.contains(.needs_rearm)) linux.EPOLL.CTL_MOD else linux.EPOLL.CTL_ADD;
const ctl = linux.epoll_ctl(
watcher_fd,
op,
@intCast(fd),
&event,
);
this.flags.insert(.was_ever_registered);
if (JSC.Maybe(void).errnoSys(ctl, .epoll_ctl)) |errno| {
this.deactivate(loop);
return errno;
}
} else if (comptime Environment.isMac) {
var changelist = std.mem.zeroes([2]std.os.system.kevent64_s);
// output events only include change errors
const KEVENT_FLAG_ERROR_EVENTS = 0x000002;
// The kevent() system call returns the number of events placed in
// the eventlist, up to the value given by nevents. If the time
// limit expires, then kevent() returns 0.
const rc = rc: {
while (true) {
const rc = std.os.system.kevent64(
watcher_fd,
&changelist,
1,
// The same array may be used for the changelist and eventlist.
&changelist,
// we set 0 here so that if we get an error on
// registration, it becomes errno
0,
KEVENT_FLAG_ERROR_EVENTS,
&timeout,
);
if (std.c.getErrno(rc) == .INTR) continue;
break :rc rc;
}
};
this.flags.insert(.was_ever_registered);
// If an error occurs while
// processing an element of the changelist and there is enough room
// in the eventlist, then the event will be placed in the eventlist
// with EV_ERROR set in flags and the system error in data.
if (changelist[0].flags == std.c.EV_ERROR and changelist[0].data != 0) {
return JSC.Maybe(void).errnoSys(changelist[0].data, .kevent).?;
// Otherwise, -1 will be returned, and errno will be set to
// indicate the error condition.
}
const errno = std.c.getErrno(rc);
if (errno != .SUCCESS) {
this.deactivate(loop);
return JSC.Maybe(void){
.err = bun.sys.Error.fromCode(errno, .kqueue),
};
}
} else {
bun.todo(@src(), {});
}
this.flags.insert(switch (flag) {
.readable => .poll_readable,
.process => if (comptime Environment.isLinux) .poll_readable else .poll_process,
.writable => .poll_writable,
.machport => .poll_machport,
else => unreachable,
});
this.flags.remove(.needs_rearm);
return JSC.Maybe(void).success;
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7461,7 +7461,7 @@ var require__ = __commonJS({
BlockHash = common.BlockHash,
sha1_K = [1518500249, 1859775393, 2400959708, 3395469782];
function SHA1() {
if (!(this instanceof SHA1)) return new SHA1();
if (!new.target) return new SHA1();
BlockHash.$call(this),
(this.h = [1732584193, 4023233417, 2562383102, 271733878, 3285377520]),
(this.W = new Array(80));
@@ -7526,7 +7526,7 @@ var require__2 = __commonJS({
3329325298,
];
function SHA256() {
if (!(this instanceof SHA256)) return new SHA256();
if (!new.target) return new SHA256();
BlockHash.$call(this),
(this.h = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]),
(this.k = sha256_K),
@@ -7576,7 +7576,7 @@ var require__3 = __commonJS({
var utils = require_utils4(),
SHA256 = require__2();
function SHA224() {
if (!(this instanceof SHA224)) return new SHA224();
if (!new.target) return new SHA224();
SHA256.$call(this),
(this.h = [3238371032, 914150663, 812702999, 4144912697, 4290775857, 1750603025, 1694076839, 3204075428]);
}
@@ -7632,7 +7632,7 @@ var require__4 = __commonJS({
3409855158, 1501505948, 4234509866, 1607167915, 987167468, 1816402316, 1246189591,
];
function SHA512() {
if (!(this instanceof SHA512)) return new SHA512();
if (!new.target) return new SHA512();
BlockHash.$call(this),
(this.h = [
1779033703, 4089235720, 3144134277, 2227873595, 1013904242, 4271175723, 2773480762, 1595750129, 1359893119,
@@ -7812,7 +7812,7 @@ var require__5 = __commonJS({
var utils = require_utils4(),
SHA512 = require__4();
function SHA384() {
if (!(this instanceof SHA384)) return new SHA384();
if (!new.target) return new SHA384();
SHA512.$call(this),
(this.h = [
3418070365, 3238371032, 1654270250, 914150663, 2438529370, 812702999, 355462360, 4144912697, 1731405415,
@@ -7855,7 +7855,7 @@ var require_ripemd = __commonJS({
sum32_4 = utils.sum32_4,
BlockHash = common.BlockHash;
function RIPEMD160() {
if (!(this instanceof RIPEMD160)) return new RIPEMD160();
if (!new.target) return new RIPEMD160();
BlockHash.$call(this),
(this.h = [1732584193, 4023233417, 2562383102, 271733878, 3285377520]),
(this.endian = "little");
@@ -7954,7 +7954,7 @@ var require_hmac = __commonJS({
function Hmac(hash, key, enc) {
key = exportIfKeyObject(key);
if (!(this instanceof Hmac)) return new Hmac(hash, key, enc);
if (!new.target) return new Hmac(hash, key, enc);
(this.Hash = hash),
(this.blockSize = hash.blockSize / 8),
(this.outSize = hash.outSize / 8),
@@ -8965,7 +8965,7 @@ var require_hmac_drbg = __commonJS({
utils = require_utils2(),
assert = require_minimalistic_assert();
function HmacDRBG(options) {
if (!(this instanceof HmacDRBG)) return new HmacDRBG(options);
if (!new.target) return new HmacDRBG(options);
(this.hash = options.hash),
(this.predResist = !!options.predResist),
(this.outLen = this.hash.outSize),
@@ -9196,7 +9196,7 @@ var require_ec = __commonJS({
KeyPair = require_key(),
Signature = require_signature();
function EC(options) {
if (!(this instanceof EC)) return new EC(options);
if (!new.target) return new EC(options);
typeof options == "string" &&
(assert(Object.prototype.hasOwnProperty.$call(curves, options), "Unknown curve " + options),
(options = curves[options])),

View File

@@ -75,6 +75,7 @@ function ERR_INVALID_ARG_VALUE(name, value, reason) {
return new Error(`The value '${value}' is invalid for argument '${name}'. Reason: ${reason}`);
}
var isCallingDuplexStreamConstructor = false;
// node_modules/readable-stream/lib/ours/primordials.js
var require_primordials = __commonJS({
"node_modules/readable-stream/lib/ours/primordials.js"(exports, module) {
@@ -2000,7 +2001,6 @@ var require_legacy = __commonJS({
var { ArrayIsArray, ObjectSetPrototypeOf } = require_primordials();
function Stream(options) {
if (!(this instanceof Stream)) return new Stream(options);
EE.$call(this, options);
}
Stream.prototype = {};
@@ -2265,22 +2265,42 @@ var require_readable = __commonJS({
function Readable(options) {
if (!(this instanceof Readable)) return new Readable(options);
const isDuplex = this instanceof require_duplex();
this._readableState = new ReadableState(options, this, isDuplex);
const wasCallingDuplexStreamConstructor = isCallingDuplexStreamConstructor;
isCallingDuplexStreamConstructor = false;
this._events ??= {
close: undefined,
error: undefined,
data: undefined,
end: undefined,
readable: undefined,
// Skip uncommon events...
// pause: undefined,
// resume: undefined,
// pipe: undefined,
// unpipe: undefined,
// [destroyImpl.kConstruct]: undefined,
// [destroyImpl.kDestroy]: undefined,
};
this._readableState = new ReadableState(options, this, wasCallingDuplexStreamConstructor);
if (options) {
const { read, destroy, construct, signal } = options;
if (typeof read === "function") this._read = read;
if (typeof destroy === "function") this._destroy = destroy;
if (typeof construct === "function") this._construct = construct;
if (signal && !isDuplex) addAbortSignal(signal, this);
if ($isCallable(read)) this._read = read;
if ($isCallable(destroy)) this._destroy = destroy;
if ($isCallable(construct)) this._construct = construct;
if (signal) addAbortSignal(signal, this);
}
Stream.$call(this, options);
destroyImpl.construct(this, () => {
if (this._readableState.needReadable) {
maybeReadMore(this, this._readableState);
}
});
if (!$isUndefinedOrNull(this._construct)) {
destroyImpl.construct(this, () => {
if (this._readableState.needReadable) {
maybeReadMore(this, this._readableState);
}
});
}
}
Readable.prototype = {};
ObjectSetPrototypeOf(Readable.prototype, Stream.prototype);
@@ -4387,7 +4407,10 @@ var require_duplex = __commonJS({
function Duplex(options) {
if (!(this instanceof Duplex)) return new Duplex(options);
const prevIsCallingDuplexStreamConstructor = isCallingDuplexStreamConstructor;
isCallingDuplexStreamConstructor = true;
Readable.$call(this, options);
isCallingDuplexStreamConstructor = prevIsCallingDuplexStreamConstructor;
Writable.$call(this, options);
if (options) {

View File

@@ -4123,31 +4123,31 @@ var require_lib = __commonJS({
return engine._processChunk(buffer, flushFlag);
}
function Deflate(opts) {
if (!(this instanceof Deflate)) return new Deflate(opts);
if (!new.target) return new Deflate(opts);
Zlib.$call(this, opts, binding.DEFLATE);
}
function Inflate(opts) {
if (!(this instanceof Inflate)) return new Inflate(opts);
if (!new.target) return new Inflate(opts);
Zlib.$call(this, opts, binding.INFLATE);
}
function Gzip(opts) {
if (!(this instanceof Gzip)) return new Gzip(opts);
if (!new.target) return new Gzip(opts);
Zlib.$call(this, opts, binding.GZIP);
}
function Gunzip(opts) {
if (!(this instanceof Gunzip)) return new Gunzip(opts);
if (!new.target) return new Gunzip(opts);
Zlib.$call(this, opts, binding.GUNZIP);
}
function DeflateRaw(opts) {
if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts);
if (!new.target) return new DeflateRaw(opts);
Zlib.$call(this, opts, binding.DEFLATERAW);
}
function InflateRaw(opts) {
if (!(this instanceof InflateRaw)) return new InflateRaw(opts);
if (!new.target) return new InflateRaw(opts);
Zlib.$call(this, opts, binding.INFLATERAW);
}
function Unzip(opts) {
if (!(this instanceof Unzip)) return new Unzip(opts);
if (!new.target) return new Unzip(opts);
Zlib.$call(this, opts, binding.UNZIP);
}
function isValidFlushFlag(flag) {

View File

@@ -620,7 +620,7 @@ pub export fn napi_new_instance(env: napi_env, constructor: napi_value, argc: us
pub export fn napi_instanceof(env: napi_env, object: napi_value, constructor: napi_value, result: *bool) napi_status {
log("napi_instanceof", .{});
// TODO: does this throw object_expected in node?
result.* = object.isCell() and object.isInstanceOf(env, constructor);
result.* = object.isObject() and object.isInstanceOf(env, constructor);
return .ok;
}
pub extern fn napi_get_cb_info(env: napi_env, cbinfo: napi_callback_info, argc: [*c]usize, argv: *napi_value, this_arg: *napi_value, data: [*]*anyopaque) napi_status;

View File

@@ -16,6 +16,8 @@ pub const TaggedPointer = packed struct {
_ptr: AddressableSize,
data: TagSize,
pub const Tag = TagSize;
pub inline fn init(ptr: anytype, data: TagSize) TaggedPointer {
const Ptr = @TypeOf(ptr);

View File

@@ -3,5 +3,7 @@ import { readFileSync, writeFileSync } from "fs";
// @ts-expect-error
const highlighter: (code: string) => string = globalThis[Symbol.for("Bun.lazy")]("unstable_syntaxHighlight");
// TODO: write tests for syntax highlighting
test("highlighter", () => {});
test("highlighter", () => {
expect(highlighter("`can do ${123} ${'123'} ${`123`}`").length).toBeLessThan(150);
expect(highlighter("`can do ${123} ${'123'} ${`123`}`123").length).toBeLessThan(150);
});