Skip to content

Commit 826dd24

Browse files
committed
Cache native grammar on parser grammar object
1 parent 5fc4ca2 commit 826dd24

2 files changed

Lines changed: 36 additions & 138 deletions

File tree

packages/mysql-on-sqlite/ext/wp-mysql-parser/src/lib.rs

Lines changed: 35 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::collections::{HashMap, HashSet};
44
use std::os::raw::c_char;
55
use std::ptr;
6-
use std::sync::{Arc, Mutex, OnceLock};
6+
use std::sync::Arc;
77

88
use ext_php_rs::convert::{FromZval, IntoZval, IntoZvalDyn};
99
use ext_php_rs::exception::{PhpException, PhpResult};
@@ -915,12 +915,6 @@ struct Rule {
915915
is_fragment: bool,
916916
}
917917

918-
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
919-
struct GrammarCacheKey {
920-
low: u64,
921-
high: u64,
922-
}
923-
924918
impl Grammar {
925919
fn rule(&self, rule_id: i64) -> Option<&Rule> {
926920
usize::try_from(rule_id)
@@ -930,10 +924,11 @@ impl Grammar {
930924
}
931925
}
932926

933-
// Cache only Rust-owned grammar data, keyed by exported grammar content. Zend
934-
// object handles are request-local and can be reused, so they must not identify
935-
// cached entries.
936-
static GRAMMAR_CACHE: OnceLock<Mutex<HashMap<GrammarCacheKey, Arc<Grammar>>>> = OnceLock::new();
927+
#[php_class]
928+
#[php(name = "WP_MySQL_Native_Grammar")]
929+
pub struct WpMySqlNativeGrammar {
930+
grammar: Arc<Grammar>,
931+
}
937932

938933
enum ParserTokenSource {
939934
Php(Vec<Zval>),
@@ -1675,23 +1670,17 @@ impl WpMySqlNativeParser {
16751670
}
16761671

16771672
fn export_grammar(grammar_zval: &mut Zval) -> PhpResult<Arc<Grammar>> {
1673+
if let Some(cached) = cached_native_grammar(grammar_zval)? {
1674+
return Ok(cached);
1675+
}
1676+
16781677
let exported = php_function("wp_sqlite_mysql_native_export_grammar")?
16791678
.try_call(vec![&*grammar_zval as &dyn IntoZvalDyn])
16801679
.map_err(php_error)?;
16811680
let array = exported
16821681
.array()
16831682
.ok_or_else(|| php_error("Exported grammar must be an array"))?;
16841683

1685-
let cache_key = grammar_cache_key(array)?;
1686-
if let Some(cached) = GRAMMAR_CACHE
1687-
.get_or_init(|| Mutex::new(HashMap::new()))
1688-
.lock()
1689-
.map_err(|_| php_error("Grammar cache lock poisoned"))?
1690-
.get(&cache_key)
1691-
{
1692-
return Ok(Arc::clone(cached));
1693-
}
1694-
16951684
let highest_terminal_id = array
16961685
.get("highest_terminal_id")
16971686
.and_then(Zval::long)
@@ -1741,130 +1730,37 @@ fn export_grammar(grammar_zval: &mut Zval) -> PhpResult<Arc<Grammar>> {
17411730
select_statement_rule_id,
17421731
});
17431732

1744-
GRAMMAR_CACHE
1745-
.get_or_init(|| Mutex::new(HashMap::new()))
1746-
.lock()
1747-
.map_err(|_| php_error("Grammar cache lock poisoned"))?
1748-
.insert(cache_key, Arc::clone(&grammar));
1733+
cache_native_grammar(grammar_zval, Arc::clone(&grammar))?;
17491734

17501735
Ok(grammar)
17511736
}
17521737

1753-
fn grammar_cache_key(array: &ZendHashTable) -> PhpResult<GrammarCacheKey> {
1754-
let mut hasher = GrammarCacheHasher::new();
1755-
hash_grammar_array(&mut hasher, array)?;
1756-
Ok(hasher.finish())
1757-
}
1758-
1759-
struct GrammarCacheHasher {
1760-
low: u64,
1761-
high: u64,
1762-
}
1763-
1764-
impl GrammarCacheHasher {
1765-
fn new() -> Self {
1766-
Self {
1767-
low: 0xcbf29ce484222325,
1768-
high: 0x6c62272e07bb0142,
1769-
}
1770-
}
1771-
1772-
fn write_byte(&mut self, byte: u8) {
1773-
self.low ^= u64::from(byte);
1774-
self.low = self.low.wrapping_mul(0x100000001b3);
1775-
self.high ^= u64::from(byte).rotate_left(5);
1776-
self.high = self.high.wrapping_mul(0x100000001b3 ^ 0x9e3779b97f4a7c15);
1777-
}
1778-
1779-
fn write_bytes(&mut self, bytes: &[u8]) {
1780-
self.write_usize(bytes.len());
1781-
for byte in bytes {
1782-
self.write_byte(*byte);
1783-
}
1784-
}
1785-
1786-
fn write_i64(&mut self, value: i64) {
1787-
for byte in value.to_le_bytes() {
1788-
self.write_byte(byte);
1789-
}
1790-
}
1791-
1792-
fn write_u64(&mut self, value: u64) {
1793-
for byte in value.to_le_bytes() {
1794-
self.write_byte(byte);
1795-
}
1796-
}
1797-
1798-
fn write_usize(&mut self, value: usize) {
1799-
self.write_u64(value as u64);
1800-
}
1801-
1802-
fn finish(self) -> GrammarCacheKey {
1803-
GrammarCacheKey {
1804-
low: self.low,
1805-
high: self.high,
1806-
}
1807-
}
1808-
}
1809-
1810-
fn hash_grammar_array(hasher: &mut GrammarCacheHasher, array: &ZendHashTable) -> PhpResult<()> {
1811-
hasher.write_usize(array.len());
1812-
for (key, value) in array {
1813-
hash_grammar_array_key(hasher, key);
1814-
hash_grammar_zval(hasher, value)?;
1815-
}
1816-
Ok(())
1817-
}
1818-
1819-
fn hash_grammar_zval(hasher: &mut GrammarCacheHasher, zval: &Zval) -> PhpResult<()> {
1820-
let zval = zval.dereference();
1821-
match zval.get_type() {
1822-
DataType::Null => hasher.write_byte(0),
1823-
DataType::False => hasher.write_byte(1),
1824-
DataType::True => hasher.write_byte(2),
1825-
DataType::Long => {
1826-
hasher.write_byte(3);
1827-
hasher.write_i64(
1828-
zval.long()
1829-
.ok_or_else(|| php_error("Grammar integer value is invalid"))?,
1830-
);
1831-
}
1832-
DataType::String => {
1833-
hasher.write_byte(4);
1834-
hasher.write_bytes(
1835-
zval.str()
1836-
.ok_or_else(|| php_error("Grammar string value is invalid"))?
1837-
.as_bytes(),
1838-
);
1839-
}
1840-
DataType::Array => {
1841-
hasher.write_byte(5);
1842-
let array = zval
1843-
.array()
1844-
.ok_or_else(|| php_error("Grammar array value is invalid"))?;
1845-
hash_grammar_array(hasher, array)?;
1846-
}
1847-
_ => return Err(php_error("Unsupported grammar cache value")),
1848-
}
1738+
fn cached_native_grammar(grammar: &Zval) -> PhpResult<Option<Arc<Grammar>>> {
1739+
let object = grammar
1740+
.object()
1741+
.ok_or_else(|| php_error("Parser grammar must be an object"))?;
1742+
let properties = object.get_properties().map_err(php_error)?;
1743+
let Some(native_grammar) = properties.get("native_grammar") else {
1744+
return Ok(None);
1745+
};
1746+
let Some(native_grammar) = <&WpMySqlNativeGrammar as FromZval>::from_zval(native_grammar)
1747+
else {
1748+
return Ok(None);
1749+
};
18491750

1850-
Ok(())
1751+
Ok(Some(Arc::clone(&native_grammar.grammar)))
18511752
}
18521753

1853-
fn hash_grammar_array_key(hasher: &mut GrammarCacheHasher, key: ArrayKey<'_>) {
1854-
match key {
1855-
ArrayKey::Long(value) => {
1856-
hasher.write_byte(0);
1857-
hasher.write_i64(value);
1858-
}
1859-
ArrayKey::String(value) => {
1860-
hasher.write_byte(1);
1861-
hasher.write_bytes(value.as_bytes());
1862-
}
1863-
ArrayKey::Str(value) => {
1864-
hasher.write_byte(1);
1865-
hasher.write_bytes(value.as_bytes());
1866-
}
1867-
}
1754+
fn cache_native_grammar(grammar_zval: &mut Zval, grammar: Arc<Grammar>) -> PhpResult<()> {
1755+
let object = grammar_zval
1756+
.object_mut()
1757+
.ok_or_else(|| php_error("Parser grammar must be an object"))?;
1758+
let native_grammar = WpMySqlNativeGrammar { grammar }
1759+
.into_zval(false)
1760+
.map_err(php_error)?;
1761+
object
1762+
.set_property("native_grammar", native_grammar)
1763+
.map_err(php_error)
18681764
}
18691765

18701766
fn export_tokens(tokens: &mut Zval) -> PhpResult<(ParserTokenSource, Vec<i64>)> {
@@ -2018,6 +1914,7 @@ extern "C" fn php_module_info(_module: *mut ModuleEntry) {
20181914
#[php_module]
20191915
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
20201916
module
1917+
.class::<WpMySqlNativeGrammar>()
20211918
.class::<WpMySqlNativeAst>()
20221919
.class::<WpMySqlNativeTokenStream>()
20231920
.class::<WpMySqlNativeLexer>()

packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class WP_Parser_Grammar {
3232
public $lookahead_is_match_possible = array();
3333
public $lowest_non_terminal_id;
3434
public $highest_terminal_id;
35+
public $native_grammar;
3536

3637
public function __construct( array $rules ) {
3738
$this->inflate( $rules );

0 commit comments

Comments
 (0)