Skip to content

Commit 04b2073

Browse files
authored
Rollup merge of #92923 - dtolnay:ringbuffer, r=petrochenkov
Abstract the pretty printer's ringbuffer to be infinitely sized This PR backports dtolnay/prettyplease@8e5e83c from the `prettyplease` crate into `rustc_ast_pretty`. Using a dedicated RingBuffer type with non-wrapping indices, instead of manually `%`-ing indices into a capped sized buffer, unlocks a number of simplifications to the pretty printing algorithm implementation in followup commits such as dtolnay/prettyplease@fcb5968 and dtolnay/prettyplease@4427ced. This change also greatly reduces memory overhead of the pretty printer. The old implementation always grows its buffer to 205920 bytes even for files without deeply nested code, because it only wraps its indices when they hit the maximum tolerable size of the ring buffer (the size after which the pretty printer will crash if there are that many tokens buffered). In contrast, the new implementation uses memory proportional to the peak number of simultaneously buffered tokens only, not the number of tokens that have ever been in the buffer. Speaking of crashing the pretty printer and "maximum tolerable size", the constant used for that in the old implementation is a lie: https://github.com/rust-lang/rust/blob/de9b573eedaaa6d6e7c00c986cccbee802f9287b/compiler/rustc_ast_pretty/src/pp.rs#L227-L228 It was raised from 3 to 55 in #33934 because that was empirically the size that avoided crashing on one particular test crate, but according to #33934 (comment) other syntax trees still crash at that size. There is no reason to believe that any particular size is good enough for arbitrary code, and using a large number like 55 adds overhead to inputs that never need close to that much of a buffer. The new implementation eliminates this tradeoff.
2 parents be3d25b + 7b5b3cf commit 04b2073

File tree

2 files changed

+67
-22
lines changed

2 files changed

+67
-22
lines changed

Diff for: compiler/rustc_ast_pretty/src/pp.rs

+14-22
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@
132132
//! methods called `Printer::scan_*`, and the 'PRINT' process is the
133133
//! method called `Printer::print`.
134134
135+
mod ring;
136+
137+
use ring::RingBuffer;
135138
use std::borrow::Cow;
136139
use std::collections::VecDeque;
137140
use std::fmt;
@@ -190,8 +193,7 @@ impl fmt::Display for Token {
190193
}
191194
}
192195

