Hi Pádraig,

> $ yes áááááááááááááááááááá | head -n100000 > mbc.txt
> $ yes 12345678901234567890 | head -n100000 > num.txt
> 
> ===== Before ====
> 
> $ time src/wc -m < mbc.txt
> 2100000
> real    0m0.186s
> 
> $ time src/wc -m < num.txt
> 2100000
> real    0m0.056s

Here's my take on improving this. I'm attaching draft patches that have
this effect on the timings:

* On glibc:

             num     mbc
  Before    0.056   0.152
  After     0.057   0.089
  -------
  Speedup    1.0     1.7
  factor

* On macOS 10.13:

             num     mbc
  Before    0.153   0.229
  After     0.042   0.112
  -------
  Speedup    3.6     2.0
  factor

Basically, the two problems that the profiling found were:

  * It is pointless to call locale_charset repeatedly, because the
    locale won't change while 'wc' is running.

  * glibc has a slow mbrtowc() implementation for UTF-8 locales.

Both problems can be addressed with the "abstract factory" design patterns.
Namely, instead of using the generic 'wcwidth'/'mbrtowc' function each
time, let the program produce an optimized 'wcwidth'/'mbrtowc' function
[pointer] once, and then call this optimized function pointer repeatedly
for each character.

While at it, let me also do the same for the initialization of an mbstate_t,
because on macOS the mbstate_t is 128 bytes long but only the first 12 bytes
actually matter.

This factory of function pointers side-steps the portability problems of
'locale_t'.

Notes:
  - When you use these new gnulib modules, you are programming against an API
    that is very similar to POSIX, but not exactly POSIX.
  - The platform-specific #ifs have to be adjusted, by the help of configure
    tests.
  - mbrtowc-factory needs a unit test (for which I have a draft).

I'm presenting the effect on the profiling in separate mails.

Bruno

