Skip to content
Closed
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
8 changes: 8 additions & 0 deletions src/bundler/OutputFile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ pub struct OutputFile {
pub source_map_index: u32,
pub bytecode_index: u32,
pub module_info_index: u32,
/// Only set on `OutputKind::Bytecode` files: the JSC source hash the
/// bytecode was keyed with (`dispatch::GeneratedBytecode::source_hash`),
/// embedded next to the bytecode by `bun build --compile`. 0 otherwise.
pub bytecode_source_hash: u32,
pub output_kind: OutputKind,
/// Relative
pub dest_path: Box<[u8]>,
Expand Down Expand Up @@ -52,6 +56,7 @@ impl OutputFile {
source_map_index: u32::MAX,
bytecode_index: u32::MAX,
module_info_index: u32::MAX,
bytecode_source_hash: 0,
output_kind: OutputKind::Chunk,
dest_path: Box::default(),
side: None,
Expand Down Expand Up @@ -91,6 +96,7 @@ impl Clone for OutputFile {
source_map_index: self.source_map_index,
bytecode_index: self.bytecode_index,
module_info_index: self.module_info_index,
bytecode_source_hash: self.bytecode_source_hash,
output_kind: self.output_kind,
dest_path: self.dest_path.clone(),
side: self.side,
Expand Down Expand Up @@ -198,6 +204,7 @@ pub struct Options {
pub(crate) source_map_index: Option<u32>,
pub(crate) bytecode_index: Option<u32>,
pub(crate) module_info_index: Option<u32>,
pub(crate) bytecode_source_hash: u32,
pub(crate) output_path: Box<[u8]>,
pub(crate) source_index: IndexOptional,
pub(crate) size: Option<usize>,
Expand Down Expand Up @@ -235,6 +242,7 @@ impl OutputFile {
output_kind: options.output_kind,
bytecode_index: options.bytecode_index.unwrap_or(u32::MAX),
module_info_index: options.module_info_index.unwrap_or(u32::MAX),
bytecode_source_hash: options.bytecode_source_hash,
source_map_index: options.source_map_index.unwrap_or(u32::MAX),
is_executable: options.is_executable,
value: match options.data {
Expand Down
14 changes: 12 additions & 2 deletions src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,16 @@ pub mod bv2_impl {
}
}

/// A chunk's generated JSC bytecode cache.
pub struct GeneratedBytecode {
pub bytes: Box<[u8]>,
/// `JSC::SourceProvider::hash()` of the source the bytecode was
/// generated from; part of the cache key JSC checks before using
/// `bytes`. Recording it lets the runtime build the key without
/// reading the source text. Never 0.
pub source_hash: u32,
}

unsafe extern "Rust" {
/// Defined `#[no_mangle]` in `bun_jsc::cached_bytecode`. Generic
/// "generate JSC bytecode off the main JS thread" helper — marks the
Expand All @@ -1376,7 +1386,7 @@ pub mod bv2_impl {
format: crate::options_impl::Format,
source: &[u8],
source_provider_url: &mut bun_core::String,
) -> Option<Box<[u8]>>;
) -> Option<GeneratedBytecode>;
}

unsafe extern "Rust" {
Expand Down Expand Up @@ -1414,7 +1424,7 @@ pub mod bv2_impl {
format: crate::options_impl::Format,
source: &[u8],
source_provider_url: &mut bun_core::String,
) -> Option<Box<[u8]>> {
) -> Option<GeneratedBytecode> {
__bun_jsc_generate_cached_bytecode(format, source, source_provider_url)
}

Expand Down
1 change: 1 addition & 0 deletions src/bundler/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ impl Default for output_file::Options {
source_map_index: None,
bytecode_index: None,
module_info_index: None,
bytecode_source_hash: 0,
output_path: Box::default(),
source_index: output_file::IndexOptional::NONE,
size: None,
Expand Down
6 changes: 5 additions & 1 deletion src/bundler/linker_context/generateChunksInParallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,10 @@ pub(crate) fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(
let mut source_provider_url =
bun_core::OwnedString::new(source_provider_url);

if let Some(bytecode) = crate::bundle_v2::dispatch::generate_cached_bytecode(
if let Some(crate::bundle_v2::dispatch::GeneratedBytecode {
bytes: bytecode,
source_hash,
}) = crate::bundle_v2::dispatch::generate_cached_bytecode(
c.options.output_format,
&code_result.buffer,
&mut source_provider_url,
Expand Down Expand Up @@ -1105,6 +1108,7 @@ pub(crate) fn generate_chunks_in_parallel<const IS_DEV_SERVER: bool>(
None
},
output_kind: options::OutputKind::Bytecode,
bytecode_source_hash: source_hash,
loader: Loader::File,
size: Some(bytecode.len()),
display_size: bytecode.len() as u32,
Expand Down
10 changes: 9 additions & 1 deletion src/bundler/linker_context/writeOutputFilesToDisk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ pub(crate) fn write_output_files_to_disk(
source_map_index: None,
bytecode_index: None,
module_info_index: None,
bytecode_source_hash: 0,
display_size: 0,
referenced_css_chunks: Box::default(),
source_index: IndexOptional::NONE,
Expand All @@ -184,6 +185,7 @@ pub(crate) fn write_output_files_to_disk(
source_map_index,
bytecode_index: None,
module_info_index: None,
bytecode_source_hash: 0,
side: Some(options::Side::Client),
entry_point_index: None,
referenced_css_chunks: Box::default(),
Expand Down Expand Up @@ -356,6 +358,7 @@ pub(crate) fn write_output_files_to_disk(
source_map_index: None,
bytecode_index: None,
module_info_index: None,
bytecode_source_hash: 0,
display_size: 0,
referenced_css_chunks: Box::default(),
source_index: IndexOptional::NONE,
Expand Down Expand Up @@ -408,7 +411,10 @@ pub(crate) fn write_output_files_to_disk(
// `defer source_provider_url.deref()` handled by Drop on OwnedString.
let mut source_provider_url = bun_core::OwnedString::new(source_provider_url);

if let Some(bytecode) = crate::bundle_v2::dispatch::generate_cached_bytecode(
if let Some(crate::bundle_v2::dispatch::GeneratedBytecode {
bytes: bytecode,
source_hash,
}) = crate::bundle_v2::dispatch::generate_cached_bytecode(
c.options.output_format,
&code_result.buffer,
&mut source_provider_url,
Expand Down Expand Up @@ -478,6 +484,7 @@ pub(crate) fn write_output_files_to_disk(
None
},
output_kind: options::OutputKind::Bytecode,
bytecode_source_hash: source_hash,
loader: Loader::File,
size: Some(bytecode.len()),
display_size: bytecode.len() as u32,
Expand Down Expand Up @@ -560,6 +567,7 @@ pub(crate) fn write_output_files_to_disk(
source_map_index,
bytecode_index,
module_info_index: None,
bytecode_source_hash: 0,
size: Some(code_result.buffer.len()),
display_size: display_size as u32,
is_executable: chunk.flags.contains(ChunkFlags::IS_EXECUTABLE),
Expand Down
53 changes: 42 additions & 11 deletions src/jsc/CachedBytecode.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use core::ptr::NonNull;

use bun_bundler::dispatch::GeneratedBytecode;
use bun_core::String as BunString;
use bun_options_types::Format;

Expand All @@ -15,6 +16,7 @@ unsafe extern "C" {
input_source_code_size: usize,
output_byte_code: *mut Option<NonNull<u8>>,
output_byte_code_size: *mut usize,
output_source_hash: *mut u32,
cached_bytecode: *mut Option<NonNull<CachedBytecode>>,
) -> bool;

Expand All @@ -24,6 +26,7 @@ unsafe extern "C" {
input_source_code_size: usize,
output_byte_code: *mut Option<NonNull<u8>>,
output_byte_code_size: *mut usize,
output_source_hash: *mut u32,
cached_bytecode: *mut Option<NonNull<CachedBytecode>>,
) -> bool;

Expand All @@ -33,18 +36,28 @@ unsafe extern "C" {
safe fn CachedBytecode__deref(this: &mut CachedBytecode);
}

/// Output of [`CachedBytecode::generate`].
///
/// SAFETY CONTRACT: `bytes` actually borrows from `handle` and is invalidated
/// when `CachedBytecode__deref` is called on it. Callers own the handle and
/// must deref it to free.
pub(crate) struct Generated {
pub bytes: &'static [u8],
/// See [`GeneratedBytecode::source_hash`].
pub source_hash: u32,
pub handle: NonNull<CachedBytecode>,
}

impl CachedBytecode {
// SAFETY CONTRACT: the returned `&'static [u8]` actually borrows from the
// `CachedBytecode` handle and is invalidated when `deref()` is called. Callers own
// the handle and must call `deref()` (or drop via `allocator()`) to free.
pub(crate) fn generate_for_esm(
source_provider_url: &mut BunString,
input: &[u8],
) -> Option<(&'static [u8], NonNull<CachedBytecode>)> {
) -> Option<Generated> {
let mut this: Option<NonNull<CachedBytecode>> = None;

let mut input_code_size: usize = 0;
let mut input_code_ptr: Option<NonNull<u8>> = None;
let mut source_hash: u32 = 0;
// SAFETY: out-params are valid for write; input slice valid for read.
let ok = unsafe {
generateCachedModuleByteCodeFromSourceCode(
Expand All @@ -53,6 +66,7 @@ impl CachedBytecode {
input.len(),
&raw mut input_code_ptr,
&raw mut input_code_size,
&raw mut source_hash,
&raw mut this,
)
};
Expand All @@ -61,7 +75,11 @@ impl CachedBytecode {
// and the slice is valid for `input_code_size` bytes until deref().
let slice =
unsafe { bun_core::ffi::slice(input_code_ptr.unwrap().as_ptr(), input_code_size) };
return Some((slice, this.unwrap()));
return Some(Generated {
bytes: slice,
source_hash,
handle: this.unwrap(),
});
}

None
Expand All @@ -70,10 +88,11 @@ impl CachedBytecode {
pub(crate) fn generate_for_cjs(
source_provider_url: &mut BunString,
input: &[u8],
) -> Option<(&'static [u8], NonNull<CachedBytecode>)> {
) -> Option<Generated> {
let mut this: Option<NonNull<CachedBytecode>> = None;
let mut input_code_size: usize = 0;
let mut input_code_ptr: Option<NonNull<u8>> = None;
let mut source_hash: u32 = 0;
// SAFETY: out-params are valid for write; input slice valid for read.
let ok = unsafe {
generateCachedCommonJSProgramByteCodeFromSourceCode(
Expand All @@ -82,6 +101,7 @@ impl CachedBytecode {
input.len(),
&raw mut input_code_ptr,
&raw mut input_code_size,
&raw mut source_hash,
&raw mut this,
)
};
Expand All @@ -90,7 +110,11 @@ impl CachedBytecode {
// and the slice is valid for `input_code_size` bytes until deref().
let slice =
unsafe { bun_core::ffi::slice(input_code_ptr.unwrap().as_ptr(), input_code_size) };
return Some((slice, this.unwrap()));
return Some(Generated {
bytes: slice,
source_hash,
handle: this.unwrap(),
});
}

None
Expand All @@ -100,7 +124,7 @@ impl CachedBytecode {
format: Format,
input: &[u8],
source_provider_url: &mut BunString,
) -> Option<(&'static [u8], NonNull<CachedBytecode>)> {
) -> Option<Generated> {
match format {
Format::Esm => Self::generate_for_esm(source_provider_url, input),
Format::Cjs => Self::generate_for_cjs(source_provider_url, input),
Expand Down Expand Up @@ -133,14 +157,21 @@ pub(crate) fn __bun_jsc_generate_cached_bytecode(
format: Format,
source: &[u8],
source_provider_url: &mut BunString,
) -> Option<Box<[u8]>> {
) -> Option<GeneratedBytecode> {
crate::virtual_machine::IS_BUNDLER_THREAD_FOR_BYTECODE_CACHE.set(true);
crate::initialize(false);
let (bytes, handle) = CachedBytecode::generate(format, source, source_provider_url)?;
let Generated {
bytes,
source_hash,
handle,
} = CachedBytecode::generate(format, source, source_provider_url)?;
let owned = Box::<[u8]>::from(bytes);
// `handle` was just produced by C++ and is valid until deref;
// `CachedBytecode` is an opaque ZST handle so `opaque_mut` is the
// centralised zero-byte deref proof.
CachedBytecode__deref(CachedBytecode::opaque_mut(handle.as_ptr()));
Some(owned)
Some(GeneratedBytecode {
bytes: owned,
source_hash,
})
}
2 changes: 1 addition & 1 deletion src/jsc/NodeCompileCache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ fn generate_bytecode(format: Format, code: &[u8], url: &[u8]) -> Option<Box<[u8]
job.format, &job.code, &mut url,
);
url.deref();
let _ = job.resp.send(result);
let _ = job.resp.send(result.map(|generated| generated.bytes));
}
});
match spawned {
Expand Down
8 changes: 8 additions & 0 deletions src/jsc/ResolvedSource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ pub struct ResolvedSource {
/// was used at build time. If empty, the origin is derived from source_url.
/// This is converted to a file:// URL on the C++ side.
pub bytecode_origin_path: BunString,
/// `JSC::SourceProvider::hash()` of `source_code`, recorded when
/// `bytecode_cache` was generated. The C++ side installs it on the
/// `SourceProvider` so building the bytecode cache key does not have to
/// read `source_code` (for a compiled executable that would fault in every
/// page of the embedded source). 0 when unknown; WTF string hashes are
/// never 0, so 0 unambiguously means "compute it from the source".
pub bytecode_source_hash: u32,
}

impl Default for ResolvedSource {
Expand All @@ -70,6 +77,7 @@ impl Default for ResolvedSource {
bytecode_cache_size: 0,
module_info: core::ptr::null_mut(),
bytecode_origin_path: BunString::empty(),
bytecode_source_hash: 0,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/jsc/bindings/JSCommonJSModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,8 @@ void JSCommonJSModule::evaluate(
string.substring(trimStart, string.length() - trimStart - 4),
wrapperEnd));
source.needsDeref = true;
// The source no longer matches what the bytecode was built from.
source.bytecode_source_hash = 0;
}
}

Expand Down Expand Up @@ -1539,6 +1541,8 @@ std::optional<JSC::SourceCode> createCommonJSModule(
globalObject->m_moduleWrapperEnd);
source.source_code.deref();
source.source_code = Bun::toStringRef(concat);
// The source no longer matches what the bytecode was built from.
source.bytecode_source_hash = 0;
}

auto sourceProvider = Zig::SourceProvider::create(globalObject, source, JSC::SourceProviderSourceType::Program, isBuiltIn);
Expand Down
Loading
Loading