Add an explicit validation entry point for decoded bytecode cache blobs. This lets callers validate a blob before materialization while keeping the existing validated-before-use invariant in place. Make validation idempotent so a prevalidated blob is not walked again when materialization reaches the same check. Keep the existing decoded source length query for callers that still validate synchronously. Route decoded cache blob owner destruction through the origin event loop. This lets a worker decode or reject mapped bytecode without releasing non-atomically refcounted ImmutableBytes off-thread, while lazy cached function materialization can still retain mmap-backed bytecode.
218 lines
11 KiB
C++
218 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/ByteBuffer.h>
|
|
#include <AK/HashTable.h>
|
|
#include <AK/NonnullRefPtr.h>
|
|
#include <AK/Optional.h>
|
|
#include <AK/Result.h>
|
|
#include <AK/Span.h>
|
|
#include <AK/Utf16FlyString.h>
|
|
#include <LibCore/Forward.h>
|
|
#include <LibCore/ImmutableBytes.h>
|
|
#include <LibGC/Ptr.h>
|
|
#include <LibGC/Root.h>
|
|
#include <LibJS/ModuleEntry.h>
|
|
#include <LibJS/ParserError.h>
|
|
#include <LibJS/Runtime/AbstractOperations.h>
|
|
#include <LibJS/Runtime/FunctionKind.h>
|
|
#include <LibJS/Script.h>
|
|
#include <LibJS/SourceCode.h>
|
|
#include <LibJS/SourceTextModule.h>
|
|
|
|
// Opaque parsed program handle from the Rust pipeline.
|
|
namespace JS::FFI {
|
|
|
|
struct ParsedProgram;
|
|
struct CompiledProgram;
|
|
struct CompiledFunction;
|
|
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.
|
|
struct ScriptResult {
|
|
GC::Root<Bytecode::Executable> executable;
|
|
Vector<GC::Root<SharedFunctionInstanceData>> shared_function_data;
|
|
bool is_strict_mode { false };
|
|
Vector<Utf16FlyString> lexical_names;
|
|
Vector<Utf16FlyString> var_names;
|
|
struct FunctionToInitialize {
|
|
GC::Root<SharedFunctionInstanceData> shared_data;
|
|
Utf16FlyString name;
|
|
};
|
|
Vector<FunctionToInitialize> functions_to_initialize;
|
|
HashTable<Utf16FlyString> declared_function_names;
|
|
Vector<Utf16FlyString> var_scoped_names;
|
|
Vector<Utf16FlyString> annex_b_candidate_names;
|
|
Vector<Script::LexicalBinding> lexical_bindings;
|
|
};
|
|
|
|
// Result type for compile_eval().
|
|
// NB: Uses GC::Root to prevent collection while the result is in transit.
|
|
struct EvalResult {
|
|
GC::Root<Bytecode::Executable> executable;
|
|
bool is_strict_mode { false };
|
|
EvalDeclarationData declaration_data;
|
|
};
|
|
|
|
// Result type for compile_module().
|
|
// NB: Uses GC::Root to prevent collection while the result is in transit.
|
|
struct ModuleResult {
|
|
bool has_top_level_await { false };
|
|
Vector<ModuleRequest> requested_modules;
|
|
Vector<ImportEntry> import_entries;
|
|
Vector<ExportEntry> local_export_entries;
|
|
Vector<ExportEntry> indirect_export_entries;
|
|
Vector<ExportEntry> star_export_entries;
|
|
Optional<Utf16FlyString> default_export_binding_name;
|
|
Vector<Utf16FlyString> var_declared_names;
|
|
Vector<SourceTextModule::LexicalBinding> lexical_bindings;
|
|
struct FunctionToInitialize {
|
|
GC::Root<SharedFunctionInstanceData> shared_data;
|
|
Utf16FlyString name;
|
|
};
|
|
Vector<FunctionToInitialize> functions_to_initialize;
|
|
GC::Root<Bytecode::Executable> executable;
|
|
Vector<GC::Root<SharedFunctionInstanceData>> shared_function_data;
|
|
GC::Root<SharedFunctionInstanceData> tla_shared_data;
|
|
};
|
|
|
|
// Parse a program (script or module) without GC interaction. Thread-safe.
|
|
JS_API FFI::ParsedProgram* parse_program(u16 const* utf16_data, size_t length_in_code_units, ProgramType type, size_t line_number_offset = 0);
|
|
|
|
// Compile a parsed program to bytecode without touching the VM or GC. Thread-safe.
|
|
JS_API FFI::CompiledProgram* compile_parsed_program_off_thread(FFI::ParsedProgram* parsed, size_t length_in_code_units);
|
|
|
|
// Fully compile a parsed program to bytecode without touching the VM or GC. Thread-safe.
|
|
JS_API FFI::CompiledProgram* compile_parsed_program_fully_off_thread(FFI::ParsedProgram* parsed, size_t length_in_code_units);
|
|
|
|
// Check if a parsed program has errors. Does not consume the program.
|
|
JS_API bool parsed_program_has_errors(FFI::ParsedProgram const*);
|
|
|
|
// Free a parsed program without compiling it.
|
|
JS_API void free_parsed_program(FFI::ParsedProgram*);
|
|
|
|
// Free a compiled program without materializing it.
|
|
JS_API void free_compiled_program(FFI::CompiledProgram*);
|
|
|
|
// Serialize a fully compiled program into a versioned bytecode cache blob.
|
|
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);
|
|
JS_API FFI::DecodedBytecodeCacheBlob* decode_bytecode_cache_blob(Core::ImmutableBytes, ProgramType, ReadonlyBytes source_hash, NonnullRefPtr<Core::WeakEventLoopReference>);
|
|
|
|
// Return the decoded source length carried by a bytecode cache blob.
|
|
JS_API size_t decoded_bytecode_cache_source_length(FFI::DecodedBytecodeCacheBlob const*);
|
|
|
|
// Validate a decoded bytecode cache blob before materialization. Thread-safe.
|
|
JS_API bool validate_decoded_bytecode_cache_blob(FFI::DecodedBytecodeCacheBlob*, size_t source_length);
|
|
|
|
// 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 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&);
|
|
|
|
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);
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// Compile a previously parsed script. Must be called on the main thread.
|
|
// Consumes and frees the Rust ParsedProgram.
|
|
// Returns nullopt if Rust is not available.
|
|
Optional<Result<ScriptResult, Vector<ParserError>>> compile_parsed_script(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm& realm);
|
|
|
|
// Materialize a previously compiled script. Must be called on the main thread.
|
|
// Consumes and frees the Rust CompiledProgram.
|
|
Optional<Result<ScriptResult, Vector<ParserError>>> materialize_compiled_script(FFI::CompiledProgram* compiled, NonnullRefPtr<SourceCode const> source_code, Realm& realm);
|
|
|
|
// Compile a script. Returns nullopt if Rust is not available.
|
|
Optional<Result<ScriptResult, Vector<ParserError>>> compile_script(StringView source_text, Realm& realm, StringView filename, size_t line_number_offset);
|
|
|
|
// Compile eval code. Returns nullopt if Rust is not available.
|
|
// On success, the executable's name is set to "eval".
|
|
Optional<Result<EvalResult, String>> compile_eval(
|
|
PrimitiveString& code_string, VM& vm,
|
|
CallerMode strict_caller, bool in_function, bool in_method,
|
|
bool in_derived_constructor, bool in_class_field_initializer);
|
|
|
|
// Compile a previously parsed module. Must be called on the main thread.
|
|
// Consumes and frees the Rust ParsedProgram.
|
|
// Returns nullopt if Rust is not available.
|
|
Optional<Result<ModuleResult, Vector<ParserError>>> compile_parsed_module(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm& realm);
|
|
|
|
// Materialize a previously compiled module. Must be called on the main thread.
|
|
// Consumes and frees the Rust CompiledProgram.
|
|
Optional<Result<ModuleResult, Vector<ParserError>>> materialize_compiled_module(FFI::CompiledProgram* compiled, NonnullRefPtr<SourceCode const> source_code, Realm& realm);
|
|
|
|
// Compile a module. Returns nullopt if Rust is not available.
|
|
Optional<Result<ModuleResult, Vector<ParserError>>> compile_module(StringView source_text, Realm& realm, StringView filename);
|
|
|
|
// Compile a dynamic function (new Function()).
|
|
// On success, returns a SharedFunctionInstanceData with source_text set.
|
|
JS_API Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_function(
|
|
VM& vm, StringView source_text, StringView parameters_string, StringView body_parse_string,
|
|
FunctionKind kind);
|
|
|
|
// Compile a builtin JS file. Returns nullopt if Rust is not available.
|
|
Optional<Vector<GC::Root<SharedFunctionInstanceData>>> compile_builtin_file(
|
|
unsigned char const* script_text, VM& vm);
|
|
|
|
// Compile a function body for lazy compilation.
|
|
// Returns nullptr if Rust is not available or the SFD doesn't use Rust compilation.
|
|
GC::Ptr<Bytecode::Executable> compile_function(VM& vm, SharedFunctionInstanceData& shared_data, bool builtin_abstract_operations_enabled);
|
|
|
|
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);
|
|
|
|
}
|