-
-
Notifications
You must be signed in to change notification settings - Fork 396
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ebef3ee
commit 057a48c
Showing
3 changed files
with
99 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ mod node; | |
mod reference; | ||
mod scope; | ||
mod symbol; | ||
mod unresolved_stack; | ||
|
||
pub mod dot; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
use assert_unchecked::assert_unchecked; | ||
|
||
use crate::scope::UnresolvedReferences; | ||
|
||
// Stack used to accumulate unresolved refs while traversing scopes. | ||
// Indexed by scope depth. We recycle `UnresolvedReferences` instances during traversal | ||
// to reduce allocations, so the stack grows to maximum scope depth, but never shrinks. | ||
// See: <https://github.com/oxc-project/oxc/issues/4169> | ||
// This stack abstraction uses the invariant that stack only grows to avoid bounds checks. | ||
pub(crate) struct UnresolvedReferencesStack { | ||
stack: Vec<UnresolvedReferences>, | ||
/// Current scope depth. | ||
/// 0 is global scope. 1 is `Program`. | ||
/// Incremented on entering a scope, and decremented on exit. | ||
current_scope_depth: usize, | ||
} | ||
|
||
impl UnresolvedReferencesStack { | ||
// Most programs will have at least 1 place where scope depth reaches 16, | ||
// so initialize `stack` with this length, to reduce reallocations as it grows. | ||
// This is just an estimate of a good initial size, but certainly better than | ||
// `Vec`'s default initial capacity of 4. | ||
// SAFETY: Must be >= 2 to ensure soundness of `current_and_parent_mut`. | ||
const INITIAL_SIZE: usize = 16; | ||
// Initial scope depth. | ||
// Start on 1 (`Program` scope depth). | ||
// SAFETY: Must be >= 1 to ensure soundness of `current_and_parent_mut`. | ||
const INITIAL_DEPTH: usize = 1; | ||
#[allow(clippy::assertions_on_constants)] | ||
const _SIZE_CHECK: () = assert!(Self::INITIAL_SIZE > Self::INITIAL_DEPTH); | ||
|
||
pub(crate) fn new() -> Self { | ||
let mut stack = vec![]; | ||
stack.resize_with(Self::INITIAL_SIZE, UnresolvedReferences::default); | ||
Self { stack, current_scope_depth: Self::INITIAL_DEPTH } | ||
} | ||
|
||
pub(crate) fn increment_scope_depth(&mut self) { | ||
self.current_scope_depth += 1; | ||
|
||
// Grow stack if required to ensure `self.stack[self.depth]` is in bounds | ||
if self.stack.len() <= self.current_scope_depth { | ||
self.stack.push(UnresolvedReferences::default()); | ||
} | ||
} | ||
|
||
pub(crate) fn decrement_scope_depth(&mut self) { | ||
self.current_scope_depth -= 1; | ||
// This assertion is required to ensure depth does not go below 1. | ||
// If it did, would cause UB in `current_and_parent_mut`, which relies on | ||
// `current_scope_depth - 1` always being a valid index into `self.stack`. | ||
assert!(self.current_scope_depth > 0); | ||
} | ||
|
||
pub(crate) fn scope_depth(&self) -> usize { | ||
self.current_scope_depth | ||
} | ||
|
||
/// Get unresolved references hash map for current scope | ||
pub(crate) fn current_mut(&mut self) -> &mut UnresolvedReferences { | ||
// SAFETY: `stack.len() > current_scope_depth` initially. | ||
// Thereafter, `stack` never shrinks, only grows. | ||
// `current_scope_depth` is only increased in `increment_scope_depth`, | ||
// and it grows `stack` to ensure `stack.len()` always exceeds `current_scope_depth`. | ||
// So this read is always guaranteed to be in bounds. | ||
unsafe { self.stack.get_unchecked_mut(self.current_scope_depth) } | ||
} | ||
|
||
/// Get unresolved references hash maps for current scope, and parent scope | ||
pub(crate) fn current_and_parent_mut( | ||
&mut self, | ||
) -> (&mut UnresolvedReferences, &mut UnresolvedReferences) { | ||
// Assert invariants to remove bounds checks in code below. | ||
// https://godbolt.org/z/vv5Wo5csv | ||
// SAFETY: `current_scope_depth` starts at 1, and is only decremented | ||
// in `decrement_scope_depth` which checks it doesn't go below 1. | ||
unsafe { assert_unchecked!(self.current_scope_depth > 0) }; | ||
// SAFETY: `stack.len() > current_scope_depth` initially. | ||
// Thereafter, `stack` never shrinks, only grows. | ||
// `current_scope_depth` is only increased in `increment_scope_depth`, | ||
// and it grows `stack` to ensure `stack.len()` always exceeds `current_scope_depth`. | ||
unsafe { assert_unchecked!(self.stack.len() > self.current_scope_depth) }; | ||
|
||
let mut iter = self.stack.iter_mut(); | ||
let parent = iter.nth(self.current_scope_depth - 1).unwrap(); | ||
let current = iter.next().unwrap(); | ||
(current, parent) | ||
} | ||
|
||
pub(crate) fn into_root(self) -> UnresolvedReferences { | ||
// SAFETY: Stack starts with a non-zero size and never shrinks. | ||
// This assertion removes bounds check in `.next()`. | ||
unsafe { assert_unchecked!(!self.stack.is_empty()) }; | ||
self.stack.into_iter().next().unwrap() | ||
} | ||
} |