diff --git a/.gitattributes b/.gitattributes index 842c0220db..ef212175fd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ src/api/schema.js linguist-generated src/javascript/jsc/bindings/sqlite/sqlite3.c linguist-vendored src/javascript/jsc/bindings/sqlite/sqlite3_local.h linguist-vendored *.lockb binary diff=lockb +*.zig text eol=lf diff --git a/.gitignore b/.gitignore index 57069591ff..e8b5f0f8d1 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,6 @@ packages/bun-wasm/*.d.ts src/fallback.version src/runtime.version +*.sqlite +*.database +*.db diff --git a/Makefile b/Makefile index 5941d025cc..c2ebaf5b21 100644 --- a/Makefile +++ b/Makefile @@ -153,8 +153,8 @@ DEFAULT_LINKER_FLAGS= -pthread -ldl endif ifeq ($(OS_NAME),darwin) JSC_BUILD_STEPS += jsc-build-mac jsc-copy-headers - _MIMALLOC_FILE = libmimalloc.a - _MIMALLOC_INPUT_PATH = libmimalloc.a + _MIMALLOC_FILE = libmimalloc.o + _MIMALLOC_INPUT_PATH = CMakeFiles/mimalloc-obj.dir/src/static.c.o endif MIMALLOC_FILE= @@ -1121,7 +1121,7 @@ ifeq ($(OS_NAME),darwin) bun-link-lld-release-dsym: $(DSYMUTIL) -o $(BUN_RELEASE_BIN).dSYM $(BUN_RELEASE_BIN) -$(STRIP) $(BUN_RELEASE_BIN) - mv $(BUN_RELEASE_BIN).o /tmp/bun-$(PACKAGE_JSON_VERSION).o + cp $(BUN_RELEASE_BIN).o /tmp/bun-$(PACKAGE_JSON_VERSION).o copy-to-bun-release-dir-dsym: gzip --keep -c $(PACKAGE_DIR)/bun.dSYM > $(BUN_RELEASE_DIR)/bun.dSYM.gz @@ -1144,7 +1144,7 @@ wasm-return1: EMIT_LLVM_FOR_RELEASE= -emit-llvm EMIT_LLVM_FOR_DEBUG= -EMIT_LLVM=$(EMIT_LLVM_FOR_RELEASE) +EMIT_LLVM=$(EMIT_LLVM_FOR_DEBUG) # We do this outside of build.zig for performance reasons # The C compilation stuff with build.zig is really slow and we don't need to run this as often as the rest diff --git a/src/baby_list.zig b/src/baby_list.zig new file mode 100644 index 0000000000..b1bfc1786b --- /dev/null +++ b/src/baby_list.zig @@ -0,0 +1,104 @@ +const std = @import("std"); +const Environment = @import("./env.zig"); + +/// This is like ArrayList except it stores the length and capacity as u32 +/// In practice, it is very unusual to have lengths above 4 GB +/// +/// This lets us have array lists which occupy the same amount of space as a slice +pub fn BabyList(comptime Type: type) type { + return struct { + const ListType = @This(); + ptr: [*]Type = undefined, + len: u32 = 0, + cap: u32 = 0, + + pub inline fn init(items: []const Type) ListType { + @setRuntimeSafety(false); + return ListType{ + // Remove the const qualifier from the items + .ptr = @intToPtr([*]Type, @ptrToInt(items.ptr)), + + .len = @truncate(u32, items.len), + .cap = @truncate(u32, items.len), + }; + } + + pub inline fn fromList(list_: anytype) ListType { + @setRuntimeSafety(false); + + if (comptime Environment.allow_assert) { + std.debug.assert(list_.items.len <= list_.capacity); + } + + return ListType{ + .ptr = list_.items.ptr, + .len = @truncate(u32, list_.items.len), + .cap = @truncate(u32, list_.capacity), + }; + } + + pub fn update(this: *ListType, list_: anytype) void { + @setRuntimeSafety(false); + this.ptr = list_.items.ptr; + this.len = @truncate(u32, list_.items.len); + this.cap = @truncate(u32, list_.capacity); + + if (comptime Environment.allow_assert) { + std.debug.assert(this.len <= this.cap); + } + } + + pub fn list(this: ListType) std.ArrayListUnmanaged(Type) { + return std.ArrayListUnmanaged(Type){ + .items = this.ptr[0..this.len], + .capacity = this.cap, + }; + } + + pub fn listManaged(this: ListType, allocator: std.mem.Allocator) std.ArrayList(Type) { + return std.ArrayList(Type){ + .items = this.ptr[0..this.len], + .capacity = this.cap, + .allocator = allocator, + }; + } + + pub inline fn first(this: ListType) ?*Type { + return if (this.len > 0) this.ptr[0] else @as(?*Type, null); + } + + pub inline fn last(this: ListType) ?*Type { + return if (this.len > 0) &this.ptr[this.len - 1] else @as(?*Type, null); + } + + pub inline fn first_(this: ListType) Type { + return this.ptr[0]; + } + + pub fn one(allocator: std.mem.Allocator, value: Type) !ListType { + var items = try allocator.alloc(Type, 1); + items[0] = value; + return ListType{ + .ptr = @ptrCast([*]Type, items.ptr), + .len = 1, + .cap = 1, + }; + } + + pub inline fn @"[0]"(this: ListType) Type { + return this.ptr[0]; + } + const OOM = error{OutOfMemory}; + + pub fn push(this: *ListType, allocator: std.mem.Allocator, value: Type) OOM!void { + var list_ = this.list(); + try list_.append(allocator, value); + this.update(list_); + } + + pub inline fn slice(this: ListType) []Type { + @setRuntimeSafety(false); + return this.ptr[0..this.len]; + } + }; +} diff --git a/src/deps/uws.zig b/src/deps/uws.zig index 101bac88e0..b97555efce 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -1,4 +1,6 @@ const Api = @import("../api/schema.zig").Api; +const std = @import("std"); +const Environment = @import("../env.zig"); pub const u_int8_t = u8; pub const u_int16_t = c_ushort; pub const u_int32_t = c_uint; @@ -17,10 +19,13 @@ pub const Loop = opaque { return us_wakeup_loop(this); } + pub fn tick(this: *Loop) void { + us_loop_tick(this); + } + pub fn nextTick(this: *Loop, comptime UserType: type, user_data: UserType, comptime deferCallback: fn (ctx: UserType) void) void { const Handler = struct { pub fn callback(data: *anyopaque) callconv(.C) void { - const std = @import("std"); deferCallback(@ptrCast(UserType, @alignCast(@alignOf(std.meta.Child(UserType)), data))); } }; @@ -34,7 +39,6 @@ pub const Loop = opaque { return uws_loop_removePostHandler(handler.loop, callback); } pub fn callback(data: *anyopaque, _: *Loop) callconv(.C) void { - const std = @import("std"); callback(@ptrCast(UserType, @alignCast(@alignOf(std.meta.Child(UserType)), data))); } }; @@ -56,6 +60,7 @@ pub const Loop = opaque { extern fn us_loop_free(loop: ?*Loop) void; extern fn us_loop_ext(loop: ?*Loop) ?*anyopaque; extern fn us_loop_run(loop: ?*Loop) void; + extern fn us_loop_tick(loop: ?*Loop) void; extern fn us_wakeup_loop(loop: ?*Loop) void; extern fn us_loop_integrate(loop: ?*Loop) void; extern fn us_loop_iteration_number(loop: ?*Loop) c_longlong; @@ -104,7 +109,68 @@ extern fn us_socket_context_adopt_socket(ssl: c_int, context: ?*us_socket_contex extern fn us_create_child_socket_context(ssl: c_int, context: ?*us_socket_context_t, context_ext_size: c_int) ?*us_socket_context_t; pub const Poll = opaque { - extern fn us_create_poll(loop: ?*Loop, fallthrough: c_int, ext_size: c_uint) *Poll; + pub fn create( + loop: *Loop, + comptime Data: type, + file: c_int, + val: Data, + fallthrough: bool, + flags: Flags, + callback: CallbackType, + ) ?*Poll { + var poll = us_create_callback(loop, @as(c_int, @boolToInt(fallthrough)), file, @sizeOf(Data)); + if (comptime Data != void) { + poll.data(Data).* = val; + } + var flags_int: c_int = 0; + if (flags.read) { + flags_int |= Flags.read_flag; + } + + if (flags.write) { + flags_int |= Flags.write_flag; + } + + us_callback_set(poll, flags_int, callback); + return poll; + } + + pub fn stop(self: *Poll, loop: *Loop) void { + us_poll_stop(self, loop); + } + + pub fn data(self: *Poll, comptime Data: type) *Data { + return us_poll_ext(self).?; + } + + pub fn fd(self: *Poll) @import("std").os.fd_t { + return @intCast(@import("std").os.fd_t, us_poll_fd(self)); + } + + pub fn start(self: *Poll, poll_type: Flags) void { + // us_poll_start(self, loop: ?*Loop, events: c_int) + _ = self; + _ = poll_type; + } + + pub const Flags = struct { + read: bool = false, + write: bool = false, + + //#define LIBUS_SOCKET_READABLE + pub const read_flag = if (Environment.isLinux) std.os.linux.EPOLL.IN else 1; + // #define LIBUS_SOCKET_WRITABLE + pub const write_flag = if (Environment.isLinux) std.os.linux.EPOLL.OUT else 2; + }; + + pub fn deinit(self: *Poll) void { + us_poll_free(self); + } + + // (void* userData, int fd, int events, int error, struct us_poll_t *poll) + pub const CallbackType = fn (?*anyopaque, c_int, c_int, c_int, *Poll) void; + extern fn us_create_callback(loop: ?*Loop, fallthrough: c_int, fd: c_int, ext_size: c_uint) *Poll; + extern fn us_callback_set(poll: *Poll, events: c_int, callback: CallbackType) *Poll; extern fn us_poll_free(p: ?*Poll, loop: ?*Loop) void; extern fn us_poll_init(p: ?*Poll, fd: c_int, poll_type: c_int) void; extern fn us_poll_start(p: ?*Poll, loop: ?*Loop, events: c_int) void; diff --git a/src/feature_flags.zig b/src/feature_flags.zig index 02d09faeeb..a9665ddd07 100644 --- a/src/feature_flags.zig +++ b/src/feature_flags.zig @@ -91,3 +91,4 @@ pub const atomic_file_watcher = env.isLinux; pub const node_streams = env.isDebug or env.isTest; pub const simd = true; +pub const latin1_is_now_ascii = true; diff --git a/src/global.zig b/src/global.zig index 9c506404ed..5a5be13c0e 100644 --- a/src/global.zig +++ b/src/global.zig @@ -159,3 +159,5 @@ pub fn span(ptr: anytype) std.mem.Span(@TypeOf(ptr)) { pub const IdentityContext = @import("./identity_context.zig").IdentityContext; pub const ArrayIdentityContext = @import("./identity_context.zig").ArrayIdentityContext; +pub const BabyList = @import("./baby_list.zig").BabyList; +pub const ByteList = BabyList(u8); diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig index 70406ace5b..7605cfef7b 100644 --- a/src/javascript/jsc/api/html_rewriter.zig +++ b/src/javascript/jsc/api/html_rewriter.zig @@ -408,6 +408,124 @@ pub const HTMLRewriter = struct { this.context.deinit(bun.default_allocator); } }; + + // pub const StreamOutputSink = struct { + // global: *JSGlobalObject, + // rewriter: *LOLHTML.HTMLRewriter, + // context: LOLHTMLContext, + // response: *Response, + // input: JSC.WebCore.Blob = undefined, + // pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue { + // var result = bun.default_allocator.create(Response) catch unreachable; + // var sink = bun.default_allocator.create(StreamOutputSink) catch unreachable; + // sink.* = StreamOutputSink{ + // .global = global, + // .rewriter = undefined, + // .context = context, + // .response = result, + // }; + + // for (sink.context.document_handlers.items) |doc| { + // doc.ctx = sink; + // } + // for (sink.context.element_handlers.items) |doc| { + // doc.ctx = sink; + // } + + // sink.rewriter = builder.build( + // .UTF8, + // .{ + // .preallocated_parsing_buffer_size = @maximum(original.body.len(), 1024), + // .max_allowed_memory_usage = std.math.maxInt(u32), + // }, + // false, + // StreamOutputSink, + // sink, + // StreamOutputSink.write, + // StreamOutputSink.done, + // ) catch { + // sink.deinit(); + // bun.default_allocator.destroy(result); + + // return throwLOLHTMLError(global); + // }; + + // result.* = Response{ + // .allocator = bun.default_allocator, + // .body = .{ + // .init = .{ + // .status_code = 200, + // }, + // .value = .{ + // .Locked = .{ + // .global = global, + // .task = sink, + // }, + // }, + // }, + // }; + + // result.body.init.headers = original.body.init.headers; + // result.body.init.method = original.body.init.method; + // result.body.init.status_code = original.body.init.status_code; + + // result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable; + // result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable; + + // var input: JSC.WebCore.Blob = original.body.value.use(); + + // const is_pending = input.needsToReadFile(); + // defer if (!is_pending) input.detach(); + + // if (is_pending) { + // input.doReadFileInternal(*StreamOutputSink, sink, onFinishedLoading, global); + // } else if (sink.runOutputSink(input.sharedView(), false, false)) |error_value| { + // return error_value; + // } + + // // Hold off on cloning until we're actually done. + + // return JSC.JSValue.fromRef( + // Response.makeMaybePooled(sink.global.ref(), sink.response), + // ); + // } + + // pub fn runOutputSink( + // sink: *StreamOutputSink, + // bytes: []const u8, + // is_async: bool, + // free_bytes_on_end: bool, + // ) ?JSValue { + // defer if (free_bytes_on_end) + // bun.default_allocator.free(bun.constStrToU8(bytes)); + + // return null; + // } + + // pub const Sync = enum { suspended, pending, done }; + + // pub fn done(this: *StreamOutputSink) void { + // var prev_value = this.response.body.value; + // var bytes = this.bytes.toOwnedSliceLeaky(); + // this.response.body.value = .{ + // .Blob = JSC.WebCore.Blob.init(bytes, this.bytes.allocator, this.global), + // }; + // prev_value.resolve( + // &this.response.body.value, + // this.global, + // ); + // } + + // pub fn write(this: *StreamOutputSink, bytes: []const u8) void { + // this.bytes.append(bytes) catch unreachable; + // } + + // pub fn deinit(this: *StreamOutputSink) void { + // this.bytes.deinit(); + + // this.context.deinit(bun.default_allocator); + // } + // }; }; const DocumentHandler = struct { diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig index a76ddf9ab8..8d00207241 100644 --- a/src/javascript/jsc/api/server.zig +++ b/src/javascript/jsc/api/server.zig @@ -1522,7 +1522,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { this.listen_callback = JSC.AnyTask.New(ThisServer, run).init(this); this.vm.eventLoop().enqueueTask(JSC.Task.init(&this.listen_callback)); if (needs_post_handler) { - _ = this.vm.uws_event_loop.?.addPostHandler(*JSC.VirtualMachine.EventLoop, this.vm.eventLoop(), JSC.VirtualMachine.EventLoop.tick); + _ = this.vm.uws_event_loop.?.addPostHandler(*JSC.EventLoop, this.vm.eventLoop(), JSC.EventLoop.tick); } } diff --git a/src/javascript/jsc/bindings/BlobReadableStreamSource.cpp b/src/javascript/jsc/bindings/BlobReadableStreamSource.cpp index 4e85624acc..b697f62d95 100644 --- a/src/javascript/jsc/bindings/BlobReadableStreamSource.cpp +++ b/src/javascript/jsc/bindings/BlobReadableStreamSource.cpp @@ -94,8 +94,6 @@ void BlobReadableStreamSource::doStart() } return; } - - JSC::gcProtect(&this->controller().jsController()); } void BlobReadableStreamSource::doPull() { @@ -106,8 +104,6 @@ void BlobReadableStreamSource::doPull() return; } - - JSC::gcProtect(&this->controller().jsController()); } void BlobReadableStreamSource::doCancel() @@ -119,16 +115,12 @@ void BlobReadableStreamSource::close() { if (!m_isCancelled) controller().close(); - - JSC::gcUnprotect(&this->controller().jsController()); } void BlobReadableStreamSource::enqueue(JSC::JSValue value) { if (!m_isCancelled) controller().enqueue(value); - - JSC::gcUnprotect(&this->controller().jsController()); } bool BlobReadableStreamSource::enqueue(const uint8_t* ptr, size_t size) @@ -137,7 +129,6 @@ bool BlobReadableStreamSource::enqueue(const uint8_t* ptr, size_t size) if (m_isCancelled) return false; - JSC::gcUnprotect(&this->controller().jsController()); auto arrayBuffer = JSC::ArrayBuffer::tryCreate(ptr, size); if (!arrayBuffer) return false; @@ -154,8 +145,6 @@ bool BlobReadableStreamSource::enqueue(uint8_t* ptr, size_t read, void* ctx, JST return false; } - JSC::gcUnprotect(&this->controller().jsController()); - auto buffer = ArrayBuffer::createFromBytes(ptr, read, createSharedTask([bytesDeallocator, ctx](void* p) { if (bytesDeallocator) { bytesDeallocator(p, ctx); diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index f21107634d..aa4dc3105c 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -97,6 +97,7 @@ #include "napi.h" #include "JSZigGlobalObjectBuiltins.h" #include "JSSQLStatement.h" +#include "bun-base64.h" using JSGlobalObject = JSC::JSGlobalObject; using Exception = JSC::Exception; diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp index dcb48e2919..dc43afd79d 100644 --- a/src/javascript/jsc/bindings/bindings.cpp +++ b/src/javascript/jsc/bindings/bindings.cpp @@ -2582,6 +2582,11 @@ void JSC__VM__holdAPILock(JSC__VM* arg0, void* ctx, void (*callback)(void* arg0) callback(ctx); } +void JSC__JSString__iterator(JSC__JSString* arg0, JSC__JSGlobalObject* arg1, void* arg2) +{ + jsstring_iterator* iter = (jsstring_iterator*)arg2; + arg0->value(iter); +} void JSC__VM__deferGC(JSC__VM* vm, void* ctx, void (*callback)(void* arg0)) { JSC::GCDeferralContext deferralContext(reinterpret_cast(vm)); diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index ceeacfff72..cce3a40284 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -809,6 +809,10 @@ pub const JSString = extern struct { return shim.cppFn("value", .{ this, globalObject }); } + pub fn iterator(this: *JSString, globalObject: *JSGlobalObject, iter: *anyopaque) void { + return shim.cppFn("iterator", .{ this, globalObject, iter }); + } + pub fn length(this: *const JSString) usize { return shim.cppFn("length", .{ this, @@ -833,7 +837,20 @@ pub const JSString = extern struct { }); } - pub const Extern = [_][]const u8{ "toObject", "eql", "value", "length", "is8Bit", "createFromOwnedString", "createFromString" }; + pub const JStringIteratorAppend8Callback = fn (*Iterator, [*]const u8, u32) callconv(.C) void; + pub const JStringIteratorAppend16Callback = fn (*Iterator, [*]const u16, u32) callconv(.C) void; + pub const JStringIteratorWrite8Callback = fn (*Iterator, [*]const u8, u32, u32) callconv(.C) void; + pub const JStringIteratorWrite16Callback = fn (*Iterator, [*]const u16, u32, u32) callconv(.C) void; + pub const Iterator = extern struct { + data: ?*anyopaque, + stop: u8, + append8: ?JStringIteratorAppend8Callback, + append16: ?JStringIteratorAppend16Callback, + write8: ?JStringIteratorWrite8Callback, + write16: ?JStringIteratorWrite16Callback, + }; + + pub const Extern = [_][]const u8{ "iterator", "toObject", "eql", "value", "length", "is8Bit", "createFromOwnedString", "createFromString" }; }; pub const JSPromiseRejectionOperation = enum(u32) { diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index a0f87d57df..8b5c3bfa4f 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1653120181 +//-- AUTOGENERATED FILE -- 1653281389 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index c5964f0018..ad00b46647 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1653120181 +//-- AUTOGENERATED FILE -- 1653281389 #pragma once #include @@ -300,6 +300,7 @@ CPP_DECL JSC__JSString* JSC__JSString__createFromOwnedString(JSC__VM* arg0, cons CPP_DECL JSC__JSString* JSC__JSString__createFromString(JSC__VM* arg0, const WTF__String* arg1); CPP_DECL bool JSC__JSString__eql(const JSC__JSString* arg0, JSC__JSGlobalObject* arg1, JSC__JSString* arg2); CPP_DECL bool JSC__JSString__is8Bit(const JSC__JSString* arg0); +CPP_DECL void JSC__JSString__iterator(JSC__JSString* arg0, JSC__JSGlobalObject* arg1, void* arg2); CPP_DECL size_t JSC__JSString__length(const JSC__JSString* arg0); CPP_DECL JSC__JSObject* JSC__JSString__toObject(JSC__JSString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL bWTF__String JSC__JSString__value(JSC__JSString* arg0, JSC__JSGlobalObject* arg1); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 0c09354096..44ce2e7945 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -180,6 +180,7 @@ pub extern fn JSC__JSString__createFromOwnedString(arg0: [*c]JSC__VM, arg1: [*c] pub extern fn JSC__JSString__createFromString(arg0: [*c]JSC__VM, arg1: [*c]const WTF__String) [*c]JSC__JSString; pub extern fn JSC__JSString__eql(arg0: [*c]const JSC__JSString, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]JSC__JSString) bool; pub extern fn JSC__JSString__is8Bit(arg0: [*c]const JSC__JSString) bool; +pub extern fn JSC__JSString__iterator(arg0: [*c]JSC__JSString, arg1: [*c]JSC__JSGlobalObject, arg2: ?*anyopaque) void; pub extern fn JSC__JSString__length(arg0: [*c]const JSC__JSString) usize; pub extern fn JSC__JSString__toObject(arg0: [*c]JSC__JSString, arg1: [*c]JSC__JSGlobalObject) [*c]JSC__JSObject; pub extern fn JSC__JSString__value(arg0: [*c]JSC__JSString, arg1: [*c]JSC__JSGlobalObject) bWTF__String; diff --git a/src/javascript/jsc/bindings/webcore/JSTextEncoder.cpp b/src/javascript/jsc/bindings/webcore/JSTextEncoder.cpp index bc348276c3..c12c94d56e 100644 --- a/src/javascript/jsc/bindings/webcore/JSTextEncoder.cpp +++ b/src/javascript/jsc/bindings/webcore/JSTextEncoder.cpp @@ -18,36 +18,42 @@ Boston, MA 02110-1301, USA. */ -#include "config.h" +#include "root.h" + #include "JSTextEncoder.h" +#include "JavaScriptCore/JavaScript.h" +#include "JavaScriptCore/APICast.h" + +#include "JavaScriptCore/FunctionPrototype.h" +#include "JavaScriptCore/HeapAnalyzer.h" +#include "JavaScriptCore/JSDestructibleObjectHeapCellType.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "JavaScriptCore/SlotVisitorMacros.h" +#include "JavaScriptCore/SubspaceInlines.h" +#include "wtf/GetPtr.h" +#include "wtf/PointerPreparations.h" +#include "wtf/URL.h" +// #include "JavaScriptCore/JSTypedArrays.h" + +#include "GCDefferalContext.h" #include "ActiveDOMObject.h" #include "ExtendedDOMClientIsoSubspaces.h" #include "ExtendedDOMIsoSubspaces.h" #include "JSDOMAttribute.h" -#include "JSDOMBinding.h" +// #include "JSDOMBinding.h" #include "JSDOMConstructor.h" -#include "JSDOMConvertBufferSource.h" +// #include "JSDOMConvertBufferSource.h" #include "JSDOMConvertInterface.h" #include "JSDOMConvertNumbers.h" #include "JSDOMConvertStrings.h" -#include "JSDOMExceptionHandling.h" -#include "JSDOMGlobalObject.h" +// #include "JSDOMExceptionHandling.h" +// #include "JSDOMGlobalObject.h" #include "JSDOMGlobalObjectInlines.h" #include "JSDOMOperation.h" #include "JSDOMWrapperCache.h" -#include "ScriptExecutionContext.h" -#include "WebCoreJSClientData.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// #include "ScriptExecutionContext.h" +// #include "WebCoreJSClientData.h" namespace WebCore { using namespace JSC; @@ -267,6 +273,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsTextEncoder_encoding, (JSGlobalObject * lexicalGlobal extern "C" JSC::EncodedJSValue TextEncoder__encode(JSC::JSGlobalObject* lexicalGlobalObject, const ZigString*); extern "C" JSC::EncodedJSValue TextEncoder__encodeInto(JSC::JSGlobalObject* lexicalGlobalObject, const ZigString*, void* ptr, size_t len); +extern "C" JSC::EncodedJSValue TextEncoder__encodeRopeString(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSString* str); static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation::ClassParameter castedThis) { @@ -274,14 +281,16 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeBody(JSC: auto throwScope = DECLARE_THROW_SCOPE(vm); UNUSED_PARAM(throwScope); UNUSED_PARAM(callFrame); - auto& impl = castedThis->wrapped(); EnsureStillAliveScope argument0 = callFrame->argument(0); - if (argument0.value().isUndefined()) { - return JSValue::encode(JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, lexicalGlobalObject->m_typedArrayUint8.get(lexicalGlobalObject), 0)); + JSC::JSString* input = argument0.value().toStringOrNull(lexicalGlobalObject); + if (input && input->is8Bit() && input->isRope()) { + auto encodedValue = TextEncoder__encodeRopeString(lexicalGlobalObject, input); + if (!JSC::JSValue::decode(encodedValue).isUndefined()) { + RELEASE_AND_RETURN(throwScope, encodedValue); + } } - auto input = argument0.value().toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - auto str = Zig::toZigString(WTFMove(input)); + + auto str = Zig::toZigString(input->tryGetValue(lexicalGlobalObject)); auto res = TextEncoder__encode(lexicalGlobalObject, &str); if (UNLIKELY(JSC::JSValue::decode(res).isObject() && JSC::JSValue::decode(res).getObject()->isErrorInstance())) { throwScope.throwException(lexicalGlobalObject, JSC::JSValue::decode(res)); @@ -308,10 +317,15 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeIntoBody( auto source = argument0.value().toWTFString(lexicalGlobalObject); RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); - auto destination = convert>(*lexicalGlobalObject, argument1.value(), [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentTypeError(lexicalGlobalObject, scope, 1, "destination", "TextEncoder", "encodeInto", "Uint8Array"); }); + auto* destination = JSC::jsDynamicCast(argument1.value()); + if (!destination) { + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Uint8Array"_s); + return encodedJSValue(); + } + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); auto str = Zig::toZigString(WTFMove(source)); - auto res = TextEncoder__encodeInto(lexicalGlobalObject, &str, destination->data(), destination->length()); + auto res = TextEncoder__encodeInto(lexicalGlobalObject, &str, destination->vector(), destination->length()); if (UNLIKELY(JSC::JSValue::decode(res).isObject() && JSC::JSValue::decode(res).getObject()->isErrorInstance())) { throwScope.throwException(lexicalGlobalObject, JSC::JSValue::decode(res)); return encodedJSValue(); diff --git a/src/javascript/jsc/bindings/webcore/JSTextEncoder.h b/src/javascript/jsc/bindings/webcore/JSTextEncoder.h index ce0ffd3c90..e37b962567 100644 --- a/src/javascript/jsc/bindings/webcore/JSTextEncoder.h +++ b/src/javascript/jsc/bindings/webcore/JSTextEncoder.h @@ -20,6 +20,8 @@ #pragma once +#include "root.h" + #include "JSDOMConvertDictionary.h" #include "JSDOMWrapper.h" #include "TextEncoder.h" @@ -58,6 +60,7 @@ public: } static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + protected: JSTextEncoder(JSC::Structure*, JSDOMGlobalObject&, Ref&&); @@ -94,5 +97,4 @@ template<> TextEncoder::EncodeIntoResult convertDictionary #include -#include namespace WebCore { diff --git a/src/javascript/jsc/event_loop.zig b/src/javascript/jsc/event_loop.zig new file mode 100644 index 0000000000..43f4f9ac9a --- /dev/null +++ b/src/javascript/jsc/event_loop.zig @@ -0,0 +1,592 @@ +const std = @import("std"); +const JSC = @import("javascript_core"); +const JSGlobalObject = JSC.JSGlobalObject; +const VirtualMachine = JSC.VirtualMachine; +const Lock = @import("../../lock.zig").Lock; +const Microtask = JSC.Microtask; +const bun = @import("../../global.zig"); +const Environment = bun.Environment; +const Fetch = JSC.WebCore.Fetch; +const WebCore = JSC.WebCore; +const Bun = JSC.API.Bun; +const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion; +const CopyFilePromiseTask = WebCore.Blob.Store.CopyFile.CopyFilePromiseTask; +const AsyncTransformTask = @import("./api/transpiler.zig").TransformTask.AsyncTransformTask; +const BunTimerTimeoutTask = Bun.Timer.Timeout.TimeoutTask; +const ReadFileTask = WebCore.Blob.Store.ReadFile.ReadFileTask; +const WriteFileTask = WebCore.Blob.Store.WriteFile.WriteFileTask; +const napi_async_work = JSC.napi.napi_async_work; +const FetchTasklet = Fetch.FetchTasklet; +const JSValue = JSC.JSValue; +const js = JSC.C; +const WorkPool = @import("../../work_pool.zig").WorkPool; +const WorkPoolTask = @import("../../work_pool.zig").Task; +const NetworkThread = @import("http").NetworkThread; + +pub fn ConcurrentPromiseTask(comptime Context: type) type { + return struct { + const This = @This(); + ctx: *Context, + task: WorkPoolTask = .{ .callback = runFromThreadPool }, + event_loop: *JSC.EventLoop, + allocator: std.mem.Allocator, + promise: JSValue, + globalThis: *JSGlobalObject, + + pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { + var this = try allocator.create(This); + this.* = .{ + .event_loop = VirtualMachine.vm.event_loop, + .ctx = value, + .allocator = allocator, + .promise = JSValue.createInternalPromise(globalThis), + .globalThis = globalThis, + }; + js.JSValueProtect(globalThis.ref(), this.promise.asObjectRef()); + VirtualMachine.vm.active_tasks +|= 1; + return this; + } + + pub fn runFromThreadPool(task: *WorkPoolTask) void { + var this = @fieldParentPtr(This, "task", task); + Context.run(this.ctx); + this.onFinish(); + } + + pub fn runFromJS(this: This) void { + var promise_value = this.promise; + var promise = promise_value.asInternalPromise() orelse { + if (comptime @hasDecl(Context, "deinit")) { + @call(.{}, Context.deinit, .{this.ctx}); + } + return; + }; + + var ctx = this.ctx; + + js.JSValueUnprotect(this.globalThis.ref(), promise_value.asObjectRef()); + ctx.then(promise); + } + + pub fn schedule(this: *This) void { + WorkPool.schedule(&this.task); + } + + pub fn onFinish(this: *This) void { + this.event_loop.enqueueTaskConcurrent(Task.init(this)); + } + + pub fn deinit(this: *This) void { + this.allocator.destroy(this); + } + }; +} + +pub fn SerialPromiseTask(comptime Context: type) type { + return struct { + const SerialWorkPool = @import("../../work_pool.zig").NewWorkPool(1); + const This = @This(); + + ctx: *Context, + task: WorkPoolTask = .{ .callback = runFromThreadPool }, + event_loop: *JSC.EventLoop, + allocator: std.mem.Allocator, + promise: JSValue, + globalThis: *JSGlobalObject, + + pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { + var this = try allocator.create(This); + this.* = .{ + .event_loop = VirtualMachine.vm.event_loop, + .ctx = value, + .allocator = allocator, + .promise = JSValue.createInternalPromise(globalThis), + .globalThis = globalThis, + }; + js.JSValueProtect(globalThis.ref(), this.promise.asObjectRef()); + VirtualMachine.vm.active_tasks +|= 1; + return this; + } + + pub fn runFromThreadPool(task: *WorkPoolTask) void { + var this = @fieldParentPtr(This, "task", task); + Context.run(this.ctx); + this.onFinish(); + } + + pub fn runFromJS(this: This) void { + var promise_value = this.promise; + var promise = promise_value.asInternalPromise() orelse { + if (comptime @hasDecl(Context, "deinit")) { + @call(.{}, Context.deinit, .{this.ctx}); + } + return; + }; + + var ctx = this.ctx; + + js.JSValueUnprotect(this.globalThis.ref(), promise_value.asObjectRef()); + ctx.then(promise, this.globalThis); + } + + pub fn schedule(this: *This) void { + SerialWorkPool.schedule(&this.task); + } + + pub fn onFinish(this: *This) void { + this.event_loop.enqueueTaskConcurrent(Task.init(this)); + } + + pub fn deinit(this: *This) void { + this.allocator.destroy(this); + } + }; +} + +pub fn IOTask(comptime Context: type) type { + return struct { + const This = @This(); + ctx: *Context, + task: NetworkThread.Task = .{ .callback = runFromThreadPool }, + event_loop: *JSC.EventLoop, + allocator: std.mem.Allocator, + globalThis: *JSGlobalObject, + + pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { + var this = try allocator.create(This); + this.* = .{ + .event_loop = VirtualMachine.vm.eventLoop(), + .ctx = value, + .allocator = allocator, + .globalThis = globalThis, + }; + return this; + } + + pub fn runFromThreadPool(task: *NetworkThread.Task) void { + var this = @fieldParentPtr(This, "task", task); + Context.run(this.ctx, this); + } + + pub fn runFromJS(this: This) void { + var ctx = this.ctx; + ctx.then(this.globalThis); + } + + pub fn schedule(this: *This) void { + NetworkThread.init() catch return; + NetworkThread.global.pool.schedule(NetworkThread.Batch.from(&this.task)); + } + + pub fn onFinish(this: *This) void { + this.event_loop.enqueueTaskConcurrent(Task.init(this)); + } + + pub fn deinit(this: *This) void { + var allocator = this.allocator; + this.* = undefined; + allocator.destroy(this); + } + }; +} + +pub fn AsyncNativeCallbackTask(comptime Context: type) type { + return struct { + const This = @This(); + ctx: *Context, + task: WorkPoolTask = .{ .callback = runFromThreadPool }, + event_loop: *JSC.EventLoop, + allocator: std.mem.Allocator, + globalThis: *JSGlobalObject, + + pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { + var this = try allocator.create(This); + this.* = .{ + .event_loop = VirtualMachine.vm.eventLoop(), + .ctx = value, + .allocator = allocator, + .globalThis = globalThis, + }; + return this; + } + + pub fn runFromThreadPool(task: *WorkPoolTask) void { + var this = @fieldParentPtr(This, "task", task); + Context.run(this.ctx, this); + } + + pub fn runFromJS(this: This) void { + this.ctx.runFromJS(this.globalThis); + } + + pub fn schedule(this: *This) void { + WorkPool.get().schedule(WorkPool.schedule(&this.task)); + } + + pub fn onFinish(this: *This) void { + this.event_loop.enqueueTaskConcurrent(Task.init(this)); + } + + pub fn deinit(this: *This) void { + var allocator = this.allocator; + this.* = undefined; + allocator.destroy(this); + } + }; +} + +pub const AnyTask = struct { + ctx: ?*anyopaque, + callback: fn (*anyopaque) void, + + pub fn run(this: *AnyTask) void { + @setRuntimeSafety(false); + this.callback(this.ctx.?); + } + + pub fn New(comptime Type: type, comptime Callback: anytype) type { + return struct { + pub fn init(ctx: *Type) AnyTask { + return AnyTask{ + .callback = wrap, + .ctx = ctx, + }; + } + + pub fn wrap(this: ?*anyopaque) void { + Callback(@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))); + } + }; + } +}; +const ThreadSafeFunction = JSC.napi.ThreadSafeFunction; + +// const PromiseTask = JSInternalPromise.Completion.PromiseTask; +pub const Task = TaggedPointerUnion(.{ + FetchTasklet, + Microtask, + AsyncTransformTask, + BunTimerTimeoutTask, + ReadFileTask, + CopyFilePromiseTask, + WriteFileTask, + AnyTask, + napi_async_work, + ThreadSafeFunction, + // PromiseTask, + // TimeoutTasklet, +}); + +pub const EventLoop = struct { + ready_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + pending_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + io_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), + tasks: Queue = undefined, + concurrent_tasks: Queue = undefined, + concurrent_lock: Lock = Lock.init(), + global: *JSGlobalObject = undefined, + virtual_machine: *VirtualMachine = undefined, + pub const Queue = std.fifo.LinearFifo(Task, .Dynamic); + + pub fn tickWithCount(this: *EventLoop) u32 { + var finished: u32 = 0; + var global = this.global; + var vm_ = this.virtual_machine; + while (this.tasks.readItem()) |task| { + switch (task.tag()) { + .Microtask => { + var micro: *Microtask = task.as(Microtask); + micro.run(global); + finished += 1; + }, + .FetchTasklet => { + var fetch_task: *Fetch.FetchTasklet = task.get(Fetch.FetchTasklet).?; + fetch_task.onDone(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(AsyncTransformTask)) => { + var transform_task: *AsyncTransformTask = task.get(AsyncTransformTask).?; + transform_task.*.runFromJS(); + transform_task.deinit(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(CopyFilePromiseTask)) => { + var transform_task: *CopyFilePromiseTask = task.get(CopyFilePromiseTask).?; + transform_task.*.runFromJS(); + transform_task.deinit(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(JSC.napi.napi_async_work)) => { + var transform_task: *JSC.napi.napi_async_work = task.get(JSC.napi.napi_async_work).?; + transform_task.*.runFromJS(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(BunTimerTimeoutTask)) => { + var transform_task: *BunTimerTimeoutTask = task.get(BunTimerTimeoutTask).?; + transform_task.*.runFromJS(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(ReadFileTask)) => { + var transform_task: *ReadFileTask = task.get(ReadFileTask).?; + transform_task.*.runFromJS(); + transform_task.deinit(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(WriteFileTask)) => { + var transform_task: *WriteFileTask = task.get(WriteFileTask).?; + transform_task.*.runFromJS(); + transform_task.deinit(); + finished += 1; + vm_.active_tasks -|= 1; + }, + @field(Task.Tag, @typeName(AnyTask)) => { + var any: *AnyTask = task.get(AnyTask).?; + any.run(); + finished += 1; + vm_.active_tasks -|= 1; + }, + else => unreachable, + } + } + + if (finished > 0) { + _ = this.pending_tasks_count.fetchSub(finished, .Monotonic); + } + + return finished; + } + + pub fn tickConcurrent(this: *EventLoop) void { + if (this.ready_tasks_count.load(.Monotonic) > 0) { + this.concurrent_lock.lock(); + defer this.concurrent_lock.unlock(); + const add: u32 = @truncate(u32, this.concurrent_tasks.readableLength()); + + // TODO: optimzie + this.tasks.ensureUnusedCapacity(add) catch unreachable; + + { + this.tasks.writeAssumeCapacity(this.concurrent_tasks.readableSlice(0)); + this.concurrent_tasks.discard(this.concurrent_tasks.count); + } + + _ = this.pending_tasks_count.fetchAdd(add, .Monotonic); + _ = this.ready_tasks_count.fetchSub(add, .Monotonic); + } + } + + // TODO: fix this technical debt + pub fn tick(this: *EventLoop) void { + var poller = &this.virtual_machine.poller; + while (true) { + this.tickConcurrent(); + + // this.global.vm().doWork(); + + while (this.tickWithCount() > 0) {} + poller.tick(); + + this.tickConcurrent(); + + if (this.tickWithCount() == 0) break; + } + } + + // TODO: fix this technical debt + pub fn waitForPromise(this: *EventLoop, promise: *JSC.JSInternalPromise) void { + switch (promise.status(this.global.vm())) { + JSC.JSPromise.Status.Pending => { + while (promise.status(this.global.vm()) == .Pending) { + this.tick(); + } + }, + else => {}, + } + } + + pub fn waitForTasks(this: *EventLoop) void { + this.tick(); + while (this.pending_tasks_count.load(.Monotonic) > 0) { + this.tick(); + } + } + + pub fn enqueueTask(this: *EventLoop, task: Task) void { + _ = this.pending_tasks_count.fetchAdd(1, .Monotonic); + this.tasks.writeItem(task) catch unreachable; + } + + pub fn enqueueTaskConcurrent(this: *EventLoop, task: Task) void { + this.concurrent_lock.lock(); + defer this.concurrent_lock.unlock(); + this.concurrent_tasks.writeItem(task) catch unreachable; + if (this.virtual_machine.uws_event_loop) |loop| { + loop.nextTick(*EventLoop, this, EventLoop.tick); + } + _ = this.ready_tasks_count.fetchAdd(1, .Monotonic); + } +}; + +pub const Poller = struct { + /// kqueue() or epoll() + /// 0 == unset + watch_fd: i32 = 0, + + pub const PlatformSpecificFlags = struct {}; + + const Completion = fn (ctx: ?*anyopaque, sizeOrOffset: i64, flags: u16) void; + const kevent64 = std.os.system.kevent64_s; + pub fn dispatchKQueueEvent(kqueue_event: *const kevent64) void { + if (comptime !Environment.isMac) { + unreachable; + } + + const ptr = @intToPtr(?*anyopaque, kqueue_event.udata); + const callback: Completion = @intToPtr(Completion, kqueue_event.ext[0]); + callback(ptr, @bitCast(i64, kqueue_event.data), kqueue_event.flags); + } + const timeout = std.mem.zeroes(std.os.timespec); + + pub fn watch(this: *Poller, fd: JSC.Node.FileDescriptor, flag: Flag, ctx: ?*anyopaque, completion: Completion) JSC.Maybe(void) { + if (comptime Environment.isLinux) { + std.debug.assert(this.watch_fd != 0); + } else if (comptime Environment.isMac) { + if (this.watch_fd == 0) { + this.watch_fd = std.c.kqueue(); + if (this.watch_fd == -1) { + defer this.watch_fd = 0; + return JSC.Maybe(void).errnoSys(this.watch_fd, .kqueue).?; + } + } + std.debug.assert(this.watch_fd != 0); + var events_list = std.mem.zeroes([2]kevent64); + events_list[0] = switch (flag) { + .read => .{ + .ident = @intCast(u64, fd), + .filter = std.os.system.EVFILT_READ, + .data = 0, + .fflags = 0, + .udata = @ptrToInt(ctx), + .flags = std.c.EV_ADD | std.c.EV_ENABLE | std.c.EV_ONESHOT, + .ext = .{ @ptrToInt(completion), 0 }, + }, + .write => .{ + .ident = @intCast(u64, fd), + .filter = std.os.system.EVFILT_WRITE, + .data = 0, + .fflags = 0, + .udata = @ptrToInt(ctx), + .flags = std.c.EV_ADD | std.c.EV_ENABLE | std.c.EV_ONESHOT, + .ext = .{ @ptrToInt(completion), 0 }, + }, + }; + + // 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 = std.os.system.kevent64( + this.watch_fd, + &events_list, + 1, + // The same array may be used for the changelist and eventlist. + &events_list, + 1, + 0, + &timeout, + ); + + // 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 (events_list[0].flags == std.c.EV_ERROR) { + return JSC.Maybe(void).errnoSys(events_list[0].data, .kevent).?; + // Otherwise, -1 will be returned, and errno will be set to + // indicate the error condition. + } + + switch (rc) { + std.math.minInt(@TypeOf(rc))...-1 => return JSC.Maybe(void).errnoSys(@enumToInt(std.c.getErrno(rc)), .kevent).?, + 0 => return JSC.Maybe(void).success, + 1 => { + dispatchKQueueEvent(&events_list[0]); + return JSC.Maybe(void).success; + }, + 2 => { + dispatchKQueueEvent(&events_list[0]); + dispatchKQueueEvent(&events_list[1]); + return JSC.Maybe(void).success; + }, + else => unreachable, + } + } else { + @compileError("TODO: Poller"); + } + } + + const kqueue_events_ = std.mem.zeroes([4]kevent64); + pub fn tick(this: *Poller) void { + if (comptime Environment.isMac) { + if (this.watch_fd == 0) return; + + var events_list = kqueue_events_; + // ub extern "c" fn kevent64( + // kq: c_int, + // changelist: [*]const kevent64_s, + // nchanges: c_int, + // eventlist: [*]kevent64_s, + // nevents: c_int, + // flags: c_uint, + // timeout: ?*const timespec, + // ) c_int; + const rc = std.os.system.kevent64( + this.watch_fd, + &events_list, + 0, + // The same array may be used for the changelist and eventlist. + &events_list, + 4, + 0, + &timeout, + ); + + switch (rc) { + std.math.minInt(@TypeOf(rc))...-1 => { + // EINTR is fine + switch (std.c.getErrno(rc)) { + .INTR => return, + else => |errno| std.debug.panic("kevent64() failed: {d}", .{errno}), + } + }, + 0 => {}, + 1 => { + dispatchKQueueEvent(&events_list[0]); + }, + 2 => { + dispatchKQueueEvent(&events_list[0]); + dispatchKQueueEvent(&events_list[1]); + }, + 3 => { + dispatchKQueueEvent(&events_list[0]); + dispatchKQueueEvent(&events_list[1]); + dispatchKQueueEvent(&events_list[2]); + }, + 4 => { + dispatchKQueueEvent(&events_list[0]); + dispatchKQueueEvent(&events_list[1]); + dispatchKQueueEvent(&events_list[2]); + dispatchKQueueEvent(&events_list[3]); + }, + else => unreachable, + } + } + } + + pub const Flag = enum { read, write }; +}; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 7f0a572aaf..e65b05b777 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -84,6 +84,7 @@ const Config = @import("./config.zig"); const URL = @import("../../url.zig").URL; const Transpiler = @import("./api/transpiler.zig"); const Bun = JSC.API.Bun; +const EventLoop = JSC.EventLoop; const ThreadSafeFunction = JSC.napi.ThreadSafeFunction; pub const GlobalConstructors = [_]type{ WebCore.Blob.Constructor, @@ -109,6 +110,8 @@ pub const GlobalClasses = [_]type{ // The last item in this array becomes "process.env" Bun.EnvironmentVariables.Class, }; +const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion; +const Task = JSC.Task; const Blob = @import("../../blob.zig"); pub const Buffer = MarkedArrayBuffer; const Lock = @import("../../lock.zig").Lock; @@ -125,244 +128,6 @@ pub fn OpaqueWrap(comptime Context: type, comptime Function: fn (this: *Context) const bun_file_import_path = "/node_modules.server.bun"; -const FetchTasklet = Fetch.FetchTasklet; -const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion; -const WorkPool = @import("../../work_pool.zig").WorkPool; -const WorkPoolTask = @import("../../work_pool.zig").Task; -pub fn ConcurrentPromiseTask(comptime Context: type) type { - return struct { - const This = @This(); - ctx: *Context, - task: WorkPoolTask = .{ .callback = runFromThreadPool }, - event_loop: *VirtualMachine.EventLoop, - allocator: std.mem.Allocator, - promise: JSValue, - globalThis: *JSGlobalObject, - - pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { - var this = try allocator.create(This); - this.* = .{ - .event_loop = VirtualMachine.vm.event_loop, - .ctx = value, - .allocator = allocator, - .promise = JSValue.createInternalPromise(globalThis), - .globalThis = globalThis, - }; - js.JSValueProtect(globalThis.ref(), this.promise.asObjectRef()); - VirtualMachine.vm.active_tasks +|= 1; - return this; - } - - pub fn runFromThreadPool(task: *WorkPoolTask) void { - var this = @fieldParentPtr(This, "task", task); - Context.run(this.ctx); - this.onFinish(); - } - - pub fn runFromJS(this: This) void { - var promise_value = this.promise; - var promise = promise_value.asInternalPromise() orelse { - if (comptime @hasDecl(Context, "deinit")) { - @call(.{}, Context.deinit, .{this.ctx}); - } - return; - }; - - var ctx = this.ctx; - - js.JSValueUnprotect(this.globalThis.ref(), promise_value.asObjectRef()); - ctx.then(promise); - } - - pub fn schedule(this: *This) void { - WorkPool.schedule(&this.task); - } - - pub fn onFinish(this: *This) void { - this.event_loop.enqueueTaskConcurrent(Task.init(this)); - } - - pub fn deinit(this: *This) void { - this.allocator.destroy(this); - } - }; -} - -pub fn SerialPromiseTask(comptime Context: type) type { - return struct { - const SerialWorkPool = @import("../../work_pool.zig").NewWorkPool(1); - const This = @This(); - - ctx: *Context, - task: WorkPoolTask = .{ .callback = runFromThreadPool }, - event_loop: *VirtualMachine.EventLoop, - allocator: std.mem.Allocator, - promise: JSValue, - globalThis: *JSGlobalObject, - - pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { - var this = try allocator.create(This); - this.* = .{ - .event_loop = VirtualMachine.vm.event_loop, - .ctx = value, - .allocator = allocator, - .promise = JSValue.createInternalPromise(globalThis), - .globalThis = globalThis, - }; - js.JSValueProtect(globalThis.ref(), this.promise.asObjectRef()); - VirtualMachine.vm.active_tasks +|= 1; - return this; - } - - pub fn runFromThreadPool(task: *WorkPoolTask) void { - var this = @fieldParentPtr(This, "task", task); - Context.run(this.ctx); - this.onFinish(); - } - - pub fn runFromJS(this: This) void { - var promise_value = this.promise; - var promise = promise_value.asInternalPromise() orelse { - if (comptime @hasDecl(Context, "deinit")) { - @call(.{}, Context.deinit, .{this.ctx}); - } - return; - }; - - var ctx = this.ctx; - - js.JSValueUnprotect(this.globalThis.ref(), promise_value.asObjectRef()); - ctx.then(promise, this.globalThis); - } - - pub fn schedule(this: *This) void { - SerialWorkPool.schedule(&this.task); - } - - pub fn onFinish(this: *This) void { - this.event_loop.enqueueTaskConcurrent(Task.init(this)); - } - - pub fn deinit(this: *This) void { - this.allocator.destroy(this); - } - }; -} - -pub fn IOTask(comptime Context: type) type { - return struct { - const This = @This(); - ctx: *Context, - task: NetworkThread.Task = .{ .callback = runFromThreadPool }, - event_loop: *VirtualMachine.EventLoop, - allocator: std.mem.Allocator, - globalThis: *JSGlobalObject, - - pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { - var this = try allocator.create(This); - this.* = .{ - .event_loop = VirtualMachine.vm.eventLoop(), - .ctx = value, - .allocator = allocator, - .globalThis = globalThis, - }; - return this; - } - - pub fn runFromThreadPool(task: *NetworkThread.Task) void { - var this = @fieldParentPtr(This, "task", task); - Context.run(this.ctx, this); - } - - pub fn runFromJS(this: This) void { - var ctx = this.ctx; - ctx.then(this.globalThis); - } - - pub fn schedule(this: *This) void { - NetworkThread.init() catch return; - NetworkThread.global.pool.schedule(NetworkThread.Batch.from(&this.task)); - } - - pub fn onFinish(this: *This) void { - this.event_loop.enqueueTaskConcurrent(Task.init(this)); - } - - pub fn deinit(this: *This) void { - var allocator = this.allocator; - this.* = undefined; - allocator.destroy(this); - } - }; -} - -pub fn AsyncNativeCallbackTask(comptime Context: type) type { - return struct { - const This = @This(); - ctx: *Context, - task: WorkPoolTask = .{ .callback = runFromThreadPool }, - event_loop: *VirtualMachine.EventLoop, - allocator: std.mem.Allocator, - globalThis: *JSGlobalObject, - - pub fn createOnJSThread(allocator: std.mem.Allocator, globalThis: *JSGlobalObject, value: *Context) !*This { - var this = try allocator.create(This); - this.* = .{ - .event_loop = VirtualMachine.vm.eventLoop(), - .ctx = value, - .allocator = allocator, - .globalThis = globalThis, - }; - return this; - } - - pub fn runFromThreadPool(task: *WorkPoolTask) void { - var this = @fieldParentPtr(This, "task", task); - Context.run(this.ctx, this); - } - - pub fn runFromJS(this: This) void { - this.ctx.runFromJS(this.globalThis); - } - - pub fn schedule(this: *This) void { - WorkPool.get().schedule(WorkPool.schedule(&this.task)); - } - - pub fn onFinish(this: *This) void { - this.event_loop.enqueueTaskConcurrent(Task.init(this)); - } - - pub fn deinit(this: *This) void { - var allocator = this.allocator; - this.* = undefined; - allocator.destroy(this); - } - }; -} - -const CopyFilePromiseTask = WebCore.Blob.Store.CopyFile.CopyFilePromiseTask; -const AsyncTransformTask = @import("./api/transpiler.zig").TransformTask.AsyncTransformTask; -const BunTimerTimeoutTask = Bun.Timer.Timeout.TimeoutTask; -const ReadFileTask = WebCore.Blob.Store.ReadFile.ReadFileTask; -const WriteFileTask = WebCore.Blob.Store.WriteFile.WriteFileTask; -const napi_async_work = JSC.napi.napi_async_work; -// const PromiseTask = JSInternalPromise.Completion.PromiseTask; -pub const Task = TaggedPointerUnion(.{ - FetchTasklet, - Microtask, - AsyncTransformTask, - BunTimerTimeoutTask, - ReadFileTask, - CopyFilePromiseTask, - WriteFileTask, - AnyTask, - napi_async_work, - ThreadSafeFunction, - // PromiseTask, - // TimeoutTasklet, -}); - const SourceMap = @import("../../sourcemap/sourcemap.zig"); const MappingList = SourceMap.Mapping.List; @@ -480,31 +245,6 @@ pub const SavedSourceMap = struct { }; const uws = @import("uws"); -pub const AnyTask = struct { - ctx: ?*anyopaque, - callback: fn (*anyopaque) void, - - pub fn run(this: *AnyTask) void { - @setRuntimeSafety(false); - this.callback(this.ctx.?); - } - - pub fn New(comptime Type: type, comptime Callback: anytype) type { - return struct { - pub fn init(ctx: *Type) AnyTask { - return AnyTask{ - .callback = wrap, - .ctx = ctx, - }; - } - - pub fn wrap(this: ?*anyopaque) void { - Callback(@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))); - } - }; - } -}; - pub export fn Bun__getDefaultGlobal() *JSGlobalObject { return JSC.VirtualMachine.vm.global; } @@ -575,7 +315,7 @@ pub const VirtualMachine = struct { response_objects_pool: ?*Response.Pool = null, rare_data: ?*JSC.RareData = null, - poller: JSC.WebCore.Poller = JSC.WebCore.Poller{}, + poller: JSC.Poller = JSC.Poller{}, pub fn io(this: *VirtualMachine) *IO { if (this.io_ == null) { @@ -615,162 +355,6 @@ pub const VirtualMachine = struct { } } - pub const EventLoop = struct { - ready_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), - pending_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), - io_tasks_count: std.atomic.Atomic(u32) = std.atomic.Atomic(u32).init(0), - tasks: Queue = undefined, - concurrent_tasks: Queue = undefined, - concurrent_lock: Lock = Lock.init(), - global: *JSGlobalObject = undefined, - virtual_machine: *VirtualMachine = undefined, - pub const Queue = std.fifo.LinearFifo(Task, .Dynamic); - - pub fn tickWithCount(this: *EventLoop) u32 { - var finished: u32 = 0; - var global = this.global; - var vm_ = this.virtual_machine; - while (this.tasks.readItem()) |task| { - switch (task.tag()) { - .Microtask => { - var micro: *Microtask = task.as(Microtask); - micro.run(global); - finished += 1; - }, - .FetchTasklet => { - var fetch_task: *Fetch.FetchTasklet = task.get(Fetch.FetchTasklet).?; - fetch_task.onDone(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(AsyncTransformTask)) => { - var transform_task: *AsyncTransformTask = task.get(AsyncTransformTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(CopyFilePromiseTask)) => { - var transform_task: *CopyFilePromiseTask = task.get(CopyFilePromiseTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(JSC.napi.napi_async_work)) => { - var transform_task: *JSC.napi.napi_async_work = task.get(JSC.napi.napi_async_work).?; - transform_task.*.runFromJS(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(BunTimerTimeoutTask)) => { - var transform_task: *BunTimerTimeoutTask = task.get(BunTimerTimeoutTask).?; - transform_task.*.runFromJS(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(ReadFileTask)) => { - var transform_task: *ReadFileTask = task.get(ReadFileTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(WriteFileTask)) => { - var transform_task: *WriteFileTask = task.get(WriteFileTask).?; - transform_task.*.runFromJS(); - transform_task.deinit(); - finished += 1; - vm_.active_tasks -|= 1; - }, - @field(Task.Tag, @typeName(AnyTask)) => { - var any: *AnyTask = task.get(AnyTask).?; - any.run(); - finished += 1; - vm_.active_tasks -|= 1; - }, - else => unreachable, - } - } - - if (finished > 0) { - _ = this.pending_tasks_count.fetchSub(finished, .Monotonic); - } - - return finished; - } - - pub fn tickConcurrent(this: *EventLoop) void { - if (this.ready_tasks_count.load(.Monotonic) > 0) { - this.concurrent_lock.lock(); - defer this.concurrent_lock.unlock(); - const add: u32 = @truncate(u32, this.concurrent_tasks.readableLength()); - - // TODO: optimzie - this.tasks.ensureUnusedCapacity(add) catch unreachable; - - { - this.tasks.writeAssumeCapacity(this.concurrent_tasks.readableSlice(0)); - this.concurrent_tasks.discard(this.concurrent_tasks.count); - } - - _ = this.pending_tasks_count.fetchAdd(add, .Monotonic); - _ = this.ready_tasks_count.fetchSub(add, .Monotonic); - } - } - - // TODO: fix this technical debt - pub fn tick(this: *EventLoop) void { - var poller = &this.virtual_machine.poller; - while (true) { - this.tickConcurrent(); - - // this.global.vm().doWork(); - - while (this.tickWithCount() > 0) {} - poller.tick(); - - this.tickConcurrent(); - - if (this.tickWithCount() == 0) break; - } - } - - // TODO: fix this technical debt - pub fn waitForPromise(this: *EventLoop, promise: *JSC.JSInternalPromise) void { - switch (promise.status(this.global.vm())) { - JSC.JSPromise.Status.Pending => { - while (promise.status(this.global.vm()) == .Pending) { - this.tick(); - } - }, - else => {}, - } - } - - pub fn waitForTasks(this: *EventLoop) void { - this.tick(); - while (this.pending_tasks_count.load(.Monotonic) > 0) { - this.tick(); - } - } - - pub fn enqueueTask(this: *EventLoop, task: Task) void { - _ = this.pending_tasks_count.fetchAdd(1, .Monotonic); - this.tasks.writeItem(task) catch unreachable; - } - - pub fn enqueueTaskConcurrent(this: *EventLoop, task: Task) void { - this.concurrent_lock.lock(); - defer this.concurrent_lock.unlock(); - this.concurrent_tasks.writeItem(task) catch unreachable; - if (this.virtual_machine.uws_event_loop) |loop| { - loop.nextTick(*EventLoop, this, EventLoop.tick); - } - _ = this.ready_tasks_count.fetchAdd(1, .Monotonic); - } - }; - pub inline fn enqueueTask(this: *VirtualMachine, task: Task) void { this.eventLoop().enqueueTask(task); } diff --git a/src/javascript/jsc/node/dir_iterator.zig b/src/javascript/jsc/node/dir_iterator.zig index f4c8b6d4aa..3d28541e76 100644 --- a/src/javascript/jsc/node/dir_iterator.zig +++ b/src/javascript/jsc/node/dir_iterator.zig @@ -15,7 +15,7 @@ const PathString = JSC.PathString; const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError; const mem = std.mem; const strings = @import("../../../global.zig").strings; -const Maybe = JSC.Node.Maybe; +const Maybe = JSC.Maybe; const File = std.fs.File; const Result = Maybe(?Entry); diff --git a/src/javascript/jsc/node/node_fs.zig b/src/javascript/jsc/node/node_fs.zig index 2bf1fb685b..c19df3c597 100644 --- a/src/javascript/jsc/node/node_fs.zig +++ b/src/javascript/jsc/node/node_fs.zig @@ -12,7 +12,7 @@ const Environment = bun.Environment; const C = bun.C; const Flavor = JSC.Node.Flavor; const system = std.os.system; -const Maybe = JSC.Node.Maybe; +const Maybe = JSC.Maybe; const Encoding = JSC.Node.Encoding; const Syscall = @import("./syscall.zig"); const Constants = @import("./node_fs_constant.zig").Constants; diff --git a/src/javascript/jsc/node/node_fs_binding.zig b/src/javascript/jsc/node/node_fs_binding.zig index 59b58778a2..6e89ee3751 100644 --- a/src/javascript/jsc/node/node_fs_binding.zig +++ b/src/javascript/jsc/node/node_fs_binding.zig @@ -3,7 +3,7 @@ const std = @import("std"); const Flavor = JSC.Node.Flavor; const ArgumentsSlice = JSC.Node.ArgumentsSlice; const system = std.os.system; -const Maybe = JSC.Node.Maybe; +const Maybe = JSC.Maybe; const Encoding = JSC.Node.Encoding; const FeatureFlags = @import("../../../global.zig").FeatureFlags; const Args = JSC.Node.NodeFS.Arguments; @@ -30,7 +30,7 @@ fn callSync(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction { comptime if (function.args.len != 3) @compileError("Expected 3 arguments"); const Arguments = comptime function.args[1].arg_type.?; const FormattedName = comptime [1]u8{std.ascii.toUpper(@tagName(FunctionEnum)[0])} ++ @tagName(FunctionEnum)[1..]; - const Result = comptime JSC.Node.Maybe(@field(JSC.Node.NodeFS.ReturnType, FormattedName)); + const Result = comptime JSC.Maybe(@field(JSC.Node.NodeFS.ReturnType, FormattedName)); const NodeBindingClosure = struct { pub fn bind( diff --git a/src/javascript/jsc/node/syscall.zig b/src/javascript/jsc/node/syscall.zig index 561196791f..ba99cd0638 100644 --- a/src/javascript/jsc/node/syscall.zig +++ b/src/javascript/jsc/node/syscall.zig @@ -15,7 +15,7 @@ const MAX_PATH_BYTES = bun.MAX_PATH_BYTES; const fd_t = bun.FileDescriptorType; const C = @import("../../../global.zig").C; const linux = os.linux; -const Maybe = JSC.Node.Maybe; +const Maybe = JSC.Maybe; pub const system = if (Environment.isLinux) linux else @import("io").darwin; pub const S = struct { @@ -542,6 +542,16 @@ pub const Error = struct { syscall: Syscall.Tag = @intToEnum(Syscall.Tag, 0), path: []const u8 = "", + pub const retry = Error{ + .errno = if (Environment.isLinux) + @intCast(Int, @enumToInt(os.E.AGAIN)) + else if (Environment.isMac) + @intCast(Int, @enumToInt(os.E.WOULDBLOCK)) + else + @intCast(Int, @enumToInt(os.E.INTR)), + .syscall = .retry, + }; + pub inline fn getErrno(this: Error) os.E { return @intToEnum(os.E, this.errno); } diff --git a/src/javascript/jsc/node/types.zig b/src/javascript/jsc/node/types.zig index 8ba9a59010..8d26c6dbf2 100644 --- a/src/javascript/jsc/node/types.zig +++ b/src/javascript/jsc/node/types.zig @@ -73,6 +73,10 @@ pub fn Maybe(comptime ResultType: type) type { err: Syscall.Error, result: ReturnType, + pub const retry: @This() = .{ + .err = Syscall.Error.retry, + }; + pub const Tag = enum { err, result }; pub const success: @This() = @This(){ diff --git a/src/javascript/jsc/webcore/encoding.zig b/src/javascript/jsc/webcore/encoding.zig index d7f0da6c6e..6d47596601 100644 --- a/src/javascript/jsc/webcore/encoding.zig +++ b/src/javascript/jsc/webcore/encoding.zig @@ -73,6 +73,87 @@ pub const TextEncoder = struct { unreachable; } + // This is a fast path for copying a Rope string into a Uint8Array. + // This keeps us from an extra string temporary allocation + const RopeStringEncoder = struct { + globalThis: *JSGlobalObject, + allocator: std.mem.Allocator, + buffer_value: JSC.JSValue, + slice: []u8, + tail: usize = 0, + any_utf16: bool = false, + + pub fn append8(it: *JSC.JSString.Iterator, ptr: [*]const u8, len: u32) callconv(.C) void { + var this = bun.cast(*RopeStringEncoder, it.data.?); + // we use memcpy here instead of encoding + // SIMD only has an impact for long strings + // so in a case like this, the fastest path is to memcpy + // and then later, we can use the SIMD version + @memcpy(this.slice.ptr + this.tail, ptr, len); + this.tail += len; + } + pub fn append16(it: *JSC.JSString.Iterator, _: [*]const u16, _: u32) callconv(.C) void { + var this = bun.cast(*RopeStringEncoder, it.data.?); + this.any_utf16 = true; + it.stop = 1; + return; + } + pub fn write8(it: *JSC.JSString.Iterator, ptr: [*]const u8, len: u32, offset: u32) callconv(.C) void { + var this = bun.cast(*RopeStringEncoder, it.data.?); + // we use memcpy here instead of encoding + // SIMD only has an impact for long strings + // so in a case like this, the fastest path is to memcpy + // and then later, we can use the SIMD version + @memcpy(this.slice.ptr + offset, ptr, len); + } + pub fn write16(it: *JSC.JSString.Iterator, _: [*]const u16, _: u32, _: u32) callconv(.C) void { + var this = bun.cast(*RopeStringEncoder, it.data.?); + this.any_utf16 = true; + it.stop = 1; + return; + } + + pub fn iter(this: *RopeStringEncoder) JSC.JSString.Iterator { + return .{ + .data = this, + .stop = 0, + .append8 = append8, + .append16 = append16, + .write8 = write8, + .write16 = write16, + }; + } + }; + + // This fast path is only suitable for Latin-1 strings. + // It's not suitable for UTF-16 strings, because getting the byteLength is unpredictable + pub export fn TextEncoder__encodeRopeString( + globalThis: *JSGlobalObject, + rope_str: *JSC.JSString, + ) JSValue { + var ctx = globalThis.ref(); + if (comptime Environment.allow_assert) std.debug.assert(rope_str.is8Bit()); + var array = JSC.JSValue.createUninitializedUint8Array(ctx.ptr(), rope_str.length()); + var encoder = RopeStringEncoder{ + .globalThis = globalThis, + .allocator = bun.default_allocator, + .buffer_value = array, + .slice = (array.asArrayBuffer(globalThis) orelse return JSC.JSValue.jsUndefined()).slice(), + }; + var iter = encoder.iter(); + rope_str.iterator(globalThis, &iter); + + if (encoder.any_utf16) { + return JSC.JSValue.jsUndefined(); + } + + if (comptime !bun.FeatureFlags.latin1_is_now_ascii) { + strings.replaceLatin1WithUTF8(encoder.slice); + } + + return array; + } + const read_key = ZigString.init("read"); const written_key = ZigString.init("written"); @@ -98,6 +179,7 @@ comptime { if (!JSC.is_bindgen) { _ = TextEncoder.TextEncoder__encode; _ = TextEncoder.TextEncoder__encodeInto; + _ = TextEncoder.TextEncoder__encodeRopeString; } } diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 6ea33259c3..b0393abd8c 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -38,11 +38,12 @@ const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; const VirtualMachine = @import("../javascript.zig").VirtualMachine; -const Task = @import("../javascript.zig").Task; +const Task = JSC.Task; const JSPrinter = @import("../../../js_printer.zig"); const picohttp = @import("picohttp"); const StringJoiner = @import("../../../string_joiner.zig"); const uws = @import("uws"); + pub const Response = struct { pub const Pool = struct { response_objects_pool: [127]JSC.C.JSObjectRef = undefined, @@ -92,6 +93,7 @@ pub const Response = struct { }, .{}, ); + pub const Class = NewClass( Response, .{ .name = "Response" }, @@ -1017,7 +1019,7 @@ pub const Fetch = struct { } }; -pub const ReadableStream = opaque { +pub const ReadableStream = struct { pub fn fromBlob(globalThis: *JSGlobalObject, this: *Blob) JSC.JSValue { if (comptime JSC.is_bindgen) unreachable; @@ -1127,164 +1129,6 @@ const PathOrBlob = union(enum) { } }; -pub const Poller = struct { - /// kqueue() or epoll() - /// 0 == unset - watch_fd: i32 = 0, - - pub const PlatformSpecificFlags = struct {}; - - const Completion = fn (ctx: ?*anyopaque, sizeOrOffset: i64, flags: u16) void; - const kevent64 = std.os.system.kevent64_s; - pub fn dispatchKQueueEvent(kqueue_event: *const kevent64) void { - if (comptime !Environment.isMac) { - unreachable; - } - - const ptr = @intToPtr(?*anyopaque, kqueue_event.udata); - const callback: Completion = @intToPtr(Completion, kqueue_event.ext[0]); - callback(ptr, @bitCast(i64, kqueue_event.data), kqueue_event.flags); - } - const timeout = std.mem.zeroes(std.os.timespec); - - pub fn watch(this: *Poller, fd: JSC.Node.FileDescriptor, flag: Flag, ctx: ?*anyopaque, completion: Completion) JSC.Node.Maybe(void) { - if (comptime Environment.isLinux) { - std.debug.assert(this.watch_fd != 0); - } else if (comptime Environment.isMac) { - if (this.watch_fd == 0) { - this.watch_fd = std.c.kqueue(); - if (this.watch_fd == -1) { - defer this.watch_fd = 0; - return JSC.Node.Maybe(void).errnoSys(this.watch_fd, .kqueue).?; - } - } - std.debug.assert(this.watch_fd != 0); - var events_list = std.mem.zeroes([2]kevent64); - events_list[0] = switch (flag) { - .read => .{ - .ident = @intCast(u64, fd), - .filter = std.os.system.EVFILT_READ, - .data = 0, - .fflags = 0, - .udata = @ptrToInt(ctx), - .flags = std.c.EV_ADD | std.c.EV_ENABLE | std.c.EV_ONESHOT, - .ext = .{ @ptrToInt(completion), 0 }, - }, - .write => .{ - .ident = @intCast(u64, fd), - .filter = std.os.system.EVFILT_WRITE, - .data = 0, - .fflags = 0, - .udata = @ptrToInt(ctx), - .flags = std.c.EV_ADD | std.c.EV_ENABLE | std.c.EV_ONESHOT, - .ext = .{ @ptrToInt(completion), 0 }, - }, - }; - - // 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 = std.os.system.kevent64( - this.watch_fd, - &events_list, - 1, - // The same array may be used for the changelist and eventlist. - &events_list, - 1, - 0, - &timeout, - ); - - // 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 (events_list[0].flags == std.c.EV_ERROR) { - return JSC.Node.Maybe(void).errnoSys(events_list[0].data, .kevent).?; - // Otherwise, -1 will be returned, and errno will be set to - // indicate the error condition. - } - - switch (rc) { - std.math.minInt(@TypeOf(rc))...-1 => return JSC.Node.Maybe(void).errnoSys(@enumToInt(std.c.getErrno(rc)), .kevent).?, - 0 => return JSC.Node.Maybe(void).success, - 1 => { - dispatchKQueueEvent(&events_list[0]); - return JSC.Node.Maybe(void).success; - }, - 2 => { - dispatchKQueueEvent(&events_list[0]); - dispatchKQueueEvent(&events_list[1]); - return JSC.Node.Maybe(void).success; - }, - else => unreachable, - } - } else { - @compileError("TODO: Poller"); - } - } - - const kqueue_events_ = std.mem.zeroes([4]kevent64); - pub fn tick(this: *Poller) void { - if (comptime Environment.isMac) { - if (this.watch_fd == 0) return; - - var events_list = kqueue_events_; - // ub extern "c" fn kevent64( - // kq: c_int, - // changelist: [*]const kevent64_s, - // nchanges: c_int, - // eventlist: [*]kevent64_s, - // nevents: c_int, - // flags: c_uint, - // timeout: ?*const timespec, - // ) c_int; - const rc = std.os.system.kevent64( - this.watch_fd, - &events_list, - 0, - // The same array may be used for the changelist and eventlist. - &events_list, - 4, - 0, - &timeout, - ); - - switch (rc) { - std.math.minInt(@TypeOf(rc))...-1 => { - // EINTR is fine - switch (std.c.getErrno(rc)) { - .INTR => return, - else => |errno| std.debug.panic("kevent64() failed: {d}", .{errno}), - } - }, - 0 => {}, - 1 => { - dispatchKQueueEvent(&events_list[0]); - }, - 2 => { - dispatchKQueueEvent(&events_list[0]); - dispatchKQueueEvent(&events_list[1]); - }, - 3 => { - dispatchKQueueEvent(&events_list[0]); - dispatchKQueueEvent(&events_list[1]); - dispatchKQueueEvent(&events_list[2]); - }, - 4 => { - dispatchKQueueEvent(&events_list[0]); - dispatchKQueueEvent(&events_list[1]); - dispatchKQueueEvent(&events_list[2]); - dispatchKQueueEvent(&events_list[3]); - }, - else => unreachable, - } - } - } - - pub const Flag = enum { read, write }; -}; - pub const Blob = struct { size: SizeType = 0, offset: SizeType = 0, @@ -1702,313 +1546,76 @@ pub const Blob = struct { offset: usize, length: usize, ) bool { + _ = store; _ = stream; - store.ref(); - defer store.deref(); - if (comptime JSC.is_bindgen) unreachable; - switch (store.data) { - .bytes => |*bytes| { - var slice = bytes.slice(); - var base = slice[@minimum(offset, slice.len)..]; - slice = base; - slice = if (length > 0) - slice[0..@minimum(@minimum(slice.len, length), max_chunk_size)] - else - slice[0..@minimum(slice.len, max_chunk_size)]; - if (slice.len == 0) - return false; - - const has_more = base.len > slice.len; - - return BlobStore__onRead(ctx, slice.ptr, slice.len) and has_more; - }, - .file => |*file| { - _ = file; - const fd = stream.fd(); - - // invalid fd - if (fd == std.math.maxInt(@TypeOf(fd))) { - return false; - } - const auto_close = file.pathlike == .path; - - const chunk_size = if (length > 0) @minimum(length, max_chunk_size) else max_chunk_size; - - const this_chunk_size = if (file.max_size > 0) - @minimum(chunk_size, file.max_size) - else if (std.os.S.ISFIFO(file.mode)) - fifo_chunk_size - else - chunk_size; - - var buf = bun.default_allocator.alloc( - u8, - this_chunk_size, - ) catch { - const err = JSC.SystemError{ - .code = ZigString.init("ENOMEM"), - .path = if (file.pathlike == .path) - ZigString.init(file.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Out of memory"), - .syscall = ZigString.init("malloc"), - }; - BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); - - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - }; - - const rc = // read() for files - JSC.Node.Syscall.read(fd, buf); - - switch (rc) { - .err => |err| { - const retry = comptime if (Environment.isLinux) - std.os.E.WOULDBLOCK - else - std.os.E.AGAIN; - - switch (err.getErrno()) { - retry => { - outer: { - NetworkThread.init() catch break :outer; - - _ = AsyncStreamRequest.init( - bun.default_allocator, - ctx, - buf, - fd, - file.mode, - if (!std.os.S.ISREG(file.mode)) @as(?u64, null) else offset, - ) catch { - const sys = JSC.SystemError{ - .code = ZigString.init("ENOMEM"), - .path = if (file.pathlike == .path) - ZigString.init(file.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Out of memory"), - .syscall = ZigString.init("malloc"), - }; - BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); - - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - }; - return true; - } - }, - else => {}, - } - const sys = err.toSystemError(); - - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - - BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); - bun.default_allocator.free(buf); - return false; - }, - .result => |result| { - // this handles: - // - empty file - // - stream closed for some reason - if ((result == 0 and (file.seekable orelse false)) or - BlobReadableStreamSource_isCancelled(ctx)) - { - bun.default_allocator.free(buf); - return false; - } - - const will_continue = BlobStore__onReadExternal( - ctx, - buf.ptr, - result, - buf.ptr, - JSC.MarkedArrayBuffer_deallocator, - ) and - // if it's not a regular file, we don't know how much to read so we should continue - (!(file.seekable orelse false) or - // if it is a regular file, we stop reading when we've read all the data - !((file.seekable orelse false) and @intCast(SizeType, result + offset) >= file.max_size)); - - return will_continue; - }, - } - }, - } - + _ = ctx; + _ = offset; + _ = length; return false; + // _ = stream; + // store.ref(); + // defer store.deref(); + // if (comptime JSC.is_bindgen) unreachable; + // switch (store.data) { + // .bytes => |*bytes| { + // var slice = bytes.slice(); + // var base = slice[@minimum(offset, slice.len)..]; + // slice = base; + // slice = if (length > 0) + // slice[0..@minimum(@minimum(slice.len, length), max_chunk_size)] + // else + // slice[0..@minimum(slice.len, max_chunk_size)]; + // if (slice.len == 0) + // return false; + + // const has_more = base.len > slice.len; + + // return BlobStore__onRead(ctx, slice.ptr, slice.len) and has_more; + // }, + // .file => |*file| { + // _ = file; + // const fd = stream.fd(); + + // // invalid fd + // if (fd == std.math.maxInt(@TypeOf(fd))) { + // return false; + // } + // const auto_close = file.pathlike == .path; + + // const chunk_size = if (length > 0) @minimum(length, max_chunk_size) else max_chunk_size; + + // const this_chunk_size = if (file.max_size > 0) + // @minimum(chunk_size, file.max_size) + // else if (std.os.S.ISFIFO(file.mode)) + // fifo_chunk_size + // else + // chunk_size; + + // var buf = bun.default_allocator.alloc( + // u8, + // this_chunk_size, + // ) catch { + // const err = JSC.SystemError{ + // .code = ZigString.init("ENOMEM"), + // .path = if (file.pathlike == .path) + // ZigString.init(file.pathlike.path.slice()) + // else + // ZigString.Empty, + // .message = ZigString.init("Out of memory"), + // .syscall = ZigString.init("malloc"), + // }; + // BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); + + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // }; + // }, + // } + + // return false; } - const StreamingRead = struct { - buf_ptr: [*]u8, - buf_len: SizeType = 0, - buf_tail: SizeType = 0, - ref_count: u32 = 0, - store: *Blob.Store, - ctx: ?*anyopaque = null, - - has_pending_read: bool = false, - - pub fn owns(this: *const StreamingRead, ptr: ?*const anyopaque) bool { - const addr = @ptrToInt(ptr); - return addr >= @ptrToInt(this.buf_ptr) and addr < @ptrToInt(this.buf_ptr) + this.buf_len; - } - - pub fn deref(this: *StreamingRead) void { - if (this.ref_count == 0) - unreachable; - - this.ref_count -= 1; - if (this.ref_count == 0 and !this.has_pending_read) { - bun.default_allocator.destroy(this); - return; - } - } - - pub fn ref(this: *StreamingRead) void { - this.ref_count +|= 1; - } - }; - - const AsyncStreamRequest = struct { - buf: []u8, - fd: JSC.Node.FileDescriptor, - // task: NetworkThread.Task = .{ .callback = callback }, - // completion: AsyncIO.Completion = undefined, - offset: ?u64 = null, - read_len: u64 = 0, - loop: *JSC.VirtualMachine.EventLoop = undefined, - allocator: std.mem.Allocator, - source: ?*anyopaque, - mode: JSC.Node.Mode, - - pub fn init(allocator: std.mem.Allocator, source: ?*anyopaque, buf: []u8, fd: JSC.Node.FileDescriptor, mode: JSC.Node.Mode, offset: ?u64) !*AsyncStreamRequest { - var req = try allocator.create(AsyncStreamRequest); - req.* = .{ - .buf = buf, - .fd = fd, - .source = source, - .offset = offset, - .loop = JSC.VirtualMachine.vm.eventLoop(), - .allocator = allocator, - .mode = mode, - }; - _ = JSC.VirtualMachine.vm.poller.watch(fd, .read, req, callback); - return req; - } - - pub fn callback(task: ?*anyopaque, sizeOrOffset: i64, _: u16) void { - var this: *AsyncStreamRequest = bun.cast(*AsyncStreamRequest, task.?); - var buf = this.buf; - if (comptime Environment.isMac) { - if (std.os.S.ISREG(this.mode)) { - - // Returns when the file pointer is not at the end of - // file. data contains the offset from current position - // to end of file, and may be negative. - buf = buf[0..@minimum(@intCast(usize, sizeOrOffset), this.buf.len)]; - } else if (std.os.S.ISCHR(this.mode) or std.os.S.ISFIFO(this.mode)) { - buf = buf[0..@minimum(@intCast(usize, sizeOrOffset), this.buf.len)]; - } - } - - const rc = JSC.Node.Syscall.read(this.fd, buf); - - switch (rc) { - .err => |err| { - // const retry = comptime if (Environment.isLinux) - // std.os.E.WOULDBLOCK - // else - // std.os.E.AGAIN; - - // switch (err.getErrno()) { - // retry => { - // outer: { - // NetworkThread.init() catch break :outer; - - // _ = AsyncStreamRequest.init( - // bun.default_allocator, - // this.source, - // buf, - // fd, - // if (!std.os.S.ISREG(file.mode)) null else offset, - // ) catch { - // const sys = JSC.SystemError{ - // .code = ZigString.init("ENOMEM"), - // .path = if (file.pathlike == .path) - // ZigString.init(file.pathlike.path.slice()) - // else - // ZigString.Empty, - // .message = ZigString.init("Out of memory"), - // .syscall = ZigString.init("malloc"), - // }; - // BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); - - // if (auto_close) { - // _ = JSC.Node.Syscall.close(fd); - // } - // return false; - // }; - // return true; - // } - // }, - // else => {}, - // } - const sys = err.toSystemError(); - - BlobStore__onError(this.source, &sys, JSC.VirtualMachine.vm.global); - const allocator = this.allocator; - allocator.free(this.buf); - allocator.destroy(this); - return; - }, - .result => |result| { - // this handles: - // - empty file - // - stream closed for some reason - if (result == 0) { - _ = BlobStore__onRead(this.source, null, 0); - return; - } - - _ = BlobStore__onReadExternal( - this.source, - buf.ptr, - result, - buf.ptr, - JSC.MarkedArrayBuffer_deallocator, - ); - }, - } - // } - - } - - pub fn runFromJS(task: *anyopaque) void { - var this: *AsyncStreamRequest = bun.cast(*AsyncStreamRequest, task); - var allocator = this.allocator; - defer allocator.destroy(this); - if (this.errno) |err| { - const system_error = JSC.SystemError{ - .code = ZigString.init(std.mem.span(@errorName(err))), - .path = ZigString.Empty, // TODO - .syscall = ZigString.init("read"), - }; - BlobStore__onError(this.source, &system_error, this.loop.global); - return; - } - - _ = BlobStore__onReadExternal(this.source, this.buf.ptr, this.read_len, this.buf.ptr, JSC.MarkedArrayBuffer_deallocator); - } - }; const fifo_chunk_size = 512; pub export fn BlobStore__requestStart( @@ -2018,247 +1625,253 @@ pub const Blob = struct { offset: usize, length: usize, ) bool { - store.ref(); - defer store.deref(); - if (comptime JSC.is_bindgen) unreachable; - const chunk_size = if (length > 0) @minimum(length, max_chunk_size) else max_chunk_size; - switch (store.data) { - .bytes => |*bytes| { - var slice = bytes.slice(); - var base = slice[@minimum(offset, slice.len)..]; - slice = base; - slice = slice[0..@minimum(slice.len, chunk_size)]; - if (slice.len == 0) - return false; + _ = store; + _ = streamer; + _ = ctx; + _ = offset; + _ = length; + return false; + // store.ref(); + // defer store.deref(); + // if (comptime JSC.is_bindgen) unreachable; + // const chunk_size = if (length > 0) @minimum(length, max_chunk_size) else max_chunk_size; + // switch (store.data) { + // .bytes => |*bytes| { + // var slice = bytes.slice(); + // var base = slice[@minimum(offset, slice.len)..]; + // slice = base; + // slice = slice[0..@minimum(slice.len, chunk_size)]; + // if (slice.len == 0) + // return false; - const has_more = base.len > slice.len; + // const has_more = base.len > slice.len; - return BlobStore__onRead(ctx, slice.ptr, slice.len) and has_more; - }, - .file => |*file| { - var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var auto_close = file.pathlike != .fd; - var fd = if (!auto_close) - file.pathlike.fd - else switch (JSC.Node.Syscall.open(file.pathlike.path.sliceZ(&file_buf), std.os.O.RDONLY | std.os.O.NONBLOCK | std.os.O.CLOEXEC, 0)) { - .result => |_fd| _fd, - .err => |err| { - BlobStore__onError(ctx, &err.withPath(file.pathlike.path.slice()).toSystemError(), JSC.VirtualMachine.vm.global); - return false; - }, - }; + // return BlobStore__onRead(ctx, slice.ptr, slice.len) and has_more; + // }, + // .file => |*file| { + // var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + // var auto_close = file.pathlike != .fd; + // var fd = if (!auto_close) + // file.pathlike.fd + // else switch (JSC.Node.Syscall.open(file.pathlike.path.sliceZ(&file_buf), std.os.O.RDONLY | std.os.O.NONBLOCK | std.os.O.CLOEXEC, 0)) { + // .result => |_fd| _fd, + // .err => |err| { + // BlobStore__onError(ctx, &err.withPath(file.pathlike.path.slice()).toSystemError(), JSC.VirtualMachine.vm.global); + // return false; + // }, + // }; - if (!auto_close) { - // ensure we have non-blocking IO set - const flags = (std.os.fcntl(fd, std.os.F.GETFL, 0) catch return false); + // if (!auto_close) { + // // ensure we have non-blocking IO set + // const flags = (std.os.fcntl(fd, std.os.F.GETFL, 0) catch return false); - // if we do not, clone the descriptor and set non-blocking - // it is important for us to clone it so we don't cause Weird Things to happen - if ((flags & std.os.O.NONBLOCK) == 0) { - auto_close = false; - fd = @intCast(@TypeOf(fd), std.os.fcntl(fd, std.os.F.DUPFD, 0) catch return false); - _ = std.os.fcntl(fd, std.os.F.SETFL, flags | std.os.O.NONBLOCK) catch return false; - } - } + // // if we do not, clone the descriptor and set non-blocking + // // it is important for us to clone it so we don't cause Weird Things to happen + // if ((flags & std.os.O.NONBLOCK) == 0) { + // auto_close = false; + // fd = @intCast(@TypeOf(fd), std.os.fcntl(fd, std.os.F.DUPFD, 0) catch return false); + // _ = std.os.fcntl(fd, std.os.F.SETFL, flags | std.os.O.NONBLOCK) catch return false; + // } + // } - const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) { - .result => |result| result, - .err => |err| { - BlobStore__onError(ctx, &err.withPath(file.pathlike.path.slice()).toSystemError(), JSC.VirtualMachine.vm.global); + // const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) { + // .result => |result| result, + // .err => |err| { + // BlobStore__onError(ctx, &err.withPath(file.pathlike.path.slice()).toSystemError(), JSC.VirtualMachine.vm.global); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - }, - }; + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // }, + // }; - if (std.os.S.ISDIR(stat.mode)) { - const err = JSC.SystemError{ - .code = ZigString.init("EISDIR"), - .path = if (file.pathlike == .path) - ZigString.init(file.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Directories cannot be read like files"), - .syscall = ZigString.init("read"), - }; + // if (std.os.S.ISDIR(stat.mode)) { + // const err = JSC.SystemError{ + // .code = ZigString.init("EISDIR"), + // .path = if (file.pathlike == .path) + // ZigString.init(file.pathlike.path.slice()) + // else + // ZigString.Empty, + // .message = ZigString.init("Directories cannot be read like files"), + // .syscall = ZigString.init("read"), + // }; - BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); + // BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - } + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // } - if (std.os.S.ISSOCK(stat.mode)) { - const err = JSC.SystemError{ - .code = ZigString.init("ENOTSUP"), - .path = if (file.pathlike == .path) - ZigString.init(file.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Sockets cannot be read like files with this API"), - .syscall = ZigString.init("read"), - }; + // if (std.os.S.ISSOCK(stat.mode)) { + // const err = JSC.SystemError{ + // .code = ZigString.init("ENOTSUP"), + // .path = if (file.pathlike == .path) + // ZigString.init(file.pathlike.path.slice()) + // else + // ZigString.Empty, + // .message = ZigString.init("Sockets cannot be read like files with this API"), + // .syscall = ZigString.init("read"), + // }; - BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); + // BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - } + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // } - // if (comptime Environment.isMac) { - // if (std.os.S.ISSOCK(stat.mode)) { - // // darwin doesn't support os.MSG.NOSIGNAL, - // // but instead a socket option to avoid SIGPIPE. - // const _bytes = &std.mem.toBytes(@as(c_int, 1)); - // _ = std.os.darwin.setsockopt(fd, std.os.SOL.SOCKET, std.os.SO.NOSIGPIPE, _bytes, @intCast(std.os.socklen_t, _bytes.len)); - // } - // } + // // if (comptime Environment.isMac) { + // // if (std.os.S.ISSOCK(stat.mode)) { + // // // darwin doesn't support os.MSG.NOSIGNAL, + // // // but instead a socket option to avoid SIGPIPE. + // // const _bytes = &std.mem.toBytes(@as(c_int, 1)); + // // _ = std.os.darwin.setsockopt(fd, std.os.SOL.SOCKET, std.os.SO.NOSIGPIPE, _bytes, @intCast(std.os.socklen_t, _bytes.len)); + // // } + // // } - file.seekable = std.os.S.ISREG(stat.mode); - file.mode = @intCast(JSC.Node.Mode, stat.mode); + // file.seekable = std.os.S.ISREG(stat.mode); + // file.mode = @intCast(JSC.Node.Mode, stat.mode); - if (file.seekable orelse false) - file.max_size = @intCast(SizeType, stat.size); + // if (file.seekable orelse false) + // file.max_size = @intCast(SizeType, stat.size); - if ((file.seekable orelse false) and file.max_size == 0) { - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - } + // if ((file.seekable orelse false) and file.max_size == 0) { + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // } - const this_chunk_size = if (file.max_size > 0) - @minimum(chunk_size, file.max_size) - else if (std.os.S.ISFIFO(stat.mode)) - fifo_chunk_size - else - chunk_size; + // const this_chunk_size = if (file.max_size > 0) + // @minimum(chunk_size, file.max_size) + // else if (std.os.S.ISFIFO(stat.mode)) + // fifo_chunk_size + // else + // chunk_size; - var buf = bun.default_allocator.alloc( - u8, - this_chunk_size, - ) catch { - const err = JSC.SystemError{ - .code = ZigString.init("ENOMEM"), - .path = if (file.pathlike == .path) - ZigString.init(file.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Out of memory"), - .syscall = ZigString.init("malloc"), - }; - BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); + // var buf = bun.default_allocator.alloc( + // u8, + // this_chunk_size, + // ) catch { + // const err = JSC.SystemError{ + // .code = ZigString.init("ENOMEM"), + // .path = if (file.pathlike == .path) + // ZigString.init(file.pathlike.path.slice()) + // else + // ZigString.Empty, + // .message = ZigString.init("Out of memory"), + // .syscall = ZigString.init("malloc"), + // }; + // BlobStore__onError(ctx, &err, JSC.VirtualMachine.vm.global); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - }; + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // }; - const rc = // read() for files - JSC.Node.Syscall.read(fd, buf); + // const rc = // read() for files + // JSC.Node.Syscall.read(fd, buf); - switch (rc) { - .err => |err| { - const retry = comptime if (Environment.isLinux) - std.os.E.WOULDBLOCK - else - std.os.E.AGAIN; + // switch (rc) { + // .err => |err| { + // const retry = comptime if (Environment.isLinux) + // std.os.E.WOULDBLOCK + // else + // std.os.E.AGAIN; - switch (err.getErrno()) { - retry => { - outer: { - NetworkThread.init() catch break :outer; + // switch (err.getErrno()) { + // retry => { + // outer: { + // NetworkThread.init() catch break :outer; - _ = AsyncStreamRequest.init( - bun.default_allocator, - ctx, - buf, - fd, - file.mode, - if (!std.os.S.ISREG(stat.mode)) @as(?u64, null) else offset, - ) catch { - const sys = JSC.SystemError{ - .code = ZigString.init("ENOMEM"), - .path = if (file.pathlike == .path) - ZigString.init(file.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Out of memory"), - .syscall = ZigString.init("malloc"), - }; - BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); + // _ = FileReader.init( + // bun.default_allocator, + // ctx, + // buf, + // fd, + // file.mode, + // if (!std.os.S.ISREG(stat.mode)) @as(?u64, null) else offset, + // ) catch { + // const sys = JSC.SystemError{ + // .code = ZigString.init("ENOMEM"), + // .path = if (file.pathlike == .path) + // ZigString.init(file.pathlike.path.slice()) + // else + // ZigString.Empty, + // .message = ZigString.init("Out of memory"), + // .syscall = ZigString.init("malloc"), + // }; + // BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - return false; - }; - streamer.* = ReadableStream.StreamTag.init(fd); - return true; - } - }, - else => {}, - } - const sys = err.toSystemError(); + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } + // return false; + // }; + // streamer.* = ReadableStream.StreamTag.init(fd); + // return true; + // } + // }, + // else => {}, + // } + // const sys = err.toSystemError(); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } - BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); - bun.default_allocator.free(buf); - return false; - }, - .result => |result| { - // this handles: - // - empty file - // - stream closed for some reason - if ((result == 0 and (file.seekable orelse false)) or - BlobReadableStreamSource_isCancelled(ctx)) - { - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } + // BlobStore__onError(ctx, &sys, JSC.VirtualMachine.vm.global); + // bun.default_allocator.free(buf); + // return false; + // }, + // .result => |result| { + // // this handles: + // // - empty file + // // - stream closed for some reason + // if ((result == 0 and (file.seekable orelse false)) or + // BlobReadableStreamSource_isCancelled(ctx)) + // { + // if (auto_close) { + // _ = JSC.Node.Syscall.close(fd); + // } - bun.default_allocator.free(buf); - return false; - } + // bun.default_allocator.free(buf); + // return false; + // } - const will_continue = BlobStore__onReadExternal( - ctx, - buf.ptr, - result, - buf.ptr, - JSC.MarkedArrayBuffer_deallocator, - ) and - // if it's not a regular file, we don't know how much to read so we should continue - (!(file.seekable orelse false) or - // if it is a regular file, we stop reading when we've read all the data - !((file.seekable orelse false) and @intCast(SizeType, result) >= file.max_size)); + // const will_continue = BlobStore__onReadExternal( + // ctx, + // buf.ptr, + // result, + // buf.ptr, + // JSC.MarkedArrayBuffer_deallocator, + // ) and + // // if it's not a regular file, we don't know how much to read so we should continue + // (!(file.seekable orelse false) or + // // if it is a regular file, we stop reading when we've read all the data + // !((file.seekable orelse false) and @intCast(SizeType, result) >= file.max_size)); - // did the first read cover the whole file? - // if yes, then we are done! - // we can safely close the file descriptor now - if (auto_close and !will_continue) { - _ = JSC.Node.Syscall.close(fd); - } + // // did the first read cover the whole file? + // // if yes, then we are done! + // // we can safely close the file descriptor now + // if (auto_close and !will_continue) { + // _ = JSC.Node.Syscall.close(fd); + // } - if (will_continue) { - streamer.* = ReadableStream.StreamTag.init(fd); - } + // if (will_continue) { + // streamer.* = ReadableStream.StreamTag.init(fd); + // } - return will_continue; - }, - } - }, - } + // return will_continue; + // }, + // } + // }, + // } } comptime { @@ -4309,6 +3922,7 @@ pub const Blob = struct { JSC.JSValue.JSType.DataView, => { var buf = try bun.default_allocator.dupe(u8, top_value.asArrayBuffer(global).?.byteSlice()); + return Blob.init(buf, bun.default_allocator, global); }, @@ -4339,6 +3953,7 @@ pub const Blob = struct { var stack_mem_all = stack_allocator.get(); var stack: std.ArrayList(JSValue) = std.ArrayList(JSValue).init(stack_mem_all); var joiner = StringJoiner{ .use_pool = false, .node_allocator = stack_mem_all }; + var could_have_non_ascii = false; defer if (stack_allocator.fixed_buffer_allocator.end_index >= 1024) stack.deinit(); @@ -4350,6 +3965,7 @@ pub const Blob = struct { JSC.JSValue.JSType.DerivedStringObject, => { var sliced = current.toSlice(global, bun.default_allocator); + could_have_non_ascii = could_have_non_ascii or sliced.allocated; joiner.append( sliced.slice(), 0, @@ -4378,6 +3994,7 @@ pub const Blob = struct { JSC.JSValue.JSType.DerivedStringObject, => { var sliced = item.toSlice(global, bun.default_allocator); + could_have_non_ascii = could_have_non_ascii or sliced.allocated; joiner.append( sliced.slice(), 0, @@ -4399,12 +4016,14 @@ pub const Blob = struct { JSC.JSValue.JSType.BigUint64Array, JSC.JSValue.JSType.DataView, => { + could_have_non_ascii = true; var buf = item.asArrayBuffer(global).?; joiner.append(buf.byteSlice(), 0, null); continue; }, .Array, .DerivedArray => { any_arrays = true; + could_have_non_ascii = true; break; }, else => { @@ -4413,6 +4032,7 @@ pub const Blob = struct { switch (data.tag()) { .Blob => { var blob: *Blob = data.as(Blob); + could_have_non_ascii = could_have_non_ascii or !(blob.is_all_ascii orelse false); joiner.append(blob.sharedView(), 0, null); continue; }, @@ -4443,6 +4063,7 @@ pub const Blob = struct { => { var buf = current.asArrayBuffer(global).?; joiner.append(buf.slice(), 0, null); + could_have_non_ascii = true; }, else => { @@ -4452,6 +4073,7 @@ pub const Blob = struct { switch (data.tag()) { .Blob => { var blob: *Blob = data.as(Blob); + could_have_non_ascii = could_have_non_ascii or !(blob.is_all_ascii orelse false); joiner.append(blob.sharedView(), 0, null); break :outer; }, @@ -4460,6 +4082,7 @@ pub const Blob = struct { } var sliced = current.toSlice(global, bun.default_allocator); + could_have_non_ascii = could_have_non_ascii or sliced.allocated; joiner.append( sliced.slice(), 0, @@ -4472,6 +4095,10 @@ pub const Blob = struct { } var joined = try joiner.done(bun.default_allocator); + + if (!could_have_non_ascii) { + return Blob.initWithAllASCII(joined, bun.default_allocator, global, true); + } return Blob.init(joined, bun.default_allocator, global); } }; @@ -4592,6 +4219,8 @@ pub const Body = struct { pub const PendingValue = struct { promise: ?JSValue = null, + readable: JSValue = JSValue.zero, + global: *JSGlobalObject, task: ?*anyopaque = null, /// runs after the data is available. @@ -5521,3 +5150,456 @@ pub const FetchEvent = struct { return js.JSValueMakeUndefined(ctx); } }; + +pub const StreamResult = union(enum) { + owned: bun.ByteList(u8), + temporary: bun.ByteList(u8), + pending: anyframe->StreamResult, + err: JSC.Node.Syscall.Error, + done: void, +}; + +pub fn WritableStreamSink( + comptime Context: type, + comptime onStart: ?fn (this: Context) void, + comptime onWrite: fn (this: Context, bytes: []const u8) JSC.Maybe(Blob.SizeType), + comptime onAbort: ?fn (this: Context) void, + comptime onClose: ?fn (this: Context) void, + comptime deinit: ?fn (this: Context) void, +) type { + return struct { + context: Context, + closed: bool = false, + deinited: bool = false, + pending_err: ?JSC.Node.Syscall.Error = null, + aborted: bool = false, + + abort_signaler: ?*anyopaque = null, + onAbortCallback: ?fn (?*anyopaque) void = null, + + close_signaler: ?*anyopaque = null, + onCloseCallback: ?fn (?*anyopaque) void = null, + + pub const This = @This(); + + pub fn write(this: *This, bytes: []const u8) JSC.Maybe(Blob.SizeType) { + if (this.pending_err) |err| { + this.pending_err = null; + return .{ .err = err }; + } + + if (this.closed or this.aborted or this.deinited) { + return .{ .result = 0 }; + } + return onWrite(this.context, bytes); + } + + pub fn start(this: *This) StreamResult { + return onStart(this.context); + } + + pub fn abort(this: *This) void { + if (this.closed or this.deinited or this.aborted) { + return; + } + + this.aborted = true; + onAbort(this.context); + } + + pub fn didAbort(this: *This) void { + if (this.closed or this.deinited or this.aborted) { + return; + } + this.aborted = true; + + if (this.onAbortCallback) |cb| { + this.onAbortCallback = null; + cb(this.abort_signaler); + } + } + + pub fn didClose(this: *This) void { + if (this.closed or this.deinited or this.aborted) { + return; + } + this.closed = true; + + if (this.onCloseCallback) |cb| { + this.onCloseCallback = null; + cb(this.close_signaler); + } + } + + pub fn close(this: *This) void { + if (this.closed or this.deinited or this.aborted) { + return; + } + + this.closed = true; + onClose(this.context); + } + + pub fn deinit(this: *This) void { + if (this.deinited) { + return; + } + this.deinited = true; + deinit(this.context); + } + + pub fn getError(this: *This) ?JSC.Node.Syscall.Error { + if (this.pending_err) |err| { + this.pending_err = null; + return err; + } + + return null; + } + }; +} + +pub fn HTTPServerWritable(comptime ssl: bool) type { + return struct { + pub const UWSResponse = uws.NewApp(ssl).Response; + res: *UWSResponse, + pending_chunk: []const u8 = "", + is_listening_for_abort: bool = false, + wrote: Blob.SizeType = 0, + callback: anyframe->JSC.Maybe(Blob.SizeType) = undefined, + writable: Writable, + + pub fn onWritable(this: *@This(), available: c_ulong, _: *UWSResponse) callconv(.C) bool { + const to_write = @minimum(@truncate(Blob.SizeType, available), @truncate(Blob.SizeType, this.pending_chunk.len)); + if (!this.res.write(this.pending_chunk[0..to_write])) { + return true; + } + + this.pending_chunk = this.pending_chunk[to_write..]; + this.wrote += to_write; + if (this.pending_chunk.len > 0) { + this.res.onWritable(*@This(), onWritable, this); + return true; + } + + var callback = this.callback; + this.callback = undefined; + // TODO: clarify what the boolean means + resume callback; + bun.default_allocator.destroy(callback.*); + return false; + } + + pub fn onStart(this: *@This()) void { + if (this.res.hasResponded()) { + this.writable.didClose(); + } + } + pub fn onWrite(this: *@This(), bytes: []const u8) JSC.Maybe(Blob.SizeType) { + if (this.writable.aborted) { + return .{ .result = 0 }; + } + + if (this.pending_chunk.len > 0) { + return JSC.Maybe(Blob.SizeType).retry; + } + + if (this.res.write(bytes)) { + return .{ .result = @truncate(Blob.SizeType, bytes.len) }; + } + + this.pending_chunk = bytes; + this.writable.pending_err = null; + suspend { + if (!this.is_listening_for_abort) { + this.is_listening_for_abort = true; + this.res.onAborted(*@This(), onAborted); + } + + this.res.onWritable(*@This(), onWritable, this); + var frame = bun.default_allocator.create(@TypeOf(@Frame(onWrite))) catch unreachable; + this.callback = frame; + frame.* = @frame().*; + } + const wrote = this.wrote; + this.wrote = 0; + if (this.writable.pending_err) |err| { + this.writable.pending_err = null; + return .{ .err = err }; + } + return .{ .result = wrote }; + } + + // client-initiated + pub fn onAborted(this: *@This(), _: *UWSResponse) void { + this.writable.didAbort(); + } + // writer-initiated + pub fn onAbort(this: *@This()) void { + this.res.end("", true); + } + pub fn onClose(this: *@This()) void { + this.res.end("", false); + } + pub fn deinit(_: *@This()) void {} + + pub const Writable = WritableStreamSink(@This(), onStart, onWrite, onAbort, onClose, deinit); + }; +} +pub const HTTPSWriter = HTTPServerWritable(true); +pub const HTTPWriter = HTTPServerWritable(false); + +pub fn ReadableStreamSource( + comptime Context: type, + comptime onStart: fn (this: Context) StreamResult, + comptime onPull: fn (this: Context) StreamResult, + comptime onCancel: fn (this: Context) void, + comptime deinit: fn (this: Context) void, +) type { + return struct { + context: Context, + cancelled: bool = false, + deinited: bool = false, + pending_err: ?JSC.Node.Syscall.Error = null, + + pub const This = @This(); + + pub fn pull(this: *This) StreamResult { + return onPull(this.context); + } + + pub fn start(this: *This) StreamResult { + return onStart(this.context); + } + + pub fn cancel(this: *This) void { + if (this.cancelled or this.deinited) { + return; + } + + this.cancelled = true; + onCancel(this.context); + } + + pub fn deinit(this: *This) void { + if (this.deinited) { + return; + } + this.deinited = true; + deinit(this.context); + } + + pub fn getError(this: *This) ?JSC.Node.Syscall.Error { + if (this.pending_err) |err| { + this.pending_err = null; + return err; + } + + return null; + } + + pub const JSReadableStreamSource = struct { + pub fn pull(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) + pub fn start(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) + pub fn cancel(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) + pub fn deinit(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) + + }; + }; +} + +const ByteBlobLoader = struct { + offset: Blob.SizeType = 0, + store: *Blob.Store, + chunk_size: Blob.SizeType = 1024 * 1024 * 2, + remain: Blob.SizeType = 1024 * 1024 * 2, + source: Source, + done: bool = false, + + pub fn setup( + this: *ByteBlobLoader, + blob: Blob, + ) ByteBlobLoader { + blob.store.ref(); + this.* = ByteBlobLoader{ + .offset = blob.offset, + .store = blob.store, + .chunk_size = @minimum(1024 * 1024 * 2, blob.size), + .remain = blob.size, + .source = Source{ .context = this }, + .done = false, + }; + } + + pub fn onStart(this: *ByteBlobLoader) StreamResult { + return this.onPull(); + } + + pub fn onPull(this: *ByteBlobLoader) StreamResult { + if (this.done) { + return .{ .done = {} }; + } + + var temporary = this.store.sharedView(); + temporary = temporary[this.offset..]; + temporary = temporary[0..@minimum(this.chunk_size, @minimum(temporary.len, this.remain))]; + if (temporary.len == 0) { + this.store.deref(); + this.done = true; + return .{ .done = {} }; + } + + this.remain -|= @intCast(Blob.SizeType, temporary.len); + this.offset +|= @intCast(Blob.SizeType, temporary.len); + + return .{ + .temporary = temporary, + }; + } + + pub fn onCancel(_: *ByteBlobLoader) void {} + + pub fn deinit(this: *ByteBlobLoader) void { + if (!this.done) + this.store.deref(); + } + + const Source = ReadableStreamSource(@This(), onStart, onPull, onCancel, deinit); +}; + +pub fn NewFileReader( + comptime Context: type, + comptime onRead: fn (ctx: *Context, buf: []u8, len: Blob.SizeType) bool, + comptime onError: fn (ctx: *Context, err: JSC.SystemError) void, + comptime onWouldBlock: fn (ctx: *Context, err: JSC.SystemError) void, + comptime isCancelled: fn (ctx: *Context) bool, +) type { + return struct { + buf: []u8, + fd: JSC.Node.FileDescriptor, + loop: *JSC.EventLoop = undefined, + mode: JSC.Node.Mode, + ctx: *Context, + + const FileReader = @This(); + + pub fn init(ctx: *Context, buf: []u8, fd: JSC.Node.FileDescriptor, mode: JSC.Node.Mode) FileReader { + return .{ + .buf = buf, + .fd = fd, + .ctx = ctx, + .loop = JSC.VirtualMachine.vm.eventLoop(), + .mode = mode, + }; + } + + pub fn watch(this: *FileReader) void { + _ = JSC.VirtualMachine.vm.poller.watch(this.fd, .read, this, callback); + } + + pub fn read( + ctx: *Context, + path: []const u8, + buf: []u8, + fd: JSC.Node.FileDescriptor, + remaining: usize, + global: *JSGlobalObject, + would_block: ?*bool, + ) bool { + const rc = // read() for files + JSC.Node.Syscall.read(fd, buf); + + switch (rc) { + .err => |err| { + const retry = comptime if (Environment.isLinux) + std.os.E.WOULDBLOCK + else + std.os.E.AGAIN; + + switch (err.getErrno()) { + retry => { + if (would_block) |blocker| { + blocker.* = true; + } else { + onWouldBlock(ctx); + } + + return true; + }, + else => {}, + } + const sys = if (path.len > 0) err.withPath(path).toSystemError() else err.toSystemError(); + + onError(ctx, &sys, global); + return false; + }, + .result => |result| { + // this handles: + // - empty file + // - stream closed for some reason + if ((result == 0 and remaining == 0) or + isCancelled(ctx)) + { + return false; + } + + return onRead( + ctx, + buf, + result, + ) and + // if it's not a regular file, we don't know how much to read so we should continue + (remaining == std.math.maxInt(usize)) or + // if it is a regular file, we stop reading when we've read all the data + !(@intCast(Blob.SizeType, result) >= remaining); + }, + } + } + + pub fn callback(task: ?*anyopaque, sizeOrOffset: i64, _: u16) void { + var this: *FileReader = bun.cast(*FileReader, task.?); + var buf = this.buf; + var blocked = false; + var remaining = std.math.maxInt(usize); + if (comptime Environment.isMac) { + if (std.os.S.ISREG(this.mode)) { + remaining = @intCast(usize, @maximum(sizeOrOffset, 0)); + // Returns when the file pointer is not at the end of + // file. data contains the offset from current position + // to end of file, and may be negative. + buf = buf[0..@minimum(remaining, this.buf.len)]; + } else if (std.os.S.ISCHR(this.mode) or std.os.S.ISFIFO(this.mode)) { + buf = buf[0..@minimum(@intCast(usize, @maximum(sizeOrOffset, 0)), this.buf.len)]; + } + } + + if (read(this.ctx, "", buf, this.fd, remaining, this.loop.global, &blocked) and blocked) { + this.watch(); + return; + } + } + }; +} + +// pub const BlobFileLoader = struct { +// reader: Reader, +// source: ?*anyopaque = null, +// store: *Store, +// pub const Reader = NewFileReader(onRead, onError, onWouldBlock, isCancelled); + +// pub fn onRead(this: *BlobFileReader, buf: []u8, len: Blob.SizeType) bool { +// if (len == 0) { +// return BlobStore__onRead(this.source, null, 0); +// } + +// return BlobStore__onReadExternal(this.source, buf.ptr, len, buf.ptr, JSC.MarkedArrayBuffer_deallocator); +// } + +// pub fn onError(ctx: *BlobFileReader, err: JSC.SystemError) void { +// _ = BlobStore__onError(ctx.source, &err, ctx.reader.global); +// } + +// pub fn isCancelled(ctx: *BlobFileReader) bool { +// return BlobStore__isCancelled(ctx.source); +// } +// }; diff --git a/src/js_ast.zig b/src/js_ast.zig index f9a501537b..5f3b3d44e4 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -211,108 +211,7 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { pub const BindingNodeIndex = Binding; pub const StmtNodeIndex = Stmt; pub const ExprNodeIndex = Expr; - -/// This is like ArrayList except it stores the length and capacity as u32 -/// In practice, it is very unusual to have lengths above 4 GB -/// -/// This lets us have array lists which occupy the same amount of space as a slice -pub fn BabyList(comptime Type: type) type { - return struct { - const ListType = @This(); - ptr: [*]Type = undefined, - len: u32 = 0, - cap: u32 = 0, - - pub inline fn init(items: []const Type) ListType { - @setRuntimeSafety(false); - return ListType{ - // Remove the const qualifier from the items - .ptr = @intToPtr([*]Type, @ptrToInt(items.ptr)), - - .len = @truncate(u32, items.len), - .cap = @truncate(u32, items.len), - }; - } - - pub inline fn fromList(list_: anytype) ListType { - @setRuntimeSafety(false); - - if (comptime Environment.allow_assert) { - std.debug.assert(list_.items.len <= list_.capacity); - } - - return ListType{ - .ptr = list_.items.ptr, - .len = @truncate(u32, list_.items.len), - .cap = @truncate(u32, list_.capacity), - }; - } - - pub fn update(this: *ListType, list_: anytype) void { - @setRuntimeSafety(false); - this.ptr = list_.items.ptr; - this.len = @truncate(u32, list_.items.len); - this.cap = @truncate(u32, list_.capacity); - - if (comptime Environment.allow_assert) { - std.debug.assert(this.len <= this.cap); - } - } - - pub fn list(this: ListType) std.ArrayListUnmanaged(Type) { - return std.ArrayListUnmanaged(Type){ - .items = this.ptr[0..this.len], - .capacity = this.cap, - }; - } - - pub fn listManaged(this: ListType, allocator: std.mem.Allocator) std.ArrayList(Type) { - return std.ArrayList(Type){ - .items = this.ptr[0..this.len], - .capacity = this.cap, - .allocator = allocator, - }; - } - - pub inline fn first(this: ListType) ?*Type { - return if (this.len > 0) this.ptr[0] else @as(?*Type, null); - } - - pub inline fn last(this: ListType) ?*Type { - return if (this.len > 0) &this.ptr[this.len - 1] else @as(?*Type, null); - } - - pub inline fn first_(this: ListType) Type { - return this.ptr[0]; - } - - pub fn one(allocator: std.mem.Allocator, value: Type) !ListType { - var items = try allocator.alloc(Type, 1); - items[0] = value; - return ListType{ - .ptr = @ptrCast([*]Type, items.ptr), - .len = 1, - .cap = 1, - }; - } - - pub inline fn @"[0]"(this: ListType) Type { - return this.ptr[0]; - } - const OOM = error{OutOfMemory}; - - pub fn push(this: *ListType, allocator: std.mem.Allocator, value: Type) OOM!void { - var list_ = this.list(); - try list_.append(allocator, value); - this.update(list_); - } - - pub inline fn slice(this: ListType) []Type { - @setRuntimeSafety(false); - return this.ptr[0..this.len]; - } - }; -} +pub const BabyList = @import("./baby_list.zig").BabyList; /// Slice that stores capacity and length in the same space as a regular slice. pub const ExprNodeList = BabyList(Expr); diff --git a/src/jsc.zig b/src/jsc.zig index fa71683dd3..85c3b9dc39 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -4,6 +4,7 @@ pub const is_bindgen = @import("std").meta.globalOption("bindgen", bool) orelse pub const napi = @import("./napi/napi.zig"); pub usingnamespace @import("./javascript/jsc/bindings/exports.zig"); pub usingnamespace @import("./javascript/jsc/bindings/bindings.zig"); +pub usingnamespace @import("./javascript/jsc/event_loop.zig"); pub usingnamespace @import("./javascript/jsc/base.zig"); pub const RareData = @import("./javascript/jsc/rare_data.zig"); pub usingnamespace @import("./javascript/jsc/javascript.zig"); @@ -39,3 +40,4 @@ pub const Node = struct { pub const Syscall = @import("./javascript/jsc/node/syscall.zig"); pub const fs = @import("./javascript/jsc/node/node_fs_constant.zig"); }; +pub const Maybe = Node.Maybe; diff --git a/src/napi/napi.zig b/src/napi/napi.zig index 7514f43b27..103c0a365c 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -924,7 +924,7 @@ const WorkPoolTask = @import("../work_pool.zig").Task; pub const napi_async_work = struct { task: WorkPoolTask = .{ .callback = runFromThreadPool }, completion_task: ?*anyopaque = null, - event_loop: *JSC.VirtualMachine.EventLoop, + event_loop: *JSC.EventLoop, global: napi_env, execute: napi_async_execute_callback = null, complete: napi_async_complete_callback = null, @@ -1161,7 +1161,7 @@ pub export fn napi_get_node_version(_: napi_env, version: **const napi_node_vers version.* = &napi_node_version.global; return .ok; } -pub export fn napi_get_uv_event_loop(_: napi_env, loop: **JSC.VirtualMachine.EventLoop) napi_status { +pub export fn napi_get_uv_event_loop(_: napi_env, loop: **JSC.EventLoop) napi_status { // lol loop.* = JSC.VirtualMachine.vm.eventLoop(); return .ok; @@ -1226,7 +1226,7 @@ pub const ThreadSafeFunction = struct { owning_threads: std.AutoArrayHashMapUnmanaged(u64, void) = .{}, owning_thread_lock: Lock = Lock.init(), - event_loop: *JSC.VirtualMachine.EventLoop, + event_loop: *JSC.EventLoop, javascript_function: JSValue, finalizer_task: JSC.AnyTask = undefined, diff --git a/src/string_immutable.zig b/src/string_immutable.zig index f1ffb83783..97bbbf4591 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -997,8 +997,8 @@ pub fn toUTF8AllocWithType(allocator: std.mem.Allocator, comptime Type: type, ut list.items.len += utf16_remaining.len; copyU16IntoU8(list.items[old_len..], Type, utf16_remaining); - // don't call toOwnedSlice() because our - return list.items; + // don't call toOwnedSlice() because our + return list.items; } pub const EncodeIntoResult = struct { @@ -1006,6 +1006,12 @@ pub const EncodeIntoResult = struct { written: u32 = 0, }; pub fn allocateLatin1IntoUTF8(allocator: std.mem.Allocator, comptime Type: type, latin1_: Type) ![]u8 { + if (comptime bun.FeatureFlags.latin1_is_now_ascii) { + var out = try allocator.alloc(u8, latin1_.len); + @memcpy(out.ptr, latin1_.ptr, latin1_.len); + return out; + } + var list = try std.ArrayList(u8).initCapacity(allocator, latin1_.len); var latin1 = latin1_; while (latin1.len > 0) { @@ -1133,6 +1139,11 @@ pub fn convertUTF8BytesIntoUTF16(sequence: *const [4]u8) UTF16Replacement { } pub fn copyLatin1IntoUTF8(buf_: []u8, comptime Type: type, latin1_: Type) EncodeIntoResult { + if (comptime bun.FeatureFlags.latin1_is_now_ascii) { + const to_copy = @truncate(u32, @minimum(buf_.len, latin1_.len)); + @memcpy(buf_.ptr, latin1_.ptr, to_copy); + return .{ .written = to_copy, .read = to_copy }; + } var buf = buf_; var latin1 = latin1_; while (buf.len > 0 and latin1.len > 0) { @@ -1171,6 +1182,14 @@ pub fn copyLatin1IntoUTF8(buf_: []u8, comptime Type: type, latin1_: Type) Encode }; } +pub fn replaceLatin1WithUTF8(buf_: []u8) void { + var latin1 = buf_; + while (strings.firstNonASCII(latin1)) |i| { + latin1[i..][0..2].* = latin1ToCodepointBytesAssumeNotASCII(latin1[i]); + latin1 = latin1[i + 2 ..]; + } +} + pub fn elementLengthLatin1IntoUTF8(comptime Type: type, latin1_: Type) usize { var latin1 = latin1_; var count: usize = 0;