From 07d4b44bca1eb9c476d0f296c10066068882d334 Mon Sep 17 00:00:00 2001
From: Grisha Levit <grishalevit@gmail.com>
Date: Mon, 5 Feb 2024 20:37:52 -0500
Subject: [PATCH] use unlocked stdio functions

Bash makes many calls to stdio functions that may have unlocked_stdio(3)
equivalents. Since the locking functionality provided by the regular
versions is only useful in multi-threaded applications, it probably makes
sense for Bash to use the *_unlocked versions where available.

E.g. in situations where execution time is dominated by putchar(3) calls,
using putchar_unlocked(3) can offer an ~2X speedup:

    $ fun() { printf -v X "%$((1<<24))s" && time printf "$X"; }
    $ export -f fun

    $ bash.old -c fun >/dev/null
    real    0m0.720s
    user    0m0.669s
    sys     0m0.048s
    $ bash.new -c fun >/dev/null
    real    0m0.370s
    user    0m0.301s
    sys     0m0.067s

This patch is basically an adoption of the unlocked-io module from Gnulib.

m4/unlocked-io.m4
- BASH_FUNC_UNLOCKED_IO: new AC_DEFUN, adapted from Gnulib

config.h.in
- HAVE_DECL_*_UNLOCKED: for checking by BASH_FUNC_UNLOCKED_IO

configure.ac
- include m4/unlocked-io.m4, use BASH_FUNC_UNLOCKED_IO

include/unlocked-io.h
- clearerr, feof, ferror, fflush, fgets, fputc, fputs, fread, fwrite,
  getc, getchar, putc, putchar: replace with *_unlocked versions if
  available. Adopted from Gnulib.

general.h
- include unlocked-io.h
---
 config.h.in           |  16 +++++
 configure.ac          |   2 +
 general.h             |   1 +
 include/unlocked-io.h | 138 ++++++++++++++++++++++++++++++++++++++++++
 m4/unlocked-io.m4     |  31 ++++++++++
 5 files changed, 188 insertions(+)
 create mode 100644 include/unlocked-io.h
 create mode 100644 m4/unlocked-io.m4

diff --git a/config.h.in b/config.h.in
index f0c8be79..93536427 100644
--- a/config.h.in
+++ b/config.h.in
@@ -510,6 +510,22 @@
 #undef HAVE_DECL_STRTOULL
 #undef HAVE_DECL_STRTOUMAX
 
+/* These are checked with BASH_FUNC_UNLOCKED_IO */
+
+#undef HAVE_DECL_CLEARERR_UNLOCKED
+#undef HAVE_DECL_FEOF_UNLOCKED
+#undef HAVE_DECL_FERROR_UNLOCKED
+#undef HAVE_DECL_FFLUSH_UNLOCKED
+#undef HAVE_DECL_FGETS_UNLOCKED
+#undef HAVE_DECL_FPUTC_UNLOCKED
+#undef HAVE_DECL_FPUTS_UNLOCKED
+#undef HAVE_DECL_FREAD_UNLOCKED
+#undef HAVE_DECL_FWRITE_UNLOCKED
+#undef HAVE_DECL_GETC_UNLOCKED
+#undef HAVE_DECL_GETCHAR_UNLOCKED
+#undef HAVE_DECL_PUTC_UNLOCKED
+#undef HAVE_DECL_PUTCHAR_UNLOCKED
+
 /* Characteristics of system calls and C library functions. */
 
 /* Define if the `getpgrp' function takes no argument.  */
diff --git a/configure.ac b/configure.ac
index c25a8088..29add098 100644
--- a/configure.ac
+++ b/configure.ac
@@ -717,6 +717,7 @@ m4_include([m4/stat-time.m4])
 m4_include([m4/timespec.m4])
 
 m4_include([m4/strtoimax.m4])
+m4_include([m4/unlocked-io.m4])
 
 dnl include files for gettext
 
@@ -1092,6 +1093,7 @@ BASH_FUNC_SNPRINTF
 BASH_FUNC_VSNPRINTF
 
 BASH_FUNC_STRTOIMAX
+BASH_FUNC_UNLOCKED_IO
 
 dnl If putenv or unsetenv is not present, set the right define so the
 dnl prototype and declaration in lib/sh/getenv.c will be standard-conformant
diff --git a/general.h b/general.h
index ecbc8877..6f7a3aaa 100644
--- a/general.h
+++ b/general.h
@@ -43,6 +43,7 @@
 #  include <limits.h>
 #endif
 
+#include "unlocked-io.h"
 #include "xmalloc.h"
 
 /* Hardly used anymore */
