Skip to content

Commit

Permalink
Optimize for the most common cases of format!
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
pczarn committed Sep 9, 2014
1 parent 696367f commit 5aaa606
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 55 deletions.
58 changes: 52 additions & 6 deletions src/libcore/fmt/mod.rs
Expand Up @@ -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
}
}
Expand All @@ -144,8 +158,14 @@ impl<'a> Arguments<'a> {
/// 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>],
}

Expand Down Expand Up @@ -276,6 +296,18 @@ uniform_fn_call_workaround! {
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.
Expand All @@ -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()));
Expand Down
122 changes: 73 additions & 49 deletions src/libsyntax/ext/format.rs
Expand Up @@ -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>>,

Expand Down Expand Up @@ -383,7 +386,6 @@ impl<'a, 'b> Context<'a, 'b> {
/// 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) => {
Expand Down Expand Up @@ -416,8 +418,25 @@ impl<'a, 'b> Context<'a, 'b> {
}
};

// 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 => {
Expand Down Expand Up @@ -453,6 +472,26 @@ impl<'a, 'b> Context<'a, 'b> {
}
}

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> {
Expand All @@ -471,54 +510,31 @@ impl<'a, 'b> Context<'a, 'b> {

// 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))
Expand Down Expand Up @@ -565,13 +581,20 @@ impl<'a, 'b> Context<'a, 'b> {

// 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.
Expand Down Expand Up @@ -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,
};
Expand Down

0 comments on commit 5aaa606

Please sign in to comment.