LibJS: Defer regex literal compilation to post-parse step

Move regex compilation out of the parsing hot path. Both the C++ and
Rust parsers now collect raw regex pattern+flags strings during parsing
and batch-compile them after parsing completes.

This is a prerequisite for moving the Rust parser to a background
thread, since LibRegex is thread-unsafe and FFI calls during parsing
prevent parallelization.

Flag validation remains in the parser since it's trivial string
checking with no LibRegex dependency.
This commit is contained in:
Andreas Kling 2026-02-26 22:39:24 +01:00 committed by Andreas Kling
parent 8d234620bc
commit b2b72a1884
7 changed files with 177 additions and 34 deletions

View file

@ -1373,6 +1373,15 @@ public:
{
}
RegExpLiteral(SourceRange source_range, Utf16String pattern, Utf16String flags, regex::RegexOptions<ECMAScriptFlags> parsed_flags)
: Expression(move(source_range))
, m_parsed_regex { .bytecode = regex::ByteCode {} }
, m_parsed_flags(parsed_flags)
, m_pattern(move(pattern))
, m_flags(move(flags))
{
}
virtual void dump(ASTDumpState const& state = {}) const override;
virtual Optional<Bytecode::ScopedOperand> generate_bytecode(Bytecode::Generator&, Optional<Bytecode::ScopedOperand> preferred_dst = {}) const override;
@ -1382,9 +1391,15 @@ public:
Utf16String const& pattern() const { return m_pattern; }
Utf16String const& flags() const { return m_flags; }
void set_compiled_regex(regex::Parser::Result parsed_regex, String parsed_pattern) const
{
m_parsed_regex = move(parsed_regex);
m_parsed_pattern = move(parsed_pattern);
}
private:
regex::Parser::Result m_parsed_regex;
String m_parsed_pattern;
mutable regex::Parser::Result m_parsed_regex;
mutable String m_parsed_pattern;
regex::RegexOptions<ECMAScriptFlags> m_parsed_flags;
Utf16String m_pattern;
Utf16String m_flags;

View file

