ladybird/Libraries/LibJS/RustIntegration.cpp
Shannon Booth f27bc38aa7 Everywhere: Remove ShadowRealm support
The proposal has not seemed to progress for a while, and there is
a open issue about module imports which breaks HTML integration.
While we could probably make an AD-HOC change to fix that issue,
it is deep enough in the JS engine that I am not particularly
keen on making that change.

Until other browsers begin to make positive signals about shipping
ShadowRealms, let's remove our implementation for now.

There is still some cleanup that can be done with regard to the
HTML integration, but there are a few more items that need to be
untangled there.
2026-04-05 13:57:58 +02:00

1129 lines
42 KiB
C++

/*
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/RustIntegration.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <LibGC/DeferGC.h>
#include <LibJS/Bytecode/ClassBlueprint.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/IdentifierTable.h>
#include <LibJS/Bytecode/PropertyKeyTable.h>
#include <LibJS/Bytecode/RegexTable.h>
#include <LibJS/Bytecode/StringTable.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Intrinsics.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/RustFFI.h>
#include <LibJS/Script.h>
#include <LibJS/SourceCode.h>
extern bool JS::g_dump_ast;
extern bool JS::g_dump_ast_use_color;
using namespace JS::FFI;
namespace JS::RustIntegration {
// --- Shared helpers ---
static Utf16View utf16_view_from_bytes(uint16_t const* data, size_t len)
{
if (len == 0)
return {};
return Utf16View { reinterpret_cast<char16_t const*>(data), len };
}
static Utf16FlyString utf16_fly_from(uint16_t const* data, size_t len)
{
return Utf16FlyString::from_utf16(utf16_view_from_bytes(data, len));
}
static Utf16FlyString utf16_fly_from_raw(uint16_t const* data, size_t len)
{
if (len == 0)
return {};
return Utf16FlyString::from_utf16(utf16_view_from_bytes(data, len));
}
static Utf16String utf16_from_raw(uint16_t const* data, size_t len)
{
if (len == 0)
return {};
return Utf16String::from_utf16(utf16_view_from_bytes(data, len));
}
// --- Error collection callbacks ---
// Collects parse errors as a Vector<ParserError> (for Script/Module compilation).
static void collect_parse_errors(void* ctx, uint8_t const* message, size_t message_len, uint32_t line, uint32_t column)
{
auto& errors = *static_cast<Vector<ParserError>*>(ctx);
errors.append({
MUST(String::from_utf8({ message, message_len })),
Position { line, column, 0 },
});
}
// Collects a single parse error as a formatted String (for eval/dynamic function compilation).
static void collect_single_parse_error(void* ctx, uint8_t const* message, size_t message_len, uint32_t line, uint32_t column)
{
auto& error_message = *static_cast<String*>(ctx);
if (error_message.is_empty())
error_message = MUST(String::formatted("{} (line: {}, column: {})", MUST(String::from_utf8({ message, message_len })), line, column));
}
// --- Script GDI builder and callbacks ---
struct ScriptGdiBuilder {
ScriptResult result;
};
}
namespace JS::FFI {
extern "C" void script_gdi_push_lexical_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx)->result.lexical_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void script_gdi_push_var_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx)->result.var_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void script_gdi_push_function(void* ctx, void* sfd_ptr, uint16_t const* name, size_t len)
{
auto& builder = *static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx);
auto fn_name = JS::RustIntegration::utf16_fly_from(name, len);
builder.result.declared_function_names.set(fn_name);
auto& sfd = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
builder.result.functions_to_initialize.append({ sfd, move(fn_name) });
}
extern "C" void script_gdi_push_var_scoped_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx)->result.var_scoped_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void script_gdi_push_annex_b_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx)->result.annex_b_candidate_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void script_gdi_push_lexical_binding(void* ctx, uint16_t const* name, size_t len, bool is_constant)
{
static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx)->result.lexical_bindings.append({ JS::RustIntegration::utf16_fly_from(name, len), is_constant });
}
}
// --- Eval GDI builder and callbacks ---
namespace JS::RustIntegration {
struct EvalGdiBuilder {
GC::Ptr<Bytecode::Executable> executable;
bool is_strict_mode { false };
Vector<Utf16FlyString> var_names;
Vector<EvalDeclarationData::FunctionToInitialize> functions_to_initialize;
HashTable<Utf16FlyString> declared_function_names;
Vector<Utf16FlyString> var_scoped_names;
Vector<Utf16FlyString> annex_b_candidate_names;
Vector<EvalDeclarationData::LexicalBinding> lexical_bindings;
EvalResult to_result()
{
EvalResult result;
result.executable = executable;
result.is_strict_mode = is_strict_mode;
result.declaration_data.var_names = move(var_names);
result.declaration_data.functions_to_initialize = move(functions_to_initialize);
result.declaration_data.declared_function_names = move(declared_function_names);
result.declaration_data.var_scoped_names = move(var_scoped_names);
result.declaration_data.annex_b_candidate_names = move(annex_b_candidate_names);
result.declaration_data.lexical_bindings = move(lexical_bindings);
return result;
}
};
}
namespace JS::FFI {
extern "C" void eval_gdi_set_strict(void* ctx, bool is_strict)
{
static_cast<JS::RustIntegration::EvalGdiBuilder*>(ctx)->is_strict_mode = is_strict;
}
extern "C" void eval_gdi_push_var_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::EvalGdiBuilder*>(ctx)->var_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void eval_gdi_push_function(void* ctx, void* sfd_ptr, uint16_t const* name, size_t len)
{
auto& builder = *static_cast<JS::RustIntegration::EvalGdiBuilder*>(ctx);
auto fn_name = JS::RustIntegration::utf16_fly_from(name, len);
builder.declared_function_names.set(fn_name);
auto& sfd = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
builder.functions_to_initialize.append({ sfd, move(fn_name) });
}
extern "C" void eval_gdi_push_var_scoped_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::EvalGdiBuilder*>(ctx)->var_scoped_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void eval_gdi_push_annex_b_name(void* ctx, uint16_t const* name, size_t len)
{
static_cast<JS::RustIntegration::EvalGdiBuilder*>(ctx)->annex_b_candidate_names.append(JS::RustIntegration::utf16_fly_from(name, len));
}
extern "C" void eval_gdi_push_lexical_binding(void* ctx, uint16_t const* name, size_t len, bool is_constant)
{
static_cast<JS::RustIntegration::EvalGdiBuilder*>(ctx)->lexical_bindings.append({ JS::RustIntegration::utf16_fly_from(name, len), is_constant });
}
}
// --- Module builder and callbacks ---
namespace JS::RustIntegration {
struct ModuleBuilder {
ModuleResult result;
};
static Vector<ImportAttribute> attributes_from_ffi(FFIUtf16Slice const* keys, FFIUtf16Slice const* values, size_t count)
{
Vector<ImportAttribute> attributes;
for (size_t i = 0; i < count; ++i)
attributes.empend(utf16_from_raw(keys[i].data, keys[i].length), utf16_from_raw(values[i].data, values[i].length));
return attributes;
}
static Optional<ModuleRequest> module_request_from_ffi(uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
if (specifier == nullptr || specifier_len == 0)
return {};
auto attributes = attributes_from_ffi(attribute_keys, attribute_values, attribute_count);
if (attributes.is_empty())
return ModuleRequest { utf16_fly_from_raw(specifier, specifier_len) };
return ModuleRequest { utf16_fly_from_raw(specifier, specifier_len), move(attributes) };
}
}
extern "C" {
static void module_set_has_top_level_await(void* ctx, bool value)
{
static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.has_top_level_await = value;
}
static void module_push_import_entry(void* ctx,
uint16_t const* import_name, size_t import_name_len, bool is_namespace,
uint16_t const* local_name, size_t local_name_len,
uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
auto* builder = static_cast<JS::RustIntegration::ModuleBuilder*>(ctx);
Optional<Utf16FlyString> import_name_opt;
if (!is_namespace)
import_name_opt = JS::RustIntegration::utf16_fly_from_raw(import_name, import_name_len);
JS::ImportEntry entry { move(import_name_opt), JS::RustIntegration::utf16_fly_from_raw(local_name, local_name_len) };
entry.m_module_request = JS::RustIntegration::module_request_from_ffi(specifier, specifier_len, attribute_keys, attribute_values, attribute_count);
builder->result.import_entries.append(move(entry));
}
static void module_push_export_entry(Vector<JS::ExportEntry>& list, uint8_t kind,
uint16_t const* export_name, size_t export_name_len,
uint16_t const* local_or_import_name, size_t local_or_import_name_len,
uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
Optional<Utf16FlyString> en;
if (export_name)
en = JS::RustIntegration::utf16_fly_from_raw(export_name, export_name_len);
Optional<Utf16FlyString> lin;
if (local_or_import_name)
lin = JS::RustIntegration::utf16_fly_from_raw(local_or_import_name, local_or_import_name_len);
JS::ExportEntry entry { static_cast<JS::ExportEntry::Kind>(kind), move(en), move(lin) };
entry.m_module_request = JS::RustIntegration::module_request_from_ffi(specifier, specifier_len, attribute_keys, attribute_values, attribute_count);
list.append(move(entry));
}
static void module_push_local_export(void* ctx, uint8_t kind,
uint16_t const* export_name, size_t export_name_len,
uint16_t const* local_or_import_name, size_t local_or_import_name_len,
uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
module_push_export_entry(static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.local_export_entries, kind,
export_name, export_name_len, local_or_import_name, local_or_import_name_len,
specifier, specifier_len, attribute_keys, attribute_values, attribute_count);
}
static void module_push_indirect_export(void* ctx, uint8_t kind,
uint16_t const* export_name, size_t export_name_len,
uint16_t const* local_or_import_name, size_t local_or_import_name_len,
uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
module_push_export_entry(static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.indirect_export_entries, kind,
export_name, export_name_len, local_or_import_name, local_or_import_name_len,
specifier, specifier_len, attribute_keys, attribute_values, attribute_count);
}
static void module_push_star_export(void* ctx, uint8_t kind,
uint16_t const* export_name, size_t export_name_len,
uint16_t const* local_or_import_name, size_t local_or_import_name_len,
uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
module_push_export_entry(static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.star_export_entries, kind,
export_name, export_name_len, local_or_import_name, local_or_import_name_len,
specifier, specifier_len, attribute_keys, attribute_values, attribute_count);
}
static void module_push_requested_module(void* ctx,
uint16_t const* specifier, size_t specifier_len,
FFIUtf16Slice const* attribute_keys, FFIUtf16Slice const* attribute_values, size_t attribute_count)
{
auto* builder = static_cast<JS::RustIntegration::ModuleBuilder*>(ctx);
auto attributes = JS::RustIntegration::attributes_from_ffi(attribute_keys, attribute_values, attribute_count);
if (attributes.is_empty())
builder->result.requested_modules.empend(JS::RustIntegration::utf16_fly_from_raw(specifier, specifier_len));
else
builder->result.requested_modules.empend(JS::RustIntegration::utf16_fly_from_raw(specifier, specifier_len), move(attributes));
}
static void module_set_default_export_binding(void* ctx, uint16_t const* name, size_t name_len)
{
static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.default_export_binding_name = JS::RustIntegration::utf16_fly_from_raw(name, name_len);
}
static void module_push_var_name(void* ctx, uint16_t const* name, size_t name_len)
{
static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.var_declared_names.append(JS::RustIntegration::utf16_fly_from_raw(name, name_len));
}
static void module_push_function(void* ctx, void* sfd_ptr, uint16_t const* name, size_t name_len)
{
auto& shared = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.functions_to_initialize.append({ shared, JS::RustIntegration::utf16_fly_from_raw(name, name_len) });
}
static void module_push_lexical_binding(void* ctx, uint16_t const* name, size_t name_len, bool is_constant, int32_t function_index)
{
static_cast<JS::RustIntegration::ModuleBuilder*>(ctx)->result.lexical_bindings.append({
.name = JS::RustIntegration::utf16_fly_from_raw(name, name_len),
.is_constant = is_constant,
.function_index = function_index,
});
}
} // extern "C"
// --- Builtin file callback ---
namespace JS::RustIntegration {
static void collect_builtin_function(void* ctx, void* sfd_ptr, uint16_t const*, size_t)
{
auto& list = *static_cast<Vector<GC::Root<SharedFunctionInstanceData>>*>(ctx);
list.append(*static_cast<SharedFunctionInstanceData*>(sfd_ptr));
}
// --- Compile functions ---
bool rust_pipeline_available()
{
return true;
}
ParsedProgram* parse_program(u16 const* utf16_data, size_t length_in_code_units, ProgramType type, size_t line_number_offset)
{
return rust_parse_program(utf16_data, length_in_code_units, static_cast<u8>(type), line_number_offset, g_dump_ast, g_dump_ast_use_color);
}
bool parsed_program_has_errors(ParsedProgram const* parsed)
{
return rust_parsed_program_has_errors(const_cast<ParsedProgram*>(parsed));
}
void free_parsed_program(ParsedProgram* parsed)
{
rust_free_parsed_program(parsed);
}
Optional<Result<ScriptResult, Vector<ParserError>>> compile_parsed_script(ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
{
if (!parsed)
return {};
if (rust_parsed_program_has_errors(parsed)) {
Vector<ParserError> parse_errors;
rust_parsed_program_take_errors(parsed, &parse_errors, collect_parse_errors);
rust_free_parsed_program(parsed);
return parse_errors;
}
auto length = source_code->length_in_code_units();
GC::DeferGC defer_gc(realm.vm().heap());
ScriptGdiBuilder builder;
void* exec_ptr = rust_compile_parsed_script(parsed, &realm.vm(), source_code.ptr(), &builder, length);
if (!exec_ptr)
return Vector<ParserError> {};
builder.result.executable = static_cast<Bytecode::Executable*>(exec_ptr);
return builder.result;
}
Optional<Result<ScriptResult, Vector<ParserError>>> compile_script(StringView source_text, Realm& realm, StringView filename, size_t line_number_offset)
{
auto source_code = SourceCode::create(
String::from_utf8(filename).release_value_but_fixme_should_propagate_errors(),
Utf16String::from_utf8(source_text));
auto const* source_ptr = source_code->utf16_data();
auto length = source_code->length_in_code_units();
auto* parsed = rust_parse_program(source_ptr, length, static_cast<u8>(ProgramType::Script), line_number_offset, g_dump_ast, g_dump_ast_use_color);
return compile_parsed_script(parsed, source_code, realm);
}
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)
{
auto source_code = SourceCode::create({}, code_string.utf16_string());
auto const& code_view = source_code->code_view();
auto length = code_view.length_in_code_units();
GC::DeferGC defer_gc(vm.heap());
EvalGdiBuilder builder;
String parse_error;
auto const* source_ptr = source_code->utf16_data();
void* exec_ptr = rust_compile_eval(source_ptr, length, &vm, source_code.ptr(), &builder,
strict_caller == CallerMode::Strict,
in_function, in_method, in_derived_constructor, in_class_field_initializer,
&parse_error, collect_single_parse_error, nullptr, nullptr);
if (!exec_ptr)
return parse_error;
builder.executable = static_cast<Bytecode::Executable*>(exec_ptr);
builder.executable->name = "eval"_utf16_fly_string;
auto result = builder.to_result();
// If the caller is strict, the eval is always strict regardless of what Rust reported.
if (strict_caller == CallerMode::Strict)
result.is_strict_mode = true;
return result;
}
Optional<Result<ModuleResult, Vector<ParserError>>> compile_parsed_module(ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm& realm)
{
if (!parsed)
return {};
if (rust_parsed_program_has_errors(parsed)) {
Vector<ParserError> parse_errors;
rust_parsed_program_take_errors(parsed, &parse_errors, collect_parse_errors);
rust_free_parsed_program(parsed);
return parse_errors;
}
auto length = source_code->length_in_code_units();
GC::DeferGC defer_gc(realm.vm().heap());
ModuleBuilder builder;
ModuleCallbacks callbacks {
.set_has_top_level_await = module_set_has_top_level_await,
.push_import_entry = module_push_import_entry,
.push_local_export = module_push_local_export,
.push_indirect_export = module_push_indirect_export,
.push_star_export = module_push_star_export,
.push_requested_module = module_push_requested_module,
.set_default_export_binding = module_set_default_export_binding,
.push_var_name = module_push_var_name,
.push_function = module_push_function,
.push_lexical_binding = module_push_lexical_binding,
};
void* tla_executable = nullptr;
void* exec_ptr = rust_compile_parsed_module(parsed, &realm.vm(), source_code.ptr(),
&builder, &callbacks, &tla_executable, length);
if (!exec_ptr && !tla_executable)
return Vector<ParserError> {};
if (tla_executable) {
auto& vm = realm.vm();
auto* tla_exec = static_cast<Bytecode::Executable*>(tla_executable);
builder.result.tla_shared_data = vm.heap().allocate<SharedFunctionInstanceData>(
vm, FunctionKind::Async,
"module code with top-level await"_utf16_fly_string,
0, 0, true, false, true,
Vector<Utf16FlyString> {}, nullptr);
builder.result.tla_shared_data->m_is_module_wrapper = true;
builder.result.tla_shared_data->m_uses_this = true;
builder.result.tla_shared_data->m_function_environment_needed = true;
builder.result.tla_shared_data->m_executable = tla_exec;
} else {
builder.result.executable = static_cast<Bytecode::Executable*>(exec_ptr);
}
return builder.result;
}
Optional<Result<ModuleResult, Vector<ParserError>>> compile_module(StringView source_text, Realm& realm, StringView filename)
{
auto source_code = SourceCode::create(String::from_utf8(filename).release_value_but_fixme_should_propagate_errors(), Utf16String::from_utf8(source_text));
auto const* source_ptr = source_code->utf16_data();
auto length = source_code->length_in_code_units();
auto* parsed = rust_parse_program(source_ptr, length, static_cast<u8>(ProgramType::Module), 0, g_dump_ast, g_dump_ast_use_color);
return compile_parsed_module(parsed, source_code, realm);
}
Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_function(
VM& vm, StringView source_text, StringView parameters_string, StringView body_parse_string,
FunctionKind kind)
{
auto source_code = SourceCode::create({}, Utf16String::from_utf8(source_text));
auto const& code_view = source_code->code_view();
auto full_length = code_view.length_in_code_units();
auto params_utf16 = Utf16String::from_utf8(parameters_string);
auto body_utf16 = Utf16String::from_utf8(body_parse_string);
auto prepare_utf16 = [](Utf16View const& view, Vector<u16>& buf) -> u16 const* {
if (view.has_ascii_storage()) {
auto ascii = view.ascii_span();
buf.ensure_capacity(view.length_in_code_units());
for (size_t i = 0; i < view.length_in_code_units(); ++i)
buf.unchecked_append(static_cast<u16>(ascii[i]));
return buf.data();
}
return reinterpret_cast<u16 const*>(view.utf16_span().data());
};
Vector<u16> full_buf, params_buf, body_buf;
auto const* full_data = prepare_utf16(code_view, full_buf);
auto const* params_data = prepare_utf16(params_utf16.utf16_view(), params_buf);
auto const* body_data = prepare_utf16(body_utf16.utf16_view(), body_buf);
GC::DeferGC defer_gc(vm.heap());
String parse_error;
void* sfd_ptr = rust_compile_dynamic_function(
full_data, full_length,
params_data, params_utf16.utf16_view().length_in_code_units(),
body_data, body_utf16.utf16_view().length_in_code_units(),
&vm, source_code.ptr(),
static_cast<u8>(kind),
&parse_error, collect_single_parse_error,
nullptr, nullptr);
if (!sfd_ptr)
return parse_error;
auto& function_data = *static_cast<SharedFunctionInstanceData*>(sfd_ptr);
function_data.m_source_text_owner = Utf16String::from_utf8(source_text);
function_data.m_source_text = function_data.m_source_text_owner.utf16_view();
return GC::Ref<SharedFunctionInstanceData> { function_data };
}
Optional<Vector<GC::Root<SharedFunctionInstanceData>>> compile_builtin_file(
unsigned char const* script_text, VM& vm)
{
auto script_text_as_utf16 = Utf16String::from_utf8_without_validation({ script_text, strlen(reinterpret_cast<char const*>(script_text)) });
auto code = SourceCode::create("BuiltinFile"_string, move(script_text_as_utf16));
auto const& code_view = code->code_view();
auto length = code_view.length_in_code_units();
GC::DeferGC defer_gc(vm.heap());
Vector<GC::Root<SharedFunctionInstanceData>> shared_data_list;
auto const* source_ptr = code->utf16_data();
rust_compile_builtin_file(source_ptr, length, &vm, code.ptr(), &shared_data_list, collect_builtin_function,
nullptr, nullptr);
return shared_data_list;
}
GC::Ptr<Bytecode::Executable> compile_function(VM& vm, SharedFunctionInstanceData& shared_data, bool builtin_abstract_operations_enabled)
{
if (!shared_data.m_use_rust_compilation)
return nullptr;
VERIFY(shared_data.m_rust_function_ast);
GC::DeferGC defer_gc(vm.heap());
auto const* source_ptr = shared_data.m_source_code->utf16_data();
auto* exec = static_cast<Bytecode::Executable*>(rust_compile_function(
&vm,
shared_data.m_source_code.ptr(),
source_ptr,
shared_data.m_source_code->length_in_code_units(),
&shared_data,
shared_data.m_rust_function_ast,
builtin_abstract_operations_enabled));
shared_data.m_rust_function_ast = nullptr;
return exec;
}
void free_function_ast(void* ast)
{
if (ast)
rust_free_function_ast(ast);
}
}
// --- FFI factory functions (called by Rust to create C++ objects) ---
namespace JS::FFI {
struct RustCompiledRegex {
String parsed_pattern;
};
static Utf16View view_from_ffi(FFIUtf16Slice slice)
{
return JS::RustIntegration::utf16_view_from_bytes(slice.data, slice.length);
}
static Utf16String utf16_from_ffi(FFIUtf16Slice slice)
{
return Utf16String::from_utf16(view_from_ffi(slice));
}
static Utf16FlyString utf16_fly_from_ffi(FFIUtf16Slice slice)
{
return Utf16FlyString::from_utf16(view_from_ffi(slice));
}
static JS::Value decode_constant(JS::VM& vm, uint8_t const*& cursor, uint8_t const* end)
{
VERIFY(cursor < end);
auto const tag = *cursor++;
switch (static_cast<ConstantTag>(tag)) {
case ConstantTag::Number: {
VERIFY(cursor + 8 <= end);
double value;
memcpy(&value, cursor, 8);
cursor += 8;
return JS::Value(value);
}
case ConstantTag::BooleanTrue:
return JS::Value(true);
case ConstantTag::BooleanFalse:
return JS::Value(false);
case ConstantTag::Null:
return JS::js_null();
case ConstantTag::Undefined:
return JS::js_undefined();
case ConstantTag::Empty:
return JS::js_special_empty_value();
case ConstantTag::String: {
VERIFY(cursor + 4 <= end);
uint32_t len;
memcpy(&len, cursor, 4);
cursor += 4;
VERIFY(cursor + len * 2 <= end);
if (len == 0)
return JS::PrimitiveString::create(vm, Utf16String {});
// NB: cursor may not be 2-byte aligned, so copy to an aligned buffer.
Vector<char16_t> aligned_buf;
aligned_buf.resize(len);
memcpy(aligned_buf.data(), cursor, len * 2);
auto str = Utf16String::from_utf16(Utf16View(aligned_buf.data(), len));
cursor += len * 2;
return JS::PrimitiveString::create(vm, move(str));
}
case ConstantTag::BigInt: {
VERIFY(cursor + 4 <= end);
uint32_t len;
memcpy(&len, cursor, 4);
cursor += 4;
VERIFY(cursor + len <= end);
auto ascii = StringView(reinterpret_cast<char const*>(cursor), len);
cursor += len;
auto integer = [&] {
if (len >= 3 && ascii[0] == '0') {
if (ascii[1] == 'x' || ascii[1] == 'X')
return MUST(Crypto::SignedBigInteger::from_base(16, ascii.substring_view(2)));
if (ascii[1] == 'o' || ascii[1] == 'O')
return MUST(Crypto::SignedBigInteger::from_base(8, ascii.substring_view(2)));
if (ascii[1] == 'b' || ascii[1] == 'B')
return MUST(Crypto::SignedBigInteger::from_base(2, ascii.substring_view(2)));
}
return MUST(Crypto::SignedBigInteger::from_base(10, ascii));
}();
return JS::BigInt::create(vm, move(integer));
}
case ConstantTag::RawValue: {
VERIFY(cursor + 8 <= end);
JS::Value value;
memcpy(&value, cursor, 8);
cursor += 8;
return value;
}
default:
VERIFY_NOT_REACHED();
}
}
extern "C" void* rust_create_executable(
void* vm_ptr,
void const* source_code_ptr,
FFIExecutableData const* data)
{
auto& vm = *static_cast<JS::VM*>(vm_ptr);
auto& source_code = *static_cast<JS::SourceCode const*>(source_code_ptr);
// Build bytecode vector
Vector<u8> bytecode_vec;
bytecode_vec.append(data->bytecode, data->bytecode_length);
// Build identifier table
auto ident_table = make<JS::Bytecode::IdentifierTable>();
for (size_t i = 0; i < data->identifier_count; ++i) {
ident_table->insert(utf16_fly_from_ffi(data->identifier_table[i]));
}
// Build property key table
auto prop_key_table = make<JS::Bytecode::PropertyKeyTable>();
for (size_t i = 0; i < data->property_key_count; ++i) {
prop_key_table->insert(utf16_fly_from_ffi(data->property_key_table[i]));
}
// Build string table
auto str_table = make<JS::Bytecode::StringTable>();
for (size_t i = 0; i < data->string_count; ++i) {
str_table->insert(utf16_from_ffi(data->string_table[i]));
}
// Build regex table from pre-compiled regex objects.
// NB: The regex table is no longer read at runtime (new_regexp uses pattern+flags directly),
// but we still need to iterate and free the RustCompiledRegex objects.
auto regex_tbl = make<JS::Bytecode::RegexTable>();
for (size_t i = 0; i < data->regex_count; ++i) {
auto* cr = static_cast<RustCompiledRegex*>(data->compiled_regexes[i]);
delete cr;
}
// Decode constants
Vector<JS::Value> constants_vec;
constants_vec.ensure_capacity(data->constants_count);
auto const* cursor = data->constants_data;
auto const* end = data->constants_data + data->constants_data_length;
for (size_t i = 0; i < data->constants_count; ++i) {
constants_vec.append(decode_constant(vm, cursor, end));
}
VERIFY(cursor == end);
// Create executable
auto executable = vm.heap().allocate<JS::Bytecode::Executable>(
move(bytecode_vec),
move(ident_table),
move(prop_key_table),
move(str_table),
move(regex_tbl),
move(constants_vec),
source_code,
data->property_lookup_cache_count,
data->global_variable_cache_count,
data->template_object_cache_count,
data->object_shape_cache_count,
data->number_of_registers,
data->is_strict ? JS::Strict::Yes : JS::Strict::No);
// Set exception handlers
for (size_t i = 0; i < data->exception_handler_count; ++i) {
executable->exception_handlers.append({
data->exception_handlers[i].start_offset,
data->exception_handlers[i].end_offset,
data->exception_handlers[i].handler_offset,
});
}
// Set source map
for (size_t i = 0; i < data->source_map_count; ++i) {
executable->source_map.append({
data->source_map[i].bytecode_offset,
{ data->source_map[i].source_start, data->source_map[i].source_end },
});
}
// Set basic block offsets
for (size_t i = 0; i < data->basic_block_count; ++i) {
executable->basic_block_start_offsets.append(data->basic_block_offsets[i]);
}
// Set local variable names
for (size_t i = 0; i < data->local_variable_count; ++i) {
executable->local_variable_names.append({
.name = utf16_fly_from_ffi(data->local_variable_names[i]),
.declaration_kind = JS::LocalVariable::DeclarationKind::Var,
});
}
// Set layout indices
executable->local_index_base = data->number_of_registers;
executable->argument_index_base = data->number_of_registers + data->local_variable_count + data->constants_count;
executable->registers_and_locals_count = data->number_of_registers + data->local_variable_count;
executable->registers_and_locals_and_constants_count = data->number_of_registers + data->local_variable_count + data->constants_count;
// Set length identifier (for GetLength optimization)
if (data->length_identifier.has_value)
executable->length_identifier = JS::Bytecode::PropertyKeyTableIndex(data->length_identifier.value);
// Set shared function data (inner function definitions)
for (size_t i = 0; i < data->shared_function_data_count; ++i) {
auto* sfd = const_cast<JS::SharedFunctionInstanceData*>(
static_cast<JS::SharedFunctionInstanceData const*>(data->shared_function_data[i]));
executable->shared_function_data.append(sfd);
}
// Set class blueprints (move from heap-allocated objects)
for (size_t i = 0; i < data->class_blueprint_count; ++i) {
auto* bp = static_cast<JS::Bytecode::ClassBlueprint*>(data->class_blueprints[i]);
executable->class_blueprints.append(move(*bp));
delete bp;
}
executable->fixup_cache_pointers();
return executable.ptr();
}
extern "C" void* rust_create_sfd(
void* vm_ptr,
void const* source_code_ptr,
FFISharedFunctionData const* data)
{
auto& vm = *static_cast<JS::VM*>(vm_ptr);
auto& source_code = *static_cast<JS::SourceCode const*>(source_code_ptr);
auto fn_name = data->name_len > 0
? Utf16FlyString::from_utf16(Utf16View(reinterpret_cast<char16_t const*>(data->name), data->name_len))
: Utf16FlyString {};
Vector<Utf16FlyString> mapped_param_names;
if (data->has_simple_parameter_list) {
mapped_param_names.ensure_capacity(data->parameter_name_count);
for (size_t i = 0; i < data->parameter_name_count; ++i)
mapped_param_names.append(utf16_fly_from_ffi(data->parameter_names[i]));
}
auto shared = vm.heap().allocate<JS::SharedFunctionInstanceData>(
vm,
static_cast<JS::FunctionKind>(data->function_kind),
move(fn_name),
data->function_length,
data->formal_parameter_count,
data->strict,
data->is_arrow,
data->has_simple_parameter_list,
move(mapped_param_names),
data->rust_function_ast);
// Set parsing insights that must be available before lazy compilation.
shared->m_uses_this = data->uses_this;
if (data->uses_this_from_environment)
shared->m_function_environment_needed = true;
// Set source text as a view into the original source code.
shared->m_source_code = &source_code;
if (data->source_text_length > 0) {
auto const& code_view = source_code.code_view();
shared->m_source_text = code_view.substring_view(data->source_text_offset, data->source_text_length);
}
return shared.ptr();
}
extern "C" void rust_sfd_set_metadata(
void* sfd_ptr,
bool uses_this,
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_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;
}
extern "C" void rust_sfd_set_class_field_initializer_name(
void* sfd_ptr,
uint16_t const* name,
size_t name_len,
bool is_private)
{
auto& shared = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
auto utf16_name = Utf16FlyString::from_utf16(JS::RustIntegration::utf16_view_from_bytes(name, name_len));
if (is_private) {
shared.m_class_field_initializer_name = JS::PrivateName(0, utf16_name);
} else {
shared.m_class_field_initializer_name = JS::PropertyKey(utf16_name.to_utf16_string());
}
}
extern "C" void* rust_create_class_blueprint(
void* vm_ptr,
void const* source_code_ptr,
uint16_t const* name,
size_t name_len,
size_t source_text_offset,
size_t source_text_len,
uint32_t constructor_sfd_index,
bool has_super_class,
bool has_name,
FFIClassElement const* elements,
size_t element_count)
{
auto* blueprint = new JS::Bytecode::ClassBlueprint();
blueprint->constructor_shared_function_data_index = constructor_sfd_index;
blueprint->has_super_class = has_super_class;
blueprint->has_name = has_name;
if (name_len > 0)
blueprint->name = Utf16FlyString::from_utf16(JS::RustIntegration::utf16_view_from_bytes(name, name_len));
// Store source text as a view into the SourceCode buffer.
if (source_text_len > 0) {
auto& source_code = *static_cast<JS::SourceCode const*>(source_code_ptr);
auto const& code_view = source_code.code_view();
blueprint->source_text = code_view.substring_view(source_text_offset, source_text_len);
}
for (size_t i = 0; i < element_count; ++i) {
auto const& elem = elements[i];
JS::Bytecode::ClassElementDescriptor desc;
desc.kind = static_cast<JS::Bytecode::ClassElementDescriptor::Kind>(elem.kind);
desc.is_static = elem.is_static;
desc.is_private = elem.is_private;
if (elem.private_identifier_len > 0)
desc.private_identifier = Utf16FlyString::from_utf16(JS::RustIntegration::utf16_view_from_bytes(elem.private_identifier, elem.private_identifier_len));
if (elem.shared_function_data_index.has_value)
desc.shared_function_data_index = elem.shared_function_data_index.value;
desc.has_initializer = elem.has_initializer;
switch (elem.literal_value_kind) {
case LiteralValueKind::None:
break;
case LiteralValueKind::Number:
desc.literal_value = JS::Value(elem.literal_value_number);
break;
case LiteralValueKind::BooleanTrue:
desc.literal_value = JS::Value(true);
break;
case LiteralValueKind::BooleanFalse:
desc.literal_value = JS::Value(false);
break;
case LiteralValueKind::Null:
desc.literal_value = JS::js_null();
break;
case LiteralValueKind::String: {
auto& vm = *static_cast<JS::VM*>(vm_ptr);
auto str_view = JS::RustIntegration::utf16_view_from_bytes(elem.literal_value_string, elem.literal_value_string_len);
desc.literal_value = JS::Value(JS::PrimitiveString::create(vm, str_view));
break;
}
}
blueprint->elements.append(desc);
}
return blueprint;
}
extern "C" void module_sfd_set_name(
void* sfd_ptr,
uint16_t const* name,
size_t name_len)
{
auto& shared = *static_cast<JS::SharedFunctionInstanceData*>(sfd_ptr);
shared.m_name = Utf16FlyString::from_utf16(JS::RustIntegration::utf16_view_from_bytes(name, name_len));
}
extern "C" void* rust_compile_regex(
uint16_t const* pattern_data, size_t pattern_len,
uint16_t const* flags_data, size_t flags_len,
char const** error_out)
{
*error_out = nullptr;
auto pattern = JS::RustIntegration::utf16_view_from_bytes(pattern_data, pattern_len);
auto flags_view = JS::RustIntegration::utf16_view_from_bytes(flags_data, flags_len);
// Extract unicode/unicode_sets from flags for parse_regex_pattern.
bool is_unicode = false;
bool is_unicode_sets = false;
for (size_t i = 0; i < flags_view.length_in_code_units(); ++i) {
auto ch = flags_view.code_unit_at(i);
if (ch == 'u')
is_unicode = true;
else if (ch == 'v')
is_unicode_sets = true;
}
auto parsed_pattern = JS::parse_regex_pattern(pattern, is_unicode, is_unicode_sets);
if (parsed_pattern.is_error()) {
auto msg = MUST(String::formatted("RegExp compile error: {}", parsed_pattern.release_error().error));
auto* buf = static_cast<char*>(malloc(msg.byte_count() + 1));
memcpy(buf, msg.bytes().data(), msg.byte_count());
buf[msg.byte_count()] = '\0';
*error_out = buf;
return nullptr;
}
auto pattern_str = parsed_pattern.release_value();
// Build compile flags from the flag characters.
regex::ECMAScriptCompileFlags compile_flags {};
for (size_t i = 0; i < flags_view.length_in_code_units(); ++i) {
auto ch = flags_view.code_unit_at(i);
switch (ch) {
case 'g':
compile_flags.global = true;
break;
case 'i':
compile_flags.ignore_case = true;
break;
case 'm':
compile_flags.multiline = true;
break;
case 's':
compile_flags.dot_all = true;
break;
case 'u':
compile_flags.unicode = true;
break;
case 'v':
compile_flags.unicode_sets = true;
break;
case 'y':
compile_flags.sticky = true;
break;
case 'd':
compile_flags.has_indices = true;
break;
default:
break;
}
}
auto compiled = regex::ECMAScriptRegex::compile(pattern_str.bytes_as_string_view(), compile_flags);
if (compiled.is_error()) {
auto msg = MUST(String::formatted("RegExp compile error: {}", compiled.release_error()));
auto* buf = static_cast<char*>(malloc(msg.byte_count() + 1));
memcpy(buf, msg.bytes().data(), msg.byte_count());
buf[msg.byte_count()] = '\0';
*error_out = buf;
return nullptr;
}
return new RustCompiledRegex { move(pattern_str) };
}
extern "C" void rust_free_compiled_regex(void* ptr)
{
delete static_cast<RustCompiledRegex*>(ptr);
}
extern "C" void rust_free_error_string(char const* str)
{
free(const_cast<char*>(str));
}
extern "C" size_t rust_number_to_utf16(double value, uint16_t* buffer, size_t buffer_len)
{
auto str = JS::number_to_utf16_string(value);
auto view = str.utf16_view();
auto len = min(view.length_in_code_units(), buffer_len);
for (size_t i = 0; i < len; ++i)
buffer[i] = view.code_unit_at(i);
return len;
}
// FIXME: This FFI workaround exists only to match C++ float-to-string
// formatting in the Rust AST dump. Once the C++ pipeline is
// removed, this can be deleted and the Rust side can use its own
// formatting without needing to match C++.
extern "C" size_t rust_format_double(double value, uint8_t* buffer, size_t buffer_len)
{
auto str = MUST(String::formatted("{}", value));
auto bytes = str.bytes();
auto len = min(bytes.size(), buffer_len);
memcpy(buffer, bytes.data(), len);
return len;
}
extern "C" uint64_t get_well_known_symbol(void* vm_ptr, WellKnownSymbolKind symbol_id)
{
auto& vm = *static_cast<JS::VM*>(vm_ptr);
JS::Value value;
switch (symbol_id) {
case WellKnownSymbolKind::SymbolIterator:
value = vm.well_known_symbol_iterator();
break;
case WellKnownSymbolKind::SymbolAsyncIterator:
value = vm.well_known_symbol_async_iterator();
break;
default:
VERIFY_NOT_REACHED();
}
return value.encoded();
}
extern "C" uint64_t get_abstract_operation_function(void* vm_ptr, uint16_t const* name, size_t name_len)
{
auto& vm = *static_cast<JS::VM*>(vm_ptr);
auto& intrinsics = vm.current_realm()->intrinsics();
auto name_view = JS::RustIntegration::utf16_view_from_bytes(name, name_len);
auto name_str = MUST(name_view.to_utf8());
#define __JS_ENUMERATE(snake_name, functionName, length) \
if (name_str == #functionName##sv) \
return JS::Value(intrinsics.snake_name##_abstract_operation_function().ptr()).encoded();
JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS
#undef __JS_ENUMERATE
VERIFY_NOT_REACHED();
}
}