Skip to content

Commit e31ff22

Browse files
committed
New format_args!()+fmt::Arguments implementation.
1 parent 1132958 commit e31ff22

File tree

4 files changed

+167
-169
lines changed

4 files changed

+167
-169
lines changed

core/src/fmt/mod.rs

Lines changed: 137 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use crate::char::{EscapeDebugExtArgs, MAX_LEN_UTF8};
77
use crate::marker::{PhantomData, PointeeSized};
88
use crate::num::fmt as numfmt;
99
use crate::ops::Deref;
10-
use crate::{iter, result, str};
10+
use crate::ptr::NonNull;
11+
use crate::{iter, mem, result, str};
1112

1213
mod builders;
1314
#[cfg(not(no_fp_fmt_parse))]
@@ -616,15 +617,8 @@ impl<'a> Formatter<'a> {
616617
#[stable(feature = "rust1", since = "1.0.0")]
617618
#[derive(Copy, Clone)]
618619
pub struct Arguments<'a> {
619-
// Format string pieces to print.
620-
pieces: &'a [&'static str],
621-
622-
// Placeholder specs, or `None` if all specs are default (as in "{}{}").
623-
fmt: Option<&'a [rt::Placeholder]>,
624-
625-
// Dynamic arguments for interpolation, to be interleaved with string
626-
// pieces. (Every argument is preceded by a string piece.)
627-
args: &'a [rt::Argument<'a>],
620+
template: NonNull<u8>,
621+
args: NonNull<rt::Argument<'a>>,
628622
}
629623

