LibJS: Remove C++ compiler pipeline fallback paths

Now that the Rust pipeline is the sole compilation path, remove all
C++ parser/codegen fallback paths from the callers:

- Script::parse() no longer falls back to C++ Parser
- SourceTextModule::parse() no longer falls back to C++ Parser
- perform_eval() no longer falls back to C++ Parser + Generator
- create_dynamic_function() no longer falls back to C++ Parser
- ShadowRealm eval no longer falls back to C++ Parser + Generator
- Interpreter::run(Script&) no longer falls back to Generator

Also remove the now-dead old constructors that took C++ AST nodes,
the module_requests() helper, and AST dump code from js.cpp.
This commit is contained in:
Andreas Kling 2026-03-19 12:26:15 -05:00 committed by Andreas Kling
parent 2c45472a11
commit 77cd434710
9 changed files with 47 additions and 580 deletions

View file

@ -117,13 +117,6 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, GC::Ptr<Environ
// 11. Let script be scriptRecord.[[ECMAScriptCode]].
GC::Ptr<Executable> executable = script_record.cached_executable();
if (!executable && result.type() == Completion::Type::Normal) {
executable = JS::Bytecode::Generator::generate_from_ast_node(vm, *script_record.parse_node(), {});
if (executable) {
script_record.cache_executable(*executable);
script_record.drop_ast();
}
}
if (executable && g_dump_bytecode)
executable->dump();

View file

