Skip to content

Commit

Permalink
perf(es/minifier): Introduce FastJsWord and FastId in swc_atoms (
Browse files Browse the repository at this point in the history
…#9826)

**Description**:

`FastJsWord` and `FastId` implement `Copy` and do not need to be cloned or dropped, unlike `JsWord` or `Id`. Of course, they are inherently unsafe. But I found that I could use it for the minifier.


**Related issue:**

 - Extracted from #9813
  • Loading branch information
kdy1 authored Jan 1, 2025
1 parent f8dff56 commit ef0ec38
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 3 deletions.
8 changes: 8 additions & 0 deletions .changeset/loud-bottles-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
swc_atoms: patch
swc_core: patch
swc_ecma_ast: patch
swc_ecma_utils: patch
---

perf(es/minifier): Introduce `FastJsWord` and `FastId` in `swc_atoms`
56 changes: 56 additions & 0 deletions crates/swc_atoms/src/fast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::{
mem::{transmute_copy, ManuallyDrop},
ops::Deref,
};

use crate::Atom;

/// FastAtom is a wrapper around [Atom] that does not allocate, but extremely
/// unsafe.
///
/// Do not use this unless you know what you are doing.
///
/// **Currently, it's considered as a unstable API and may be changed in the
/// future without a semver bump.**
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FastAtom(ManuallyDrop<Atom>);

impl FastAtom {
/// # Safety
///
/// - You should ensure that the passed `atom` is not freed.
///
/// Some simple solutions to ensure this are
///
/// - Collect all [Atom] and store them somewhere while you are using
/// [FastAtom]
/// - Use [FastAtom] only for short-lived operations where all [Atom] is
/// stored in AST and ensure that the AST is not dropped.
#[inline]
pub unsafe fn new(atom: &Atom) -> Self {
Self(ManuallyDrop::new(transmute_copy(atom)))
}
}

impl Deref for FastAtom {
type Target = Atom;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Clone for FastAtom {
#[inline]
fn clone(&self) -> Self {
unsafe { Self::new(&self.0) }
}
}

impl PartialEq<Atom> for FastAtom {
#[inline]
fn eq(&self, other: &Atom) -> bool {
*self.0 == *other
}
}
2 changes: 2 additions & 0 deletions crates/swc_atoms/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use serde::Serializer;

pub use self::{atom as js_word, Atom as JsWord};

pub mod fast;

/// Clone-on-write string.
///
///
Expand Down
35 changes: 34 additions & 1 deletion crates/swc_ecma_ast/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

use phf::phf_set;
use swc_atoms::{js_word, Atom};
use swc_atoms::{fast::FastAtom, js_word, Atom};
use swc_common::{
ast_node, util::take::Take, BytePos, EqIgnoreSpan, Mark, Span, Spanned, SyntaxContext, DUMMY_SP,
};
Expand Down Expand Up @@ -465,6 +465,39 @@ impl From<IdentName> for BindingIdent {
}
}

/// FastId is a wrapper around [Id] that does not allocate, but extremely
/// unsafe.
///
/// Do not use this unless you know what you are doing.
///
/// **Currently, it's considered as a unstable API and may be changed in the
/// future without a semver bump.**
pub type FastId = (FastAtom, SyntaxContext);

/// This is extremely unsafe so don't use it unless you know what you are doing.
///
/// # Safety
///
/// See [`FastAtom::new`] for constraints.
///
/// **Currently, it's considered as a unstable API and may be changed in the
/// future without a semver bump.**
pub unsafe fn fast_id(id: &Id) -> FastId {
(FastAtom::new(&id.0), id.1)
}

/// This is extremely unsafe so don't use it unless you know what you are doing.
///
/// # Safety
///
/// See [`FastAtom::new`] for constraints.
///
/// **Currently, it's considered as a unstable API and may be changed in the
/// future without a semver bump.**
pub unsafe fn fast_id_from_ident(id: &Ident) -> FastId {
(FastAtom::new(&id.sym), id.ctxt)
}

/// See [Ident] for documentation.
pub type Id = (Atom, SyntaxContext);

Expand Down
5 changes: 4 additions & 1 deletion crates/swc_ecma_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ pub use self::{
decl::{ClassDecl, Decl, FnDecl, UsingDecl, VarDecl, VarDeclKind, VarDeclarator},
expr::*,
function::{Function, Param, ParamOrTsParamProp},
ident::{BindingIdent, EsReserved, Id, Ident, IdentName, PrivateName},
ident::{
fast_id, fast_id_from_ident, BindingIdent, EsReserved, FastId, Id, Ident, IdentName,
PrivateName,
},
jsx::{
JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXClosingElement, JSXClosingFragment,
JSXElement, JSXElementChild, JSXElementName, JSXEmptyExpr, JSXExpr, JSXExprContainer,
Expand Down
16 changes: 15 additions & 1 deletion crates/swc_ecma_utils/src/ident.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use swc_atoms::JsWord;
use swc_common::SyntaxContext;
use swc_ecma_ast::{BindingIdent, Id, Ident};
use swc_ecma_ast::{fast_id, BindingIdent, FastId, Id, Ident};

pub trait IdentLike: Sized + Send + Sync + 'static {
fn from_ident(i: &Ident) -> Self;
Expand Down Expand Up @@ -70,6 +70,20 @@ impl IdentLike for Ident {
}
}

impl IdentLike for FastId {
fn from_ident(i: &Ident) -> Self {
unsafe { fast_id(&i.to_id()) }
}

fn to_id(&self) -> Id {
unreachable!("FastId.to_id() is not allowed because it is very likely to be unsafe")
}

fn into_id(self) -> Id {
unreachable!("FastId.into_id() is not allowed because it is very likely to be unsafe")
}
}

#[deprecated = "Use i.to_id() instead"]
#[inline(always)]
pub fn id(i: &Ident) -> Id {
Expand Down

0 comments on commit ef0ec38

Please sign in to comment.