Skip to content

Commit

Permalink
Move to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Jul 21, 2024
1 parent ebef3ee commit 057a48c
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 94 deletions.
96 changes: 2 additions & 94 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ use std::{
sync::Arc,
};

use assert_unchecked::assert_unchecked;

#[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, AstKind, Trivias, Visit};
use oxc_cfg::{
Expand All @@ -28,8 +26,9 @@ use crate::{
module_record::ModuleRecordBuilder,
node::{AstNodeId, AstNodes, NodeFlags},
reference::{Reference, ReferenceFlag, ReferenceId},
scope::{ScopeFlags, ScopeId, ScopeTree, UnresolvedReferences},
scope::{ScopeFlags, ScopeId, ScopeTree},
symbol::{SymbolFlags, SymbolId, SymbolTable},
unresolved_stack::UnresolvedReferencesStack,
JSDocFinder, Semantic,
};

Expand Down Expand Up @@ -1925,94 +1924,3 @@ impl<'a> SemanticBuilder<'a> {
false
}
}

// 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.
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);

fn new() -> Self {
let mut stack = vec![];
stack.resize_with(Self::INITIAL_SIZE, UnresolvedReferences::default);
Self { stack, current_scope_depth: Self::INITIAL_DEPTH }
}

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());
}
}

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);
}

fn scope_depth(&self) -> usize {
self.current_scope_depth
}

/// Get unresolved references hash map for current scope
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
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)
}

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()
}
}
1 change: 1 addition & 0 deletions crates/oxc_semantic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod node;
mod reference;
mod scope;
mod symbol;
mod unresolved_stack;

pub mod dot;

Expand Down
96 changes: 96 additions & 0 deletions crates/oxc_semantic/src/unresolved_stack.rs
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()
}
}

0 comments on commit 057a48c

Please sign in to comment.