Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions src/bundler/Chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,8 @@ pub struct Chunk<'a> {
/// Like `unique_key`, but replaced with `id()` rather than the chunk's path.
pub(crate) id_key: &'static [u8],

/// Maps source index to bytes contributed to this chunk's output (for metafile).
/// Empty for a CSS or HTML chunk: the metafile takes the inputs of those from the compile results.
/// The value is updated during parallel chunk generation to track bytesInOutput.
/// CONCURRENCY: the key set is frozen before codegen starts; worker threads
/// only `fetch_add` the per-source counters (see
/// `generate_compile_result_for_js_chunk`), so the value type is
/// `AtomicUsize` rather than `usize` to avoid materializing aliased `&mut`.
pub(crate) files_with_parts_in_chunk: ArrayHashMap<IndexInt, core::sync::atomic::AtomicUsize>,
/// The files assigned to a JS chunk, in graph order. Not all that it prints: the JS code of a stylesheet has no key (`files_in_chunk_order` lists it). Empty for a CSS or HTML chunk.
pub(crate) files_with_parts_in_chunk: ArrayHashMap<IndexInt, ()>,

/// We must not keep pointers to this type until all chunks have been allocated.
pub(crate) entry_bits: AutoBitSet,
Expand Down Expand Up @@ -124,12 +118,10 @@ impl Default for Content<'_> {
// (UnsafeCell-per-slot) so the per-task write is routed through interior
// mutability and never requires an aliased `&mut Chunk` /
// `&mut [CompileResult]` — see [`Chunk::write_compile_result_slot`].
// `files_with_parts_in_chunk` values are bumped via atomic RMW;
// the renamer is fully populated before fan-out, and the printer only reads it.
// The renamer is fully populated before fan-out, and the printer only reads it.
unsafe impl Send for Chunk<'_> {}
// SAFETY: shared `&Chunk` access during the worker fan-out writes only
// `compile_results_for_chunk` (UnsafeCell-per-slot, disjoint indices) and
// `files_with_parts_in_chunk` atomic counters; the remaining fields are
// `compile_results_for_chunk` (UnsafeCell-per-slot, disjoint indices); the remaining fields are
// frozen before fan-out.
unsafe impl Sync for Chunk<'_> {}

