Skip to content

Commit af4c482

Browse files
committed
Use musl implementation of strftime rather than custom JS one. NFC
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 4856d55 commit af4c482

Some content is hidden

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

46 files changed

+100
-346
lines changed

src/library.js

Lines changed: 0 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -699,266 +699,6 @@ addToLibrary({
699699
return newDate;
700700
},
701701

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

src/library_sigs.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,8 +1537,6 @@ sigs = {
15371537
rotozoomSurface__sig: 'ppddi',
15381538
sched_yield__sig: 'i',
15391539
setprotoent__sig: 'vi',
1540-
strftime__sig: 'ppppp',
1541-
strftime_l__sig: 'pppppp',
15421540
strptime__sig: 'pppp',
15431541
strptime_l__sig: 'ppppp',
15441542
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.

test/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
config.NODE_JS_TEST = config.NODE_JS
9494

9595

96+
requires_network = unittest.skipIf(os.getenv('EMTEST_SKIP_NETWORK_TESTS'), "This test requires network access")
97+
98+
9699
def test_file(*path_components):
97100
"""Construct a path relative to the emscripten "tests" directory."""
98101
return str(Path(TEST_ROOT, *path_components))

0 commit comments

Comments
 (0)