Skip to content

Commit addff4f

Browse files
authored
Use musl implementation of strftime rather than custom JS code. NFC (#21379)
This leads to some code size savings in several tests, reduces the differences between standalone mode and non-standalone mode and reduces the amount of custom JS code we need to maitain. In simple microbenchmark that does nothing but export `strftime` we do code size win at `-O0` but a regression in codesize at `-O2`: ``` $ wc -c old_O0.* 74026 old.js 12124 old.wasm 86150 total $ wc -c new_O0.* 62120 new.js 22852 new.wasm 84972 total ``` ``` $ wc -c old_O2.* 8492 old.js 194 old.wasm 8686 total $ wc -c new_O2.* 5040 new.js 14291 new.wasm 19331 total ``` This regression comes from the fact that `snprintf` is used in the implementation of `strftime` and its hard to optimize away the cost of that function. In practice I suspect that any application large enough to call `strftime` is likely already directly or indirectly pulling in `snprintf` so I would hope that this will be codesize win in practice since it removes about 3k of JS.
1 parent 64acac2 commit addff4f

File tree

50 files changed

+78
-347
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+78
-347
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ See docs/process.md for more on how version tagging works.
2727
`readAsync`. (#22080)
2828
- reference-types feature is now enabled by default in Emscripten, due to the
2929
upstream LLVM change (https://github.com/llvm/llvm-project/pull/93261).
30+
- Emscripten now uses `strftime` from musl rather than using a custom
31+
JavaScript implementation. (#21379)
3032

3133
3.1.61 - 05/31/24
3234
-----------------

src/library.js

Lines changed: 0 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -728,266 +728,6 @@ addToLibrary({
728728
return newDate;
729729
},
730730

731-
// Note: this is not used in STANDALONE_WASM mode, because it is more
732-
// compact to do it in JS.
733-
strftime__deps: ['$isLeapYear', '$arraySum', '$addDays', '$MONTH_DAYS_REGULAR', '$MONTH_DAYS_LEAP',
734-
'$intArrayFromString', '$writeArrayToMemory'
735-
],
736-
strftime: (s, maxsize, format, tm) => {
737-
// size_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict timeptr);
738-
// http://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html
739-
740-
var tm_zone = {{{ makeGetValue('tm', C_STRUCTS.tm.tm_zone, '*') }}};
741-
742-
var date = {
743-
tm_sec: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_sec, 'i32') }}},
744-
tm_min: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_min, 'i32') }}},
745-
tm_hour: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_hour, 'i32') }}},
746-
tm_mday: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_mday, 'i32') }}},
747-
tm_mon: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_mon, 'i32') }}},
748-
tm_year: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_year, 'i32') }}},
749-
tm_wday: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_wday, 'i32') }}},
750-
tm_yday: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_yday, 'i32') }}},
751-
tm_isdst: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_isdst, 'i32') }}},
752-
tm_gmtoff: {{{ makeGetValue('tm', C_STRUCTS.tm.tm_gmtoff, LONG_TYPE) }}},
753-
tm_zone: tm_zone ? UTF8ToString(tm_zone) : ''
754-
};
755-
{{{ from64('date.tm_gmtoff') }}}
756-
757-
var pattern = UTF8ToString(format);
758-
759-
// expand format
760-
var EXPANSION_RULES_1 = {
761-
'%D': '%m/%d/%y', // Equivalent to %m / %d / %y
762-
'%F': '%Y-%m-%d', // Equivalent to %Y - %m - %d
763-
'%h': '%b', // Equivalent to %b
764-
'%r': '%I:%M:%S %p', // Replaced by the time in a.m. and p.m. notation
765-
'%R': '%H:%M', // Replaced by the time in 24-hour notation
766-
'%T': '%H:%M:%S', // Replaced by the time
767-
'%x': '%m/%d/%y', // Replaced by the locale's appropriate date representation
768-
'%X': '%H:%M:%S', // Replaced by the locale's appropriate time representation
769-
// Modified Conversion Specifiers
770-
'%Ec': '%c', // Replaced by the locale's alternative appropriate date and time representation.
771-
'%EC': '%C', // Replaced by the name of the base year (period) in the locale's alternative representation.
772-
'%Ex': '%m/%d/%y', // Replaced by the locale's alternative date representation.
773-
'%EX': '%H:%M:%S', // Replaced by the locale's alternative time representation.
774-
'%Ey': '%y', // Replaced by the offset from %EC (year only) in the locale's alternative representation.
775-
'%EY': '%Y', // Replaced by the full alternative year representation.
776-
'%Od': '%d', // Replaced by the day of the month, using the locale's alternative numeric symbols, filled as needed with leading zeros if there is any alternative symbol for zero; otherwise, with leading <space> characters.
777-
'%Oe': '%e', // Replaced by the day of the month, using the locale's alternative numeric symbols, filled as needed with leading <space> characters.
778-
'%OH': '%H', // Replaced by the hour (24-hour clock) using the locale's alternative numeric symbols.
779-
'%OI': '%I', // Replaced by the hour (12-hour clock) using the locale's alternative numeric symbols.
780-
'%Om': '%m', // Replaced by the month using the locale's alternative numeric symbols.
781-
'%OM': '%M', // Replaced by the minutes using the locale's alternative numeric symbols.
782-
'%OS': '%S', // Replaced by the seconds using the locale's alternative numeric symbols.
783-
'%Ou': '%u', // Replaced by the weekday as a number in the locale's alternative representation (Monday=1).
784-
'%OU': '%U', // Replaced by the week number of the year (Sunday as the first day of the week, rules corresponding to %U ) using the locale's alternative numeric symbols.
785-
'%OV': '%V', // Replaced by the week number of the year (Monday as the first day of the week, rules corresponding to %V ) using the locale's alternative numeric symbols.
786-
'%Ow': '%w', // Replaced by the number of the weekday (Sunday=0) using the locale's alternative numeric symbols.
787-
'%OW': '%W', // Replaced by the week number of the year (Monday as the first day of the week) using the locale's alternative numeric symbols.
788-
'%Oy': '%y', // Replaced by the year (offset from %C ) using the locale's alternative numeric symbols.
789-
'%c': '%a %b %d %H:%M:%S %Y', // Replaced by the locale's appropriate date and time representation - e.g., Mon Aug 3 14:02:01 2013
790-
};
791-
for (var rule in EXPANSION_RULES_1) {
792-
pattern = pattern.replace(new RegExp(rule, 'g'), EXPANSION_RULES_1[rule]);
793-
}
794-
795-
var WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
796-
var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
797-
798-
function leadingSomething(value, digits, character) {
799-
var str = typeof value == 'number' ? value.toString() : (value || '');
800-
while (str.length < digits) {
801-
str = character[0]+str;
802-
}
803-
return str;
804-
}
805-
806-
function leadingNulls(value, digits) {
807-
return leadingSomething(value, digits, '0');
808-
}
809-
810-
function compareByDay(date1, date2) {
811-
function sgn(value) {
812-
return value < 0 ? -1 : (value > 0 ? 1 : 0);
813-
}
814-
815-
var compare;
816-
if ((compare = sgn(date1.getFullYear()-date2.getFullYear())) === 0) {
817-
if ((compare = sgn(date1.getMonth()-date2.getMonth())) === 0) {
818-
compare = sgn(date1.getDate()-date2.getDate());
819-
}
820-
}
821-
return compare;
822-
}
823-
824-
function getFirstWeekStartDate(janFourth) {
825-
switch (janFourth.getDay()) {
826-
case 0: // Sunday
827-
return new Date(janFourth.getFullYear()-1, 11, 29);
828-
case 1: // Monday
829-
return janFourth;
830-
case 2: // Tuesday
831-
return new Date(janFourth.getFullYear(), 0, 3);
832-
case 3: // Wednesday
833-
return new Date(janFourth.getFullYear(), 0, 2);
834-
case 4: // Thursday
835-
return new Date(janFourth.getFullYear(), 0, 1);
836-
case 5: // Friday
837-
return new Date(janFourth.getFullYear()-1, 11, 31);
838-
case 6: // Saturday
839-
return new Date(janFourth.getFullYear()-1, 11, 30);
840-
}
841-
}
842-
843-
function getWeekBasedYear(date) {
844-
var thisDate = addDays(new Date(date.tm_year+1900, 0, 1), date.tm_yday);
845-
846-
var janFourthThisYear = new Date(thisDate.getFullYear(), 0, 4);
847-
var janFourthNextYear = new Date(thisDate.getFullYear()+1, 0, 4);
848-
849-
var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear);
850-
var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear);
851-
852-
if (compareByDay(firstWeekStartThisYear, thisDate) <= 0) {
853-
// this date is after the start of the first week of this year
854-
if (compareByDay(firstWeekStartNextYear, thisDate) <= 0) {
855-
return thisDate.getFullYear()+1;
856-
}
857-
return thisDate.getFullYear();
858-
}
859-
return thisDate.getFullYear()-1;
860-
}
861-
862-
var EXPANSION_RULES_2 = {
863-
'%a': (date) => WEEKDAYS[date.tm_wday].substring(0,3) ,
864-
'%A': (date) => WEEKDAYS[date.tm_wday],
865-
'%b': (date) => MONTHS[date.tm_mon].substring(0,3),
866-
'%B': (date) => MONTHS[date.tm_mon],
867-
'%C': (date) => {
868-
var year = date.tm_year+1900;
869-
return leadingNulls((year/100)|0,2);
870-
},
871-
'%d': (date) => leadingNulls(date.tm_mday, 2),
872-
'%e': (date) => leadingSomething(date.tm_mday, 2, ' '),
873-
'%g': (date) => {
874-
// %g, %G, and %V give values according to the ISO 8601:2000 standard week-based year.
875-
// In this system, weeks begin on a Monday and week 1 of the year is the week that includes
876-
// January 4th, which is also the week that includes the first Thursday of the year, and
877-
// is also the first week that contains at least four days in the year.
878-
// If the first Monday of January is the 2nd, 3rd, or 4th, the preceding days are part of
879-
// the last week of the preceding year; thus, for Saturday 2nd January 1999,
880-
// %G is replaced by 1998 and %V is replaced by 53. If December 29th, 30th,
881-
// or 31st is a Monday, it and any following days are part of week 1 of the following year.
882-
// Thus, for Tuesday 30th December 1997, %G is replaced by 1998 and %V is replaced by 01.
883-
884-
return getWeekBasedYear(date).toString().substring(2);
885-
},
886-
'%G': getWeekBasedYear,
887-
'%H': (date) => leadingNulls(date.tm_hour, 2),
888-
'%I': (date) => {
889-
var twelveHour = date.tm_hour;
890-
if (twelveHour == 0) twelveHour = 12;
891-
else if (twelveHour > 12) twelveHour -= 12;
892-
return leadingNulls(twelveHour, 2);
893-
},
894-
'%j': (date) => {
895-
// Day of the year (001-366)
896-
return leadingNulls(date.tm_mday + arraySum(isLeapYear(date.tm_year+1900) ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, date.tm_mon-1), 3);
897-
},
898-
'%m': (date) => leadingNulls(date.tm_mon+1, 2),
899-
'%M': (date) => leadingNulls(date.tm_min, 2),
900-
'%n': () => '\n',
901-
'%p': (date) => {
902-
if (date.tm_hour >= 0 && date.tm_hour < 12) {
903-
return 'AM';
904-
}
905-
return 'PM';
906-
},
907-
'%S': (date) => leadingNulls(date.tm_sec, 2),
908-
'%t': () => '\t',
909-
'%u': (date) => date.tm_wday || 7,
910-
'%U': (date) => {
911-
var days = date.tm_yday + 7 - date.tm_wday;
912-
return leadingNulls(Math.floor(days / 7), 2);
913-
},
914-
'%V': (date) => {
915-
// Replaced by the week number of the year (Monday as the first day of the week)
916-
// as a decimal number [01,53]. If the week containing 1 January has four
917-
// or more days in the new year, then it is considered week 1.
918-
// Otherwise, it is the last week of the previous year, and the next week is week 1.
919-
// Both January 4th and the first Thursday of January are always in week 1. [ tm_year, tm_wday, tm_yday]
920-
var val = Math.floor((date.tm_yday + 7 - (date.tm_wday + 6) % 7 ) / 7);
921-
// If 1 Jan is just 1-3 days past Monday, the previous week
922-
// is also in this year.
923-
if ((date.tm_wday + 371 - date.tm_yday - 2) % 7 <= 2) {
924-
val++;
925-
}
926-
if (!val) {
927-
val = 52;
928-
// If 31 December of prev year a Thursday, or Friday of a
929-
// leap year, then the prev year has 53 weeks.
930-
var dec31 = (date.tm_wday + 7 - date.tm_yday - 1) % 7;
931-
if (dec31 == 4 || (dec31 == 5 && isLeapYear(date.tm_year%400-1))) {
932-
val++;
933-
}
934-
} else if (val == 53) {
935-
// If 1 January is not a Thursday, and not a Wednesday of a
936-
// leap year, then this year has only 52 weeks.
937-
var jan1 = (date.tm_wday + 371 - date.tm_yday) % 7;
938-
if (jan1 != 4 && (jan1 != 3 || !isLeapYear(date.tm_year)))
939-
val = 1;
940-
}
941-
return leadingNulls(val, 2);
942-
},
943-
'%w': (date) => date.tm_wday,
944-
'%W': (date) => {
945-
var days = date.tm_yday + 7 - ((date.tm_wday + 6) % 7);
946-
return leadingNulls(Math.floor(days / 7), 2);
947-
},
948-
'%y': (date) => {
949-
// Replaced by the last two digits of the year as a decimal number [00,99]. [ tm_year]
950-
return (date.tm_year+1900).toString().substring(2);
951-
},
952-
// Replaced by the year as a decimal number (for example, 1997). [ tm_year]
953-
'%Y': (date) => date.tm_year+1900,
954-
'%z': (date) => {
955-
// Replaced by the offset from UTC in the ISO 8601:2000 standard format ( +hhmm or -hhmm ).
956-
// For example, "-0430" means 4 hours 30 minutes behind UTC (west of Greenwich).
957-
var off = date.tm_gmtoff;
958-
var ahead = off >= 0;
959-
off = Math.abs(off) / 60;
960-
// convert from minutes into hhmm format (which means 60 minutes = 100 units)
961-
off = (off / 60)*100 + (off % 60);
962-
return (ahead ? '+' : '-') + String("0000" + off).slice(-4);
963-
},
964-
'%Z': (date) => date.tm_zone,
965-
'%%': () => '%'
966-
};
967-
968-
// Replace %% with a pair of NULLs (which cannot occur in a C string), then
969-
// re-inject them after processing.
970-
pattern = pattern.replace(/%%/g, '\0\0')
971-
for (var rule in EXPANSION_RULES_2) {
972-
if (pattern.includes(rule)) {
973-
pattern = pattern.replace(new RegExp(rule, 'g'), EXPANSION_RULES_2[rule](date));
974-
}
975-
}
976-
pattern = pattern.replace(/\0\0/g, '%')
977-
978-
var bytes = intArrayFromString(pattern, false);
979-
if (bytes.length > maxsize) {
980-
return 0;
981-
}
982-
983-
writeArrayToMemory(bytes, s);
984-
return bytes.length-1;
985-
},
986-
strftime_l__deps: ['strftime'],
987-
strftime_l: (s, maxsize, format, tm, loc) => {
988-
return _strftime(s, maxsize, format, tm); // no locale support yet
989-
},
990-
991731
strptime__deps: ['$isLeapYear', '$arraySum', '$addDays', '$MONTH_DAYS_REGULAR', '$MONTH_DAYS_LEAP',
992732
'$jstoi_q', '$intArrayFromString' ],
993733
strptime: (buf, format, tm) => {

src/library_sigs.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,8 +1536,6 @@ sigs = {
15361536
rotozoomSurface__sig: 'ppddi',
15371537
sched_yield__sig: 'i',
15381538
setprotoent__sig: 'vi',
1539-
strftime__sig: 'ppppp',
1540-
strftime_l__sig: 'pppppp',
15411539
strptime__sig: 'pppp',
15421540
strptime_l__sig: 'ppppp',
15431541
uuid_clear__sig: 'vp',

system/lib/libc/emscripten_time.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@
1515

1616
#include "emscripten_internal.h"
1717

18-
// Replaces musl's __tz.c
19-
20-
weak long timezone = 0;
21-
weak int daylight = 0;
22-
weak char *tzname[2] = { 0, 0 };
23-
2418
weak clock_t __clock() {
2519
static thread_local double start = 0;
2620
if (!start) {

system/lib/libc/musl/src/time/__tz.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
#include "lock.h"
1010
#include "fork_impl.h"
1111

12+
#ifdef __EMSCRIPTEN__
13+
#include <stdbool.h>
14+
#include <pthread.h>
15+
#include "emscripten_internal.h"
16+
#endif
17+
1218
#define malloc __libc_malloc
1319
#define calloc undef
1420
#define realloc undef
@@ -39,6 +45,7 @@ static size_t old_tz_size = sizeof old_tz_buf;
3945
static volatile int lock[1];
4046
volatile int *const __timezone_lockptr = lock;
4147

48+
#ifndef __EMSCRIPTEN__
4249
static int getint(const char **p)
4350
{
4451
unsigned x;
@@ -122,9 +129,24 @@ static size_t zi_dotprod(const unsigned char *z, const unsigned char *v, size_t
122129
}
123130
return y;
124131
}
132+
#endif
125133

126134
static void do_tzset()
127135
{
136+
#ifdef __EMSCRIPTEN__
137+
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
138+
static _Atomic bool done_init = false;
139+
if (!done_init) {
140+
pthread_mutex_lock(&lock);
141+
if (!done_init) {
142+
_tzset_js(&timezone, &daylight, std_name, dst_name);
143+
__tzname[0] = std_name;
144+
__tzname[1] = dst_name;
145+
done_init = true;
146+
}
147+
pthread_mutex_unlock(&lock);
148+
}
149+
#else
128150
char buf[NAME_MAX+25], *pathname=buf+24;
129151
const char *try, *s, *p;
130152
const unsigned char *map = 0;
@@ -255,8 +277,10 @@ static void do_tzset()
255277

256278
if (*s == ',') s++, getrule(&s, r0);
257279
if (*s == ',') s++, getrule(&s, r1);
280+
#endif
258281
}
259282

283+
#ifndef __EMSCRIPTEN__
260284
/* Search zoneinfo rules to find the one that applies to the given time,
261285
* and determine alternate opposite-DST-status rule that may be needed. */
262286

@@ -416,6 +440,7 @@ void __secs_to_zone(long long t, int local, int *isdst, long *offset, long *oppo
416440
*zonename = __tzname[1];
417441
UNLOCK(lock);
418442
}
443+
#endif
419444

420445
static void __tzset()
421446
{

system/lib/libc/tzset.c

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)