@ -9,10 +9,8 @@
#include <AK/Function.h>
#include <AK/Optional.h>
#include <AK/Utf16View.h>
#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/ModuleLoading.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/ArgumentsObject.h>
@ -674,51 +672,15 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
// g. If inDerivedConstructor is false, and body Contains SuperCall, throw a SyntaxError exception.
// h. If inClassFieldInitializer is true, and ContainsArguments of body is true, throw a SyntaxError exception.
GC::Ptr<Bytecode::Executable> executable;
bool strict_eval = false;
EvalDeclarationData eval_declaration_data;
auto rust_compilation = RustIntegration::compile_eval(*code_string, vm, strict_caller, in_function, in_method, in_derived_constructor, in_class_field_initializer);
if (rust_compilation.has_value()) {
if (rust_compilation->is_error())
return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
auto& eval_result = rust_compilation->value();
executable = eval_result.executable;
strict_eval = eval_result.is_strict_mode;
eval_declaration_data = move(eval_result.declaration_data);
}
RefPtr<Program> cpp_program;
if (!executable) {
Parser::EvalInitialState initial_state {
.in_eval_function_context = in_function,
.allow_super_property_lookup = in_method,
.allow_super_constructor_call = in_derived_constructor,
.in_class_field_initializer = in_class_field_initializer,
};
Parser parser(Lexer(SourceCode::create({}, code_string->utf16_string())), Program::Type::Script, move(initial_state));
cpp_program = parser.parse_program(strict_caller == CallerMode::Strict);
// b. If script is a List of errors, throw a SyntaxError exception.
if (parser.has_errors()) {
auto& error = parser.errors()[0];
return vm.throw_completion<SyntaxError>(error.to_string());
}
// 14. If strictCaller is true, let strictEval be true.
if (strict_caller == CallerMode::Strict)
strict_eval = true;
// 15. Else, let strictEval be IsStrict of script.
else
strict_eval = cpp_program->is_strict_mode();
eval_declaration_data = EvalDeclarationData::create(vm, *cpp_program, strict_eval);
// NB: Bytecode compilation is deferred until after EvalDeclarationInstantiation,
// which sets annex B flags on AST nodes that affect codegen.
}
if (!rust_compilation.has_value())
return vm.throw_completion<SyntaxError>("Failed to compile eval code"_string);
if (rust_compilation->is_error())
return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
auto& eval_result = rust_compilation->value();
auto executable = eval_result.executable;
auto strict_eval = eval_result.is_strict_mode;
auto eval_declaration_data = move(eval_result.declaration_data);
// 16. Let runningContext be the running execution context.
// 17. NOTE: If direct is true, runningContext will be the execution context that performed the direct eval. If direct is false, runningContext will be the execution context for the invocation of the eval function.
@ -770,12 +732,6 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
// 30. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)).
TRY(eval_declaration_instantiation(vm, eval_declaration_data, variable_environment, lexical_environment, private_environment, strict_eval));
// Compile C++ AST after EDI, since EDI sets annex B flags on AST nodes.
if (cpp_program) {
executable = Bytecode::Generator::generate_from_ast_node(vm, *cpp_program, {});
executable->name = "eval"_utf16_fly_string;
}
if (Bytecode::g_dump_bytecode)
executable->dump();
@ -815,16 +771,16 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
stack.deallocate(stack_mark);
};
Optional<Value> eval_result;
Optional<Value> result;
eval_result = TRY(vm.bytecode_interpreter().run_executable(*eval_context, *executable, {}));
result = TRY(vm.bytecode_interpreter().run_executable(*eval_context, *executable, {}));
// 32. If result.[[Type]] is normal and result.[[Value]] is empty, then
// a. Set result to NormalCompletion(undefined).
// NOTE: Step 33 and 34 is handled by `pop_guard` above.
// 35. Return ? result.
// NOTE: Step 35 is also performed with each use of `TRY` above.
return eval_result.value_or(js_undefined());
return result.value_or(js_undefined());
}
EvalDeclarationData EvalDeclarationData::create(VM& vm, Program const& program, bool strict)

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Error.h>
@ -153,73 +151,11 @@ ThrowCompletionOr<GC::Ref<ECMAScriptFunctionObject>> FunctionConstructor::create
GC::Ptr<SharedFunctionInstanceData> function_data;
auto rust_compilation = RustIntegration::compile_dynamic_function(vm, source_text, parameters_string, body_parse_string, kind);
if (rust_compilation.has_value()) {
if (rust_compilation->is_error())
return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
function_data = rust_compilation->value();
}
if (!function_data) {
u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName;
if (kind == FunctionKind::Async || kind == FunctionKind::AsyncGenerator)
parse_options |= FunctionNodeParseOptions::IsAsyncFunction;
if (kind == FunctionKind::Generator || kind == FunctionKind::AsyncGenerator)
parse_options |= FunctionNodeParseOptions::IsGeneratorFunction;
// 17. Let parameters be ParseText(P, parameterSym).
i32 function_length = 0;
auto parameters_parser = Parser(Lexer(SourceCode::create({}, Utf16String::from_utf8(parameters_string))));
auto parameters = parameters_parser.parse_formal_parameters(function_length, parse_options);
// 18. If parameters is a List of errors, throw a SyntaxError exception.
if (parameters_parser.has_errors()) {
auto error = parameters_parser.errors()[0];
return vm.throw_completion<SyntaxError>(error.to_string());
}
// 19. Let body be ParseText(bodyParseString, bodySym).
FunctionParsingInsights parsing_insights;
auto body_parser = Parser::parse_function_body_from_string(body_parse_string, parse_options, parameters, kind, parsing_insights);
// 20. If body is a List of errors, throw a SyntaxError exception.
if (body_parser.has_errors()) {
auto error = body_parser.errors()[0];
return vm.throw_completion<SyntaxError>(error.to_string());
}
// 21. NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, new Function("/*", "*/ ) {") does not evaluate to a function.
// 22. NOTE: If this step is reached, sourceText must have the syntax of exprSym (although the reverse implication does not hold). The purpose of the next two steps is to enforce any Early Error rules which apply to exprSym directly.
// 23. Let expr be ParseText(sourceText, exprSym).
auto source_parser = Parser(Lexer(SourceCode::create({}, Utf16String::from_utf8(source_text))));
// This doesn't need any parse_options, it determines those & the function type based on the tokens that were found.
auto expr = source_parser.parse_function_node<FunctionExpression>();
source_parser.run_scope_analysis();
// 24. If expr is a List of errors, throw a SyntaxError exception.
if (source_parser.has_errors()) {
auto error = source_parser.errors()[0];
return vm.throw_completion<SyntaxError>(error.to_string());
}
// 28. Let F be OrdinaryFunctionCreate(proto, sourceText, parameters, body, non-lexical-this, env, privateEnv).
parsing_insights.might_need_arguments_object = true;
function_data = vm.heap().allocate<SharedFunctionInstanceData>(
vm,
expr->kind(),
"anonymous"_utf16_fly_string,
expr->function_length(),
expr->parameters(),
expr->body(),
Utf16View {},
expr->is_strict_mode(),
false,
parsing_insights,
expr->local_variables_names());
function_data->m_source_text_owner = Utf16String::from_utf8(source_text);
function_data->m_source_text = function_data->m_source_text_owner.utf16_view();
}
if (!rust_compilation.has_value())
return vm.throw_completion<SyntaxError>("Failed to compile dynamic function"_string);
if (rust_compilation->is_error())
return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
function_data = rust_compilation->value();
// 25. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
auto* prototype = TRY(get_prototype_from_constructor(vm, *new_target, fallback_prototype));