@ -254,6 +254,7 @@ NonnullRefPtr<Program> Parser::parse_program(bool starts_in_strict_mode)
parse_module(program);
}
compile_regex_literals();
scope_collector().analyze(m_is_dynamic_function);
program->set_end_offset({}, position().offset);
@ -1365,21 +1366,10 @@ NonnullRefPtr<RegExpLiteral const> Parser::parse_regexp_literal()
parsed_flags = parsed_flags_or_error.release_value();
}
String parsed_pattern;
auto parsed_pattern_result = parse_regex_pattern(pattern, parsed_flags.has_flag_set(ECMAScriptFlags::Unicode), parsed_flags.has_flag_set(ECMAScriptFlags::UnicodeSets));
if (parsed_pattern_result.is_error()) {
syntax_error(parsed_pattern_result.release_error().error, rule_start.position());
parsed_pattern = ""_string;
} else {
parsed_pattern = parsed_pattern_result.release_value();
}
auto parsed_regex = Regex<ECMA262>::parse_pattern(parsed_pattern, parsed_flags);
if (parsed_regex.error != regex::Error::NoError)
syntax_error(MUST(String::formatted("RegExp compile error: {}", Regex<ECMA262>(parsed_regex, parsed_pattern.to_byte_string(), parsed_flags).error_string())), rule_start.position());
SourceRange range { m_source_code, rule_start.position(), position() };
return create_ast_node<RegExpLiteral>(move(range), move(parsed_regex), move(parsed_pattern), parsed_flags, move(pattern), move(flags));
auto literal = create_ast_node<RegExpLiteral>(move(range), move(pattern), move(flags), parsed_flags);
m_deferred_regex_literals.append({ literal, rule_start.position() });
return literal;
}
static bool is_simple_assignment_target(Expression const& expression, bool allow_web_reality_call_expression = true)
@ -4054,20 +4044,44 @@ void Parser::syntax_error(String const& message, Optional<Position> position)
m_state.errors.append({ message, position });
}
void Parser::compile_regex_literals()
{
for (auto& deferred : m_deferred_regex_literals) {
auto const& pattern = deferred.literal->pattern();
auto const& parsed_flags = deferred.literal->parsed_flags();
auto parsed_pattern_result = parse_regex_pattern(pattern, parsed_flags.has_flag_set(ECMAScriptFlags::Unicode), parsed_flags.has_flag_set(ECMAScriptFlags::UnicodeSets));
String parsed_pattern;
if (parsed_pattern_result.is_error()) {
syntax_error(parsed_pattern_result.release_error().error, deferred.position);
parsed_pattern = ""_string;
} else {
parsed_pattern = parsed_pattern_result.release_value();
}
auto parsed_regex = Regex<ECMA262>::parse_pattern(parsed_pattern, parsed_flags);
if (parsed_regex.error != regex::Error::NoError)
syntax_error(MUST(String::formatted("RegExp compile error: {}", Regex<ECMA262>(parsed_regex, parsed_pattern.to_byte_string(), parsed_flags).error_string())), deferred.position);
deferred.literal->set_compiled_regex(move(parsed_regex), move(parsed_pattern));
}
m_deferred_regex_literals.clear();
}
void Parser::save_state()
{
m_saved_state.append(m_state);
m_saved_deferred_regex_sizes.append(m_deferred_regex_literals.size());
}
void Parser::load_state()
{
VERIFY(!m_saved_state.is_empty());
m_state = m_saved_state.take_last();
m_deferred_regex_literals.shrink(m_saved_deferred_regex_sizes.take_last());
}
void Parser::discard_saved_state()
{
m_saved_state.take_last();
m_saved_deferred_regex_sizes.take_last();
}
void Parser::check_identifier_name_for_assignment_validity(Utf16FlyString const& name, bool force_strict)
@ -4719,7 +4733,7 @@ Parser Parser::parse_function_body_from_string(ByteString const& body_string, u1
function_body = body_parser.parse_function_body(move(parameters), kind, parsing_insights);
}
body_parser.scope_collector().analyze();
body_parser.run_scope_analysis();
return body_parser;
}

View file

@ -196,7 +196,11 @@ public:
Vector<CallExpression::Argument> parse_arguments();
void run_scope_analysis() { m_scope_collector.analyze(); }
void run_scope_analysis()
{
compile_regex_literals();
m_scope_collector.analyze();
}
void set_is_dynamic_function() { m_is_dynamic_function = true; }
bool has_errors() const { return m_state.errors.size(); }
@ -246,6 +250,7 @@ private:
Token consume(TokenType type);
Token consume_and_validate_numeric_literal();
void consume_or_insert_semicolon();
void compile_regex_literals();
void save_state();
void load_state();
void discard_saved_state();
@ -329,6 +334,11 @@ private:
ScopeCollector& scope_collector() { return m_scope_collector_override ? *m_scope_collector_override : m_scope_collector; }
ScopeCollector const& scope_collector() const { return m_scope_collector_override ? *m_scope_collector_override : m_scope_collector; }
struct DeferredRegexLiteral {
NonnullRefPtr<RegExpLiteral const> literal;
Position position;
};
NonnullRefPtr<SourceCode const> m_source_code;
Vector<Position> m_rule_starts;
ParserState m_state;
@ -339,6 +349,9 @@ private:
bool m_is_dynamic_function { false };
ScopeCollector m_scope_collector;
ScopeCollector* m_scope_collector_override { nullptr };
Vector<DeferredRegexLiteral> m_deferred_regex_literals;
Vector<size_t> m_saved_deferred_regex_sizes;
};
}

View file

