From 48e0338dff52b43c75eec5a4520791aff9ab5761 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 10 Jul 2026 08:59:51 +1000 Subject: [PATCH 1/4] Remove unused `TokenStream::chunks` --- compiler/rustc_ast/src/tokenstream.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index baad20a86784d..e7b094c64b7fa 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -688,10 +688,6 @@ impl TokenStream { } } - pub fn chunks(&self, chunk_size: usize) -> core::slice::Chunks<'_, TokenTree> { - self.0.chunks(chunk_size) - } - /// Desugar doc comments like `/// foo` in the stream into `#[doc = /// r"foo"]`. Modifies the `TokenStream` via `Arc::make_mut`, but as little /// as possible. From 4b5a9f3811de0ea130a4658051c2edb4ef656799 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 10 Jul 2026 09:04:15 +1000 Subject: [PATCH 2/4] Adjust signature of `TokenStream::try_glue_to_last` A slice is more general. --- compiler/rustc_ast/src/tokenstream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index e7b094c64b7fa..5cc54033f96af 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -643,7 +643,7 @@ impl TokenStream { // If `vec` is not empty, try to glue `tt` onto its last token. The return // value indicates if gluing took place. - fn try_glue_to_last(vec: &mut Vec, tt: &TokenTree) -> bool { + fn try_glue_to_last(vec: &mut [TokenTree], tt: &TokenTree) -> bool { if let Some(TokenTree::Token(last_tok, Spacing::Joint | Spacing::JointHidden)) = vec.last() && let TokenTree::Token(tok, spacing) = tt && let Some(glued_tok) = last_tok.glue(tok) From d052ec8e50fec93ac4131ddbb842b31f87a372d7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 10 Jul 2026 09:06:41 +1000 Subject: [PATCH 3/4] Improve `StableHash` impl for `TokenStream` It currently doesn't include the length. It should. This change makes it more like other `StableHash` impls for vec-like types. --- compiler/rustc_ast/src/tokenstream.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index 5cc54033f96af..d0ed6f8503c5a 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -822,9 +822,7 @@ impl FromIterator for TokenStream { impl StableHash for TokenStream { fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { - for sub_tt in self.iter() { - sub_tt.stable_hash(hcx, hasher); - } + self.0.as_slice().stable_hash(hcx, hasher); } } From 500af7034f0fac25ad9c1912c15e635ad086d506 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 9 Jul 2026 16:15:57 +1000 Subject: [PATCH 4/4] Use `AtomicSharedVector` for token streams This adds a dependency on a new crate: `shared_vector`. It's done for efficiency: `AtomicSharedVector` stores everything (strong refcount, len, capacity, and elements) in a single allocation, as opposed to `Arc>` which requires two allocations. This also requires changing the `Vec` argument of `TokenStream::new` to `shared_vector::Vector`. Some of the changes are because the API is slightly different. There are also various `FIXME(shared_vector)` comments for places where `shared_vector` has suboptimal behaviour that might be improved in the future. --- Cargo.lock | 21 +- compiler/rustc_ast/Cargo.toml | 1 + compiler/rustc_ast/src/attr/mod.rs | 7 +- compiler/rustc_ast/src/tokenstream.rs | 208 ++++++++++++++---- compiler/rustc_ast_lowering/src/lib.rs | 2 +- compiler/rustc_data_structures/Cargo.toml | 1 + compiler/rustc_data_structures/src/marker.rs | 4 +- compiler/rustc_expand/Cargo.toml | 1 + compiler/rustc_expand/src/config.rs | 76 ++++--- compiler/rustc_expand/src/mbe/quoted.rs | 3 +- compiler/rustc_expand/src/mbe/transcribe.rs | 7 +- compiler/rustc_expand/src/placeholders.rs | 2 +- .../rustc_expand/src/proc_macro_server.rs | 8 +- compiler/rustc_parse/Cargo.toml | 1 + compiler/rustc_parse/src/lexer/tokentrees.rs | 3 +- compiler/rustc_parse/src/parser/item.rs | 3 +- compiler/rustc_parse/src/parser/mod.rs | 3 +- compiler/rustc_parse/src/parser/tests.rs | 7 +- src/tools/rustfmt/src/lib.rs | 1 + src/tools/rustfmt/src/macros.rs | 3 +- src/tools/tidy/src/deps.rs | 1 + tests/ui-fulldeps/auxiliary/parser.rs | 2 +- 22 files changed, 262 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 873182dde9102..fcb670f0d1c1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -1713,7 +1719,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", + "allocator-api2 0.2.21", "equivalent", "foldhash 0.1.5", ] @@ -3593,6 +3599,7 @@ dependencies = [ "rustc_macros", "rustc_serialize", "rustc_span", + "shared_vector", "smallvec", "thin-vec", "tracing", @@ -3872,6 +3879,7 @@ dependencies = [ "rustc_macros", "rustc_serialize", "rustc_thread_pool", + "shared_vector", "smallvec", "stacker", "tempfile", @@ -3995,6 +4003,7 @@ dependencies = [ "rustc_session", "rustc_span", "scoped-tls", + "shared_vector", "smallvec", "thin-vec", "tracing", @@ -4496,6 +4505,7 @@ dependencies = [ "rustc_macros", "rustc_session", "rustc_span", + "shared_vector", "thin-vec", "tracing", "unicode-normalization", @@ -5280,6 +5290,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared_vector" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db178a7b1e552c11440665d314451b8dc12bfc4da84212728292bbbd08f28f6f" +dependencies = [ + "allocator-api2 0.4.0", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/compiler/rustc_ast/Cargo.toml b/compiler/rustc_ast/Cargo.toml index c60f666d3bfad..e1e5cd27dad6a 100644 --- a/compiler/rustc_ast/Cargo.toml +++ b/compiler/rustc_ast/Cargo.toml @@ -14,6 +14,7 @@ rustc_index = { path = "../rustc_index" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } +shared_vector = "0.5" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } thin-vec = "0.2.18" tracing = "0.1" diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 88556aa58c773..5036c38349dd3 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -8,6 +8,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use rustc_index::bit_set::GrowableBitSet; use rustc_span::{Ident, Span, Symbol, sym}; +use shared_vector::{Vector, vector}; use smallvec::{SmallVec, smallvec}; use thin_vec::{ThinVec, thin_vec}; @@ -299,7 +300,7 @@ impl Attribute { } } - pub fn token_trees(&self) -> Vec { + pub fn token_trees(&self) -> Vector { match self.kind { AttrKind::Normal(ref normal) => normal .tokens @@ -307,7 +308,7 @@ impl Attribute { .unwrap_or_else(|| panic!("attribute is missing tokens: {self:?}")) .to_attr_token_stream() .to_token_trees(), - AttrKind::DocComment(comment_kind, data) => vec![TokenTree::token_alone( + AttrKind::DocComment(comment_kind, data) => vector![TokenTree::token_alone( token::DocComment(comment_kind, self.style, data), self.span, )], @@ -789,7 +790,7 @@ pub fn mk_attr_nested_word( inner: Symbol, span: Span, ) -> Attribute { - let inner_tokens = TokenStream::new(vec![TokenTree::Token( + let inner_tokens = TokenStream::new(vector![TokenTree::Token( Token::from_ast_ident(Ident::new(inner, span)), Spacing::Alone, )]); diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index d0ed6f8503c5a..e3e178ed62f05 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -5,9 +5,9 @@ //! which are themselves a single [`Token`] or a `Delimited` subsequence of tokens. use std::borrow::Cow; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::ops::Range; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; use std::{cmp, fmt, iter, mem}; use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; @@ -15,6 +15,7 @@ use rustc_data_structures::sync; use rustc_macros::{Decodable, Encodable, StableHash, Walkable}; use rustc_serialize::{Decodable, Encodable}; use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol, sym}; +use shared_vector::{AtomicSharedVector, Vector, vector}; use thin_vec::ThinVec; use crate::ast::AttrStyle; @@ -322,13 +323,6 @@ enum FlatToken { Empty, } -/// An `AttrTokenStream` is similar to a `TokenStream`, but with extra -/// information about the tokens for attribute targets. This is used -/// during expansion to perform early cfg-expansion, and to process attributes -/// during proc-macro invocations. -#[derive(Clone, Debug, Default, Encodable, Decodable)] -pub struct AttrTokenStream(pub Arc>); - /// Converts a flattened iterator of tokens (including open and close delimiter tokens) into an /// `AttrTokenStream`, creating an `AttrTokenTree::Delimited` for each matching pair of open and /// close delims. @@ -340,10 +334,10 @@ fn make_attr_token_stream( struct FrameData { // This is `None` for the first frame, `Some` for all others. open_delim_sp: Option<(Delimiter, Span, Spacing)>, - inner: Vec, + inner: Vector, } // The stack always has at least one element. Storing it separately makes for shorter code. - let mut stack_top = FrameData { open_delim_sp: None, inner: vec![] }; + let mut stack_top = FrameData { open_delim_sp: None, inner: vector![] }; let mut stack_rest = vec![]; for flat_token in iter { match flat_token { @@ -351,7 +345,7 @@ fn make_attr_token_stream( if let Some(delim) = kind.open_delim() { stack_rest.push(mem::replace( &mut stack_top, - FrameData { open_delim_sp: Some((delim, span, spacing)), inner: vec![] }, + FrameData { open_delim_sp: Some((delim, span, spacing)), inner: vector![] }, )); } else if let Some(delim) = kind.close_delim() { // If there's no matching opening delimiter, the token stream is malformed, @@ -411,17 +405,30 @@ pub enum AttrTokenTree { AttrsTarget(AttrsTarget), } +/// An `AttrTokenStream` is similar to a `TokenStream`, but with extra +/// information about the tokens for attribute targets. This is used +/// during expansion to perform early cfg-expansion, and to process attributes +/// during proc-macro invocations. +#[derive(Clone, Debug)] +pub struct AttrTokenStream(pub AtomicSharedVector); + impl AttrTokenStream { - pub fn new(tokens: Vec) -> AttrTokenStream { - AttrTokenStream(Arc::new(tokens)) + pub fn new(tts: Vector) -> AttrTokenStream { + // FIXME(shared_vector): See the comment on `EMPTY_TOKEN_STREAM` for why `default` is used. + // FIXME(shared_vector): See the comment on `TokenStream::new` about minimum capacity. + if tts.is_empty() { + AttrTokenStream::default() + } else { + AttrTokenStream(tts.into_shared_atomic()) + } } /// Converts this `AttrTokenStream` to a plain `Vec`. During /// conversion, any `AttrTokenTree::AttrsTarget` gets "flattened" back to a /// `TokenStream`, as described in the comment on /// `attrs_and_tokens_to_token_trees`. - pub fn to_token_trees(&self) -> Vec { - let mut res = Vec::with_capacity(self.0.len()); + pub fn to_token_trees(&self) -> Vector { + let mut res = Vector::with_capacity(self.0.len()); for tree in self.0.iter() { match tree { AttrTokenTree::Token(inner, spacing) => { @@ -444,6 +451,45 @@ impl AttrTokenStream { } } +// FIXME(shared_vector): See EMPTY_TOKEN_STREAM. +static EMPTY_ATTR_TOKEN_STREAM: LazyLock = LazyLock::new(|| { + let mut asv = AtomicSharedVector::new(); + asv.shrink_to_fit(); + AttrTokenStream(asv) +}); + +impl Default for AttrTokenStream { + fn default() -> AttrTokenStream { + EMPTY_ATTR_TOKEN_STREAM.clone() + } +} + +// FIXME(shared_vector): see `Decodable` impl for `TokenStream`. +impl Encodable for AttrTokenStream { + fn encode(&self, s: &mut S) { + self.0.as_slice().encode(s); + } +} + +// FIXME(shared_vector): see `Decodable` impl for `TokenStream`. +impl Decodable for AttrTokenStream { + fn decode(d: &mut D) -> AttrTokenStream { + let len = d.read_usize(); + (0..len).map(|_| Decodable::decode(d)).collect() + } +} + +impl FromIterator for AttrTokenStream { + fn from_iter>(iter: I) -> Self { + // FIXME(shared_vector): `shared_vector::Vector` lacks `FromIterator`, so we can't use + // `iter.into_iter().collect()` here. + let iter = iter.into_iter(); + let mut tts = Vector::with_capacity(iter.size_hint().0); + tts.extend(iter); + AttrTokenStream::new(tts) + } +} + // Converts multiple attributes and the tokens for a target AST node into token trees, and appends // them to `res`. // @@ -455,18 +501,21 @@ impl AttrTokenStream { fn attrs_and_tokens_to_token_trees( attrs: &[Attribute], target_tokens: &LazyAttrTokenStream, - res: &mut Vec, + res: &mut Vector, ) { let idx = attrs.partition_point(|attr| matches!(attr.style, crate::AttrStyle::Outer)); let (outer_attrs, inner_attrs) = attrs.split_at(idx); // Add outer attribute tokens. for attr in outer_attrs { - res.extend(attr.token_trees()); + // FIXME(shared_vector): can't use `res.extend(attr.token_trees())` because `Vector` + // doesn't have a by-value `IntoIterator` impl. (Likewise for additional `append` calls + // below.) + res.append(&mut attr.token_trees()); } // Add target AST node tokens. - res.extend(target_tokens.to_attr_token_stream().to_token_trees()); + res.append(&mut target_tokens.to_attr_token_stream().to_token_trees()); // Insert inner attribute tokens. if !inner_attrs.is_empty() { @@ -487,13 +536,13 @@ fn attrs_and_tokens_to_token_trees( // `#![my_attr]` at the start of a file. Support for custom attributes in // this position is not properly implemented - we always synthesize fake // tokens, so we never reach this code. - fn insert_inner_attrs(inner_attrs: &[Attribute], tts: &mut Vec) -> bool { + fn insert_inner_attrs(inner_attrs: &[Attribute], tts: &mut Vector) -> bool { for tree in tts.iter_mut().rev() { if let TokenTree::Delimited(span, spacing, Delimiter::Brace, stream) = tree { // Found it: the rightmost, outermost braced group. - let mut tts = vec![]; + let mut tts = vector![]; for inner_attr in inner_attrs { - tts.extend(inner_attr.token_trees()); + tts.append(&mut inner_attr.token_trees()); } tts.extend(stream.0.iter().cloned()); let stream = TokenStream::new(tts); @@ -503,7 +552,10 @@ fn attrs_and_tokens_to_token_trees( tree { // Recurse inside invisible delimiters. - let mut vec: Vec<_> = stream.iter().cloned().collect(); + // FIXME(shared_vector): `AtomicSharedVector` doesn't impl `FromIterator`, so we + // can't do `stream.iter().cloned().collect()`. + let mut vec = Vector::with_capacity(stream.len()); + vec.extend(stream.iter().cloned()); if insert_inner_attrs(inner_attrs, &mut vec) { *tree = TokenTree::Delimited( *span, @@ -601,13 +653,21 @@ pub enum Spacing { JointHidden, } -/// A `TokenStream` is an abstract sequence of tokens, organized into [`TokenTree`]s. -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Encodable, Decodable)] -pub struct TokenStream(pub(crate) Arc>); +#[derive(Clone, Debug, PartialEq)] +pub struct TokenStream(pub(crate) AtomicSharedVector); + +// FIXME(shared_vector): `AtomicSharedVector` doesn't impl `Eq` even though it impls `PartialEq`. +// This appears to just be an oversight. So we can't derive it for `TokenStream`. +impl Eq for TokenStream {} impl TokenStream { - pub fn new(tts: Vec) -> TokenStream { - TokenStream(Arc::new(tts)) + pub fn new(tts: Vector) -> TokenStream { + // FIXME(shared_vector): See the comment on `EMPTY_TOKEN_STREAM` for why `default` is used. + // FIXME(shared_vector): For non-empty `Vector`s grown by incremental pushing the minimum + // capacity is 8, compared to a minimum of 4 for `Vec`. This results in non-trivial amounts + // of extra memory being used because a lot of token streams have 1, 2, 3, or 4 tokens and + // are created by incremental pushing. + if tts.is_empty() { TokenStream::default() } else { TokenStream(tts.into_shared_atomic()) } } pub fn is_empty(&self) -> bool { @@ -631,12 +691,12 @@ impl TokenStream { /// because it's never used. In practice we arbitrarily use /// `Spacing::Alone`. pub fn token_alone(kind: TokenKind, span: Span) -> TokenStream { - TokenStream::new(vec![TokenTree::token_alone(kind, span)]) + TokenStream::new(vector![TokenTree::token_alone(kind, span)]) } pub fn from_ast(node: &(impl HasAttrs + HasTokens + fmt::Debug)) -> TokenStream { let tokens = node.tokens().unwrap_or_else(|| panic!("missing tokens for node: {:?}", node)); - let mut tts = vec![]; + let mut tts = vector![]; attrs_and_tokens_to_token_trees(node.attrs(), tokens, &mut tts); TokenStream::new(tts) } @@ -658,39 +718,34 @@ impl TokenStream { } /// Push `tt` onto the end of the stream, possibly gluing it to the last - /// token. Uses `make_mut` to maximize efficiency. + /// token. If `self` is unshared the modification will happen in place. pub fn push_tree(&mut self, tt: TokenTree) { - let vec_mut = Arc::make_mut(&mut self.0); - - if Self::try_glue_to_last(vec_mut, &tt) { + if Self::try_glue_to_last(self.0.as_mut_slice(), &tt) { // nothing else to do } else { - vec_mut.push(tt); + self.0.push(tt); } } /// Push `stream` onto the end of the stream, possibly gluing the first /// token tree to the last token. (No other token trees will be glued.) - /// Uses `make_mut` to maximize efficiency. + /// If `self` is unshared the modification will happen in place. pub fn push_stream(&mut self, stream: TokenStream) { - let vec_mut = Arc::make_mut(&mut self.0); - let stream_iter = stream.0.iter().cloned(); if let Some(first) = stream.0.first() - && Self::try_glue_to_last(vec_mut, first) + && Self::try_glue_to_last(self.0.as_mut_slice(), first) { // Now skip the first token tree from `stream`. - vec_mut.extend(stream_iter.skip(1)); + self.0.extend(stream_iter.skip(1)); } else { // Append all of `stream`. - vec_mut.extend(stream_iter); + self.0.extend(stream_iter); } } /// Desugar doc comments like `/// foo` in the stream into `#[doc = - /// r"foo"]`. Modifies the `TokenStream` via `Arc::make_mut`, but as little - /// as possible. + /// r"foo"]`. Modifies the `TokenStream` when necessary. pub fn desugar_doc_comments(&mut self) { if let Some(desugared_stream) = desugar_inner(self.clone()) { *self = desugared_stream; @@ -708,7 +763,11 @@ impl TokenStream { ) => { let desugared = desugared_tts(attr_style, data, span); let desugared_len = desugared.len(); - Arc::make_mut(&mut stream.0).splice(i..i + 1, desugared); + + let mut vec = stream.0.into_unique(); + vec.splice(i..i + 1, desugared); + stream.0 = vec.into_shared_atomic(); + modified = true; i += desugared_len; } @@ -719,7 +778,8 @@ impl TokenStream { if let Some(desugared_delim_stream) = desugar_inner(delim_stream.clone()) { let new_tt = TokenTree::Delimited(sp, spacing, delim, desugared_delim_stream); - Arc::make_mut(&mut stream.0)[i] = new_tt; + + stream.0.as_mut_slice()[i] = new_tt; modified = true; } i += 1; @@ -803,7 +863,7 @@ impl TokenStream { } } if let Some((pos, comma, sp)) = suggestion { - let mut new_stream = Vec::with_capacity(self.0.len() + 1); + let mut new_stream = Vector::with_capacity(self.0.len() + 1); let parts = self.0.split_at(pos + 1); new_stream.extend_from_slice(parts.0); new_stream.push(comma); @@ -814,9 +874,63 @@ impl TokenStream { } } +// FIXME(shared_vector): Any empty `AtomicSharedVector` (created with `new()` or `from_slice(&[])` +// or `with_capacity(0)`) actually gets a capacity of 16, due to an unavoidable capacity adjustment +// in `shared_vector::raw::allocate_header_buffer`. This is weird and annoying. It's possible to +// shrink the capacity back to zero, but that takes an extra allocation. +// +// To work around this, we create a single empty `TokenStream` and clone it whenever a new empty +// `TokenStream` is needed, either from `TokenStream::default()` or `TokenStream::new(tts)` where +// `tts` is empty. (Note that this overcapacity problem doesn't affect `shared_vector::Vector`, +// so passing an empty `tts` is fine.) +static EMPTY_TOKEN_STREAM: LazyLock = LazyLock::new(|| { + let mut asv = AtomicSharedVector::new(); + // Reduce capacity from 16 to 0. Doesn't really matter for a single token stream, but useful + // if the capacity is ever printed for debugging purposes. + asv.shrink_to_fit(); + TokenStream(asv) +}); + +impl Default for TokenStream { + fn default() -> TokenStream { + EMPTY_TOKEN_STREAM.clone() + } +} + +// FIXME(shared_vector): this impl can't be derived because `AtomicSharedVector` doesn't impl +// `Hash`. +impl Hash for TokenStream { + fn hash(&self, state: &mut H) { + self.0.as_slice().hash(state) + } +} + +// FIXME(shared_vector): see `Decodable` impl for `TokenStream`. +impl Encodable for TokenStream { + fn encode(&self, s: &mut S) { + self.0.as_slice().encode(s); + } +} + +// FIXME(shared_vector): ideally we'd impl `Decodable` on `AtomicSharedVector` and then derive +// `Decodable` for `TokenStream`. But we need to impl it directly on `TokenStream` in order to use +// `TokenStream::FromIterator` which uses `TokenStream::new` which uses `EMPTY_TOKEN_STREAM` which +// avoids empty token streams having a capacity of 16. +impl Decodable for TokenStream { + fn decode(d: &mut D) -> TokenStream { + let len = d.read_usize(); + (0..len).map(|_| Decodable::decode(d)).collect() + } +} + impl FromIterator for TokenStream { fn from_iter>(iter: I) -> Self { - TokenStream::new(iter.into_iter().collect::>()) + // FIXME(shared_vector): `shared_vector::Vector` lacks `FromIterator`, so we can't use + // `iter.into_iter().collect()` here. + let iter = iter.into_iter(); + let mut tts = Vector::with_capacity(iter.size_hint().0); + tts.extend(iter); + TokenStream::new(tts) } } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index dc1acade85ed5..ce8e916dc55e7 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -565,7 +565,7 @@ fn index_ast<'tcx>( args: Box::new(DelimArgs { dspan: DelimSpan::from_single(span), delim: Delimiter::Parenthesis, - tokens: TokenStream::new(Vec::new()), + tokens: TokenStream::default(), }), })), tokens: None, diff --git a/compiler/rustc_data_structures/Cargo.toml b/compiler/rustc_data_structures/Cargo.toml index 6db3a624500a9..77095c865a484 100644 --- a/compiler/rustc_data_structures/Cargo.toml +++ b/compiler/rustc_data_structures/Cargo.toml @@ -23,6 +23,7 @@ rustc_index = { path = "../rustc_index", package = "rustc_index" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_thread_pool = { path = "../rustc_thread_pool" } +shared_vector = "0.5" smallvec = { version = "1.8.1", features = [ "const_generics", "union", diff --git a/compiler/rustc_data_structures/src/marker.rs b/compiler/rustc_data_structures/src/marker.rs index 2fe2a30c36751..ab7ba4b7627cc 100644 --- a/compiler/rustc_data_structures/src/marker.rs +++ b/compiler/rustc_data_structures/src/marker.rs @@ -94,8 +94,9 @@ impl_dyn_send!( [hashbrown::HashTable where T: DynSend] [indexmap::IndexSet where V: DynSend, S: DynSend] [indexmap::IndexMap where K: DynSend, V: DynSend, S: DynSend] - [thin_vec::ThinVec where T: DynSend] + [shared_vector::AtomicSharedVector where T: DynSync + DynSend] [smallvec::SmallVec where A: smallvec::Array + DynSend] + [thin_vec::ThinVec where T: DynSend] ); macro_rules! impls_dyn_sync_neg { @@ -180,6 +181,7 @@ impl_dyn_sync!( [hashbrown::HashTable where T: DynSync] [indexmap::IndexSet where V: DynSync, S: DynSync] [indexmap::IndexMap where K: DynSync, V: DynSync, S: DynSync] + [shared_vector::AtomicSharedVector where T: DynSync + DynSend] [smallvec::SmallVec where A: smallvec::Array + DynSync] [thin_vec::ThinVec where T: DynSync] ); diff --git a/compiler/rustc_expand/Cargo.toml b/compiler/rustc_expand/Cargo.toml index 068c84da719a3..00c647cc466ef 100644 --- a/compiler/rustc_expand/Cargo.toml +++ b/compiler/rustc_expand/Cargo.toml @@ -29,6 +29,7 @@ rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } scoped-tls = "1.0" +shared_vector = "0.5" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } thin-vec = "0.2.18" tracing = "0.1" diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 1289f8ed63af6..4532d87b5fcbd 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -29,6 +29,7 @@ use rustc_parse::parser::Recovery; use rustc_session::Session; use rustc_session::errors::feature_err; use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym}; +use shared_vector::{Vector, vector}; use tracing::instrument; use crate::diagnostics::{ @@ -201,36 +202,36 @@ impl<'a> StripUnconfigured<'a> { return stream.clone(); } - let trees: Vec<_> = stream - .0 - .iter() - .filter_map(|tree| match tree.clone() { - AttrTokenTree::AttrsTarget(mut target) => { - // Expand any `cfg_attr` attributes. - target.attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr)); - - if self.in_cfg(&target.attrs) { - target.tokens = LazyAttrTokenStream::new_direct( - self.configure_tokens(&target.tokens.to_attr_token_stream()), - ); - Some(AttrTokenTree::AttrsTarget(target)) - } else { - // Remove the target if there's a `cfg` attribute and - // the condition isn't satisfied. - None - } - } - AttrTokenTree::Delimited(sp, spacing, delim, mut inner) => { - inner = self.configure_tokens(&inner); - Some(AttrTokenTree::Delimited(sp, spacing, delim, inner)) - } - AttrTokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => { - panic!("Should be `AttrTokenTree::Delimited`, not delim tokens: {:?}", tree); + let iter = stream.0.iter().filter_map(|tree| match tree.clone() { + AttrTokenTree::AttrsTarget(mut target) => { + // Expand any `cfg_attr` attributes. + target.attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr)); + + if self.in_cfg(&target.attrs) { + target.tokens = LazyAttrTokenStream::new_direct( + self.configure_tokens(&target.tokens.to_attr_token_stream()), + ); + Some(AttrTokenTree::AttrsTarget(target)) + } else { + // Remove the target if there's a `cfg` attribute and + // the condition isn't satisfied. + None } - AttrTokenTree::Token(token, spacing) => Some(AttrTokenTree::Token(token, spacing)), - }) - .collect(); - AttrTokenStream::new(trees) + } + AttrTokenTree::Delimited(sp, spacing, delim, mut inner) => { + inner = self.configure_tokens(&inner); + Some(AttrTokenTree::Delimited(sp, spacing, delim, inner)) + } + AttrTokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => { + panic!("Should be `AttrTokenTree::Delimited`, not delim tokens: {:?}", tree); + } + AttrTokenTree::Token(token, spacing) => Some(AttrTokenTree::Token(token, spacing)), + }); + // FIXME(shared_vector): `shared_vector::Vector` lacks `FromIterator`, so we can't use + // `iter.collect()` here. + let mut tts = Vector::with_capacity(stream.0.len()); + tts.extend(iter); + AttrTokenStream::new(tts) } /// Parse and expand all `cfg_attr` attributes into a list of attributes @@ -313,7 +314,10 @@ impl<'a> StripUnconfigured<'a> { // Convert `#[cfg_attr(pred, attr)]` to `#[attr]`. // Use the `#` from `#[cfg_attr(pred, attr)]` in the result `#[attr]`. - let mut orig_trees = cfg_attr.token_trees().into_iter(); + // FIXME(shared_vector): can't use `cfg_attr.token_trees().into_iter()` because `Vector` + // lacks a by-value `IntoIterator` impl. + let orig_trees = cfg_attr.token_trees(); + let mut orig_trees = orig_trees.into_iter(); let Some(TokenTree::Token(pound_token @ Token { kind: TokenKind::Pound, .. }, _)) = orig_trees.next() else { @@ -327,12 +331,12 @@ impl<'a> StripUnconfigured<'a> { else { panic!("Bad tokens for attribute {cfg_attr:?}"); }; - vec![ - AttrTokenTree::Token(pound_token, Spacing::Joint), - AttrTokenTree::Token(bang_token, Spacing::JointHidden), + vector![ + AttrTokenTree::Token(*pound_token, Spacing::Joint), + AttrTokenTree::Token(*bang_token, Spacing::JointHidden), ] } else { - vec![AttrTokenTree::Token(pound_token, Spacing::JointHidden)] + vector![AttrTokenTree::Token(*pound_token, Spacing::JointHidden)] }; // And the same thing for the `[`/`]` delimiters in `#[attr]`. @@ -342,8 +346,8 @@ impl<'a> StripUnconfigured<'a> { panic!("Bad tokens for attribute {cfg_attr:?}"); }; trees.push(AttrTokenTree::Delimited( - delim_span, - delim_spacing, + *delim_span, + *delim_spacing, Delimiter::Bracket, item.tokens .as_ref() diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index 3e82a61067af4..bf842450d4e0e 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -8,6 +8,7 @@ use rustc_session::Session; use rustc_session::errors::feature_err; use rustc_span::edition::Edition; use rustc_span::{Ident, Span, kw, sym}; +use shared_vector::vector; use crate::diagnostics; use crate::mbe::macro_parser::count_metavar_decls; @@ -191,7 +192,7 @@ pub(super) fn parse_one_tt( features: &Features, edition: Edition, ) -> TokenTree { - parse(&tokenstream::TokenStream::new(vec![input]), part, sess, node_id, features, edition) + parse(&tokenstream::TokenStream::new(vector![input]), part, sess, node_id, features, edition) .pop() .unwrap() } diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index 34d5147606e90..ca631dc7aab90 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -15,6 +15,7 @@ use rustc_span::{ BytePos, Ident, MacroRulesNormalizedIdent, Span, Symbol, SyntaxContext, kw, sym, with_metavar_spans, }; +use shared_vector::Vector; use smallvec::{SmallVec, smallvec}; use crate::diagnostics::{ @@ -64,11 +65,11 @@ struct TranscrCtx<'psess, 'itp> { /// /// Thus, if we try to pop the `result_stack` and it is empty, we have reached the top-level /// again, and we are done transcribing. - result: Vec, + result: Vector, /// The in-progress `result` lives at the top of this stack. Each entered `TokenTree` adds a /// new entry. - result_stack: Vec>, + result_stack: Vec>, } impl<'psess> TranscrCtx<'psess, '_> { @@ -186,7 +187,7 @@ pub(super) fn transcribe<'a>( src_span, DelimSpacing::new(Spacing::Alone, Spacing::Alone) )], - result: Vec::new(), + result: Vector::new(), result_stack: Vec::new(), }; diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index 4044a414c5fee..f35d14f93f74e 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -20,7 +20,7 @@ pub(crate) fn placeholder( args: Box::new(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), delim: Delimiter::Parenthesis, - tokens: ast::tokenstream::TokenStream::new(Vec::new()), + tokens: ast::tokenstream::TokenStream::default(), }), }) } diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 65af690611919..d2cce583dc7e5 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -19,6 +19,7 @@ use rustc_session::Session; use rustc_session::parse::ParseSess; use rustc_span::def_id::CrateNum; use rustc_span::{BytePos, FileName, Pos, Span, Symbol, sym}; +use shared_vector::Vector; use smallvec::{SmallVec, smallvec}; use crate::base::ExtCtxt; @@ -652,7 +653,12 @@ impl server::Server for Rustc<'_, '_> { &mut self, tree: TokenTree, ) -> Self::TokenStream { - Self::TokenStream::new((tree, &mut *self).to_internal().into_iter().collect::>()) + let internal = (tree, &mut *self).to_internal(); + // FIXME(shared_vector): `AtomicSharedVector` doesn't impl `FromIterator`, so we can't do + // `internal.into_iter().collect()`. + let mut tts = Vector::with_capacity(internal.len()); + tts.extend(internal); + Self::TokenStream::new(tts) } fn ts_concat_trees( diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml index 768a9546cccd4..9d79b4408830a 100644 --- a/compiler/rustc_parse/Cargo.toml +++ b/compiler/rustc_parse/Cargo.toml @@ -17,6 +17,7 @@ rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } +shared_vector = "0.5" thin-vec = "0.2.18" tracing = "0.1" unicode-normalization = "0.1.25" diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index 757cd755bf65f..9da8747eb742c 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -2,6 +2,7 @@ use rustc_ast::token::{self, Delimiter, Token}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_ast_pretty::pprust::token_to_string; use rustc_errors::Diag; +use shared_vector::Vector; use super::diagnostics::{ report_missing_open_delim, report_suspicious_mismatch_block, same_indentation_level, @@ -18,7 +19,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { // Move past the opening delimiter. let open_spacing = self.bump_minimal(); - let mut buf = Vec::new(); + let mut buf = Vector::new(); loop { if let Some(delim) = self.token.kind.open_delim() { // Invisible delimiters cannot occur here because `TokenTreesReader` parses diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 98b2ed1642582..4c6678763762c 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -14,6 +14,7 @@ use rustc_session::lint::builtin::VARARGS_WITHOUT_PATTERN; use rustc_span::edit_distance::edit_distance; use rustc_span::edition::Edition; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Ident, Span, Symbol, kw, respan, sym}; +use shared_vector::vector; use thin_vec::{ThinVec, thin_vec}; use tracing::debug; @@ -2502,7 +2503,7 @@ impl<'a> Parser<'a> { // Convert `MacParams MacBody` into `{ MacParams => MacBody }`. let bspan = body.span(); let arrow = TokenTree::token_alone(token::FatArrow, pspan.between(bspan)); // `=>` - let tokens = TokenStream::new(vec![params, arrow, body]); + let tokens = TokenStream::new(vector![params, arrow, body]); let dspan = DelimSpan::from_pair(pspan.shrink_to_lo(), bspan.shrink_to_hi()); Box::new(DelimArgs { dspan, delim: Delimiter::Brace, tokens }) } else { diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 04a9b1ff212a7..bd225b9fbee11 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -45,6 +45,7 @@ use rustc_errors::{Applicability, Diag, FatalError, MultiSpan, PResult}; use rustc_index::interval::IntervalSet; use rustc_session::parse::ParseSess; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym}; +use shared_vector::Vector; use thin_vec::ThinVec; use token_type::TokenTypeSet; pub use token_type::{ExpKeywordPair, ExpTokenPair, TokenType}; @@ -1451,7 +1452,7 @@ impl<'a> Parser<'a> { } pub fn parse_tokens(&mut self) -> TokenStream { - let mut result = Vec::new(); + let mut result = Vector::new(); loop { if self.token.kind.is_close_delim_or_eof() { break; diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 5286873f3dc55..78dc8baca8a7d 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -18,6 +18,7 @@ use rustc_span::source_map::{FilePathMapping, SourceMap}; use rustc_span::{ BytePos, FileName, Pos, Span, Symbol, create_default_session_globals_then, kw, sym, }; +use shared_vector::vector; use crate::lexer::StripTokens; use crate::parser::{AllowConstBlockItems, ForceCollect, Parser}; @@ -2320,7 +2321,7 @@ fn string_to_tts_1() { create_default_session_globals_then(|| { let tts = string_to_stream("fn a(b: i32) { b; }".to_string()); - let expected = TokenStream::new(vec![ + let expected = TokenStream::new(vector![ TokenTree::token_alone(token::Ident(kw::Fn, IdentIsRaw::No), sp(0, 2)), TokenTree::token_joint_hidden( token::Ident(sym::character('a'), IdentIsRaw::No), @@ -2332,7 +2333,7 @@ fn string_to_tts_1() { // `b`, `Alone` because the `)` is followed by whitespace. DelimSpacing::new(Spacing::JointHidden, Spacing::Alone), Delimiter::Parenthesis, - TokenStream::new(vec![ + TokenStream::new(vector![ TokenTree::token_joint( token::Ident(sym::character('b'), IdentIsRaw::No), sp(5, 6), @@ -2352,7 +2353,7 @@ fn string_to_tts_1() { // EOF. DelimSpacing::new(Spacing::Alone, Spacing::Alone), Delimiter::Brace, - TokenStream::new(vec![ + TokenStream::new(vector![ TokenTree::token_joint( token::Ident(sym::character('b'), IdentIsRaw::No), sp(15, 16), diff --git a/src/tools/rustfmt/src/lib.rs b/src/tools/rustfmt/src/lib.rs index 942b42ec5f20c..905e06aa540b1 100644 --- a/src/tools/rustfmt/src/lib.rs +++ b/src/tools/rustfmt/src/lib.rs @@ -14,6 +14,7 @@ extern crate rustc_expand; extern crate rustc_parse; extern crate rustc_session; extern crate rustc_span; +extern crate shared_vector; extern crate thin_vec; // Necessary to pull in object code as the rest of the rustc crates are shipped only as rmeta diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index 1c5d804c6d465..46c1f5ae04ce9 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -17,6 +17,7 @@ use rustc_ast::token::{Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{TokenStream, TokenStreamIter, TokenTree}; use rustc_ast_pretty::pprust; use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol}; +use shared_vector::vector; use tracing::debug; use crate::comment::{ @@ -1209,7 +1210,7 @@ impl<'a> MacroParser<'a> { TokenTree::Token(..) => return None, &TokenTree::Delimited(delimited_span, _, d, _) => (delimited_span.open.lo(), d), }; - let args = TokenStream::new(vec![tok.clone()]); + let args = TokenStream::new(vector![tok.clone()]); match self.iter.next()? { TokenTree::Token( Token { diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index ebe3c04ea4cc4..35fa8be0314bc 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -437,6 +437,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "sha1", "sha2", "sharded-slab", + "shared_vector", "shlex", "simd-adler32", "smallvec", diff --git a/tests/ui-fulldeps/auxiliary/parser.rs b/tests/ui-fulldeps/auxiliary/parser.rs index 6ee39e5130f68..a517f32a3ee4e 100644 --- a/tests/ui-fulldeps/auxiliary/parser.rs +++ b/tests/ui-fulldeps/auxiliary/parser.rs @@ -63,7 +63,7 @@ impl MutVisitor for Normalize { } fn normalize_attr_token_stream(stream: &mut AttrTokenStream) { - Arc::make_mut(&mut stream.0) + stream.0 .iter_mut() .for_each(normalize_attr_token_tree); }