Skip to content

Commit a1746fe

Browse files
committed
load sources in parallel
1 parent 3e98fd1 commit a1746fe

4 files changed

Lines changed: 83 additions & 51 deletions

File tree

src/build/mod.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -317,20 +317,27 @@ fn build_from_sources_impl(
317317
sources.len() - skip_count
318318
);
319319

320-
// Step 3: Parse only non-cached sources in parallel
321-
let parse_results: Vec<(usize, Result<(PathBuf, Module), BuildError>)> = sources
322-
.par_iter()
323-
.enumerate()
324-
.filter(|(i, _)| !skip_parse[*i])
325-
.map(|(i, &(path_str, source))| {
326-
let path = PathBuf::from(path_str);
327-
let result = match crate::parser::parse(source) {
328-
Ok(module) => Ok((path, module)),
329-
Err(e) => Err(BuildError::CompileError { path, error: e }),
330-
};
331-
(i, result)
332-
})
333-
.collect();
320+
// Step 3: Parse only non-cached sources in parallel (use a pool with large stacks
321+
// since the parser can recurse deeply on complex files)
322+
let parse_pool = rayon::ThreadPoolBuilder::new()
323+
.stack_size(16 * 1024 * 1024)
324+
.build()
325+
.expect("failed to build parse thread pool");
326+
let parse_results: Vec<(usize, Result<(PathBuf, Module), BuildError>)> = parse_pool.install(|| {
327+
sources
328+
.par_iter()
329+
.enumerate()
330+
.filter(|(i, _)| !skip_parse[*i])
331+
.map(|(i, &(path_str, source))| {
332+
let path = PathBuf::from(path_str);
333+
let result = match crate::parser::parse(source) {
334+
Ok(module) => Ok((path, module)),
335+
Err(e) => Err(BuildError::CompileError { path, error: e }),
336+
};
337+
(i, result)
338+
})
339+
.collect()
340+
});
334341

335342
// Step 4: Build parsed vec from both cached stubs and parsed results
336343
let mut parsed: Vec<ParsedModule> = Vec::new();

