LibJS: Add alternative source-to-bytecode pipeline in Rust

Implement a complete Rust reimplementation of the LibJS frontend:
lexer, parser, AST, scope collector, and bytecode code generator.

The Rust pipeline is built via Corrosion (CMake-Cargo bridge) and
linked into LibJS as a static library. It is gated behind a build
flag (ENABLE_RUST, on by default except on Windows) and two runtime
environment variables:

- LIBJS_CPP: Use the C++ pipeline instead of Rust
- LIBJS_COMPARE_PIPELINES=1: Run both pipelines in lockstep,
  aborting on any difference in AST or bytecode generated.

The C++ side communicates with Rust through a C FFI layer
(RustIntegration.cpp/h) that passes source text to Rust and receives
a populated Executable back via a BytecodeFactory interface.
This commit is contained in:
Andreas Kling 2026-02-23 11:50:46 +01:00 committed by Andreas Kling
parent 8bf1d749a1
commit 6cdfbd01a6
Notes: github-actions[bot] 2026-02-24 08:41:00 +00:00
43 changed files with 28402 additions and 148 deletions

View file

@ -7,6 +7,7 @@
#include <AK/Debug.h>
#include <AK/QuickSort.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
@ -15,6 +16,9 @@
#include <LibJS/Runtime/ModuleEnvironment.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/RustIntegration.h>
#include <LibJS/Script.h>
#include <LibJS/SourceCode.h>
#include <LibJS/SourceTextModule.h>
namespace JS {
@ -164,6 +168,29 @@ SourceTextModule::SourceTextModule(Realm& realm, StringView filename, Script::Ho
}
}
SourceTextModule::SourceTextModule(Realm& realm, StringView filename, Script::HostDefined* host_defined, bool has_top_level_await,
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<LexicalBinding> lexical_bindings,
Vector<FunctionToInitialize> functions_to_initialize,
GC::Ptr<Bytecode::Executable> executable,
GC::Ptr<SharedFunctionInstanceData> tla_shared_data)
: CyclicModule(realm, filename, has_top_level_await, move(requested_modules), host_defined)
, m_execution_context(ExecutionContext::create(0, 0, 0))
, m_import_entries(move(import_entries))
, m_local_export_entries(move(local_export_entries))
, m_indirect_export_entries(move(indirect_export_entries))
, m_star_export_entries(move(star_export_entries))
, m_var_declared_names(move(var_declared_names))
, m_lexical_bindings(move(lexical_bindings))
, m_functions_to_initialize(move(functions_to_initialize))
, m_default_export_binding_name(move(default_export_binding_name))
, m_executable(executable)
, m_tla_shared_data(tla_shared_data)
{
}
SourceTextModule::~SourceTextModule() = default;
void SourceTextModule::visit_edges(Cell::Visitor& visitor)
@ -180,6 +207,25 @@ void SourceTextModule::visit_edges(Cell::Visitor& visitor)
// 16.2.1.7.1 ParseModule ( sourceText, realm, hostDefined ), https://tc39.es/ecma262/#sec-parsemodule
Result<GC::Ref<SourceTextModule>, Vector<ParserError>> SourceTextModule::parse(StringView source_text, Realm& realm, StringView filename, Script::HostDefined* host_defined)
{
auto rust_result = RustIntegration::compile_module(source_text, realm, filename);
if (rust_result.has_value()) {
if (rust_result->is_error())
return rust_result->release_error();
auto& module_result = rust_result->value();
Vector<FunctionToInitialize> functions_to_initialize;
functions_to_initialize.ensure_capacity(module_result.functions_to_initialize.size());
for (auto& f : module_result.functions_to_initialize)
functions_to_initialize.append({ *f.shared_data, move(f.name) });
return realm.heap().allocate<SourceTextModule>(
realm, filename, host_defined, module_result.has_top_level_await,
move(module_result.requested_modules), move(module_result.import_entries),
move(module_result.local_export_entries), move(module_result.indirect_export_entries),
move(module_result.star_export_entries), move(module_result.default_export_binding_name),
move(module_result.var_declared_names), move(module_result.lexical_bindings),
move(functions_to_initialize),
module_result.executable.ptr(), module_result.tla_shared_data.ptr());
}
// 1. Let body be ParseText(sourceText, Module).
auto parser = Parser(Lexer(SourceCode::create(String::from_utf8(filename).release_value_but_fixme_should_propagate_errors(), Utf16String::from_utf8(source_text))), Program::Type::Module);
auto body = parser.parse_program();