Skip to content

Commit f489568

Browse files
authored
perf(hook): reduce hook macro expansion size and optimize performance (#14633)
* perf(hook): reduce hook macro expansion size * perf(hook): merge sorted additional tap stages * perf(hook): dedupe used stages without hash set * chore(hook): remove unused rustc-hash dependency
1 parent a316df1 commit f489568

8 files changed

Lines changed: 391 additions & 35 deletions

File tree

Cargo.lock

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

crates/rspack_hook/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ version.workspace = true
1111
async-trait = { workspace = true }
1212
rspack_error = { workspace = true }
1313
rspack_macros = { workspace = true }
14-
rustc-hash = { workspace = true }
1514
tracing = { workspace = true }
1615

1716
[lints]

crates/rspack_hook/src/lib.rs

Lines changed: 173 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,177 @@
11
use async_trait::async_trait;
22
use rspack_error::Result;
3-
use rustc_hash::FxHashSet;
3+
4+
pub struct HookMetadata {
5+
pub name: &'static str,
6+
}
7+
8+
pub struct HookCommon {
9+
metadata: HookMetadata,
10+
tap_stages: Vec<i32>,
11+
interceptor_count: usize,
12+
}
13+
14+
impl HookCommon {
15+
pub fn new(name: &'static str) -> Self {
16+
Self {
17+
metadata: HookMetadata { name },
18+
tap_stages: Vec::new(),
19+
interceptor_count: 0,
20+
}
21+
}
22+
23+
pub fn name(&self) -> &'static str {
24+
self.metadata.name
25+
}
26+
27+
pub fn tap_stages(&self) -> &[i32] {
28+
&self.tap_stages
29+
}
30+
31+
pub fn push_tap_stage(&mut self, stage: i32) {
32+
self.tap_stages.push(stage);
33+
}
34+
35+
pub fn insert_tap_stage(&mut self, index: usize, stage: i32) {
36+
self.tap_stages.insert(index, stage);
37+
}
38+
39+
pub fn tap_insert_position(&self, stage: i32) -> usize {
40+
self.tap_stages.partition_point(|&current| current <= stage)
41+
}
42+
43+
pub fn increment_interceptor_count(&mut self) {
44+
self.interceptor_count += 1;
45+
}
46+
47+
pub fn interceptor_count(&self) -> usize {
48+
self.interceptor_count
49+
}
50+
51+
pub fn used_stages(&self) -> Vec<i32> {
52+
let mut used_stages = self.tap_stages.clone();
53+
// tap_stages is kept sorted by stage, so duplicate stages are adjacent.
54+
used_stages.dedup();
55+
used_stages
56+
}
57+
58+
pub fn is_empty(&self) -> bool {
59+
self.tap_stages.is_empty() && self.interceptor_count == 0
60+
}
61+
}
62+
63+
pub fn sort_indices_by_stage(stages: &[i32]) -> Vec<u16> {
64+
debug_assert!(stages.len() <= HookTapIndex::INDEX_LIMIT);
65+
let mut indices: Vec<_> = (0..stages.len()).map(|index| index as u16).collect();
66+
indices.sort_by_key(|&index| {
67+
let index = index as usize;
68+
(stages[index], index)
69+
});
70+
debug_assert!(indices.windows(2).all(|indices| {
71+
let prev = indices[0] as usize;
72+
let next = indices[1] as usize;
73+
(stages[prev], prev) <= (stages[next], next)
74+
}));
75+
indices
76+
}
77+
78+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79+
pub struct HookTapIndex(u16);
80+
81+
impl HookTapIndex {
82+
const INTERCEPT_FLAG: u16 = 1 << 15;
83+
const INDEX_MASK: u16 = !Self::INTERCEPT_FLAG;
84+
const INDEX_LIMIT: usize = Self::INDEX_MASK as usize + 1;
85+
86+
pub fn tap(index: u16) -> Self {
87+
debug_assert!(index <= Self::INDEX_MASK);
88+
Self(index)
89+
}
90+
91+
pub fn intercept(index: u16) -> Self {
92+
debug_assert!(index <= Self::INDEX_MASK);
93+
Self(Self::INTERCEPT_FLAG | index)
94+
}
95+
96+
pub fn is_tap(self) -> bool {
97+
self.0 & Self::INTERCEPT_FLAG == 0
98+
}
99+
100+
pub fn index(self) -> usize {
101+
(self.0 & Self::INDEX_MASK) as usize
102+
}
103+
}
104+
105+
pub struct MergedTapIndicesByStage<'a> {
106+
base_stages: &'a [i32],
107+
additional_stages: &'a [i32],
108+
additional_order: Vec<u16>,
109+
base_index: u16,
110+
additional_cursor: u16,
111+
}
112+
113+
pub fn merged_tap_indices_by_stage<'a>(
114+
base_stages: &'a [i32],
115+
additional_stages: &'a [i32],
116+
) -> MergedTapIndicesByStage<'a> {
117+
debug_assert!(base_stages.len() <= HookTapIndex::INDEX_LIMIT);
118+
debug_assert!(additional_stages.len() <= HookTapIndex::INDEX_LIMIT);
119+
debug_assert!(base_stages.windows(2).all(|stages| stages[0] <= stages[1]));
120+
let additional_order = sort_indices_by_stage(additional_stages);
121+
debug_assert_eq!(additional_order.len(), additional_stages.len());
122+
debug_assert!(
123+
additional_order
124+
.iter()
125+
.all(|&index| (index as usize) < additional_stages.len())
126+
);
127+
MergedTapIndicesByStage {
128+
base_stages,
129+
additional_stages,
130+
additional_order,
131+
base_index: 0,
132+
additional_cursor: 0,
133+
}
134+
}
135+
136+
impl Iterator for MergedTapIndicesByStage<'_> {
137+
type Item = HookTapIndex;
138+
139+
fn next(&mut self) -> Option<Self::Item> {
140+
let base_index = self.base_index as usize;
141+
let additional_cursor = self.additional_cursor as usize;
142+
debug_assert!(base_index <= self.base_stages.len());
143+
debug_assert!(additional_cursor <= self.additional_order.len());
144+
if base_index == self.base_stages.len() && additional_cursor == self.additional_order.len() {
145+
return None;
146+
}
147+
148+
if additional_cursor == self.additional_order.len() {
149+
let index = self.base_index;
150+
debug_assert!(index <= HookTapIndex::INDEX_MASK);
151+
self.base_index += 1;
152+
return Some(HookTapIndex::tap(index));
153+
}
154+
155+
if base_index == self.base_stages.len() {
156+
let index = self.additional_order[additional_cursor];
157+
debug_assert!(self.additional_cursor <= HookTapIndex::INDEX_MASK);
158+
self.additional_cursor += 1;
159+
return Some(HookTapIndex::intercept(index));
160+
}
161+
162+
let additional_index = self.additional_order[additional_cursor];
163+
if self.base_stages[base_index] <= self.additional_stages[additional_index as usize] {
164+
let index = self.base_index;
165+
debug_assert!(index <= HookTapIndex::INDEX_MASK);
166+
self.base_index += 1;
167+
Some(HookTapIndex::tap(index))
168+
} else {
169+
debug_assert!(self.additional_cursor <= HookTapIndex::INDEX_MASK);
170+
self.additional_cursor += 1;
171+
Some(HookTapIndex::intercept(additional_index))
172+
}
173+
}
174+
}
4175

