mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 19:08:50 +00:00
Fixes ENG-21287
Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`
|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|
Runtime performance:
- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)
Improvements:
- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests
Still an issue:
- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed
Follow-up:
- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully
Files that add lines:
```
649 src/deprecated.zig
167 scripts/pack-codegen-for-zig-team.ts
54 scripts/cleartrace-impl.js
46 scripts/cleartrace.ts
43 src/windows.zig
18 src/fs.zig
17 src/bun.js/ConsoleObject.zig
16 src/output.zig
12 src/bun.js/test/debug.zig
12 src/bun.js/node/node_fs.zig
8 src/env_loader.zig
7 src/css/printer.zig
7 src/cli/init_command.zig
7 src/bun.js/node.zig
6 src/string/escapeRegExp.zig
6 src/install/PnpmMatcher.zig
5 src/bun.js/webcore/Blob.zig
4 src/crash_handler.zig
4 src/bun.zig
3 src/install/lockfile/bun.lock.zig
3 src/cli/update_interactive_command.zig
3 src/cli/pack_command.zig
3 build.zig
2 src/Progress.zig
2 src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2 src/css/small_list.zig
2 src/bun.js/webcore/prompt.zig
1 test/internal/ban-words.test.ts
1 test/internal/ban-limits.json
1 src/watcher/WatcherTrace.zig
1 src/transpiler.zig
1 src/shell/builtin/cp.zig
1 src/js_printer.zig
1 src/io/PipeReader.zig
1 src/install/bin.zig
1 src/css/selectors/selector.zig
1 src/cli/run_command.zig
1 src/bun.js/RuntimeTranspilerStore.zig
1 src/bun.js/bindings/JSRef.zig
1 src/bake/DevServer.zig
```
Files that remove lines:
```
-1 src/test/recover.zig
-1 src/sql/postgres/SocketMonitor.zig
-1 src/sql/mysql/MySQLRequestQueue.zig
-1 src/sourcemap/CodeCoverage.zig
-1 src/css/values/color_js.zig
-1 src/compile_target.zig
-1 src/bundler/linker_context/convertStmtsForChunk.zig
-1 src/bundler/bundle_v2.zig
-1 src/bun.js/webcore/blob/read_file.zig
-1 src/ast/base.zig
-2 src/sql/postgres/protocol/ArrayList.zig
-2 src/shell/builtin/mkdir.zig
-2 src/install/PackageManager/patchPackage.zig
-2 src/install/PackageManager/PackageManagerDirectories.zig
-2 src/fmt.zig
-2 src/css/declaration.zig
-2 src/css/css_parser.zig
-2 src/collections/baby_list.zig
-2 src/bun.js/bindings/ZigStackFrame.zig
-2 src/ast/E.zig
-3 src/StandaloneModuleGraph.zig
-3 src/deps/picohttp.zig
-3 src/deps/libuv.zig
-3 src/btjs.zig
-4 src/threading/Futex.zig
-4 src/shell/builtin/touch.zig
-4 src/meta.zig
-4 src/install/lockfile.zig
-4 src/css/selectors/parser.zig
-5 src/shell/interpreter.zig
-5 src/css/error.zig
-5 src/bun.js/web_worker.zig
-5 src/bun.js.zig
-6 src/cli/test_command.zig
-6 src/bun.js/VirtualMachine.zig
-6 src/bun.js/uuid.zig
-6 src/bun.js/bindings/JSValue.zig
-9 src/bun.js/test/pretty_format.zig
-9 src/bun.js/api/BunObject.zig
-14 src/install/install_binding.zig
-14 src/fd.zig
-14 src/bun.js/node/path.zig
-14 scripts/pack-codegen-for-zig-team.sh
-17 src/bun.js/test/diff_format.zig
```
`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
413 lines
13 KiB
Zig
413 lines
13 KiB
Zig
const Touch = @This();
|
|
|
|
opts: Opts = .{},
|
|
state: union(enum) {
|
|
idle,
|
|
exec: struct {
|
|
started: bool = false,
|
|
tasks_count: usize = 0,
|
|
tasks_done: usize = 0,
|
|
output_done: usize = 0,
|
|
output_waiting: usize = 0,
|
|
started_output_queue: bool = false,
|
|
args: []const [*:0]const u8,
|
|
err: ?jsc.SystemError = null,
|
|
},
|
|
waiting_write_err,
|
|
done,
|
|
} = .idle,
|
|
|
|
pub fn format(this: *const Touch, writer: *std.Io.Writer) !void {
|
|
try writer.print("Touch(0x{x}, state={s})", .{ @intFromPtr(this), @tagName(this.state) });
|
|
}
|
|
|
|
pub fn deinit(this: *Touch) void {
|
|
log("{f} deinit", .{this});
|
|
}
|
|
|
|
pub fn start(this: *Touch) Yield {
|
|
const filepath_args = switch (this.opts.parse(this.bltn().argsSlice())) {
|
|
.ok => |filepath_args| filepath_args,
|
|
.err => |e| {
|
|
const buf = switch (e) {
|
|
.illegal_option => |opt_str| this.bltn().fmtErrorArena(.touch, "illegal option -- {s}\n", .{opt_str}),
|
|
.show_usage => Builtin.Kind.touch.usageString(),
|
|
.unsupported => |unsupported| this.bltn().fmtErrorArena(.touch, "unsupported option, please open a GitHub issue -- {s}\n", .{unsupported}),
|
|
};
|
|
|
|
return this.writeFailingError(buf, 1);
|
|
},
|
|
} orelse {
|
|
return this.writeFailingError(Builtin.Kind.touch.usageString(), 1);
|
|
};
|
|
|
|
this.state = .{
|
|
.exec = .{
|
|
.args = filepath_args,
|
|
},
|
|
};
|
|
|
|
return this.next();
|
|
}
|
|
|
|
pub fn next(this: *Touch) Yield {
|
|
switch (this.state) {
|
|
.idle => @panic("Invalid state"),
|
|
.exec => {
|
|
var exec = &this.state.exec;
|
|
if (exec.started) {
|
|
if (this.state.exec.tasks_done >= this.state.exec.tasks_count and this.state.exec.output_done >= this.state.exec.output_waiting) {
|
|
const exit_code: ExitCode = if (this.state.exec.err != null) 1 else 0;
|
|
this.state = .done;
|
|
return this.bltn().done(exit_code);
|
|
}
|
|
return .suspended;
|
|
}
|
|
|
|
exec.started = true;
|
|
exec.tasks_count = exec.args.len;
|
|
|
|
for (exec.args) |dir_to_mk_| {
|
|
const dir_to_mk = dir_to_mk_[0..std.mem.len(dir_to_mk_) :0];
|
|
var task = ShellTouchTask.create(this, this.opts, dir_to_mk, this.bltn().parentCmd().base.shell.cwdZ());
|
|
task.schedule();
|
|
}
|
|
return .suspended;
|
|
},
|
|
.waiting_write_err => return .failed,
|
|
.done => return this.bltn().done(0),
|
|
}
|
|
}
|
|
|
|
pub fn onIOWriterChunk(this: *Touch, _: usize, e: ?jsc.SystemError) Yield {
|
|
if (this.state == .waiting_write_err) {
|
|
return this.bltn().done(1);
|
|
}
|
|
|
|
if (e) |err| err.deref();
|
|
|
|
return this.next();
|
|
}
|
|
|
|
pub fn writeFailingError(this: *Touch, buf: []const u8, exit_code: ExitCode) Yield {
|
|
if (this.bltn().stderr.needsIO()) |safeguard| {
|
|
this.state = .waiting_write_err;
|
|
return this.bltn().stderr.enqueue(this, buf, safeguard);
|
|
}
|
|
|
|
_ = this.bltn().writeNoIO(.stderr, buf);
|
|
|
|
return this.bltn().done(exit_code);
|
|
}
|
|
|
|
pub fn onShellTouchTaskDone(this: *Touch, task: *ShellTouchTask) void {
|
|
log("{f} onShellTouchTaskDone {f} tasks_done={d} tasks_count={d}", .{ this, task, this.state.exec.tasks_done, this.state.exec.tasks_count });
|
|
|
|
defer bun.default_allocator.destroy(task);
|
|
this.state.exec.tasks_done += 1;
|
|
const err = task.err;
|
|
|
|
if (err) |e| {
|
|
const output_task: *ShellTouchOutputTask = bun.new(ShellTouchOutputTask, .{
|
|
.parent = this,
|
|
.output = .{ .arrlist = .{} },
|
|
.state = .waiting_write_err,
|
|
});
|
|
const error_string = this.bltn().taskErrorToString(.touch, e);
|
|
this.state.exec.err = e;
|
|
output_task.start(error_string).run();
|
|
return;
|
|
}
|
|
|
|
this.next().run();
|
|
}
|
|
|
|
pub const ShellTouchOutputTask = OutputTask(Touch, .{
|
|
.writeErr = ShellTouchOutputTaskVTable.writeErr,
|
|
.onWriteErr = ShellTouchOutputTaskVTable.onWriteErr,
|
|
.writeOut = ShellTouchOutputTaskVTable.writeOut,
|
|
.onWriteOut = ShellTouchOutputTaskVTable.onWriteOut,
|
|
.onDone = ShellTouchOutputTaskVTable.onDone,
|
|
});
|
|
|
|
const ShellTouchOutputTaskVTable = struct {
|
|
pub fn writeErr(this: *Touch, childptr: anytype, errbuf: []const u8) ?Yield {
|
|
if (this.bltn().stderr.needsIO()) |safeguard| {
|
|
this.state.exec.output_waiting += 1;
|
|
return this.bltn().stderr.enqueue(childptr, errbuf, safeguard);
|
|
}
|
|
_ = this.bltn().writeNoIO(.stderr, errbuf);
|
|
return null;
|
|
}
|
|
|
|
pub fn onWriteErr(this: *Touch) void {
|
|
this.state.exec.output_done += 1;
|
|
}
|
|
|
|
pub fn writeOut(this: *Touch, childptr: anytype, output: *OutputSrc) ?Yield {
|
|
if (this.bltn().stdout.needsIO()) |safeguard| {
|
|
this.state.exec.output_waiting += 1;
|
|
const slice = output.slice();
|
|
log("THE SLICE: {d} {s}", .{ slice.len, slice });
|
|
return this.bltn().stdout.enqueue(childptr, slice, safeguard);
|
|
}
|
|
_ = this.bltn().writeNoIO(.stdout, output.slice());
|
|
return null;
|
|
}
|
|
|
|
pub fn onWriteOut(this: *Touch) void {
|
|
this.state.exec.output_done += 1;
|
|
}
|
|
|
|
pub fn onDone(this: *Touch) Yield {
|
|
return this.next();
|
|
}
|
|
};
|
|
|
|
pub const ShellTouchTask = struct {
|
|
touch: *Touch,
|
|
|
|
opts: Opts,
|
|
filepath: [:0]const u8,
|
|
cwd_path: [:0]const u8,
|
|
|
|
err: ?jsc.SystemError = null,
|
|
task: jsc.WorkPoolTask = .{ .callback = &runFromThreadPool },
|
|
event_loop: jsc.EventLoopHandle,
|
|
concurrent_task: jsc.EventLoopTask,
|
|
|
|
pub fn format(this: *const ShellTouchTask, writer: *std.Io.Writer) !void {
|
|
try writer.print("ShellTouchTask(0x{x}, filepath={s})", .{ @intFromPtr(this), this.filepath });
|
|
}
|
|
|
|
pub fn deinit(this: *ShellTouchTask) void {
|
|
if (this.err) |*e| {
|
|
e.deref();
|
|
}
|
|
bun.default_allocator.destroy(this);
|
|
}
|
|
|
|
pub fn create(touch: *Touch, opts: Opts, filepath: [:0]const u8, cwd_path: [:0]const u8) *ShellTouchTask {
|
|
const task = bun.handleOom(bun.default_allocator.create(ShellTouchTask));
|
|
task.* = ShellTouchTask{
|
|
.touch = touch,
|
|
.opts = opts,
|
|
.cwd_path = cwd_path,
|
|
.filepath = filepath,
|
|
.event_loop = touch.bltn().eventLoop(),
|
|
.concurrent_task = jsc.EventLoopTask.fromEventLoop(touch.bltn().eventLoop()),
|
|
};
|
|
return task;
|
|
}
|
|
|
|
pub fn schedule(this: *@This()) void {
|
|
debug("{f} schedule", .{this});
|
|
WorkPool.schedule(&this.task);
|
|
}
|
|
|
|
pub fn runFromMainThread(this: *@This()) void {
|
|
debug("{f} runFromJS", .{this});
|
|
this.touch.onShellTouchTaskDone(this);
|
|
}
|
|
|
|
pub fn runFromMainThreadMini(this: *@This(), _: *void) void {
|
|
this.runFromMainThread();
|
|
}
|
|
|
|
fn runFromThreadPool(task: *jsc.WorkPoolTask) void {
|
|
var this: *ShellTouchTask = @fieldParentPtr("task", task);
|
|
debug("{f} runFromThreadPool", .{this});
|
|
|
|
// We have to give an absolute path
|
|
const filepath: [:0]const u8 = brk: {
|
|
if (ResolvePath.Platform.auto.isAbsolute(this.filepath)) break :brk this.filepath;
|
|
const parts: []const []const u8 = &.{
|
|
this.cwd_path[0..],
|
|
this.filepath[0..],
|
|
};
|
|
break :brk ResolvePath.joinZ(parts, .auto);
|
|
};
|
|
|
|
var node_fs = jsc.Node.fs.NodeFS{};
|
|
const milliseconds: f64 = @floatFromInt(std.time.milliTimestamp());
|
|
const atime: jsc.Node.TimeLike = if (bun.Environment.isWindows) milliseconds / 1000.0 else jsc.Node.TimeLike{
|
|
.sec = @intFromFloat(@divFloor(milliseconds, std.time.ms_per_s)),
|
|
.nsec = @intFromFloat(@mod(milliseconds, std.time.ms_per_s) * std.time.ns_per_ms),
|
|
};
|
|
const mtime = atime;
|
|
const args = jsc.Node.fs.Arguments.Utimes{
|
|
.atime = atime,
|
|
.mtime = mtime,
|
|
.path = .{ .string = bun.PathString.init(filepath) },
|
|
};
|
|
if (node_fs.utimes(args, .sync).asErr()) |err| out: {
|
|
if (err.getErrno() == .NOENT) {
|
|
const perm = 0o664;
|
|
switch (Syscall.open(filepath, bun.O.CREAT | bun.O.WRONLY, perm)) {
|
|
.result => |fd| {
|
|
fd.close();
|
|
break :out;
|
|
},
|
|
.err => |e| {
|
|
this.err = e.withPath(bun.handleOom(bun.default_allocator.dupe(u8, filepath))).toShellSystemError();
|
|
break :out;
|
|
},
|
|
}
|
|
}
|
|
this.err = err.withPath(bun.handleOom(bun.default_allocator.dupe(u8, filepath))).toShellSystemError();
|
|
}
|
|
|
|
if (this.event_loop == .js) {
|
|
this.event_loop.js.enqueueTaskConcurrent(this.concurrent_task.js.from(this, .manual_deinit));
|
|
} else {
|
|
this.event_loop.mini.enqueueTaskConcurrent(this.concurrent_task.mini.from(this, "runFromMainThreadMini"));
|
|
}
|
|
}
|
|
};
|
|
|
|
const Opts = struct {
|
|
/// -a
|
|
///
|
|
/// change only the access time
|
|
access_time_only: bool = false,
|
|
|
|
/// -c, --no-create
|
|
///
|
|
/// do not create any files
|
|
no_create: bool = false,
|
|
|
|
/// -d, --date=STRING
|
|
///
|
|
/// parse STRING and use it instead of current time
|
|
date: ?[]const u8 = null,
|
|
|
|
/// -h, --no-dereference
|
|
///
|
|
/// affect each symbolic link instead of any referenced file
|
|
/// (useful only on systems that can change the timestamps of a symlink)
|
|
no_dereference: bool = false,
|
|
|
|
/// -m
|
|
///
|
|
/// change only the modification time
|
|
modification_time_only: bool = false,
|
|
|
|
/// -r, --reference=FILE
|
|
///
|
|
/// use this file's times instead of current time
|
|
reference: ?[]const u8 = null,
|
|
|
|
/// -t STAMP
|
|
///
|
|
/// use [[CC]YY]MMDDhhmm[.ss] instead of current time
|
|
timestamp: ?[]const u8 = null,
|
|
|
|
/// --time=WORD
|
|
///
|
|
/// change the specified time:
|
|
/// WORD is access, atime, or use: equivalent to -a
|
|
/// WORD is modify or mtime: equivalent to -m
|
|
time: ?[]const u8 = null,
|
|
|
|
const Parse = FlagParser(*@This());
|
|
|
|
pub fn parse(opts: *Opts, args: []const [*:0]const u8) Result(?[]const [*:0]const u8, ParseError) {
|
|
return Parse.parseFlags(opts, args);
|
|
}
|
|
|
|
pub fn parseLong(this: *Opts, flag: []const u8) ?ParseFlagResult {
|
|
_ = this;
|
|
if (bun.strings.eqlComptime(flag, "--no-create")) {
|
|
return .{
|
|
.unsupported = unsupportedFlag("--no-create"),
|
|
};
|
|
}
|
|
|
|
if (bun.strings.eqlComptime(flag, "--date")) {
|
|
return .{
|
|
.unsupported = unsupportedFlag("--date"),
|
|
};
|
|
}
|
|
|
|
if (bun.strings.eqlComptime(flag, "--reference")) {
|
|
return .{
|
|
.unsupported = unsupportedFlag("--reference=FILE"),
|
|
};
|
|
}
|
|
|
|
if (bun.strings.eqlComptime(flag, "--time")) {
|
|
return .{
|
|
.unsupported = unsupportedFlag("--reference=FILE"),
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn parseShort(this: *Opts, char: u8, smallflags: []const u8, i: usize) ?ParseFlagResult {
|
|
_ = this;
|
|
switch (char) {
|
|
'a' => {
|
|
return .{ .unsupported = unsupportedFlag("-a") };
|
|
},
|
|
'c' => {
|
|
return .{ .unsupported = unsupportedFlag("-c") };
|
|
},
|
|
'd' => {
|
|
return .{ .unsupported = unsupportedFlag("-d") };
|
|
},
|
|
'h' => {
|
|
return .{ .unsupported = unsupportedFlag("-h") };
|
|
},
|
|
'm' => {
|
|
return .{ .unsupported = unsupportedFlag("-m") };
|
|
},
|
|
'r' => {
|
|
return .{ .unsupported = unsupportedFlag("-r") };
|
|
},
|
|
't' => {
|
|
return .{ .unsupported = unsupportedFlag("-t") };
|
|
},
|
|
else => {
|
|
return .{ .illegal_option = smallflags[1 + i ..] };
|
|
},
|
|
}
|
|
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub inline fn bltn(this: *Touch) *Builtin {
|
|
const impl: *Builtin.Impl = @alignCast(@fieldParentPtr("touch", this));
|
|
return @fieldParentPtr("impl", impl);
|
|
}
|
|
|
|
// --
|
|
const debug = bun.Output.scoped(.ShellTouch, .hidden);
|
|
const log = debug;
|
|
|
|
const std = @import("std");
|
|
|
|
const interpreter = @import("../interpreter.zig");
|
|
const FlagParser = interpreter.FlagParser;
|
|
const Interpreter = interpreter.Interpreter;
|
|
const OutputSrc = interpreter.OutputSrc;
|
|
const OutputTask = interpreter.OutputTask;
|
|
const ParseError = interpreter.ParseError;
|
|
const ParseFlagResult = interpreter.ParseFlagResult;
|
|
const unsupportedFlag = interpreter.unsupportedFlag;
|
|
|
|
const Builtin = Interpreter.Builtin;
|
|
const Result = Interpreter.Builtin.Result;
|
|
|
|
const bun = @import("bun");
|
|
const ResolvePath = bun.path;
|
|
const Syscall = bun.sys;
|
|
|
|
const jsc = bun.jsc;
|
|
const WorkPool = bun.jsc.WorkPool;
|
|
|
|
const shell = bun.shell;
|
|
const ExitCode = shell.ExitCode;
|
|
const Yield = bun.shell.Yield;
|