LibJS+LibWeb: Port remaining callers to Rust pipeline

Port all remaining users of the C++ Parser/Lexer/Generator to
use the Rust pipeline instead:

- Intrinsics: Remove C++ fallback in parse_builtin_file()
- ECMAScriptFunctionObject: Remove C++ compile() fallback
- NativeJavaScriptBackedFunction: Remove C++ compile() fallback
- EventTarget: Port to compile_dynamic_function
- WebDriver/ExecuteScript: Port to compile_dynamic_function
- LibTest/JavaScriptTestRunner.h: Remove Parser/Lexer includes
- FuzzilliJs: Remove unused Parser/Lexer includes

Also remove the dead Statement-based template instantiation of
async_block_start/async_function_start.
This commit is contained in:
Andreas Kling 2026-03-19 12:41:49 -05:00 committed by Andreas Kling
parent 0c7d50b33d
commit 3518efd71c
9 changed files with 47 additions and 113 deletions

View file

@ -221,16 +221,11 @@ void ECMAScriptFunctionObject::get_stack_frame_size(size_t& registers_and_locals
auto& executable = shared_data().m_executable;
if (!executable) {
auto rust_executable = RustIntegration::compile_function(vm(), *m_shared_data, false);
if (rust_executable) {
executable = rust_executable;
executable->name = m_shared_data->m_name;
if (Bytecode::g_dump_bytecode)
executable->dump();
} else if (is_module_wrapper()) {
executable = Bytecode::compile(vm(), ecmascript_code(), kind(), name());
} else {
executable = Bytecode::compile(vm(), shared_data(), Bytecode::BuiltinAbstractOperationsEnabled::No);
}
VERIFY(rust_executable);
executable = rust_executable;
executable->name = m_shared_data->m_name;
if (Bytecode::g_dump_bytecode)
executable->dump();
m_shared_data->clear_compile_inputs();
}
registers_and_locals_count = executable->registers_and_locals_count;
@ -599,9 +594,6 @@ void async_block_start(VM& vm, T const& async_body, PromiseCapability const& pro
// 8. Return unused.
}
template void async_block_start(VM&, NonnullRefPtr<Statement const> const& async_body, PromiseCapability const&, ExecutionContext&);
template void async_function_start(VM&, PromiseCapability const&, NonnullRefPtr<Statement const> const& async_function_body);
template void async_block_start(VM&, GC::Function<Completion()> const& async_body, PromiseCapability const&, ExecutionContext&);
template void async_function_start(VM&, PromiseCapability const&, GC::Function<Completion()> const& async_function_body);

View file

@ -6,8 +6,6 @@
*/
#include <LibGC/Root.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/AggregateErrorConstructor.h>
#include <LibJS/Runtime/AggregateErrorPrototype.h>
@ -210,24 +208,8 @@ GC::Ref<Intrinsics> Intrinsics::create(Realm& realm)
static Vector<GC::Root<SharedFunctionInstanceData>> parse_builtin_file(unsigned char const* script_text, VM& vm)
{
auto rust_compilation = RustIntegration::compile_builtin_file(script_text, vm);
if (rust_compilation.has_value())
return move(rust_compilation.value());
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 lexer = Lexer { move(code) };
auto parser = Parser { move(lexer) };
VERIFY(!parser.has_errors());
auto program = parser.parse_program(true);
Vector<GC::Root<SharedFunctionInstanceData>> shared_data_list;
for (auto const& child : program->children()) {
if (auto const* function_declaration = as_if<FunctionDeclaration>(*child))
shared_data_list.append(SharedFunctionInstanceData::create_for_function_node(vm, *function_declaration));
}
return shared_data_list;
VERIFY(rust_compilation.has_value());
return move(rust_compilation.value());
}
void Intrinsics::initialize_intrinsics(Realm& realm)

View file

@ -5,8 +5,6 @@
*/
#include <AK/TypeCasts.h>
#include <LibJS/Bytecode/BuiltinAbstractOperationsEnabled.h>
#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
#include <LibJS/Runtime/AsyncGenerator.h>
@ -100,14 +98,11 @@ Bytecode::Executable& NativeJavaScriptBackedFunction::bytecode_executable()
auto& executable = m_shared_function_instance_data->m_executable;
if (!executable) {
auto rust_executable = RustIntegration::compile_function(vm(), *m_shared_function_instance_data, true);
if (rust_executable) {
executable = rust_executable;
executable->name = m_shared_function_instance_data->m_name;
if (Bytecode::g_dump_bytecode)
executable->dump();
} else {
executable = Bytecode::compile(vm(), m_shared_function_instance_data, Bytecode::BuiltinAbstractOperationsEnabled::Yes);
}
VERIFY(rust_executable);
executable = rust_executable;
executable->name = m_shared_function_instance_data->m_name;
if (Bytecode::g_dump_bytecode)
executable->dump();
m_shared_function_instance_data->clear_compile_inputs();
}

View file

@ -118,9 +118,9 @@ Optional<Result<ModuleResult, Vector<ParserError>>> compile_parsed_module(FFI::P
// 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()). Returns nullopt if Rust is not available.
// Compile a dynamic function (new Function()).
// On success, returns a SharedFunctionInstanceData with source_text set.
Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_function(
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);

View file

@ -20,12 +20,13 @@
#include <LibCore/DirIterator.h>
#include <LibCore/File.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/ParserError.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibJS/Runtime/WeakMap.h>
#include <LibJS/Runtime/WeakSet.h>
#include <LibJS/Script.h>

View file

@ -9,13 +9,13 @@
*/
#include <AK/StringBuilder.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/RustIntegration.h>
#include <LibWeb/Bindings/EventTargetPrototype.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/PrincipalHostDefined.h>
@ -428,66 +428,44 @@ WebIDL::CallbackType* EventTarget::get_current_value_of_event_handler(FlyString
// 6. Let settings object be the relevant settings object of document.
auto& settings_object = document->relevant_settings_object();
// NOTE: ECMAScriptFunctionObject::create expects a parsed body as input, so we must do the spec's sourceText steps here.
StringBuilder builder(StringBuilder::Mode::UTF16);
// Build source text and parameter strings for the event handler function.
StringBuilder source_builder;
StringView parameters_string;
// sourceText
// sourceText / ParameterList
if (name == HTML::EventNames::error && is<HTML::Window>(this)) {
// -> If name is onerror and eventTarget is a Window object
// The string formed by concatenating "function ", name, "(event, source, lineno, colno, error) {", U+000A LF, body, U+000A LF, and "}".
builder.appendff("function {}(event, source, lineno, colno, error) {{\n{}\n}}", name, body);
// Let the function have five arguments, named event, source, lineno, colno, and error.
source_builder.appendff("function {}(event, source, lineno, colno, error) {{\n{}\n}}", name, body);
parameters_string = "event, source, lineno, colno, error"sv;
} else {
// -> Otherwise
// The string formed by concatenating "function ", name, "(event) {", U+000A LF, body, U+000A LF, and "}".
builder.appendff("function {}(event) {{\n{}\n}}", name, body);
// Let the function have a single argument called event.
source_builder.appendff("function {}(event) {{\n{}\n}}", name, body);
parameters_string = "event"sv;
}
auto source_text = builder.to_utf16_string();
auto source_text = source_builder.to_byte_string();
auto parser = JS::Parser(JS::Lexer(JS::SourceCode::create({}, source_text)));
auto& vm = Bindings::main_thread_vm();
// FIXME: This should only be parsing the `body` instead of `source_text` and therefore use `JS::FunctionBody` instead of `JS::FunctionExpression`.
// However, JS::ECMAScriptFunctionObject::create wants parameters and length and JS::FunctionBody does not inherit JS::FunctionNode.
auto program = parser.parse_function_node<JS::FunctionExpression>();
auto rust_compilation = JS::RustIntegration::compile_dynamic_function(
vm, source_text, parameters_string, body, JS::FunctionKind::Normal);
// 7. If body is not parsable as FunctionBody or if parsing detects an early error, then follow these substeps:
if (parser.has_errors()) {
if (!rust_compilation.has_value() || rust_compilation->is_error()) {
// 1. Set eventHandler's value to null.
// Note: This does not deactivate the event handler, which additionally removes the event handler's listener (if present).
handler_map.remove(event_handler_iterator);
// FIXME: 2. Report the error for the appropriate script and with the appropriate position (line number and column number) given by location, using settings object's global object.
// If the error is still not handled after this, then the error may be reported to a developer console.
// 3. Return null.
return nullptr;
}
auto& vm = Bindings::main_thread_vm();
// 8. Push settings object's realm execution context onto the JavaScript execution context stack; it is now the running JavaScript execution context.
vm.push_execution_context(settings_object.realm_execution_context());
// 9. Let function be the result of calling OrdinaryFunctionCreate, with arguments:
// functionPrototype
// %Function.prototype% (This is enforced by using JS::ECMAScriptFunctionObject)
// sourceText was handled above.
// ParameterList
// If name is onerror and eventTarget is a Window object
// Let the function have five arguments, named event, source, lineno, colno, and error.
// Otherwise
// Let the function have a single argument called event.
// (This was handled above for us by the parser using sourceText)
// body
// The result of parsing body above. (This is given by program->body())
// thisMode
// non-lexical-this (For JS::ECMAScriptFunctionObject, this means passing is_arrow_function as false)
constexpr bool is_arrow_function = false;
// scope
// 1. Let realm be settings object's Realm.
auto& realm = settings_object.realm();
@ -508,10 +486,12 @@ WebIDL::CallbackType* EventTarget::get_current_value_of_event_handler(FlyString
if (element)
scope = JS::new_object_environment(*element, true, scope);
// 6. Return scope. (NOTE: Not necessary)
auto function = JS::ECMAScriptFunctionObject::create(realm, Utf16FlyString::from_utf8(name), move(source_text), program->body(), program->parameters(), program->function_length(), program->local_variables_names(), scope, nullptr, JS::FunctionKind::Normal, program->is_strict_mode(),
program->parsing_insights(), is_arrow_function);
// 9. Let function be the result of calling OrdinaryFunctionCreate.
auto function = JS::ECMAScriptFunctionObject::create_from_function_data(
realm,
rust_compilation->value(),
scope,
nullptr);
// 10. Remove settings object's realm execution context from the JavaScript execution context stack.
VERIFY(vm.execution_context_stack().last() == &settings_object.realm_execution_context());

View file

@ -5,12 +5,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Parser.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/PromiseConstructor.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/RustIntegration.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/Scripting/Environments.h>
@ -56,37 +56,23 @@ static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(HTML::BrowsingCo
}})~~~",
body);
auto parser = JS::Parser(JS::Lexer(JS::SourceCode::create({}, Utf16String::from_utf8(source_text))));
;
auto function_expression = parser.parse_function_node<JS::FunctionExpression>();
auto rust_compilation = JS::RustIntegration::compile_dynamic_function(
realm.vm(), source_text, ""sv, body, JS::FunctionKind::Normal);
// 4. If body is not parsable as a FunctionBody or if parsing detects an early error, return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }.
if (parser.has_errors())
if (!rust_compilation.has_value() || rust_compilation->is_error())
return JS::js_null();
// 5. If body begins with a directive prologue that contains a use strict directive then let strict be true, otherwise let strict be false.
// NOTE: Handled in step 8 below.
// 6. Prepare to run a script with realm.
HTML::prepare_to_run_script(realm);
// 7. Prepare to run a callback with environment settings.
HTML::prepare_to_run_callback(realm);
// 8. Let function be the result of calling FunctionCreate, with arguments:
// kind
// Normal.
// list
// An empty List.
// body
// The result of parsing body above.
// global scope
// The result of parsing global scope above.
// strict
// The result of parsing strict above.
// 8. Let function be the result of calling FunctionCreate.
auto function = JS::ECMAScriptFunctionObject::create_from_function_data(
realm,
JS::SharedFunctionInstanceData::create_for_function_node(realm.vm(), *function_expression),
rust_compilation->value(),
&global_scope,
nullptr);

View file

@ -9,8 +9,6 @@
#include <AK/StringView.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Forward.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <errno.h>

View file

@ -154,9 +154,9 @@ TESTJS_RUN_FILE_FUNCTION(ByteString const& test_file, JS::Realm& realm, JS::Exec
else
return Test::JS::RunFileHookResult::SkipFile;
auto program_type = path.basename().ends_with(".module.js"sv) ? JS::Program::Type::Module : JS::Program::Type::Script;
bool const is_module = path.basename().ends_with(".module.js"sv);
bool parse_succeeded = false;
if (program_type == JS::Program::Type::Module)
if (is_module)
parse_succeeded = !Test::JS::parse_module(test_file, realm).is_error();
else
parse_succeeded = !Test::JS::parse_script(test_file, realm).is_error();