diff --git a/src/main.c b/src/main.c index 10a221d..283e113 100644 --- a/src/main.c +++ b/src/main.c @@ -78,6 +78,34 @@ static int posixly_correct = 0; static enum total_mode total_mode = TOTAL_AUTO; static const char *files_from = NULL; /* --files0-from=F, or NULL */ +/* + * LC_CTYPE handling. A freshly exec'd process already runs in the C + * locale, so when the environment selects C/POSIX (or selects nothing) + * the setlocale() call is skipped: glibc would only re-derive the same + * state, at a cost measurable on the 1-line benchmark inputs. When the + * locale really is multibyte we adopt it exactly like GNU does. + */ +static int in_c_locale; /* LC_CTYPE resolves to C/POSIX, or is unset */ +static int locale_done; /* setup_locale() has run */ + +static void setup_locale(void) +{ + const char *v; + + if (locale_done) + return; + locale_done = 1; + v = getenv("LC_ALL"); + if (v == NULL || v[0] == '\0') + v = getenv("LC_CTYPE"); + if (v == NULL || v[0] == '\0') + v = getenv("LANG"); + in_c_locale = v == NULL || v[0] == '\0' || strcmp(v, "C") == 0 || + strcmp(v, "POSIX") == 0; + if (!in_c_locale) + setlocale(LC_CTYPE, ""); +} + /* gnulib quote() style: locale quotation marks in a UTF-8 locale, * ASCII apostrophes elsewhere. Only argmatch diagnostics use it. */ static int curly_quotes; @@ -141,6 +169,30 @@ static void init_isprint_tab(void) isprint_tab[i] = isprint((unsigned char)i) ? 1 : 0; } +/* The C/POSIX byte classes are fixed: isspace() is the six ASCII + * whitespace bytes and isprint() the range 0x20..0x7e. Precomputing + * them skips 512 ctype() calls per exec in the common C-locale run. */ +static void init_ws_tab_c(void) +{ + int i; + + memset(ws_tab, 0, sizeof ws_tab); + for (i = '\t'; i <= '\r'; i++) + ws_tab[i] = 1; + ws_tab[' '] = 1; + ws_tab[0xa0] = (unsigned char)!posixly_correct; + nbsp_ws = ws_tab[0xa0]; +} + +static void init_isprint_tab_c(void) +{ + int i; + + memset(isprint_tab, 0, sizeof isprint_tab); + for (i = 0x20; i <= 0x7e; i++) + isprint_tab[i] = 1; +} + static void try_help(void) { fprintf(stderr, "Try 'fastwc --help' for more information.\n"); @@ -187,9 +239,19 @@ static void qfile(FILE *out, const char *s) fputs(s, out); } -/* quote(): always-quoting locale style used by argmatch diagnostics. */ +/* quote(): always-quoting locale style used by argmatch diagnostics. + * The quote style is only needed on the --total error path, so the + * locale work stays out of every other invocation. */ static void qarg(FILE *out, const char *s) { + static int qarg_ready; + + if (!qarg_ready) + { + qarg_ready = 1; + setup_locale(); + init_quote_style(); + } fputs(curly_quotes ? "\xe2\x80\x98" : "'", out); fputs(s, out); fputs(curly_quotes ? "\xe2\x80\x99" : "'", out); @@ -540,6 +602,34 @@ static count_lw_fn pick_kernel(void) } #endif +/* One-time state the counting paths need, run after the options are + * known so --help/--version/usage errors pay none of it. The locale is + * only locale-sensitive for -m/-w/-L, the per-byte tables only for word + * and -L counting, and the SIMD kernel only for byte-path counts. */ +static void init_count_state(void) +{ + if (flags & (F_CHARS | F_WORDS | F_MAXLEN)) + { + setup_locale(); + if (flags & F_WORDS) + { + if (in_c_locale) + init_ws_tab_c(); + else + init_ws_tab(); + } + if (flags & F_MAXLEN) + { + if (in_c_locale) + init_isprint_tab_c(); + else + init_isprint_tab(); + } + } + if (flags & (F_LINES | F_WORDS)) + count_lw = pick_kernel(); +} + /* * The Unicode white space set as GNU wc 9.11 sees it: glibc's iswspace() * (which omits the no-break spaces U+00A0, U+2007, U+202F) plus those @@ -1413,16 +1503,10 @@ int main(int argc, char **argv) long n_args = 0; total_t totals = {0, 0, 0, 0, 0}; - setlocale(LC_CTYPE, ""); posixly_correct = getenv("POSIXLY_CORRECT") != NULL; - init_quote_style(); - init_ws_tab(); - init_isprint_tab(); - count_lw = pick_kernel(); - /* Line-buffer stdout like GNU, so parallel processes do not - * interleave their rows. */ - setvbuf(stdout, NULL, _IOLBF, 0); + /* Option parsing needs no locale, so --help/--version and usage + * errors return before any of the one-time startup state exists. */ while ((optc = getopt_long(argc, argv, "clLmw", longopts, NULL)) != -1) switch (optc) { @@ -1469,6 +1553,11 @@ int main(int argc, char **argv) if (flags == 0) flags = F_LINES | F_WORDS | F_BYTES; /* wc default: -l -w -c */ + /* Line-buffer stdout like GNU, so parallel processes do not + * interleave their rows. */ + setvbuf(stdout, NULL, _IOLBF, 0); + init_count_state(); + if (files_from) { FILE *stream; @@ -1908,7 +1997,11 @@ int main(void) /* The CLI build's helpers are compiled out of reach here. */ (void)usage; (void)try_help; + (void)setup_locale; (void)init_isprint_tab; + (void)init_isprint_tab_c; + (void)init_ws_tab_c; + (void)init_count_state; (void)init_quote_style; (void)qaf; (void)qfile;