src/lsp/handlers/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl Backend {
6363
let module_name = interner::resolve_module_name(&module.name.value.parts);
6464
let module_parts: Vec<interner::Symbol> = module.name.value.parts.clone();
6565

66-
// Type-check against the registry
66+
// Type-check against the registry (use stacker to extend stack for deep recursion)
6767
let mut registry = self.registry.write().await;
6868
let check_result = crate::typechecker::check_module_with_registry(&module, &registry);
6969

src/lsp/handlers/load_sources.rs

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::HashMap;
22
use std::sync::atomic::Ordering;
33

4+
use rayon::prelude::*;
45
use tower_lsp::lsp_types::*;
56

67
use crate::build::BuildOptions;
@@ -51,7 +52,12 @@ impl Backend {
5152
let ready = self.ready.clone();
5253
let progress_token = token.clone();
5354

54-
tokio::task::spawn_blocking(move || {
55+
let rt_handle = tokio::runtime::Handle::current();
56+
std::thread::Builder::new()
57+
.name("pfc-load-sources".to_string())
58+
.stack_size(16 * 1024 * 1024) // 16 MB — typechecker needs deep recursion
59+
.spawn(move || {
60+
let _guard = rt_handle.enter();
5561
// Run the shell command to get source globs
5662
let output = match std::process::Command::new("sh")
5763
.arg("-c")
@@ -101,34 +107,40 @@ impl Backend {
101107
.await;
102108
});
103109

104-
// Resolve globs to file paths
105-
let mut sources: Vec<(String, String)> = Vec::new();
110+
// Resolve globs to file paths (collect paths first, then read in parallel)
111+
let mut file_paths: Vec<std::path::PathBuf> = Vec::new();
106112
for pattern in &globs {
107113
match glob::glob(pattern) {
108114
Ok(entries) => {
109115
for entry in entries.flatten() {
110116
if entry.extension().map_or(false, |ext| ext == "purs") {
111-
match std::fs::read_to_string(&entry) {
112-
Ok(source) => {
113-
let abs_path = entry
114-
.canonicalize()
115-
.unwrap_or_else(|_| entry.clone());
116-
sources.push((
117-
abs_path.to_string_lossy().into_owned(),
118-
source,
119-
));
120-
}
121-
Err(e) => {
122-
log::warn!("Failed to read {}: {e}", entry.display())
123-
}
124-
}
117+
file_paths.push(entry);
125118
}
126119
}
127120
}
128121
Err(e) => log::warn!("Invalid glob pattern {pattern}: {e}"),
129122
}
130123
}
131124

125+
// Read all files in parallel
126+
let sources: Vec<(String, String)> = file_paths
127+
.par_iter()
128+
.filter_map(|entry| {
129+
match std::fs::read_to_string(entry) {
130+
Ok(source) => {
131+
let abs_path = entry
132+
.canonicalize()
133+
.unwrap_or_else(|_| entry.clone());
134+
Some((abs_path.to_string_lossy().into_owned(), source))
135+
}
136+
Err(e) => {
137+
log::warn!("Failed to read {}: {e}", entry.display());
138+
None
139+
}
140+
}
141+
})
142+
.collect();
143+
132144
// Report progress: building
133145
rt.block_on(async {
134146
client
@@ -179,29 +191,37 @@ impl Backend {
179191
.filter(|m| !m.type_errors.is_empty())
180192
.count();
181193

182-
// Build definition index and resolution exports from parsed sources
183-
let mut index = DefinitionIndex::new();
184-
let mut smap = HashMap::new();
185-
let mut mfmap = HashMap::new();
186-
let mut parsed_modules = Vec::new();
187-
for (path, source) in &sources {
188-
if let Ok(module) = crate::parser::parse(source) {
189-
index.add_module(&module, path);
190-
let mod_name = format!("{}", module.name.value);
194+
// Parse all sources in parallel for definition index
195+
let parse_results: Vec<_> = sources
196+
.par_iter()
197+
.map(|(path, source)| {
191198
let file_uri = Url::from_file_path(path)
192199
.map(|u| u.to_string())
193200
.unwrap_or_default();
201+
match crate::parser::parse(source) {
202+
Ok(module) => {
203+
let mod_name = format!("{}", module.name.value);
204+
(path.clone(), file_uri, source.clone(), Some((module, mod_name)))
205+
}
206+
Err(_) => {
207+
(path.clone(), file_uri, source.clone(), None)
208+
}
209+
}
210+
})
211+
.collect();
212+
213+
// Merge results sequentially (add_module takes &mut self)
214+
let mut index = DefinitionIndex::new();
215+
let mut smap = HashMap::with_capacity(parse_results.len());
216+
let mut mfmap = HashMap::new();
217+
let mut parsed_modules = Vec::new();
218+
for (path, file_uri, source, parsed) in parse_results {
219+
if let Some((module, mod_name)) = parsed {
220+
index.add_module(&module, &path);
194221
mfmap.insert(mod_name, file_uri.clone());
195222
parsed_modules.push(module);
196-
smap.insert(file_uri, source.clone());
197-
} else {
198-
smap.insert(
199-
Url::from_file_path(path)
200-
.map(|u| u.to_string())
201-
.unwrap_or_default(),
202-
source.clone(),
203-
);
204223
}
224+
smap.insert(file_uri, source);
205225
}
206226

207227
let exports = crate::lsp::utils::resolve::ResolutionExports::new(&parsed_modules);
@@ -234,6 +254,7 @@ impl Backend {
234254
})
235255
.await;
236256
});
237-
});
257+
})
258+
.expect("failed to spawn load-sources thread");
238259
}
239260
}

src/lsp/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ impl Backend {
154154
}
155155

156156
pub fn run_server(sources_cmd: Option<String>) {
157-
let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
157+
let rt = tokio::runtime::Builder::new_multi_thread()
158+
.enable_all()
159+
.thread_stack_size(16 * 1024 * 1024) // 16 MB — typechecker needs deep recursion
160+
.build()
161+
.expect("failed to create tokio runtime");
158162
rt.block_on(async {
159163
let stdin = tokio::io::stdin();
160164
let stdout = tokio::io::stdout();

0 commit comments

Comments
 (0)