mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-14 02:49:40 +00:00
Rollup merge of #92923 - dtolnay:ringbuffer, r=petrochenkov
Abstract the pretty printer's ringbuffer to be infinitely sized This PR backports8e5e83c3ff
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 asfcb5968b1e
and4427cedcb8
. 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:de9b573eed/compiler/rustc_ast_pretty/src/pp.rs (L227-L228)
It was raised from 3 to 55 in https://github.com/rust-lang/rust/pull/33934 because that was empirically the size that avoided crashing on one particular test crate, but according to https://github.com/rust-lang/rust/pull/33934#issuecomment-226700470 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.
This commit is contained in:
commit
04b2073c84
@ -132,6 +132,9 @@
|
||||
//! methods called `Printer::scan_*`, and the 'PRINT' process is the
|
||||
//! method called `Printer::print`.
|
||||
|
||||
mod ring;
|
||||
|
||||
use ring::RingBuffer;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
@ -190,8 +193,7 @@ impl fmt::Display for Token {
|
||||
}
|
||||
}
|
||||
|
||||
fn buf_str(buf: &[BufEntry], left: usize, right: usize, lim: usize) -> String {
|
||||
let n = buf.len();
|
||||
fn buf_str(buf: &RingBuffer<BufEntry>, left: usize, right: usize, lim: usize) -> String {
|
||||
let mut i = left;
|
||||
let mut l = lim;
|
||||
let mut s = String::from("[");
|
||||
@ -202,7 +204,6 @@ fn buf_str(buf: &[BufEntry], left: usize, right: usize, lim: usize) -> String {
|
||||
}
|
||||
s.push_str(&format!("{}={}", buf[i].size, &buf[i].token));
|
||||
i += 1;
|
||||
i %= n;
|
||||
}
|
||||
s.push(']');
|
||||
s
|
||||
@ -224,7 +225,6 @@ const SIZE_INFINITY: isize = 0xffff;
|
||||
|
||||
pub struct Printer {
|
||||
out: String,
|
||||
buf_max_len: usize,
|
||||
/// Width of lines we're constrained to
|
||||
margin: isize,
|
||||
/// Number of spaces left on line
|
||||
@ -234,7 +234,7 @@ pub struct Printer {
|
||||
/// Index of right side of input stream
|
||||
right: usize,
|
||||
/// Ring-buffer of tokens and calculated sizes
|
||||
buf: Vec<BufEntry>,
|
||||
buf: RingBuffer<BufEntry>,
|
||||
/// Running size of stream "...left"
|
||||
left_total: isize,
|
||||
/// Running size of stream "...right"
|
||||
@ -267,19 +267,16 @@ impl Default for BufEntry {
|
||||
impl Printer {
|
||||
pub fn new() -> Self {
|
||||
let linewidth = 78;
|
||||
// Yes 55, it makes the ring buffers big enough to never fall behind.
|
||||
let n: usize = 55 * linewidth;
|
||||
debug!("Printer::new {}", linewidth);
|
||||
let mut buf = RingBuffer::new();
|
||||
buf.advance_right();
|
||||
Printer {
|
||||
out: String::new(),
|
||||
buf_max_len: n,
|
||||
margin: linewidth as isize,
|
||||
space: linewidth as isize,
|
||||
left: 0,
|
||||
right: 0,
|
||||
// Initialize a single entry; advance_right() will extend it on demand
|
||||
// up to `buf_max_len` elements.
|
||||
buf: vec![BufEntry::default()],
|
||||
buf,
|
||||
left_total: 0,
|
||||
right_total: 0,
|
||||
scan_stack: VecDeque::new(),
|
||||
@ -308,8 +305,8 @@ impl Printer {
|
||||
if self.scan_stack.is_empty() {
|
||||
self.left_total = 1;
|
||||
self.right_total = 1;
|
||||
self.left = 0;
|
||||
self.right = 0;
|
||||
self.right = self.left;
|
||||
self.buf.truncate(1);
|
||||
} else {
|
||||
self.advance_right();
|
||||
}
|
||||
@ -332,8 +329,8 @@ impl Printer {
|
||||
if self.scan_stack.is_empty() {
|
||||
self.left_total = 1;
|
||||
self.right_total = 1;
|
||||
self.left = 0;
|
||||
self.right = 0;
|
||||
self.right = self.left;
|
||||
self.buf.truncate(1);
|
||||
} else {
|
||||
self.advance_right();
|
||||
}
|
||||
@ -400,12 +397,7 @@ impl Printer {
|
||||
|
||||
fn advance_right(&mut self) {
|
||||
self.right += 1;
|
||||
self.right %= self.buf_max_len;
|
||||
// Extend the buf if necessary.
|
||||
if self.right == self.buf.len() {
|
||||
self.buf.push(BufEntry::default());
|
||||
}
|
||||
assert_ne!(self.right, self.left);
|
||||
self.buf.advance_right();
|
||||
}
|
||||
|
||||
fn advance_left(&mut self) {
|
||||
@ -437,8 +429,8 @@ impl Printer {
|
||||
break;
|
||||
}
|
||||
|
||||
self.buf.advance_left();
|
||||
self.left += 1;
|
||||
self.left %= self.buf_max_len;
|
||||
|
||||
left_size = self.buf[self.left].size;
|
||||
}
|
||||
|
53
compiler/rustc_ast_pretty/src/pp/ring.rs
Normal file
53
compiler/rustc_ast_pretty/src/pp/ring.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
/// A view onto a finite range of an infinitely long sequence of T.
|
||||
///
|
||||
/// The Ts are indexed 0..infinity. A RingBuffer begins as a view of elements
|
||||
/// 0..0 (i.e. nothing). The user of the RingBuffer advances its left and right
|
||||
/// position independently, although only in the positive direction, and only
|
||||
/// with left <= right at all times.
|
||||
///
|
||||
/// Holding a RingBuffer whose view is elements left..right gives the ability to
|
||||
/// use Index and IndexMut to access elements i in the infinitely long queue for
|
||||
/// which left <= i < right.
|
||||
pub struct RingBuffer<T> {
|
||||
data: VecDeque<T>,
|
||||
// Abstract index of data[0] in the infinitely sized queue.
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<T> RingBuffer<T> {
|
||||
pub fn new() -> Self {
|
||||
RingBuffer { data: VecDeque::new(), offset: 0 }
|
||||
}
|
||||
|
||||
pub fn advance_right(&mut self)
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
self.data.push_back(T::default());
|
||||
}
|
||||
|
||||
pub fn advance_left(&mut self) {
|
||||
self.data.pop_front().unwrap();
|
||||
self.offset += 1;
|
||||
}
|
||||
|
||||
pub fn truncate(&mut self, len: usize) {
|
||||
self.data.truncate(len);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<usize> for RingBuffer<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.data[index.checked_sub(self.offset).unwrap()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<usize> for RingBuffer<T> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.data[index.checked_sub(self.offset).unwrap()]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user