Skip to content

Commit d79c92e

Browse files
committed
Optimize fmt::Arguments for the static string case.
fmt::Arguments can now store a &str without any indirection and additional storage: There is now a fmt::Arguments::from_str() function.
1 parent ff114f4 commit d79c92e

File tree

6 files changed

+107
-100
lines changed

6 files changed

+107
-100
lines changed

compiler/rustc_ast_lowering/src/format.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,19 @@ fn expand_format_args<'hir>(
401401
let default_options = 0xE000_0020_0000_0000;
402402
let mut implicit_arg_index = 0;
403403

404-
for (i, piece) in fmt.template.iter().enumerate() {
404+
let template = if fmt.template.is_empty() {
405+
// Treat empty templates as a single literal piece (with an empty string),
406+
// so we produce `from_str("")` for those.
407+
&[FormatArgsPiece::Literal(sym::empty)][..]
408+
} else {
409+
&fmt.template[..]
410+
};
411+
412+
for (i, piece) in template.iter().enumerate() {
405413
match piece {
406414
&FormatArgsPiece::Literal(sym) => {
407-
assert!(!sym.is_empty());
408415
// Coalesce adjacent literal pieces.
409-
if let Some(FormatArgsPiece::Literal(_)) = fmt.template.get(i + 1) {
416+
if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) {
410417
incomplete_lit.push_str(sym.as_str());
411418
continue;
412419
}
@@ -420,6 +427,26 @@ fn expand_format_args<'hir>(
420427
(sym, len)
421428
};
422429

430+
// If this is the last piece and was the only piece, that means
431+
// there are no placeholders and the entire format string is just a literal.
432+
//
433+
// In that case, we don't need an array of `Piece`s: we can just use `from_str`.
434+
if i + 1 == template.len() && pieces.is_empty() {
435+
// Generate:
436+
// <core::fmt::Arguments>::from_str("meow")
437+
let from_str = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
438+
macsp,
439+
hir::LangItem::FormatArguments,
440+
if allow_const { sym::from_str } else { sym::from_str_nonconst },
441+
));
442+
let s = ctx.expr_str(fmt.span, sym);
443+
let args = ctx.arena.alloc_from_iter([s]);
444+
return hir::ExprKind::Call(from_str, args);
445+
}
446+
447+
// Producing a `Piece::num(0)` would be problematic, as that is the terminator.
448+
assert!(len > 0);
449+
423450
// ```
424451
// Piece::num(4),
425452
// ```
@@ -456,7 +483,7 @@ fn expand_format_args<'hir>(
456483
// placeholder is implied by two consequtive string pieces.
457484
if bits == default_options + implicit_arg_index {
458485
if let (Some(FormatArgsPiece::Literal(_)), Some(FormatArgsPiece::Literal(_))) =
459-
(fmt.template.get(i.wrapping_sub(1)), fmt.template.get(i + 1))
486+
(template.get(i.wrapping_sub(1)), template.get(i + 1))
460487
{
461488
implicit_arg_index += 1;
462489
continue;
@@ -516,18 +543,6 @@ fn expand_format_args<'hir>(
516543

517544
let arguments = fmt.arguments.all_args();
518545

519-
if allow_const && arguments.is_empty() && argmap.is_empty() {
520-
// Generate:
521-
// <core::fmt::Arguments>::new_const(template)
522-
let new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
523-
macsp,
524-
hir::LangItem::FormatArguments,
525-
sym::new_const,
526-
));
527-
let new_args = ctx.arena.alloc_from_iter([template]);
528-
return hir::ExprKind::Call(new, new_args);
529-
}
530-
531546
let (let_statements, args) = if arguments.is_empty() {
532547
// Generate:
533548
// []

compiler/rustc_const_eval/src/check_consts/ops.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,9 @@ fn build_error_for_const_call<'tcx>(
345345
note_trait_if_possible(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, span));
346346
err
347347
}
348-
_ if tcx.opt_parent(callee) == tcx.get_diagnostic_item(sym::FmtArgumentsNew) => {
348+
_ if tcx.is_diagnostic_item(sym::FmtTemplateNew, callee)
349+
|| tcx.is_diagnostic_item(sym::FmtArgumentsFromStrNonconst, callee) =>
350+
{
349351
ccx.dcx().create_err(errors::NonConstFmtMacroCall {
350352
span,
351353
kind: ccx.const_kind(),

compiler/rustc_span/src/symbol.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ symbols! {
235235
Error,
236236
File,
237237
FileType,
238-
FmtArgumentsNew,
238+
FmtArgumentsFromStrNonconst,
239+
FmtTemplateNew,
239240
FmtWrite,
240241
Fn,
241242
FnMut,
@@ -1103,7 +1104,9 @@ symbols! {
11031104
from_output,
11041105
from_residual,
11051106
from_size_align_unchecked,
1107+
from_str,
11061108
from_str_method,
1109+
from_str_nonconst,
11071110
from_u16,
11081111
from_usize,
11091112
from_yeet,

library/core/src/fmt/mod.rs

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -630,45 +630,39 @@ impl<'a> Arguments<'a> {
630630
/// when using `format!`. Note: this is neither the lower nor upper bound.
631631
#[inline]
632632
pub fn estimated_capacity(&self) -> usize {
633+
if let Some(s) = self.as_str() {
634+
return s.len();
635+
}
633636
// Iterate over the template, counting the length of literal pieces.
634637
let mut length = 0usize;
635638
let mut starts_with_placeholder = false;
636639
let mut template = self.template;
637-
let mut has_placeholders = false;
638640
loop {
639641
// SAFETY: We can assume the template is valid.
640642
unsafe {
641-
let n = template.next().i;
643+
let n = template.next_piece().i;
642644
if n == 0 {
643645
// End of template.
644646
break;
645647
} else if n <= isize::MAX as _ {
646648
// Literal string piece.
647-
if length != 0 {
648-
// More than one literal string piece means we have placeholders.
649-
has_placeholders = true;
650-
}
651649
length += n as usize;
652-
let _ptr = template.next(); // Skip the string pointer.
650+
let _ptr = template.next_piece(); // Skip the string pointer.
653651
} else {
654652
// Placeholder piece.
655653
if length == 0 {
656654
starts_with_placeholder = true;
657655
}
658-
has_placeholders = true;
659656
// Skip remainder of placeholder:
660657
#[cfg(target_pointer_width = "32")]
661-
let _ = template.next();
658+
let _ = template.next_piece();
662659
#[cfg(target_pointer_width = "16")]
663-
let _ = (template.next(), template.next(), template.next());
660+
let _ = (template.next_piece(), template.next_piece(), template.next_piece());
664661
}
665662
}
666663
}
667664

668-
if !has_placeholders {
669-
// If the template has no placeholders, we know the length exactly.
670-
length
671-
} else if starts_with_placeholder && length < 16 {
665+
if starts_with_placeholder && length < 16 {
672666
// If the format string starts with a placeholder,
673667
// don't preallocate anything, unless length
674668
// of literal pieces is significant.
@@ -730,27 +724,17 @@ impl<'a> Arguments<'a> {
730724
#[must_use]
731725
#[inline]
732726
pub const fn as_str(&self) -> Option<&'static str> {
733-
let mut template = self.template;
734-
// SAFETY: We can assume the template is valid.
735-
let n = unsafe { template.next().i };
736-
if n == 0 {
737-
// The template is empty.
738-
return Some("");
739-
}
740-
if n <= isize::MAX as _ {
741-
// Template starts with a string piece.
742-
// SAFETY: We can assume the template is valid.
743-
unsafe {
744-
let ptr = template.next().p;
745-
if template.next().i == 0 {
746-
// The template has only one piece.
747-
return Some(str::from_utf8_unchecked(crate::slice::from_raw_parts(
748-
ptr, n as usize,
749-
)));
750-
}
751-
}
727+
if let Some(len) = self.template.as_str_len() {
728+
// SAFETY: This fmt::Arguments stores a &'static str.
729+
Some(unsafe {
730+
str::from_utf8_unchecked(crate::slice::from_raw_parts(
731+
self.args.cast().as_ptr(),
732+
len,
733+
))
734+
})
735+
} else {
736+
None
752737
}
753-
None
754738
}
755739

756740
/// Same as [`Arguments::as_str`], but will only return `Some(s)` if it can be determined at compile time.
@@ -1493,6 +1477,10 @@ pub trait UpperExp: PointeeSized {
14931477
/// [`write!`]: crate::write!
14941478
#[stable(feature = "rust1", since = "1.0.0")]
14951479
pub fn write(output: &mut dyn Write, fmt: Arguments<'_>) -> Result {
1480+
if let Some(s) = fmt.as_str() {
1481+
return output.write_str(s);
1482+
}
1483+
14961484
let mut template = fmt.template;
14971485
let args = fmt.args;
14981486

@@ -1501,7 +1489,7 @@ pub fn write(output: &mut dyn Write, fmt: Arguments<'_>) -> Result {
15011489

15021490
loop {
15031491
// SAFETY: We can assume the template is valid.
1504-
let n = unsafe { template.next().i };
1492+
let n = unsafe { template.next_piece().i };
15051493
if n == 0 {
15061494
// End of template.
15071495
return Ok(());
@@ -1518,7 +1506,7 @@ pub fn write(output: &mut dyn Write, fmt: Arguments<'_>) -> Result {
15181506
unsafe { arg.fmt(&mut Formatter::new(output, options)) }?;
15191507
}
15201508
// SAFETY: We can assume the strings in the template are valid.
1521-
let s = unsafe { crate::str::from_raw_parts(template.next().p, n as usize) };
1509+
let s = unsafe { crate::str::from_raw_parts(template.next_piece().p, n as usize) };
15221510
output.write_str(s)?;
15231511
last_piece_was_str = true;
15241512
} else {
@@ -1527,13 +1515,13 @@ pub fn write(output: &mut dyn Write, fmt: Arguments<'_>) -> Result {
15271515
let (high, low) = ((n >> 32) as u32, n as u32);
15281516
#[cfg(target_pointer_width = "32")]
15291517
// SAFETY: We can assume the template is valid.
1530-
let (high, low) = (n as u32, unsafe { template.next().i } as u32);
1518+
let (high, low) = (n as u32, unsafe { template.next_piece().i } as u32);
15311519
#[cfg(target_pointer_width = "16")]
15321520
// SAFETY: We can assume the template is valid.
15331521
let (high, low) = unsafe {
15341522
(
1535-
(n as u32) << 16 | template.next().i as u32,
1536-
(template.next().i as u32) << 16 | template.next().i as u32,
1523+
(n as u32) << 16 | template.next_piece().i as u32,
1524+
(template.next_piece().i as u32) << 16 | template.next_piece().i as u32,
15371525
)
15381526
};
15391527
let arg_index = (low & 0x3FF) as usize;

library/core/src/fmt/rt.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use super::*;
1010
use crate::hint::unreachable_unchecked;
1111
use crate::marker::PhantomData;
12+
use crate::num::NonZeroUsize;
1213
use crate::ptr::NonNull;
1314

1415
#[lang = "format_template"]
@@ -23,12 +24,29 @@ unsafe impl Sync for Template<'_> {}
2324

2425
impl<'a> Template<'a> {
2526
#[inline]
26-
pub const unsafe fn new<const N: usize>(pieces: &'a [rt::Piece; N]) -> Self {
27+
#[rustc_diagnostic_item = "FmtTemplateNew"]
28+
pub unsafe fn new<const N: usize>(pieces: &'a [rt::Piece; N]) -> Self {
2729
Self { pieces: NonNull::from_ref(pieces).cast(), lifetime: PhantomData }
2830
}
2931

3032
#[inline]
31-
pub const unsafe fn next(&mut self) -> Piece {
33+
pub const unsafe fn new_str_len(len: usize) -> Self {
34+
// SAFETY: We set the lowest bit, so it's nonzero.
35+
let bits = unsafe { NonZeroUsize::new_unchecked(len << 1 | 1) };
36+
Self { pieces: NonNull::without_provenance(bits), lifetime: PhantomData }
37+
}
38+
39+
#[inline]
40+
pub const fn as_str_len(self) -> Option<usize> {
41+
// SAFETY: During const eval, `self.pieces` must have come from a usize, not a pointer,
42+
// because `new()` above is not const.
43+
// Outside const eval, transmuting a pointer to a usize is fine.
44+
let bits = unsafe { crate::mem::transmute::<_, usize>(self.pieces) };
45+
if bits & 1 == 1 { Some(bits >> 1) } else { None }
46+
}
47+
48+
#[inline]
49+
pub const unsafe fn next_piece(&mut self) -> Piece {
3250
// SAFETY: Guaranteed by caller.
3351
unsafe {
3452
let piece = *self.pieces.as_ref();
@@ -40,6 +58,7 @@ impl<'a> Template<'a> {
4058

4159
#[lang = "format_piece"]
4260
#[derive(Copy, Clone)]
61+
#[repr(align(2))]
4362
pub union Piece {
4463
pub i: usize,
4564
pub p: *const u8,
@@ -224,31 +243,30 @@ impl Argument<'_> {
224243

225244
/// Used by the format_args!() macro to create a fmt::Arguments object.
226245
#[doc(hidden)]
227-
#[rustc_diagnostic_item = "FmtArgumentsNew"]
228246
impl<'a> Arguments<'a> {
229247
#[inline]
230-
pub const fn new_const(template: rt::Template<'a>) -> Arguments<'a> {
231-
Arguments { template, args: NonNull::dangling() }
232-
}
233-
234-
#[inline]
235-
pub fn new<const N: usize>(
248+
pub const fn new<const N: usize>(
236249
template: rt::Template<'a>,
237250
args: &'a [rt::Argument<'a>; N],
238251
) -> Arguments<'a> {
239252
Arguments { template, args: NonNull::from_ref(args).cast() }
240253
}
241254

242-
// These two methods are used in library/core/src/panicking.rs to create a
243-
// `fmt::Arguments` for a `&'static str`.
244255
#[inline]
245-
pub(crate) const fn pieces_for_str(s: &'static str) -> [rt::Piece; 3] {
246-
[rt::Piece { i: s.len() as _ }, rt::Piece::str(s), rt::Piece { i: 0 }]
256+
pub const fn from_str(s: &'static str) -> Arguments<'a> {
257+
Arguments {
258+
// SAFETY: This is the "static str" representation of fmt::Arguments.
259+
template: unsafe { rt::Template::new_str_len(s.len()) },
260+
args: NonNull::from_ref(s).cast(),
261+
}
247262
}
248-
/// Safety: Only call this with the result of `pieces_for_str`.
263+
264+
// Same as `from_str`, but not const.
265+
// Used by format_args!() expansion when arguments are inlined,
266+
// e.g. format_args!("{}", 123), which is not allowed in const.
249267
#[inline]
250-
pub(crate) const unsafe fn from_pieces(p: &'a [rt::Piece; 3]) -> Self {
251-
// SAFETY: Guaranteed by caller.
252-
Self::new_const(unsafe { rt::Template::new(p) })
268+
#[rustc_diagnostic_item = "FmtArgumentsFromStrNonconst"]
269+
pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> {
270+
Arguments::from_str(s)
253271
}
254272
}

0 commit comments

Comments
 (0)