diff --git a/lib/mbrtowc-factory.c b/lib/mbrtowc-factory.c
new file mode 100644
index 0000000..8786382
--- /dev/null
+++ b/lib/mbrtowc-factory.c
@@ -0,0 +1,580 @@
+/* Factory that produces an mbrtowc-like function for a given locale.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   Written by Bruno Haible <br...@clisp.org>, 2018.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <wchar.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "localcharset.h"
+
+#if __GLIBC__ >= 2
+
+static mbstate_t internal_state;
+
+static size_t
+utf8_mbrtowc (wchar_t *pwc, const char *s, size_t n, mbstate_t *ps)
+{
+  if (s == NULL)
+    {
+      pwc = NULL;
+      s = "";
+      n = 1;
+    }
+
+  if (n == 0)
+    return (size_t)(-2);
+
+  /* Here n > 0.  */
+
+  if (ps == NULL)
+    ps = &internal_state;
+
+  /* ps->__count is
+     (number of bytes already read) + (number of total bytes expected) << 8,
+     ps->__value.__wch is the part already read.  */
+  size_t read = ps->__count & 0xff;
+  unsigned int wc;
+
+  if (read == 0)
+    {
+      if (!(ps->__count == 0))
+        abort ();
+      unsigned char c = (unsigned char) s[0];
+      if (c < 0x80)
+        {
+          if (pwc != NULL)
+            *pwc = c;
+          return 1;
+        }
+      else if (c >= 0xc2)
+        {
+          if (c < 0xe0)
+            {
+              if (n >= 2)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40)
+                    {
+                      if (pwc != NULL)
+                        *pwc = ((unsigned int) (c & 0x1f) << 6)
+                               | (unsigned int) (c2 ^ 0x80);
+                      return 2;
+                    }
+                  else
+                    goto invalid;
+                }
+              else /* n == 1 */
+                {
+                  ps->__count = 0x0201;
+                  ps->__value.__wch = (unsigned int) (c & 0x1f) << 6;
+                  return (size_t)(-2);
+                }
+            }
+          else if (c < 0xf0)
+            {
+              if (n >= 3)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xe1 || c2 >= 0xa0)
+                      && (c != 0xed || c2 < 0xa0))
+                    {
+                      unsigned char c3 = (unsigned char) s[2];
+
+                      if ((c3 ^ 0x80) < 0x40)
+                        {
+                          if (pwc != NULL)
+                            *pwc = ((unsigned int) (c & 0x0f) << 12)
+                                   | ((unsigned int) (c2 ^ 0x80) << 6)
+                                   | (unsigned int) (c3 ^ 0x80);
+                          return 3;
+                        }
+                      else
+                        goto invalid;
+                    }
+                  else
+                    goto invalid;
+                }
+              else if (n == 2)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xe1 || c2 >= 0xa0)
+                      && (c != 0xed || c2 < 0xa0))
+                    {
+                      ps->__count = 0x0302;
+                      ps->__value.__wch = ((unsigned int) (c & 0x0f) << 12)
+                                          | ((unsigned int) (c2 ^ 0x80) << 6);
+                      return (size_t)(-2);
+                    }
+                  else
+                    goto invalid;
+                }
+              else /* n == 1 */
+                {
+                  ps->__count = 0x0301;
+                  ps->__value.__wch = (unsigned int) (c & 0x0f) << 12;
+                  return (size_t)(-2);
+                }
+            }
+          else if (c <= 0xf4)
+            {
+              if (n >= 4)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xf1 || c2 >= 0x90)
+                      && (c < 0xf4 || (c == 0xf4 && c2 < 0x90)))
+                    {
+                      unsigned char c3 = (unsigned char) s[2];
+
+                      if ((c3 ^ 0x80) < 0x40)
+                        {
+                          unsigned char c4 = (unsigned char) s[3];
+
+                          if ((c4 ^ 0x80) < 0x40)
+                            {
+                              if (pwc != NULL)
+                                *pwc = ((unsigned int) (c & 0x07) << 18)
+                                       | ((unsigned int) (c2 ^ 0x80) << 12)
+                                       | ((unsigned int) (c3 ^ 0x80) << 6)
+                                       | (unsigned int) (c4 ^ 0x80);
+                              return 4;
+                            }
+                          else
+                            goto invalid;
+                        }
+                      else
+                        goto invalid;
+                    }
+                  else
+                    goto invalid;
+                }
+              else if (n == 3)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xf1 || c2 >= 0x90)
+                      && (c < 0xf4 || (c == 0xf4 && c2 < 0x90)))
+                    {
+                      unsigned char c3 = (unsigned char) s[2];
+
+                      if ((c3 ^ 0x80) < 0x40)
+                        {
+                          ps->__count = 0x0403;
+                          ps->__value.__wch = ((unsigned int) (c & 0x07) << 18)
+                                              | ((unsigned int) (c2 ^ 0x80) << 12)
+                                              | ((unsigned int) (c3 ^ 0x80) << 6);
+                          return (size_t)(-2);
+                        }
+                      else
+                        goto invalid;
+                    }
+                  else
+                    goto invalid;
+                }
+              else if (n == 2)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xf1 || c2 >= 0x90)
+                      && (c < 0xf4 || (c == 0xf4 && c2 < 0x90)))
+                    {
+                      ps->__count = 0x0402;
+                      ps->__value.__wch = ((unsigned int) (c & 0x07) << 18)
+                                          | ((unsigned int) (c2 ^ 0x80) << 12);
+                      return (size_t)(-2);
+                    }
+                  else
+                    goto invalid;
+                }
+              else /* n == 1 */
+                {
+                  ps->__count = 0x0401;
+                  ps->__value.__wch = (unsigned int) (c & 0x07) << 18;
+                  return (size_t)(-2);
+                }
+            }
+          else
+            goto invalid;
+        }
+      else
+        goto invalid;
+    }
+  else
+    {
+      size_t expected = ps->__count >> 8;
+      if (!(expected > read && expected <= 4))
+        abort ();
+      wc = ps->__value.__wch;
+      {
+        unsigned char c = (unsigned char) s[0];
+        if (!(c >= 0x80 && c < 0xc0))
+          goto invalid;
+        c = c & 0x3f;
+        wc |= c << (6 * (expected - read - 1));
+      }
+      if (read == 1)
+        {
+          if (expected == 3)
+            {
+              if (!(wc >= 0x800 && !(wc >= 0xd800 && wc < 0xe000)))
+                goto invalid;
+            }
+          if (expected == 4)
+            {
+              if (!(wc >= 0x10000 && wc < 0x110000))
+                goto invalid;
+            }
+        }
+      size_t orig_read = read;
+      for (;;)
+        {
+          read++;
+          if (read == expected)
+            {
+              if (pwc != NULL)
+                *pwc = wc;
+              ps->__count = 0;
+              return read - orig_read;
+            }
+          n--;
+          if (n == 0)
+            break;
+          s++;
+          {
+            unsigned char c = (unsigned char) s[0];
+            if (!(c >= 0x80 && c < 0xc0))
+              goto invalid;
+            c = c & 0x3f;
+            wc |= c << (6 * (expected - read - 1));
+          }
+        }
+      ps->__count = (expected << 8) | read;
+      ps->__value.__wch = wc;
+      return (size_t)(-2);
+    }
+
+ invalid:
+  errno = EILSEQ;
+  /* The conversion state is undefined, says POSIX.  */
+  return (size_t)(-1);
+}
+
+#endif
+
+#if defined __APPLE__ && defined __MACH__ /* macOS 10.13 */
+
+static mbstate_t internal_state;
+
+typedef struct
+{
+  unsigned int wc_part; /* Part of wc, (number of bytes read) * 6 bits */
+  unsigned int remaining; /* Number of remaining bytes */
+  /* In Mac OS X 10.13: 0xBF80 if (number of bytes read) == 1, otherwise 0.
+     In Mac OS X 10.5: 0x80 or 0x800 or 0x10000, depending on number of total bytes expected.  */
+  unsigned int flag;
+} real_mbstate_t;
+
+static size_t
+utf8_mbrtowc (wchar_t *pwc, const char *s, size_t n, mbstate_t *ps)
+{
+  if (s == NULL)
+    {
+      pwc = NULL;
+      s = "";
+      n = 1;
+    }
+
+  if (n == 0)
+    return (size_t)(-2);
+
+  /* Here n > 0.  */
+
+  if (ps == NULL)
+    ps = &internal_state;
+
+  real_mbstate_t *pstate = (real_mbstate_t *) ps;
+  size_t remaining = pstate->remaining;
+  unsigned int wc;
+
+  if (remaining == 0)
+    {
+      if (!(pstate->wc_part == 0 && pstate->flag == 0))
+        abort ();
+      unsigned char c = (unsigned char) s[0];
+      if (c < 0x80)
+        {
+          if (pwc != NULL)
+            *pwc = c;
+          return 1;
+        }
+      else if (c >= 0xc2)
+        {
+          if (c < 0xe0)
+            {
+              if (n >= 2)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40)
+                    {
+                      if (pwc != NULL)
+                        *pwc = ((unsigned int) (c & 0x1f) << 6)
+                               | (unsigned int) (c2 ^ 0x80);
+                      return 2;
+                    }
+                  else
+                    goto invalid;
+                }
+              else /* n == 1 */
+                {
+                  pstate->wc_part = (unsigned int) (c & 0x1f);
+                  pstate->remaining = 1;
+                  pstate->flag = 0xBF80;
+                  return (size_t)(-2);
+                }
+            }
+          else if (c < 0xf0)
+            {
+              if (n >= 3)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xe1 || c2 >= 0xa0)
+                      && (c != 0xed || c2 < 0xa0))
+                    {
+                      unsigned char c3 = (unsigned char) s[2];
+
+                      if ((c3 ^ 0x80) < 0x40)
+                        {
+                          if (pwc != NULL)
+                            *pwc = ((unsigned int) (c & 0x0f) << 12)
+                                   | ((unsigned int) (c2 ^ 0x80) << 6)
+                                   | (unsigned int) (c3 ^ 0x80);
+                          return 3;
+                        }
+                      else
+                        goto invalid;
+                    }
+                  else
+                    goto invalid;
+                }
+              else if (n == 2)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xe1 || c2 >= 0xa0)
+                      && (c != 0xed || c2 < 0xa0))
+                    {
+                      pstate->wc_part = ((unsigned int) (c & 0x0f) << 6)
+                                        | (unsigned int) (c2 ^ 0x80);
+                      pstate->remaining = 1;
+                      pstate->flag = 0;
+                      return (size_t)(-2);
+                    }
+                  else
+                    goto invalid;
+                }
+              else /* n == 1 */
+                {
+                  pstate->wc_part = (unsigned int) (c & 0x0f);
+                  pstate->remaining = 2;
+                  pstate->flag = 0xBF80;
+                  return (size_t)(-2);
+                }
+            }
+          else if (c <= 0xf4)
+            {
+              if (n >= 4)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xf1 || c2 >= 0x90)
+                      && (c < 0xf4 || (c == 0xf4 && c2 < 0x90)))
+                    {
+                      unsigned char c3 = (unsigned char) s[2];
+
+                      if ((c3 ^ 0x80) < 0x40)
+                        {
+                          unsigned char c4 = (unsigned char) s[3];
+
+                          if ((c4 ^ 0x80) < 0x40)
+                            {
+                              if (pwc != NULL)
+                                *pwc = ((unsigned int) (c & 0x07) << 18)
+                                       | ((unsigned int) (c2 ^ 0x80) << 12)
+                                       | ((unsigned int) (c3 ^ 0x80) << 6)
+                                       | (unsigned int) (c4 ^ 0x80);
+                              return 4;
+                            }
+                          else
+                            goto invalid;
+                        }
+                      else
+                        goto invalid;
+                    }
+                  else
+                    goto invalid;
+                }
+              else if (n == 3)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xf1 || c2 >= 0x90)
+                      && (c < 0xf4 || (c == 0xf4 && c2 < 0x90)))
+                    {
+                      unsigned char c3 = (unsigned char) s[2];
+
+                      if ((c3 ^ 0x80) < 0x40)
+                        {
+                          pstate->wc_part = ((unsigned int) (c & 0x07) << 12)
+                                            | ((unsigned int) (c2 ^ 0x80) << 6)
+                                            | (unsigned int) (c3 ^ 0x80);
+                          pstate->remaining = 1;
+                          pstate->flag = 0;
+                          return (size_t)(-2);
+                        }
+                      else
+                        goto invalid;
+                    }
+                  else
+                    goto invalid;
+                }
+              else if (n == 2)
+                {
+                  unsigned char c2 = (unsigned char) s[1];
+
+                  if ((c2 ^ 0x80) < 0x40
+                      && (c >= 0xf1 || c2 >= 0x90)
+                      && (c < 0xf4 || (c == 0xf4 && c2 < 0x90)))
+                    {
+                      pstate->wc_part = ((unsigned int) (c & 0x07) << 6)
+                                        | (unsigned int) (c2 ^ 0x80);
+                      pstate->remaining = 2;
+                      pstate->flag = 0;
+                      return (size_t)(-2);
+                    }
+                  else
+                    goto invalid;
+                }
+              else /* n == 1 */
+                {
+                  pstate->wc_part = (unsigned int) (c & 0x07);
+                  pstate->remaining = 3;
+                  pstate->flag = 0xBF80;
+                  return (size_t)(-2);
+                }
+            }
+          else
+            goto invalid;
+        }
+      else
+        goto invalid;
+    }
+  else
+    {
+      if (!(remaining <= 3))
+        abort ();
+      wc = pstate->wc_part << (6 * pstate->remaining);
+      {
+        unsigned char c = (unsigned char) s[0];
+        if (!(c >= 0x80 && c < 0xc0))
+          goto invalid;
+        c = c & 0x3f;
+        wc |= c << (6 * (remaining - 1));
+      }
+      if (pstate->flag)
+        {
+          if (remaining == 2)
+            {
+              if (!(wc >= 0x800 && !(wc >= 0xd800 && wc < 0xe000)))
+                goto invalid;
+            }
+          if (remaining == 3)
+            {
+              if (!(wc >= 0x10000 && wc < 0x110000))
+                goto invalid;
+            }
+        }
+      const char *orig_s = s;
+      for (;;)
+        {
+          s++;
+          remaining--;
+          if (remaining == 0)
+            {
+              if (pwc != NULL)
+                *pwc = wc;
+              pstate->wc_part = 0;
+              pstate->remaining = 0;
+              pstate->flag = 0;
+              return s - orig_s;
+            }
+          n--;
+          if (n == 0)
+            break;
+          {
+            unsigned char c = (unsigned char) s[0];
+            if (!(c >= 0x80 && c < 0xc0))
+              goto invalid;
+            c = c & 0x3f;
+            wc |= c << (6 * (remaining - 1));
+          }
+        }
+      pstate->wc_part = wc >> (6 * remaining);
+      pstate->remaining = remaining;
+      pstate->flag = 0;
+      return (size_t)(-2);
+    }
+
+ invalid:
+  errno = EILSEQ;
+  /* The conversion state is undefined, says POSIX.  */
+  return (size_t)(-1);
+}
+
+#endif
+
+mbrtowc_func_t
+get_optimized_mbrtowc (void)
+{
+#if __GLIBC__ >= 2 || (defined __APPLE__ && defined __MACH__)
+  const char *encoding = locale_charset ();
+
+  if (strcmp (encoding, "UTF-8") == 0)
+    return utf8_mbrtowc;
+  else
+#endif
+    /* No optimization.  */
+    return mbrtowc;
+}
diff --git a/lib/mbsinit-factory.c b/lib/mbsinit-factory.c
new file mode 100644
index 0000000..137cd23
--- /dev/null
+++ b/lib/mbsinit-factory.c
@@ -0,0 +1,28 @@
+/* Factory that produces an mbsinit-like function for a given locale.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   Written by Bruno Haible <br...@clisp.org>, 2018.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <wchar.h>
+
+mbsinit_func_t
+get_optimized_mbsinit (void)
+{
+  /* No optimization is needed.  */
+  return mbsinit;
+}
diff --git a/lib/mbsreset-factory.c b/lib/mbsreset-factory.c
new file mode 100644
index 0000000..2114027
--- /dev/null
+++ b/lib/mbsreset-factory.c
@@ -0,0 +1,65 @@
+/* Factory that produces an mbsreset-like function for a given locale.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   Written by Bruno Haible <br...@clisp.org>, 2018.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <wchar.h>
+
+#include <string.h>
+
+#include "localcharset.h"
+
+/* sizeof (mbstate_t) is 128 on macOS.  But only the first few bytes of the
+   state actually matter.  */
+
+#if defined __APPLE__ && defined __MACH__
+
+typedef struct
+{
+  unsigned int wc_part;
+  unsigned int remaining;
+  unsigned int flag;
+} real_mbstate_t;
+
+static void
+utf8_mbsreset (mbstate_t *ps)
+{
+  memset (ps, '\0', sizeof (real_mbstate_t));
+}
+
+#endif
+
+static void
+generic_mbsreset (mbstate_t *ps)
+{
+  memset (ps, '\0', sizeof (mbstate_t));
+}
+
+mbsreset_func_t
+get_optimized_mbsreset (void)
+{
+#if defined __APPLE__ && defined __MACH__
+  const char *encoding = locale_charset ();
+
+  if (strcmp (encoding, "UTF-8") == 0)
+    return utf8_mbsreset;
+  else
+#endif
+    /* No optimization.  */
+    return generic_mbsreset;
+}
diff --git a/lib/wchar.in.h b/lib/wchar.in.h
index 655340c..679992a 100644
--- a/lib/wchar.in.h
+++ b/lib/wchar.in.h
@@ -1067,6 +1067,25 @@ _GL_WARN_ON_USE (wcsftime, "wcsftime is unportable - "
 #endif
 
 
+/* Factories that produce functions for a given locale.  */
+#if @GNULIB_MBRTOWC_FACTORY@
+typedef size_t (*mbrtowc_func_t) (wchar_t *, const char *, size_t, mbstate_t *);
+_GL_FUNCDECL_SYS (get_optimized_mbrtowc, mbrtowc_func_t, (void));
+#endif
+#if @GNULIB_MBSINIT_FACTORY@
+typedef int (*mbsinit_func_t) (const mbstate_t *);
+_GL_FUNCDECL_SYS (get_optimized_mbsinit, mbsinit_func_t, (void));
+#endif
+#if @GNULIB_MBSRESET_FACTORY@
+typedef void (*mbsreset_func_t) (mbstate_t *);
+_GL_FUNCDECL_SYS (get_optimized_mbsreset, mbsreset_func_t, (void));
+#endif
+#if @GNULIB_WCWIDTH_FACTORY@
+typedef int (*wcwidth_func_t) (wchar_t);
+_GL_FUNCDECL_SYS (get_optimized_wcwidth, wcwidth_func_t, (void));
+#endif
+
+
 #endif /* _@GUARD_PREFIX@_WCHAR_H */
 #endif /* _@GUARD_PREFIX@_WCHAR_H */
 #endif
diff --git a/lib/wcwidth-factory.c b/lib/wcwidth-factory.c
new file mode 100644
index 0000000..611bf40
--- /dev/null
+++ b/lib/wcwidth-factory.c
@@ -0,0 +1,56 @@
+/* Factory that produces a wcwidth-like function for a given locale.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+   Written by Bruno Haible <br...@clisp.org>, 2018.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <wchar.h>
+
+#include <string.h>
+
+#include "localcharset.h"
+#include "uniwidth.h"
+
+static int
+utf8_wcwidth (wchar_t wc)
+{
+  return uc_width (wc, "UTF-8");
+}
+
+#if !HAVE_WCWIDTH
+static int
+fallback_wcwidth (wchar_t wc)
+{
+  return wc == 0 ? 0 : iswprint (wc) ? 1 : -1;
+}
+#endif
+
+wcwidth_func_t
+get_optimized_wcwidth (void)
+{
+  const char *encoding = locale_charset ();
+
+  if (strcmp (encoding, "UTF-8") == 0)
+    return utf8_wcwidth;
+  else
+    /* No optimization.  */
+#if HAVE_WCWIDTH
+    return wcwidth;
+#else
+    return fallback_wcwidth;
+#endif
+}
diff --git a/m4/wchar_h.m4 b/m4/wchar_h.m4
index 416e0a1..ca415a9 100644
--- a/m4/wchar_h.m4
+++ b/m4/wchar_h.m4
@@ -7,7 +7,7 @@ dnl with or without modifications, as long as this notice is preserved.
 
 dnl Written by Eric Blake.
 