193-
fn buf_str(buf: &[BufEntry], left: usize, right: usize, lim: usize) -> String {
194-
let n = buf.len();
196+
fn buf_str(buf: &RingBuffer<BufEntry>, left: usize, right: usize, lim: usize) -> String {
195197
let mut i = left;
196198
let mut l = lim;
197199
let mut s = String::from("[");
@@ -202,7 +204,6 @@ fn buf_str(buf: &[BufEntry], left: usize, right: usize, lim: usize) -> String {
202204
}
203205
s.push_str(&format!("{}={}", buf[i].size, &buf[i].token));
204206
i += 1;
205-
i %= n;
206207
}
207208
s.push(']');
208209
s
@@ -224,7 +225,6 @@ const SIZE_INFINITY: isize = 0xffff;
224225

225226
pub struct Printer {
226227
out: String,
227-
buf_max_len: usize,
228228
/// Width of lines we're constrained to
229229
margin: isize,
230230
/// Number of spaces left on line
@@ -234,7 +234,7 @@ pub struct Printer {
234234
/// Index of right side of input stream
235235
right: usize,
236236
/// Ring-buffer of tokens and calculated sizes
237-
buf: Vec<BufEntry>,
237+
buf: RingBuffer<BufEntry>,
238238
/// Running size of stream "...left"
239239
left_total: isize,
240240
/// Running size of stream "...right"
@@ -267,19 +267,16 @@ impl Default for BufEntry {
267267
impl Printer {
268268
pub fn new() -> Self {
269269
let linewidth = 78;
270-
// Yes 55, it makes the ring buffers big enough to never fall behind.
271-
let n: usize = 55 * linewidth;
272270
debug!("Printer::new {}", linewidth);
271+
let mut buf = RingBuffer::new();
272+
buf.advance_right();
273273
Printer {
274274
out: String::new(),
275-
buf_max_len: n,
276275
margin: linewidth as isize,
277276
space: linewidth as isize,
278277
left: 0,
279278
right: 0,
280-
// Initialize a single entry; advance_right() will extend it on demand
281-
// up to `buf_max_len` elements.
282-
buf: vec![BufEntry::default()],
279+
buf,
283280
left_total: 0,
284281
right_total: 0,
285282
scan_stack: VecDeque::new(),
@@ -308,8 +305,8 @@ impl Printer {
308305
if self.scan_stack.is_empty() {
309306
self.left_total = 1;
310307
self.right_total = 1;
311-
self.left = 0;
312-
self.right = 0;
308+
self.right = self.left;
309+
self.buf.truncate(1);
313310
} else {
314311
self.advance_right();
315312
}
@@ -332,8 +329,8 @@ impl Printer {
332329
if self.scan_stack.is_empty() {
333330
self.left_total = 1;
334331
self.right_total = 1;
335-
self.left = 0;
336-
self.right = 0;
332+
self.right = self.left;
333+
self.buf.truncate(1);
337334
} else {
338335
self.advance_right();
339336
}
@@ -400,12 +397,7 @@ impl Printer {
400397

401398
fn advance_right(&mut self) {
402399
self.right += 1;
403-
self.right %= self.buf_max_len;
404-
// Extend the buf if necessary.
405-
if self.right == self.buf.len() {
406-
self.buf.push(BufEntry::default());
407-
}
408-
assert_ne!(self.right, self.left);
400+
self.buf.advance_right();
409401
}
410402

411403
fn advance_left(&mut self) {
@@ -437,8 +429,8 @@ impl Printer {
437429
break;
438430
}
439431

432+
self.buf.advance_left();
440433
self.left += 1;
441-
self.left %= self.buf_max_len;
442434

443435
left_size = self.buf[self.left].size;
444436
}

Diff for: compiler/rustc_ast_pretty/src/pp/ring.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::collections::VecDeque;
2+
use std::ops::{Index, IndexMut};
3+
4+
/// A view onto a finite range of an infinitely long sequence of T.
5+
///
6+
/// The Ts are indexed 0..infinity. A RingBuffer begins as a view of elements
7+
/// 0..0 (i.e. nothing). The user of the RingBuffer advances its left and right
8+
/// position independently, although only in the positive direction, and only
9+
/// with left <= right at all times.
10+
///
11+
/// Holding a RingBuffer whose view is elements left..right gives the ability to
12+
/// use Index and IndexMut to access elements i in the infinitely long queue for
13+
/// which left <= i < right.
14+
pub struct RingBuffer<T> {
15+
data: VecDeque<T>,
16+
// Abstract index of data[0] in the infinitely sized queue.
17+
offset: usize,
18+
}
19+
20+
impl<T> RingBuffer<T> {
21+
pub fn new() -> Self {
22+
RingBuffer { data: VecDeque::new(), offset: 0 }
23+
}
24+
25+
pub fn advance_right(&mut self)
26+
where
27+
T: Default,
28+
{
29+
self.data.push_back(T::default());
30+
}
31+
32+
pub fn advance_left(&mut self) {
33+
self.data.pop_front().unwrap();
34+
self.offset += 1;
35+
}
36+
37+
pub fn truncate(&mut self, len: usize) {
38+
self.data.truncate(len);
39+
}
40+
}
41+
42+
impl<T> Index<usize> for RingBuffer<T> {
43+
type Output = T;
44+
fn index(&self, index: usize) -> &Self::Output {
45+
&self.data[index.checked_sub(self.offset).unwrap()]
46+
}
47+
}
48+
49+
impl<T> IndexMut<usize> for RingBuffer<T> {
50+
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
51+
&mut self.data[index.checked_sub(self.offset).unwrap()]
52+
}
53+
}

0 commit comments

Comments
 (0)