@ -1059,6 +1059,11 @@ impl CompiledRegex {
pub fn take(&self) -> *mut c_void {
self.0.replace(std::ptr::null_mut())
}
/// Set the compiled regex handle (used by deferred compilation).
pub fn set(&self, ptr: *mut c_void) {
self.0.set(ptr);
}
}
impl Drop for CompiledRegex {

View file

@ -336,6 +336,12 @@ pub unsafe extern "C" fn rust_compile_program(
let program = parser.parse_program(starts_in_strict_mode);
// Compile deferred regex literals.
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
if !regex_errors.is_empty() {
return std::ptr::null_mut();
}
if check_errors(&mut parser) {
return std::ptr::null_mut();
}
@ -406,6 +412,18 @@ pub unsafe extern "C" fn rust_compile_script(
let program = parser.parse_program(false);
// Compile deferred regex literals.
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
if !regex_errors.is_empty() {
if let Some(cb) = error_callback {
for err in &regex_errors {
let msg = err.message.as_bytes();
cb(error_context, msg.as_ptr(), msg.len(), err.line, err.column);
}
}
return std::ptr::null_mut();
}
if check_errors_with_callback(&mut parser, error_context, error_callback) {
return std::ptr::null_mut();
}
@ -505,6 +523,18 @@ pub unsafe extern "C" fn rust_compile_eval(
let program = parser.parse_program(starts_in_strict_mode);
// Compile deferred regex literals.
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
if !regex_errors.is_empty() {
if let Some(cb) = error_callback {
for err in &regex_errors {
let msg = err.message.as_bytes();
cb(error_context, msg.as_ptr(), msg.len(), err.line, err.column);
}
}
return std::ptr::null_mut();
}
if check_errors_with_callback(&mut parser, error_context, error_callback) {
return std::ptr::null_mut();
}
@ -689,6 +719,18 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
let mut parser = Parser::new(full_slice, ProgramType::Script);
let program = parser.parse_program(false);
// Compile deferred regex literals.
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
if !regex_errors.is_empty() {
if let Some(cb) = error_callback {
for err in &regex_errors {
let msg = err.message.as_bytes();
cb(error_context, msg.as_ptr(), msg.len(), err.line, err.column);
}
}
return std::ptr::null_mut();
}
if check_errors_with_callback(&mut parser, error_context, error_callback) {
return std::ptr::null_mut();
}
@ -801,6 +843,16 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
let mut parser = Parser::new(source_slice, ProgramType::Script);
let program = parser.parse_program(true); // strict mode
// Compile deferred regex literals.
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
if !regex_errors.is_empty() {
let errors: Vec<String> = regex_errors
.iter()
.map(|e| format!("{}:{}: {}", e.line, e.column, e.message))
.collect();
panic!("Regex errors in builtin file: {}", errors.join("; "));
}
if parser.has_errors() {
let errors: Vec<String> = parser
.errors()
@ -1035,6 +1087,18 @@ pub unsafe extern "C" fn rust_compile_module(
let mut parser = Parser::new(source_slice, ProgramType::Module);
let program = parser.parse_program(false);
// Compile deferred regex literals.
let regex_errors = Parser::compile_deferred_regexes(parser.take_deferred_regexes());
if !regex_errors.is_empty() {
if let Some(cb) = error_callback {
for err in &regex_errors {
let msg = err.message.as_bytes();
cb(error_context, msg.as_ptr(), msg.len(), err.line, err.column);
}
}
return std::ptr::null_mut();
}
if check_errors_with_callback(&mut parser, error_context, error_callback) {
return std::ptr::null_mut();
}

View file

@ -37,8 +37,9 @@ use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use crate::ast::{
BindingPattern, Expression, ExpressionKind, FunctionParameter, FunctionTable, Identifier,
PrivateIdentifier, ProgramData, ScopeData, SourceRange, Statement, StatementKind, Utf16String,
BindingPattern, CompiledRegex, Expression, ExpressionKind, FunctionParameter, FunctionTable,
Identifier, PrivateIdentifier, ProgramData, ScopeData, SourceRange, Statement, StatementKind,
Utf16String,
};
use crate::lexer::{Lexer, ch};
use crate::scope_collector::{ScopeCollector, ScopeCollectorState};
@ -202,12 +203,22 @@ pub(crate) struct ParserFlags {
pub in_property_key_context: bool,
}
/// A regex literal whose compilation is deferred until after parsing.
pub struct DeferredRegex {
pub compiled_regex: Rc<CompiledRegex>,
pub pattern: Vec<u16>,
pub flags: Vec<u16>,
pub line: u32,
pub column: u32,
}
/// Snapshot of parser state for speculative parsing (backtracking).
struct SavedState {
token: Token,
errors_len: usize,
flags: ParserFlags,
scope_collector_state: ScopeCollectorState,
deferred_regexes_len: usize,
}
/// The main JavaScript parser.
@ -290,6 +301,9 @@ pub struct Parser<'a> {
/// `(a=(b=(c=0)))` where each failed arrow attempt would otherwise
/// re-attempt inner positions during grouping expression re-parse.
arrow_function_failed_positions: HashSet<usize>,
/// Regex literals whose compilation is deferred until after parsing.
deferred_regexes: Vec<DeferredRegex>,
}
impl<'a> Parser<'a> {
@ -336,6 +350,7 @@ impl<'a> Parser<'a> {
exported_names: HashSet::new(),
function_table: FunctionTable::new(),
arrow_function_failed_positions: HashSet::new(),
deferred_regexes: Vec::new(),
}
}
@ -661,20 +676,28 @@ impl<'a> Parser<'a> {
self.syntax_error(&msg);
}
/// Compile a regex pattern+flags and return the opaque compiled handle.
/// On error, reports a syntax error and returns null.
pub(crate) fn compile_regex_pattern(
&mut self,
pattern: &[u16],
flags: &[u16],
) -> *mut std::ffi::c_void {
match crate::bytecode::ffi::compile_regex(pattern, flags) {
Ok(handle) => handle,
Err(msg) => {
self.syntax_error(&msg);
std::ptr::null_mut()
/// Take the deferred regex literals collected during parsing.
/// The caller is responsible for compiling them (on the main thread).
pub(crate) fn take_deferred_regexes(&mut self) -> Vec<DeferredRegex> {
std::mem::take(&mut self.deferred_regexes)
}
/// Batch-compile deferred regex literals. On error, returns the errors.
pub(crate) fn compile_deferred_regexes(deferred: Vec<DeferredRegex>) -> Vec<ParserError> {
let mut errors = Vec::new();
for d in deferred {
match crate::bytecode::ffi::compile_regex(&d.pattern, &d.flags) {
Ok(handle) => d.compiled_regex.set(handle),
Err(msg) => {
errors.push(ParserError {
message: msg,
line: d.line,
column: d.column,
});
}
}
}
errors
}
pub(crate) fn validate_regex_flags(&mut self, flags: &[u16]) {
@ -732,6 +755,7 @@ impl<'a> Parser<'a> {
errors_len: self.errors.len(),
flags: self.flags,
scope_collector_state: self.scope_collector.save_state(),
deferred_regexes_len: self.deferred_regexes.len(),
});
}
@ -739,6 +763,7 @@ impl<'a> Parser<'a> {
let state = self.saved_states.pop().expect("No saved state to restore");
self.current_token = state.token;
self.errors.truncate(state.errors_len);
self.deferred_regexes.truncate(state.deferred_regexes_len);
self.flags = state.flags;
self.scope_collector.load_state(state.scope_collector_state);
self.lexer.load_state();

View file

@ -650,13 +650,20 @@ impl<'a> Parser<'a> {
Vec::new()
};
self.validate_regex_flags(&flags);
let compiled_regex = self.compile_regex_pattern(&pattern, &flags);
let compiled_regex = Rc::new(CompiledRegex::new(std::ptr::null_mut()));
self.deferred_regexes.push(super::DeferredRegex {
compiled_regex: compiled_regex.clone(),
pattern: pattern.clone(),
flags: flags.clone(),
line: start.line,
column: start.column,
});
self.expression(
start,
ExpressionKind::RegExpLiteral(RegExpLiteralData {
pattern: pattern.into(),
flags: flags.into(),
compiled_regex: Rc::new(CompiledRegex::new(compiled_regex)),
compiled_regex,
}),
)
}