diff --git a/include/unlocked-io.h b/include/unlocked-io.h
new file mode 100644
index 00000000..659e7986
--- /dev/null
+++ b/include/unlocked-io.h
@@ -0,0 +1,138 @@
+/* Prefer faster, non-thread-safe stdio functions if available.
+
+   Copyright (C) 2001-2004, 2009-2024 Free Software Foundation, Inc.
+
+   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/>.  */
+
+/* Written by Jim Meyering.  */
+
+/* Adapted from gnulib:lib/unlocked-io.h */
+
+#ifndef UNLOCKED_IO_H
+# define UNLOCKED_IO_H 1
+
+/* These are wrappers for functions/macros from the GNU C library, and
+   from other C libraries supporting POSIX's optional thread-safe functions.
+
+   The standard I/O functions are thread-safe.  These *_unlocked ones are
+   more efficient but not thread-safe.  That they're not thread-safe is
+   fine since all of the applications in this package are single threaded.
+
+   Also, some code that is shared with the GNU C library may invoke
+   the *_unlocked functions directly.  On hosts that lack those
+   functions, invoke the non-thread-safe versions instead.  */
+
+# include <stdio.h>
+
+# if HAVE_DECL_CLEARERR_UNLOCKED || defined clearerr_unlocked
+#  undef clearerr
+#  define clearerr(x) clearerr_unlocked (x)
+# else
+#  define clearerr_unlocked(x) clearerr (x)
+# endif
+
+# if HAVE_DECL_FEOF_UNLOCKED || defined feof_unlocked
+#  undef feof
+#  define feof(x) feof_unlocked (x)
+# else
+#  define feof_unlocked(x) feof (x)
+# endif
+
+# if HAVE_DECL_FERROR_UNLOCKED || defined ferror_unlocked
+#  undef ferror
+#  define ferror(x) ferror_unlocked (x)
+# else
+#  define ferror_unlocked(x) ferror (x)
+# endif
+
+# if HAVE_DECL_FFLUSH_UNLOCKED || defined fflush_unlocked
+#  undef fflush
+#  define fflush(x) fflush_unlocked (x)
+# else
+#  define fflush_unlocked(x) fflush (x)
+# endif
+
+# if HAVE_DECL_FGETS_UNLOCKED || defined fgets_unlocked
+#  undef fgets
+#  define fgets(x,y,z) fgets_unlocked (x,y,z)
+# else
+#  define fgets_unlocked(x,y,z) fgets (x,y,z)
+# endif
+
+# if HAVE_DECL_FPUTC_UNLOCKED || defined fputc_unlocked
+#  undef fputc
+#  define fputc(x,y) fputc_unlocked (x,y)
+# else
+#  define fputc_unlocked(x,y) fputc (x,y)
+# endif
+
+# if HAVE_DECL_FPUTS_UNLOCKED || defined fputs_unlocked
+#  undef fputs
+#  define fputs(x,y) fputs_unlocked (x,y)
+# else
+#  define fputs_unlocked(x,y) fputs (x,y)
+# endif
+
+# if HAVE_DECL_FREAD_UNLOCKED || defined fread_unlocked
+#  undef fread
+#  define fread(w,x,y,z) fread_unlocked (w,x,y,z)
+# else
+#  define fread_unlocked(w,x,y,z) fread (w,x,y,z)
+# endif
+
+# if HAVE_DECL_FWRITE_UNLOCKED || defined fwrite_unlocked
+#  undef fwrite
+#  define fwrite(w,x,y,z) fwrite_unlocked (w,x,y,z)
+# else
+#  define fwrite_unlocked(w,x,y,z) fwrite (w,x,y,z)
+# endif
+
+# if HAVE_DECL_GETC_UNLOCKED || defined getc_unlocked
+#  undef getc
+#  define getc(x) getc_unlocked (x)
+# else
+#  define getc_unlocked(x) getc (x)
+# endif
+
+# if HAVE_DECL_GETCHAR_UNLOCKED || defined getchar_unlocked
+#  undef getchar
+#  define getchar() getchar_unlocked ()
+# else
+#  define getchar_unlocked() getchar ()
+# endif
+
+# if HAVE_DECL_PUTC_UNLOCKED || defined putc_unlocked
+#  undef putc
+#  define putc(x,y) putc_unlocked (x,y)
+# else
+#  define putc_unlocked(x,y) putc (x,y)
+# endif
+
+# if HAVE_DECL_PUTCHAR_UNLOCKED || defined putchar_unlocked
+#  undef putchar
+#  define putchar(x) putchar_unlocked (x)
+# else
+#  define putchar_unlocked(x) putchar (x)
+# endif
+
+# undef flockfile
+# define flockfile(x) ((void) 0)
+
+# undef ftrylockfile
+# define ftrylockfile(x) 0
+
+# undef funlockfile
+# define funlockfile(x) ((void) 0)
+
+#endif /* UNLOCKED_IO_H */
diff --git a/m4/unlocked-io.m4 b/m4/unlocked-io.m4
new file mode 100644
index 00000000..ff14f349
--- /dev/null
+++ b/m4/unlocked-io.m4
@@ -0,0 +1,31 @@
+# unlocked-io.m4 serial 16
+
+# Copyright (C) 1998-2006, 2009-2024 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+dnl From Jim Meyering.
+dnl
+dnl Adapted from gnulib:m4/unlocked-io.m4
+AC_DEFUN([BASH_FUNC_UNLOCKED_IO],
+[
+  dnl Persuade glibc and Solaris <stdio.h> to declare
+  dnl fgets_unlocked(), fputs_unlocked() etc.
+  AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
+
+  AC_CHECK_DECLS_ONCE([clearerr_unlocked])
+  AC_CHECK_DECLS_ONCE([feof_unlocked])
+  AC_CHECK_DECLS_ONCE([ferror_unlocked])
+  AC_CHECK_DECLS_ONCE([fflush_unlocked])
+  AC_CHECK_DECLS_ONCE([fgets_unlocked])
+  AC_CHECK_DECLS_ONCE([fputc_unlocked])
+  AC_CHECK_DECLS_ONCE([fputs_unlocked])
+  AC_CHECK_DECLS_ONCE([fread_unlocked])
+  AC_CHECK_DECLS_ONCE([fwrite_unlocked])
+  AC_CHECK_DECLS_ONCE([getc_unlocked])
+  AC_CHECK_DECLS_ONCE([getchar_unlocked])
+  AC_CHECK_DECLS_ONCE([putc_unlocked])
+  AC_CHECK_DECLS_ONCE([putchar_unlocked])
+])
-- 
2.43.0

