Skip to content

Commit 38dc614

Browse files
committed
feat(oxc_linter): reuse allocators (#11736)
1 parent 584844c commit 38dc614

File tree

9 files changed

+109
-15
lines changed

9 files changed

+109
-15
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/oxlint/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ test = false
2525
doctest = false
2626

2727
[dependencies]
28+
oxc_allocator = { workspace = true }
2829
oxc_diagnostics = { workspace = true }
2930
oxc_linter = { workspace = true }
3031
oxc_span = { workspace = true }

apps/oxlint/src/lint.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010

1111
use cow_utils::CowUtils;
1212
use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
13+
use oxc_allocator::AllocatorPool;
1314
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
1415
use oxc_linter::{
1516
AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, InvalidFilterKind, LintFilter,
@@ -290,9 +291,11 @@ impl Runner for LintRunner {
290291

291292
let number_of_rules = linter.number_of_rules();
292293

294+
let allocator_pool = AllocatorPool::new(rayon::current_num_threads());
295+
293296
// Spawn linting in another thread so diagnostics can be printed immediately from diagnostic_service.run.
294297
rayon::spawn(move || {
295-
let mut lint_service = LintService::new(&linter, options);
298+
let mut lint_service = LintService::new(&linter, allocator_pool, options);
296299
lint_service.run(&tx_error);
297300
});
298301

crates/oxc_allocator/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod convert;
3232
#[cfg(feature = "from_raw_parts")]
3333
mod from_raw_parts;
3434
pub mod hash_map;
35+
mod pool;
3536
mod string_builder;
3637
mod take_in;
3738
mod vec;
@@ -44,6 +45,7 @@ pub use boxed::Box;
4445
pub use clone_in::CloneIn;
4546
pub use convert::{FromIn, IntoIn};
4647
pub use hash_map::HashMap;
48+
pub use pool::{AllocatorGuard, AllocatorPool};
4749
pub use string_builder::StringBuilder;
4850
pub use take_in::{Dummy, TakeIn};
4951
pub use vec::Vec;

crates/oxc_allocator/src/pool.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::mem::ManuallyDrop;
2+
use std::sync::{Arc, Mutex};
3+
4+
use crate::Allocator;
5+
6+
/// A thread-safe pool for reusing `Allocator` instances to reduce allocation overhead.
7+
///
8+
/// Internally uses a `Vec` protected by a `Mutex` to store available allocators.
9+
#[derive(Default)]
10+
pub struct AllocatorPool {
11+
allocators: Arc<Mutex<Vec<Allocator>>>,
12+
}
13+
14+
/// A guard object representing exclusive access to an `Allocator` from the pool.
15+
///
16+
/// On drop, the `Allocator` is reset and returned to the pool.
17+
pub struct AllocatorGuard {
18+
allocator: ManuallyDrop<Allocator>,
19+
pool: Arc<Mutex<Vec<Allocator>>>,
20+
}
21+
22+
impl AllocatorPool {
23+
/// Creates a new `AllocatorPool` pre-filled with the given number of default `Allocator` instances.
24+
pub fn new(size: usize) -> AllocatorPool {
25+
let allocators = std::iter::repeat_with(Allocator::default).take(size).collect();
26+
AllocatorPool { allocators: Arc::new(Mutex::new(allocators)) }
27+
}
28+
29+
/// Retrieves an `Allocator` from the pool, or creates a new one if the pool is empty.
30+
///
31+
/// Returns an `AllocatorPoolGuard` that gives mutable access to the allocator.
32+
///
33+
/// # Panics
34+
///
35+
/// Panics if the underlying mutex is poisoned.
36+
pub fn get(&self) -> AllocatorGuard {
37+
let allocator = {
38+
let mut allocators = self.allocators.lock().unwrap();
39+
allocators.pop().unwrap_or_default()
40+
};
41+
42+
AllocatorGuard {
43+
allocator: ManuallyDrop::new(allocator),
44+
pool: Arc::clone(&self.allocators),
45+
}
46+
}
47+
}
48+
49+
impl std::ops::Deref for AllocatorGuard {
50+
type Target = Allocator;
51+
52+
fn deref(&self) -> &Self::Target {
53+
&self.allocator
54+
}
55+
}
56+
57+
impl std::ops::DerefMut for AllocatorGuard {
58+
fn deref_mut(&mut self) -> &mut Self::Target {
59+
&mut self.allocator
60+
}
61+
}
62+
63+
impl Drop for AllocatorGuard {
64+
fn drop(&mut self) {
65+
// SAFETY: we're taking ownership and promise not to drop `allocator` again.
66+
let mut allocator = unsafe { ManuallyDrop::take(&mut self.allocator) };
67+
allocator.reset();
68+
let mut allocators = self.pool.lock().unwrap();
69+
allocators.push(allocator);
70+
}
71+
}

crates/oxc_language_server/src/linter/isolated_lint_handler.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use tower_lsp_server::{
1010
lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverity, Uri},
1111
};
1212

13-
use oxc_allocator::Allocator;
13+
use oxc_allocator::{Allocator, AllocatorPool};
1414
use oxc_linter::RuntimeFileSystem;
1515
use oxc_linter::{
1616
LINTABLE_EXTENSIONS, LintService, LintServiceOptions, Linter, MessageWithPosition,
@@ -140,9 +140,11 @@ impl IsolatedLintHandler {
140140
.with_cross_module(self.options.use_cross_module);
141141

142142
let mut lint_service =
143-
LintService::new(&self.linter, lint_service_options).with_file_system(Box::new(
144-
IsolatedLintHandlerFileSystem::new(path.to_path_buf(), source_text),
145-
));
143+
LintService::new(&self.linter, AllocatorPool::default(), lint_service_options)
144+
.with_file_system(Box::new(IsolatedLintHandlerFileSystem::new(
145+
path.to_path_buf(),
146+
source_text,
147+
)));
146148
let result = lint_service.run_source(allocator);
147149

148150
Some(result)

crates/oxc_linter/src/service/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ pub struct LintService<'l> {
7070
}
7171

7272
impl<'l> LintService<'l> {
73-
pub fn new(linter: &'l Linter, options: LintServiceOptions) -> Self {
74-
let runtime = Runtime::new(linter, options);
73+
pub fn new(
74+
linter: &'l Linter,
75+
allocator_pool: oxc_allocator::AllocatorPool,
76+
options: LintServiceOptions,
77+
) -> Self {
78+
let runtime = Runtime::new(linter, allocator_pool, options);
7579
Self { runtime }
7680
}
7781

crates/oxc_linter/src/service/runtime.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
1515
use self_cell::self_cell;
1616
use smallvec::SmallVec;
1717

18-
use oxc_allocator::Allocator;
18+
use oxc_allocator::{Allocator, AllocatorGuard, AllocatorPool};
1919
use oxc_diagnostics::{DiagnosticSender, DiagnosticService, Error, OxcDiagnostic};
2020
use oxc_parser::{ParseOptions, Parser};
2121
use oxc_resolver::Resolver;
@@ -42,6 +42,8 @@ pub struct Runtime<'l> {
4242
resolver: Option<Resolver>,
4343

4444
pub(super) file_system: Box<dyn RuntimeFileSystem + Sync + Send>,
45+
46+
allocator_pool: AllocatorPool,
4547
}
4648

4749
/// Output of `Runtime::process_path`
@@ -95,7 +97,7 @@ unsafe impl Send for ModuleContent {}
9597

9698
struct ModuleContentOwner {
9799
source_text: String,
98-
allocator: Allocator,
100+
allocator: AllocatorGuard,
99101
}
100102

101103
/// source text and semantic for each source section. They are in the same order as `ProcessedModule.section_module_records`
@@ -160,11 +162,16 @@ impl RuntimeFileSystem for OsFileSystem {
160162
}
161163

162164
impl<'l> Runtime<'l> {
163-
pub(super) fn new(linter: &'l Linter, options: LintServiceOptions) -> Self {
165+
pub(super) fn new(
166+
linter: &'l Linter,
167+
allocator_pool: AllocatorPool,
168+
options: LintServiceOptions,
169+
) -> Self {
164170
let resolver = options.cross_module.then(|| {
165171
Self::get_resolver(options.tsconfig.or_else(|| Some(options.cwd.join("tsconfig.json"))))
166172
});
167173
Self {
174+
allocator_pool,
168175
cwd: options.cwd,
169176
paths: options.paths.iter().cloned().collect(),
170177
linter,
@@ -771,7 +778,9 @@ impl<'l> Runtime<'l> {
771778
};
772779
let mut records = SmallVec::<[Result<ResolvedModuleRecord, Vec<OxcDiagnostic>>; 1]>::new();
773780
let mut module_content: Option<ModuleContent> = None;
774-
let allocator = Allocator::default();
781+
782+
let allocator = self.allocator_pool.get();
783+
775784
if self.paths.contains(path) {
776785
module_content =
777786
Some(ModuleContent::new(ModuleContentOwner { source_text, allocator }, |owner| {

crates/oxc_linter/src/tester.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
};
88

99
use cow_utils::CowUtils;
10-
use oxc_allocator::Allocator;
10+
use oxc_allocator::{Allocator, AllocatorPool};
1111
use oxc_diagnostics::{GraphicalReportHandler, GraphicalTheme, NamedSource};
1212
use rustc_hash::FxHashMap;
1313
use serde::Deserialize;
@@ -530,9 +530,10 @@ impl Tester {
530530
let paths = vec![Arc::<OsStr>::from(path_to_lint.as_os_str())];
531531
let options =
532532
LintServiceOptions::new(cwd, paths).with_cross_module(self.plugins.has_import());
533-
let mut lint_service = LintService::new(&linter, options).with_file_system(Box::new(
534-
TesterFileSystem::new(path_to_lint, source_text.to_string()),
535-
));
533+
let mut lint_service =
534+
LintService::new(&linter, AllocatorPool::default(), options).with_file_system(
535+
Box::new(TesterFileSystem::new(path_to_lint, source_text.to_string())),
536+
);
536537

537538
let (sender, _receiver) = mpsc::channel();
538539
let result = lint_service.run_test_source(&allocator, false, &sender);

0 commit comments

Comments
 (0)