View file

@ -6,8 +6,6 @@
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
@ -124,48 +122,15 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, Value source, Realm&
// 2. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection:
GC::Ptr<Bytecode::Executable> executable;
bool strict_eval = false;
EvalDeclarationData eval_declaration_data;
auto rust_compilation = RustIntegration::compile_shadow_realm_eval(*source_text, vm);
if (rust_compilation.has_value()) {
if (rust_compilation->is_error())
return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
auto& eval_result = rust_compilation->value();
executable = eval_result.executable;
strict_eval = eval_result.is_strict_mode;
eval_declaration_data = move(eval_result.declaration_data);
}
if (!executable) {
// a. Let script be ParseText(StringToCodePoints(sourceText), Script).
auto parser = Parser(Lexer(SourceCode::create({}, source_text->utf16_string())), Program::Type::Script, Parser::EvalInitialState {});
auto program = parser.parse_program();
// b. If script is a List of errors, throw a SyntaxError exception.
if (parser.has_errors()) {
auto& error = parser.errors()[0];
return vm.throw_completion<SyntaxError>(error.to_string());
}
// c. If script Contains ScriptBody is false, return undefined.
if (program->children().is_empty())
return js_undefined();
// d. Let body be the ScriptBody of script.
// e. If body Contains NewTarget is true, throw a SyntaxError exception.
// f. If body Contains SuperProperty is true, throw a SyntaxError exception.
// g. If body Contains SuperCall is true, throw a SyntaxError exception.
// FIXME: Implement these, we probably need a generic way of scanning the AST for certain nodes.
// 3. Let strictEval be IsStrict of script.
strict_eval = program->is_strict_mode();
eval_declaration_data = EvalDeclarationData::create(vm, program, strict_eval);
executable = Bytecode::compile(vm, program, FunctionKind::Normal, "ShadowRealmEval"_utf16_fly_string);
}
if (!rust_compilation.has_value())
return vm.throw_completion<SyntaxError>("Failed to compile ShadowRealm eval code"_string);
if (rust_compilation->is_error())
return vm.throw_completion<SyntaxError>(rust_compilation->release_error());
auto& compilation_result = rust_compilation->value();
auto executable = compilation_result.executable;
auto strict_eval = compilation_result.is_strict_mode;
auto eval_declaration_data = move(compilation_result.declaration_data);
// 4. Let runningContext be the running execution context.
// 5. If runningContext is not already suspended, suspend runningContext.

View file

