Files
bun.sh/src/bundler/Graph.zig
taylor.fish a57dee5721 Various safety improvements (safety.ThreadLock, stack traces, MimallocArena, RefCount, safety.alloc) (#21726)
* Move `DebugThreadLock` to `bun.safety`
* Enable in `ci_assert` builds, but store stack traces only in debug
builds
  * Reduce size of struct by making optional field non-optional
* Add `initLockedIfNonComptime` as a workaround for not being able to
call `initLocked` in comptime contexts
* Add `lockOrAssert` method to acquire the lock if unlocked, or else
assert that the current thread acquired the lock
* Add stack traces to `CriticalSection` and `AllocPtr` in debug builds
* Make `MimallocArena.init` infallible
* Make `MimallocArena.heap` non-nullable
* Rename `RefCount.active_counts` to `raw_count` and provide read-only
`get` method
* Add `bun.safety.alloc.assertEq` to assert that two allocators are
equal (avoiding comparison of undefined `ptr`s)

(For internal tracking: fixes STAB-917, STAB-918, STAB-962, STAB-963,
STAB-964, STAB-965)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-11 13:40:07 -07:00

131 lines
4.6 KiB
Zig

const Graph = @This();
pool: *ThreadPool,
heap: ThreadLocalArena,
/// This allocator is thread-local to the Bundler thread
/// .allocator == .heap.allocator()
allocator: std.mem.Allocator,
/// Mapping user-specified entry points to their Source Index
entry_points: std.ArrayListUnmanaged(Index) = .{},
/// Every source index has an associated InputFile
input_files: MultiArrayList(InputFile) = .{},
/// Every source index has an associated Ast
/// When a parse is in progress / queued, it is `Ast.empty`
ast: MultiArrayList(JSAst) = .{},
/// During the scan + parse phase, this value keeps a count of the remaining
/// tasks. Once it hits zero, the scan phase ends and linking begins. Note
/// that if `deferred_pending > 0`, it means there are plugin callbacks
/// to invoke before linking, which can initiate another scan phase.
///
/// Increment and decrement this via `incrementScanCounter` and
/// `decrementScanCounter`, as asynchronous bundles check for `0` in the
/// decrement function, instead of at the top of the event loop.
///
/// - Parsing a file (ParseTask and ServerComponentParseTask)
/// - onResolve and onLoad functions
/// - Resolving an onDefer promise
pending_items: u32 = 0,
/// When an `onLoad` plugin calls `.defer()`, the count from `pending_items`
/// is "moved" into this counter (pending_items -= 1; deferred_pending += 1)
///
/// When `pending_items` hits zero and there are deferred pending tasks, those
/// tasks will be run, and the count is "moved" back to `pending_items`
deferred_pending: u32 = 0,
/// A map of build targets to their corresponding module graphs.
build_graphs: std.EnumArray(options.Target, PathToSourceIndexMap) = .initFill(.{}),
/// When Server Components is enabled, this holds a list of all boundary
/// files. This happens for all files with a "use <side>" directive.
server_component_boundaries: ServerComponentBoundary.List = .{},
/// Track HTML imports from server-side code
/// Each entry represents a server file importing an HTML file that needs a client build
///
/// OutputPiece.Kind.HTMLManifest corresponds to indices into the array.
html_imports: struct {
/// Source index of the server file doing the import
server_source_indices: BabyList(Index.Int) = .{},
/// Source index of the HTML file being imported
html_source_indices: BabyList(Index.Int) = .{},
} = .{},
estimated_file_loader_count: usize = 0,
/// For Bake, a count of the CSS asts is used to make precise
/// pre-allocations without re-iterating the file listing.
css_file_count: usize = 0,
additional_output_files: std.ArrayListUnmanaged(options.OutputFile) = .{},
kit_referenced_server_data: bool,
kit_referenced_client_data: bool,
pub const InputFile = struct {
source: Logger.Source,
loader: options.Loader = options.Loader.file,
side_effects: _resolver.SideEffects,
allocator: std.mem.Allocator = bun.default_allocator,
additional_files: BabyList(AdditionalFile) = .{},
unique_key_for_additional_file: string = "",
content_hash_for_additional_file: u64 = 0,
is_plugin_file: bool = false,
};
pub inline fn pathToSourceIndexMap(this: *Graph, target: options.Target) *PathToSourceIndexMap {
return this.build_graphs.getPtr(target);
}
/// Schedule a task to be run on the JS thread which resolves the promise of
/// each `.defer()` called in an onLoad plugin.
///
/// Returns true if there were more tasks queued.
pub fn drainDeferredTasks(this: *Graph, transpiler: *BundleV2) bool {
transpiler.thread_lock.assertLocked();
if (this.deferred_pending > 0) {
this.pending_items += this.deferred_pending;
this.deferred_pending = 0;
transpiler.drain_defer_task.init();
transpiler.drain_defer_task.schedule();
return true;
}
return false;
}
pub const Ref = bun.ast.Ref;
pub const Index = bun.ast.Index;
const string = []const u8;
const Logger = @import("../logger.zig");
const _resolver = @import("../resolver/resolver.zig");
const std = @import("std");
const options = @import("../options.zig");
const Loader = options.Loader;
const bun = @import("bun");
const MultiArrayList = bun.MultiArrayList;
const default_allocator = bun.default_allocator;
const BabyList = bun.collections.BabyList;
const allocators = bun.allocators;
const ThreadLocalArena = bun.allocators.MimallocArena;
const js_ast = bun.ast;
const JSAst = js_ast.BundledAst;
const ServerComponentBoundary = js_ast.ServerComponentBoundary;
const AdditionalFile = bun.bundle_v2.AdditionalFile;
const BundleV2 = bun.bundle_v2.BundleV2;
const ParseTask = bun.bundle_v2.ParseTask;
const PathToSourceIndexMap = bun.bundle_v2.PathToSourceIndexMap;
const ThreadPool = bun.bundle_v2.ThreadPool;