Merge "strftime: format small positive integers ourselves." am: ad12582726

Original change: https://android-review.googlesource.com/c/platform/bionic/+/1897133

Change-Id: I3dab64aa9f5f94b17399b4a08d81ae847473a43a
diff --git a/benchmarks/time_benchmark.cpp b/benchmarks/time_benchmark.cpp
index 437dc78..a765e3e 100644
--- a/benchmarks/time_benchmark.cpp
+++ b/benchmarks/time_benchmark.cpp
@@ -187,3 +187,13 @@
   }
 }
 BIONIC_BENCHMARK(BM_time_localtime_r);
+
+void BM_time_strftime(benchmark::State& state) {
+  char buf[128];
+  time_t t = 0;
+  struct tm* tm = gmtime(&t);
+  while (state.KeepRunning()) {
+    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
+  }
+}
+BIONIC_BENCHMARK(BM_time_strftime);
diff --git a/libc/tzcode/strftime.c b/libc/tzcode/strftime.c
index c05f6b5..7c4be49 100644
--- a/libc/tzcode/strftime.c
+++ b/libc/tzcode/strftime.c
@@ -252,8 +252,8 @@
                                 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
                 continue;
             case 'd':
-                                pt = _conv(t->tm_mday, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'E':
             case 'O':
                 /*
@@ -274,22 +274,21 @@
                 modifier = *format;
                 goto label;
             case 'e':
-                pt = _conv(t->tm_mday, getformat(modifier, "%2d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'F':
                 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
                 continue;
             case 'H':
-                pt = _conv(t->tm_hour, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'I':
-                pt = _conv((t->tm_hour % 12) ?
-                    (t->tm_hour % 12) : 12,
-                    getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
+                         getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'j':
-                pt = _conv(t->tm_yday + 1, getformat(modifier, "%03d", "%3d", "%d", "%03d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", "  ", "03"), pt, ptlim);
+              continue;
             case 'k':
                 /*
                 ** This used to be...
@@ -301,7 +300,7 @@
                 ** "%l" have been swapped.
                 ** (ado, 1993-05-24)
                 */
-                pt = _conv(t->tm_hour, getformat(modifier, "%2d", "%2d", "%d", "%02d"), pt, ptlim);
+                pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", "  ", "02"), pt, ptlim);
                 continue;
 #ifdef KITCHEN_SINK
             case 'K':
@@ -321,16 +320,15 @@
                 ** "%l" have been swapped.
                 ** (ado, 1993-05-24)
                 */
-                pt = _conv((t->tm_hour % 12) ?
-                    (t->tm_hour % 12) : 12,
-                    getformat(modifier, "%2d", "%2d", "%d", "%02d"), pt, ptlim);
+                pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
+                           getformat(modifier, " 2", " 2", "  ", "02"), pt, ptlim);
                 continue;
             case 'M':
-                pt = _conv(t->tm_min, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_min, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'm':
-                pt = _conv(t->tm_mon + 1, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'n':
                 pt = _add("\n", pt, ptlim, modifier);
                 continue;
@@ -348,13 +346,12 @@
                 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
                 continue;
             case 'S':
-                pt = _conv(t->tm_sec, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 's':
                 {
                     struct tm   tm;
-                    char        buf[INT_STRLEN_MAXIMUM(
-                                time64_t) + 1];
+                    char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
                     time64_t    mkt;
 
                     tm = *t;
@@ -374,10 +371,9 @@
                 pt = _add("\t", pt, ptlim, modifier);
                 continue;
             case 'U':
-                pt = _conv((t->tm_yday + DAYSPERWEEK -
-                    t->tm_wday) / DAYSPERWEEK,
-                    getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
+                         getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'u':
                 /*
                 ** From Arnold Robbins' strftime version 3.0:
@@ -385,9 +381,7 @@
                 ** [1 (Monday) - 7]"
                 ** (ado, 1993-05-24)
                 */
-                pt = _conv((t->tm_wday == 0) ?
-                    DAYSPERWEEK : t->tm_wday,
-                    "%d", pt, ptlim);
+                pt = _conv((t->tm_wday == 0) ? DAYSPERWEEK : t->tm_wday, "  ", pt, ptlim);
                 continue;
             case 'V':   /* ISO 8601 week number */
             case 'G':   /* ISO 8601 year (four digits) */
@@ -467,8 +461,7 @@
                             w = 53;
 #endif /* defined XPG4_1994_04_09 */
                     if (*format == 'V')
-                        pt = _conv(w, getformat(modifier, "%02d", "%2d", "%d", "%02d"),
-                               pt, ptlim);
+                      pt = _conv(w, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
                     else if (*format == 'g') {
                         *warnp = IN_ALL;
                         pt = _yconv(year, base,
@@ -488,15 +481,14 @@
                 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
                 continue;
             case 'W':
-                pt = _conv((t->tm_yday + DAYSPERWEEK -
-                    (t->tm_wday ?
-                    (t->tm_wday - 1) :
-                    (DAYSPERWEEK - 1))) / DAYSPERWEEK,
-                    getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
-                continue;
+              pt = _conv(
+                  (t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
+                      DAYSPERWEEK,
+                  getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
+              continue;
             case 'w':
-                pt = _conv(t->tm_wday, "%d", pt, ptlim);
-                continue;
+              pt = _conv(t->tm_wday, "  ", pt, ptlim);
+              continue;
             case 'X':
                 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
                 continue;
@@ -602,7 +594,7 @@
                 diff /= SECSPERMIN;
                 diff = (diff / MINSPERHOUR) * 100 +
                     (diff % MINSPERHOUR);
-                pt = _conv(diff, getformat(modifier, "%04d", "%4d", "%d", "%04d"), pt, ptlim);
+                pt = _conv(diff, getformat(modifier, "04", " 4", "  ", "04"), pt, ptlim);
                 }
                 continue;
             case '+':
@@ -629,10 +621,40 @@
 static char *
 _conv(int n, const char *format, char *pt, const char *ptlim)
 {
-	char	buf[INT_STRLEN_MAXIMUM(int) + 1];
+  // The original implementation used snprintf(3) here, but rolling our own is
+  // about 5x faster. Seems like a good trade-off for so little code, especially
+  // for users like logcat that have a habit of formatting 10k times all at
+  // once...
 
-	snprintf(buf, sizeof(buf), format, n);
-	return _add(buf, pt, ptlim, 0);
+  // Format is '0' or ' ' for the fill character, followed by a single-digit
+  // width or ' ' for "whatever".
+  //   %d -> "  "
+  //  %2d -> " 2"
+  // %02d -> "02"
+  char fill = format[0];
+  int width = format[1] == ' ' ? 0 : format[1] - '0';
+
+  char buf[32] __attribute__((__uninitialized__));
+
+  // Terminate first, so we can walk backwards from the least-significant digit
+  // without having to later reverse the result.
+  char* p = &buf[31];
+  *--p = '\0';
+  char* end = p;
+
+  // Output digits backwards, from least-significant to most.
+  while (n >= 10) {
+    *--p = '0' + (n % 10);
+    n /= 10;
+  }
+  *--p = '0' + n;
+
+  // Fill if more digits are required by the format.
+  while ((end - p) < width) {
+    *--p = fill;
+  }
+
+  return _add(p, pt, ptlim, 0);
 }
 
 static char *
@@ -704,9 +726,11 @@
     if (convert_top) {
         if (lead == 0 && trail < 0)
             pt = _add("-0", pt, ptlim, modifier);
-        else    pt = _conv(lead, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
+        else
+          pt = _conv(lead, getformat(modifier, "02", " 2", "  ", "02"), pt, ptlim);
     }
     if (convert_yy)
-        pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
+      pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", "  ", "02"), pt,
+                 ptlim);
     return pt;
 }