diff options
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/base.zig | 55 | ||||
-rw-r--r-- | src/bun.js/bindings/BunString.cpp | 50 | ||||
-rw-r--r-- | src/bun.js/node/node_fs.zig | 344 | ||||
-rw-r--r-- | src/bun.js/node/node_fs_binding.zig | 57 | ||||
-rw-r--r-- | src/bun.js/node/types.zig | 28 |
5 files changed, 338 insertions, 196 deletions
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index f7b2eb343..579a0975a 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -266,58 +266,21 @@ pub const To = struct { // Recursion can stack overflow here if (comptime std.meta.trait.isSlice(Type)) { - const Child = std.meta.Child(Type); - - const prefill = 32; - if (value.len <= prefill) { - var array: [prefill]JSC.C.JSValueRef = undefined; - var i: u8 = 0; - const len = @min(@as(u8, @intCast(value.len)), prefill); - while (i < len and exception.* == null) : (i += 1) { - array[i] = if (comptime Child == JSC.C.JSValueRef) - value[i] - else - To.JS.withType(Child, value[i], context, exception); - } - - if (exception.* != null) { - return null; - } - - // TODO: this function copies to a MarkedArgumentsBuffer - // That copy is unnecessary. - const obj = JSC.C.JSObjectMakeArray(context, len, &array, exception); - - if (exception.* != null) { - return null; - } - return obj; - } - - { - var array = bun.default_allocator.alloc(JSC.C.JSValueRef, value.len) catch unreachable; - defer bun.default_allocator.free(array); - var i: usize = 0; - while (i < value.len and exception.* == null) : (i += 1) { - array[i] = if (comptime Child == JSC.C.JSValueRef) - value[i] - else - To.JS.withType(Child, value[i], context, exception); - } + const Child = comptime std.meta.Child(Type); - if (exception.* != null) { - return null; - } + var array = JSC.JSValue.createEmptyArray(context, value.len); + for (value, 0..) |item, i| { + array.putIndex( + context, + @truncate(i), + JSC.JSValue.c(To.JS.withType(Child, item, context, exception)), + ); - // TODO: this function copies to a MarkedArgumentsBuffer - // That copy is unnecessary. - const obj = JSC.C.JSObjectMakeArray(context, value.len, array.ptr, exception); if (exception.* != null) { return null; } - - return obj; } + return array.asObjectRef(); } if (comptime std.meta.trait.isZigString(Type)) { diff --git a/src/bun.js/bindings/BunString.cpp b/src/bun.js/bindings/BunString.cpp index 714f10080..e044730c4 100644 --- a/src/bun.js/bindings/BunString.cpp +++ b/src/bun.js/bindings/BunString.cpp @@ -256,31 +256,47 @@ extern "C" EncodedJSValue BunString__createArray( auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); - // We must do this or Bun.gc(true) in a loop creating large arrays of strings will crash due to GC'ing. - MarkedArgumentBuffer arguments; - JSC::ObjectInitializationScope scope(vm); - GCDeferralContext context(vm); - - arguments.fill(length, [&](JSC::JSValue* value) { - const BunString* end = ptr + length; - while (ptr != end) { - *value++ = Bun::toJS(globalObject, *ptr++); - } - }); + if (length < 64) { + // We must do this or Bun.gc(true) in a loop creating large arrays of strings will crash due to GC'ing. + MarkedArgumentBuffer arguments; + + arguments.fill(length, [&](JSC::JSValue* value) { + const BunString* end = ptr + length; + while (ptr != end) { + *value++ = Bun::toJS(globalObject, *ptr++); + } + }); + + JSC::ObjectInitializationScope scope(vm); + GCDeferralContext context(vm); - if (JSC::JSArray* array = JSC::JSArray::tryCreateUninitializedRestricted( + JSC::JSArray* array = JSC::JSArray::tryCreateUninitializedRestricted( scope, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), - length)) { + length); + + if (array) { + for (size_t i = 0; i < length; ++i) { + array->initializeIndex(scope, i, arguments.at(i)); + } + return JSValue::encode(array); + } + + JSC::throwOutOfMemoryError(globalObject, throwScope); + RELEASE_AND_RETURN(throwScope, JSValue::encode(JSC::JSValue())); + } else { + JSC::JSArray* array = constructEmptyArray(globalObject, nullptr, length); + if (!array) { + JSC::throwOutOfMemoryError(globalObject, throwScope); + RELEASE_AND_RETURN(throwScope, JSValue::encode(JSC::JSValue())); + } for (size_t i = 0; i < length; ++i) { - array->initializeIndex(scope, i, arguments.at(i)); + array->putDirectIndex(globalObject, i, Bun::toJS(globalObject, *ptr++)); } + return JSValue::encode(array); } - - JSC::throwOutOfMemoryError(globalObject, throwScope); - RELEASE_AND_RETURN(throwScope, JSValue::encode(JSC::JSValue())); } extern "C" void BunString__toWTFString(BunString* bunString) diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 314cd44bd..6d2ec4120 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -55,6 +55,160 @@ const ArrayBuffer = JSC.MarkedArrayBuffer; const Buffer = JSC.Buffer; const FileSystemFlags = JSC.Node.FileSystemFlags; +pub const AsyncReaddirTask = struct { + promise: JSC.JSPromise.Strong, + args: Arguments.Readdir, + globalObject: *JSC.JSGlobalObject, + task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, + result: JSC.Maybe(Return.Readdir), + ref: JSC.PollRef = .{}, + + pub fn create(globalObject: *JSC.JSGlobalObject, readdir_args: Arguments.Readdir, vm: *JSC.VirtualMachine) JSC.JSValue { + var task = bun.default_allocator.create(AsyncReaddirTask) catch @panic("out of memory"); + task.* = AsyncReaddirTask{ + .promise = JSC.JSPromise.Strong.init(globalObject), + .args = readdir_args, + .result = undefined, + .globalObject = globalObject, + }; + task.ref.ref(vm); + + JSC.WorkPool.schedule(&task.task); + + return task.promise.value(); + } + + fn workPoolCallback(task: *JSC.WorkPoolTask) void { + var this: *AsyncReaddirTask = @fieldParentPtr(AsyncReaddirTask, "task", task); + + var node_fs = NodeFS{}; + this.result = node_fs.readdir(this.args, .promise); + + this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); + } + + fn runFromJSThread(this: *AsyncReaddirTask) void { + var globalObject = this.globalObject; + var success = @as(JSC.Maybe(Return.Readdir).Tag, this.result) == .result; + const result = switch (this.result) { + .err => |err| err.toJSC(globalObject), + .result => |res| brk: { + var exceptionref: JSC.C.JSValueRef = null; + const out = JSC.JSValue.c(JSC.To.JS.withType(Return.Readdir, res, globalObject, &exceptionref)); + const exception = JSC.JSValue.c(exceptionref); + if (exception != .zero) { + success = false; + break :brk exception; + } + + break :brk out; + }, + }; + var promise_value = this.promise.value(); + var promise = this.promise.get(); + promise_value.ensureStillAlive(); + + this.deinit(); + switch (success) { + false => { + promise.reject(globalObject, result); + }, + true => { + promise.resolve(globalObject, result); + }, + } + } + + pub fn deinit(this: *AsyncReaddirTask) void { + this.ref.unref(this.globalObject.bunVM()); + this.args.deinit(); + this.promise.strong.deinit(); + bun.default_allocator.destroy(this); + } +}; + +pub const AsyncStatTask = struct { + promise: JSC.JSPromise.Strong, + args: Arguments.Stat, + globalObject: *JSC.JSGlobalObject, + task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, + result: JSC.Maybe(Return.Stat), + ref: JSC.PollRef = .{}, + is_lstat: bool = false, + + pub fn create( + globalObject: *JSC.JSGlobalObject, + readdir_args: Arguments.Stat, + vm: *JSC.VirtualMachine, + is_lstat: bool, + ) JSC.JSValue { + var task = bun.default_allocator.create(AsyncStatTask) catch @panic("out of memory"); + task.* = AsyncStatTask{ + .promise = JSC.JSPromise.Strong.init(globalObject), + .args = readdir_args, + .result = undefined, + .globalObject = globalObject, + .is_lstat = is_lstat, + }; + task.ref.ref(vm); + + JSC.WorkPool.schedule(&task.task); + + return task.promise.value(); + } + + fn workPoolCallback(task: *JSC.WorkPoolTask) void { + var this: *AsyncStatTask = @fieldParentPtr(AsyncStatTask, "task", task); + + var node_fs = NodeFS{}; + this.result = if (this.is_lstat) + node_fs.lstat(this.args, .promise) + else + node_fs.stat(this.args, .promise); + + this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); + } + + fn runFromJSThread(this: *AsyncStatTask) void { + var globalObject = this.globalObject; + var success = @as(JSC.Maybe(Return.Lstat).Tag, this.result) == .result; + const result = switch (this.result) { + .err => |err| err.toJSC(globalObject), + .result => |res| brk: { + var exceptionref: JSC.C.JSValueRef = null; + const out = JSC.JSValue.c(JSC.To.JS.withType(Return.Lstat, res, globalObject, &exceptionref)); + const exception = JSC.JSValue.c(exceptionref); + if (exception != .zero) { + success = false; + break :brk exception; + } + + break :brk out; + }, + }; + var promise_value = this.promise.value(); + var promise = this.promise.get(); + promise_value.ensureStillAlive(); + + this.deinit(); + switch (success) { + false => { + promise.reject(globalObject, result); + }, + true => { + promise.resolve(globalObject, result); + }, + } + } + + pub fn deinit(this: *AsyncStatTask) void { + this.ref.unref(this.globalObject.bunVM()); + this.args.deinit(); + this.promise.strong.deinit(); + bun.default_allocator.destroy(this); + } +}; + // TODO: to improve performance for all of these // The tagged unions for each type should become regular unions // and the tags should be passed in as comptime arguments to the functions performing the syscalls @@ -2624,11 +2778,20 @@ const Return = struct { }; pub fn toJS(this: Readdir, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef { - return switch (this) { - .with_file_types => JSC.To.JS.withType([]const Dirent, this.with_file_types, ctx, exception), - .buffers => JSC.To.JS.withType([]const Buffer, this.buffers, ctx, exception), - .files => JSC.To.JS.withType([]const bun.String, this.files, ctx, exception), - }; + switch (this) { + .with_file_types => { + defer bun.default_allocator.free(this.with_file_types); + return JSC.To.JS.withType([]const Dirent, this.with_file_types, ctx, exception); + }, + .buffers => { + defer bun.default_allocator.free(this.buffers); + return JSC.To.JS.withType([]const Buffer, this.buffers, ctx, exception); + }, + .files => { + // automatically freed + return JSC.To.JS.withType([]const bun.String, this.files, ctx, exception); + }, + } } }; pub const ReadFile = JSC.Node.StringOrNodeBuffer; @@ -3145,28 +3308,22 @@ pub const NodeFS = struct { return Maybe(Return.Link).todo; } pub fn lstat(this: *NodeFS, args: Arguments.Lstat, comptime flavor: Flavor) Maybe(Return.Lstat) { + _ = flavor; if (args.big_int) return Maybe(Return.Lstat).todo; - switch (comptime flavor) { - .sync => { - return switch (Syscall.lstat( - args.path.sliceZ( - &this.sync_error_buf, - ), - )) { - .result => |result| Maybe(Return.Lstat){ .result = .{ .stats = Stats.init(result, args.big_int) } }, - .err => |err| brk: { - if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { - return Maybe(Return.Lstat){ .result = .{ .not_found = {} } }; - } - break :brk Maybe(Return.Lstat){ .err = err }; - }, - }; + return switch (Syscall.lstat( + args.path.sliceZ( + &this.sync_error_buf, + ), + )) { + .result => |result| Maybe(Return.Lstat){ .result = .{ .stats = Stats.init(result, args.big_int) } }, + .err => |err| brk: { + if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { + return Maybe(Return.Lstat){ .result = .{ .not_found = {} } }; + } + break :brk Maybe(Return.Lstat){ .err = err }; }, - else => {}, - } - - return Maybe(Return.Lstat).todo; + }; } pub fn mkdir(this: *NodeFS, args: Arguments.Mkdir, comptime flavor: Flavor) Maybe(Return.Mkdir) { @@ -3575,7 +3732,7 @@ pub const NodeFS = struct { pub fn readdir(this: *NodeFS, args: Arguments.Readdir, comptime flavor: Flavor) Maybe(Return.Readdir) { return switch (args.encoding) { .buffer => _readdir( - this, + &this.sync_error_buf, args, Buffer, flavor, @@ -3583,7 +3740,7 @@ pub const NodeFS = struct { else => { if (!args.with_file_types) { return _readdir( - this, + &this.sync_error_buf, args, bun.String, flavor, @@ -3591,7 +3748,7 @@ pub const NodeFS = struct { } return _readdir( - this, + &this.sync_error_buf, args, Dirent, flavor, @@ -3601,10 +3758,10 @@ pub const NodeFS = struct { } pub fn _readdir( - this: *NodeFS, + buf: *[bun.MAX_PATH_BYTES]u8, args: Arguments.Readdir, comptime ExpectedType: type, - comptime flavor: Flavor, + comptime _: Flavor, ) Maybe(Return.Readdir) { const file_type = comptime switch (ExpectedType) { Dirent => "with_file_types", @@ -3613,73 +3770,66 @@ pub const NodeFS = struct { else => unreachable, }; - switch (comptime flavor) { - .sync => { - var path = args.path.sliceZ(&this.sync_error_buf); - const flags = os.O.DIRECTORY | os.O.RDONLY; - const fd = switch (Syscall.open(path, flags, 0)) { - .err => |err| return .{ - .err = err.withPath(args.path.slice()), - }, - .result => |fd_| fd_, - }; - defer { - _ = Syscall.close(fd); - } - - var entries = std.ArrayList(ExpectedType).init(bun.default_allocator); - var dir = std.fs.Dir{ .fd = fd }; - var iterator = DirIterator.iterate(dir); - var entry = iterator.next(); - while (switch (entry) { - .err => |err| { - for (entries.items) |*item| { - switch (comptime ExpectedType) { - Dirent => { - item.name.deref(); - }, - Buffer => { - item.destroy(); - }, - bun.String => { - item.deref(); - }, - else => unreachable, - } - } - - entries.deinit(); + var path = args.path.sliceZ(buf); + const flags = os.O.DIRECTORY | os.O.RDONLY; + const fd = switch (Syscall.open(path, flags, 0)) { + .err => |err| return .{ + .err = err.withPath(args.path.slice()), + }, + .result => |fd_| fd_, + }; + defer { + _ = Syscall.close(fd); + } - return .{ - .err = err.withPath(args.path.slice()), - }; - }, - .result => |ent| ent, - }) |current| : (entry = iterator.next()) { - const utf8_name = current.name.slice(); + var entries = std.ArrayList(ExpectedType).init(bun.default_allocator); + var dir = std.fs.Dir{ .fd = fd }; + var iterator = DirIterator.iterate(dir); + var entry = iterator.next(); + while (switch (entry) { + .err => |err| { + for (entries.items) |*item| { switch (comptime ExpectedType) { Dirent => { - entries.append(.{ - .name = bun.String.create(utf8_name), - .kind = current.kind, - }) catch unreachable; + item.name.deref(); }, Buffer => { - entries.append(Buffer.fromString(utf8_name, bun.default_allocator) catch unreachable) catch unreachable; + item.destroy(); }, bun.String => { - entries.append(bun.String.create(utf8_name)) catch unreachable; + item.deref(); }, else => unreachable, } } - return .{ .result = @unionInit(Return.Readdir, file_type, entries.items) }; + entries.deinit(); + + return .{ + .err = err.withPath(args.path.slice()), + }; }, - else => {}, + .result => |ent| ent, + }) |current| : (entry = iterator.next()) { + const utf8_name = current.name.slice(); + switch (comptime ExpectedType) { + Dirent => { + entries.append(.{ + .name = bun.String.create(utf8_name), + .kind = current.kind, + }) catch unreachable; + }, + Buffer => { + entries.append(Buffer.fromString(utf8_name, bun.default_allocator) catch unreachable) catch unreachable; + }, + bun.String => { + entries.append(bun.String.create(utf8_name)) catch unreachable; + }, + else => unreachable, + } } - return Maybe(Return.Readdir).todo; + return .{ .result = @unionInit(Return.Readdir, file_type, entries.items) }; } pub const StringType = enum { @@ -4354,28 +4504,22 @@ pub const NodeFS = struct { return Maybe(Return.Rm).todo; } pub fn stat(this: *NodeFS, args: Arguments.Stat, comptime flavor: Flavor) Maybe(Return.Stat) { + _ = flavor; if (args.big_int) return Maybe(Return.Stat).todo; - switch (comptime flavor) { - .sync => { - return @as(Maybe(Return.Stat), switch (Syscall.stat( - args.path.sliceZ( - &this.sync_error_buf, - ), - )) { - .result => |result| Maybe(Return.Stat){ .result = .{ .stats = Stats.init(result, args.big_int) } }, - .err => |err| brk: { - if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { - return Maybe(Return.Stat){ .result = .{ .not_found = {} } }; - } - break :brk Maybe(Return.Stat){ .err = err }; - }, - }); + return @as(Maybe(Return.Stat), switch (Syscall.stat( + args.path.sliceZ( + &this.sync_error_buf, + ), + )) { + .result => |result| Maybe(Return.Stat){ .result = .{ .stats = Stats.init(result, args.big_int) } }, + .err => |err| brk: { + if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { + return Maybe(Return.Stat){ .result = .{ .not_found = {} } }; + } + break :brk Maybe(Return.Stat){ .err = err }; }, - else => {}, - } - - return Maybe(Return.Stat).todo; + }); } pub fn symlink(this: *NodeFS, args: Arguments.Symlink, comptime flavor: Flavor) Maybe(Return.Symlink) { diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig index a4cc62cd3..88e0b30e2 100644 --- a/src/bun.js/node/node_fs_binding.zig +++ b/src/bun.js/node/node_fs_binding.zig @@ -95,25 +95,54 @@ fn callSync(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction { return NodeBindingClosure.bind; } -fn call(comptime Function: NodeFSFunctionEnum) NodeFSFunction { - // const FunctionType = @TypeOf(Function); - _ = Function; - - // const function: std.builtin.Type.Fn = comptime @typeInfo(FunctionType).Fn; - // comptime if (function.args.len != 3) @compileError("Expected 3 arguments"); - // const Arguments = comptime function.args[2].type orelse @compileError(std.fmt.comptimePrint("Function {s} expected to have an arg type at [2]", .{@typeName(FunctionType)})); - // const Result = comptime function.return_type.?; - // comptime if (Arguments != void and !fromJSTrait(Arguments)) @compileError(std.fmt.comptimePrint("{s} is missing fromJS()", .{@typeName(Arguments)})); - // comptime if (Result != void and !toJSTrait(Result)) @compileError(std.fmt.comptimePrint("{s} is missing toJS()", .{@typeName(Result)})); +fn call(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction { + const Function = @field(JSC.Node.NodeFS, @tagName(FunctionEnum)); + const FunctionType = @TypeOf(Function); + + const function: std.builtin.Type.Fn = comptime @typeInfo(FunctionType).Fn; + comptime if (function.params.len != 3) @compileError("Expected 3 arguments"); + const Arguments = comptime function.params[1].type.?; const NodeBindingClosure = struct { pub fn bind( _: *JSC.Node.NodeJSFS, globalObject: *JSC.JSGlobalObject, - _: *JSC.CallFrame, + callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - globalObject.throw("Not implemented yet", .{}); - return .zero; - // var slice = ArgumentsSlice.init(arguments); + if (comptime FunctionEnum != .readdir and FunctionEnum != .lstat and FunctionEnum != .stat) { + globalObject.throw("Not implemented yet", .{}); + return .zero; + } + + var arguments = callframe.arguments(8); + + var slice = ArgumentsSlice.init(globalObject.bunVM(), arguments.ptr[0..arguments.len]); + var exceptionref: JSC.C.JSValueRef = null; + const args = if (comptime Arguments != void) + (Arguments.fromJS(globalObject, &slice, &exceptionref) orelse { + // we might've already thrown + if (exceptionref != null) + globalObject.throwValue(JSC.JSValue.c(exceptionref)); + return .zero; + }) + else + Arguments{}; + + const exception1 = JSC.JSValue.c(exceptionref); + + if (exception1 != .zero) { + globalObject.throwValue(exception1); + return .zero; + } + + // TODO: handle globalObject.throwValue + + if (comptime FunctionEnum == .readdir) { + return JSC.Node.AsyncReaddirTask.create(globalObject, args, slice.vm); + } + + if (comptime FunctionEnum == .stat or FunctionEnum == .lstat) { + return JSC.Node.AsyncStatTask.create(globalObject, args, slice.vm, FunctionEnum == .lstat); + } // defer { // for (arguments.len) |arg| { diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 23d693d69..dadf28629 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -1937,27 +1937,20 @@ pub const Path = struct { ) callconv(.C) JSC.JSValue { if (comptime is_bindgen) return JSC.JSValue.jsUndefined(); if (args_len == 0) return JSC.ZigString.init("").toValue(globalThis); - + var arena = @import("root").bun.ArenaAllocator.init(heap_allocator); + var arena_allocator = arena.allocator(); var stack_fallback_allocator = std.heap.stackFallback( - (32 * @sizeOf(string)), - heap_allocator, + ((32 * @sizeOf(string)) + 1024), + arena_allocator, ); var allocator = stack_fallback_allocator.get(); - var arena = @import("root").bun.ArenaAllocator.init(heap_allocator); - var arena_allocator = arena.allocator(); + defer arena.deinit(); var buf: [bun.MAX_PATH_BYTES]u8 = undefined; var to_join = allocator.alloc(string, args_len) catch unreachable; - var possibly_utf16 = false; for (args_ptr[0..args_len], 0..) |arg, i| { const zig_str: JSC.ZigString = arg.getZigString(globalThis); - if (zig_str.is16Bit()) { - // TODO: remove this string conversion - to_join[i] = zig_str.toSlice(arena_allocator).slice(); - possibly_utf16 = true; - } else { - to_join[i] = zig_str.slice(); - } + to_join[i] = zig_str.toSlice(allocator).slice(); } const out = if (!isWindows) @@ -1965,12 +1958,9 @@ pub const Path = struct { else PathHandler.joinStringBuf(&buf, to_join, .windows); - var out_str = JSC.ZigString.init(out); - if (possibly_utf16) { - out_str.setOutputEncoding(); - } - - return out_str.toValueGC(globalThis); + var str = bun.String.create(out); + defer str.deref(); + return str.toJS(globalThis); } pub fn normalize(globalThis: *JSC.JSGlobalObject, isWindows: bool, args_ptr: [*]JSC.JSValue, args_len: u16) callconv(.C) JSC.JSValue { if (comptime is_bindgen) return JSC.JSValue.jsUndefined(); |