From ec082db67c6c08d990c5494961d503264bc6096b Mon Sep 17 00:00:00 2001 From: dave caruso Date: Thu, 23 May 2024 23:30:11 -0700 Subject: [PATCH] fix: fix sourcemap generation (rewrites bun.StringJoiner) (#11288) --- src/NullableAllocator.zig | 30 +++++ src/StringJoiner.zig | 163 +++++++++++++++++++++++++ src/bun.js/bindings/bindings.zig | 2 +- src/bun.js/webcore/blob.zig | 86 +++++--------- src/bun.js/webcore/body.zig | 4 +- src/bun.js/webcore/request.zig | 4 +- src/bun.js/webcore/response.zig | 4 +- src/bun.js/webcore/streams.zig | 2 +- src/bun.zig | 3 +- src/bundler/bundle_v2.zig | 82 ++++++------- src/crash_handler.zig | 4 +- src/nullable_allocator.zig | 31 ----- src/sourcemap/sourcemap.zig | 38 +++--- src/string_immutable.zig | 1 - src/string_joiner.zig | 165 -------------------------- test/bun.lockb | Bin 315702 -> 316090 bytes test/bundler/bundler_edgecase.test.ts | 26 +++- test/bundler/expectBundled.ts | 32 +++-- test/package.json | 1 + 19 files changed, 346 insertions(+), 332 deletions(-) create mode 100644 src/NullableAllocator.zig create mode 100644 src/StringJoiner.zig delete mode 100644 src/nullable_allocator.zig delete mode 100644 src/string_joiner.zig diff --git a/src/NullableAllocator.zig b/src/NullableAllocator.zig new file mode 100644 index 0000000000..3c0fb5f366 --- /dev/null +++ b/src/NullableAllocator.zig @@ -0,0 +1,30 @@ +//! A nullable allocator the same size as `std.mem.Allocator`. +const std = @import("std"); +const NullableAllocator = @This(); + +ptr: *anyopaque = undefined, +// Utilize the null pointer optimization on the vtable instead of +// the regular ptr because some allocator implementations might tag their +// `ptr` property. +vtable: ?*const std.mem.Allocator.VTable = null, + +pub inline fn init(allocator: ?std.mem.Allocator) NullableAllocator { + return if (allocator) |a| .{ + .ptr = a.ptr, + .vtable = a.vtable, + } else .{}; +} + +pub inline fn isNull(this: NullableAllocator) bool { + return this.vtable == null; +} + +pub inline fn get(this: NullableAllocator) ?std.mem.Allocator { + return if (this.vtable) |vt| std.mem.Allocator{ .ptr = this.ptr, .vtable = vt } else null; +} + +comptime { + if (@sizeOf(NullableAllocator) != @sizeOf(std.mem.Allocator)) { + @compileError("Expected the sizes to be the same."); + } +} diff --git a/src/StringJoiner.zig b/src/StringJoiner.zig new file mode 100644 index 0000000000..a1385b998f --- /dev/null +++ b/src/StringJoiner.zig @@ -0,0 +1,163 @@ +//! Rope-like data structure for joining many small strings into one big string. +//! Implemented as a linked list of potentially-owned slices and a length. +const std = @import("std"); +const default_allocator = bun.default_allocator; +const bun = @import("root").bun; +const string = bun.string; +const Allocator = std.mem.Allocator; +const NullableAllocator = bun.NullableAllocator; +const StringJoiner = @This(); +const assert = bun.assert; + +/// Temporary allocator used for nodes and duplicated strings. +/// It is recommended to use a stack-fallback allocator for this. +allocator: Allocator, + +/// Total length of all nodes +len: usize = 0, + +head: ?*Node = null, +tail: ?*Node = null, + +/// Avoid an extra pass over the list when joining +watcher: Watcher = .{}, + +const Node = struct { + allocator: NullableAllocator = .{}, + slice: []const u8 = "", + next: ?*Node = null, + + pub fn init(joiner_alloc: Allocator, slice: []const u8, slice_alloc: ?Allocator) *Node { + const node = joiner_alloc.create(Node) catch bun.outOfMemory(); + node.* = .{ + .slice = slice, + .allocator = NullableAllocator.init(slice_alloc), + }; + return node; + } + + pub fn deinit(node: *Node, joiner_alloc: Allocator) void { + if (node.allocator.get()) |alloc| + alloc.free(node.slice); + joiner_alloc.destroy(node); + } +}; + +pub const Watcher = struct { + input: []const u8 = "", + estimated_count: u32 = 0, + needs_newline: bool = false, +}; + +/// `data` is expected to live until `.done` is called +pub fn pushStatic(this: *StringJoiner, data: []const u8) void { + this.push(data, null); +} + +/// `data` is cloned +pub fn pushCloned(this: *StringJoiner, data: []const u8) void { + this.push( + this.allocator.dupe(u8, data) catch bun.outOfMemory(), + this.allocator, + ); +} + +pub fn push(this: *StringJoiner, data: []const u8, allocator: ?Allocator) void { + this.len += data.len; + + const new_tail = Node.init(this.allocator, data, allocator); + + if (data.len > 0) { + this.watcher.estimated_count += @intFromBool( + this.watcher.input.len > 0 and + bun.strings.contains(data, this.watcher.input), + ); + this.watcher.needs_newline = data[data.len - 1] != '\n'; + } + + if (this.tail) |current_tail| { + current_tail.next = new_tail; + } else { + assert(this.head == null); + this.head = new_tail; + } + this.tail = new_tail; +} + +/// This deinits the string joiner on success, the new string is owned by `allocator` +pub fn done(this: *StringJoiner, allocator: Allocator) ![]u8 { + var current: ?*Node = this.head orelse { + assert(this.tail == null); + assert(this.len == 0); + return &.{}; + }; + + const slice = try allocator.alloc(u8, this.len); + + var remaining = slice; + while (current) |node| { + @memcpy(remaining[0..node.slice.len], node.slice); + remaining = remaining[node.slice.len..]; + + const prev = node; + current = node.next; + prev.deinit(this.allocator); + } + + bun.assert(remaining.len == 0); + + return slice; +} + +/// Same as `.done`, but appends extra slice `end` +pub fn doneWithEnd(this: *StringJoiner, allocator: Allocator, end: []const u8) ![]u8 { + var current: ?*Node = this.head orelse { + assert(this.tail == null); + assert(this.len == 0); + + if (end.len > 0) { + return allocator.dupe(u8, end); + } + + return &.{}; + }; + + const slice = try allocator.alloc(u8, this.len + end.len); + + var remaining = slice; + while (current) |node| { + @memcpy(remaining[0..node.slice.len], node.slice); + remaining = remaining[node.slice.len..]; + + const prev = node; + current = node.next; + prev.deinit(this.allocator); + } + + bun.assert(remaining.len == end.len); + @memcpy(remaining, end); + + return slice; +} + +pub fn lastByte(this: *const StringJoiner) u8 { + const slice = (this.tail orelse return 0).slice; + return if (slice.len > 0) slice[slice.len - 1] else 0; +} + +pub fn ensureNewlineAtEnd(this: *StringJoiner) void { + if (this.watcher.needs_newline) { + this.watcher.needs_newline = false; + this.pushStatic("\n"); + } +} + +pub fn contains(this: *const StringJoiner, slice: string) bool { + var el = this.head; + while (el) |node| { + el = node.next; + if (bun.strings.contains(node.slice, slice)) return true; + } + + return false; +} diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 3e0808b58a..cc6e60d8f7 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -16,7 +16,7 @@ const JSC = bun.JSC; const Shimmer = JSC.Shimmer; const ConsoleObject = JSC.ConsoleObject; const FFI = @import("./FFI.zig"); -const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; +const NullableAllocator = bun.NullableAllocator; const MutableString = bun.MutableString; const JestPrettyFormat = @import("../test/pretty_format.zig").JestPrettyFormat; const String = bun.String; diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 059f24f8cd..e948620548 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -29,13 +29,13 @@ const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; -const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; +const NullableAllocator = bun.NullableAllocator; const VirtualMachine = JSC.VirtualMachine; const Task = JSC.Task; const JSPrinter = bun.js_printer; const picohttp = bun.picohttp; -const StringJoiner = @import("../../string_joiner.zig"); +const StringJoiner = bun.StringJoiner; const uws = bun.uws; const invalid_fd = bun.invalid_fd; @@ -225,33 +225,31 @@ pub const Blob = struct { const joiner = &this.joiner; const boundary = this.boundary; - joiner.append("--", 0, null); - joiner.append(boundary, 0, null); - joiner.append("\r\n", 0, null); + joiner.pushStatic("--"); + joiner.pushStatic(boundary); // note: "static" here means "outlives the joiner" + joiner.pushStatic("\r\n"); - joiner.append("Content-Disposition: form-data; name=\"", 0, null); + joiner.pushStatic("Content-Disposition: form-data; name=\""); const name_slice = name.toSlice(allocator); - joiner.append(name_slice.slice(), 0, name_slice.allocator.get()); - name_slice.deinit(); + joiner.push(name_slice.slice(), name_slice.allocator.get()); switch (entry) { .string => |value| { - joiner.append("\"\r\n\r\n", 0, null); + joiner.pushStatic("\"\r\n\r\n"); const value_slice = value.toSlice(allocator); - joiner.append(value_slice.slice(), 0, value_slice.allocator.get()); + joiner.push(value_slice.slice(), value_slice.allocator.get()); }, .file => |value| { - joiner.append("\"; filename=\"", 0, null); + joiner.pushStatic("\"; filename=\""); const filename_slice = value.filename.toSlice(allocator); - joiner.append(filename_slice.slice(), 0, filename_slice.allocator.get()); - filename_slice.deinit(); - joiner.append("\"\r\n", 0, null); + joiner.push(filename_slice.slice(), filename_slice.allocator.get()); + joiner.pushStatic("\"\r\n"); const blob = value.blob; const content_type = if (blob.content_type.len > 0) blob.content_type else "application/octet-stream"; - joiner.append("Content-Type: ", 0, null); - joiner.append(content_type, 0, null); - joiner.append("\r\n\r\n", 0, null); + joiner.pushStatic("Content-Type: "); + joiner.pushStatic(content_type); + joiner.pushStatic("\r\n\r\n"); if (blob.store) |store| { blob.resolveSize(); @@ -277,19 +275,19 @@ pub const Blob = struct { this.failed = true; }, .result => |result| { - joiner.append(result.slice(), 0, result.buffer.allocator); + joiner.push(result.slice(), result.buffer.allocator); }, } }, .bytes => |_| { - joiner.append(blob.sharedView(), 0, null); + joiner.pushStatic(blob.sharedView()); }, } } }, } - joiner.append("\r\n", 0, null); + joiner.pushStatic("\r\n"); } }; @@ -532,7 +530,7 @@ pub const Blob = struct { var context = FormDataContext{ .allocator = allocator, - .joiner = StringJoiner{ .use_pool = false, .node_allocator = stack_mem_all }, + .joiner = .{ .allocator = stack_mem_all }, .boundary = boundary, .globalThis = globalThis, }; @@ -542,9 +540,9 @@ pub const Blob = struct { return Blob.initEmpty(globalThis); } - context.joiner.append("--", 0, null); - context.joiner.append(boundary, 0, null); - context.joiner.append("--\r\n", 0, null); + context.joiner.pushStatic("--"); + context.joiner.pushStatic(boundary); + context.joiner.pushStatic("--\r\n"); const store = Blob.Store.init(context.joiner.done(allocator) catch unreachable, allocator) catch unreachable; var blob = Blob.initWithStore(store, globalThis); @@ -4066,7 +4064,7 @@ pub const Blob = struct { var stack_allocator = std.heap.stackFallback(1024, bun.default_allocator); const 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 joiner = StringJoiner{ .allocator = stack_mem_all }; var could_have_non_ascii = false; defer if (stack_allocator.fixed_buffer_allocator.end_index >= 1024) stack.deinit(); @@ -4081,11 +4079,7 @@ pub const Blob = struct { var sliced = current.toSlice(global, bun.default_allocator); const allocator = sliced.allocator.get(); could_have_non_ascii = could_have_non_ascii or allocator != null; - joiner.append( - sliced.slice(), - 0, - allocator, - ); + joiner.push(sliced.slice(), allocator); }, .Array, .DerivedArray => { @@ -4111,11 +4105,7 @@ pub const Blob = struct { var sliced = item.toSlice(global, bun.default_allocator); const allocator = sliced.allocator.get(); could_have_non_ascii = could_have_non_ascii or allocator != null; - joiner.append( - sliced.slice(), - 0, - allocator, - ); + joiner.push(sliced.slice(), allocator); continue; }, JSC.JSValue.JSType.ArrayBuffer, @@ -4134,7 +4124,7 @@ pub const Blob = struct { => { could_have_non_ascii = true; var buf = item.asArrayBuffer(global).?; - joiner.append(buf.byteSlice(), 0, null); + joiner.pushStatic(buf.byteSlice()); continue; }, .Array, .DerivedArray => { @@ -4146,16 +4136,12 @@ pub const Blob = struct { .DOMWrapper => { if (item.as(Blob)) |blob| { could_have_non_ascii = could_have_non_ascii or !(blob.is_all_ascii orelse false); - joiner.append(blob.sharedView(), 0, null); + joiner.pushStatic(blob.sharedView()); continue; } else if (current.toSliceClone(global)) |sliced| { const allocator = sliced.allocator.get(); could_have_non_ascii = could_have_non_ascii or allocator != null; - joiner.append( - sliced.slice(), - 0, - allocator, - ); + joiner.push(sliced.slice(), allocator); } }, else => {}, @@ -4169,15 +4155,11 @@ pub const Blob = struct { .DOMWrapper => { if (current.as(Blob)) |blob| { could_have_non_ascii = could_have_non_ascii or !(blob.is_all_ascii orelse false); - joiner.append(blob.sharedView(), 0, null); + joiner.pushStatic(blob.sharedView()); } else if (current.toSliceClone(global)) |sliced| { const allocator = sliced.allocator.get(); could_have_non_ascii = could_have_non_ascii or allocator != null; - joiner.append( - sliced.slice(), - 0, - allocator, - ); + joiner.push(sliced.slice(), allocator); } }, @@ -4196,7 +4178,7 @@ pub const Blob = struct { JSC.JSValue.JSType.DataView, => { var buf = current.asArrayBuffer(global).?; - joiner.append(buf.slice(), 0, null); + joiner.pushStatic(buf.slice()); could_have_non_ascii = true; }, @@ -4204,11 +4186,7 @@ pub const Blob = struct { var sliced = current.toSlice(global, bun.default_allocator); const allocator = sliced.allocator.get(); could_have_non_ascii = could_have_non_ascii or allocator != null; - joiner.append( - sliced.slice(), - 0, - allocator, - ); + joiner.push(sliced.slice(), allocator); }, } current = stack.popOrNull() orelse break; diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index cebbcedc8c..27193bb750 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -31,13 +31,13 @@ const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; -const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; +const NullableAllocator = bun.NullableAllocator; const VirtualMachine = JSC.VirtualMachine; const Task = JSC.Task; const JSPrinter = bun.js_printer; const picohttp = bun.picohttp; -const StringJoiner = @import("../../string_joiner.zig"); +const StringJoiner = bun.StringJoiner; const uws = bun.uws; const Blob = JSC.WebCore.Blob; diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index 117f0a6e97..2ea98d4f91 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -32,13 +32,13 @@ const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; -const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; +const NullableAllocator = bun.NullableAllocator; const VirtualMachine = JSC.VirtualMachine; const Task = JSC.Task; const JSPrinter = bun.js_printer; const picohttp = bun.picohttp; -const StringJoiner = @import("../../string_joiner.zig"); +const StringJoiner = bun.StringJoiner; const uws = bun.uws; const InlineBlob = JSC.WebCore.InlineBlob; diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 9a3373982f..c252419889 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -32,14 +32,14 @@ const JSPromise = JSC.JSPromise; const JSValue = JSC.JSValue; const JSError = JSC.JSError; const JSGlobalObject = JSC.JSGlobalObject; -const NullableAllocator = @import("../../nullable_allocator.zig").NullableAllocator; +const NullableAllocator = bun.NullableAllocator; const DataURL = @import("../../resolver/data_url.zig").DataURL; const VirtualMachine = JSC.VirtualMachine; const Task = JSC.Task; const JSPrinter = bun.js_printer; const picohttp = bun.picohttp; -const StringJoiner = @import("../../string_joiner.zig"); +const StringJoiner = bun.StringJoiner; const uws = bun.uws; const Mutex = @import("../../lock.zig").Lock; diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 4d0d7911cb..e79c6a07be 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -37,7 +37,7 @@ const VirtualMachine = JSC.VirtualMachine; const Task = JSC.Task; const JSPrinter = bun.js_printer; const picohttp = bun.picohttp; -const StringJoiner = @import("../../string_joiner.zig"); +const StringJoiner = bun.StringJoiner; const uws = bun.uws; const Blob = JSC.WebCore.Blob; const Response = JSC.WebCore.Response; diff --git a/src/bun.zig b/src/bun.zig index 68e3a7e92a..6a31ebe356 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1465,8 +1465,9 @@ pub const fast_debug_build_mode = fast_debug_build_cmd != .None and Environment.isDebug; pub const MultiArrayList = @import("./multi_array_list.zig").MultiArrayList; +pub const StringJoiner = @import("./StringJoiner.zig"); +pub const NullableAllocator = @import("./NullableAllocator.zig"); -pub const Joiner = @import("./string_joiner.zig"); pub const renamer = @import("./renamer.zig"); pub const sourcemap = struct { pub usingnamespace @import("./sourcemap/sourcemap.zig"); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index b350fe8fed..8f4b4456d7 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -65,7 +65,7 @@ const js_printer = @import("../js_printer.zig"); const js_ast = @import("../js_ast.zig"); const linker = @import("../linker.zig"); const sourcemap = bun.sourcemap; -const Joiner = bun.Joiner; +const StringJoiner = bun.StringJoiner; const base64 = bun.base64; const Ref = @import("../ast/base.zig").Ref; const Define = @import("../defines.zig").Define; @@ -6786,9 +6786,8 @@ const LinkerContext = struct { break :brk CompileResult.empty; }; - var j = bun.Joiner{ - .use_pool = false, - .node_allocator = worker.allocator, + var j = StringJoiner{ + .allocator = worker.allocator, .watcher = .{ .input = chunk.unique_key, }, @@ -6808,8 +6807,8 @@ const LinkerContext = struct { const hashbang = c.graph.ast.items(.hashbang)[chunk.entry_point.source_index]; if (hashbang.len > 0) { - j.push(hashbang); - j.push("\n"); + j.pushStatic(hashbang); + j.pushStatic("\n"); line_offset.advance(hashbang); line_offset.advance("\n"); newline_before_comment = true; @@ -6817,7 +6816,7 @@ const LinkerContext = struct { } if (is_bun) { - j.push("// @bun\n"); + j.pushStatic("// @bun\n"); line_offset.advance("// @bun\n"); } } @@ -6831,7 +6830,7 @@ const LinkerContext = struct { if (cross_chunk_prefix.len > 0) { newline_before_comment = true; line_offset.advance(cross_chunk_prefix); - j.append(cross_chunk_prefix, 0, bun.default_allocator); + j.push(cross_chunk_prefix, bun.default_allocator); } // Concatenate the generated JavaScript chunks together @@ -6853,7 +6852,7 @@ const LinkerContext = struct { if (c.options.mode == .bundle and !c.options.minify_whitespace and source_index != prev_filename_comment and compile_result.code().len > 0) { prev_filename_comment = source_index; if (newline_before_comment) { - j.push("\n"); + j.pushStatic("\n"); line_offset.advance("\n"); } @@ -6875,25 +6874,25 @@ const LinkerContext = struct { switch (comment_type) { .multiline => { - j.push("/* "); + j.pushStatic("/* "); line_offset.advance("/* "); }, .single => { - j.push("// "); + j.pushStatic("// "); line_offset.advance("// "); }, } - j.push(pretty); + j.pushStatic(pretty); line_offset.advance(pretty); switch (comment_type) { .multiline => { - j.push(" */\n"); + j.pushStatic(" */\n"); line_offset.advance(" */\n"); }, .single => { - j.push("\n"); + j.pushStatic("\n"); line_offset.advance("\n"); }, } @@ -6902,10 +6901,10 @@ const LinkerContext = struct { if (is_runtime) { line_offset.advance(compile_result.code()); - j.append(compile_result.code(), 0, bun.default_allocator); + j.push(compile_result.code(), bun.default_allocator); } else { const generated_offset = line_offset; - j.append(compile_result.code(), 0, bun.default_allocator); + j.push(compile_result.code(), bun.default_allocator); if (compile_result.source_map_chunk()) |source_map_chunk| { line_offset.reset(); @@ -6930,16 +6929,16 @@ const LinkerContext = struct { // Stick the entry point tail at the end of the file. Deliberately don't // include any source mapping information for this because it's automatically // generated and doesn't correspond to a location in the input file. - j.append(tail_code, 0, bun.default_allocator); + j.push(tail_code, bun.default_allocator); } // Put the cross-chunk suffix inside the IIFE if (cross_chunk_suffix.len > 0) { if (newline_before_comment) { - j.push("\n"); + j.pushStatic("\n"); } - j.append(cross_chunk_suffix, 0, bun.default_allocator); + j.push(cross_chunk_suffix, bun.default_allocator); } if (c.options.output_format == .iife) { @@ -6950,7 +6949,7 @@ const LinkerContext = struct { else without_newline; - j.push(with_newline); + j.pushStatic(with_newline); } j.ensureNewlineAtEnd(); @@ -6992,9 +6991,8 @@ const LinkerContext = struct { const trace = tracer(@src(), "generateSourceMapForChunk"); defer trace.end(); - var j = Joiner{ - .node_allocator = worker.allocator, - .use_pool = false, + var j = StringJoiner{ + .allocator = worker.allocator, }; const sources = c.parse_graph.input_files.items(.source); @@ -7005,7 +7003,7 @@ const LinkerContext = struct { var next_source_index: u32 = 0; const source_indices = results.items(.source_index); - j.push("{\n \"version\": 3,\n \"sources\": ["); + j.pushStatic("{\n \"version\": 3,\n \"sources\": ["); if (source_indices.len > 0) { { var path = sources[source_indices[0]].path; @@ -7017,7 +7015,7 @@ const LinkerContext = struct { var quote_buf = try MutableString.init(worker.allocator, path.pretty.len + 2); quote_buf = try js_printer.quoteForJSON(path.pretty, quote_buf, false); - j.push(quote_buf.list.items); + j.pushStatic(quote_buf.list.items); // freed by arena } if (source_indices.len > 1) { for (source_indices[1..]) |index| { @@ -7031,24 +7029,24 @@ const LinkerContext = struct { var quote_buf = try MutableString.init(worker.allocator, path.pretty.len + ", ".len + 2); quote_buf.appendAssumeCapacity(", "); quote_buf = try js_printer.quoteForJSON(path.pretty, quote_buf, false); - j.push(quote_buf.list.items); + j.pushStatic(quote_buf.list.items); // freed by arena } } } - j.push("],\n \"sourcesContent\": ["); + j.pushStatic("],\n \"sourcesContent\": ["); if (source_indices.len > 0) { - j.push("\n "); - j.push(quoted_source_map_contents[source_indices[0]]); + j.pushStatic("\n "); + j.pushStatic(quoted_source_map_contents[source_indices[0]]); if (source_indices.len > 1) { for (source_indices[1..]) |index| { - j.push(",\n "); - j.push(quoted_source_map_contents[index]); + j.pushStatic(",\n "); + j.pushStatic(quoted_source_map_contents[index]); } } } - j.push("\n ],\n \"mappings\": \""); + j.pushStatic("\n ],\n \"mappings\": \""); const mapping_start = j.len; var prev_end_state = sourcemap.SourceMapState{}; @@ -7086,14 +7084,18 @@ const LinkerContext = struct { const mapping_end = j.len; if (comptime FeatureFlags.source_map_debug_id) { - j.push("\",\n \"debugId\": \""); - j.push(try std.fmt.allocPrint(worker.allocator, "{}", .{bun.sourcemap.DebugIDFormatter{ .id = isolated_hash }})); - j.push("\",\n \"names\": []\n}"); + j.pushStatic("\",\n \"debugId\": \""); + j.push( + try std.fmt.allocPrint(worker.allocator, "{}", .{bun.sourcemap.DebugIDFormatter{ .id = isolated_hash }}), + worker.allocator, + ); + j.pushStatic("\",\n \"names\": []\n}"); } else { - j.push("\",\n \"names\": []\n}"); + j.pushStatic("\",\n \"names\": []\n}"); } const done = try j.done(worker.allocator); + bun.assert(done[0] == '{'); var pieces = sourcemap.SourceMapPieces.init(worker.allocator); if (can_have_shifts) { @@ -7174,7 +7176,7 @@ const LinkerContext = struct { } else { var el = chunk.intermediate_output.joiner.head; while (el) |e| : (el = e.next) { - hasher.write(e.data.slice); + hasher.write(e.slice); } } @@ -8932,7 +8934,7 @@ const LinkerContext = struct { } const SubstituteChunkFinalPathResult = struct { - j: Joiner, + j: StringJoiner, shifts: []sourcemap.SourceMapShifts, }; @@ -10813,7 +10815,7 @@ const LinkerContext = struct { pub fn breakOutputIntoPieces( c: *LinkerContext, allocator: std.mem.Allocator, - j: *bun.Joiner, + j: *StringJoiner, count: u32, ) !Chunk.IntermediateOutput { const trace = tracer(@src(), "breakOutputIntoPieces"); @@ -11123,7 +11125,7 @@ pub const Chunk = struct { /// If the chunk doesn't have any references to other chunks, then /// `joiner` contains the contents of the chunk. This is more efficient /// because it avoids doing a join operation twice. - joiner: bun.Joiner, + joiner: StringJoiner, empty: void, diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 7cd0b83e54..bf68337c98 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -835,9 +835,9 @@ const Platform = enum(u8) { /// '1' - original. uses 7 char hash with VLQ encoded stack-frames /// '2' - same as '1' but this build is known to be a canary build const version_char = if (bun.Environment.is_canary) - "1" + "2" else - "2"; + "1"; const git_sha = if (bun.Environment.git_sha.len > 0) bun.Environment.git_sha[0..7] else "unknown"; diff --git a/src/nullable_allocator.zig b/src/nullable_allocator.zig deleted file mode 100644 index 8af65d6bbf..0000000000 --- a/src/nullable_allocator.zig +++ /dev/null @@ -1,31 +0,0 @@ -const std = @import("std"); - -/// A nullable allocator the same size as `std.mem.Allocator`. -pub const NullableAllocator = struct { - ptr: *anyopaque = undefined, - // Utilize the null pointer optimization on the vtable instead of - // the regular ptr because some allocator implementations might tag their - // `ptr` property. - vtable: ?*const std.mem.Allocator.VTable = null, - - pub inline fn init(a: std.mem.Allocator) @This() { - return .{ - .ptr = a.ptr, - .vtable = a.vtable, - }; - } - - pub inline fn isNull(this: @This()) bool { - return this.vtable == null; - } - - pub inline fn get(this: @This()) ?std.mem.Allocator { - return if (this.vtable) |vt| std.mem.Allocator{ .ptr = this.ptr, .vtable = vt } else null; - } -}; - -comptime { - if (@sizeOf(NullableAllocator) != @sizeOf(std.mem.Allocator)) { - @compileError("Expected the sizes to be the same."); - } -} diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index ab221f53c8..e46c136eae 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -11,7 +11,7 @@ const BabyList = JSAst.BabyList; const Logger = bun.logger; const strings = bun.strings; const MutableString = bun.MutableString; -const Joiner = @import("../string_joiner.zig"); +const StringJoiner = bun.StringJoiner; const JSPrinter = bun.js_printer; const URL = bun.URL; const FileSystem = bun.fs.FileSystem; @@ -886,9 +886,14 @@ pub const SourceMapPieces = struct { var current: usize = 0; var generated = LineColumnOffset{}; var prev_shift_column_delta: i32 = 0; - var j = Joiner{}; - j.push(this.prefix.items); + // the joiner's node allocator contains string join nodes as well as some vlq encodings + // it doesnt contain json payloads or source code, so 16kb is probably going to cover + // most applications. + var sfb = std.heap.stackFallback(16384, bun.default_allocator); + var j = StringJoiner{ .allocator = sfb.get() }; + + j.pushStatic(this.prefix.items); const mappings = this.mappings.items; while (current < mappings.len) { @@ -938,23 +943,24 @@ pub const SourceMapPieces = struct { continue; } - j.push(mappings[start_of_run..potential_end_of_run]); + j.pushStatic(mappings[start_of_run..potential_end_of_run]); assert(shift.before.lines == shift.after.lines); const shift_column_delta = shift.after.columns - shift.before.columns; const vlq_value = decode_result.value + shift_column_delta - prev_shift_column_delta; const encode = encodeVLQ(vlq_value); - j.push(encode.bytes[0..encode.len]); + j.pushCloned(encode.bytes[0..encode.len]); prev_shift_column_delta = shift_column_delta; start_of_run = potential_start_of_run; } - j.push(mappings[start_of_run..]); - j.push(this.suffix.items); + j.pushStatic(mappings[start_of_run..]); - return try j.done(allocator); + const str = try j.doneWithEnd(allocator, this.suffix.items); + bun.assert(str[0] == '{'); // invalid json + return str; } }; @@ -967,18 +973,18 @@ pub const SourceMapPieces = struct { // After all chunks are computed, they are joined together in a second pass. // This rewrites the first mapping in each chunk to be relative to the end // state of the previous chunk. -pub fn appendSourceMapChunk(j: *Joiner, allocator: std.mem.Allocator, prev_end_state_: SourceMapState, start_state_: SourceMapState, source_map_: bun.string) !void { +pub fn appendSourceMapChunk(j: *StringJoiner, allocator: std.mem.Allocator, prev_end_state_: SourceMapState, start_state_: SourceMapState, source_map_: bun.string) !void { var prev_end_state = prev_end_state_; var start_state = start_state_; // Handle line breaks in between this mapping and the previous one if (start_state.generated_line > 0) { - j.append(try strings.repeatingAlloc(allocator, @as(usize, @intCast(start_state.generated_line)), ';'), 0, allocator); + j.push(try strings.repeatingAlloc(allocator, @intCast(start_state.generated_line), ';'), allocator); prev_end_state.generated_column = 0; } var source_map = source_map_; if (strings.indexOfNotChar(source_map, ';')) |semicolons| { - j.push(source_map[0..semicolons]); + j.pushStatic(source_map[0..semicolons]); source_map = source_map[semicolons..]; prev_end_state.generated_column = 0; start_state.generated_column = 0; @@ -1009,19 +1015,18 @@ pub fn appendSourceMapChunk(j: *Joiner, allocator: std.mem.Allocator, prev_end_s start_state.original_line += original_line_.value; start_state.original_column += original_column_.value; - j.append( + j.push( appendMappingToBuffer( MutableString.initEmpty(allocator), j.lastByte(), prev_end_state, start_state, ).list.items, - 0, allocator, ); // Then append everything after that without modification. - j.push(source_map); + j.pushStatic(source_map); } const vlq_lookup_table: [256]VLQ = brk: { @@ -1455,9 +1460,8 @@ pub fn appendMappingToBuffer(buffer_: MutableString, last_byte: u8, prev_state: buffer.appendCharAssumeCapacity(','); } - comptime var i: usize = 0; - inline while (i < vlq.len) : (i += 1) { - buffer.appendAssumeCapacity(vlq[i].bytes[0..vlq[i].len]); + inline for (vlq) |item| { + buffer.appendAssumeCapacity(item.bytes[0..item.len]); } return buffer; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 74a6c65f78..2f53139b01 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -5,7 +5,6 @@ const string = bun.string; const stringZ = bun.stringZ; const CodePoint = bun.CodePoint; const bun = @import("root").bun; -pub const joiner = @import("./string_joiner.zig"); const log = bun.Output.scoped(.STR, true); const js_lexer = @import("./js_lexer.zig"); const grapheme = @import("./grapheme.zig"); diff --git a/src/string_joiner.zig b/src/string_joiner.zig deleted file mode 100644 index 61a8eebb95..0000000000 --- a/src/string_joiner.zig +++ /dev/null @@ -1,165 +0,0 @@ -/// Rope-like data structure for joining many small strings into one big string. -const std = @import("std"); -const default_allocator = bun.default_allocator; -const bun = @import("root").bun; -const string = bun.string; -const Allocator = std.mem.Allocator; -const ObjectPool = @import("./pool.zig").ObjectPool; -const Joiner = @This(); - -const Joinable = struct { - offset: u31 = 0, - needs_deinit: bool = false, - allocator: Allocator = undefined, - slice: []const u8 = "", - - pub const Pool = ObjectPool(Joinable, null, true, 4); -}; - -len: usize = 0, -use_pool: bool = true, -node_allocator: Allocator = undefined, - -head: ?*Joinable.Pool.Node = null, -tail: ?*Joinable.Pool.Node = null, - -/// Avoid an extra pass over the list when joining -watcher: Watcher = .{}, - -pub const Watcher = struct { - input: []const u8 = "", - estimated_count: u32 = 0, - needs_newline: bool = false, -}; - -pub fn done(this: *Joiner, allocator: Allocator) ![]u8 { - if (this.head == null) { - const out: []u8 = &[_]u8{}; - return out; - } - - var slice = try allocator.alloc(u8, this.len); - var remaining = slice; - var el_ = this.head; - while (el_) |join| { - const to_join = join.data.slice[join.data.offset..]; - @memcpy(remaining[0..to_join.len], to_join); - - remaining = remaining[@min(remaining.len, to_join.len)..]; - - var prev = join; - el_ = join.next; - if (prev.data.needs_deinit) { - prev.data.allocator.free(prev.data.slice); - prev.data = Joinable{}; - } - - if (this.use_pool) prev.release(); - } - - return slice[0 .. slice.len - remaining.len]; -} - -pub fn doneWithEnd(this: *Joiner, allocator: Allocator, end: []const u8) ![]u8 { - if (this.head == null and end.len == 0) { - return &[_]u8{}; - } - - if (this.head == null) { - var slice = try allocator.alloc(u8, end.len); - @memcpy(slice[0..end.len], end); - - return slice; - } - - var slice = try allocator.alloc(u8, this.len + end.len); - var remaining = slice; - var el_ = this.head; - while (el_) |join| { - const to_join = join.data.slice[join.data.offset..]; - @memcpy(remaining[0..to_join.len], to_join); - - remaining = remaining[@min(remaining.len, to_join.len)..]; - - var prev = join; - el_ = join.next; - if (prev.data.needs_deinit) { - prev.data.allocator.free(prev.data.slice); - prev.data = Joinable{}; - } - - if (this.use_pool) prev.release(); - } - - @memcpy(remaining[0..end.len], end); - - remaining = remaining[@min(remaining.len, end.len)..]; - - return slice[0 .. slice.len - remaining.len]; -} - -pub fn lastByte(this: *const Joiner) u8 { - if (this.tail) |tail| { - const slice = tail.data.slice[tail.data.offset..]; - return if (slice.len > 0) slice[slice.len - 1] else 0; - } - - return 0; -} - -pub fn push(this: *Joiner, slice: string) void { - this.append(slice, 0, null); -} - -pub fn ensureNewlineAtEnd(this: *Joiner) void { - if (this.watcher.needs_newline) { - this.watcher.needs_newline = false; - this.push("\n"); - } -} - -pub fn append(this: *Joiner, slice: string, offset: u32, allocator: ?Allocator) void { - const data = slice[offset..]; - this.len += @as(u32, @truncate(data.len)); - - const new_tail = if (this.use_pool) - Joinable.Pool.get(default_allocator) - else - (this.node_allocator.create(Joinable.Pool.Node) catch unreachable); - - this.watcher.estimated_count += @intFromBool( - this.watcher.input.len > 0 and - bun.strings.contains(data, this.watcher.input), - ); - - this.watcher.needs_newline = this.watcher.input.len > 0 and data.len > 0 and - data[data.len - 1] != '\n'; - - new_tail.* = .{ - .allocator = default_allocator, - .data = Joinable{ - .offset = @as(u31, @truncate(offset)), - .allocator = allocator orelse undefined, - .needs_deinit = allocator != null, - .slice = slice, - }, - }; - - var tail = this.tail orelse { - this.tail = new_tail; - this.head = new_tail; - return; - }; - tail.next = new_tail; - this.tail = new_tail; -} - -pub fn contains(this: *const Joiner, slice: string) bool { - var el = this.head; - while (el) |node| { - el = node.next; - if (bun.strings.contains(node.data.slice[node.data.offset..], slice)) return true; - } - - return false; -} diff --git a/test/bun.lockb b/test/bun.lockb index edfce7c97cfc9d3114227ce3ea278fc4743a6e57..6d3168fc913c34f92b80e78f13398aef218cd560 100755 GIT binary patch delta 33313 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 r)y1mM5^IaqP^+vhR&ccs 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