630624
#[doc(hidden)]
@@ -636,20 +630,49 @@ impl<'a> Arguments<'a> {
636630
/// when using `format!`. Note: this is neither the lower nor upper bound.
637631
#[inline]
638632
pub fn estimated_capacity(&self) -> usize {
639-
let pieces_length: usize = self.pieces.iter().map(|x| x.len()).sum();
633+
if let Some(s) = self.as_str() {
634+
return s.len();
635+
}
636+
// Iterate over the template, counting the length of literal pieces.
637+
let mut length = 0usize;
638+
let mut starts_with_placeholder = false;
639+
let mut template = self.template;
640+
loop {
641+
// SAFETY: We can assume the template is valid.
642+
unsafe {
643+
let n = template.read();
644+
if n == 0 {
645+
// End of template.
646+
break;
647+
} else if n < 128 {
648+
// Literal string piece.
649+
length += n as usize;
650+
template = template.add(1 + n as usize);
651+
} else {
652+
// Placeholder piece.
653+
if length == 0 {
654+
starts_with_placeholder = true;
655+
}
656+
// Skip remainder of placeholder:
657+
let skip = (n & 1 == 1) as usize * 4
658+
+ (n & 2 == 2) as usize * 2
659+
+ (n & 4 == 4) as usize * 2
660+
+ (n & 8 == 8) as usize * 2;
661+
template = template.add(1 + skip as usize);
662+
}
663+
}
664+
}
640665

641-
if self.args.is_empty() {
642-
pieces_length
643-
} else if !self.pieces.is_empty() && self.pieces[0].is_empty() && pieces_length < 16 {
644-
// If the format string starts with an argument,
666+
if starts_with_placeholder && length < 16 {
667+
// If the format string starts with a placeholder,
645668
// don't preallocate anything, unless length
646-
// of pieces is significant.
669+
// of literal pieces is significant.
647670
0
648671
} else {
649-
// There are some arguments, so any additional push
672+
// There are some placeholders, so any additional push
650673
// will reallocate the string. To avoid that,
651674
// we're "pre-doubling" the capacity here.
652-
pieces_length.checked_mul(2).unwrap_or(0)
675+
length.wrapping_mul(2)
653676
}
654677
}
655678
}
@@ -702,10 +725,20 @@ impl<'a> Arguments<'a> {
702725
#[must_use]
703726
#[inline]
704727
pub const fn as_str(&self) -> Option<&'static str> {
705-
match (self.pieces, self.args) {
706-
([], []) => Some(""),
707-
([s], []) => Some(s),
708-
_ => None,
728+
// SAFETY: During const eval, `self.args` must have come from a usize,
729+
// not a pointer, because that's the only way to creat a fmt::Arguments in const.
730+
// Outside const eval, transmuting a pointer to a usize is fine.
731+
let bits: usize = unsafe { mem::transmute(self.args) };
732+
if bits & 1 == 1 {
733+
// SAFETY: This fmt::Arguments stores a &'static str.
734+
Some(unsafe {
735+
str::from_utf8_unchecked(crate::slice::from_raw_parts(
736+
self.template.as_ptr(),
737+
bits >> 1,
738+
))
739+
})
740+
} else {
741+
None
709742
}
710743
}
711744

@@ -1448,86 +1481,96 @@ pub trait UpperExp: PointeeSized {
14481481
///
14491482
/// [`write!`]: crate::write!
14501483
#[stable(feature = "rust1", since = "1.0.0")]
1451-
pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result {
1452-
let mut formatter = Formatter::new(output, FormattingOptions::new());
1453-
let mut idx = 0;
1454-
1455-
match args.fmt {
1456-
None => {
1457-
// We can use default formatting parameters for all arguments.
1458-
for (i, arg) in args.args.iter().enumerate() {
1459-
// SAFETY: args.args and args.pieces come from the same Arguments,
1460-
// which guarantees the indexes are always within bounds.
1461-
let piece = unsafe { args.pieces.get_unchecked(i) };
1462-
if !piece.is_empty() {
1463-
formatter.buf.write_str(*piece)?;
1464-
}
1465-
1466-
// SAFETY: There are no formatting parameters and hence no
1467-
// count arguments.
1468-
unsafe {
1469-
arg.fmt(&mut formatter)?;
1470-
}
1471-
idx += 1;
1472-
}
1473-
}
1474-
Some(fmt) => {
1475-
// Every spec has a corresponding argument that is preceded by
1476-
// a string piece.
1477-
for (i, arg) in fmt.iter().enumerate() {
1478-
// SAFETY: fmt and args.pieces come from the same Arguments,
1479-
// which guarantees the indexes are always within bounds.
1480-
let piece = unsafe { args.pieces.get_unchecked(i) };
1481-
if !piece.is_empty() {
1482-
formatter.buf.write_str(*piece)?;
1483-
}
1484-
// SAFETY: arg and args.args come from the same Arguments,
1485-
// which guarantees the indexes are always within bounds.
1486-
unsafe { run(&mut formatter, arg, args.args) }?;
1487-
idx += 1;
1488-
}
1489-
}
1484+
pub fn write(output: &mut dyn Write, fmt: Arguments<'_>) -> Result {
1485+
if let Some(s) = fmt.as_str() {
1486+
return output.write_str(s);
14901487
}
14911488

1492-
// There can be only one trailing string piece left.
1493-
if let Some(piece) = args.pieces.get(idx) {
1494-
formatter.buf.write_str(*piece)?;
1495-
}
1496-
1497-
Ok(())
1498-
}
1489+
let mut template = fmt.template;
1490+
let args = fmt.args;
14991491

1500-
unsafe fn run(fmt: &mut Formatter<'_>, arg: &rt::Placeholder, args: &[rt::Argument<'_>]) -> Result {
1501-
let (width, precision) =
1502-
// SAFETY: arg and args come from the same Arguments,
1503-
// which guarantees the indexes are always within bounds.
1504-
unsafe { (getcount(args, &arg.width), getcount(args, &arg.precision)) };
1492+
let mut arg_index = 0;
15051493

1506-
let options = FormattingOptions { flags: arg.flags, width, precision };
1494+
// This must match the encoding from `expand_format_args` in
1495+
// compiler/rustc_ast_lowering/src/format.rs.
1496+
loop {
1497+
// SAFETY: We can assume the template is valid.
1498+
let n = unsafe {
1499+
let n = template.read();
1500+
template = template.add(1);
1501+
n
1502+
};
15071503

1508-
// Extract the correct argument
1509-
debug_assert!(arg.position < args.len());
1510-
// SAFETY: arg and args come from the same Arguments,
1511-
// which guarantees its index is always within bounds.
1512-
let value = unsafe { args.get_unchecked(arg.position) };
1504+
if n == 0 {
1505+
// End of template.
1506+
return Ok(());
1507+
} else if n < 128 {
1508+
// Literal string piece of length `n`.
1509+
1510+
// SAFETY: We can assume the strings in the template are valid.
1511+
let s = unsafe {
1512+
let s = crate::str::from_raw_parts(template.as_ptr(), n as usize);
1513+
template = template.add(n as usize);
1514+
s
1515+
};
1516+
output.write_str(s)?;
1517+
} else if n == 128 {
1518+
// Placeholder for next argument with default options.
1519+
//
1520+
// Having this as a separate case improves performance for the common case.
1521+
1522+
// SAFETY: We can assume the template only refers to arguments that exist.
1523+
unsafe {
1524+
args.add(arg_index)
1525+
.as_ref()
1526+
.fmt(&mut Formatter::new(output, FormattingOptions::new()))?;
1527+
}
1528+
arg_index += 1;
1529+
} else {
1530+
// Placeholder with custom options.
15131531

1514-
// Set all the formatting options.
1515-
fmt.options = options;
1532+
let mut opt = FormattingOptions::new();
15161533

1517-
// Then actually do some printing
1518-
// SAFETY: this is a placeholder argument.
1519-
unsafe { value.fmt(fmt) }
1520-
}
1534+
// SAFETY: We can assume the template is valid.
1535+
unsafe {
1536+
if n & 1 != 0 {
1537+
opt.flags = u32::from_le_bytes(template.cast_array().read());
1538+
template = template.add(4);
1539+
}
1540+
if n & 2 != 0 {
1541+
opt.width = u16::from_le_bytes(template.cast_array().read());
1542+
template = template.add(2);
1543+
}
1544+
if n & 4 != 0 {
1545+
opt.precision = u16::from_le_bytes(template.cast_array().read());
1546+
template = template.add(2);
1547+
}
1548+
if n & 8 != 0 {
1549+
arg_index = usize::from(u16::from_le_bytes(template.cast_array().read()));
1550+
template = template.add(2);
1551+
}
1552+
}
1553+
if n & 16 != 0 {
1554+
// Dynamic width from a usize argument.
1555+
// SAFETY: We can assume the template only refers to arguments that exist.
1556+
unsafe {
1557+
opt.width = args.add(opt.width as usize).as_ref().as_u16().unwrap_unchecked();
1558+
}
1559+
}
1560+
if n & 32 != 0 {
1561+
// Dynamic precision from a usize argument.
1562+
// SAFETY: We can assume the template only refers to arguments that exist.
1563+
unsafe {
1564+
opt.precision =
1565+
args.add(opt.precision as usize).as_ref().as_u16().unwrap_unchecked();
1566+
}
1567+
}
15211568

1522-
unsafe fn getcount(args: &[rt::Argument<'_>], cnt: &rt::Count) -> u16 {
1523-
match *cnt {
1524-
rt::Count::Is(n) => n,
1525-
rt::Count::Implied => 0,
1526-
rt::Count::Param(i) => {
1527-
debug_assert!(i < args.len());
1528-
// SAFETY: cnt and args come from the same Arguments,
1529-
// which guarantees this index is always within bounds.
1530-
unsafe { args.get_unchecked(i).as_u16().unwrap_unchecked() }
1569+
// SAFETY: We can assume the template only refers to arguments that exist.
1570+
unsafe {
1571+
args.add(arg_index).as_ref().fmt(&mut Formatter::new(output, opt))?;
1572+
}
1573+
arg_index += 1;
15311574
}
15321575
}
15331576
}

core/src/fmt/rt.rs

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,9 @@
88
99
use super::*;
1010
use crate::hint::unreachable_unchecked;
11+
use crate::mem;
1112
use crate::ptr::NonNull;
1213

13-
#[lang = "format_placeholder"]
14-
#[derive(Copy, Clone)]
15-
pub struct Placeholder {
16-
pub position: usize,
17-
pub flags: u32,
18-
pub precision: Count,
19-
pub width: Count,
20-
}
21-
22-
/// Used by [width](https://doc.rust-lang.org/std/fmt/#width)
23-
/// and [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
24-
#[lang = "format_count"]
25-
#[derive(Copy, Clone)]
26-
pub enum Count {
27-
/// Specified with a literal number, stores the value
28-
Is(u16),
29-
/// Specified using `$` and `*` syntaxes, stores the index into `args`
30-
Param(usize),
31-
/// Not specified
32-
Implied,
33-
}
34-
3514
#[derive(Copy, Clone)]
3615
enum ArgumentType<'a> {
3716
Placeholder {
@@ -56,6 +35,7 @@ enum ArgumentType<'a> {
5635
/// precision and width.
5736
#[lang = "format_argument"]
5837
#[derive(Copy, Clone)]
38+
#[repr(align(2))]
5939
pub struct Argument<'a> {
6040
ty: ArgumentType<'a>,
6141
}
@@ -187,52 +167,33 @@ impl Argument<'_> {
187167

188168
/// Used by the format_args!() macro to create a fmt::Arguments object.
189169
#[doc(hidden)]
190-
#[unstable(feature = "fmt_internals", issue = "none")]
191170
#[rustc_diagnostic_item = "FmtArgumentsNew"]
192171
impl<'a> Arguments<'a> {
193172
#[inline]
194-
pub const fn new_const<const N: usize>(pieces: &'a [&'static str; N]) -> Self {
195-
const { assert!(N <= 1) };
196-
Arguments { pieces, fmt: None, args: &[] }
173+
pub unsafe fn new<const N: usize, const M: usize>(
174+
template: &'a [u8; N],
175+
args: &'a [rt::Argument<'a>; M],
176+
) -> Arguments<'a> {
177+
// SAFETY: ...
178+
unsafe { Arguments { template: mem::transmute(template), args: mem::transmute(args) } }
197179
}
198180

199-
/// When using the format_args!() macro, this function is used to generate the
200-
/// Arguments structure.
201-
///
202-
/// This function should _not_ be const, to make sure we don't accept
203-
/// format_args!() and panic!() with arguments in const, even when not evaluated:
204-
///
205-
/// ```compile_fail,E0015
206-
/// const _: () = if false { panic!("a {}", "a") };
207-
/// ```
208181
#[inline]
209-
pub fn new_v1<const P: usize, const A: usize>(
210-
pieces: &'a [&'static str; P],
211-
args: &'a [rt::Argument<'a>; A],
212-
) -> Arguments<'a> {
213-
const { assert!(P >= A && P <= A + 1, "invalid args") }
214-
Arguments { pieces, fmt: None, args }
182+
pub const fn from_str(s: &'static str) -> Arguments<'a> {
183+
// SAFETY: This is the "static str" representation of fmt::Arguments.
184+
unsafe {
185+
Arguments {
186+
template: mem::transmute(s.as_ptr()),
187+
args: mem::transmute(s.len() << 1 | 1),
188+
}
189+
}
215190
}
216191

217-
/// Specifies nonstandard formatting parameters.
218-
///
219-
/// SAFETY: the following invariants must be held:
220-
/// 1. The `pieces` slice must be at least as long as `fmt`.
221-
/// 2. Every `rt::Placeholder::position` value within `fmt` must be a valid index of `args`.
222-
/// 3. Every `rt::Count::Param` within `fmt` must contain a valid index of `args`.
223-
///
224-
/// This function should _not_ be const, to make sure we don't accept
225-
/// format_args!() and panic!() with arguments in const, even when not evaluated:
226-
///
227-
/// ```compile_fail,E0015
228-
/// const _: () = if false { panic!("a {:1}", "a") };
229-
/// ```
230-
#[inline]
231-
pub unsafe fn new_v1_formatted(
232-
pieces: &'a [&'static str],
233-
args: &'a [rt::Argument<'a>],
234-
fmt: &'a [rt::Placeholder],
235-
) -> Arguments<'a> {
236-
Arguments { pieces, fmt: Some(fmt), args }
192+
// Same as `from_str`, but not const.
193+
// Used by format_args!() expansion when arguments are inlined,
194+
// e.g. format_args!("{}", 123), which is not allowed in const.
195+
#[inline]
196+
pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> {
197+
Arguments::from_str(s)
237198
}
238199
}

0 commit comments

Comments
 (0)