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:
parent
8d234620bc
commit
b2b72a1884
7 changed files with 177 additions and 34 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 ®ex_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 ®ex_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 ®ex_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 ®ex_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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue