Skip to content

Commit

Permalink
auto merge of #16662 : pczarn/rust/format-fmtstr-opt, r=brson
Browse files Browse the repository at this point in the history
Based on an observation that strings and arguments are always interleaved, thanks to #15832. Additionally optimize invocations where formatting parameters are unspecified for all arguments, e.g. `"{} {:?} {:x}"`, by emptying the `__STATIC_FMTARGS` array. Next, `Arguments::new` replaces an empty slice with `None` so that passing empty `__STATIC_FMTARGS` generates slightly less machine code when `Arguments::new` is inlined. Furthermore, formatting itself treats these cases separately without making redundant copies of formatting parameters.

All in all, this adds a single mov instruction per `write!` in most cases. That's why code size has increased.
  • Loading branch information
bors committed Sep 9, 2014
2 parents 6511064 + fcf88b8 commit a1f4973
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 66 deletions.
117 changes: 117 additions & 0 deletions src/libcore/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ impl<'a> Arguments<'a> {
/// Arguments structure. The compiler inserts an `unsafe` block to call this,
/// which is valid because the compiler performs all necessary validation to
/// ensure that the resulting call to format/write would be safe.
#[cfg(not(stage0))]
#[doc(hidden)] #[inline]
pub unsafe fn new<'a>(pieces: &'static [&'static str],
args: &'a [Argument<'a>]) -> Arguments<'a> {
Arguments {
pieces: mem::transmute(pieces),
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
}
}

#[cfg(stage0)]
#[doc(hidden)] #[inline]
pub unsafe fn new<'a>(fmt: &'static [rt::Piece<'static>],
args: &'a [Argument<'a>]) -> Arguments<'a> {
Expand All @@ -129,6 +156,20 @@ impl<'a> Arguments<'a> {
/// and pass it to a function or closure, passed as the first argument. The
/// macro validates the format string at compile-time so usage of the `write`
/// and `format` functions can be safely performed.
#[cfg(not(stage0))]
pub struct Arguments<'a> {
// Format string pieces to print.
pieces: &'a [&'a str],

// 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>],
}

#[cfg(stage0)] #[doc(hidden)]
pub struct Arguments<'a> {
fmt: &'a [rt::Piece<'a>],
args: &'a [Argument<'a>],
Expand Down Expand Up @@ -255,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 @@ -263,6 +316,51 @@ uniform_fn_call_workaround! {
///
/// * output - the buffer to write output to
/// * args - the precompiled arguments generated by `format_args!`
#[cfg(not(stage0))]
pub fn write(output: &mut FormatWriter, args: &Arguments) -> Result {
let mut formatter = Formatter {
flags: 0,
width: None,
precision: None,
buf: output,
align: rt::AlignUnknown,
fill: ' ',
args: args.args,
curarg: args.args.iter(),
};

let mut pieces = args.pieces.iter();

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()));
}
None => {}
}

Ok(())
}

#[cfg(stage0)] #[doc(hidden)]
pub fn write(output: &mut FormatWriter, args: &Arguments) -> Result {
let mut formatter = Formatter {
flags: 0,
Expand All @@ -285,7 +383,26 @@ impl<'a> Formatter<'a> {
// First up is the collection of functions used to execute a format string
// at runtime. This consumes all of the compile-time statics generated by
// the format! syntax extension.
#[cfg(not(stage0))]
fn run(&mut self, arg: &rt::Argument) -> Result {
// Fill in the format parameters into the formatter
self.fill = arg.format.fill;
self.align = arg.format.align;
self.flags = arg.format.flags;
self.width = self.getcount(&arg.format.width);
self.precision = self.getcount(&arg.format.precision);

// Extract the correct argument
let value = match arg.position {
rt::ArgumentNext => { *self.curarg.next().unwrap() }
rt::ArgumentIs(i) => self.args[i],
};

// Then actually do some printing
(value.formatter)(value.value, self)
}

#[cfg(stage0)] #[doc(hidden)]
fn run(&mut self, piece: &rt::Piece) -> Result {
match *piece {
rt::String(s) => self.buf.write(s.as_bytes()),
Expand Down
2 changes: 1 addition & 1 deletion src/libcore/fmt/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//! These definitions are similar to their `ct` equivalents, but differ in that
//! these can be statically allocated and are slightly optimized for the runtime

#[cfg(stage0)]
#[doc(hidden)]
pub enum Piece<'a> {
String(&'a str),
Expand Down
7 changes: 7 additions & 0 deletions src/libcoretest/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
// except according to those terms.

mod num;

#[test]
fn test_format_flags() {
// No residual flags left by pointer formatting
let p = "".as_ptr();
assert_eq!(format!("{:p} {:x}", p, 16u), format!("{:p} 10", p));
}
Loading

0 comments on commit a1f4973

Please sign in to comment.