]> git.lizzy.rs Git - rust.git/commitdiff
Optimize for the most common cases of `format!`
authorPiotr Czarnecki <pioczarn@gmail.com>
Mon, 25 Aug 2014 13:26:18 +0000 (14:26 +0100)
committerPiotr Czarnecki <pioczarn@gmail.com>
Tue, 9 Sep 2014 19:34:41 +0000 (20:34 +0100)
Format specs are ignored and not stored in case they're all default.
Restore default formatting parameters during iteration.
Pass `None` instead of empty slices of format specs to take advantage
of non-nullable pointer optimization.

Generate a call to one of two functions of `fmt::Argument`.

src/libcore/fmt/mod.rs
src/libsyntax/ext/format.rs

index 9f64e1aa4928ef7ff3137f380e07046606af53bd..be75bfec32c86dd9fb172a5e42ce8dbbee0c39fd 100644 (file)
@@ -116,11 +116,25 @@ impl<'a> Arguments<'a> {
     #[cfg(not(stage0))]
     #[doc(hidden)] #[inline]
     pub unsafe fn new<'a>(pieces: &'static [&'static str],
-                          fmt: &'static [rt::Argument<'static>],
                           args: &'a [Argument<'a>]) -> Arguments<'a> {
         Arguments {
             pieces: mem::transmute(pieces),
-            fmt: mem::transmute(fmt),
+            fmt: None,
+            args: args
+        }
+    }
+
+    /// This function is used to specify nonstandard formatting parameters.
+    /// The `pieces` array must be at least as long as `fmt` to construct
+    /// a valid Arguments structure.
+    #[cfg(not(stage0))]
+    #[doc(hidden)] #[inline]
+    pub unsafe fn with_placeholders<'a>(pieces: &'static [&'static str],
+                                        fmt: &'static [rt::Argument<'static>],
+                                        args: &'a [Argument<'a>]) -> Arguments<'a> {
+        Arguments {
+            pieces: mem::transmute(pieces),
+            fmt: Some(mem::transmute(fmt)),
             args: args
         }
     }
@@ -144,8 +158,14 @@ pub unsafe fn new<'a>(fmt: &'static [rt::Piece<'static>],
 /// and `format` functions can be safely performed.
 #[cfg(not(stage0))]
 pub struct Arguments<'a> {
+    // Format string pieces to print.
     pieces: &'a [&'a str],
-    fmt: &'a [rt::Argument<'a>],
+
+    // Placeholder specs, or `None` if all specs are default (as in "{}{}").
+    fmt: Option<&'a [rt::Argument<'a>]>,
+
+    // Dynamic arguments for interpolation, to be interleaved with string
+    // pieces. (Every argument is preceded by a string piece.)
     args: &'a [Argument<'a>],
 }
 
@@ -276,6 +296,18 @@ pub fn $name<T: $trait_>(x: &T, fmt: &mut Formatter) -> Result {
     secret_upper_exp, UpperExp;
 }
 
+#[cfg(not(stage0))]
+static DEFAULT_ARGUMENT: rt::Argument<'static> = rt::Argument {
+    position: rt::ArgumentNext,
+    format: rt::FormatSpec {
+        fill: ' ',
+        align: rt::AlignUnknown,
+        flags: 0,
+        precision: rt::CountImplied,
+        width: rt::CountImplied,
+    }
+};
+
 /// The `write` function takes an output stream, a precompiled format string,
 /// and a list of arguments. The arguments will be formatted according to the
 /// specified format string into the output stream provided.
