Skip to content

Commit ee7ab9a

Browse files
committed
core and std: add format_arg!() to speed up write*!() and print*!()
Before this patch, `write!(wr, "foo")` was not as fast as `wr.write_str("foo")` because the underlying write_fmt has some overhead in order to work with multiple arguments.
1 parent c5db290 commit ee7ab9a

File tree

7 files changed

+155
-11
lines changed

7 files changed

+155
-11
lines changed

src/libcollections/macros.rs

+13
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,21 @@ macro_rules! vec {
3333
/// format!("hello {}", "world!");
3434
/// format!("x = {}, y = {y}", 10, y = 30);
3535
/// ```
36+
#[cfg(stage0)]
3637
#[macro_export]
3738
#[stable(feature = "rust1", since = "1.0.0")]
3839
macro_rules! format {
3940
($($arg:tt)*) => ($crate::fmt::format(format_args!($($arg)*)))
4041
}
42+
43+
#[cfg(not(stage0))]
44+
#[macro_export]
45+
#[stable(feature = "rust1", since = "1.0.0")]
46+
macro_rules! format {
47+
($fmt:expr) => {
48+
format_arg!($fmt).to_string()
49+
};
50+
($fmt:expr, $($arg:tt)+) => {
51+
$crate::fmt::format(format_args!($fmt, $($arg)*))
52+
}
53+
}

src/libcore/macros.rs

+12
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,23 @@ macro_rules! try {
177177
/// write!(&mut w, "test");
178178
/// write!(&mut w, "formatted {}", "arguments");
179179
/// ```
180+
#[cfg(stage0)]
180181
#[macro_export]
181182
macro_rules! write {
182183
($dst:expr, $($arg:tt)*) => ((&mut *$dst).write_fmt(format_args!($($arg)*)))
183184
}
184185

186+
#[cfg(not(stage0))]
187+
#[macro_export]
188+
macro_rules! write {
189+
($dst:expr, $fmt:expr) => {
190+
(&mut *$dst).write_str(format_arg!($fmt))
191+
};
192+
($dst:expr, $fmt:expr, $($arg:tt)+) => {
193+
(&mut *$dst).write_fmt(format_args!($fmt, $($arg)*))
194+
};
195+
}
196+
185197
/// Equivalent to the `write!` macro, except that a newline is appended after
186198
/// the message is written.
187199
#[macro_export]

src/libstd/macros.rs

+48
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,25 @@ macro_rules! format {
7979

8080
/// Equivalent to the `println!` macro except that a newline is not printed at
8181
/// the end of the message.
82+
#[cfg(stage0)]
8283
#[macro_export]
8384
#[stable(feature = "rust1", since = "1.0.0")]
8485
macro_rules! print {
8586
($($arg:tt)*) => ($crate::old_io::stdio::print_args(format_args!($($arg)*)))
8687
}
8788

89+
#[cfg(not(stage0))]
90+
#[macro_export]
91+
#[stable(feature = "rust1", since = "1.0.0")]
92+
macro_rules! print {
93+
($fmt:expr) => {
94+
$crate::old_io::stdio::print(format_arg!($fmt))
95+
};
96+
($fmt:expr, $($arg:tt)+) => {
97+
$crate::old_io::stdio::print_args(format_args!($fmt, $($arg)*))
98+
};
99+
}
100+
88101
/// Macro for printing to a task's stdout handle.
89102
///
90103
/// Each task can override its stdout handle via `std::old_io::stdio::set_stdout`.
@@ -97,12 +110,25 @@ macro_rules! print {
97110
/// println!("hello there!");
98111
/// println!("format {} arguments", "some");
99112
/// ```
113+
#[cfg(stage0)]
100114
#[macro_export]
101115
#[stable(feature = "rust1", since = "1.0.0")]
102116
macro_rules! println {
103117
($($arg:tt)*) => ($crate::old_io::stdio::println_args(format_args!($($arg)*)))
104118
}
105119

120+
#[cfg(not(stage0))]
121+
#[macro_export]
122+
#[stable(feature = "rust1", since = "1.0.0")]
123+
macro_rules! println {
124+
($fmt:expr) => {
125+
$crate::old_io::stdio::println(format_arg!($fmt))
126+
};
127+
($fmt:expr, $($arg:tt)*) => {
128+
$crate::old_io::stdio::println_args(format_args!($fmt, $($arg)*))
129+
};
130+
}
131+
106132
/// Helper macro for unwrapping `Result` values while returning early with an
107133
/// error if the value of the expression is `Err`. For more information, see
108134
/// `std::io`.
@@ -185,6 +211,28 @@ macro_rules! log {
185211
/// into libsyntax itself.
186212
#[cfg(dox)]
187213
pub mod builtin {
214+
/// Parse a `&'static str` with a compatible format to `format_args!()`.
215+
///
216+
/// This macro produces a value of type `&'static str`, and is intended to
217+
/// be used by the other formatting macros (`format!`, `write!`, `println!`)
218+
/// are proxied through this one.
219+
///
220+
/// For more information, see the documentation in `std::fmt`.
221+
///
222+
/// # Example
223+
///
224+
/// ```rust
225+
/// use std::fmt;
226+
///
227+
/// let s = format_arg!("hello");
228+
/// assert_eq!(s, format!("hello"));
229+
///
230+
/// ```
231+
#[macro_export]
232+
macro_rules! format_arg { ($fmt:expr) => ({
233+
/* compiler built-in */
234+
}) }
235+
188236
/// The core macro for formatted string creation & output.
189237
///
190238
/// This macro produces a value of type `fmt::Arguments`. This value can be

src/libsyntax/ext/base.rs

+3
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,9 @@ fn initial_syntax_expander_table(ecfg: &expand::ExpansionConfig) -> SyntaxEnv {
447447

448448
let mut syntax_expanders = SyntaxEnv::new();
449449
syntax_expanders.insert(intern("macro_rules"), MacroRulesTT);
450+
syntax_expanders.insert(intern("format_arg"),
451+
builtin_normal_expander(
452+
ext::format::expand_format_arg));
450453
syntax_expanders.insert(intern("format_args"),
451454
builtin_normal_expander(
452455
ext::format::expand_format_args));

src/libsyntax/ext/format.rs

+63-9
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct Context<'a, 'b:'a> {
5757
/// Collection of the compiled `rt::Argument` structures
5858
pieces: Vec<P<ast::Expr>>,
5959
/// Collection of string literals
60-
str_pieces: Vec<P<ast::Expr>>,
60+
str_pieces: Vec<(Span, token::InternedString)>,
6161
/// Stays `true` if all formatting parameters are default (as in "{}{}").
6262
all_pieces_simple: bool,
6363

@@ -335,11 +335,11 @@ impl<'a, 'b> Context<'a, 'b> {
335335
}
336336

337337
/// Translate the accumulated string literals to a literal expression
338-
fn trans_literal_string(&mut self) -> P<ast::Expr> {
338+
fn trans_string(&mut self) -> (Span, token::InternedString) {
339339
let sp = self.fmtsp;
340340
let s = token::intern_and_get_ident(&self.literal[]);
341341
self.literal.clear();
342-
self.ecx.expr_str(sp, s)
342+
(sp, s)
343343
}
344344

345345
/// Translate a `parse::Piece` to a static `rt::Argument` or append
@@ -475,10 +475,15 @@ impl<'a, 'b> Context<'a, 'b> {
475475
self.ecx.ty_ident(self.fmtsp, self.ecx.ident_of("str")),
476476
Some(static_lifetime),
477477
ast::MutImmutable);
478+
479+
let str_pieces = self.str_pieces.iter()
480+
.map(|&(sp, ref s)| self.ecx.expr_str(sp, s.clone()))
481+
.collect();
482+
478483
let pieces = Context::static_array(self.ecx,
479484
"__STATIC_FMTSTR",
480485
piece_ty,
481-
self.str_pieces);
486+
str_pieces);
482487

483488

484489
// Right now there is a bug such that for the expression:
@@ -627,6 +632,43 @@ impl<'a, 'b> Context<'a, 'b> {
627632
}
628633
}
629634

635+
pub fn expand_format_arg<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
636+
tts: &[ast::TokenTree])
637+
-> Box<base::MacResult+'cx> {
638+
639+
match parse_args(ecx, sp, tts) {
640+
Some((efmt, args, order, names)) => {
641+
if !args.is_empty() || !order.is_empty() || !names.is_empty() {
642+
ecx.span_err(sp,
643+
&format!("requires no arguments, found {}",
644+
args.len() + order.len()));
645+
646+
DummyResult::expr(sp)
647+
} else {
648+
MacExpr::new(expand_preparsed_format_arg(ecx, sp, efmt))
649+
}
650+
}
651+
None => DummyResult::expr(sp)
652+
}
653+
}
654+
655+
/// Take the various parts of `format_arg!(efmt)`
656+
/// and construct the appropriate formatting expression.
657+
pub fn expand_preparsed_format_arg(ecx: &mut ExtCtxt, sp: Span,
658+
efmt: P<ast::Expr>)
659+
-> P<ast::Expr> {
660+
match parse_format_args(ecx, sp, efmt, Vec::new(), Vec::new(), HashMap::new()) {
661+
Some(cx) => {
662+
let mut s = String::new();
663+
for &(_, ref part) in cx.str_pieces.iter() {
664+
s.push_str(part.as_slice());
665+
}
666+
cx.ecx.expr_str(sp, token::intern_and_get_ident(&s[]))
667+
}
668+
None => DummyResult::raw_expr(sp),
669+
}
670+
}
671+
630672
pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt, sp: Span,
631673
tts: &[ast::TokenTree])
632674
-> Box<base::MacResult+'cx> {
@@ -648,6 +690,18 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
648690
name_ordering: Vec<String>,
649691
names: HashMap<String, P<ast::Expr>>)
650692
-> P<ast::Expr> {
693+
match parse_format_args(ecx, sp, efmt, args, name_ordering, names) {
694+
Some(cx) => cx.into_expr(),
695+
None => DummyResult::raw_expr(sp),
696+
}
697+
}
698+
699+
fn parse_format_args<'a, 'b: 'a>(ecx: &'a mut ExtCtxt<'b>, sp: Span,
700+
efmt: P<ast::Expr>,
701+
args: Vec<P<ast::Expr>>,
702+
name_ordering: Vec<String>,
703+
names: HashMap<String, P<ast::Expr>>)
704+
-> Option<Context<'a, 'b>> {
651705
let arg_types: Vec<_> = (0..args.len()).map(|_| None).collect();
652706
let mut cx = Context {
653707
ecx: ecx,
@@ -670,7 +724,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
670724
efmt,
671725
"format argument must be a string literal.") {
672726
Some((fmt, _)) => fmt,
673-
None => return DummyResult::raw_expr(sp)
727+
None => return None
674728
};
675729

676730
let mut parser = parse::Parser::new(&fmt);
@@ -682,7 +736,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
682736
cx.verify_piece(&piece);
683737
match cx.trans_piece(&piece) {
684738
Some(piece) => {
685-
let s = cx.trans_literal_string();
739+
let s = cx.trans_string();
686740
cx.str_pieces.push(s);
687741
cx.pieces.push(piece);
688742
}
@@ -695,10 +749,10 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
695749
if !parser.errors.is_empty() {
696750
cx.ecx.span_err(cx.fmtsp, &format!("invalid format string: {}",
697751
parser.errors.remove(0))[]);
698-
return DummyResult::raw_expr(sp);
752+
return None;
699753
}
700754
if !cx.literal.is_empty() {
701-
let s = cx.trans_literal_string();
755+
let s = cx.trans_string();
702756
cx.str_pieces.push(s);
703757
}
704758

@@ -714,5 +768,5 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
714768
}
715769
}
716770

717-
cx.into_expr()
771+
Some(cx)
718772
}

