Optimize parser reduction stack popping logic#57
Conversation
There was a problem hiding this comment.
Code Review
This pull request optimizes the generated parser code by batching consecutive unused stack pops into a single truncate operation for both the location and data stacks, reducing generated code size and overhead. The reviewer suggested refactoring the generator logic in rusty_lr_parser/src/emit.rs to extract the repetitive pop and truncate generation code into a local helper closure, which would eliminate duplication and improve maintainability.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // 1. Generate location pops/truncates | ||
| { | ||
| let mut consecutive_unneeded = 0; | ||
| for loc_mapto in &location_maptos { | ||
| if let Some(loc_mapto) = loc_mapto { | ||
| if consecutive_unneeded > 0 { | ||
| if consecutive_unneeded == 1 { | ||
| extract_data_stream.extend(quote! { | ||
| __location_stack.pop(); | ||
| }); | ||
| } else { | ||
| let consecutive_unneeded = | ||
| syn::Index::from(consecutive_unneeded); | ||
| extract_data_stream.extend(quote! { | ||
| __location_stack.truncate(__location_stack.len() - #consecutive_unneeded); | ||
| }); | ||
| } | ||
| consecutive_unneeded = 0; | ||
| } | ||
| extract_data_stream.extend(quote! { | ||
| let mut #loc_mapto = __location_stack.pop().unwrap(); | ||
| }); | ||
| } else { | ||
| quote! { val } | ||
| }; | ||
| extract_data_stream.extend(quote! { | ||
| let mut #data_mapto = match __data_stack.__stack.pop().unwrap() { | ||
| #data_enum_typename::#variant_name(val) => #val_extracted, | ||
| _ => unreachable!(), | ||
| consecutive_unneeded += 1; | ||
| } | ||
| } | ||
| if consecutive_unneeded > 0 { | ||
| if consecutive_unneeded == 1 { | ||
| extract_data_stream.extend(quote! { | ||
| __location_stack.pop(); | ||
| }); | ||
| } else { | ||
| let consecutive_unneeded = syn::Index::from(consecutive_unneeded); | ||
| extract_data_stream.extend(quote! { | ||
| __location_stack.truncate(__location_stack.len() - #consecutive_unneeded); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. Generate data stack pops/truncates | ||
| { | ||
| let mut consecutive_unneeded = 0; | ||
| for data_info in &data_maptos { | ||
| if let Some((variant_name, data_mapto, is_boxed)) = data_info { | ||
| if consecutive_unneeded > 0 { | ||
| if consecutive_unneeded == 1 { | ||
| extract_data_stream.extend(quote! { | ||
| __data_stack.__stack.pop(); | ||
| }); | ||
| } else { | ||
| let consecutive_unneeded = | ||
| syn::Index::from(consecutive_unneeded); | ||
| extract_data_stream.extend(quote! { | ||
| __data_stack.__stack.truncate(__data_stack.__stack.len() - #consecutive_unneeded); | ||
| }); | ||
| } | ||
| consecutive_unneeded = 0; | ||
| } | ||
| let val_extracted = if *is_boxed { | ||
| quote! { *val } | ||
| } else { | ||
| quote! { val } | ||
| }; | ||
| }); | ||
| } else { | ||
| extract_data_stream.extend(quote! { | ||
| __data_stack.__stack.pop().unwrap(); | ||
| }); | ||
| extract_data_stream.extend(quote! { | ||
| let mut #data_mapto = match __data_stack.__stack.pop().unwrap() { | ||
| #data_enum_typename::#variant_name(val) => #val_extracted, | ||
| _ => unreachable!(), | ||
| }; | ||
| }); | ||
| } else { | ||
| consecutive_unneeded += 1; | ||
| } | ||
| } | ||
| if consecutive_unneeded > 0 { | ||
| if consecutive_unneeded == 1 { | ||
| extract_data_stream.extend(quote! { | ||
| __data_stack.__stack.pop(); | ||
| }); | ||
| } else { | ||
| let consecutive_unneeded = syn::Index::from(consecutive_unneeded); | ||
| extract_data_stream.extend(quote! { | ||
| __data_stack.__stack.truncate(__data_stack.__stack.len() - #consecutive_unneeded); | ||
| }); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic for generating pop and truncate operations for both __location_stack and __data_stack is highly repetitive. We can extract this into a local helper closure to eliminate code duplication, improve maintainability, and make the code much cleaner.
let mut emit_unneeded = |stack: TokenStream, count: usize, stream: &mut TokenStream| {
if count == 1 {
stream.extend(quote! {
#stack.pop();
});
} else if count > 1 {
let count_idx = syn::Index::from(count);
stream.extend(quote! {
#stack.truncate(#stack.len() - #count_idx);
});
}
};
// 1. Generate location pops/truncates
{
let mut consecutive_unneeded = 0;
for loc_mapto in &location_maptos {
if let Some(loc_mapto) = loc_mapto {
emit_unneeded(quote! { __location_stack }, consecutive_unneeded, &mut extract_data_stream);
consecutive_unneeded = 0;
extract_data_stream.extend(quote! {
let mut #loc_mapto = __location_stack.pop().unwrap();
});
} else {
consecutive_unneeded += 1;
}
}
emit_unneeded(quote! { __location_stack }, consecutive_unneeded, &mut extract_data_stream);
}
// 2. Generate data stack pops/truncates
{
let mut consecutive_unneeded = 0;
for data_info in &data_maptos {
if let Some((variant_name, data_mapto, is_boxed)) = data_info {
emit_unneeded(quote! { __data_stack.__stack }, consecutive_unneeded, &mut extract_data_stream);
consecutive_unneeded = 0;
let val_extracted = if *is_boxed {
quote! { *val }
} else {
quote! { val }
};
extract_data_stream.extend(quote! {
let mut #data_mapto = match __data_stack.__stack.pop().unwrap() {
#data_enum_typename::#variant_name(val) => #val_extracted,
_ => unreachable!(),
};
});
} else {
consecutive_unneeded += 1;
}
}
emit_unneeded(quote! { __data_stack.__stack }, consecutive_unneeded, &mut extract_data_stream);
}
Description
This PR optimizes the generated parser's stack extraction logic during production rule reductions.
Currently, during a reduction step, the parser pops from the
__location_stackand__data_stackalternately for each token in the rule. In addition, it issues individual.pop().unwrap()calls for every discarded value, leading to extra branching and larger binary sizes in the generated parser.Key Changes
emit_data_stackinemit.rsto extract all required__location_stackelements first, followed by all__data_stackelements.Vec::truncate): Introduced an optimization that detects consecutive runs of discarded/unneeded elements from the stacks:Vec::truncatecall utilizing unsuffixed integer literals (e.g.,__location_stack.truncate(__location_stack.len() - 2))..pop()rather than.pop().unwrap(), avoiding unnecessary panic-checking code branches.syn::Index::fromfor numerical constants so they compile cleanly as unsuffixed integer literals, allowing Rust to infer the type asusizedirectly.Impact
.pop().unwrap()calls reduces compiled code size and compile times.