Repository: vercel/next.js
Pull request: [turbopack] Create a macro rcstr! for constructing RcStr from string literals.

## Add `rcstr!` macro for efficient string literal handling

### What?

This PR introduces a new `rcstr!` macro that creates `RcStr` instances from string literals, optimizing for inline storage when possible and using `LazyLock` for longer strings.

### Why?

The `rcstr!` macro provides several benefits:
- Allows compile-time evaluation of inline strings
- Caches longer strings using `LazyLock` to avoid repeated allocations


Closes PACK-4729

Changed files:
filename: turbopack/crates/turbo-rcstr/benches/mod.rs
patch: @@ -1,5 +1,5 @@
 use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main};
-use turbo_rcstr::RcStr;
+use turbo_rcstr::{RcStr, rcstr};
 
 // map has a fast-path if the Arc is uniquely owned
 fn bench_map(c: &mut Criterion) {
@@ -30,9 +30,26 @@ fn bench_map(c: &mut Criterion) {
     }
 }
 
+/// Compare the performance of `from` and `rcstr!`
+fn bench_construct(c: &mut Criterion) {
+    let mut g = c.benchmark_group("Rcstr::construct");
+    g.bench_with_input("rcstr!/small", "small", |f, _| {
+        f.iter(|| rcstr!("hello"));
+    });
+    g.bench_with_input("rcstr!/large", "large", |f, _| {
+        f.iter(|| rcstr!("this is a long string that will take time to copy"));
+    });
+
+    g.bench_with_input("from/small", "small", |f, _| {
+        f.iter(|| RcStr::from("hello"));
+    });
+    g.bench_with_input("from/large", "large", |f, _| {
+        f.iter(|| RcStr::from("this is a long string that will take time to copy"));
+    });
+}
 criterion_group!(
   name = benches;
   config = Criterion::default();
-  targets = bench_map,
+  targets = bench_map,bench_construct,
 );
 criterion_main!(benches);

filename: turbopack/crates/turbo-rcstr/src/dynamic.rs
patch: @@ -1,9 +1,9 @@
-use std::ptr::NonNull;
+use std::{num::NonZeroU8, ptr::NonNull};
 
 use triomphe::Arc;
 
 use crate::{
-    INLINE_TAG_INIT, LEN_OFFSET, RcStr, TAG_MASK,
+    INLINE_TAG, INLINE_TAG_INIT, LEN_OFFSET, RcStr, TAG_MASK,
     tagged_value::{MAX_INLINE_LEN, TaggedValue},
 };
 
@@ -48,3 +48,26 @@ pub(crate) fn new_atom<T: AsRef<str> + Into<String>>(text: T) -> RcStr {
         unsafe_data: TaggedValue::new_ptr(ptr),
     }
 }
+
+/// Attempts to construct an RcStr but only if it can be constructed inline.
+/// This is primarily useful in constant contexts.
+#[doc(hidden)]
+pub(crate) const fn inline_atom(text: &str) -> Option<RcStr> {
+    let len = text.len();
+    if len < MAX_INLINE_LEN {
+        let tag = INLINE_TAG | ((len as u8) << LEN_OFFSET);
+        let mut unsafe_data = TaggedValue::new_tag(NonZeroU8::new(tag).unwrap());
+
+        // This odd pattern is needed because we cannot create slices from ranges in constant
+        // context.
+        unsafe {
+            unsafe_data
+                .data_mut()
+                .split_at_mut(len)
+                .0
+                .copy_from_slice(text.as_bytes());
+        }
+        return Some(RcStr { unsafe_data });
+    }
+    None
+}

filename: turbopack/crates/turbo-rcstr/src/lib.rs
patch: @@ -250,7 +250,7 @@ impl Clone for RcStr {
 
 impl Default for RcStr {
     fn default() -> Self {
-        RcStr::from("")
+        rcstr!("")
     }
 }
 
@@ -301,6 +301,33 @@ impl Drop for RcStr {
     }
 }
 
+#[doc(hidden)]
+pub const fn inline_atom(s: &str) -> Option<RcStr> {
+    dynamic::inline_atom(s)
+}
+
+/// Create an rcstr from a string literal.
+/// allocates the RcStr inline when possible otherwise uses a `LazyLock` to manage the allocation.
+#[macro_export]
+macro_rules! rcstr {
+    ($s:tt) => {{
+        const INLINE: core::option::Option<$crate::RcStr> = $crate::inline_atom($s);
+        // this condition should be able to be compile time evaluated and inlined.
+        if INLINE.is_some() {
+            INLINE.unwrap()
+        } else {
+            #[inline(never)]
+            fn get_rcstr() -> $crate::RcStr {
+                static CACHE: std::sync::LazyLock<$crate::RcStr> =
+                    std::sync::LazyLock::new(|| $crate::RcStr::from($s));
+
+                (*CACHE).clone()
+            }
+            get_rcstr()
+        }
+    }};
+}
+
 /// noop
 impl ShrinkToFit for RcStr {
     #[inline(always)]
@@ -375,4 +402,33 @@ mod tests {
         let _ = str.clone().into_owned();
         assert_eq!(refcount(&str), 1);
     }
+
+    #[test]
+    fn test_rcstr() {
+        // Test enough to exceed the small string optimization
+        assert_eq!(rcstr!(""), RcStr::default());
+        assert_eq!(rcstr!(""), RcStr::from(""));
+        assert_eq!(rcstr!("a"), RcStr::from("a"));
+        assert_eq!(rcstr!("ab"), RcStr::from("ab"));
+        assert_eq!(rcstr!("abc"), RcStr::from("abc"));
+        assert_eq!(rcstr!("abcd"), RcStr::from("abcd"));
+        assert_eq!(rcstr!("abcde"), RcStr::from("abcde"));
+        assert_eq!(rcstr!("abcdef"), RcStr::from("abcdef"));
+        assert_eq!(rcstr!("abcdefg"), RcStr::from("abcdefg"));
+        assert_eq!(rcstr!("abcdefgh"), RcStr::from("abcdefgh"));
+        assert_eq!(rcstr!("abcdefghi"), RcStr::from("abcdefghi"));
+    }
+    #[test]
+    fn test_inline_atom() {
+        // This is a silly test, just asserts that we can evaluate this in a constant context.
+        const STR: RcStr = {
+            let inline = inline_atom("hello");
+            if inline.is_some() {
+                inline.unwrap()
+            } else {
+                unreachable!();
+            }
+        };
+        assert_eq!(STR, RcStr::from("hello"));
+    }
 }

filename: turbopack/crates/turbo-rcstr/src/tagged_value.rs
patch: @@ -72,7 +72,7 @@ impl TaggedValue {
     }
 
     #[inline(always)]
-    pub fn new_tag(value: NonZeroU8) -> Self {
+    pub const fn new_tag(value: NonZeroU8) -> Self {
         let value = value.get() as RawTaggedValue;
         Self {
             value: unsafe { std::mem::transmute(value) },
@@ -129,7 +129,7 @@ impl TaggedValue {
     /// used when setting the untagged slice part of this value. If tag is
     /// zero and the slice is zeroed out, using this `TaggedValue` will be
     /// UB!
-    pub unsafe fn data_mut(&mut self) -> &mut [u8] {
+    pub const unsafe fn data_mut(&mut self) -> &mut [u8] {
         let x: *mut _ = &mut self.value;
         let mut data = x as *mut u8;
         // All except the lowest byte, which is first in little-endian, last in