5176
#[async_trait]
6177
pub trait Interceptor<H: Hook> {
@@ -16,7 +187,7 @@ pub trait Interceptor<H: Hook> {
16187
pub trait Hook {
17188
type Tap;
18189

19-
fn used_stages(&self) -> FxHashSet<i32>;
190+
fn used_stages(&self) -> Vec<i32>;
20191

21192
fn intercept(&mut self, interceptor: impl Interceptor<Self> + Send + Sync + 'static)
22193
where
@@ -31,7 +202,6 @@ pub trait Hook {
31202
pub mod __macro_helper {
32203
pub use async_trait::async_trait;
33204
pub use rspack_error::Result;
34-
pub use rustc_hash::FxHashSet;
35205
pub use tracing;
36206
}
37207

crates/rspack_macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ test = false
1414
[dependencies]
1515
proc-macro2 = { workspace = true }
1616
quote = { workspace = true }
17-
syn = { workspace = true, features = ["full", "visit-mut", "derive", "parsing", "printing", "clone-impls", "proc-macro"] }
17+
syn = { workspace = true, features = ["full", "visit", "visit-mut", "derive", "parsing", "printing", "clone-impls", "proc-macro"] }
1818

1919
[lints]
2020
workspace = true

0 commit comments

Comments
 (0)