Skip to content

Commit 290a41e

Browse files
authored
Optimizing AST Traversal with Adaptive Iteration (#5754)
## Description Implements a new `adaptive_iter` method for use when traversing the ASTs. If the size of the collection is less than 8, then regular sequential iteration is used, otherwise a rayon parallel iterator is used. I tested all values between 0-30 and found that 8 to be the sweet spot. Less than this we don't see any benefit as the overhead for spawning rayon threads isn't worth it. This also fixes a stackoverlow error I noticed below. With this change, we aren't overloading the rayon thread stack size when the recursion level is quite deep. ``` thread '<unknown>' has overflowed its stack fatal runtime error: stack overflow error: test failed, to rerun pass `--test lib` Caused by: process didn't exit successfully: `/Users/josh/Documents/rust/fuel/sway/target/debug/deps/lib-6ef1da655cf2d895 pedro --nocapture` (signal: 6, SIGABRT: process abort signal) ```
1 parent 4d23798 commit 290a41e

File tree

6 files changed

+291
-414
lines changed

6 files changed

+291
-414
lines changed

Cargo.lock

+21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sway-lsp/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ parking_lot = "0.12.1"
2424
proc-macro2 = "1.0.5"
2525
quote = "1.0.9"
2626
rayon = "1.5.0"
27+
rayon-cond = "0.3"
2728
ropey = "1.2"
2829
serde = { version = "1.0", features = ["derive"] }
2930
serde_json = "1.0.60"

sway-lsp/src/traverse/lexed_tree.rs

+31-51
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::{
22
core::token::{AstToken, SymbolKind, Token},
3-
traverse::{Parse, ParseContext},
3+
traverse::{adaptive_iter, Parse, ParseContext},
44
};
5-
use rayon::iter::{IntoParallelRefIterator, ParallelBridge, ParallelIterator};
5+
use rayon::iter::{ParallelBridge, ParallelIterator};
66
use sway_ast::{
77
expr::LoopControlFlow, ty::TyTupleDescriptor, Assignable, CodeBlockContents, ConfigurableField,
88
Expr, ExprArrayDescriptor, ExprStructField, ExprTupleDescriptor, FnArg, FnArgs, FnSignature,
@@ -16,23 +16,18 @@ use sway_types::{Ident, Span, Spanned};
1616

1717
pub fn parse(lexed_program: &LexedProgram, ctx: &ParseContext) {
1818
insert_module_kind(ctx, &lexed_program.root.tree.kind);
19-
lexed_program
20-
.root
21-
.tree
22-
.items
23-
.par_iter()
24-
.for_each(|item| item.value.parse(ctx));
19+
adaptive_iter(&lexed_program.root.tree.items, |item| {
20+
item.value.parse(ctx);
21+
});
2522

2623
lexed_program
2724
.root
2825
.submodules_recursive()
2926
.for_each(|(_, dep)| {
3027
insert_module_kind(ctx, &dep.module.tree.kind);
31-
dep.module
32-
.tree
33-
.items
34-
.par_iter()
35-
.for_each(|item| item.value.parse(ctx));
28+
adaptive_iter(&dep.module.tree.items, |item| {
29+
item.value.parse(ctx);
30+
});
3631
});
3732
}
3833

@@ -157,7 +152,7 @@ impl Parse for Expr {
157152
} => {
158153
insert_keyword(ctx, match_token.span());
159154
value.parse(ctx);
160-
branches.get().iter().par_bridge().for_each(|branch| {
155+
adaptive_iter(branches.get(), |branch| {
161156
branch.pattern.parse(ctx);
162157
branch.kind.parse(ctx);
163158
});
@@ -326,21 +321,15 @@ impl Parse for ItemTrait {
326321
insert_keyword(ctx, where_clause_opt.where_token.span());
327322
}
328323

329-
self.trait_items
330-
.get()
331-
.par_iter()
332-
.for_each(|annotated| match &annotated.value {
333-
sway_ast::ItemTraitItem::Fn(fn_sig, _) => fn_sig.parse(ctx),
334-
sway_ast::ItemTraitItem::Const(item_const, _) => item_const.parse(ctx),
335-
sway_ast::ItemTraitItem::Type(item_type, _) => item_type.parse(ctx),
336-
sway_ast::ItemTraitItem::Error(_, _) => {}
337-
});
324+
adaptive_iter(self.trait_items.get(), |annotated| match &annotated.value {
325+
sway_ast::ItemTraitItem::Fn(fn_sig, _) => fn_sig.parse(ctx),
326+
sway_ast::ItemTraitItem::Const(item_const, _) => item_const.parse(ctx),
327+
sway_ast::ItemTraitItem::Type(item_type, _) => item_type.parse(ctx),
328+
sway_ast::ItemTraitItem::Error(_, _) => {}
329+
});
338330

339331
if let Some(trait_defs_opt) = &self.trait_defs_opt {
340-
trait_defs_opt
341-
.get()
342-
.par_iter()
343-
.for_each(|item| item.value.parse(ctx));
332+
adaptive_iter(trait_defs_opt.get(), |item| item.value.parse(ctx));
344333
}
345334
}
346335
}
@@ -359,36 +348,27 @@ impl Parse for ItemImpl {
359348
insert_keyword(ctx, where_clause_opt.where_token.span());
360349
}
361350

362-
self.contents
363-
.get()
364-
.par_iter()
365-
.for_each(|item| match &item.value {
366-
ItemImplItem::Fn(fn_decl) => fn_decl.parse(ctx),
367-
ItemImplItem::Const(const_decl) => const_decl.parse(ctx),
368-
ItemImplItem::Type(type_decl) => type_decl.parse(ctx),
369-
});
351+
adaptive_iter(self.contents.get(), |item| match &item.value {
352+
ItemImplItem::Fn(fn_decl) => fn_decl.parse(ctx),
353+
ItemImplItem::Const(const_decl) => const_decl.parse(ctx),
354+
ItemImplItem::Type(type_decl) => type_decl.parse(ctx),
355+
});
370356
}
371357
}
372358

373359
impl Parse for ItemAbi {
374360
fn parse(&self, ctx: &ParseContext) {
375361
insert_keyword(ctx, self.abi_token.span());
376362

377-
self.abi_items
378-
.get()
379-
.par_iter()
380-
.for_each(|annotated| match &annotated.value {
381-
sway_ast::ItemTraitItem::Fn(fn_sig, _) => fn_sig.parse(ctx),
382-
sway_ast::ItemTraitItem::Const(item_const, _) => item_const.parse(ctx),
383-
sway_ast::ItemTraitItem::Type(item_type, _) => item_type.parse(ctx),
384-
sway_ast::ItemTraitItem::Error(_, _) => {}
385-
});
363+
adaptive_iter(self.abi_items.get(), |annotated| match &annotated.value {
364+
sway_ast::ItemTraitItem::Fn(fn_sig, _) => fn_sig.parse(ctx),
365+
sway_ast::ItemTraitItem::Const(item_const, _) => item_const.parse(ctx),
366+
sway_ast::ItemTraitItem::Type(item_type, _) => item_type.parse(ctx),
367+
sway_ast::ItemTraitItem::Error(_, _) => {}
368+
});
386369

387370
if let Some(abi_defs_opt) = self.abi_defs_opt.as_ref() {
388-
abi_defs_opt
389-
.get()
390-
.par_iter()
391-
.for_each(|item| item.value.parse(ctx));
371+
adaptive_iter(abi_defs_opt.get(), |item| item.value.parse(ctx));
392372
}
393373
}
394374
}
@@ -576,9 +556,9 @@ impl Parse for FnArg {
576556

577557
impl Parse for CodeBlockContents {
578558
fn parse(&self, ctx: &ParseContext) {
579-
self.statements
580-
.par_iter()
581-
.for_each(|statement| statement.parse(ctx));
559+
adaptive_iter(&self.statements, |statement| {
560+
statement.parse(ctx);
561+
});
582562
if let Some(expr) = self.final_expr_opt.as_ref() {
583563
expr.parse(ctx);
584564
}

sway-lsp/src/traverse/mod.rs

+17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::core::{token::TokenIdent, token_map::TokenMap};
2+
use rayon_cond::CondIterator;
23
use sway_core::{namespace::Module, Engines};
34

45
pub(crate) mod dependency;
@@ -30,3 +31,19 @@ impl<'a> ParseContext<'a> {
3031
pub trait Parse {
3132
fn parse(&self, ctx: &ParseContext);
3233
}
34+
35+
/// Determines the threshold a collection must meet to be processed in parallel.
36+
const PARALLEL_THRESHOLD: usize = 8;
37+
38+
/// Iterates over items, choosing parallel or sequential execution based on size.
39+
pub fn adaptive_iter<T, F>(items: &[T], action: F)
40+
where
41+
T: Sync + Send, // Required for parallel processing
42+
F: Fn(&T) + Sync + Send, // Action to be applied to each item
43+
{
44+
// Determine if the length meets the parallel threshold
45+
let use_parallel = items.len() >= PARALLEL_THRESHOLD;
46+
47+
// Create a conditional iterator based on the use_parallel flag
48+
CondIterator::new(items, use_parallel).for_each(action);
49+
}

0 commit comments

Comments
 (0)