Expand Down
1 change: 0 additions & 1 deletion src/bundler/LinkerContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1888,7 +1888,6 @@ pub struct PendingPartRange<'c, 'a> {
/// - `chunk.compile_results_for_chunk[i]` is written at a per-task unique `i`
/// via [`Chunk::write_compile_result_slot`] (raw `addr_of_mut!` +
/// `UnsafeCell` slot write — never `&mut Chunk`),
/// - `chunk.files_with_parts_in_chunk` entries are updated via atomic RMW only,
/// - all other access through `ctx.c` / `ctx.chunk` during codegen is read-only.
#[inline]
pub(crate) unsafe fn pending_part_range_prologue<'c, 'a>(
Expand Down
31 changes: 24 additions & 7 deletions src/bundler/linker_context/MetafileBuilder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,30 @@ fn fmt_size(bytes: u64) -> bfmt::SizeFormatter {
/// The inputs of the output of `chunk`, each with its "bytesInOutput", in the order of the record.
fn chunk_inputs(chunk: &Chunk) -> crate::Result<Vec<(IndexInt, usize)>> {
match &chunk.content {
ChunkContent::Javascript(_) => {
let counted = chunk.files_with_parts_in_chunk.iter();
let load = |bytes: &core::sync::atomic::AtomicUsize| {
// The codegen workers that add to the counters are joined before any output is emitted.
bytes.load(core::sync::atomic::Ordering::Relaxed)
};
Ok(counted.map(|(&file, bytes)| (file, load(bytes))).collect())
ChunkContent::Javascript(js) => {
let results = &chunk.compile_results_for_chunk;
debug_assert_eq!(
results.len(),
js.parts_in_chunk_in_order.len(),
"the metafile reads the compile results of the chunk",
);
// Unlike esbuild, which lists sources in print order, the files of the chunk come first, in graph order.
let files = &chunk.files_with_parts_in_chunk;
let mut inputs: Vec<(IndexInt, usize)> =
files.keys().iter().map(|&file| (file, 0)).collect();
// A stylesheet is a file of no JS chunk. The ones that the chunk prints code of come next, in print order.
let mut copied: ArrayHashMap<IndexInt, usize> = ArrayHashMap::new();
for result in results.iter() {
let bytes = result.code().len();
match files.get_index(&result.source_index()) {
Some(file) => inputs[file].1 += bytes,
None => *copied.get_or_put(result.source_index())?.value_ptr += bytes,
}
}
// A wrapped stylesheet has a compile result also when the chunk drops its wrapper. That one gets no record.
let printed = copied.iter().filter(|&(_, &bytes)| bytes > 0);
Comment thread
robobun marked this conversation as resolved.
Resolved
inputs.extend(printed.map(|(&file, &bytes)| (file, bytes)));
Ok(inputs)
}
ChunkContent::Css(_) => {
// One input for each stylesheet, also when the chunk prints it under two import conditions.
Expand Down
18 changes: 4 additions & 14 deletions src/bundler/linker_context/computeChunks.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::mal_prelude::*;
use core::sync::atomic::AtomicUsize;

use bun_alloc::Arena; // bumpalo::Bump re-export
use bun_collections::{ArrayHashMap, AutoBitSet, VecExt, index_sort};
Expand Down Expand Up @@ -354,14 +353,10 @@ pub(crate) fn compute_chunks<'a>(
.insert(chunk::Flags::IS_BROWSER_CHUNK_FROM_SERVER_BUILD);
}

let entry = js_chunk_entry
js_chunk_entry
.value_ptr
.files_with_parts_in_chunk
.get_or_put(source_index.get() as u32)
.expect("unreachable");
if !entry.found_existing {
*entry.value_ptr = AtomicUsize::new(0); // Initialize byte count to 0
}
.put(source_index.get(), ())?;
} else {
// Pass a context struct + fn pointer to entry_bits' forEach.
struct Handler<'a, 'c> {
Expand All @@ -377,13 +372,8 @@ pub(crate) fn compute_chunks<'a>(
return;
}

let entry = c.chunks[chunk_idx as usize]
.files_with_parts_in_chunk
.get_or_put(c.source_id as u32)
.expect("unreachable");
if !entry.found_existing {
*entry.value_ptr = AtomicUsize::new(0); // Initialize byte count to 0
}
let files = &mut c.chunks[chunk_idx as usize].files_with_parts_in_chunk;
bun_core::handle_oom(files.put(c.source_id, ()));
}
let mut handler = Handler {
chunks: js_chunks.values_mut(),
Expand Down
25 changes: 2 additions & 23 deletions src/bundler/linker_context/generateCompileResultForJSChunk.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::mal_prelude::*;
use core::sync::atomic::Ordering;

use bun_ast::Scope;
use bun_js_printer::{self as js_printer, PrintResult};
use bun_js_printer as js_printer;
use bun_threading::thread_pool as ThreadPoolLib;

use crate::analyze_transpiled_module::ModuleInfo;
Expand All @@ -15,8 +14,7 @@ use super::generate_code_for_file_in_chunk_js::generate_code_for_file_in_chunk_j

// CONCURRENCY: thread-pool callback — runs on worker threads, one task per
// `PendingPartRange`. Writes: `chunk.compile_results_for_chunk[i]` (disjoint
// by per-task `i`), `chunk.files_with_parts_in_chunk[source].counter`
// (atomic RMW). Reads `c.graph`/`c.parse_graph` SoA columns shared. Never
// by per-task `i`). Reads `c.graph`/`c.parse_graph` SoA columns shared. Never
// forms `&mut LinkerContext` or `&mut Chunk`.
// `PendingPartRange` is `Send` because its only non-auto-`Send` field is
// `&GenerateChunkCtx` whose pointee is `Send + Sync`.
Expand Down Expand Up @@ -136,25 +134,6 @@ fn generate_compile_result_for_js_chunk_impl(
module_info.as_deref_mut(),
);

// Update bytesInOutput for this source in the chunk (for metafile)
// Use atomic operation since multiple threads may update the same counter
let code_len = match &result {
PrintResult::Result(r) => r.code.len(),
_ => 0,
};
if code_len > 0 && !part_range.source_index.is_runtime() {
// CONCURRENCY: the map's key set is frozen before parallel codegen; we
// only need a shared `&AtomicUsize` to RMW the counter. Using `get`
// (not `get_ptr_mut`) avoids materializing an aliased `&mut` to a slot
// that other worker threads may be updating for the same source.
if let Some(bytes) = chunk
.files_with_parts_in_chunk
.get(&part_range.source_index.get())
{
let _ = bytes.fetch_add(code_len, Ordering::Relaxed);
}
}

CompileResult::Javascript {
source_index: part_range.source_index.get(),
result,
Expand Down
1 change: 0 additions & 1 deletion src/bundler/linker_context/postProcessJSChunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,6 @@ pub(crate) fn post_process_js_chunk(
}
}

// TODO: metafile
newline_before_comment = !compile_result.code().is_empty();
}
// An entry chunk whose code all lives in shared chunks has no compile results of its own.
Expand Down
Loading
Loading