-# wchar_h.m4 serial 42
+# wchar_h.m4 serial 43
 
 AC_DEFUN([gl_WCHAR_H],
 [
@@ -180,6 +180,10 @@ AC_DEFUN([gl_WCHAR_H_DEFAULTS],
   GNULIB_WCSTOK=0;      AC_SUBST([GNULIB_WCSTOK])
   GNULIB_WCSWIDTH=0;    AC_SUBST([GNULIB_WCSWIDTH])
   GNULIB_WCSFTIME=0;    AC_SUBST([GNULIB_WCSFTIME])
+  GNULIB_MBRTOWC_FACTORY=0;  AC_SUBST([GNULIB_MBRTOWC_FACTORY])
+  GNULIB_MBSINIT_FACTORY=0;  AC_SUBST([GNULIB_MBSINIT_FACTORY])
+  GNULIB_MBSRESET_FACTORY=0; AC_SUBST([GNULIB_MBSRESET_FACTORY])
+  GNULIB_WCWIDTH_FACTORY=0;  AC_SUBST([GNULIB_WCWIDTH_FACTORY])
   dnl Assume proper GNU behavior unless another module says otherwise.
   HAVE_BTOWC=1;         AC_SUBST([HAVE_BTOWC])
   HAVE_MBSINIT=1;       AC_SUBST([HAVE_MBSINIT])
diff --git a/modules/mbrtowc-factory b/modules/mbrtowc-factory
new file mode 100644
index 0000000..90d348d
--- /dev/null
+++ b/modules/mbrtowc-factory
@@ -0,0 +1,24 @@
+Description:
+Factory that produces an mbrtowc-like function for a given locale.
+
+Files:
+lib/mbrtowc-factory.c
+
+Depends-on:
+wchar
+mbrtowc
+
+configure.ac:
+gl_WCHAR_MODULE_INDICATOR([mbrtowc-factory])
+
+Makefile.am:
+lib_SOURCES += mbrtowc-factory.c
+
+Include:
+<wchar.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/mbsinit-factory b/modules/mbsinit-factory
new file mode 100644
index 0000000..5c311a8
--- /dev/null
+++ b/modules/mbsinit-factory
@@ -0,0 +1,24 @@
+Description:
+Factory that produces an mbsinit-like function for a given locale.
+
+Files:
+lib/mbsinit-factory.c
+
+Depends-on:
+wchar
+mbsinit
+
+configure.ac:
+gl_WCHAR_MODULE_INDICATOR([mbsinit-factory])
+
+Makefile.am:
+lib_SOURCES += mbsinit-factory.c
+
+Include:
+<wchar.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/mbsreset-factory b/modules/mbsreset-factory
new file mode 100644
index 0000000..dd08563
--- /dev/null
+++ b/modules/mbsreset-factory
@@ -0,0 +1,23 @@
+Description:
+Factory that produces an mbsreset-like function for a given locale.
+
+Files:
+lib/mbsreset-factory.c
+
+Depends-on:
+wchar
+
+configure.ac:
+gl_WCHAR_MODULE_INDICATOR([mbsreset-factory])
+
+Makefile.am:
+lib_SOURCES += mbsreset-factory.c
+
+Include:
+<wchar.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/modules/wchar b/modules/wchar
index 31770d8..3a83b63 100644
--- a/modules/wchar
+++ b/modules/wchar
@@ -73,6 +73,10 @@ wchar.h: wchar.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H)
 	      -e 's/@''GNULIB_WCSTOK''@/$(GNULIB_WCSTOK)/g' \
 	      -e 's/@''GNULIB_WCSWIDTH''@/$(GNULIB_WCSWIDTH)/g' \
 	      -e 's/@''GNULIB_WCSFTIME''@/$(GNULIB_WCSFTIME)/g' \
+	      -e 's/@''GNULIB_MBRTOWC_FACTORY''@/$(GNULIB_MBRTOWC_FACTORY)/g' \
+	      -e 's/@''GNULIB_MBSINIT_FACTORY''@/$(GNULIB_MBSINIT_FACTORY)/g' \
+	      -e 's/@''GNULIB_MBSRESET_FACTORY''@/$(GNULIB_MBSRESET_FACTORY)/g' \
+	      -e 's/@''GNULIB_WCWIDTH_FACTORY''@/$(GNULIB_WCWIDTH_FACTORY)/g' \
 	      < $(srcdir)/wchar.in.h | \
 	  sed -e 's|@''HAVE_WINT_T''@|$(HAVE_WINT_T)|g' \
 	      -e 's|@''HAVE_BTOWC''@|$(HAVE_BTOWC)|g' \
diff --git a/modules/wcwidth-factory b/modules/wcwidth-factory
new file mode 100644
index 0000000..b91e76c
--- /dev/null
+++ b/modules/wcwidth-factory
@@ -0,0 +1,24 @@
+Description:
+Factory that produces a wcwidth-like function for a given locale.
+
+Files:
+lib/wcwidth-factory.c
+
+Depends-on:
+wchar
+wcwidth
+
+configure.ac:
+gl_WCHAR_MODULE_INDICATOR([wcwidth-factory])
+
+Makefile.am:
+lib_SOURCES += wcwidth-factory.c
+
+Include:
+<wchar.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
diff --git a/bootstrap.conf b/bootstrap.conf
index 8c2265f..70bced4 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -156,9 +156,12 @@ gnulib_modules="
   manywarnings
   mbrlen
   mbrtowc
+  mbrtowc-factory
   mbsalign
   mbschr
+  mbsinit-factory
   mbslen
+  mbsreset-factory
   mbswidth
   memcasecmp
   memchr
@@ -273,6 +276,7 @@ gnulib_modules="
   version-etc-fsf
   wcswidth
   wcwidth
+  wcwidth-factory
   winsz-ioctl
   winsz-termios
   write-any-file
diff --git a/src/wc.c b/src/wc.c
index 0c72042..daf596a 100644
--- a/src/wc.c
+++ b/src/wc.c
@@ -197,6 +199,13 @@ write_counts (uintmax_t lines,
 static bool
 wc (int fd, char const *file_x, struct fstatus *fstatus, off_t current_pos)
 {
+  mbsreset_func_t mbsreset = get_optimized_mbsreset ();
+#undef mbsinit
+  mbsinit_func_t mbsinit = get_optimized_mbsinit ();
+#undef mbrtowc
+  mbrtowc_func_t mbrtowc = get_optimized_mbrtowc ();
+#undef wcwidth
+  wcwidth_func_t wcwidth = get_optimized_wcwidth ();
   bool ok = true;
   char buf[BUFFER_SIZE + 1];
   size_t bytes_read;
@@ -344,7 +353,7 @@ wc (int fd, char const *file_x, struct fstatus *fstatus, off_t current_pos)
     {
       bool in_word = false;
       uintmax_t linepos = 0;
-      mbstate_t state = { 0, };
+      mbstate_t state;
       bool in_shift = false;
 # if SUPPORT_OLD_MBRTOWC
       /* Back-up the state before each multibyte character conversion and
@@ -359,6 +368,8 @@ wc (int fd, char const *file_x, struct fstatus *fstatus, off_t current_pos)
       const size_t prev = 0;
 # endif
 
+      mbsreset (&state);
+
       while ((bytes_read = safe_read (fd, buf + prev, BUFFER_SIZE - prev)) > 0)
         {
           const char *p;

Reply via email to