Merge 'parser: use YYSTACKDEPTH' from Lâm Hoàng Phúc

sqlite uses [fixed-size](https://github.com/sqlite/sqlite/blob/7fc6e6a27
26e650d3b82c6d3683bdbdc10e02467/tool/lempar.c#L238) array for `yystack`
and grow stack if needed. Let replace `vec` with `smallvec` in rust
version.
after:
```sh
sqlparser-rs parsing benchmark/sqlparser::select
                        time:   [564.19 ns 565.63 ns 567.18 ns]
                        change: [-11.514% -11.288% -11.067%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
sqlparser-rs parsing benchmark/sqlparser::with_select
                        time:   [1.9812 µs 1.9861 µs 1.9914 µs]
                        change: [-7.5226% -7.3080% -7.0858%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  4 (4.00%) high mild

```

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1999
This commit is contained in:
Pekka Enberg
2025-07-08 16:20:51 +03:00
4 changed files with 29 additions and 11 deletions

5
Cargo.lock generated
View File

@@ -3257,9 +3257,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.14.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
@@ -3917,6 +3917,7 @@ dependencies = [
"memchr",
"miette",
"serde",
"smallvec",
"strum",
"strum_macros",
]

View File

@@ -34,6 +34,7 @@ miette = "7.4.0"
strum = { workspace = true }
strum_macros = {workspace = true }
serde = { workspace = true , optional = true, features = ["derive"] }
smallvec = { version = "1.15.1", features = ["const_generics"] }
[dev-dependencies]
env_logger = { version = "0.11", default-features = false }

View File

@@ -4519,7 +4519,8 @@ void ReportTable(
if( lemp->stacksize ){
fprintf(out,"const YYSTACKDEPTH: usize = %s;\n",lemp->stacksize); lineno++;
} else {
fprintf(out, "const YYSTACKDEPTH: usize = 128;\n"); lineno++;
// from sqlite: The default value is 100. A typical application will use less than about 20 levels of the stack. Developers whose applications contain SQL statements that need more than 100 LALR(1) stack entries should seriously consider refactoring their SQL as it is likely to be well beyond the ability of any human to comprehend.
fprintf(out, "const YYSTACKDEPTH: usize = 100;\n"); lineno++;
}
if( lemp->errsym && lemp->errsym->useCnt ){
fprintf(out,"const YYERRORSYMBOL: YYCODETYPE = %d;\n",lemp->errsym->index); lineno++;

View File

@@ -184,12 +184,13 @@ pub struct yyParser<'input> {
//#[cfg(not(feature = "YYNOERRORRECOVERY"))]
yyerrcnt: i32, /* Shifts left before out of the error */
%% /* A place to hold %extra_context */
yystack: Vec<yyStackEntry<'input>>, /* The parser's stack */
yystack: smallvec::SmallVec<[yyStackEntry<'input>; YYSTACKDEPTH]>, /* The parser's stack */
}
use std::cmp::Ordering;
use std::ops::Neg;
impl<'input> yyParser<'input> {
#[inline]
fn shift(&self, shift: i8) -> usize {
assert!(shift <= 1);
match shift.cmp(&0) {
@@ -199,6 +200,7 @@ impl<'input> yyParser<'input> {
}
}
#[inline]
fn yyidx_shift(&mut self, shift: i8) {
match shift.cmp(&0) {
Ordering::Greater => self.yyidx += shift as usize,
@@ -207,12 +209,17 @@ impl<'input> yyParser<'input> {
}
}
#[inline]
fn yy_move(&mut self, shift: i8) -> yyStackEntry<'input> {
use std::mem::take;
let idx = self.shift(shift);
take(&mut self.yystack[idx])
// TODO: The compiler optimizes `std::mem::take` to two `memcpy`
// but `yyStackEntry` requires 168 bytes, so it is not worth it (maybe).
assert_eq!(std::mem::size_of::<yyStackEntry>(), 168);
std::mem::take(&mut self.yystack[idx])
}
#[inline]
fn push(&mut self, entry: yyStackEntry<'input>) {
if self.yyidx == self.yystack.len() {
self.yystack.push(entry);
@@ -226,12 +233,14 @@ use std::ops::{Index, IndexMut};
impl<'input> Index<i8> for yyParser<'input> {
type Output = yyStackEntry<'input>;
#[inline]
fn index(&self, shift: i8) -> &yyStackEntry<'input> {
let idx = self.shift(shift);
&self.yystack[idx]
}
}
impl<'input> IndexMut<i8> for yyParser<'input> {
#[inline]
fn index_mut(&mut self, shift: i8) -> &mut yyStackEntry<'input> {
let idx = self.shift(shift);
&mut self.yystack[idx]
@@ -261,9 +270,11 @@ static yyRuleName: [&str; YYNRULE] = [
** of errors. Return 0 on success.
*/
impl yyParser<'_> {
#[inline]
fn yy_grow_stack_if_needed(&mut self) -> bool {
false
}
#[inline]
fn yy_grow_stack_for_push(&mut self) -> bool {
// yystack is not prefilled with zero value like in C.
if self.yyidx == self.yystack.len() {
@@ -281,17 +292,15 @@ impl yyParser<'_> {
pub fn new(
%% /* Optional %extra_context parameter */
) -> yyParser {
let mut p = yyParser {
yyParser {
yyidx: 0,
#[cfg(feature = "YYTRACKMAXSTACKDEPTH")]
yyhwm: 0,
yystack: Vec::with_capacity(YYSTACKDEPTH),
yystack: smallvec::smallvec![yyStackEntry::default()],
//#[cfg(not(feature = "YYNOERRORRECOVERY"))]
yyerrcnt: -1,
%% /* Optional %extra_context store */
};
p.push(yyStackEntry::default());
p
}
}
}
@@ -299,6 +308,7 @@ impl yyParser<'_> {
** Pop the parser's stack once.
*/
impl yyParser<'_> {
#[inline]
fn yy_pop_parser_stack(&mut self) {
use std::mem::take;
let _yytos = take(&mut self.yystack[self.yyidx]);
@@ -319,6 +329,7 @@ impl yyParser<'_> {
*/
impl yyParser<'_> {
#[expect(non_snake_case)]
#[inline]
pub fn ParseFinalize(&mut self) {
while self.yyidx > 0 {
self.yy_pop_parser_stack();
@@ -333,9 +344,11 @@ impl yyParser<'_> {
#[cfg(feature = "YYTRACKMAXSTACKDEPTH")]
impl yyParser<'_> {
#[expect(non_snake_case)]
#[inline]
pub fn ParseStackPeak(&self) -> usize {
self.yyhwm
}
#[inline]
fn yyhwm_incr(&mut self) {
if self.yyidx > self.yyhwm {
self.yyhwm += 1;
@@ -488,6 +501,7 @@ fn yy_find_reduce_action(
impl yyParser<'_> {
#[expect(non_snake_case)]
#[cfg(feature = "NDEBUG")]
#[inline]
fn yyTraceShift(&self, _: YYACTIONTYPE, _: &str) {
}
#[expect(non_snake_case)]
@@ -893,6 +907,7 @@ impl<'input> yyParser<'input> {
** Return the fallback token corresponding to canonical token iToken, or
** 0 if iToken has no fallback.
*/
#[inline]
pub fn parse_fallback(i_token: YYCODETYPE) -> YYCODETYPE {
if YYFALLBACK {
return yyFallback[i_token as usize];