LibWeb+LibJS: Cache decoded JS bytecode sidecars

Add a ref-counted decoded bytecode cache backing so bytecode cache
materialization can create fresh script or module records from a shared
decoded sidecar without passing around one-shot raw blob ownership.

Keep that backing in ExecutableBacking for records materialized from
bytecode cache sidecars, so the immutable decoded data stays alive for
as long as the installed record needs it.

Cover the shared backing path with a bytecode-cache test that
materializes and runs two scripts from one decoded backing.
This commit is contained in:
Andreas Kling 2026-05-28 19:09:12 +02:00 committed by Andreas Kling
parent 72720bc229
commit 6ecfcd3e68
17 changed files with 385 additions and 298 deletions

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Span.h>
#include <AK/Types.h>
#include <LibCore/ImmutableBytes.h>
#include <LibJS/Export.h>
namespace JS::FFI {
struct DecodedBytecodeCacheBlob;
}
namespace JS::RustIntegration {
enum class ProgramType : u8 {
Script = 0,
Module = 1,
};
class JS_API DecodedBytecodeCache final : public RefCounted<DecodedBytecodeCache> {
public:
static RefPtr<DecodedBytecodeCache> create(Core::ImmutableBytes, ProgramType, ReadonlyBytes source_hash);
static NonnullRefPtr<DecodedBytecodeCache> create(FFI::DecodedBytecodeCacheBlob*);
~DecodedBytecodeCache();
FFI::DecodedBytecodeCacheBlob* create_materialization_handle() const;
private:
explicit DecodedBytecodeCache(FFI::DecodedBytecodeCacheBlob*);
FFI::DecodedBytecodeCacheBlob* m_blob { nullptr };
};
}

View file

@ -7,6 +7,8 @@
#pragma once
#include <AK/Assertions.h>
#include <AK/NonnullRefPtr.h>
#include <LibJS/DecodedBytecodeCache.h>
namespace JS {
@ -41,9 +43,11 @@ private:
return ExecutableBacking(State::HeapBytecode);
}
static ExecutableBacking mapped_bytecode_cache()
static ExecutableBacking mapped_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache)
{
return ExecutableBacking(State::MappedBytecodeCache);
auto backing = ExecutableBacking(State::MappedBytecodeCache);
backing.m_bytecode_cache = move(bytecode_cache);
return backing;
}
public:
@ -118,13 +122,15 @@ private:
VERIFY_NOT_REACHED();
}
void finish_bytecode_cache_install()
void finish_bytecode_cache_install(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache)
{
VERIFY(!is_mapped_bytecode_cache());
m_state = State::MappedBytecodeCache;
m_bytecode_cache = move(bytecode_cache);
}
State m_state { State::Source };
RefPtr<RustIntegration::DecodedBytecodeCache> m_bytecode_cache;
};
}

View file