@ -4,10 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/AST.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
@ -27,22 +24,11 @@ GC_DEFINE_ALLOCATOR(Script);
Result<GC::Ref<Script>, Vector<ParserError>> Script::parse(StringView source_text, Realm& realm, StringView filename, HostDefined* host_defined, size_t line_number_offset)
{
auto rust_compilation = RustIntegration::compile_script(source_text, realm, filename, line_number_offset);
if (rust_compilation.has_value()) {
if (rust_compilation->is_error())
return rust_compilation->release_error();
return realm.heap().allocate<Script>(realm, filename, move(rust_compilation->value()), host_defined);
}
// 1. Let script be ParseText(sourceText, Script).
auto parser = Parser(Lexer(SourceCode::create(String::from_utf8(filename).release_value_but_fixme_should_propagate_errors(), Utf16String::from_utf8(source_text)), line_number_offset));
auto script = parser.parse_program();
// 2. If script is a List of errors, return body.
if (parser.has_errors())
return parser.errors();
// 3. Return Script Record { [[Realm]]: realm, [[ECMAScriptCode]]: script, [[HostDefined]]: hostDefined }.
return realm.heap().allocate<Script>(realm, filename, move(script), host_defined);
if (!rust_compilation.has_value())
return Vector<ParserError> {};
if (rust_compilation->is_error())
return rust_compilation->release_error();
return realm.heap().allocate<Script>(realm, filename, move(rust_compilation->value()), host_defined);
}
Result<GC::Ref<Script>, Vector<ParserError>> Script::create_from_parsed(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm& realm, HostDefined* host_defined)
@ -56,64 +42,6 @@ Result<GC::Ref<Script>, Vector<ParserError>> Script::create_from_parsed(FFI::Par
return realm.heap().allocate<Script>(realm, filename, move(rust_compilation->value()), host_defined);
}
Script::Script(Realm& realm, StringView filename, RefPtr<Program> parse_node, HostDefined* host_defined)
: m_realm(realm)
, m_parse_node(move(parse_node))
, m_filename(filename)
, m_host_defined(host_defined)
{
auto& vm = realm.vm();
auto& program = *m_parse_node;
m_is_strict_mode = program.is_strict_mode();
// Pre-compute lexically declared names (GDI step 3).
MUST(program.for_each_lexically_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
m_lexical_names.append(identifier.string());
return {};
}));
// Pre-compute var declared names (GDI step 4).
MUST(program.for_each_var_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
m_var_names.append(identifier.string());
return {};
}));
// Pre-compute functions to initialize and declared function names (GDI steps 7-8).
MUST(program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr<void> {
auto function_name = function.name();
if (m_declared_function_names.set(function_name) != AK::HashSetResult::InsertedNewEntry)
return {};
m_functions_to_initialize.append({ SharedFunctionInstanceData::create_for_function_node(vm, function), function_name });
return {};
}));
// Pre-compute var scoped variable names (GDI step 10).
MUST(program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) {
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
m_var_scoped_names.append(identifier.string());
return {};
});
}));
// Pre-compute AnnexB candidates (GDI step 13).
if (!m_is_strict_mode) {
MUST(program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) -> ThrowCompletionOr<void> {
m_annex_b_candidate_names.append(function_declaration.name());
m_annex_b_function_declarations.append(function_declaration);
return {};
}));
}
// Pre-compute lexical bindings (GDI step 15).
MUST(program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
m_lexical_bindings.append({ identifier.string(), declaration.is_constant_declaration() });
return {};
});
}));
}
Script::Script(Realm& realm, StringView filename, RustIntegration::ScriptResult&& result, HostDefined* host_defined)
: m_realm(realm)
, m_executable(result.executable)
@ -219,10 +147,6 @@ ThrowCompletionOr<void> Script::global_declaration_instantiation(VM& vm, GlobalE
// i. Perform ? env.CreateGlobalVarBinding(F, false).
TRY(global_environment.create_global_var_binding(function_name, false));
}
// iii. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6:
if (i < m_annex_b_function_declarations.size())
m_annex_b_function_declarations[i]->set_should_do_additional_annexB_steps();
}
}
@ -269,12 +193,6 @@ ThrowCompletionOr<void> Script::global_declaration_instantiation(VM& vm, GlobalE
return {};
}
void Script::drop_ast()
{
m_parse_node = nullptr;
m_annex_b_function_declarations.clear();
}
Script::~Script()
{
}

View file

@ -7,7 +7,6 @@
#pragma once
#include <AK/HashTable.h>
#include <AK/NonnullRefPtr.h>
#include <AK/Utf16FlyString.h>
#include <LibGC/Ptr.h>
#include <LibGC/Root.h>
@ -21,8 +20,6 @@ namespace JS {
JS_API extern bool g_dump_ast;
JS_API extern bool g_dump_ast_use_color;
class FunctionDeclaration;
namespace FFI {
struct ParsedProgram;
@ -60,7 +57,6 @@ public:
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_parsed(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
Realm& realm() { return *m_realm; }
Program const* parse_node() const { return m_parse_node; }
Vector<LoadedModuleRequest>& loaded_modules() { return m_loaded_modules; }
Vector<LoadedModuleRequest> const& loaded_modules() const { return m_loaded_modules; }
@ -68,12 +64,9 @@ public:
StringView filename() const LIFETIME_BOUND { return m_filename; }
Bytecode::Executable* cached_executable() const { return m_executable; }
void cache_executable(Bytecode::Executable& executable) const { m_executable = &executable; }
ThrowCompletionOr<void> global_declaration_instantiation(VM&, GlobalEnvironment&);
void drop_ast();
// Pre-computed global declaration instantiation data.
// These are extracted from the AST at parse time so that GDI can run
// without needing to walk the AST.
@ -87,13 +80,11 @@ public:
};
private:
Script(Realm&, StringView filename, RefPtr<Program>, HostDefined*);
Script(Realm&, StringView filename, RustIntegration::ScriptResult&&, HostDefined*);
virtual void visit_edges(Cell::Visitor&) override;
GC::Ptr<Realm> m_realm; // [[Realm]]
RefPtr<Program> m_parse_node; // [[ECMAScriptCode]]
Vector<LoadedModuleRequest> m_loaded_modules; // [[LoadedModules]]
mutable GC::Ptr<Bytecode::Executable> m_executable;
@ -104,7 +95,6 @@ private:
HashTable<Utf16FlyString> m_declared_function_names;
Vector<Utf16FlyString> m_var_scoped_names;
Vector<Utf16FlyString> m_annex_b_candidate_names;
Vector<NonnullRefPtr<FunctionDeclaration>> m_annex_b_function_declarations;
Vector<LexicalBinding> m_lexical_bindings;
bool m_is_strict_mode { false };

View file

@ -9,7 +9,6 @@
#include <AK/QuickSort.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
@ -25,149 +24,6 @@ namespace JS {
GC_DEFINE_ALLOCATOR(SourceTextModule);
// 16.2.2.4 Static Semantics: WithClauseToAttributes, https://tc39.es/ecma262/#sec-withclausetoattributes
static Vector<ImportAttribute> with_clause_to_assertions(Vector<ImportAttribute> const& source_attributes)
{
// WithClause : with { WithEntries ,opt }
// 1. Let attributes be WithClauseToAttributes of WithEntries.
Vector<ImportAttribute> attributes;
// AssertEntries : AssertionKey : StringLiteral
// AssertEntries : AssertionKey : StringLiteral , WithEntries
for (auto const& attribute : source_attributes) {
// 1. Let key be the PropName of AttributeKey.
// 2. Let entry be the ImportAttribute Record { [[Key]]: key, [[Value]]: SV of StringLiteral }.
// 3. Return « entry ».
attributes.empend(attribute);
}
// 2. Sort attributes according to the lexicographic order of their [[Key]] field, treating the value of each such
// field as a sequence of UTF-16 code unit values. NOTE: This sorting is observable only in that hosts are
// prohibited from changing behaviour based on the order in which attributes are enumerated.
// NOTE: The sorting is done in construction of the ModuleRequest object.
// 3. Return attributes.
return attributes;
}
// 16.2.1.4 Static Semantics: ModuleRequests, https://tc39.es/ecma262/#sec-static-semantics-modulerequests
static Vector<ModuleRequest> module_requests(Program& program)
{
// A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module.
// NOTE: The List is source text occurrence ordered!
struct RequestedModuleAndSourceIndex {
u32 source_offset { 0 };
ModuleRequest const* module_request { nullptr };
};
Vector<RequestedModuleAndSourceIndex> requested_modules_with_indices;
for (auto const& import_statement : program.imports())
requested_modules_with_indices.empend(import_statement->start_offset(), &import_statement->module_request());
for (auto const& export_statement : program.exports()) {
for (auto const& export_entry : export_statement->entries()) {
if (!export_entry.is_module_request())
continue;
requested_modules_with_indices.empend(export_statement->start_offset(), &export_statement->module_request());
}
}
// NOTE: The List is source code occurrence ordered. https://tc39.es/ecma262/#table-cyclic-module-fields
quick_sort(requested_modules_with_indices, [&](RequestedModuleAndSourceIndex const& lhs, RequestedModuleAndSourceIndex const& rhs) {
return lhs.source_offset < rhs.source_offset;
});
Vector<ModuleRequest> requested_modules_in_source_order;
requested_modules_in_source_order.ensure_capacity(requested_modules_with_indices.size());
for (auto const& module : requested_modules_with_indices) {
if (module.module_request->attributes.is_empty()) {
// ImportDeclaration : import ImportClause FromClause ;
// ExportDeclaration : export ExportFromClause FromClause ;
// 1. Let specifier be SV of FromClause.
// 2. Return a List whose sole element is the ModuleRequest Record { [[Specifer]]: specifier, [[Attributes]]: « » }.
requested_modules_in_source_order.empend(module.module_request->module_specifier);
} else {
// ImportDeclaration : import ImportClause FromClause WithClause ;
// ExportDeclaration : export ExportFromClause FromClause WithClause ;
// 1. Let specifier be the SV of FromClause.
// 2. Let attributes be WithClauseToAttributes of WithClause.
auto attributes = with_clause_to_assertions(module.module_request->attributes);
// NOTE: We have to modify the attributes in place because else it might keep unsupported ones.
const_cast<ModuleRequest*>(module.module_request)->attributes = move(attributes);
// 3. Return a List whose sole element is the ModuleRequest Record { [[Specifier]]: specifier, [[Attributes]]: attributes }.
requested_modules_in_source_order.empend(module.module_request->module_specifier, module.module_request->attributes);
}
}
return requested_modules_in_source_order;
}
SourceTextModule::SourceTextModule(Realm& realm, StringView filename, Script::HostDefined* host_defined, bool has_top_level_await, NonnullRefPtr<Program> body, 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)
: CyclicModule(realm, filename, has_top_level_await, move(requested_modules), host_defined)
, m_ecmascript_code(move(body))
, 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_default_export_binding_name(move(default_export_binding_name))
{
auto& vm = realm.vm();
// Pre-compute var declared names (initialize_environment step 21).
MUST(m_ecmascript_code->for_each_var_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
m_var_declared_names.append(identifier.string());
return {};
}));
// Pre-compute lexical bindings and functions to initialize (initialize_environment step 24).
MUST(m_ecmascript_code->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
LexicalBinding binding;
binding.name = identifier.string();
binding.is_constant = declaration.is_constant_declaration();
if (declaration.is_function_declaration()) {
VERIFY(is<FunctionDeclaration>(declaration));
auto const& function_declaration = static_cast<FunctionDeclaration const&>(declaration);
auto shared_data = SharedFunctionInstanceData::create_for_function_node(vm, function_declaration);
if (function_declaration.name() == ExportStatement::local_name_for_default)
shared_data->m_name = "default"_utf16_fly_string;
binding.function_index = static_cast<i32>(m_functions_to_initialize.size());
m_functions_to_initialize.append({ *shared_data, shared_data->m_name });
}
m_lexical_bindings.append(move(binding));
return {};
});
}));
// For TLA modules, pre-create the SharedFunctionInstanceData for the
// async wrapper function so that execute_module() doesn't need the AST.
if (has_top_level_await) {
FunctionParsingInsights parsing_insights;
parsing_insights.uses_this_from_environment = true;
parsing_insights.uses_this = true;
m_tla_shared_data = vm.heap().allocate<SharedFunctionInstanceData>(
vm, FunctionKind::Async,
"module code with top-level await"_utf16_fly_string,
0, FunctionParameters::empty(), *m_ecmascript_code,
Utf16View {}, true, false, parsing_insights, Vector<LocalVariable> {});
m_tla_shared_data->m_is_module_wrapper = true;
m_ecmascript_code = nullptr;
}
}
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,
@ -231,155 +87,24 @@ Result<GC::Ref<SourceTextModule>, Vector<ParserError>> SourceTextModule::parse_f
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());
}
if (!rust_result.has_value())
return Vector<ParserError> {};
if (rust_result->is_error())
return rust_result->release_error();
// 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();
// 2. If body is a List of errors, return body.
if (parser.has_errors())
return parser.errors();
// 3. Let requestedModules be the ModuleRequests of body.
auto requested_modules = module_requests(*body);
// 4. Let importEntries be ImportEntries of body.
Vector<ImportEntry> import_entries;
for (auto const& import_statement : body->imports())
import_entries.extend(import_statement->entries());
// 5. Let importedBoundNames be ImportedLocalNames(importEntries).
// NOTE: Since we have to potentially extract the import entry we just use importEntries
// In the future it might be an optimization to have a set/map of string to speed up the search.
// 6. Let indirectExportEntries be a new empty List.
Vector<ExportEntry> indirect_export_entries;
// 7. Let localExportEntries be a new empty List.
Vector<ExportEntry> local_export_entries;
// 8. Let starExportEntries be a new empty List.
Vector<ExportEntry> star_export_entries;
// NOTE: Not in the spec but makes it easier to find the default.
Optional<Utf16FlyString> default_export_binding_name;
// 9. Let exportEntries be ExportEntries of body.
// 10. For each ExportEntry Record ee of exportEntries, do
for (auto const& export_statement : body->exports()) {
if (export_statement->is_default_export()) {
VERIFY(!default_export_binding_name.has_value());
VERIFY(export_statement->entries().size() == 1);
VERIFY(export_statement->has_statement());
auto const& entry = export_statement->entries()[0];
VERIFY(entry.kind == ExportEntry::Kind::NamedExport);
VERIFY(!entry.is_module_request());
VERIFY(import_entries.find_if(
[&](ImportEntry const& import_entry) {
return import_entry.local_name == entry.local_or_import_name;
})
.is_end());
// Extract the binding name if the default export is a non-declaration statement.
if (!is<Declaration>(export_statement->statement()))
default_export_binding_name = entry.local_or_import_name.value();
}
for (auto const& export_entry : export_statement->entries()) {
// Special case, export {} from "module" should add "module" to
// required_modules but not any import or export so skip here.
if (export_entry.kind == ExportEntry::Kind::EmptyNamedExport) {
VERIFY(export_statement->entries().size() == 1);
break;
}
// a. If ee.[[ModuleRequest]] is null, then
if (!export_entry.is_module_request()) {
auto in_imported_bound_names = import_entries.find_if(
[&](ImportEntry const& import_entry) {
return import_entry.local_name == export_entry.local_or_import_name;
});
// i. If ee.[[LocalName]] is not an element of importedBoundNames, then
if (in_imported_bound_names.is_end()) {
// 1. Append ee to localExportEntries.
local_export_entries.empend(export_entry);
}
// ii. Else,
else {
// 1. Let ie be the element of importEntries whose [[LocalName]] is the same as ee.[[LocalName]].
auto& import_entry = *in_imported_bound_names;
// 2. If ie.[[ImportName]] is NAMESPACE-OBJECT, then
if (import_entry.is_namespace()) {
// a. NOTE: This is a re-export of an imported module namespace object.
// b. Append ee to localExportEntries.
local_export_entries.empend(export_entry);
}
// 3. Else,
else {
// a. NOTE: This is a re-export of a single name.
// b. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries.
indirect_export_entries.empend(ExportEntry::indirect_export_entry(import_entry.module_request(), export_entry.export_name, import_entry.import_name));
}
}
}
// b. Else if ee.[[ImportName]] is all-but-default, then
else if (export_entry.kind == ExportEntry::Kind::ModuleRequestAllButDefault) {
// i. Assert: ee.[[ExportName]] is null.
VERIFY(!export_entry.export_name.has_value());
// ii. Append ee to starExportEntries.
star_export_entries.empend(export_entry);
}
// c. Else,
else {
// i. Append ee to indirectExportEntries.
indirect_export_entries.empend(export_entry);
}
}
}
// 11. Let async be body Contains await.
bool async = body->has_top_level_await();
// 12. Return Source Text Module Record {
// [[Realm]]: realm, [[Environment]]: empty, [[Namespace]]: empty, [[CycleRoot]]: empty, [[HasTLA]]: async,
// [[AsyncEvaluation]]: false, [[TopLevelCapability]]: empty, [[AsyncParentModules]]: « »,
// [[PendingAsyncDependencies]]: empty, [[Status]]: unlinked, [[EvaluationError]]: empty,
// [[HostDefined]]: hostDefined, [[ECMAScriptCode]]: body, [[Context]]: empty, [[ImportMeta]]: empty,
// [[RequestedModules]]: requestedModules, [[ImportEntries]]: importEntries, [[LocalExportEntries]]: localExportEntries,
// [[IndirectExportEntries]]: indirectExportEntries, [[StarExportEntries]]: starExportEntries, [[DFSIndex]]: empty, [[DFSAncestorIndex]]: empty }.
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,
async,
move(body),
move(requested_modules),
move(import_entries),
move(local_export_entries),
move(indirect_export_entries),
move(star_export_entries),
move(default_export_binding_name));
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());
}
// 16.2.1.7.2.1 GetExportedNames ( [ exportStarSet ] ), https://tc39.es/ecma262/#sec-getexportednames
@ -775,10 +500,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<Promise
{
dbgln_if(JS_MODULE_DEBUG, "[JS MODULE] SourceTextModule::execute_module({}, PromiseCapability @ {})", filename(), capability.ptr());
if (!m_has_top_level_await && !m_executable) {
m_executable = Bytecode::compile(vm, *m_ecmascript_code, FunctionKind::Normal, "ShadowRealmEval"_utf16_fly_string);
m_ecmascript_code = nullptr;
}
VERIFY(m_has_top_level_await || m_executable);
u32 registers_and_locals_count = 0;
u32 constants_count = 0;

View file

@ -31,8 +31,6 @@ public:
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse(StringView source_text, Realm&, StringView filename = {}, Script::HostDefined* host_defined = nullptr);
static Result<GC::Ref<SourceTextModule>, Vector<ParserError>> parse_from_pre_parsed(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm&, Script::HostDefined* host_defined = nullptr);
Program const* parse_node() const { return m_ecmascript_code; }
virtual Vector<Utf16FlyString> get_exported_names(VM& vm, HashTable<Module const*>& export_star_set) override;
virtual ResolvedBinding resolve_export(VM& vm, Utf16FlyString const& export_name, Vector<ResolvedBinding> resolve_set = {}) override;
@ -57,14 +55,10 @@ protected:
virtual ThrowCompletionOr<void> execute_module(VM& vm, GC::Ptr<PromiseCapability> capability) override;
private:
SourceTextModule(Realm&, StringView filename, Script::HostDefined* host_defined, bool has_top_level_await, NonnullRefPtr<Program> body, 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);
// Constructor for the Rust pipeline (pre-computed metadata, no AST).
SourceTextModule(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);
virtual void visit_edges(Cell::Visitor&) override;
RefPtr<Program> m_ecmascript_code; // [[ECMAScriptCode]]
NonnullOwnPtr<ExecutionContext> m_execution_context; // [[Context]]
GC::Ptr<Object> m_import_meta; // [[ImportMeta]]
Vector<ImportEntry> m_import_entries; // [[ImportEntries]]

View file

@ -195,10 +195,6 @@ static ErrorOr<bool> parse_and_run(JS::Realm& realm, StringView source, StringVi
JS::ThrowCompletionOr<JS::Value> result { JS::js_undefined() };
auto dump_ast = [&](auto const& node) {
node.dump({ .prefix = {}, .use_color = !s_strip_ansi });
};
if (!s_as_module) {
auto script_or_error = JS::Script::parse(source, realm, source_name);
if (script_or_error.is_error()) {
@ -214,8 +210,7 @@ static ErrorOr<bool> parse_and_run(JS::Realm& realm, StringView source, StringVi
result = vm.throw_completion<JS::SyntaxError>(move(error_string));
} else {
auto script = script_or_error.release_value();
if (s_dump_ast && script->parse_node())
dump_ast(*script->parse_node());
if (!parse_only)
result = vm.bytecode_interpreter().run(*script);
}
@ -234,8 +229,6 @@ static ErrorOr<bool> parse_and_run(JS::Realm& realm, StringView source, StringVi
result = vm.throw_completion<JS::SyntaxError>(move(error_string));
} else {
auto module = module_or_error.release_value();
if (s_dump_ast && module->parse_node())
dump_ast(*module->parse_node());
if (!parse_only)
result = vm.bytecode_interpreter().run(*module);
}