mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +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>
233 lines
7.9 KiB
Zig
233 lines
7.9 KiB
Zig
// https://github.com/Vexu/zuri/blob/master/src/zuri.zig#L61-L127
|
|
pub const PercentEncoding = struct {
|
|
/// possible errors for decode and encode
|
|
pub const EncodeError = error{
|
|
InvalidCharacter,
|
|
OutOfMemory,
|
|
};
|
|
|
|
/// returns true if c is a hexadecimal digit
|
|
pub fn isHex(c: u8) bool {
|
|
return switch (c) {
|
|
'0'...'9', 'a'...'f', 'A'...'F' => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// returns true if str starts with a valid path character or a percent encoded octet
|
|
pub fn isPchar(str: []const u8) bool {
|
|
if (comptime Environment.allow_assert) bun.assert(str.len > 0);
|
|
return switch (str[0]) {
|
|
'a'...'z', 'A'...'Z', '0'...'9', '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@' => true,
|
|
'%' => str.len >= 3 and isHex(str[1]) and isHex(str[2]),
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// decode path if it is percent encoded, returns EncodeError if URL unsafe characters are present and not percent encoded
|
|
pub fn decode(allocator: Allocator, path: []const u8) EncodeError!?[]u8 {
|
|
return _decode(allocator, path, true);
|
|
}
|
|
|
|
/// Replaces percent encoded entities within `path` without throwing an error if other URL unsafe characters are present
|
|
pub fn decodeUnstrict(allocator: Allocator, path: []const u8) EncodeError!?[]u8 {
|
|
return _decode(allocator, path, false);
|
|
}
|
|
|
|
fn _decode(allocator: Allocator, path: []const u8, strict: bool) EncodeError!?[]u8 {
|
|
var ret: ?[]u8 = null;
|
|
errdefer if (ret) |some| allocator.free(some);
|
|
var ret_index: usize = 0;
|
|
var i: usize = 0;
|
|
|
|
while (i < path.len) : (i += 1) {
|
|
if (path[i] == '%' and path[i..].len >= 3 and isHex(path[i + 1]) and isHex(path[i + 2])) {
|
|
if (ret == null) {
|
|
ret = try allocator.alloc(u8, path.len);
|
|
bun.copy(u8, ret.?, path[0..i]);
|
|
ret_index = i;
|
|
}
|
|
|
|
// charToDigit can't fail because the chars are validated earlier
|
|
var new = (std.fmt.charToDigit(path[i + 1], 16) catch unreachable) << 4;
|
|
new |= std.fmt.charToDigit(path[i + 2], 16) catch unreachable;
|
|
ret.?[ret_index] = new;
|
|
ret_index += 1;
|
|
i += 2;
|
|
} else if (path[i] != '/' and !isPchar(path[i..]) and strict) {
|
|
return error.InvalidCharacter;
|
|
} else if (ret != null) {
|
|
ret.?[ret_index] = path[i];
|
|
ret_index += 1;
|
|
}
|
|
}
|
|
|
|
if (ret) |some| return some[0..ret_index];
|
|
return null;
|
|
}
|
|
};
|
|
|
|
pub const DataURL = struct {
|
|
url: bun.String = bun.String.empty,
|
|
mime_type: string,
|
|
data: string,
|
|
is_base64: bool = false,
|
|
|
|
pub fn parse(url: string) !?DataURL {
|
|
if (!strings.startsWith(url, "data:")) {
|
|
return null;
|
|
}
|
|
|
|
return try parseWithoutCheck(url);
|
|
}
|
|
|
|
pub fn parseWithoutCheck(url: string) !DataURL {
|
|
const comma = strings.indexOfChar(url, ',') orelse return error.InvalidDataURL;
|
|
|
|
var parsed = DataURL{
|
|
.mime_type = url["data:".len..comma],
|
|
.data = url[comma + 1 .. url.len],
|
|
};
|
|
|
|
if (strings.endsWith(parsed.mime_type, ";base64")) {
|
|
parsed.mime_type = parsed.mime_type[0..(parsed.mime_type.len - ";base64".len)];
|
|
parsed.is_base64 = true;
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
pub fn decodeMimeType(d: DataURL) bun.http.MimeType {
|
|
return bun.http.MimeType.init(d.mime_type, null, null);
|
|
}
|
|
|
|
/// Decodes the data from the data URL. Always returns an owned slice.
|
|
pub fn decodeData(url: DataURL, allocator: Allocator) ![]u8 {
|
|
const percent_decoded = PercentEncoding.decodeUnstrict(allocator, url.data) catch url.data orelse url.data;
|
|
if (url.is_base64) {
|
|
const len = bun.base64.decodeLen(percent_decoded);
|
|
const buf = try allocator.alloc(u8, len);
|
|
const result = bun.base64.decode(buf, percent_decoded);
|
|
if (!result.isSuccessful() or result.count != len) {
|
|
return error.Base64DecodeError;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
return try allocator.dupe(u8, percent_decoded);
|
|
}
|
|
|
|
/// Returns the shorter of either a base64-encoded or percent-escaped data URL
|
|
pub fn encodeStringAsShortestDataURL(allocator: Allocator, mime_type: []const u8, text: []const u8) []u8 {
|
|
// Calculate base64 version
|
|
const base64_encode_len = bun.base64.encodeLen(text);
|
|
const total_base64_encode_len = "data:".len + mime_type.len + ";base64,".len + base64_encode_len;
|
|
|
|
use_base64: {
|
|
var counter = CountingBuf{};
|
|
const success = encodeStringAsPercentEscapedDataURL(&counter, mime_type, text) catch unreachable;
|
|
if (!success) {
|
|
break :use_base64;
|
|
}
|
|
|
|
if (counter.len > total_base64_encode_len) {
|
|
break :use_base64;
|
|
}
|
|
|
|
var buf = std.array_list.Managed(u8).init(allocator);
|
|
errdefer buf.deinit();
|
|
const success2 = encodeStringAsPercentEscapedDataURL(&buf, mime_type, text) catch unreachable;
|
|
if (!success2) {
|
|
break :use_base64;
|
|
}
|
|
return buf.items;
|
|
}
|
|
|
|
const base64buf = bun.handleOom(allocator.alloc(u8, total_base64_encode_len));
|
|
return std.fmt.bufPrint(base64buf, "data:{s};base64,{s}", .{ mime_type, text }) catch unreachable;
|
|
}
|
|
|
|
const CountingBuf = struct {
|
|
len: usize = 0,
|
|
|
|
pub fn appendSlice(self: *CountingBuf, slice: []const u8) Allocator.Error!void {
|
|
self.len += slice.len;
|
|
}
|
|
|
|
pub fn append(self: *CountingBuf, _: u8) Allocator.Error!void {
|
|
self.len += 1;
|
|
}
|
|
|
|
pub fn toOwnedSlice(_: *CountingBuf) Allocator.Error![]u8 {
|
|
return "";
|
|
}
|
|
};
|
|
|
|
pub fn encodeStringAsPercentEscapedDataURL(buf: anytype, mime_type: []const u8, text: []const u8) !bool {
|
|
const hex = "0123456789ABCDEF";
|
|
|
|
try buf.appendSlice("data:");
|
|
try buf.appendSlice(mime_type);
|
|
try buf.append(',');
|
|
|
|
// Scan for trailing characters that need to be escaped
|
|
var trailing_start = text.len;
|
|
while (trailing_start > 0) {
|
|
const c = text[trailing_start - 1];
|
|
if (c > 0x20 or c == '\t' or c == '\n' or c == '\r') {
|
|
break;
|
|
}
|
|
trailing_start -= 1;
|
|
}
|
|
|
|
if (!bun.simdutf.validate.utf8(text)) {
|
|
return false;
|
|
}
|
|
|
|
var i: usize = 0;
|
|
var run_start: usize = 0;
|
|
|
|
// TODO: vectorize this
|
|
while (i < text.len) {
|
|
const first_byte = text[i];
|
|
|
|
// Check if we need to escape this character
|
|
const needs_escape = first_byte == '\t' or
|
|
first_byte == '\n' or
|
|
first_byte == '\r' or
|
|
first_byte == '#' or
|
|
i >= trailing_start or
|
|
(first_byte == '%' and i + 2 < text.len and
|
|
PercentEncoding.isHex(text[i + 1]) and
|
|
PercentEncoding.isHex(text[i + 2]));
|
|
|
|
if (needs_escape) {
|
|
if (run_start < i) {
|
|
try buf.appendSlice(text[run_start..i]);
|
|
}
|
|
try buf.append('%');
|
|
try buf.append(hex[first_byte >> 4]);
|
|
try buf.append(hex[first_byte & 15]);
|
|
run_start = i + 1;
|
|
}
|
|
|
|
i += bun.strings.utf8ByteSequenceLength(first_byte);
|
|
}
|
|
|
|
if (run_start < text.len) {
|
|
try buf.appendSlice(text[run_start..]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
const string = []const u8;
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const strings = bun.strings;
|