Skip to content

Commit 1119688

Browse files
committed
Auto merge of rust-lang#136932 - m-ou-se:fmt-width-precision-u16, r=scottmcm
Reduce formatting `width` and `precision` to 16 bits This is part of rust-lang#99012 This is reduces the `width` and `precision` fields in format strings to 16 bits. They are currently full `usize`s, but it's a bit nonsensical that we need to support the case where someone wants to pad their value to eighteen quintillion spaces and/or have eighteen quintillion digits of precision. By reducing these fields to 16 bit, we can reduce `FormattingOptions` to 64 bits (see rust-lang#136974) and improve the in memory representation of `format_args!()`. (See additional context below.) This also fixes a bug where the width or precision is silently truncated when cross-compiling to a target with a smaller `usize`. By reducing the width and precision fields to the minimum guaranteed size of `usize`, 16 bits, this bug is eliminated. This is a breaking change, but affects almost no existing code. --- Details of this change: There are three ways to set a width or precision today: 1. Directly a formatting string, e.g. `println!("{a:1234}")` 2. Indirectly in a formatting string, e.g. `println!("{a:width$}", width=1234)` 3. Through the unstable `FormattingOptions::width` method. This PR: - Adds a compiler error for 1. (`println!("{a:9999999}")` no longer compiles and gives a clear error.) - Adds a runtime check for 2. (`println!("{a:width$}, width=9999999)` will panic.) - Changes the signatures of the (unstable) `FormattingOptions::[get_]width` methods to use a `u16` instead. --- Additional context for improving `FormattingOptions` and `fmt::Arguments`: All the formatting flags and options are currently: - The `+` flag (1 bit) - The `-` flag (1 bit) - The `#` flag (1 bit) - The `0` flag (1 bit) - The `x?` flag (1 bit) - The `X?` flag (1 bit) - The alignment (2 bits) - The fill character (21 bits) - Whether a width is specified (1 bit) - Whether a precision is specified (1 bit) - If used, the width (a full usize) - If used, the precision (a full usize) Everything except the last two can simply fit in a `u32` (those add up to 31 bits in total). If we can accept a max width and precision of u16::MAX, we can make a `FormattingOptions` that is exactly 64 bits in size; the same size as a thin reference on most platforms. If, additionally, we also limit the number of formatting arguments, we can also reduce the size of `fmt::Arguments` (that is, of a `format_args!()` expression).
2 parents c5a68b2 + 24955d9 commit 1119688

File tree

5 files changed

+61
-49
lines changed

5 files changed

+61
-49
lines changed

core/src/fmt/float.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn float_to_decimal_common_exact<T>(
2929
fmt: &mut Formatter<'_>,
3030
num: &T,
3131
sign: flt2dec::Sign,
32-
precision: usize,
32+
precision: u16,
3333
) -> Result
3434
where
3535
T: flt2dec::DecodableFloat,
@@ -40,7 +40,7 @@ where
4040
flt2dec::strategy::grisu::format_exact,
4141
*num,
4242
sign,
43-
precision,
43+
precision.into(),
4444
&mut buf,
4545
&mut parts,
4646
);
@@ -55,7 +55,7 @@ fn float_to_decimal_common_shortest<T>(
5555
fmt: &mut Formatter<'_>,
5656
num: &T,
5757
sign: flt2dec::Sign,
58-
precision: usize,
58+
precision: u16,
5959
) -> Result
6060
where
6161
T: flt2dec::DecodableFloat,
@@ -68,7 +68,7 @@ where
6868
flt2dec::strategy::grisu::format_shortest,
6969
*num,
7070
sign,
71-
precision,
71+
precision.into(),
7272
&mut buf,
7373
&mut parts,
7474
);
@@ -101,7 +101,7 @@ fn float_to_exponential_common_exact<T>(
101101
fmt: &mut Formatter<'_>,
102102
num: &T,
103103
sign: flt2dec::Sign,
104-
precision: usize,
104+
precision: u16,
105105
upper: bool,
106106
) -> Result
107107
where
@@ -113,7 +113,7 @@ where
113113
flt2dec::strategy::grisu::format_exact,
114114
*num,
115115
sign,
116-
precision,
116+
precision.into(),
117117
upper,
118118
&mut buf,
119119
&mut parts,