@@ -299,11 +331,25 @@ pub fn write(output: &mut FormatWriter, args: &Arguments) -> Result {
 
     let mut pieces = args.pieces.iter();
 
-    for arg in args.fmt.iter() {
-        try!(formatter.buf.write(pieces.next().unwrap().as_bytes()));
-        try!(formatter.run(arg));
+    match args.fmt {
+        None => {
+            // We can use default formatting parameters for all arguments.
+            for _ in range(0, args.args.len()) {
+                try!(formatter.buf.write(pieces.next().unwrap().as_bytes()));
+                try!(formatter.run(&DEFAULT_ARGUMENT));
+            }
+        }
+        Some(fmt) => {
+            // Every spec has a corresponding argument that is preceded by
+            // a string piece.
+            for (arg, piece) in fmt.iter().zip(pieces.by_ref()) {
+                try!(formatter.buf.write(piece.as_bytes()));
+                try!(formatter.run(arg));
+            }
+        }
     }
 
+    // There can be only one trailing string piece left.
     match pieces.next() {
         Some(piece) => {
             try!(formatter.buf.write(piece.as_bytes()));
index fc4d8c83c514e24f7b073c4a188e576e65b20928..0bb32c73ca264ce666fb956819cb376515be6efa 100644 (file)
@@ -56,6 +56,9 @@ struct Context<'a, 'b:'a> {
     pieces: Vec<Gc<ast::Expr>>,
     /// Collection of string literals
     str_pieces: Vec<Gc<ast::Expr>>,
+    /// Stays `true` if all formatting parameters are default (as in "{}{}").
+    all_pieces_simple: bool,
+
     name_positions: HashMap<String, uint>,
     method_statics: Vec<Gc<ast::Item>>,
 
@@ -383,7 +386,6 @@ fn trans_literal_string(&mut self) -> Gc<ast::Expr> {
     /// Translate a `parse::Piece` to a static `rt::Argument` or append
     /// to the `literal` string.
     fn trans_piece(&mut self, piece: &parse::Piece) -> Option<Gc<ast::Expr>> {
-        // let mut is_not_default = true;
         let sp = self.fmtsp;
         match *piece {
             parse::String(s) => {
@@ -416,8 +418,25 @@ fn trans_piece(&mut self, piece: &parse::Piece) -> Option<Gc<ast::Expr>> {
                     }
                 };
 
-                // Translate the format
+                let simple_arg = parse::Argument {
+                    position: parse::ArgumentNext,
+                    format: parse::FormatSpec {
+                        fill: arg.format.fill,
+                        align: parse::AlignUnknown,
+                        flags: 0,
+                        precision: parse::CountImplied,
+                        width: parse::CountImplied,
+                        ty: arg.format.ty
+                    }
+                };
+
                 let fill = match arg.format.fill { Some(c) => c, None => ' ' };
+
+                if *arg != simple_arg || fill != ' ' {
+                    self.all_pieces_simple = false;
+                }
+
+                // Translate the format
                 let fill = self.ecx.expr_lit(sp, ast::LitChar(fill));
                 let align = match arg.format.align {
                     parse::AlignLeft => {
@@ -453,6 +472,26 @@ fn trans_piece(&mut self, piece: &parse::Piece) -> Option<Gc<ast::Expr>> {
         }
     }
 
+    fn item_static_array(&self,
+                         name: ast::Ident,
+                         piece_ty: Gc<ast::Ty>,
+                         pieces: Vec<Gc<ast::Expr>>)
+        -> ast::Stmt
+    {
+        let pieces_len = self.ecx.expr_uint(self.fmtsp, pieces.len());
+        let fmt = self.ecx.expr_vec(self.fmtsp, pieces);
+        let ty = ast::TyFixedLengthVec(
+            piece_ty,
+            pieces_len
+        );
+        let ty = self.ecx.ty(self.fmtsp, ty);
+        let st = ast::ItemStatic(ty, ast::MutImmutable, fmt);
+        let item = self.ecx.item(self.fmtsp, name,
+                                 self.static_attrs(), st);
+        let decl = respan(self.fmtsp, ast::DeclItem(item));
+        respan(self.fmtsp, ast::StmtDecl(box(GC) decl, ast::DUMMY_NODE_ID))
+    }
+
     /// Actually builds the expression which the iformat! block will be expanded
     /// to
     fn to_expr(&self, invocation: Invocation) -> Gc<ast::Expr> {
@@ -471,54 +510,31 @@ fn to_expr(&self, invocation: Invocation) -> Gc<ast::Expr> {
 
         // Next, build up the static array which will become our precompiled
         // format "string"
-        let fmt = self.ecx.expr_vec(self.fmtsp, self.str_pieces.clone());
-        let piece_ty = self.ecx.ty_rptr(self.fmtsp,
-                                        self.ecx.ty_ident(self.fmtsp,
-                                             self.ecx.ident_of("str")),
-                                        Some(self.ecx.lifetime(self.fmtsp,
-                                             self.ecx.ident_of(
-                                                 "'static").name)),
-                                        ast::MutImmutable);
-
-        let ty = ast::TyFixedLengthVec(
-            piece_ty,
-            self.ecx.expr_uint(self.fmtsp, self.str_pieces.len())
-        );
-        let ty = self.ecx.ty(self.fmtsp, ty);
-        let st = ast::ItemStatic(ty, ast::MutImmutable, fmt);
         let static_str_name = self.ecx.ident_of("__STATIC_FMTSTR");
-        let item = self.ecx.item(self.fmtsp, static_str_name,
-                                 self.static_attrs(), st);
-        let decl = respan(self.fmtsp, ast::DeclItem(item));
-        lets.push(box(GC) respan(self.fmtsp,
-                                 ast::StmtDecl(box(GC) decl, ast::DUMMY_NODE_ID)));
-
-        // Then, build up the static array which will become our precompiled
-        // format "string"
-        let fmt = self.ecx.expr_vec(self.fmtsp, self.pieces.clone());
-        let piece_ty = self.ecx.ty_path(self.ecx.path_all(
+        let static_lifetime = self.ecx.lifetime(self.fmtsp, self.ecx.ident_of("'static").name);
+        let piece_ty = self.ecx.ty_rptr(
                 self.fmtsp,
-                true, vec!(
-                    self.ecx.ident_of("std"),
-                    self.ecx.ident_of("fmt"),
-                    self.ecx.ident_of("rt"),
-                    self.ecx.ident_of("Argument")),
-                vec!(self.ecx.lifetime(self.fmtsp,
-                                       self.ecx.ident_of("'static").name)),
-                Vec::new()
-            ), None);
-        let ty = ast::TyFixedLengthVec(
-            piece_ty,
-            self.ecx.expr_uint(self.fmtsp, self.pieces.len())
-        );
-        let ty = self.ecx.ty(self.fmtsp, ty);
-        let st = ast::ItemStatic(ty, ast::MutImmutable, fmt);
+                self.ecx.ty_ident(self.fmtsp, self.ecx.ident_of("str")),
+                Some(static_lifetime),
+                ast::MutImmutable);
+        lets.push(box(GC) self.item_static_array(static_str_name,
+                                                 piece_ty,
+                                                 self.str_pieces.clone()));
+
+        // Then, build up the static array which will store our precompiled
+        // nonstandard placeholders, if there are any.
         let static_args_name = self.ecx.ident_of("__STATIC_FMTARGS");
-        let item = self.ecx.item(self.fmtsp, static_args_name,
-                                 self.static_attrs(), st);
-        let decl = respan(self.fmtsp, ast::DeclItem(item));
-        lets.push(box(GC) respan(self.fmtsp,
-                                 ast::StmtDecl(box(GC) decl, ast::DUMMY_NODE_ID)));
+        if !self.all_pieces_simple {
+            let piece_ty = self.ecx.ty_path(self.ecx.path_all(
+                    self.fmtsp,
+                    true, self.rtpath("Argument"),
+                    vec![static_lifetime],
+                    vec![]
+                ), None);
+            lets.push(box(GC) self.item_static_array(static_args_name,
+                                                     piece_ty,
+                                                     self.pieces.clone()));
+        }
 
         // Right now there is a bug such that for the expression:
         //      foo(bar(&1))
@@ -565,13 +581,20 @@ fn to_expr(&self, invocation: Invocation) -> Gc<ast::Expr> {
 
         // Now create the fmt::Arguments struct with all our locals we created.
         let pieces = self.ecx.expr_ident(self.fmtsp, static_str_name);
-        let fmt = self.ecx.expr_ident(self.fmtsp, static_args_name);
         let args_slice = self.ecx.expr_ident(self.fmtsp, slicename);
+
+        let (fn_name, fn_args) = if self.all_pieces_simple {
+            ("new", vec![pieces, args_slice])
+        } else {
+            let fmt = self.ecx.expr_ident(self.fmtsp, static_args_name);
+            ("with_placeholders", vec![pieces, fmt, args_slice])
+        };
+
         let result = self.ecx.expr_call_global(self.fmtsp, vec!(
                 self.ecx.ident_of("std"),
                 self.ecx.ident_of("fmt"),
                 self.ecx.ident_of("Arguments"),
-                self.ecx.ident_of("new")), vec!(pieces, fmt, args_slice));
+                self.ecx.ident_of(fn_name)), fn_args);
 
         // We did all the work of making sure that the arguments
         // structure is safe, so we can safely have an unsafe block.
@@ -741,6 +764,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, sp: Span,
         literal: String::new(),
         pieces: Vec::new(),
         str_pieces: Vec::new(),
+        all_pieces_simple: true,
         method_statics: Vec::new(),
         fmtsp: sp,
     };