LibJS: Borrow mapped bytecode cache executable bytes

Keep executable bytecode payloads decoded from owner-backed bytecode
cache blobs as ranges into the original blob instead of copying them
into Rust Vec allocations. The mapped blob owner is held by decoded
executable records, including lazy nested function executables, so the
borrowed bytecode remains alive until materialization copies it into the
final C++ Executable.

Use the owner-backed decoder for HTTP bytecode cache hits and keep the
plain byte decoder for tests and in-memory callers. Add coverage for
materializing bytecode cache data from an ImmutableBytes mapped file.
This commit is contained in:
Andreas Kling 2026-05-15 16:45:31 +02:00 committed by Andreas Kling
parent c0b19ff981
commit a646f9d0bf
7 changed files with 357 additions and 51 deletions

View file

@ -24,9 +24,11 @@
use std::ffi::c_void;
use super::generator::{
AssembledBytecode, ConstantValue, Generator, PendingClassBlueprint, PendingClassElement, PendingLiteralValueKind,
AssembledBytecode, ConstantValue, ExceptionHandler, Generator, PendingClassBlueprint, PendingClassElement,
PendingLiteralValueKind,
};
use crate::ast::Utf16String;
use crate::bytecode::basic_block::SourceMapEntry;
use crate::u32_from_usize;
/// Opaque pointer returned from rust_create_executable.
@ -657,6 +659,45 @@ pub unsafe fn create_executable_with_dependencies(
source_code_ptr: *const c_void,
sfd_ptrs: &[*const c_void],
bp_ptrs: &[*mut c_void],
) -> ExecutableHandle {
unsafe {
let parts = ExecutableParts {
bytecode: &assembled.bytecode,
exception_handlers: &assembled.exception_handlers,
source_map: &assembled.source_map,
basic_block_start_offsets: &assembled.basic_block_start_offsets,
number_of_registers: assembled.number_of_registers,
number_of_arguments: assembled.number_of_arguments,
};
create_executable_with_dependencies_from_parts(generator, parts, vm_ptr, source_code_ptr, sfd_ptrs, bp_ptrs)
}
}
pub struct ExecutableParts<'a> {
pub bytecode: &'a [u8],
pub exception_handlers: &'a [ExceptionHandler],
pub source_map: &'a [SourceMapEntry],
pub basic_block_start_offsets: &'a [usize],
pub number_of_registers: u32,
pub number_of_arguments: u32,
}
/// Create a C++ Executable from already materialized dependency objects and
/// borrowed bytecode/table slices.
///
/// This variant lets bytecode cache materialization point at mmap-backed cache
/// blob bytes without first cloning executable bytecode into a Rust Vec.
///
/// # Safety
/// `vm_ptr`, `source_code_ptr`, all dependency pointers, and all borrowed
/// slices must be valid for the duration of the call.
pub unsafe fn create_executable_with_dependencies_from_parts(
generator: &Generator,
parts: ExecutableParts<'_>,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
sfd_ptrs: &[*const c_void],
bp_ptrs: &[*mut c_void],
) -> ExecutableHandle {
unsafe {
// Build FFI slices for tables
@ -682,7 +723,7 @@ pub unsafe fn create_executable_with_dependencies(
let constants_buffer = encode_constants(&generator.constants);
// Build FFI exception handlers
let ffi_handlers: Vec<FFIExceptionHandler> = assembled
let ffi_handlers: Vec<FFIExceptionHandler> = parts
.exception_handlers
.iter()
.map(|h| FFIExceptionHandler {
@ -693,7 +734,7 @@ pub unsafe fn create_executable_with_dependencies(
.collect();
// Build FFI source map
let ffi_source_map: Vec<FFISourceMapEntry> = assembled
let ffi_source_map: Vec<FFISourceMapEntry> = parts
.source_map
.iter()
.map(|e| FFISourceMapEntry {
@ -711,8 +752,8 @@ pub unsafe fn create_executable_with_dependencies(
.collect();
let ffi_data = FFIExecutableData {
bytecode: assembled.bytecode.as_ptr(),
bytecode_length: assembled.bytecode.len(),
bytecode: parts.bytecode.as_ptr(),
bytecode_length: parts.bytecode.len(),
identifier_table: ident_slices.as_ptr(),
identifier_count: ident_slices.len(),
property_key_table: property_key_slices.as_ptr(),
@ -726,8 +767,8 @@ pub unsafe fn create_executable_with_dependencies(
exception_handler_count: ffi_handlers.len(),
source_map: ffi_source_map.as_ptr(),
source_map_count: ffi_source_map.len(),
basic_block_offsets: assembled.basic_block_start_offsets.as_ptr(),
basic_block_count: assembled.basic_block_start_offsets.len(),
basic_block_offsets: parts.basic_block_start_offsets.as_ptr(),
basic_block_count: parts.basic_block_start_offsets.len(),
local_variable_names: local_var_slices.as_ptr(),
local_variable_count: local_var_slices.len(),
property_lookup_cache_count: generator.next_property_lookup_cache,
@ -735,8 +776,8 @@ pub unsafe fn create_executable_with_dependencies(
template_object_cache_count: generator.next_template_object_cache,
object_shape_cache_count: generator.next_object_shape_cache,
object_property_iterator_cache_count: generator.next_object_property_iterator_cache,
number_of_registers: assembled.number_of_registers,
number_of_arguments: assembled.number_of_arguments,
number_of_registers: parts.number_of_registers,
number_of_arguments: parts.number_of_arguments,
is_strict: generator.strict,
length_identifier: FFIOptionalU32::from(generator.length_identifier.map(|index| index.0)),
shared_function_data: sfd_ptrs.as_ptr(),

View file

@ -12,6 +12,8 @@
use std::collections::HashMap;
use std::ffi::c_void;
use std::ops::Range;
use std::rc::Rc;
use crate::bytecode::basic_block::SourceMapEntry;
use crate::bytecode::ffi::{
@ -67,7 +69,32 @@ pub(crate) fn decode_blob(
expected_program_type: ast::ProgramType,
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
) -> Option<DecodedCacheBlob> {
let mut decoder = Decoder::new(bytes);
decode_blob_impl(bytes, expected_program_type, expected_source_hash, None)
}
pub(crate) type FreeBytecodeCacheBlobOwner = unsafe extern "C" fn(*mut c_void);
pub(crate) struct ForeignBytecodeCacheBlobOwner {
pub(crate) owner: *mut c_void,
pub(crate) free_owner: FreeBytecodeCacheBlobOwner,
}
pub(crate) fn decode_blob_with_foreign_owner(
bytes: &[u8],
expected_program_type: ast::ProgramType,
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
owner: ForeignBytecodeCacheBlobOwner,
) -> Option<DecodedCacheBlob> {
decode_blob_impl(bytes, expected_program_type, expected_source_hash, Some(owner))
}
fn decode_blob_impl(
bytes: &[u8],
expected_program_type: ast::ProgramType,
expected_source_hash: &[u8; SOURCE_HASH_SIZE],
owner: Option<ForeignBytecodeCacheBlobOwner>,
) -> Option<DecodedCacheBlob> {
let mut decoder = Decoder::new(bytes, owner);
let blob = CacheBlob::decode(&mut decoder, expected_program_type, expected_source_hash)?;
if !decoder.is_empty() {
return None;
@ -105,13 +132,42 @@ impl Encoder {
}
}
struct ForeignBytecodeCacheBlob {
data: *const u8,
length: usize,
owner: *mut c_void,
free_owner: FreeBytecodeCacheBlobOwner,
}
impl Drop for ForeignBytecodeCacheBlob {
fn drop(&mut self) {
unsafe {
(self.free_owner)(self.owner);
}
}
}
struct Decoder<'a> {
bytes: &'a [u8],
offset: usize,
foreign_blob: Option<Rc<ForeignBytecodeCacheBlob>>,
}
impl<'a> Decoder<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes }
fn new(bytes: &'a [u8], owner: Option<ForeignBytecodeCacheBlobOwner>) -> Self {
let foreign_blob = owner.map(|owner| {
Rc::new(ForeignBytecodeCacheBlob {
data: bytes.as_ptr(),
length: bytes.len(),
owner: owner.owner,
free_owner: owner.free_owner,
})
});
Self {
bytes,
offset: 0,
foreign_blob,
}
}
fn is_empty(&self) -> bool {
@ -125,9 +181,23 @@ impl<'a> Decoder<'a> {
let (bytes, rest) = self.bytes.split_at(length);
self.bytes = rest;
self.offset = self.offset.checked_add(length)?;
Some(bytes)
}
fn bytecode_bytes(&mut self, length: usize) -> Option<DecodedBytecodeBytes> {
let offset = self.offset;
let bytes = self.bytes(length)?;
if let Some(foreign_blob) = &self.foreign_blob {
return Some(DecodedBytecodeBytes::Foreign {
blob: foreign_blob.clone(),
range: offset..offset + length,
});
}
Some(DecodedBytecodeBytes::Owned(bytes.to_vec()))
}
fn expect_bytes(&mut self, expected: &[u8]) -> Option<()> {
(self.bytes(expected.len())? == expected).then_some(())
}
@ -303,6 +373,35 @@ impl ByteVector {
}
}
enum DecodedBytecodeBytes {
Owned(Vec<u8>),
Foreign {
blob: Rc<ForeignBytecodeCacheBlob>,
range: Range<usize>,
},
}
impl DecodedBytecodeBytes {
fn decode(decoder: &mut Decoder<'_>) -> Option<Self> {
let length: usize = u32::decode(decoder)?.try_into().ok()?;
decoder.bytecode_bytes(length)
}
fn as_slice(&self) -> &[u8] {
match self {
Self::Owned(bytes) => bytes,
Self::Foreign { blob, range } => {
debug_assert!(range.end <= blob.length);
unsafe { std::slice::from_raw_parts(blob.data.add(range.start), range.len()) }
}
}
}
fn len(&self) -> usize {
self.as_slice().len()
}
}
struct Utf16<'a>(&'a [u16]);
impl Encode for Utf16<'_> {
@ -792,23 +891,41 @@ unsafe fn materialize_executable(
source_code_ptr: *const c_void,
) -> *mut c_void {
unsafe {
let mut generator = Generator::new();
generator.strict = executable.strict;
generator.this_value_needs_environment_resolution = executable.this_value_needs_environment_resolution;
generator.next_property_lookup_cache = executable.cache_counters.property_lookup_cache_count;
generator.next_global_variable_cache = executable.cache_counters.global_variable_cache_count;
generator.next_template_object_cache = executable.cache_counters.template_object_cache_count;
generator.next_object_shape_cache = executable.cache_counters.object_shape_cache_count;
generator.next_object_property_iterator_cache = executable.cache_counters.object_property_iterator_cache_count;
generator.identifier_table = executable.identifier_table;
generator.property_key_table = executable.property_key_table;
generator.string_table = executable.string_table;
generator.constants = executable.constants;
generator.local_variables = executable.local_variables;
generator.length_identifier = executable.length_identifier.map(PropertyKeyTableIndex);
let DecodedExecutableRecord {
strict,
number_of_registers,
number_of_arguments,
cache_counters,
this_value_needs_environment_resolution,
length_identifier,
bytecode,
identifier_table,
property_key_table,
string_table,
constants,
exception_handlers,
source_map,
local_variables,
shared_functions,
class_blueprints,
} = executable;
let sfd_ptrs: Vec<*const c_void> = executable
.shared_functions
let mut generator = Generator::new();
generator.strict = strict;
generator.this_value_needs_environment_resolution = this_value_needs_environment_resolution;
generator.next_property_lookup_cache = cache_counters.property_lookup_cache_count;
generator.next_global_variable_cache = cache_counters.global_variable_cache_count;
generator.next_template_object_cache = cache_counters.template_object_cache_count;
generator.next_object_shape_cache = cache_counters.object_shape_cache_count;
generator.next_object_property_iterator_cache = cache_counters.object_property_iterator_cache_count;
generator.identifier_table = identifier_table;
generator.property_key_table = property_key_table;
generator.string_table = string_table;
generator.constants = constants;
generator.local_variables = local_variables;
generator.length_identifier = length_identifier.map(PropertyKeyTableIndex);
let sfd_ptrs: Vec<*const c_void> = shared_functions
.into_iter()
.map(|function| materialize_function(function, generator.strict, vm_ptr, source_code_ptr) as *const c_void)
.collect();
@ -816,11 +933,8 @@ unsafe fn materialize_executable(
return std::ptr::null_mut();
}
let class_blueprints: Vec<PendingClassBlueprint> = executable
.class_blueprints
.into_iter()
.map(PendingClassBlueprint::from)
.collect();
let class_blueprints: Vec<PendingClassBlueprint> =
class_blueprints.into_iter().map(PendingClassBlueprint::from).collect();
let bp_ptrs: Vec<*mut c_void> = class_blueprints
.iter()
.map(|blueprint| crate::bytecode::ffi::materialize_class_blueprint(blueprint, vm_ptr, source_code_ptr))
@ -829,18 +943,16 @@ unsafe fn materialize_executable(
return std::ptr::null_mut();
}
let assembled = AssembledBytecode {
bytecode: executable.bytecode,
source_map: executable.source_map,
exception_handlers: executable.exception_handlers,
basic_block_start_offsets: Vec::new(),
number_of_registers: executable.number_of_registers,
number_of_arguments: executable.number_of_arguments,
};
crate::bytecode::ffi::create_executable_with_dependencies(
crate::bytecode::ffi::create_executable_with_dependencies_from_parts(
&generator,
&assembled,
crate::bytecode::ffi::ExecutableParts {
bytecode: bytecode.as_slice(),
exception_handlers: &exception_handlers,
source_map: &source_map,
basic_block_start_offsets: &[],
number_of_registers,
number_of_arguments,
},
vm_ptr,
source_code_ptr,
&sfd_ptrs,
@ -1918,7 +2030,7 @@ impl ExecutableRecord<'_> {
cache_counters: CacheCounters::decode(decoder)?,
this_value_needs_environment_resolution: bool::decode(decoder)?,
length_identifier: Option::<u32>::decode(decoder)?,
bytecode: ByteVector::decode(decoder)?,
bytecode: DecodedBytecodeBytes::decode(decoder)?,
identifier_table: Utf16Table::decode(decoder)?,
property_key_table: Utf16Table::decode(decoder)?,
string_table: Utf16Table::decode(decoder)?,
@ -1939,7 +2051,7 @@ struct DecodedExecutableRecord {
cache_counters: DecodedCacheCounters,
this_value_needs_environment_resolution: bool,
length_identifier: Option<u32>,
bytecode: Vec<u8>,
bytecode: DecodedBytecodeBytes,
identifier_table: Vec<ast::Utf16String>,
property_key_table: Vec<ast::Utf16String>,
string_table: Vec<ast::Utf16String>,
@ -2012,8 +2124,14 @@ impl DecodedExecutableRecord {
.collect();
let source_map_offsets: Vec<u32> = self.source_map.iter().map(|entry| entry.bytecode_offset).collect();
validate_bytecode(&self.bytecode, &bounds, &[], &exception_handlers, &source_map_offsets)
.map_err(|error| error.kind)?;
validate_bytecode(
self.bytecode.as_slice(),
&bounds,
&[],
&exception_handlers,
&source_map_offsets,
)
.map_err(|error| error.kind)?;
for function in &self.shared_functions {
function.precompiled.validate_cached_bytecode()?;

View file

@ -861,10 +861,55 @@ pub unsafe extern "C" fn rust_decode_bytecode_cache_blob(
let expected_source_hash = std::slice::from_raw_parts(expected_source_hash, expected_source_hash_len)
.try_into()
.expect("source hash length was checked");
let Some(blob) = bytecode_cache::decode_blob(
let bytes = std::slice::from_raw_parts(data, length);
let Some(blob) = bytecode_cache::decode_blob(bytes, expected_program_type, expected_source_hash) else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(DecodedBytecodeCacheBlob { _blob: blob }))
})
}
}
/// Decode an mmap-backed bytecode cache blob into an owned parser-free cache handle.
///
/// # Safety
/// - `data` must point to `length` readable bytes.
/// - `owner` must keep `data` alive until `free_owner` is called.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_decode_bytecode_cache_blob_with_owner(
data: *const u8,
length: usize,
expected_program_type: u8,
expected_source_hash: *const u8,
expected_source_hash_len: usize,
owner: *mut c_void,
free_owner: bytecode_cache::FreeBytecodeCacheBlobOwner,
) -> *mut DecodedBytecodeCacheBlob {
unsafe {
abort_on_panic(|| {
if owner.is_null() {
return std::ptr::null_mut();
}
let reject = || {
free_owner(owner);
std::ptr::null_mut()
};
if data.is_null() || expected_source_hash.is_null() || expected_source_hash_len != 32 {
return reject();
}
let expected_program_type = match expected_program_type {
0 => ast::ProgramType::Script,
1 => ast::ProgramType::Module,
_ => return reject(),
};
let expected_source_hash = std::slice::from_raw_parts(expected_source_hash, expected_source_hash_len)
.try_into()
.expect("source hash length was checked");
let Some(blob) = bytecode_cache::decode_blob_with_foreign_owner(
std::slice::from_raw_parts(data, length),
expected_program_type,
expected_source_hash,
bytecode_cache::ForeignBytecodeCacheBlobOwner { owner, free_owner },
) else {
return std::ptr::null_mut();
};
@ -3409,3 +3454,57 @@ pub unsafe extern "C" fn rust_validate_bytecode(
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
static FREED_FOREIGN_OWNERS: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn count_freed_foreign_owner(owner: *mut c_void) {
FREED_FOREIGN_OWNERS.fetch_add(1, Ordering::Relaxed);
unsafe {
drop(Box::from_raw(owner.cast::<u8>()));
}
}
fn new_foreign_owner() -> *mut c_void {
Box::into_raw(Box::new(0u8)).cast()
}
#[test]
fn decode_blob_with_owner_frees_owner_on_early_rejection() {
FREED_FOREIGN_OWNERS.store(0, Ordering::Relaxed);
let source_hash = [0u8; 32];
let blob = unsafe {
rust_decode_bytecode_cache_blob_with_owner(
std::ptr::null(),
0,
0,
source_hash.as_ptr(),
source_hash.len(),
new_foreign_owner(),
count_freed_foreign_owner,
)
};
assert!(blob.is_null());
assert_eq!(FREED_FOREIGN_OWNERS.load(Ordering::Relaxed), 1);
let bytes = [0u8; 1];
let blob = unsafe {
rust_decode_bytecode_cache_blob_with_owner(
bytes.as_ptr(),
bytes.len(),
2,
source_hash.as_ptr(),
source_hash.len(),
new_foreign_owner(),
count_freed_foreign_owner,
)
};
assert!(blob.is_null());
assert_eq!(FREED_FOREIGN_OWNERS.load(Ordering::Relaxed), 2);
}
}

View file

@ -409,6 +409,17 @@ DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(ReadonlyBytes bytes, Progra
return rust_decode_bytecode_cache_blob(bytes.data(), bytes.size(), static_cast<u8>(expected_type), source_hash.data(), source_hash.size());
}
static void free_bytecode_cache_blob_owner(void* owner)
{
delete static_cast<Core::ImmutableBytes*>(owner);
}
DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes bytes, ProgramType expected_type, ReadonlyBytes source_hash)
{
auto* owner = new Core::ImmutableBytes(move(bytes));
return rust_decode_bytecode_cache_blob_with_owner(owner->bytes().data(), owner->bytes().size(), static_cast<u8>(expected_type), source_hash.data(), source_hash.size(), owner, free_bytecode_cache_blob_owner);
}
void free_decoded_bytecode_cache_blob(DecodedBytecodeCacheBlob* blob)
{
rust_free_decoded_bytecode_cache_blob(blob);

View file

@ -12,6 +12,7 @@
#include <AK/Optional.h>
#include <AK/Result.h>
#include <AK/Utf16FlyString.h>
#include <LibCore/ImmutableBytes.h>
#include <LibGC/Ptr.h>
#include <LibGC/Root.h>
#include <LibJS/ModuleEntry.h>
@ -113,6 +114,7 @@ JS_API ByteBuffer serialize_compiled_program_for_bytecode_cache(FFI::CompiledPro
// Decode a bytecode cache blob into an owned parser-free cache handle.
JS_API FFI::DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(ReadonlyBytes, ProgramType, ReadonlyBytes source_hash);
JS_API FFI::DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes, ProgramType, ReadonlyBytes source_hash);
// Free a decoded bytecode cache blob.
JS_API void free_decoded_bytecode_cache_blob(FFI::DecodedBytecodeCacheBlob*);

View file

@ -612,7 +612,7 @@ void fetch_classic_script(GC::Ref<HTMLScriptElement> element, URL::URL const& ur
// so the fallback compile path below can reuse them if decode or materialization is rejected.
if (auto const& bytecode = response->javascript_bytecode_cache(); bytecode.has_value()) {
auto source_hash = bytecode_cache_source_hash(*source_code);
if (auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(bytecode->bytes(), JS::RustIntegration::ProgramType::Script, source_hash.bytes())) {
if (auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(*bytecode, JS::RustIntegration::ProgramType::Script, source_hash.bytes())) {
auto script = ClassicScript::create_from_bytecode_cache(response_url_string, source_code, settings_object, response_url, bytecode_cache, muted_errors);
// Bytecode validation runs during materialization and may reject a structurally valid blob whose
// bytecode is corrupt. Treat that as a cache miss and fall through to off-thread source compile.
@ -998,7 +998,7 @@ void fetch_single_module_script(JS::Realm& realm,
auto bytecode_cache_context = bytecode_cache_context_for_request(*request, *internal_response, response_url);
if (auto const& bytecode = internal_response->javascript_bytecode_cache(); bytecode.has_value()) {
auto source_hash = bytecode_cache_source_hash(*source_code);
if (auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(bytecode->bytes(), JS::RustIntegration::ProgramType::Module, source_hash.bytes())) {
if (auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(*bytecode, JS::RustIntegration::ProgramType::Module, source_hash.bytes())) {
auto module_script = ModuleScript::create_from_bytecode_cache(url_string, source_code, settings_object, response_url, bytecode_cache).release_value_but_fixme_should_propagate_errors();
if (module_script && module_script->parse_error().is_null()) {
settings_object.module_map().set(url, module_type_string, { ModuleMap::EntryType::ModuleScript, module_script });

View file

@ -5,6 +5,10 @@
*/
#include <AK/ScopeGuard.h>
#include <LibCore/File.h>
#include <LibCore/ImmutableBytes.h>
#include <LibCore/StandardPaths.h>
#include <LibCore/System.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/ModuleRequest.h>
@ -356,6 +360,37 @@ TEST_CASE(bytecode_cache_materializes_function_executables_lazily)
EXPECT(!shared_data.m_cached_bytecode_executable);
}
TEST_CASE(bytecode_cache_materializes_from_mapped_blob)
{
auto vm = JS::VM::create();
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
auto& realm = *root_execution_context->realm;
auto test_data = create_bytecode_cache_blob("let f = function mapped() { return 1; }; f();"_string);
auto path = ByteString::formatted("{}/bytecode-cache-test-{}.blob", Core::StandardPaths::tempfile_directory(), Core::System::getpid());
ScopeGuard remove_file = [&] {
(void)Core::System::unlink(path);
};
{
auto file = TRY_OR_FAIL(Core::File::open(path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
TRY_OR_FAIL(file->write_until_depleted(test_data.blob.bytes()));
}
auto file = TRY_OR_FAIL(Core::File::open(path, Core::File::OpenMode::Read));
auto mapped_blob = TRY_OR_FAIL(Core::ImmutableBytes::map_from_fd_range_and_close(file->leak_fd(), path, 0, test_data.blob.size()));
EXPECT(mapped_blob.is_file_backed());
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(mapped_blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm);
VERIFY(!script_or_error.is_error());
auto result = vm->run(script_or_error.release_value());
VERIFY(!result.is_throw_completion());
}
TEST_CASE(fresh_precompiled_function_executables_materialize_lazily)
{
auto vm = JS::VM::create();