src/test/compile-fail/ifmt-bad-format-args.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
// except according to those terms.
1010

1111
fn main() {
12+
format_arg!(); //~ ERROR: requires at least a format string argument
13+
format_arg!("{}", "bar"); //~ ERROR: requires no arguments, found 1.
14+
1215
format_args!(); //~ ERROR: requires at least a format string argument
1316
format_args!(|| {}); //~ ERROR: must be a string literal
1417
}

src/test/run-pass/ifmt.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ pub fn main() {
157157
format!("{}", a);
158158
}
159159

160+
test_format_arg();
160161
test_format_args();
161162

162163
// test that trailing commas are acceptable
@@ -172,13 +173,17 @@ fn test_write() {
172173
write!(&mut buf, "{}", 3);
173174
{
174175
let w = &mut buf;
176+
write!(w, "{{");
177+
write!(w, "foo");
175178
write!(w, "{foo}", foo=4);
176179
write!(w, "{}", "hello");
180+
writeln!(w, "line");
177181
writeln!(w, "{}", "line");
178182
writeln!(w, "{foo}", foo="bar");
183+
writeln!(w, "}}");
179184
}
180185

181-
t!(buf, "34helloline\nbar\n");
186+
t!(buf, "{{foo34helloline\nbar\n}}\n");
182187
}
183188

184189
// Just make sure that the macros are defined, there's not really a lot that we
@@ -198,12 +203,18 @@ fn test_format_args() {
198203
let mut buf = String::new();
199204
{
200205
let w = &mut buf;
206+
write!(w, "{{");
201207
write!(w, "{}", format_args!("{}", 1));
202208
write!(w, "{}", format_args!("test"));
203209
write!(w, "{}", format_args!("{test}", test=3));
210+
write!(w, "}}");
204211
}
205212
let s = buf;
206-
t!(s, "1test3");
213+
t!(s, "{1test3}");
214+
215+
t!(format_args!("hello"), "hello");
216+
t!(format_args!("{{"), "{");
217+
t!(format_args!("}}"), "}");
207218

208219
let s = fmt::format(format_args!("hello {}", "world"));
209220
t!(s, "hello world");

0 commit comments

Comments
 (0)