Skip to content

Commit c8af902

Browse files
authored
Revert "perf(alias): short-circuit load_alias with a 1+2 byte prefix index" (#230)
1 parent e836a64 commit c8af902

2 files changed

Lines changed: 6 additions & 130 deletions

File tree

src/lib.rs

Lines changed: 6 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -110,73 +110,10 @@ pub struct ResolveContext {
110110
/// Resolver with the current operating system as the file system
111111
pub type Resolver = ResolverGeneric<FileSystemOs>;
112112

113-
/// Pre-computed accelerator for [`ResolverGeneric::load_alias`].
114-
///
115-
/// Records which 1-byte and 2-byte specifier prefixes could possibly match an
116-
/// alias key. The check lets `load_alias` short-circuit before iterating every
117-
/// alias entry when the specifier can't match anything (e.g. a relative request
118-
/// like `./foo` against bare-name aliases).
119-
#[derive(Clone)]
120-
struct AliasFirstBytes {
121-
byte1_only: [bool; 256],
122-
byte12: rustc_hash::FxHashSet<u16>,
123-
/// An empty alias key (or a bare `$` exact-match) matches any absolute
124-
/// specifier in enhanced-resolve. When present, the prefix gate must
125-
/// fall through to the full loop instead of short-circuiting.
126-
wildcard: bool,
127-
}
128-
129-
impl AliasFirstBytes {
130-
fn build(aliases: &Alias) -> Self {
131-
let mut byte1_only = [false; 256];
132-
let mut byte12 = rustc_hash::FxHashSet::default();
133-
let mut wildcard = false;
134-
for (key, _) in aliases {
135-
// `$`-suffixed keys are exact-match aliases — index by the stripped key.
136-
let effective = key.strip_suffix('$').unwrap_or(key);
137-
let bytes = effective.as_bytes();
138-
match bytes {
139-
[b1] => byte1_only[*b1 as usize] = true,
140-
[b1, b2, ..] => {
141-
byte12.insert(u16::from(*b1) << 8 | u16::from(*b2));
142-
}
143-
[] => wildcard = true,
144-
}
145-
}
146-
Self {
147-
byte1_only,
148-
byte12,
149-
wildcard,
150-
}
151-
}
152-
153-
fn may_match_bytes(&self, bytes: &[u8]) -> bool {
154-
if self.wildcard {
155-
return true;
156-
}
157-
match bytes {
158-
[] => false,
159-
[b1] => self.byte1_only[*b1 as usize],
160-
[b1, b2, ..] => {
161-
self.byte1_only[*b1 as usize]
162-
|| self
163-
.byte12
164-
.contains(&(u16::from(*b1) << 8 | u16::from(*b2)))
165-
}
166-
}
167-
}
168-
169-
fn may_match(&self, specifier: &str) -> bool {
170-
self.may_match_bytes(specifier.as_bytes())
171-
}
172-
}
173-
174113
/// Generic implementation of the resolver, can be configured by the [FileSystem] trait
175114
pub struct ResolverGeneric<Fs> {
176115
options: ResolveOptions,
177116
cache: Arc<Cache<Fs>>,
178-
alias_first_bytes: AliasFirstBytes,
179-
fallback_first_bytes: AliasFirstBytes,
180117
#[cfg(feature = "yarn_pnp")]
181118
pnp_manifest: Arc<arc_swap::ArcSwapOption<(PathBuf, pnp::Manifest)>>,
182119
/// Paths that have been searched and confirmed to have no `.pnp.cjs` reachable by filesystem walk.
@@ -200,14 +137,9 @@ impl<Fs: Send + Sync + FileSystem + Default> Default for ResolverGeneric<Fs> {
200137

201138
impl<Fs: Send + Sync + FileSystem + Default> ResolverGeneric<Fs> {
202139
pub fn new(options: ResolveOptions) -> Self {
203-
let options = options.sanitize();
204-
let alias_first_bytes = AliasFirstBytes::build(&options.alias);
205-
let fallback_first_bytes = AliasFirstBytes::build(&options.fallback);
206140
Self {
207-
options,
141+
options: options.sanitize(),
208142
cache: Arc::new(Cache::new(Fs::default())),
209-
alias_first_bytes,
210-
fallback_first_bytes,
211143
#[cfg(feature = "yarn_pnp")]
212144
pnp_manifest: Arc::new(arc_swap::ArcSwapOption::empty()),
213145
#[cfg(feature = "yarn_pnp")]
@@ -219,14 +151,9 @@ impl<Fs: Send + Sync + FileSystem + Default> ResolverGeneric<Fs> {
219151

220152
impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
221153
pub fn new_with_file_system(file_system: Fs, options: ResolveOptions) -> Self {
222-
let options = options.sanitize();
223-
let alias_first_bytes = AliasFirstBytes::build(&options.alias);
224-
let fallback_first_bytes = AliasFirstBytes::build(&options.fallback);
225154
Self {
226-
options,
155+
options: options.sanitize(),
227156
cache: Arc::new(Cache::new(file_system)),
228-
alias_first_bytes,
229-
fallback_first_bytes,
230157
#[cfg(feature = "yarn_pnp")]
231158
pnp_manifest: Arc::new(arc_swap::ArcSwapOption::empty()),
232159
#[cfg(feature = "yarn_pnp")]
@@ -238,14 +165,9 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
238165
/// Clone the resolver using the same underlying cache.
239166
#[must_use]
240167
pub fn clone_with_options(&self, options: ResolveOptions) -> Self {
241-
let options = options.sanitize();
242-
let alias_first_bytes = AliasFirstBytes::build(&options.alias);
243-
let fallback_first_bytes = AliasFirstBytes::build(&options.fallback);
244168
Self {
245-
options,
169+
options: options.sanitize(),
246170
cache: Arc::clone(&self.cache),
247-
alias_first_bytes,
248-
fallback_first_bytes,
249171
#[cfg(feature = "yarn_pnp")]
250172
pnp_manifest: Arc::clone(&self.pnp_manifest),
251173
#[cfg(feature = "yarn_pnp")]
@@ -408,13 +330,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
408330

409331
// enhanced-resolve: try alias
410332
if let Some(path) = self
411-
.load_alias(
412-
cached_path,
413-
specifier,
414-
&self.options.alias,
415-
&self.alias_first_bytes,
416-
ctx,
417-
)
333+
.load_alias(cached_path, specifier, &self.options.alias, ctx)
418334
.await?
419335
{
420336
return Ok(path);
@@ -454,13 +370,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
454370
}
455371
// enhanced-resolve: try fallback
456372
self
457-
.load_alias(
458-
cached_path,
459-
specifier,
460-
&self.options.fallback,
461-
&self.fallback_first_bytes,
462-
ctx,
463-
)
373+
.load_alias(cached_path, specifier, &self.options.fallback, ctx)
464374
.await
465375
.and_then(|value| value.ok_or(err))
466376
}
@@ -890,13 +800,7 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
890800
// enhanced-resolve: try file as alias
891801
let alias_specifier = path_to_str(cached_path.path());
892802
if let Some(path) = self
893-
.load_alias(
894-
cached_path,
895-
alias_specifier,
896-
&self.options.alias,
897-
&self.alias_first_bytes,
898-
ctx,
899-
)
803+
.load_alias(cached_path, alias_specifier, &self.options.alias, ctx)
900804
.await?
901805
{
902806
return Ok(Some(path));
@@ -1344,14 +1248,8 @@ impl<Fs: FileSystem + Send + Sync> ResolverGeneric<Fs> {
13441248
cached_path: &CachedPath,
13451249
specifier: &str,
13461250
aliases: &Alias,
1347-
first_bytes: &AliasFirstBytes,
13481251
ctx: &mut Ctx,
13491252
) -> ResolveResult {
1350-
// Quick reject: if the specifier's first 1–2 bytes can't match any alias
1351-
// key prefix, no `strip_prefix` in the loop below can succeed.
1352-
if !first_bytes.may_match(specifier) {
1353-
return Ok(None);
1354-
}
13551253
for (alias_key_raw, specifiers) in aliases {
13561254
let alias_key = if let Some(alias_key) = alias_key_raw.strip_suffix('$') {
13571255
if alias_key != specifier {

src/tests/alias.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -333,25 +333,3 @@ async fn alias_try_fragment_as_path() {
333333
let resolution = resolver.resolve(&f, "#/a").await.map(|r| r.full_path());
334334
assert_eq!(resolution, Ok(f.join("#").join("a.js")));
335335
}
336-
337-
// Regression for the alias prefix-byte accelerator.
338-
// enhanced-resolve treats an alias key of "" as a wildcard that matches any
339-
// specifier whose tail starts with `/` or `\` (see `path::SLASH_START`). The
340-
// prefix index must keep this path open instead of short-circuiting.
341-
//
342-
// We use a synthesised slash-prefixed specifier instead of the fixture's real
343-
// absolute path because Windows drive-letter paths (e.g. `D:\...`) don't
344-
// satisfy `strip_package_name`'s SLASH_START filter on `main` — the alias
345-
// match would skip there regardless of the prefix index.
346-
#[tokio::test]
347-
async fn empty_alias_key_matches_absolute_specifier() {
348-
let resolver = Resolver::new(ResolveOptions {
349-
alias: vec![(String::new(), vec![AliasValue::Ignore])],
350-
..ResolveOptions::default()
351-
});
352-
let resolution = resolver.resolve(super::fixture(), "/foo").await;
353-
assert!(
354-
matches!(resolution, Err(ResolveError::Ignored(_))),
355-
"empty alias key must match a slash-prefixed specifier, got {resolution:?}"
356-
);
357-
}

0 commit comments

Comments
 (0)