core/src/fmt/mod.rs

+27-24
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ pub struct FormattingOptions {
294294
flags: u32,
295295
fill: char,
296296
align: Option<Alignment>,
297-
width: Option<usize>,
298-
precision: Option<usize>,
297+
width: Option<u16>,
298+
precision: Option<u16>,
299299
}
300300

301301
impl FormattingOptions {
@@ -389,7 +389,7 @@ impl FormattingOptions {
389389
/// the padding specified by [`FormattingOptions::fill`]/[`FormattingOptions::align`]
390390
/// will be used to take up the required space.
391391
#[unstable(feature = "formatting_options", issue = "118117")]
392-
pub fn width(&mut self, width: Option<usize>) -> &mut Self {
392+
pub fn width(&mut self, width: Option<u16>) -> &mut Self {
393393
self.width = width;
394394
self
395395
}
@@ -403,7 +403,7 @@ impl FormattingOptions {
403403
/// - For floating-point types, this indicates how many digits after the
404404
/// decimal point should be printed.
405405
#[unstable(feature = "formatting_options", issue = "118117")]
406-
pub fn precision(&mut self, precision: Option<usize>) -> &mut Self {
406+
pub fn precision(&mut self, precision: Option<u16>) -> &mut Self {
407407
self.precision = precision;
408408
self
409409
}
@@ -455,12 +455,12 @@ impl FormattingOptions {
455455
}
456456
/// Returns the current width.
457457
#[unstable(feature = "formatting_options", issue = "118117")]
458-
pub const fn get_width(&self) -> Option<usize> {
458+
pub const fn get_width(&self) -> Option<u16> {
459459
self.width
460460
}
461461
/// Returns the current precision.
462462
#[unstable(feature = "formatting_options", issue = "118117")]
463-
pub const fn get_precision(&self) -> Option<usize> {
463+
pub const fn get_precision(&self) -> Option<u16> {
464464
self.precision
465465
}
466466
/// Returns the current precision.
@@ -1499,15 +1499,18 @@ unsafe fn run(fmt: &mut Formatter<'_>, arg: &rt::Placeholder, args: &[rt::Argume
14991499
unsafe { value.fmt(fmt) }
15001500
}
15011501

1502-
unsafe fn getcount(args: &[rt::Argument<'_>], cnt: &rt::Count) -> Option<usize> {
1502+
unsafe fn getcount(args: &[rt::Argument<'_>], cnt: &rt::Count) -> Option<u16> {
15031503
match *cnt {
1504+
#[cfg(bootstrap)]
1505+
rt::Count::Is(n) => Some(n as u16),
1506+
#[cfg(not(bootstrap))]
15041507
rt::Count::Is(n) => Some(n),
15051508
rt::Count::Implied => None,
15061509
rt::Count::Param(i) => {
15071510
debug_assert!(i < args.len());
15081511
// SAFETY: cnt and args come from the same Arguments,
15091512
// which guarantees this index is always within bounds.
1510-
unsafe { args.get_unchecked(i).as_usize() }
1513+
unsafe { args.get_unchecked(i).as_u16() }
15111514
}
15121515
}
15131516
}
@@ -1516,11 +1519,11 @@ unsafe fn getcount(args: &[rt::Argument<'_>], cnt: &rt::Count) -> Option<usize>
15161519
#[must_use = "don't forget to write the post padding"]
15171520
pub(crate) struct PostPadding {
15181521
fill: char,
1519-
padding: usize,
1522+
padding: u16,
15201523
}
15211524

15221525
impl PostPadding {
1523-
fn new(fill: char, padding: usize) -> PostPadding {
1526+
fn new(fill: char, padding: u16) -> PostPadding {
15241527
PostPadding { fill, padding }
15251528
}
15261529

@@ -1634,7 +1637,7 @@ impl<'a> Formatter<'a> {
16341637
}
16351638
// Check if we're over the minimum width, if so then we can also
16361639
// just write the bytes.
1637-
Some(min) if width >= min => {
1640+
Some(min) if width >= usize::from(min) => {
16381641
write_prefix(self, sign, prefix)?;
16391642
self.buf.write_str(buf)
16401643
}
@@ -1645,7 +1648,7 @@ impl<'a> Formatter<'a> {
16451648
let old_align =
16461649
crate::mem::replace(&mut self.options.align, Some(Alignment::Right));
16471650
write_prefix(self, sign, prefix)?;
1648-
let post_padding = self.padding(min - width, Alignment::Right)?;
1651+
let post_padding = self.padding(min - width as u16, Alignment::Right)?;
16491652
self.buf.write_str(buf)?;
16501653
post_padding.write(self)?;
16511654
self.options.fill = old_fill;
@@ -1654,7 +1657,7 @@ impl<'a> Formatter<'a> {
16541657
}
16551658
// Otherwise, the sign and prefix goes after the padding
16561659
Some(min) => {
1657-
let post_padding = self.padding(min - width, Alignment::Right)?;
1660+
let post_padding = self.padding(min - width as u16, Alignment::Right)?;
16581661
write_prefix(self, sign, prefix)?;
16591662
self.buf.write_str(buf)?;
16601663
post_padding.write(self)
@@ -1702,26 +1705,26 @@ impl<'a> Formatter<'a> {
17021705
// string being formatted.
17031706
let (s, char_count) = if let Some(max_char_count) = self.options.precision {
17041707
let mut iter = s.char_indices();
1705-
let remaining = match iter.advance_by(max_char_count) {
1708+
let remaining = match iter.advance_by(usize::from(max_char_count)) {
17061709
Ok(()) => 0,
17071710
Err(remaining) => remaining.get(),
17081711
};
17091712
// SAFETY: The offset of `.char_indices()` is guaranteed to be
17101713
// in-bounds and between character boundaries.
17111714
let truncated = unsafe { s.get_unchecked(..iter.offset()) };
1712-
(truncated, max_char_count - remaining)
1715+
(truncated, usize::from(max_char_count) - remaining)
17131716
} else {
17141717
// Use the optimized char counting algorithm for the full string.
17151718
(s, s.chars().count())
17161719
};
17171720

17181721
// The `width` field is more of a minimum width parameter at this point.
17191722
if let Some(width) = self.options.width
1720-
&& char_count < width
1723+
&& char_count < usize::from(width)
17211724
{
17221725
// If we're under the minimum width, then fill up the minimum width
17231726
// with the specified string + some alignment.
1724-
let post_padding = self.padding(width - char_count, Alignment::Left)?;
1727+
let post_padding = self.padding(width - char_count as u16, Alignment::Left)?;
17251728
self.buf.write_str(s)?;
17261729
post_padding.write(self)
17271730
} else {
@@ -1737,7 +1740,7 @@ impl<'a> Formatter<'a> {
17371740
/// thing that is being padded.
17381741
pub(crate) fn padding(
17391742
&mut self,
1740-
padding: usize,
1743+
padding: u16,
17411744
default: Alignment,
17421745
) -> result::Result<PostPadding, Error> {
17431746
let align = self.align().unwrap_or(default);
@@ -1777,19 +1780,19 @@ impl<'a> Formatter<'a> {
17771780

17781781
// remove the sign from the formatted parts
17791782
formatted.sign = "";
1780-
width = width.saturating_sub(sign.len());
1783+
width = width.saturating_sub(sign.len() as u16);
17811784
self.options.fill = '0';
17821785
self.options.align = Some(Alignment::Right);
17831786
}
17841787

17851788
// remaining parts go through the ordinary padding process.
17861789
let len = formatted.len();
1787-
let ret = if width <= len {
1790+
let ret = if usize::from(width) <= len {
17881791
// no padding
17891792
// SAFETY: Per the precondition.
17901793
unsafe { self.write_formatted_parts(&formatted) }
17911794
} else {
1792-
let post_padding = self.padding(width - len, Alignment::Right)?;
1795+
let post_padding = self.padding(width - len as u16, Alignment::Right)?;
17931796
// SAFETY: Per the precondition.
17941797
unsafe {
17951798
self.write_formatted_parts(&formatted)?;
@@ -2021,7 +2024,7 @@ impl<'a> Formatter<'a> {
20212024
#[must_use]
20222025
#[stable(feature = "fmt_flags", since = "1.5.0")]
20232026
pub fn width(&self) -> Option<usize> {
2024-
self.options.width
2027+
self.options.width.map(|x| x as usize)
20252028
}
20262029

20272030
/// Returns the optionally specified precision for numeric types.
@@ -2052,7 +2055,7 @@ impl<'a> Formatter<'a> {
20522055
#[must_use]
20532056
#[stable(feature = "fmt_flags", since = "1.5.0")]
20542057
pub fn precision(&self) -> Option<usize> {
2055-
self.options.precision
2058+
self.options.precision.map(|x| x as usize)
20562059
}
20572060

20582061
/// Determines if the `+` flag was specified.
@@ -2792,7 +2795,7 @@ pub(crate) fn pointer_fmt_inner(ptr_addr: usize, f: &mut Formatter<'_>) -> Resul
27922795
f.options.flags |= 1 << (rt::Flag::SignAwareZeroPad as u32);
27932796

27942797
if f.options.width.is_none() {
2795-
f.options.width = Some((usize::BITS / 4) as usize + 2);
2798+
f.options.width = Some((usize::BITS / 4) as u16 + 2);
27962799
}
27972800
}
27982801
f.options.flags |= 1 << (rt::Flag::Alternate as u32);

core/src/fmt/rt.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ pub enum Alignment {
4747
#[derive(Copy, Clone)]
4848
pub enum Count {
4949
/// Specified with a literal number, stores the value
50+
#[cfg(bootstrap)]
5051
Is(usize),
52+
/// Specified with a literal number, stores the value
53+
#[cfg(not(bootstrap))]
54+
Is(u16),
5155
/// Specified using `$` and `*` syntaxes, stores the index into `args`
5256
Param(usize),
5357
/// Not specified
@@ -74,7 +78,7 @@ enum ArgumentType<'a> {
7478
formatter: unsafe fn(NonNull<()>, &mut Formatter<'_>) -> Result,
7579
_lifetime: PhantomData<&'a ()>,
7680
},
77-
Count(usize),
81+
Count(u16),
7882
}
7983

8084
/// This struct represents a generic "argument" which is taken by format_args!().
@@ -150,8 +154,12 @@ impl Argument<'_> {
150154
Self::new(x, UpperExp::fmt)
151155
}
152156
#[inline]
157+
#[track_caller]
153158
pub const fn from_usize(x: &usize) -> Argument<'_> {
154-
Argument { ty: ArgumentType::Count(*x) }
159+
if *x > u16::MAX as usize {
160+
panic!("Formatting argument out of range");
161+
}
162+
Argument { ty: ArgumentType::Count(*x as u16) }
155163
}
156164

157165
/// Format this placeholder argument.
@@ -181,7 +189,7 @@ impl Argument<'_> {
181189
}
182190

183191
#[inline]
184-
pub(super) const fn as_usize(&self) -> Option<usize> {
192+
pub(super) const fn as_u16(&self) -> Option<u16> {
185193
match self.ty {
186194
ArgumentType::Count(count) => Some(count),
187195
ArgumentType::Placeholder { .. } => None,

core/src/time.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1377,7 +1377,8 @@ impl fmt::Debug for Duration {
13771377
} else {
13781378
// We need to add padding. Use the `Formatter::padding` helper function.
13791379
let default_align = fmt::Alignment::Left;
1380-
let post_padding = f.padding(requested_w - actual_w, default_align)?;
1380+
let post_padding =
1381+
f.padding((requested_w - actual_w) as u16, default_align)?;
13811382
emit_without_padding(f)?;
13821383
post_padding.write(f)
13831384
}

coretests/tests/num/flt2dec/mod.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ where
577577
}
578578

579579
// very large output
580-
assert_eq!(to_string(f, 1.1, Minus, 80000), format!("1.1{:0>79999}", ""));
580+
assert_eq!(to_string(f, 1.1, Minus, 50000), format!("1.1{:0>49999}", ""));
581581
}
582582

583583
pub fn to_shortest_exp_str_test<F>(mut f_: F)
@@ -914,22 +914,22 @@ where
914914
);
915915

916916
// very large output
917-
assert_eq!(to_string(f, 0.0, Minus, 80000, false), format!("0.{:0>79999}e0", ""));
918-
assert_eq!(to_string(f, 1.0e1, Minus, 80000, false), format!("1.{:0>79999}e1", ""));
919-
assert_eq!(to_string(f, 1.0e0, Minus, 80000, false), format!("1.{:0>79999}e0", ""));
917+
assert_eq!(to_string(f, 0.0, Minus, 50000, false), format!("0.{:0>49999}e0", ""));
918+
assert_eq!(to_string(f, 1.0e1, Minus, 50000, false), format!("1.{:0>49999}e1", ""));
919+
assert_eq!(to_string(f, 1.0e0, Minus, 50000, false), format!("1.{:0>49999}e0", ""));
920920
assert_eq!(
921-
to_string(f, 1.0e-1, Minus, 80000, false),
921+
to_string(f, 1.0e-1, Minus, 50000, false),
922922
format!(
923-
"1.000000000000000055511151231257827021181583404541015625{:0>79945}\
923+
"1.000000000000000055511151231257827021181583404541015625{:0>49945}\
924924
e-1",
925925
""
926926
)
927927
);
928928
assert_eq!(
929-
to_string(f, 1.0e-20, Minus, 80000, false),
929+
to_string(f, 1.0e-20, Minus, 50000, false),
930930
format!(
931931
"9.999999999999999451532714542095716517295037027873924471077157760\
932-
66783064379706047475337982177734375{:0>79901}e-21",
932+
66783064379706047475337982177734375{:0>49901}e-21",
933933
""
934934
)
935935
);
@@ -1150,18 +1150,18 @@ where
11501150
);
11511151

11521152
// very large output
1153-
assert_eq!(to_string(f, 0.0, Minus, 80000), format!("0.{:0>80000}", ""));
1154-
assert_eq!(to_string(f, 1.0e1, Minus, 80000), format!("10.{:0>80000}", ""));
1155-
assert_eq!(to_string(f, 1.0e0, Minus, 80000), format!("1.{:0>80000}", ""));
1153+
assert_eq!(to_string(f, 0.0, Minus, 50000), format!("0.{:0>50000}", ""));
1154+
assert_eq!(to_string(f, 1.0e1, Minus, 50000), format!("10.{:0>50000}", ""));
1155+
assert_eq!(to_string(f, 1.0e0, Minus, 50000), format!("1.{:0>50000}", ""));
11561156
assert_eq!(
1157-
to_string(f, 1.0e-1, Minus, 80000),
1158-
format!("0.1000000000000000055511151231257827021181583404541015625{:0>79945}", "")
1157+
to_string(f, 1.0e-1, Minus, 50000),
1158+
format!("0.1000000000000000055511151231257827021181583404541015625{:0>49945}", "")
11591159
);
11601160
assert_eq!(
1161-
to_string(f, 1.0e-20, Minus, 80000),
1161+
to_string(f, 1.0e-20, Minus, 50000),
11621162
format!(
11631163
"0.0000000000000000000099999999999999994515327145420957165172950370\
1164-
2787392447107715776066783064379706047475337982177734375{:0>79881}",
1164+
2787392447107715776066783064379706047475337982177734375{:0>49881}",
11651165
""
11661166
)
11671167
);

0 commit comments

Comments
 (0)