LibJS: Materialize compiled function bytecode lazily
Keep fully compiled function bytecode in its Rust-side form until the function is called for the first time. This covers decoded disk cache records and freshly precompiled bytecode, so startup avoids eagerly allocating every nested function executable. Validate cached function bytecode before accepting a cache entry. This keeps the existing failure behavior for corrupt on-disk cache data. Add coverage for bytecode-cache and freshly precompiled functions to assert that nested executables stay absent after script materialization, then appear after the function is called.
This commit is contained in:
parent
3f1be9d93c
commit
e926e86f8d
8 changed files with 392 additions and 52 deletions
|
|
@ -98,6 +98,10 @@ void SharedFunctionInstanceData::finalize()
|
|||
Base::finalize();
|
||||
RustIntegration::free_function_ast(m_rust_function_ast);
|
||||
m_rust_function_ast = nullptr;
|
||||
RustIntegration::free_cached_bytecode_executable(m_cached_bytecode_executable);
|
||||
m_cached_bytecode_executable = nullptr;
|
||||
RustIntegration::free_precompiled_bytecode_executable(m_precompiled_bytecode_executable);
|
||||
m_precompiled_bytecode_executable = nullptr;
|
||||
}
|
||||
|
||||
void SharedFunctionInstanceData::clear_compile_inputs()
|
||||
|
|
@ -108,6 +112,10 @@ void SharedFunctionInstanceData::clear_compile_inputs()
|
|||
m_lexical_bindings.clear();
|
||||
RustIntegration::free_function_ast(m_rust_function_ast);
|
||||
m_rust_function_ast = nullptr;
|
||||
RustIntegration::free_cached_bytecode_executable(m_cached_bytecode_executable);
|
||||
m_cached_bytecode_executable = nullptr;
|
||||
RustIntegration::free_precompiled_bytecode_executable(m_precompiled_bytecode_executable);
|
||||
m_precompiled_bytecode_executable = nullptr;
|
||||
}
|
||||
|
||||
void SharedFunctionInstanceData::update_can_inline_call()
|
||||
|
|
|
|||
|
|
@ -147,6 +147,12 @@ public:
|
|||
// NB: When non-null, points to a Rust Box<FunctionData> used for
|
||||
// lazy compilation through the Rust pipeline.
|
||||
void* m_rust_function_ast { nullptr };
|
||||
// NB: When non-null, points to a Rust Box<DecodedExecutableRecord> used
|
||||
// for lazy materialization from the bytecode cache.
|
||||
void* m_cached_bytecode_executable { nullptr };
|
||||
// NB: When non-null, points to a Rust Box<PrecompiledFunction> used for
|
||||
// lazy materialization from freshly compiled bytecode.
|
||||
void* m_precompiled_bytecode_executable { nullptr };
|
||||
bool m_use_rust_compilation { false };
|
||||
|
||||
void clear_compile_inputs();
|
||||
|
|
|
|||
|
|
@ -258,6 +258,28 @@ unsafe extern "C" {
|
|||
contains_direct_call_to_eval: bool,
|
||||
);
|
||||
|
||||
pub fn rust_sfd_set_cached_bytecode_executable(
|
||||
sfd_ptr: *mut c_void,
|
||||
cached_executable_ptr: *mut c_void,
|
||||
uses_this: bool,
|
||||
this_value_needs_environment_resolution: bool,
|
||||
function_environment_needed: bool,
|
||||
function_environment_bindings_count: usize,
|
||||
might_need_arguments_object: bool,
|
||||
contains_direct_call_to_eval: bool,
|
||||
);
|
||||
|
||||
pub fn rust_sfd_set_precompiled_bytecode_executable(
|
||||
sfd_ptr: *mut c_void,
|
||||
precompiled_executable_ptr: *mut c_void,
|
||||
uses_this: bool,
|
||||
this_value_needs_environment_resolution: bool,
|
||||
function_environment_needed: bool,
|
||||
function_environment_bindings_count: usize,
|
||||
might_need_arguments_object: bool,
|
||||
contains_direct_call_to_eval: bool,
|
||||
);
|
||||
|
||||
pub fn rust_create_class_blueprint(
|
||||
vm_ptr: *mut c_void,
|
||||
source_code_ptr: *const c_void,
|
||||
|
|
@ -447,28 +469,24 @@ unsafe fn materialize_shared_function_data(
|
|||
if let Some((name, is_private)) = &pending.class_field_initializer_name {
|
||||
rust_sfd_set_class_field_initializer_name(sfd_ptr, name.as_ptr(), name.len(), *is_private);
|
||||
}
|
||||
if let Some(precompiled) = pending.precompiled_function.as_mut() {
|
||||
precompiled.generator.vm_ptr = vm_ptr;
|
||||
precompiled.generator.source_code_ptr = source_code_ptr;
|
||||
let executable_ptr = create_executable(
|
||||
&mut precompiled.generator,
|
||||
&precompiled.assembled,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
);
|
||||
assert!(
|
||||
!executable_ptr.is_null(),
|
||||
"materialize_shared_function_data: eager function executable materialization failed"
|
||||
);
|
||||
rust_sfd_set_precompiled_executable(
|
||||
if let Some(precompiled) = pending.precompiled_function.take() {
|
||||
let uses_this = precompiled.metadata.uses_this;
|
||||
let this_value_needs_environment_resolution =
|
||||
precompiled.metadata.this_value_needs_environment_resolution;
|
||||
let function_environment_needed = precompiled.metadata.function_environment_needed;
|
||||
let function_environment_bindings_count = precompiled.metadata.function_environment_bindings_count;
|
||||
let might_need_arguments = precompiled.metadata.might_need_arguments;
|
||||
let contains_eval = precompiled.metadata.contains_eval;
|
||||
let precompiled_ptr = Box::into_raw(precompiled) as *mut c_void;
|
||||
rust_sfd_set_precompiled_bytecode_executable(
|
||||
sfd_ptr,
|
||||
executable_ptr,
|
||||
precompiled.metadata.uses_this,
|
||||
precompiled.metadata.this_value_needs_environment_resolution,
|
||||
precompiled.metadata.function_environment_needed,
|
||||
precompiled.metadata.function_environment_bindings_count,
|
||||
precompiled.metadata.might_need_arguments,
|
||||
precompiled.metadata.contains_eval,
|
||||
precompiled_ptr,
|
||||
uses_this,
|
||||
this_value_needs_environment_resolution,
|
||||
function_environment_needed,
|
||||
function_environment_bindings_count,
|
||||
might_need_arguments,
|
||||
contains_eval,
|
||||
);
|
||||
}
|
||||
sfd_ptrs.push(sfd_ptr as *const c_void);
|
||||
|
|
|
|||
|
|
@ -23,11 +23,19 @@ use crate::bytecode::generator::{
|
|||
PrecompiledFunction,
|
||||
};
|
||||
use crate::bytecode::operand::PropertyKeyTableIndex;
|
||||
use crate::bytecode::validator::{
|
||||
FFIExceptionHandlerOffsets, FFIValidatorBounds, ValidationErrorKind, validate_bytecode,
|
||||
};
|
||||
use crate::{CompiledProgram, CompiledProgramBytecode, ModuleCallbacks, ast, u32_from_usize};
|
||||
|
||||
const MAGIC: &[u8; 8] = b"LBJSBC\0\0";
|
||||
const FORMAT_VERSION: u32 = 2;
|
||||
const SOURCE_HASH_SIZE: usize = 32;
|
||||
const COMPLETION_TYPE_VARIANT_COUNT: u32 = 6;
|
||||
const ITERATOR_HINT_VARIANT_COUNT: u32 = 2;
|
||||
const ENVIRONMENT_MODE_VARIANT_COUNT: u32 = 2;
|
||||
const PUT_KIND_VARIANT_COUNT: u32 = 5;
|
||||
const ARGUMENTS_KIND_VARIANT_COUNT: u32 = 2;
|
||||
|
||||
fn source_span_is_valid(start: u32, end: u32, source_len: usize) -> bool {
|
||||
let start = start as usize;
|
||||
|
|
@ -687,6 +695,10 @@ unsafe fn materialize_function(
|
|||
source_code_ptr: *const c_void,
|
||||
) -> *mut c_void {
|
||||
unsafe {
|
||||
if function.precompiled.validate_cached_bytecode().is_err() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let parameter_names: Vec<FFIUtf16Slice> = function
|
||||
.parameter_names
|
||||
.as_ref()
|
||||
|
|
@ -736,13 +748,10 @@ unsafe fn materialize_function(
|
|||
);
|
||||
}
|
||||
|
||||
let executable_ptr = materialize_executable(function.precompiled, vm_ptr, source_code_ptr);
|
||||
if executable_ptr.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
crate::bytecode::ffi::rust_sfd_set_precompiled_executable(
|
||||
let cached_executable_ptr = Box::into_raw(Box::new(function.precompiled)) as *mut c_void;
|
||||
crate::bytecode::ffi::rust_sfd_set_cached_bytecode_executable(
|
||||
sfd_ptr,
|
||||
executable_ptr,
|
||||
cached_executable_ptr,
|
||||
function.metadata.uses_this,
|
||||
function.metadata.this_value_needs_environment_resolution,
|
||||
function.metadata.function_environment_needed,
|
||||
|
|
@ -755,6 +764,28 @@ unsafe fn materialize_function(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn materialize_cached_function(
|
||||
cached_executable_ptr: *mut c_void,
|
||||
vm_ptr: *mut c_void,
|
||||
source_code_ptr: *const c_void,
|
||||
) -> *mut c_void {
|
||||
unsafe {
|
||||
if cached_executable_ptr.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let executable = Box::from_raw(cached_executable_ptr as *mut DecodedExecutableRecord);
|
||||
materialize_executable(*executable, vm_ptr, source_code_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn free_cached_function(cached_executable_ptr: *mut c_void) {
|
||||
unsafe {
|
||||
if !cached_executable_ptr.is_null() {
|
||||
drop(Box::from_raw(cached_executable_ptr as *mut DecodedExecutableRecord));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn materialize_executable(
|
||||
executable: DecodedExecutableRecord,
|
||||
vm_ptr: *mut c_void,
|
||||
|
|
@ -1949,6 +1980,52 @@ impl DecodedExecutableRecord {
|
|||
}
|
||||
}
|
||||
|
||||
fn validate_cached_bytecode(&self) -> Result<(), ValidationErrorKind> {
|
||||
let bounds = FFIValidatorBounds {
|
||||
number_of_registers: self.number_of_registers,
|
||||
number_of_locals: self.local_variables.len() as u32,
|
||||
number_of_constants: self.constants.len() as u32,
|
||||
number_of_arguments: self.number_of_arguments,
|
||||
identifier_table_size: self.identifier_table.len() as u32,
|
||||
string_table_size: self.string_table.len() as u32,
|
||||
property_key_table_size: self.property_key_table.len() as u32,
|
||||
regex_table_size: 0,
|
||||
property_lookup_cache_count: self.cache_counters.property_lookup_cache_count,
|
||||
global_variable_cache_count: self.cache_counters.global_variable_cache_count,
|
||||
template_object_cache_count: self.cache_counters.template_object_cache_count,
|
||||
object_shape_cache_count: self.cache_counters.object_shape_cache_count,
|
||||
object_property_iterator_cache_count: self.cache_counters.object_property_iterator_cache_count,
|
||||
class_blueprint_count: self.class_blueprints.len() as u32,
|
||||
shared_function_data_count: self.shared_functions.len() as u32,
|
||||
completion_type_variant_count: COMPLETION_TYPE_VARIANT_COUNT,
|
||||
iterator_hint_variant_count: ITERATOR_HINT_VARIANT_COUNT,
|
||||
environment_mode_variant_count: ENVIRONMENT_MODE_VARIANT_COUNT,
|
||||
put_kind_variant_count: PUT_KIND_VARIANT_COUNT,
|
||||
arguments_kind_variant_count: ARGUMENTS_KIND_VARIANT_COUNT,
|
||||
before_cache_fixup: true,
|
||||
};
|
||||
|
||||
let exception_handlers: Vec<FFIExceptionHandlerOffsets> = self
|
||||
.exception_handlers
|
||||
.iter()
|
||||
.map(|handler| FFIExceptionHandlerOffsets {
|
||||
start: handler.start_offset,
|
||||
end: handler.end_offset,
|
||||
handler: handler.handler_offset,
|
||||
})
|
||||
.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)?;
|
||||
|
||||
for function in &self.shared_functions {
|
||||
function.precompiled.validate_cached_bytecode()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn source_ranges_are_valid(&self, source_len: usize) -> bool {
|
||||
self.shared_functions
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -946,6 +946,90 @@ pub unsafe extern "C" fn rust_materialize_bytecode_cache_module(
|
|||
}
|
||||
}
|
||||
|
||||
/// Materialize a decoded function executable from a bytecode cache blob.
|
||||
/// Consumes and frees the cached executable.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `cached_executable` must be a valid pointer attached to a
|
||||
/// `SharedFunctionInstanceData` by bytecode cache materialization.
|
||||
/// - `vm_ptr` must be a valid `JS::VM*`.
|
||||
/// - `source_code_ptr` must be a valid `JS::SourceCode const*`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_materialize_bytecode_cache_function(
|
||||
cached_executable: *mut c_void,
|
||||
vm_ptr: *mut c_void,
|
||||
source_code_ptr: *const c_void,
|
||||
) -> *mut c_void {
|
||||
unsafe {
|
||||
abort_on_panic(|| bytecode_cache::materialize_cached_function(cached_executable, vm_ptr, source_code_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a cached decoded function executable without materializing it.
|
||||
///
|
||||
/// # Safety
|
||||
/// `cached_executable` must be either null or a valid pointer attached to a
|
||||
/// `SharedFunctionInstanceData` by bytecode cache materialization.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_free_cached_bytecode_executable(cached_executable: *mut c_void) {
|
||||
unsafe {
|
||||
abort_on_panic(|| {
|
||||
bytecode_cache::free_cached_function(cached_executable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Materialize a precompiled function executable.
|
||||
/// Consumes and frees the precompiled executable.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `precompiled_executable` must be a valid `Box<PrecompiledFunction>`
|
||||
/// pointer attached to a `SharedFunctionInstanceData`.
|
||||
/// - `vm_ptr` must be a valid `JS::VM*`.
|
||||
/// - `source_code_ptr` must be a valid `JS::SourceCode const*`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_materialize_precompiled_bytecode_function(
|
||||
precompiled_executable: *mut c_void,
|
||||
vm_ptr: *mut c_void,
|
||||
source_code_ptr: *const c_void,
|
||||
) -> *mut c_void {
|
||||
unsafe {
|
||||
abort_on_panic(|| {
|
||||
if precompiled_executable.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let mut precompiled =
|
||||
Box::from_raw(precompiled_executable as *mut bytecode::generator::PrecompiledFunction);
|
||||
precompiled.generator.vm_ptr = vm_ptr;
|
||||
precompiled.generator.source_code_ptr = source_code_ptr;
|
||||
bytecode::ffi::create_executable(
|
||||
&mut precompiled.generator,
|
||||
&precompiled.assembled,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a precompiled function executable without materializing it.
|
||||
///
|
||||
/// # Safety
|
||||
/// `precompiled_executable` must be either null or a valid
|
||||
/// `Box<PrecompiledFunction>` pointer attached to a `SharedFunctionInstanceData`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_free_precompiled_bytecode_executable(precompiled_executable: *mut c_void) {
|
||||
unsafe {
|
||||
abort_on_panic(|| {
|
||||
if !precompiled_executable.is_null() {
|
||||
drop(Box::from_raw(
|
||||
precompiled_executable as *mut bytecode::generator::PrecompiledFunction,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the AST dump string from a ParsedProgram.
|
||||
///
|
||||
/// Generates the dump on first call and caches it. Writes the pointer
|
||||
|
|
@ -2694,9 +2778,10 @@ pub unsafe extern "C" fn rust_compile_function_off_thread(
|
|||
}
|
||||
}
|
||||
|
||||
/// Materialize a GC-free compiled function onto an existing SFD.
|
||||
/// Attach a GC-free compiled function to an existing SFD.
|
||||
///
|
||||
/// Consumes and frees the compiled function.
|
||||
/// Consumes the compiled function. Its precompiled bytecode is materialized
|
||||
/// lazily the first time the function is called.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `compiled` must be a valid pointer returned by `rust_compile_function_off_thread`.
|
||||
|
|
@ -2704,8 +2789,8 @@ pub unsafe extern "C" fn rust_compile_function_off_thread(
|
|||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_materialize_compiled_function(
|
||||
compiled: *mut CompiledFunction,
|
||||
vm_ptr: *mut c_void,
|
||||
source_code_ptr: *const c_void,
|
||||
_vm_ptr: *mut c_void,
|
||||
_source_code_ptr: *const c_void,
|
||||
sfd_ptr: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
|
|
@ -2713,28 +2798,23 @@ pub unsafe extern "C" fn rust_materialize_compiled_function(
|
|||
if compiled.is_null() {
|
||||
return;
|
||||
}
|
||||
let mut compiled = Box::from_raw(compiled);
|
||||
compiled.precompiled.generator.vm_ptr = vm_ptr;
|
||||
compiled.precompiled.generator.source_code_ptr = source_code_ptr;
|
||||
let executable_ptr = bytecode::ffi::create_executable(
|
||||
&mut compiled.precompiled.generator,
|
||||
&compiled.precompiled.assembled,
|
||||
vm_ptr,
|
||||
source_code_ptr,
|
||||
);
|
||||
assert!(
|
||||
!executable_ptr.is_null(),
|
||||
"rust_materialize_compiled_function: executable materialization failed"
|
||||
);
|
||||
bytecode::ffi::rust_sfd_set_precompiled_executable(
|
||||
let CompiledFunction { precompiled } = *Box::from_raw(compiled);
|
||||
let uses_this = precompiled.metadata.uses_this;
|
||||
let this_value_needs_environment_resolution = precompiled.metadata.this_value_needs_environment_resolution;
|
||||
let function_environment_needed = precompiled.metadata.function_environment_needed;
|
||||
let function_environment_bindings_count = precompiled.metadata.function_environment_bindings_count;
|
||||
let might_need_arguments = precompiled.metadata.might_need_arguments;
|
||||
let contains_eval = precompiled.metadata.contains_eval;
|
||||
let precompiled_ptr = Box::into_raw(precompiled) as *mut c_void;
|
||||
bytecode::ffi::rust_sfd_set_precompiled_bytecode_executable(
|
||||
sfd_ptr,
|
||||
executable_ptr,
|
||||
compiled.precompiled.metadata.uses_this,
|
||||
compiled.precompiled.metadata.this_value_needs_environment_resolution,
|
||||
compiled.precompiled.metadata.function_environment_needed,
|
||||
compiled.precompiled.metadata.function_environment_bindings_count,
|
||||
compiled.precompiled.metadata.might_need_arguments,
|
||||
compiled.precompiled.metadata.contains_eval,
|
||||
precompiled_ptr,
|
||||
uses_this,
|
||||
this_value_needs_environment_resolution,
|
||||
function_environment_needed,
|
||||
function_environment_bindings_count,
|
||||
might_need_arguments,
|
||||
contains_eval,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -763,6 +763,27 @@ Optional<Vector<GC::Root<SharedFunctionInstanceData>>> compile_builtin_file(
|
|||
|
||||
GC::Ptr<Bytecode::Executable> compile_function(VM& vm, SharedFunctionInstanceData& shared_data, bool builtin_abstract_operations_enabled)
|
||||
{
|
||||
if (shared_data.m_precompiled_bytecode_executable) {
|
||||
GC::DeferGC defer_gc(vm.heap());
|
||||
auto* exec = static_cast<Bytecode::Executable*>(rust_materialize_precompiled_bytecode_function(
|
||||
shared_data.m_precompiled_bytecode_executable,
|
||||
&vm,
|
||||
shared_data.m_source_code.ptr()));
|
||||
shared_data.m_precompiled_bytecode_executable = nullptr;
|
||||
return exec;
|
||||
}
|
||||
|
||||
if (shared_data.m_cached_bytecode_executable) {
|
||||
GC::DeferGC defer_gc(vm.heap());
|
||||
TemporaryChange validate_cache_executables { s_validate_materialized_bytecode_cache_executables, true };
|
||||
auto* exec = static_cast<Bytecode::Executable*>(rust_materialize_bytecode_cache_function(
|
||||
shared_data.m_cached_bytecode_executable,
|
||||
&vm,
|
||||
shared_data.m_source_code.ptr()));
|
||||
shared_data.m_cached_bytecode_executable = nullptr;
|
||||
return exec;
|
||||
}
|
||||
|
||||
if (!shared_data.m_use_rust_compilation)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -803,6 +824,18 @@ void free_compiled_function(CompiledFunction* compiled)
|
|||
rust_free_compiled_function(compiled);
|
||||
}
|
||||
|
||||
void free_cached_bytecode_executable(void* executable)
|
||||
{
|
||||
if (executable)
|
||||
rust_free_cached_bytecode_executable(executable);
|
||||
}
|
||||
|
||||
void free_precompiled_bytecode_executable(void* executable)
|
||||
{
|
||||
if (executable)
|
||||
rust_free_precompiled_bytecode_executable(executable);
|
||||
}
|
||||
|
||||
void free_function_ast(void* ast)
|
||||
{
|
||||
if (ast)
|
||||
|
|
@ -1192,6 +1225,52 @@ extern "C" void rust_sfd_set_precompiled_executable(
|
|||
shared.clear_compile_inputs();
|
||||
}
|
||||
|
||||
extern "C" void rust_sfd_set_cached_bytecode_executable(
|
||||
void* sfd_ptr,
|
||||
void* cached_executable_ptr,
|
||||
bool uses_this,
|
||||
bool this_value_needs_environment_resolution,
|
||||
bool function_environment_needed,
|
||||
size_t function_environment_bindings_count,
|
||||
bool might_need_arguments_object,
|
||||
bool contains_direct_call_to_eval)
|
||||
{
|
||||
auto& shared = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
|
||||
|
||||
shared.m_uses_this = uses_this;
|
||||
shared.m_this_value_needs_environment_resolution = this_value_needs_environment_resolution;
|
||||
shared.m_function_environment_needed = function_environment_needed;
|
||||
shared.m_function_environment_bindings_count = function_environment_bindings_count;
|
||||
shared.m_might_need_arguments_object = might_need_arguments_object;
|
||||
shared.m_contains_direct_call_to_eval = contains_direct_call_to_eval;
|
||||
shared.m_cached_bytecode_executable = cached_executable_ptr;
|
||||
shared.update_asm_call_metadata();
|
||||
}
|
||||
|
||||
extern "C" void rust_sfd_set_precompiled_bytecode_executable(
|
||||
void* sfd_ptr,
|
||||
void* precompiled_executable_ptr,
|
||||
bool uses_this,
|
||||
bool this_value_needs_environment_resolution,
|
||||
bool function_environment_needed,
|
||||
size_t function_environment_bindings_count,
|
||||
bool might_need_arguments_object,
|
||||
bool contains_direct_call_to_eval)
|
||||
{
|
||||
auto& shared = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
|
||||
|
||||
shared.m_uses_this = uses_this;
|
||||
shared.m_this_value_needs_environment_resolution = this_value_needs_environment_resolution;
|
||||
shared.m_function_environment_needed = function_environment_needed;
|
||||
shared.m_function_environment_bindings_count = function_environment_bindings_count;
|
||||
shared.m_might_need_arguments_object = might_need_arguments_object;
|
||||
shared.m_contains_direct_call_to_eval = contains_direct_call_to_eval;
|
||||
shared.m_precompiled_bytecode_executable = precompiled_executable_ptr;
|
||||
RustIntegration::free_function_ast(shared.m_rust_function_ast);
|
||||
shared.m_rust_function_ast = nullptr;
|
||||
shared.update_asm_call_metadata();
|
||||
}
|
||||
|
||||
extern "C" void* rust_create_class_blueprint(
|
||||
void* vm_ptr,
|
||||
void const* source_code_ptr,
|
||||
|
|
|
|||
|
|
@ -172,9 +172,16 @@ GC::Ptr<Bytecode::Executable> compile_function(VM& vm, SharedFunctionInstanceDat
|
|||
|
||||
JS_API void* clone_function_ast(void const*);
|
||||
JS_API FFI::CompiledFunction* compile_function_off_thread(void* function_ast, size_t length_in_code_units, bool builtin_abstract_operations_enabled);
|
||||
// Attach a previously compiled function for lazy materialization.
|
||||
JS_API void materialize_compiled_function(FFI::CompiledFunction*, VM&, SourceCode const&, SharedFunctionInstanceData&);
|
||||
JS_API void free_compiled_function(FFI::CompiledFunction*);
|
||||
|
||||
// Free a Rust decoded bytecode cache executable pointer. No-op if null.
|
||||
void free_cached_bytecode_executable(void*);
|
||||
|
||||
// Free a Rust precompiled bytecode executable pointer. No-op if null.
|
||||
void free_precompiled_bytecode_executable(void*);
|
||||
|
||||
// Free a Rust function AST pointer. No-op if Rust is not available.
|
||||
void free_function_ast(void* ast);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
#include <LibCrypto/Hash/SHA2.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/ModuleRequest.h>
|
||||
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/RustIntegration.h>
|
||||
#include <LibJS/Script.h>
|
||||
#include <LibJS/SourceCode.h>
|
||||
#include <LibJS/SourceTextModule.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
|
@ -326,6 +328,69 @@ TEST_CASE(bytecode_cache_rejects_out_of_range_declaration_function_source_span)
|
|||
EXPECT_EQ(materialized->error().first().message, "Failed to materialize bytecode cache"_string);
|
||||
}
|
||||
|
||||
TEST_CASE(bytecode_cache_materializes_function_executables_lazily)
|
||||
{
|
||||
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 lazy() { return 1; }; f();"_string);
|
||||
|
||||
auto* decoded_blob = JS::RustIntegration::decode_bytecode_cache_blob(test_data.blob.bytes(), 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 script = script_or_error.release_value();
|
||||
|
||||
auto* executable = script->cached_executable();
|
||||
VERIFY(executable);
|
||||
VERIFY(!executable->shared_function_data.is_empty());
|
||||
auto& shared_data = *executable->shared_function_data[0];
|
||||
EXPECT(!shared_data.m_executable);
|
||||
EXPECT(shared_data.m_cached_bytecode_executable);
|
||||
|
||||
auto result = vm->run(script);
|
||||
VERIFY(!result.is_throw_completion());
|
||||
EXPECT(shared_data.m_executable);
|
||||
EXPECT(!shared_data.m_cached_bytecode_executable);
|
||||
}
|
||||
|
||||
TEST_CASE(fresh_precompiled_function_executables_materialize_lazily)
|
||||
{
|
||||
auto vm = JS::VM::create();
|
||||
auto root_execution_context = JS::create_simple_execution_context<JS::GlobalObject>(*vm);
|
||||
auto& realm = *root_execution_context->realm;
|
||||
|
||||
auto source_code = JS::SourceCode::create("test.js"_string, Utf16String::from_utf8("let f = function lazy() { return 1; }; f();"_string));
|
||||
auto* parsed = JS::RustIntegration::parse_program(source_code->utf16_data(), source_code->length_in_code_units(), JS::RustIntegration::ProgramType::Script);
|
||||
VERIFY(parsed);
|
||||
ArmedScopeGuard free_parsed = [&] {
|
||||
JS::RustIntegration::free_parsed_program(parsed);
|
||||
};
|
||||
EXPECT(!JS::RustIntegration::parsed_program_has_errors(parsed));
|
||||
|
||||
auto* compiled = JS::RustIntegration::compile_parsed_program_fully_off_thread(parsed, source_code->length_in_code_units());
|
||||
VERIFY(compiled);
|
||||
free_parsed.disarm();
|
||||
|
||||
auto script_or_error = JS::Script::create_from_compiled(compiled, source_code, realm);
|
||||
VERIFY(!script_or_error.is_error());
|
||||
auto script = script_or_error.release_value();
|
||||
|
||||
auto* executable = script->cached_executable();
|
||||
VERIFY(executable);
|
||||
VERIFY(!executable->shared_function_data.is_empty());
|
||||
auto& shared_data = *executable->shared_function_data[0];
|
||||
EXPECT(!shared_data.m_executable);
|
||||
EXPECT(shared_data.m_precompiled_bytecode_executable);
|
||||
|
||||
auto result = vm->run(script);
|
||||
VERIFY(!result.is_throw_completion());
|
||||
EXPECT(shared_data.m_executable);
|
||||
EXPECT(!shared_data.m_precompiled_bytecode_executable);
|
||||
}
|
||||
|
||||
TEST_CASE(bytecode_cache_preserves_re_exported_import_names)
|
||||
{
|
||||
auto vm = JS::VM::create();
|
||||
|
|
|
|||
Loading…
Reference in a new issue