@ -58,6 +58,7 @@ pub struct PendingSharedFunctionData {
}
/// Metadata computed from scope analysis for a SharedFunctionInstanceData.
#[derive(Clone)]
pub struct FunctionSfdMetadata {
pub uses_this: bool,
pub this_value_needs_environment_resolution: bool,

View file

@ -536,6 +536,7 @@ impl ByteVector {
}
}
#[derive(Clone)]
enum DecodedBytecodeBytes {
Foreign {
blob: Rc<ForeignBytecodeCacheBlob>,
@ -564,10 +565,9 @@ impl DecodedBytecodeBytes {
fn owner_for_ffi(&self) -> *mut c_void {
match self {
// The C++ executable retains the immutable blob so the hot
// instruction stream can point directly into the cache file. Clone
// the small owner wrapper here; the original decoded blob still
// owns its copy until materialization finishes.
// The C++ executable adopts this as its bytecode_owner, so the callback
// returns the exact owner type rust_create_executable() expects. The
// original decoded blob keeps its own owner until materialization finishes.
Self::Foreign { blob, .. } => unsafe { (blob.clone_owner)(blob.owner.cast_const()) },
#[cfg(test)]
Self::Owned(_) => std::ptr::null_mut(),
@ -762,7 +762,7 @@ impl DecodedCacheBlob {
}
pub(crate) unsafe fn materialize_script(
self,
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_list_ptr: *mut c_void,
@ -770,24 +770,17 @@ impl DecodedCacheBlob {
) -> *mut c_void {
unsafe {
self.verify_has_been_validated_for_materialization();
let Self {
program_type,
is_strict_mode,
metadata,
program,
..
} = self;
if program_type != ast::ProgramType::Script {
if self.program_type != ast::ProgramType::Script {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Script {
metadata,
declaration_functions,
} = metadata
} = &self.metadata
else {
return std::ptr::null_mut();
};
let ProgramKind::ScriptOrModule = program.kind else {
let ProgramKind::ScriptOrModule = self.program.kind else {
return std::ptr::null_mut();
};
if declaration_functions.len() != metadata.function_names.len() {
@ -799,7 +792,7 @@ impl DecodedCacheBlob {
if !materialize_script_declaration_metadata(
metadata,
declaration_functions,
is_strict_mode,
self.is_strict_mode,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
@ -808,7 +801,7 @@ impl DecodedCacheBlob {
return std::ptr::null_mut();
}
materialize_executable(
program.executable,
&self.program.executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
@ -818,7 +811,7 @@ impl DecodedCacheBlob {
}
pub(crate) unsafe fn materialize_module(
self,
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_list_ptr: *mut c_void,
@ -832,20 +825,13 @@ impl DecodedCacheBlob {
return std::ptr::null_mut();
}
let cb = &*callbacks;
let Self {
program_type,
has_top_level_await,
metadata,
program,
..
} = self;
if program_type != ast::ProgramType::Module {
if self.program_type != ast::ProgramType::Module {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Module {
metadata,
declaration_functions,
} = metadata
} = &self.metadata
else {
return std::ptr::null_mut();
};
@ -855,7 +841,7 @@ impl DecodedCacheBlob {
let shared_function_data_owner =
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr);
(cb.set_has_top_level_await)(module_context, has_top_level_await);
(cb.set_has_top_level_await)(module_context, self.has_top_level_await);
if !materialize_module_declaration_metadata(
metadata,
declaration_functions,
@ -868,10 +854,10 @@ impl DecodedCacheBlob {
return std::ptr::null_mut();
}
match program.kind {
match self.program.kind {
ProgramKind::AsyncModule => {
let exec_ptr = materialize_executable(
program.executable,
&self.program.executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
@ -887,7 +873,7 @@ impl DecodedCacheBlob {
*tla_executable_out = std::ptr::null_mut();
}
materialize_executable(
program.executable,
&self.program.executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
@ -899,7 +885,7 @@ impl DecodedCacheBlob {
}
pub(crate) unsafe fn install_script(
self,
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
existing_executable_ptr: *const c_void,
@ -911,24 +897,17 @@ impl DecodedCacheBlob {
return std::ptr::null_mut();
}
let Self {
program_type,
is_strict_mode,
metadata,
program,
..
} = self;
if program_type != ast::ProgramType::Script {
if self.program_type != ast::ProgramType::Script {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Script {
metadata,
declaration_functions,
} = metadata
} = &self.metadata
else {
return std::ptr::null_mut();
};
let ProgramKind::ScriptOrModule = program.kind else {
let ProgramKind::ScriptOrModule = self.program.kind else {
return std::ptr::null_mut();
};
@ -938,7 +917,7 @@ impl DecodedCacheBlob {
declaration_functions,
metadata.function_names.len(),
&mut existing_shared_function_data,
is_strict_mode,
self.is_strict_mode,
vm_ptr,
source_code_ptr,
&mut pending_function_installs,
@ -946,7 +925,7 @@ impl DecodedCacheBlob {
return std::ptr::null_mut();
}
let executable_ptr = materialize_executable_for_install(
program.executable,
&self.program.executable,
Some(&mut existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
@ -968,7 +947,7 @@ impl DecodedCacheBlob {
}
pub(crate) unsafe fn install_module(
self,
&self,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
existing_executable_ptr: *const c_void,
@ -978,20 +957,13 @@ impl DecodedCacheBlob {
) -> *mut c_void {
unsafe {
self.verify_has_been_validated_for_materialization();
let Self {
program_type,
has_top_level_await,
metadata,
program,
..
} = self;
if program_type != ast::ProgramType::Module {
if self.program_type != ast::ProgramType::Module {
return std::ptr::null_mut();
}
let DecodedDeclarationMetadata::Module {
metadata,
declaration_functions,
} = metadata
} = &self.metadata
else {
return std::ptr::null_mut();
};
@ -1009,13 +981,13 @@ impl DecodedCacheBlob {
) {
return std::ptr::null_mut();
}
match program.kind {
match self.program.kind {
ProgramKind::AsyncModule => {
if !has_top_level_await || existing_tla_sfd_ptr.is_null() {
if !self.has_top_level_await || existing_tla_sfd_ptr.is_null() {
return std::ptr::null_mut();
}
let exec_ptr = materialize_executable_for_install(
program.executable,
&self.program.executable,
Some(&mut existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
@ -1038,14 +1010,14 @@ impl DecodedCacheBlob {
std::ptr::null_mut()
}
ProgramKind::ScriptOrModule => {
if has_top_level_await || existing_executable_ptr.is_null() {
if self.has_top_level_await || existing_executable_ptr.is_null() {
return std::ptr::null_mut();
}
if !tla_executable_out.is_null() {
*tla_executable_out = std::ptr::null_mut();
}
let executable_ptr = materialize_executable_for_install(
program.executable,
&self.program.executable,
Some(&mut existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
@ -1070,7 +1042,7 @@ impl DecodedCacheBlob {
}
unsafe fn prepare_declaration_function_installs(
declaration_functions: Vec<DecodedFunctionRecord>,
declaration_functions: &[DecodedFunctionRecord],
expected_function_count: usize,
existing_shared_function_data: &mut ExistingSharedFunctionData<'_>,
outer_strict: bool,
@ -1104,8 +1076,8 @@ unsafe fn prepare_declaration_function_installs(
}
unsafe fn materialize_script_declaration_metadata(
metadata: ScriptDeclarationMetadata,
declaration_functions: Vec<DecodedFunctionRecord>,
metadata: &ScriptDeclarationMetadata,
declaration_functions: &[DecodedFunctionRecord],
is_strict_mode: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
@ -1126,7 +1098,7 @@ unsafe fn materialize_script_declaration_metadata(
for name in &metadata.var_names {
script_gdi_push_var_name(gdi_context, name.as_ptr(), name.len());
}
for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) {
for (function, name) in declaration_functions.iter().zip(metadata.function_names.iter()) {
let sfd_ptr = materialize_function(
function,
is_strict_mode,
@ -1160,8 +1132,8 @@ unsafe fn materialize_script_declaration_metadata(
}
unsafe fn materialize_module_declaration_metadata(
metadata: ModuleDeclarationMetadata,
declaration_functions: Vec<DecodedFunctionRecord>,
metadata: &ModuleDeclarationMetadata,
declaration_functions: &[DecodedFunctionRecord],
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
@ -1217,7 +1189,7 @@ unsafe fn materialize_module_declaration_metadata(
for name in &metadata.var_declared_names {
(cb.push_var_name)(module_context, name.as_ptr(), name.len());
}
for (function, name) in declaration_functions.into_iter().zip(metadata.function_names.iter()) {
for (function, name) in declaration_functions.iter().zip(metadata.function_names.iter()) {
let sfd_ptr = materialize_function(
function,
true,
@ -1396,7 +1368,7 @@ impl PendingFunctionInstall {
}
unsafe fn materialize_function(
mut function: DecodedFunctionRecord,
function: &DecodedFunctionRecord,
outer_strict: bool,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
@ -1458,8 +1430,8 @@ unsafe fn materialize_function(
crate::bytecode::ffi::rust_sfd_set_class_field_initializer_name(sfd_ptr, name, name_len, *is_private);
}
function.precompiled.mark_as_validated(validation);
let cached_executable_ptr = Box::into_raw(Box::new(function.precompiled)) as *mut c_void;
let cached_executable_ptr =
Box::into_raw(Box::new(function.precompiled.validated_copy(validation))) as *mut c_void;
crate::bytecode::ffi::rust_sfd_set_cached_bytecode_executable(
sfd_ptr,
cached_executable_ptr,
@ -1477,7 +1449,7 @@ unsafe fn materialize_function(
}
unsafe fn prepare_function_install(
mut function: DecodedFunctionRecord,
function: &DecodedFunctionRecord,
outer_strict: bool,
existing_shared_function_data: &mut ExistingSharedFunctionData<'_>,
vm_ptr: *mut c_void,
@ -1527,21 +1499,21 @@ unsafe fn prepare_function_install(
}
if crate::bytecode::ffi::rust_sfd_executable(existing_sfd_ptr).is_null() {
function.precompiled.mark_as_validated(validation);
pending_function_installs.push(PendingFunctionInstall {
existing_sfd_ptr,
replacement: PendingFunctionInstallReplacement::CachedBytecode(function.precompiled),
metadata: function.metadata,
replacement: PendingFunctionInstallReplacement::CachedBytecode(
function.precompiled.validated_copy(validation),
),
metadata: function.metadata.clone(),
});
return existing_sfd_ptr;
}
function.precompiled.mark_as_validated(validation);
let Some(executable) = function.precompiled.decode_executable() else {
let Some(executable) = function.precompiled.decode_validated_executable(validation) else {
return std::ptr::null_mut();
};
let executable_ptr = materialize_executable_for_install(
executable,
&executable,
Some(existing_shared_function_data),
crate::bytecode::ffi::SharedFunctionDataOwner::None,
vm_ptr,
@ -1556,7 +1528,7 @@ unsafe fn prepare_function_install(
pending_function_installs.push(PendingFunctionInstall {
existing_sfd_ptr,
replacement: PendingFunctionInstallReplacement::Executable(executable_ptr),
metadata: function.metadata,
metadata: function.metadata.clone(),
});
existing_sfd_ptr
@ -1583,7 +1555,7 @@ pub(crate) unsafe fn materialize_cached_function(
crate::bytecode::ffi::SharedFunctionDataOwner::List(shared_function_data_list_ptr)
};
materialize_executable(
executable,
&executable,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
@ -1603,7 +1575,7 @@ pub(crate) unsafe fn free_cached_function(cached_executable_ptr: *mut c_void) {
}
unsafe fn materialize_executable(
executable: DecodedExecutableRecord,
executable: &DecodedExecutableRecord,
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
@ -1624,7 +1596,7 @@ unsafe fn materialize_executable(
}
unsafe fn materialize_executable_for_install(
executable: DecodedExecutableRecord,
executable: &DecodedExecutableRecord,
mut existing_shared_function_data: Option<&mut ExistingSharedFunctionData<'_>>,
shared_function_data_owner: crate::bytecode::ffi::SharedFunctionDataOwner,
vm_ptr: *mut c_void,
@ -1633,41 +1605,22 @@ unsafe fn materialize_executable_for_install(
validation: CachedBytecodeValidation,
) -> *mut c_void {
unsafe {
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 Some(identifier_table) = identifier_table.into_values() else {
let Some(identifier_table) = executable.identifier_table.values() else {
return std::ptr::null_mut();
};
let (_identifier_table_storage, identifier_table_slices) = utf16_slice_storage(identifier_table.iter());
let Some(property_key_table) = property_key_table.into_values() else {
let Some(property_key_table) = executable.property_key_table.values() else {
return std::ptr::null_mut();
};
let (_property_key_table_storage, property_key_table_slices) = utf16_slice_storage(property_key_table.iter());
let Some(string_table) = string_table.into_values() else {
let Some(string_table) = executable.string_table.values() else {
return std::ptr::null_mut();
};
let (_string_table_storage, string_table_slices) = utf16_slice_storage(string_table.iter());
let Some((constants_count, constants_bytes)) = constants.into_ffi_data() else {
let Some((constants_count, constants_bytes)) = executable.constants.ffi_data() else {
return std::ptr::null_mut();
};
let Some(local_variables) = local_variables.into_values() else {
let Some(local_variables) = executable.local_variables.values() else {
return std::ptr::null_mut();
};
let (_local_variable_storage, local_variable_name_slices) =
@ -1677,15 +1630,15 @@ unsafe fn materialize_executable_for_install(
&local_variable.name
}));
let Some(shared_functions) = shared_functions.into_values() else {
let Some(shared_functions) = executable.shared_functions.values() else {
return std::ptr::null_mut();
};
let mut sfd_ptrs = Vec::with_capacity(shared_functions.len());
for function in shared_functions {
for function in &shared_functions {
let sfd_ptr = if let Some(registry) = existing_shared_function_data.as_deref_mut() {
prepare_function_install(
function,
strict,
executable.strict,
registry,
vm_ptr,
source_code_ptr,
@ -1695,7 +1648,7 @@ unsafe fn materialize_executable_for_install(
} else {
materialize_function(
function,
strict,
executable.strict,
vm_ptr,
source_code_ptr,
shared_function_data_owner,
@ -1708,11 +1661,11 @@ unsafe fn materialize_executable_for_install(
return std::ptr::null_mut();
}
let Some(class_blueprints) = class_blueprints.into_values() else {
let Some(class_blueprints) = executable.class_blueprints.values() else {
return std::ptr::null_mut();
};
let class_blueprints: Vec<PendingClassBlueprint> =
class_blueprints.into_iter().map(PendingClassBlueprint::from).collect();
class_blueprints.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))
@ -1720,32 +1673,32 @@ unsafe fn materialize_executable_for_install(
if bp_ptrs.iter().any(|ptr| ptr.is_null()) {
return std::ptr::null_mut();
}
let Some(exception_handlers) = exception_handlers.into_values() else {
let Some(exception_handlers) = executable.exception_handlers.values() else {
return std::ptr::null_mut();
};
let Some(source_map) = source_map.into_values() else {
let Some(source_map) = executable.source_map.values() else {
return std::ptr::null_mut();
};
crate::bytecode::ffi::create_executable_from_slices(
crate::bytecode::ffi::ExecutableParts {
bytecode: bytecode.as_slice(),
bytecode_owner: bytecode.owner_for_ffi(),
bytecode: executable.bytecode.as_slice(),
bytecode_owner: executable.bytecode.owner_for_ffi(),
exception_handlers: &exception_handlers,
source_map: &source_map,
basic_block_start_offsets: &[],
number_of_registers,
number_of_arguments,
number_of_registers: executable.number_of_registers,
number_of_arguments: executable.number_of_arguments,
},
crate::bytecode::ffi::ExecutableMetadata {
property_lookup_cache_count: cache_counters.property_lookup_cache_count,
global_variable_cache_count: cache_counters.global_variable_cache_count,
environment_coordinate_cache_count: cache_counters.environment_coordinate_cache_count,
template_object_cache_count: cache_counters.template_object_cache_count,
object_shape_cache_count: cache_counters.object_shape_cache_count,
object_property_iterator_cache_count: cache_counters.object_property_iterator_cache_count,
is_strict: strict,
length_identifier,
property_lookup_cache_count: executable.cache_counters.property_lookup_cache_count,
global_variable_cache_count: executable.cache_counters.global_variable_cache_count,
environment_coordinate_cache_count: executable.cache_counters.environment_coordinate_cache_count,
template_object_cache_count: executable.cache_counters.template_object_cache_count,
object_shape_cache_count: executable.cache_counters.object_shape_cache_count,
object_property_iterator_cache_count: executable.cache_counters.object_property_iterator_cache_count,
is_strict: executable.strict,
length_identifier: executable.length_identifier,
},
crate::bytecode::ffi::ExecutableSlices {
identifier_table: &identifier_table_slices,
@ -2966,13 +2919,20 @@ struct DecodedCachedExecutableRecord {
impl DecodedCachedExecutableRecord {
fn decode_executable(&self) -> Option<DecodedExecutableRecord> {
self.verify_has_been_validated_for_materialization();
self.decode_validated_executable(CachedBytecodeValidation::Validated)
}
fn decode_validated_executable(&self, _: CachedBytecodeValidation) -> Option<DecodedExecutableRecord> {
let mut decoder = self.bytes.decoder();
let executable = ExecutableRecord::decode(&mut decoder)?;
decoder.is_empty().then_some(executable)
}
fn mark_as_validated(&mut self, _: CachedBytecodeValidation) {
self.has_been_validated_for_materialization = true;
fn validated_copy(&self, _: CachedBytecodeValidation) -> Self {
Self {
bytes: self.bytes.clone(),
has_been_validated_for_materialization: true,
}
}
fn verify_has_been_validated_for_materialization(&self) {
@ -3068,7 +3028,7 @@ impl DecodedUtf16Table {
self.sequence.len()
}
fn into_values(self) -> Option<Vec<DecodedUtf16String>> {
fn values(&self) -> Option<Vec<DecodedUtf16String>> {
let mut decoder = self.sequence.decoder();
let mut values = Vec::with_capacity(self.sequence.len());
for _ in 0..self.sequence.len() {
@ -3117,7 +3077,7 @@ impl DecodedConstantTable {
self.count
}
fn into_ffi_data(self) -> Option<(usize, DecodedBytecodeBytes)> {
fn ffi_data(&self) -> Option<(usize, &DecodedBytecodeBytes)> {
{
let mut decoder = Decoder::new(self.bytes.as_slice(), None);
for _ in 0..self.count {
@ -3127,7 +3087,7 @@ impl DecodedConstantTable {
return None;
}
}
Some((self.count, self.bytes))
Some((self.count, &self.bytes))
}
}
@ -3268,10 +3228,6 @@ impl DecodedExceptionHandlerTable {
}
decoder.is_empty().then_some(values)
}
fn into_values(self) -> Option<Vec<ExceptionHandler>> {
self.values()
}
}
struct SourceMapTable<'a>(&'a AssembledBytecode);
@ -3315,10 +3271,6 @@ impl DecodedSourceMapTable {
}
decoder.is_empty().then_some(values)
}
fn into_values(self) -> Option<Vec<SourceMapEntry>> {
self.values()
}
}
struct LocalVariableTable<'a>(&'a Generator);
@ -3352,7 +3304,7 @@ impl DecodedLocalVariableTable {
self.sequence.len()
}
fn into_values(self) -> Option<Vec<DecodedLocalVariable>> {
fn values(&self) -> Option<Vec<DecodedLocalVariable>> {
let mut decoder = self.sequence.decoder();
let mut values = Vec::with_capacity(self.sequence.len());
for _ in 0..self.sequence.len() {
@ -3412,7 +3364,7 @@ impl DecodedFunctionTable {
let _ = self.sequence.len();
}
fn into_values(self) -> Option<Vec<DecodedFunctionRecord>> {
fn values(&self) -> Option<Vec<DecodedFunctionRecord>> {
let mut decoder = self.sequence.decoder();
let mut values = Vec::with_capacity(self.sequence.len());
for _ in 0..self.sequence.len() {
@ -3751,7 +3703,7 @@ impl DecodedClassBlueprintTable {
let _ = self.sequence.len();
}
fn into_values(self) -> Option<Vec<DecodedClassBlueprintRecord>> {
fn values(&self) -> Option<Vec<DecodedClassBlueprintRecord>> {
let mut decoder = self.sequence.decoder();
let mut values = Vec::with_capacity(self.sequence.len());
for _ in 0..self.sequence.len() {
@ -3835,16 +3787,16 @@ impl DecodedClassBlueprintRecord {
}
}
impl From<DecodedClassBlueprintRecord> for PendingClassBlueprint {
fn from(record: DecodedClassBlueprintRecord) -> Self {
impl From<&DecodedClassBlueprintRecord> for PendingClassBlueprint {
fn from(record: &DecodedClassBlueprintRecord) -> Self {
Self {
name: record.name.map(|name| name.to_utf16_string()),
name: record.name.as_ref().map(DecodedUtf16String::to_utf16_string),
source_text_offset: record.source_text_offset,
source_text_length: record.source_text_length,
constructor_sfd_index: record.constructor_sfd_index,
has_super_class: record.has_super_class,
has_name: record.has_name,
elements: record.elements.into_iter().map(PendingClassElement::from).collect(),
elements: record.elements.iter().map(PendingClassElement::from).collect(),
}
}
}
@ -3915,18 +3867,24 @@ impl DecodedClassElementRecord {
}
}
impl From<DecodedClassElementRecord> for PendingClassElement {
fn from(record: DecodedClassElementRecord) -> Self {
impl From<&DecodedClassElementRecord> for PendingClassElement {
fn from(record: &DecodedClassElementRecord) -> Self {
Self {
kind: record.kind,
is_static: record.is_static,
is_private: record.is_private,
private_identifier: record.private_identifier.map(|identifier| identifier.to_utf16_string()),
private_identifier: record
.private_identifier
.as_ref()
.map(DecodedUtf16String::to_utf16_string),
shared_function_data_index: record.shared_function_data_index,
has_initializer: record.has_initializer,
literal_value_kind: record.literal_value_kind,
literal_value_number: record.literal_value_number,
literal_value_string: record.literal_value_string.map(|value| value.to_utf16_string()),
literal_value_string: record
.literal_value_string
.as_ref()
.map(DecodedUtf16String::to_utf16_string),
}
}
}

View file

@ -101,10 +101,12 @@ use bytecode::generator::PendingSharedFunctionData;
use parser::ParseError;
use parser::Parser;
use parser::ProgramType;
use std::cell::RefCell;
use std::collections::HashSet;
use std::ffi::c_void;
use std::panic::AssertUnwindSafe;
use std::panic::catch_unwind;
use std::rc::Rc;
// Compile-time assertion: `ParsedProgram` travels between the parse worker
// thread and the main thread, so it must be `Send`. After the StringId and
@ -151,7 +153,12 @@ pub struct BytecodeCacheBlob {
}
pub struct DecodedBytecodeCacheBlob {
_blob: bytecode_cache::DecodedCacheBlob,
_blob: Rc<RefCell<bytecode_cache::DecodedCacheBlob>>,
}
fn validate_decoded_blob(blob: &DecodedBytecodeCacheBlob, source_len: usize) -> bool {
let mut decoded_blob = blob._blob.borrow_mut();
decoded_blob.validate_for_materialization(source_len).is_ok()
}
enum CompiledProgramBytecode {
@ -860,7 +867,7 @@ pub unsafe extern "C" fn rust_free_bytecode_cache_blob(data: *mut u8, length: us
/// # Safety
/// - `data` must point to `length` readable bytes.
/// - `owner` must keep `data` alive until `free_owner` is called.
/// - `clone_owner` must return a new owner that keeps the same bytes alive.
/// - `clone_owner` must return a new `bytecode_owner` for `rust_create_executable()`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_decode_bytecode_cache_blob_with_owner(
data: *const u8,
@ -904,7 +911,9 @@ pub unsafe extern "C" fn rust_decode_bytecode_cache_blob_with_owner(
) else {
return std::ptr::null_mut();
};
Box::into_raw(Box::new(DecodedBytecodeCacheBlob { _blob: blob }))
Box::into_raw(Box::new(DecodedBytecodeCacheBlob {
_blob: Rc::new(RefCell::new(blob)),
}))
})
}
}
@ -934,7 +943,32 @@ pub unsafe extern "C" fn rust_validate_decoded_bytecode_cache_blob(
if blob.is_null() {
return false;
}
(*blob)._blob.validate_for_materialization(source_len).is_ok()
(*blob)
._blob
.borrow_mut()
.validate_for_materialization(source_len)
.is_ok()
})
}
}
/// Add a reference to a decoded bytecode cache blob.
///
/// # Safety
/// `blob` must be a valid pointer from `rust_decode_bytecode_cache_blob_with_owner()`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_ref_decoded_bytecode_cache_blob(
blob: *const DecodedBytecodeCacheBlob,
) -> *mut DecodedBytecodeCacheBlob {
unsafe {
abort_on_panic(|| {
if blob.is_null() {
return std::ptr::null_mut();
}
Box::into_raw(Box::new(DecodedBytecodeCacheBlob {
_blob: Rc::clone(&(*blob)._blob),
}))
})
}
}
@ -960,11 +994,12 @@ pub unsafe extern "C" fn rust_materialize_bytecode_cache_script(
if blob.is_null() {
return std::ptr::null_mut();
}
let mut blob = Box::from_raw(blob);
if blob._blob.validate_for_materialization(source_len).is_err() {
let blob = Box::from_raw(blob);
if !validate_decoded_blob(&blob, source_len) {
return std::ptr::null_mut();
}
blob._blob
.borrow()
.materialize_script(vm_ptr, source_code_ptr, shared_function_data_list_ptr, gdi_context)
})
}
@ -994,11 +1029,11 @@ pub unsafe extern "C" fn rust_materialize_bytecode_cache_module(
if blob.is_null() {
return std::ptr::null_mut();
}
let mut blob = Box::from_raw(blob);
if blob._blob.validate_for_materialization(source_len).is_err() {
let blob = Box::from_raw(blob);
if !validate_decoded_blob(&blob, source_len) {
return std::ptr::null_mut();
}
blob._blob.materialize_module(
blob._blob.borrow().materialize_module(
vm_ptr,
source_code_ptr,
shared_function_data_list_ptr,
@ -1036,7 +1071,7 @@ pub unsafe extern "C" fn rust_install_bytecode_cache_script(
if blob.is_null() {
return std::ptr::null_mut();
}
let mut blob = Box::from_raw(blob);
let blob = Box::from_raw(blob);
let existing_declaration_functions = if existing_declaration_function_count == 0 {
&[]
} else {
@ -1045,10 +1080,10 @@ pub unsafe extern "C" fn rust_install_bytecode_cache_script(
}
std::slice::from_raw_parts(existing_declaration_function_ptrs, existing_declaration_function_count)
};
if blob._blob.validate_for_materialization(source_len).is_err() {
if !validate_decoded_blob(&blob, source_len) {
return std::ptr::null_mut();
}
blob._blob.install_script(
blob._blob.borrow().install_script(
vm_ptr,
source_code_ptr,
existing_executable_ptr,
@ -1087,7 +1122,7 @@ pub unsafe extern "C" fn rust_install_bytecode_cache_module(
if blob.is_null() {
return std::ptr::null_mut();
}
let mut blob = Box::from_raw(blob);
let blob = Box::from_raw(blob);
let existing_declaration_functions = if existing_declaration_function_count == 0 {
&[]
} else {
@ -1096,10 +1131,10 @@ pub unsafe extern "C" fn rust_install_bytecode_cache_module(
}
std::slice::from_raw_parts(existing_declaration_function_ptrs, existing_declaration_function_count)
};
if blob._blob.validate_for_materialization(source_len).is_err() {
if !validate_decoded_blob(&blob, source_len) {
return std::ptr::null_mut();
}
blob._blob.install_module(
blob._blob.borrow().install_module(
vm_ptr,
source_code_ptr,
existing_executable_ptr,

View file

@ -441,22 +441,22 @@ static void free_bytecode_cache_blob_owner(void* owner)
}
}
static void* clone_bytecode_cache_blob_owner(void const* owner)
static void* clone_bytecode_cache_bytecode_owner(void const* owner)
{
auto const& existing_owner = *static_cast<BytecodeCacheBlobOwner const*>(owner);
return new Core::ImmutableBytes(existing_owner.bytes);
return new Core::ImmutableBytes { existing_owner.bytes };
}
DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes bytes, ProgramType expected_type, ReadonlyBytes source_hash)
static DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes bytes, ProgramType expected_type, ReadonlyBytes source_hash)
{
auto* owner = new BytecodeCacheBlobOwner { move(bytes) };
return rust_decode_bytecode_cache_blob_with_owner(owner->bytes.bytes().data(), owner->bytes.bytes().size(), static_cast<u8>(expected_type), source_hash.data(), source_hash.size(), owner, clone_bytecode_cache_blob_owner, free_bytecode_cache_blob_owner);
return rust_decode_bytecode_cache_blob_with_owner(owner->bytes.bytes().data(), owner->bytes.bytes().size(), static_cast<u8>(expected_type), source_hash.data(), source_hash.size(), owner, clone_bytecode_cache_bytecode_owner, free_bytecode_cache_blob_owner);
}
DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes bytes, ProgramType expected_type, ReadonlyBytes source_hash, Core::EventLoop& event_loop)
{
auto* owner = new BytecodeCacheBlobOwner { move(bytes), &event_loop };
return rust_decode_bytecode_cache_blob_with_owner(owner->bytes.bytes().data(), owner->bytes.bytes().size(), static_cast<u8>(expected_type), source_hash.data(), source_hash.size(), owner, clone_bytecode_cache_blob_owner, free_bytecode_cache_blob_owner);
return rust_decode_bytecode_cache_blob_with_owner(owner->bytes.bytes().data(), owner->bytes.bytes().size(), static_cast<u8>(expected_type), source_hash.data(), source_hash.size(), owner, clone_bytecode_cache_bytecode_owner, free_bytecode_cache_blob_owner);
}
bool validate_decoded_bytecode_cache_blob(DecodedBytecodeCacheBlob* blob, size_t source_length)
@ -469,6 +469,35 @@ void free_decoded_bytecode_cache_blob(DecodedBytecodeCacheBlob* blob)
rust_free_decoded_bytecode_cache_blob(blob);
}
DecodedBytecodeCache::DecodedBytecodeCache(DecodedBytecodeCacheBlob* blob)
: m_blob(blob)
{
VERIFY(m_blob);
}
DecodedBytecodeCache::~DecodedBytecodeCache()
{
rust_free_decoded_bytecode_cache_blob(m_blob);
}
RefPtr<DecodedBytecodeCache> DecodedBytecodeCache::create(Core::ImmutableBytes bytes, ProgramType expected_type, ReadonlyBytes source_hash)
{
auto* blob = decode_bytecode_cache_blob(move(bytes), expected_type, source_hash);
if (!blob)
return {};
return create(blob);
}
NonnullRefPtr<DecodedBytecodeCache> DecodedBytecodeCache::create(DecodedBytecodeCacheBlob* blob)
{
return adopt_ref(*new DecodedBytecodeCache(blob));
}
DecodedBytecodeCacheBlob* DecodedBytecodeCache::create_materialization_handle() const
{
return rust_ref_decoded_bytecode_cache_blob(m_blob);
}
Optional<Result<ScriptResult, Vector<ParserError>>> compile_parsed_script(ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
{
if (!parsed)
@ -514,10 +543,10 @@ Optional<Result<ScriptResult, Vector<ParserError>>> materialize_compiled_script(
return builder.result;
}
Optional<Result<ScriptResult, Vector<ParserError>>> materialize_bytecode_cache_script(DecodedBytecodeCacheBlob* blob, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
Optional<Result<ScriptResult, Vector<ParserError>>> materialize_bytecode_cache_script(DecodedBytecodeCache& bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
{
if (!blob)
return {};
auto* blob = bytecode_cache.create_materialization_handle();
VERIFY(blob);
GC::DeferGC defer_gc(realm.vm().heap());
TemporaryChange skip_cache_executable_validation { s_skip_bytecode_validation_for_prevalidated_cache, true };
@ -691,10 +720,10 @@ Optional<Result<ModuleResult, Vector<ParserError>>> materialize_compiled_module(
return builder.result;
}
Optional<Result<ModuleResult, Vector<ParserError>>> materialize_bytecode_cache_module(DecodedBytecodeCacheBlob* blob, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
Optional<Result<ModuleResult, Vector<ParserError>>> materialize_bytecode_cache_module(DecodedBytecodeCache& bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
{
if (!blob)
return {};
auto* blob = bytecode_cache.create_materialization_handle();
VERIFY(blob);
GC::DeferGC defer_gc(realm.vm().heap());
TemporaryChange skip_cache_executable_validation { s_skip_bytecode_validation_for_prevalidated_cache, true };
@ -742,10 +771,10 @@ Optional<Result<ModuleResult, Vector<ParserError>>> materialize_bytecode_cache_m
return builder.result;
}
GC::Ptr<Bytecode::Executable> try_install_bytecode_cache_script(DecodedBytecodeCacheBlob* blob, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data)
GC::Ptr<Bytecode::Executable> try_install_bytecode_cache_script(DecodedBytecodeCache& bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data)
{
if (!blob)
return {};
auto* blob = bytecode_cache.create_materialization_handle();
VERIFY(blob);
Vector<void*> existing_shared_function_data_ptrs;
existing_shared_function_data_ptrs.ensure_capacity(existing_shared_function_data.size());
@ -767,17 +796,17 @@ GC::Ptr<Bytecode::Executable> try_install_bytecode_cache_script(DecodedBytecodeC
return executable.ptr();
}
GC::Ref<Bytecode::Executable> install_generated_bytecode_cache_script(DecodedBytecodeCacheBlob* blob, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data)
GC::Ref<Bytecode::Executable> install_generated_bytecode_cache_script(DecodedBytecodeCache& bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data)
{
auto executable = try_install_bytecode_cache_script(blob, move(source_code), realm, existing_executable, existing_shared_function_data);
auto executable = try_install_bytecode_cache_script(bytecode_cache, move(source_code), realm, existing_executable, existing_shared_function_data);
VERIFY(executable);
return *executable;
}
Optional<ModuleBytecodeCacheInstallResult> try_install_bytecode_cache_module(DecodedBytecodeCacheBlob* blob, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data)
Optional<ModuleBytecodeCacheInstallResult> try_install_bytecode_cache_module(DecodedBytecodeCache& bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data)
{
if (!blob)
return {};
auto* blob = bytecode_cache.create_materialization_handle();
VERIFY(blob);
Vector<void*> existing_shared_function_data_ptrs;
existing_shared_function_data_ptrs.ensure_capacity(existing_shared_function_data.size());
@ -811,9 +840,9 @@ Optional<ModuleBytecodeCacheInstallResult> try_install_bytecode_cache_module(Dec
return result;
}
ModuleBytecodeCacheInstallResult install_generated_bytecode_cache_module(DecodedBytecodeCacheBlob* blob, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data)
ModuleBytecodeCacheInstallResult install_generated_bytecode_cache_module(DecodedBytecodeCache& bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data)
{
auto result = try_install_bytecode_cache_module(blob, move(source_code), realm, existing_executable, existing_shared_function_data, existing_top_level_await_shared_data);
auto result = try_install_bytecode_cache_module(bytecode_cache, move(source_code), realm, existing_executable, existing_shared_function_data, existing_top_level_await_shared_data);
VERIFY(result.has_value());
return result.release_value();
}

View file

@ -17,6 +17,7 @@
#include <LibCore/ImmutableBytes.h>
#include <LibGC/Ptr.h>
#include <LibGC/Root.h>
#include <LibJS/DecodedBytecodeCache.h>
#include <LibJS/ModuleEntry.h>
#include <LibJS/ParserError.h>
#include <LibJS/Runtime/AbstractOperations.h>
@ -37,11 +38,6 @@ struct DecodedBytecodeCacheBlob;
namespace JS::RustIntegration {
enum class ProgramType : u8 {
Script = 0,
Module = 1,
};
// Result type for compile_script().
// NB: Uses GC::Root to prevent collection while the result is in transit
// between compile_script() and the Script constructor.
@ -114,7 +110,7 @@ JS_API void free_compiled_program(FFI::CompiledProgram*);
JS_API ByteBuffer serialize_compiled_program_for_bytecode_cache(FFI::CompiledProgram const&, ProgramType, ReadonlyBytes source_hash);
// Decode an ImmutableBytes-backed bytecode cache blob into a parser-free cache handle.
JS_API FFI::DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes, ProgramType, ReadonlyBytes source_hash);
// The returned blob can be validated off-thread before main-thread materialization.
JS_API FFI::DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes, ProgramType, ReadonlyBytes source_hash, Core::EventLoop&);
// Validate a decoded bytecode cache blob before materialization. Thread-safe.
@ -123,34 +119,32 @@ JS_API bool validate_decoded_bytecode_cache_blob(FFI::DecodedBytecodeCacheBlob*,
// Free a decoded bytecode cache blob.
JS_API void free_decoded_bytecode_cache_blob(FFI::DecodedBytecodeCacheBlob*);
// Materialize a decoded script bytecode cache blob. Must be called on the main thread.
// Consumes and frees the decoded blob.
JS_API Optional<Result<ScriptResult, Vector<ParserError>>> materialize_bytecode_cache_script(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&);
// Materialize a decoded script bytecode cache. Must be called on the main thread.
JS_API Optional<Result<ScriptResult, Vector<ParserError>>> materialize_bytecode_cache_script(DecodedBytecodeCache&, NonnullRefPtr<SourceCode const> source_code, Realm&);
// Materialize a decoded module bytecode cache blob. Must be called on the main thread.
// Consumes and frees the decoded blob.
JS_API Optional<Result<ModuleResult, Vector<ParserError>>> materialize_bytecode_cache_module(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&);
// Materialize a decoded module bytecode cache. Must be called on the main thread.
JS_API Optional<Result<ModuleResult, Vector<ParserError>>> materialize_bytecode_cache_module(DecodedBytecodeCache&, NonnullRefPtr<SourceCode const> source_code, Realm&);
struct ModuleBytecodeCacheInstallResult {
GC::Root<Bytecode::Executable> executable;
GC::Root<Bytecode::Executable> top_level_await_executable;
};
// Try to install a decoded script bytecode cache blob into an existing script executable tree.
// Must be called on the main thread. Consumes and frees the decoded blob.
JS_API GC::Ptr<Bytecode::Executable> try_install_bytecode_cache_script(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data);
// Try to install a decoded script bytecode cache into an existing script executable tree.
// Must be called on the main thread.
JS_API GC::Ptr<Bytecode::Executable> try_install_bytecode_cache_script(DecodedBytecodeCache&, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data);
// Install a decoded script bytecode cache blob produced by the current process.
// Must be called on the main thread. Consumes and frees the decoded blob.
JS_API GC::Ref<Bytecode::Executable> install_generated_bytecode_cache_script(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data);
// Install a decoded script bytecode cache produced by the current process.
// Must be called on the main thread.
JS_API GC::Ref<Bytecode::Executable> install_generated_bytecode_cache_script(DecodedBytecodeCache&, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable& existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data);
// Try to install a decoded module bytecode cache blob into an existing module executable tree.
// Must be called on the main thread. Consumes and frees the decoded blob.
JS_API Optional<ModuleBytecodeCacheInstallResult> try_install_bytecode_cache_module(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data);
// Try to install a decoded module bytecode cache into an existing module executable tree.
// Must be called on the main thread.
JS_API Optional<ModuleBytecodeCacheInstallResult> try_install_bytecode_cache_module(DecodedBytecodeCache&, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data);
// Install a decoded module bytecode cache blob produced by the current process.
// Must be called on the main thread. Consumes and frees the decoded blob.
JS_API ModuleBytecodeCacheInstallResult install_generated_bytecode_cache_module(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data);
// Install a decoded module bytecode cache produced by the current process.
// Must be called on the main thread.
JS_API ModuleBytecodeCacheInstallResult install_generated_bytecode_cache_module(DecodedBytecodeCache&, NonnullRefPtr<SourceCode const> source_code, Realm&, Bytecode::Executable* existing_executable, ReadonlySpan<SharedFunctionInstanceData*> existing_shared_function_data, SharedFunctionInstanceData* existing_top_level_await_shared_data);
// Compile a previously parsed script. Must be called on the main thread.
// Consumes and frees the Rust ParsedProgram.

View file

@ -54,7 +54,7 @@ Result<GC::Ref<Script>, Vector<ParserError>> Script::create_from_compiled(FFI::C
return realm.heap().allocate<Script>(realm, filename, move(rust_compilation->value()), ExecutableBacking::heap_bytecode(), host_defined);
}
Result<GC::Ref<Script>, Vector<ParserError>> Script::create_from_bytecode_cache(FFI::DecodedBytecodeCacheBlob* bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, HostDefined* host_defined)
Result<GC::Ref<Script>, Vector<ParserError>> Script::create_from_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, HostDefined* host_defined)
{
auto filename = source_code->filename();
auto rust_compilation = RustIntegration::materialize_bytecode_cache_script(bytecode_cache, move(source_code), realm);
@ -62,38 +62,34 @@ Result<GC::Ref<Script>, Vector<ParserError>> Script::create_from_bytecode_cache(
return Vector<ParserError> {};
if (rust_compilation->is_error())
return rust_compilation->release_error();
return realm.heap().allocate<Script>(realm, filename, move(rust_compilation->value()), ExecutableBacking::mapped_bytecode_cache(), host_defined);
return realm.heap().allocate<Script>(realm, filename, move(rust_compilation->value()), ExecutableBacking::mapped_bytecode_cache(move(bytecode_cache)), host_defined);
}
bool Script::try_install_bytecode_cache(FFI::DecodedBytecodeCacheBlob* bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
bool Script::try_install_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
{
if (m_executable_backing.is_mapped_bytecode_cache()) {
RustIntegration::free_decoded_bytecode_cache_blob(bytecode_cache);
if (m_executable_backing.is_mapped_bytecode_cache())
return false;
}
if (!m_executable) {
RustIntegration::free_decoded_bytecode_cache_blob(bytecode_cache);
if (!m_executable)
return false;
}
auto shared_function_data = collect_shared_function_data();
auto executable = RustIntegration::try_install_bytecode_cache_script(bytecode_cache, move(source_code), realm(), *m_executable, shared_function_data);
if (!executable)
return false;
complete_bytecode_cache_install(*executable);
complete_bytecode_cache_install(*executable, move(bytecode_cache));
return true;
}
void Script::install_generated_bytecode_cache(FFI::DecodedBytecodeCacheBlob* bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
void Script::install_generated_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
{
VERIFY(can_install_generated_bytecode_cache());
VERIFY(m_executable);
auto shared_function_data = collect_shared_function_data();
auto executable = RustIntegration::install_generated_bytecode_cache_script(bytecode_cache, move(source_code), realm(), *m_executable, shared_function_data);
complete_bytecode_cache_install(executable);
complete_bytecode_cache_install(executable, move(bytecode_cache));
}
bool Script::can_generate_bytecode_cache() const
@ -129,11 +125,11 @@ Vector<SharedFunctionInstanceData*> Script::collect_shared_function_data()
return shared_function_data;
}
void Script::complete_bytecode_cache_install(GC::Ref<Bytecode::Executable> executable)
void Script::complete_bytecode_cache_install(GC::Ref<Bytecode::Executable> executable, NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache)
{
m_executable = executable;
m_shared_function_data.clear_non_bytecode_cache_compile_inputs();
m_executable_backing.finish_bytecode_cache_install();
m_executable_backing.finish_bytecode_cache_install(move(bytecode_cache));
verify_executable_backing_invariants();
}

View file

@ -32,6 +32,7 @@ struct DecodedBytecodeCacheBlob;
namespace RustIntegration {
class DecodedBytecodeCache;
struct ScriptResult;
}
@ -59,7 +60,7 @@ public:
static Result<GC::Ref<Script>, Vector<ParserError>> parse(StringView source_text, Realm&, StringView filename = {}, HostDefined* = nullptr, size_t line_number_offset = 1);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_parsed(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_compiled(FFI::CompiledProgram* compiled, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_bytecode_cache(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
Realm& realm() { return *m_realm; }
Vector<LoadedModuleRequest>& loaded_modules() { return m_loaded_modules; }
@ -74,8 +75,8 @@ public:
[[nodiscard]] bool can_install_generated_bytecode_cache() const;
void begin_bytecode_cache_generation();
void finish_bytecode_cache_generation_without_install();
bool try_install_bytecode_cache(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code);
void install_generated_bytecode_cache(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code);
bool try_install_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code);
void install_generated_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code);
ThrowCompletionOr<void> global_declaration_instantiation(VM&, GlobalEnvironment&);
@ -97,7 +98,7 @@ private:
virtual void visit_edges(Cell::Visitor&) override;
virtual size_t external_memory_size() const override;
Vector<SharedFunctionInstanceData*> collect_shared_function_data();
void complete_bytecode_cache_install(GC::Ref<Bytecode::Executable>);
void complete_bytecode_cache_install(GC::Ref<Bytecode::Executable>, NonnullRefPtr<RustIntegration::DecodedBytecodeCache>);
void verify_executable_backing_invariants();
GC::Ptr<Realm> m_realm; // [[Realm]]

View file

@ -164,7 +164,7 @@ Result<GC::Ref<SourceTextModule>, Vector<ParserError>> SourceTextModule::parse_f
module_result.executable.ptr(), module_result.tla_shared_data.ptr(), ExecutableBacking::heap_bytecode());
}
Result<GC::Ref<SourceTextModule>, Vector<ParserError>> SourceTextModule::parse_from_bytecode_cache(FFI::DecodedBytecodeCacheBlob* bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Script::HostDefined* host_defined)
Result<GC::Ref<SourceTextModule>, Vector<ParserError>> SourceTextModule::parse_from_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache, NonnullRefPtr<SourceCode const> source_code, Realm& realm, Script::HostDefined* host_defined)
{
auto filename = source_code->filename();
auto rust_result = RustIntegration::materialize_bytecode_cache_module(bytecode_cache, move(source_code), realm);
@ -185,32 +185,30 @@ Result<GC::Ref<SourceTextModule>, Vector<ParserError>> SourceTextModule::parse_f
move(module_result.var_declared_names), move(module_result.lexical_bindings),
move(functions_to_initialize),
move(module_result.shared_function_data),
module_result.executable.ptr(), module_result.tla_shared_data.ptr(), ExecutableBacking::mapped_bytecode_cache());
module_result.executable.ptr(), module_result.tla_shared_data.ptr(), ExecutableBacking::mapped_bytecode_cache(move(bytecode_cache)));
}
bool SourceTextModule::try_install_bytecode_cache(FFI::DecodedBytecodeCacheBlob* bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
bool SourceTextModule::try_install_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
{
if (m_executable_backing.is_mapped_bytecode_cache()) {
RustIntegration::free_decoded_bytecode_cache_blob(bytecode_cache);
if (m_executable_backing.is_mapped_bytecode_cache())
return false;
}
auto shared_function_data = collect_shared_function_data();
auto result = RustIntegration::try_install_bytecode_cache_module(bytecode_cache, move(source_code), realm(), m_executable, shared_function_data, m_tla_shared_data);
if (!result.has_value())
return false;
complete_bytecode_cache_install(result->executable.ptr(), result->top_level_await_executable.ptr());
complete_bytecode_cache_install(result->executable.ptr(), result->top_level_await_executable.ptr(), move(bytecode_cache));
return true;
}
void SourceTextModule::install_generated_bytecode_cache(FFI::DecodedBytecodeCacheBlob* bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
void SourceTextModule::install_generated_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache, NonnullRefPtr<SourceCode const> source_code)
{
VERIFY(can_install_generated_bytecode_cache());
auto shared_function_data = collect_shared_function_data();
auto result = RustIntegration::install_generated_bytecode_cache_module(bytecode_cache, move(source_code), realm(), m_executable, shared_function_data, m_tla_shared_data);
complete_bytecode_cache_install(result.executable.ptr(), result.top_level_await_executable.ptr());
complete_bytecode_cache_install(result.executable.ptr(), result.top_level_await_executable.ptr(), move(bytecode_cache));
}
bool SourceTextModule::can_generate_bytecode_cache() const
@ -248,7 +246,7 @@ Vector<SharedFunctionInstanceData*> SourceTextModule::collect_shared_function_da
return shared_function_data;
}
void SourceTextModule::complete_bytecode_cache_install(GC::Ptr<Bytecode::Executable> executable, GC::Ptr<Bytecode::Executable> top_level_await_executable)
void SourceTextModule::complete_bytecode_cache_install(GC::Ptr<Bytecode::Executable> executable, GC::Ptr<Bytecode::Executable> top_level_await_executable, NonnullRefPtr<RustIntegration::DecodedBytecodeCache> bytecode_cache)
{
VERIFY(executable || top_level_await_executable);
if (executable) {
@ -261,7 +259,7 @@ void SourceTextModule::complete_bytecode_cache_install(GC::Ptr<Bytecode::Executa
m_tla_shared_data->clear_non_bytecode_cache_compile_inputs();
}
m_shared_function_data.clear_non_bytecode_cache_compile_inputs();
m_executable_backing.finish_bytecode_cache_install();
m_executable_backing.finish_bytecode_cache_install(move(bytecode_cache));
verify_executable_backing_invariants();
}

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/NonnullRefPtr.h>
#include <LibJS/CyclicModule.h>
#include <LibJS/ExecutableBacking.h>
#include <LibJS/Export.h>
@ -24,6 +25,12 @@ struct DecodedBytecodeCacheBlob;
}
namespace RustIntegration {
class DecodedBytecodeCache;
}
// 16.2.1.6 Source Text Module Records, https://tc39.es/ecma262/#sec-source-text-module-records
class JS_API SourceTextModule final : public CyclicModule {
GC_CELL(SourceTextModule, CyclicModule);
@ -35,7 +42,7 @@ public:
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse(StringView source_text, Realm&, StringView filename = {}, Script::HostDefined* host_defined = nullptr);
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse_from_pre_parsed(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm&, Script::HostDefined* host_defined = nullptr);
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse_from_pre_compiled(FFI::CompiledProgram* compiled, NonnullRefPtr<SourceCode const> source_code, Realm&, Script::HostDefined* host_defined = nullptr);
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse_from_bytecode_cache(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code, Realm&, Script::HostDefined* host_defined = nullptr);
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse_from_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code, Realm&, Script::HostDefined* host_defined = nullptr);
virtual Vector<Utf16FlyString> get_exported_names(VM& vm, GC::RootHashTable<GC::Ref<Module const>>& export_star_set) override;
virtual ResolvedBinding resolve_export(VM& vm, Utf16FlyString const& export_name, Vector<ResolvedBinding> resolve_set = {}) override;
@ -63,8 +70,8 @@ public:
[[nodiscard]] bool can_install_generated_bytecode_cache() const;
void begin_bytecode_cache_generation();
void finish_bytecode_cache_generation_without_install();
bool try_install_bytecode_cache(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code);
void install_generated_bytecode_cache(FFI::DecodedBytecodeCacheBlob*, NonnullRefPtr<SourceCode const> source_code);
bool try_install_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code);
void install_generated_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code);
protected:
virtual ThrowCompletionOr<void> initialize_environment(VM& vm) override;
@ -76,7 +83,7 @@ private:
virtual void visit_edges(Cell::Visitor&) override;
virtual size_t external_memory_size() const override;
Vector<SharedFunctionInstanceData*> collect_shared_function_data();
void complete_bytecode_cache_install(GC::Ptr<Bytecode::Executable>, GC::Ptr<Bytecode::Executable> top_level_await_executable);
void complete_bytecode_cache_install(GC::Ptr<Bytecode::Executable>, GC::Ptr<Bytecode::Executable> top_level_await_executable, NonnullRefPtr<RustIntegration::DecodedBytecodeCache>);
void verify_executable_backing_invariants();
NonnullOwnPtr<ExecutionContext> m_execution_context; // [[Context]]

View file

@ -138,7 +138,7 @@ GC::Ref<ClassicScript> ClassicScript::create_from_pre_compiled(ByteString filena
return script;
}
GC::Ref<ClassicScript> ClassicScript::create_from_bytecode_cache(ByteString filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject& settings, URL::URL base_url, JS::FFI::DecodedBytecodeCacheBlob* bytecode_cache, MutedErrors muted_errors)
GC::Ref<ClassicScript> ClassicScript::create_from_bytecode_cache(ByteString filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject& settings, URL::URL base_url, NonnullRefPtr<JS::RustIntegration::DecodedBytecodeCache> bytecode_cache, MutedErrors muted_errors)
{
auto& realm = settings.realm();
auto& vm = realm.vm();

View file

@ -28,7 +28,7 @@ public:
static GC::Ref<ClassicScript> create(ByteString filename, StringView source, EnvironmentSettingsObject&, URL::URL base_url, size_t source_line_number = 1, MutedErrors = MutedErrors::No);
static GC::Ref<ClassicScript> create_from_pre_parsed(ByteString filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, JS::FFI::ParsedProgram* parsed, MutedErrors = MutedErrors::No);
static GC::Ref<ClassicScript> create_from_pre_compiled(ByteString filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, JS::FFI::CompiledProgram* compiled, MutedErrors = MutedErrors::No);
static GC::Ref<ClassicScript> create_from_bytecode_cache(ByteString filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, JS::FFI::DecodedBytecodeCacheBlob*, MutedErrors = MutedErrors::No);
static GC::Ref<ClassicScript> create_from_bytecode_cache(ByteString filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, NonnullRefPtr<JS::RustIntegration::DecodedBytecodeCache>, MutedErrors = MutedErrors::No);
JS::Script* script_record() { return m_script_record; }
JS::Script const* script_record() const { return m_script_record; }

View file

@ -92,16 +92,16 @@ struct BytecodeCacheInstallTarget {
switch (type) {
case JS::RustIntegration::ProgramType::Script:
if (auto script_record = script.ptr()) {
auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(move(blob), type, source_hash.bytes());
auto bytecode_cache = JS::RustIntegration::DecodedBytecodeCache::create(move(blob), type, source_hash.bytes());
VERIFY(bytecode_cache);
script_record->install_generated_bytecode_cache(bytecode_cache, move(source_code));
script_record->install_generated_bytecode_cache(bytecode_cache.release_nonnull(), move(source_code));
}
return;
case JS::RustIntegration::ProgramType::Module:
if (auto module_record = module.ptr()) {
auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(move(blob), type, source_hash.bytes());
auto bytecode_cache = JS::RustIntegration::DecodedBytecodeCache::create(move(blob), type, source_hash.bytes());
VERIFY(bytecode_cache);
module_record->install_generated_bytecode_cache(bytecode_cache, move(source_code));
module_record->install_generated_bytecode_cache(bytecode_cache.release_nonnull(), move(source_code));
}
return;
}
@ -311,23 +311,26 @@ static void compile_remaining_module_functions_off_thread(ModuleScript& module_s
struct BytecodeCachePreparation {
Core::ImmutableBytes bytecode;
Function<void(JS::FFI::DecodedBytecodeCacheBlob*)> on_prepared;
Function<void(RefPtr<JS::RustIntegration::DecodedBytecodeCache>)> on_prepared;
};
static void prepare_bytecode_cache_off_thread(Core::ImmutableBytes bytecode, JS::RustIntegration::ProgramType type, size_t source_length, BytecodeCacheSourceHash source_hash, Function<void(JS::FFI::DecodedBytecodeCacheBlob*)> on_prepared)
static void prepare_bytecode_cache_off_thread(Core::ImmutableBytes bytecode, JS::RustIntegration::ProgramType type, size_t source_length, BytecodeCacheSourceHash source_hash, Function<void(RefPtr<JS::RustIntegration::DecodedBytecodeCache>)> on_prepared)
{
auto* preparation = new BytecodeCachePreparation { move(bytecode), move(on_prepared) };
auto& main_thread_event_loop = Core::EventLoop::current();
Threading::ThreadPool::the().submit([preparation, type, source_length, source_hash, &main_thread_event_loop]() mutable {
auto* bytecode_cache = JS::RustIntegration::decode_bytecode_cache_blob(move(preparation->bytecode), type, source_hash.bytes(), main_thread_event_loop);
if (bytecode_cache && !JS::RustIntegration::validate_decoded_bytecode_cache_blob(bytecode_cache, source_length)) {
JS::RustIntegration::free_decoded_bytecode_cache_blob(bytecode_cache);
bytecode_cache = nullptr;
auto* bytecode_cache_blob = JS::RustIntegration::decode_bytecode_cache_blob(move(preparation->bytecode), type, source_hash.bytes(), main_thread_event_loop);
if (bytecode_cache_blob && !JS::RustIntegration::validate_decoded_bytecode_cache_blob(bytecode_cache_blob, source_length)) {
JS::RustIntegration::free_decoded_bytecode_cache_blob(bytecode_cache_blob);
bytecode_cache_blob = nullptr;
}
main_thread_event_loop.deferred_invoke([bytecode_cache, preparation]() {
preparation->on_prepared(bytecode_cache);
main_thread_event_loop.deferred_invoke([bytecode_cache_blob, preparation]() mutable {
RefPtr<JS::RustIntegration::DecodedBytecodeCache> bytecode_cache;
if (bytecode_cache_blob)
bytecode_cache = JS::RustIntegration::DecodedBytecodeCache::create(bytecode_cache_blob);
preparation->on_prepared(move(bytecode_cache));
delete preparation;
perform_a_microtask_checkpoint();
});
@ -745,7 +748,7 @@ void fetch_classic_script(GC::Ref<HTMLScriptElement> element, URL::URL const& ur
source_encoding = move(source_encoding),
source_length,
muted_errors, on_complete_root = move(on_complete_root),
settings_root = move(settings_root)](auto* bytecode_cache) mutable {
settings_root = move(settings_root)](auto bytecode_cache) mutable {
Optional<NonnullRefPtr<JS::SourceCode const>> source_code;
if (bytecode_cache) {
source_code = JS::SourceCode::create(
@ -753,7 +756,7 @@ void fetch_classic_script(GC::Ref<HTMLScriptElement> element, URL::URL const& ur
source_length,
source_encoding,
source_byte_storage);
auto script = ClassicScript::create_from_bytecode_cache(response_url_string, *source_code, *settings_root, response_url, bytecode_cache, muted_errors);
auto script = ClassicScript::create_from_bytecode_cache(response_url_string, *source_code, *settings_root, response_url, bytecode_cache.release_nonnull(), muted_errors);
if (script->parse_error().is_null()) {
on_complete_root->function()(script);
return;
@ -1189,7 +1192,7 @@ void fetch_single_module_script(JS::Realm& realm,
source_hash = move(source_hash),
source_length,
on_complete_root = move(on_complete_root),
settings_root = move(settings_root)](auto* bytecode_cache) mutable {
settings_root = move(settings_root)](auto bytecode_cache) mutable {
Optional<NonnullRefPtr<JS::SourceCode const>> source_code;
if (bytecode_cache) {
source_code = JS::SourceCode::create(
@ -1197,7 +1200,7 @@ void fetch_single_module_script(JS::Realm& realm,
source_length,
"UTF-8"_string,
source_byte_storage);
auto module_script = ModuleScript::create_from_bytecode_cache(url_string, *source_code, *settings_root, response_url, bytecode_cache).release_value_but_fixme_should_propagate_errors();
auto module_script = ModuleScript::create_from_bytecode_cache(url_string, *source_code, *settings_root, response_url, bytecode_cache.release_nonnull()).release_value_but_fixme_should_propagate_errors();
if (module_script && module_script->parse_error().is_null()) {
settings_root->module_map().set(url, module_type_string, { ModuleMap::EntryType::ModuleScript, module_script });
on_complete_root->function()(module_script);

View file

@ -112,7 +112,7 @@ WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> ModuleScript::create_from_pre_compile
return script;
}
WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> ModuleScript::create_from_bytecode_cache(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject& settings, URL::URL base_url, JS::FFI::DecodedBytecodeCacheBlob* bytecode_cache)
WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> ModuleScript::create_from_bytecode_cache(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject& settings, URL::URL base_url, NonnullRefPtr<JS::RustIntegration::DecodedBytecodeCache> bytecode_cache)
{
auto& realm = settings.realm();
auto script = realm.create<ModuleScript>(move(base_url), filename, settings);

View file

@ -15,7 +15,6 @@ namespace JS::FFI {
struct ParsedProgram;
struct CompiledProgram;
struct DecodedBytecodeCacheBlob;
}
@ -34,7 +33,7 @@ public:
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create(ByteString const& filename, StringView source, EnvironmentSettingsObject&, URL::URL base_url);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_from_pre_parsed(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, JS::FFI::ParsedProgram* parsed);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_from_pre_compiled(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, JS::FFI::CompiledProgram* compiled);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_from_bytecode_cache(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, JS::FFI::DecodedBytecodeCacheBlob*);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_from_bytecode_cache(ByteString const& filename, NonnullRefPtr<JS::SourceCode const> source_code, EnvironmentSettingsObject&, URL::URL base_url, NonnullRefPtr<JS::RustIntegration::DecodedBytecodeCache>);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_a_javascript_module_script(ByteString const& filename, StringView source, EnvironmentSettingsObject&, URL::URL base_url);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_a_css_module_script(ByteString const& filename, StringView source, EnvironmentSettingsObject&);
static WebIDL::ExceptionOr<GC::Ptr<ModuleScript>> create_a_json_module_script(ByteString const& filename, StringView source, EnvironmentSettingsObject&);

View file

@ -325,6 +325,13 @@ static BytecodeCacheTestData create_module_bytecode_cache_blob(StringView source
};
}
static NonnullRefPtr<JS::RustIntegration::DecodedBytecodeCache> decode_bytecode_cache_blob(Core::ImmutableBytes bytes, JS::RustIntegration::ProgramType type, ReadonlyBytes source_hash)
{
auto decoded_bytecode_cache = JS::RustIntegration::DecodedBytecodeCache::create(move(bytes), type, source_hash);
VERIFY(decoded_bytecode_cache);
return decoded_bytecode_cache.release_nonnull();
}
static JS::SharedFunctionInstanceData& first_shared_function_with_template_object_cache(JS::Bytecode::Executable& executable)
{
for (auto& shared_data : executable.shared_function_data) {
@ -445,10 +452,9 @@ TEST_CASE(bytecode_cache_materialization_failure_has_parser_error)
// Structural decode still succeeds because the blob is internally well-formed; the corruption is in the bytecode
// payload, which is only checked by the validator that runs during materialization.
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(decoded_blob, test_data.source_code, realm);
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(*decoded_blob, test_data.source_code, realm);
EXPECT(materialized.has_value());
EXPECT(materialized->is_error());
EXPECT(!materialized->error().is_empty());
@ -470,10 +476,9 @@ TEST_CASE(bytecode_cache_rejects_corrupt_declaration_function)
// Structural decode still succeeds (the blob layout is intact); the corruption is in a function's bytecode payload
// and is only caught when the materializer asks the validator to check it.
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(decoded_blob, test_data.source_code, realm);
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(*decoded_blob, test_data.source_code, realm);
EXPECT(materialized.has_value());
EXPECT(materialized->is_error());
EXPECT(!materialized->error().is_empty());
@ -494,10 +499,9 @@ TEST_CASE(bytecode_cache_rejects_out_of_range_declaration_function_source_span)
// The source hash still matches and the blob layout is intact, but the cached function source span points outside
// the current SourceCode. Materialization should reject it as a cache miss instead of handing the range to C++.
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(decoded_blob, test_data.source_code, realm);
auto materialized = JS::RustIntegration::materialize_bytecode_cache_script(*decoded_blob, test_data.source_code, realm);
EXPECT(materialized.has_value());
EXPECT(materialized->is_error());
EXPECT(!materialized->error().is_empty());
@ -512,8 +516,7 @@ TEST_CASE(bytecode_cache_materializes_function_executables_lazily)
auto test_data = create_bytecode_cache_blob("let f = function lazy() { return 1; }; f();"_string);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm);
VERIFY(!script_or_error.is_error());
@ -533,6 +536,30 @@ TEST_CASE(bytecode_cache_materializes_function_executables_lazily)
EXPECT(!shared_data.m_cached_bytecode_executable);
}
TEST_CASE(decoded_bytecode_cache_backing_materializes_independently)
{
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("function answer() { return 42; } answer();"_string);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto first_script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm);
VERIFY(!first_script_or_error.is_error());
auto second_script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm);
VERIFY(!second_script_or_error.is_error());
auto first_result = vm->run(first_script_or_error.release_value());
VERIFY(!first_result.is_throw_completion());
EXPECT_EQ(first_result.value().as_i32(), 42);
auto second_result = vm->run(second_script_or_error.release_value());
VERIFY(!second_result.is_throw_completion());
EXPECT_EQ(second_result.value().as_i32(), 42);
}
TEST_CASE(bytecode_cache_install_shares_template_object_cache_slots)
{
auto vm = JS::VM::create();
@ -564,8 +591,7 @@ TEST_CASE(bytecode_cache_install_shares_template_object_cache_slots)
auto old_template_cache = old_function_executable->template_object_caches[0];
EXPECT(!old_template_cache->cached_template_object);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
EXPECT(script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
auto new_function_executable = shared_data.m_executable;
@ -600,8 +626,7 @@ TEST_CASE(bytecode_cache_install_rejects_corrupt_declaration_function)
auto* old_executable = script->cached_executable();
VERIFY(old_executable);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
EXPECT(!script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
EXPECT_EQ(script->cached_executable(), old_executable);
@ -636,8 +661,7 @@ TEST_CASE(bytecode_cache_install_failure_preserves_existing_shared_functions)
VERIFY(bytecode_payload_offset < corrupted_blob.size());
corrupted_blob[bytecode_payload_offset] ^= 0xff;
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(Core::ImmutableBytes::adopt(move(corrupted_blob)), JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
EXPECT(!script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
EXPECT_EQ(script->cached_executable(), old_executable);
@ -672,8 +696,7 @@ TEST_CASE(bytecode_cache_install_clears_lazy_nested_function_inputs)
EXPECT_EQ(inner_shared_data.m_owner_shared_function_data_list, outer_shared_data.m_owner_shared_function_data_list);
EXPECT(inner_shared_data.m_rust_function_ast);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
EXPECT(script->try_install_bytecode_cache(decoded_blob, test_data.source_code));
EXPECT(!inner_shared_data.m_rust_function_ast);
@ -700,8 +723,7 @@ TEST_CASE(bytecode_cache_install_matches_class_constructor_after_source_text_exp
VERIFY(old_executable);
EXPECT(count_shared_functions_with_rust_ast(*old_executable) > 0);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
script->begin_bytecode_cache_generation();
EXPECT(script->executable_backing().is_source());
@ -757,8 +779,7 @@ TEST_CASE(bytecode_cache_install_clears_precompiled_lazy_function_inputs)
EXPECT(shared_data.m_precompiled_bytecode_executable);
EXPECT(!shared_data.m_cached_bytecode_executable);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
script->begin_bytecode_cache_generation();
EXPECT(script->executable_backing().is_heap_bytecode());
@ -798,8 +819,7 @@ TEST_CASE(bytecode_cache_install_updates_top_level_await_module_executable)
auto old_executable = top_level_await_shared_data->m_executable;
VERIFY(old_executable);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Module, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Module, test_data.source_hash.bytes());
module->begin_bytecode_cache_generation();
EXPECT(module->can_install_generated_bytecode_cache());
@ -825,8 +845,7 @@ TEST_CASE(bytecode_cache_to_string_caches_lazy_ascii_source_text)
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source.bytes()));
auto source_code = JS::SourceCode::create("test.js"_string, test_data.source_code->length_in_code_units(), "UTF-8"_string, move(source_bytes));
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, source_code, realm);
VERIFY(!script_or_error.is_error());
@ -859,8 +878,7 @@ TEST_CASE(bytecode_cache_to_string_uses_lazy_utf8_offset_map)
auto source_bytes = TRY_OR_FAIL(Core::ImmutableBytes::copy(source.bytes()));
auto source_code = JS::SourceCode::create("test.js"_string, test_data.source_code->length_in_code_units(), "UTF-8"_string, move(source_bytes));
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, source_code, realm);
VERIFY(!script_or_error.is_error());
@ -900,8 +918,7 @@ TEST_CASE(bytecode_cache_materializes_from_mapped_blob)
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 decoded_blob = decode_bytecode_cache_blob(mapped_blob, JS::RustIntegration::ProgramType::Script, test_data.source_hash.bytes());
auto script_or_error = JS::Script::create_from_bytecode_cache(decoded_blob, test_data.source_code, realm);
VERIFY(!script_or_error.is_error());
@ -959,8 +976,7 @@ TEST_CASE(bytecode_cache_preserves_re_exported_import_names)
auto test_data = create_module_bytecode_cache_blob("import { pass as renamed } from './source.mjs'; export { renamed as default };"sv);
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Module, test_data.source_hash.bytes());
VERIFY(decoded_blob);
auto decoded_blob = decode_bytecode_cache_blob(test_data.blob, JS::RustIntegration::ProgramType::Module, test_data.source_hash.bytes());
auto source_module = JS::SourceTextModule::parse("export function pass() {}"sv, realm, "./source.mjs"sv).release_value();
source_module->set_status(JS::ModuleStatus::Unlinked);