Subject: Re: bug#79035: Remove micro-optimization for Funintern for performance

From: Lynn Winebarger <[email-masked]>
Date: Fri, 18 Jul 2025 17:43:56 -0400

On Thu, Jul 17, 2025 at 6:12 PM Lynn Winebarger <[email-masked]> wrote:
>
> On Thu, Jul 17, 2025, 2:51 PM Mattias Engdegård <[email-masked]> wrote:
>>
>> 17 juli 2025 kl. 17.09 skrev Stefan Monnier <[email-masked]>:
>>
>> > Yeah, this static var is ugly, I'm in favor of removing it.
>>
>> Same here. I don't think it was ever meant as an optimisation, just a quick and lazy way to implement Funintern.
>>
>> > BTW, looking at the code of `Funintern` I get the impression that
>> > `oblookup_considering_shorthand` is wrong when the argument is a symbol:
>> > we should take shorthand into account only when passed a string.
>>
>> Right. Funintern isn't often used but we could do a better job here.
>
>
> Well, I certainly have no opinion on the finer points of the semantics. I only tried to eliminate the unnecessary global state without altering the behavior.
> Since I've worked out (with Pip Cet's help) the undocumented contracts with debug and elisp-mode, I'm going to be going through the rest of my patch to rebase it on your recent commits in a more incremental series of changes.  There are some other random bits of detritus in lread that I plan to submit separately from the main patch, just to simplify the larger task a bit.
> Would you (Mattias and Stefan) prefer I submit these bits as a single bug or one bug per change?
>
Since I haven't heard back, I'm just going to revise the patch set
here.  One of the bits I thought was detritus actually depended on one
of the other changes I had made in lread.c, so I only have 2 small
additional changes.  The commit messages are detailed.
0002-Remove-unreferenced-internal-variable.patch eliminates the unused
variable load_convert_to_unibyte and associated unwind-protect.
0003-Move-read-char-and-friends-to-keyboard.c.patch cleans up lread.c
by grouping read-char et al with read-key-sequence in keyboard.c.

Running make check shows no additional failures compared to the baseline.

Lynn
From c6d0f312c6a6b0de219c7b21fa6f7c5de0c6f872 Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Fri, 18 Jul 2025 17:10:49 -0400
Subject: [PATCH 2/3] Remove unreferenced internal variable

(lread.c):  The value of load_convert_to_unibyte does not appear to
be used by any code and appears not to have been since 1998.  Removing
it allows us to dispense with a useless record_unwind.  I changed the
doc-string for eval-buffer to reflect that the UNIBYTE argument is
ignored, which it has effectively been for a long time.  No callers
appear to set it to anything other than nil in any case.
---
 src/lread.c | 34 ++++++++--------------------------
 1 file changed, 8 insertions(+), 26 deletions(-)

diff --git a/src/lread.c b/src/lread.c
index ad25fe052a1..c02060d3ea5 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -238,8 +238,7 @@ #define USE_ANDROID_ASSETS
 static Lisp_Object Vloads_in_progress;
 
 static void readevalloop (Lisp_Object, struct infile *, Lisp_Object, bool,
-                          Lisp_Object, Lisp_Object,
-                          Lisp_Object, Lisp_Object);
+			   Lisp_Object, Lisp_Object, Lisp_Object);
 
 static void build_load_history (Lisp_Object, bool);
 
@@ -1784,14 +1783,14 @@ DEFUN ("load", Fload, Sload, 1, 5, 0,
 
       if (! version || version >= 22)
         readevalloop (Qget_file_char, &input, hist_file_name,
-                      0, Qnil, Qnil, Qnil, Qnil);
+                      0, Qnil, Qnil, Qnil);
       else
         {
           /* We can't handle a file which was compiled with
              byte-compile-dynamic by older version of Emacs.  */
           specbind (Qload_force_doc_strings, Qt);
           readevalloop (Qget_emacs_mule_file_char, &input, hist_file_name,
-                        0, Qnil, Qnil, Qnil, Qnil);
+                        0, Qnil, Qnil, Qnil);
         }
     }
   unbind_to (count, Qnil);
@@ -2376,12 +2375,6 @@ build_load_history (Lisp_Object filename, bool entire)
     }
 }
 
-static void
-readevalloop_1 (int old)
-{
-  load_convert_to_unibyte = old;
-}
-
 /* Signal an `end-of-file' error, if possible with file name
    information.  */
 
@@ -2415,9 +2408,7 @@ readevalloop_eager_expand_eval (Lisp_Object val, Lisp_Object macroexpand)
   return val;
 }
 
-/* UNIBYTE specifies how to set load_convert_to_unibyte
-   for this invocation.
-   READFUN, if non-nil, is used instead of `read'.
+/* READFUN, if non-nil, is used instead of `read'.
 
    START, END specify region to read in current buffer (from eval-region).
    If the input is not from a buffer, they must be nil.  */
@@ -2427,7 +2418,7 @@ readevalloop (Lisp_Object readcharfun,
 	      struct infile *infile0,
 	      Lisp_Object sourcename,
 	      bool printflag,
-	      Lisp_Object unibyte, Lisp_Object readfun,
+	      Lisp_Object readfun,
 	      Lisp_Object start, Lisp_Object end)
 {
   int c;
@@ -2470,8 +2461,6 @@ readevalloop (Lisp_Object readcharfun,
     emacs_abort ();
 
   specbind (Qstandard_input, readcharfun);
-  record_unwind_protect_int (readevalloop_1, load_convert_to_unibyte);
-  load_convert_to_unibyte = !NILP (unibyte);
 
   /* If lexical binding is active (either because it was specified in
      the file's header, or via a buffer-local variable), create an empty
@@ -2630,8 +2619,7 @@ DEFUN ("eval-buffer", Feval_buffer, Seval_buffer, 0, 5, "",
   a value of nil means discard it; anything else is the stream to print to.
   See Info node `(elisp)Output Streams' for details on streams.
 FILENAME specifies the file name to use for `load-history'.
-UNIBYTE, if non-nil, specifies `load-convert-to-unibyte' for this
- invocation.
+UNIBYTE is ignored.
 DO-ALLOW-PRINT, if non-nil, specifies that output functions in the
  evaluated code should work normally even if PRINTFLAG is nil, in
  which case the output is displayed in the echo area.
@@ -2677,7 +2665,7 @@ DEFUN ("eval-buffer", Feval_buffer, Seval_buffer, 0, 5, "",
     specbind (Qlexical_binding, get_lexical_binding (buf, buf));
   BUF_TEMP_SET_PT (XBUFFER (buf), BUF_BEGV (XBUFFER (buf)));
   readevalloop (buf, 0, filename,
-		!NILP (printflag), unibyte, Qnil, Qnil, Qnil);
+		!NILP (printflag), Qnil, Qnil, Qnil);
   return unbind_to (count, Qnil);
 }
 
@@ -2711,7 +2699,7 @@ DEFUN ("eval-region", Feval_region, Seval_region, 2, 4, "r",
 
   /* `readevalloop' calls functions which check the type of start and end.  */
   readevalloop (cbuf, 0, BVAR (XBUFFER (cbuf), filename),
-		!NILP (printflag), Qnil, read_function,
+		!NILP (printflag), read_function,
 		start, end);
 
   return unbind_to (count, Qnil);
@@ -6075,12 +6063,6 @@ syms_of_lread (void)
 This is useful when the file being loaded is a temporary copy.  */);
   load_force_doc_strings = 0;
 
-  DEFVAR_BOOL ("load-convert-to-unibyte", load_convert_to_unibyte,
-	       doc: /* Non-nil means `read' converts strings to unibyte whenever possible.
-This is normally bound by `load' and `eval-buffer' to control `read',
-and is not meant for users to change.  */);
-  load_convert_to_unibyte = 0;
-
   DEFVAR_LISP ("source-directory", Vsource_directory,
 	       doc: /* Directory in which Emacs sources were found when Emacs was built.
 You cannot count on them to still be there!  */);
-- 
2.50.0

From c4818ba01f94717876f772f9a0b5e67d2d32d532 Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Wed, 16 Jul 2025 23:04:07 -0400
Subject: [PATCH 1/3] Remove micro-optimization for Funintern

(lread.c): Remove static variable oblookup_last_bucket_number.  Removal
appears to reduce execution time of obarray functions by about 6%, while
the variable was only used to avoid a redundant lookup in the rarely
called Funintern.
---
 src/lread.c | 53 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 33 insertions(+), 20 deletions(-)

diff --git a/src/lread.c b/src/lread.c
index 5b7848c3118..ad25fe052a1 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -4927,10 +4927,6 @@ string_to_number (char const *string, int base, ptrdiff_t *plen)
 
 static Lisp_Object initial_obarray;
 
-/* `oblookup' stores the bucket number here, for the sake of Funintern.  */
-
-static size_t oblookup_last_bucket_number;
-
 static Lisp_Object make_obarray (unsigned bits);
 
 /* Slow path obarray check: return the obarray to use or signal an error.  */
@@ -5141,6 +5137,14 @@ DEFUN ("intern-soft", Fintern_soft, Sintern_soft, 1, 2, 0,
     }
 }
 
+/* Bucket index of the string STR of length SIZE_BYTE bytes in obarray OA.  */
+static ptrdiff_t
+obarray_index (struct Lisp_Obarray *oa, const char *str, ptrdiff_t size_byte)
+{
+  EMACS_UINT hash = hash_char_array (str, size_byte);
+  return knuth_hash (reduce_emacs_uint_to_hash_hash (hash), oa->size_bits);
+}
+
 DEFUN ("unintern", Funintern, Sunintern, 2, 2, 0,
        doc: /* Delete the symbol named NAME, if any, from OBARRAY.
 The value is t if a symbol was found and deleted, nil otherwise.
@@ -5174,14 +5178,15 @@ DEFUN ("unintern", Funintern, Sunintern, 2, 2, 0,
 					SCHARS (string), SBYTES (string),
 					&longhand, &longhand_chars,
 					&longhand_bytes);
-  if (longhand)
-    xfree(longhand);
 
-  if (FIXNUMP (tem))
-    return Qnil;
+  if (FIXNUMP (tem)
   /* If arg was a symbol, don't delete anything but that symbol itself.  */
-  if (BARE_SYMBOL_P (name) && !BASE_EQ (name, tem))
-    return Qnil;
+      || (BARE_SYMBOL_P (name) && !BASE_EQ (name, tem)))
+    {
+      if (longhand)
+	xfree (longhand);
+      return Qnil;
+    }
 
   /* There are plenty of other symbols which will screw up the Emacs
      session if we unintern them, as well as even more ways to use
@@ -5193,7 +5198,24 @@ DEFUN ("unintern", Funintern, Sunintern, 2, 2, 0,
   struct Lisp_Symbol *sym = XBARE_SYMBOL (tem);
   sym->u.s.interned = SYMBOL_UNINTERNED;
 
-  ptrdiff_t idx = oblookup_last_bucket_number;
+  ptrdiff_t idx = -1;
+  {
+    /* Removed static variable oblookup_last_bucket_number micro-optimization
+       Don't use a static variable just to avoid this
+       - how frequently used is unintern that using a static variable
+       to optimize away this redundant index calculation would be
+       worth adding a second reference parameter? */
+    struct Lisp_Obarray *o = XOBARRAY (obarray);
+    if (longhand)
+      {
+	idx = obarray_index (o, longhand, longhand_bytes);
+	xfree (longhand);
+      }
+    else
+      {
+	idx = obarray_index (o,SSDATA (string), SBYTES (string));
+      }
+  }
   Lisp_Object *loc = &XOBARRAY (obarray)->buckets[idx];
 
   eassert (BARE_SYMBOL_P (*loc));
@@ -5218,14 +5240,6 @@ DEFUN ("unintern", Funintern, Sunintern, 2, 2, 0,
 }
 
 
-/* Bucket index of the string STR of length SIZE_BYTE bytes in obarray OA.  */
-static ptrdiff_t
-obarray_index (struct Lisp_Obarray *oa, const char *str, ptrdiff_t size_byte)
-{
-  EMACS_UINT hash = hash_char_array (str, size_byte);
-  return knuth_hash (reduce_emacs_uint_to_hash_hash (hash), oa->size_bits);
-}
-
 /* Return the symbol in OBARRAY whose name matches the string
    of SIZE characters (SIZE_BYTE bytes) at PTR.
    If there is no such symbol, return the integer bucket number of
@@ -5240,7 +5254,6 @@ oblookup (Lisp_Object obarray, register const char *ptr, ptrdiff_t size, ptrdiff
   ptrdiff_t idx = obarray_index (o, ptr, size_byte);
   Lisp_Object bucket = o->buckets[idx];
 
-  oblookup_last_bucket_number = idx;
   if (!BASE_EQ (bucket, make_fixnum (0)))
     {
       Lisp_Object sym = bucket;
-- 
2.50.0

From 546a2b758ed3fa4ff0a64fe71b50410a2ea6c52a Mon Sep 17 00:00:00 2001
From: Onnie Lynn Winebarger <[email-masked]>
Date: Fri, 18 Jul 2025 17:20:49 -0400
Subject: [PATCH 3/3] Move read-char and friends to keyboard.c

(lread.c,keyboard.c) Relocate read-char, read-char-exclusive,
read-event, and supporting functions to keyboard.c with other functions
for processing input events.  Based on the code for Fread treating
'read-char as the nominal name of the input stream for lisp expressions
from users, read-char might have been properly considered part of the
lisp reader.  That nominal input stream has long been replaced by
the lisp function #'read-minibuffer, presumably since blind input of lisp
expressions through read-char would be challenging at the least.  These
functions have been migrated to be close to similar (and more recent
functions) read-key-sequence and read-key-sequence-vector.  This also
reduces the set of functions with access to the private state variables
in lread.c that are used by read, load, eval-buffer, etc.
---
 src/keyboard.c | 270 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/lread.c    | 269 ------------------------------------------------
 2 files changed, 270 insertions(+), 269 deletions(-)

diff --git a/src/keyboard.c b/src/keyboard.c
index b389d98feaa..f954630d8f9 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -10312,6 +10312,271 @@ #define PUSH_C_STR(str, listvar) \
       /* Help char - go round again.  */
     }
 }
+
+/* Get a character from the tty.  */
+
+/* Read input events until we get one that's acceptable for our purposes.
+
+   If NO_SWITCH_FRAME, switch-frame events are stashed
+   until we get a character we like, and then stuffed into
+   unread_switch_frame.
+
+   If ASCII_REQUIRED, check function key events to see
+   if the unmodified version of the symbol has a Qascii_character
+   property, and use that character, if present.
+
+   If ERROR_NONASCII, signal an error if the input we
+   get isn't an ASCII character with modifiers.  If it's false but
+   ASCII_REQUIRED is true, just re-read until we get an ASCII
+   character.
+
+   If INPUT_METHOD, invoke the current input method
+   if the character warrants that.
+
+   If SECONDS is a number, wait that many seconds for input, and
+   return Qnil if no input arrives within that time.
+
+   If text conversion is enabled and ASCII_REQUIRED, temporarily
+   disable any input method which wants to perform edits, unless
+   `disable-inhibit-text-conversion'.  */
+
+static Lisp_Object
+read_filtered_event (bool no_switch_frame, bool ascii_required,
+		     bool error_nonascii, bool input_method, Lisp_Object seconds)
+{
+  Lisp_Object val, delayed_switch_frame;
+  struct timespec end_time;
+#ifdef HAVE_TEXT_CONVERSION
+  specpdl_ref count;
+#endif
+
+#ifdef HAVE_WINDOW_SYSTEM
+  if (display_hourglass_p)
+    cancel_hourglass ();
+#endif
+
+#ifdef HAVE_TEXT_CONVERSION
+  count = SPECPDL_INDEX ();
+
+  /* Don't use text conversion when trying to just read a
+     character.  */
+
+  if (ascii_required && !disable_inhibit_text_conversion)
+    {
+      disable_text_conversion ();
+      record_unwind_protect_void (resume_text_conversion);
+    }
+#endif
+
+  delayed_switch_frame = Qnil;
+
+  /* Compute timeout.  */
+  if (NUMBERP (seconds))
+    {
+      double duration = XFLOATINT (seconds);
+      struct timespec wait_time = dtotimespec (duration);
+      end_time = timespec_add (current_timespec (), wait_time);
+    }
+
+  /* Read until we get an acceptable event.  */
+ retry:
+  do
+    val = read_char (0, Qnil, (input_method ? Qnil : Qt), 0,
+		     NUMBERP (seconds) ? &end_time : NULL);
+  while (FIXNUMP (val) && XFIXNUM (val) == -2); /* wrong_kboard_jmpbuf */
+
+  if (BUFFERP (val))
+    goto retry;
+
+  /* `switch-frame' events are put off until after the next ASCII
+     character.  This is better than signaling an error just because
+     the last characters were typed to a separate minibuffer frame,
+     for example.  Eventually, some code which can deal with
+     switch-frame events will read it and process it.  */
+  if (no_switch_frame
+      && EVENT_HAS_PARAMETERS (val)
+      && EQ (EVENT_HEAD_KIND (EVENT_HEAD (val)), Qswitch_frame))
+    {
+      delayed_switch_frame = val;
+      goto retry;
+    }
+
+  if (ascii_required && !(NUMBERP (seconds) && NILP (val)))
+    {
+      /* Convert certain symbols to their ASCII equivalents.  */
+      if (SYMBOLP (val))
+	{
+	  Lisp_Object tem, tem1;
+	  tem = Fget (val, Qevent_symbol_element_mask);
+	  if (!NILP (tem))
+	    {
+	      tem1 = Fget (Fcar (tem), Qascii_character);
+	      /* Merge this symbol's modifier bits
+		 with the ASCII equivalent of its basic code.  */
+	      if (FIXNUMP (tem1) && FIXNUMP (Fcar (Fcdr (tem))))
+		XSETFASTINT (val, XFIXNUM (tem1) | XFIXNUM (Fcar (Fcdr (tem))));
+	    }
+	}
+
+      /* If we don't have a character now, deal with it appropriately.  */
+      if (!FIXNUMP (val))
+	{
+	  if (error_nonascii)
+	    {
+	      Vunread_command_events = list1 (val);
+	      error ("Non-character input-event");
+	    }
+	  else
+	    goto retry;
+	}
+    }
+
+  if (! NILP (delayed_switch_frame))
+    unread_switch_frame = delayed_switch_frame;
+
+#if 0
+
+#ifdef HAVE_WINDOW_SYSTEM
+  if (display_hourglass_p)
+    start_hourglass ();
+#endif
+
+#endif
+
+#ifdef HAVE_TEXT_CONVERSION
+  return unbind_to (count, val);
+#else
+  return val;
+#endif
+}
+
+DEFUN ("read-char", Fread_char, Sread_char, 0, 3, 0,
+       doc: /* Read a character event from the command input (keyboard or macro).
+Return the character as a number.
+If the event has modifiers, they are resolved and reflected in the
+returned character code if possible (e.g. C-SPC yields 0 and C-a yields 97).
+If some of the modifiers cannot be reflected in the character code, the
+returned value will include those modifiers, and will not be a valid
+character code: it will fail the `characterp' test.  Use `event-basic-type'
+to recover the character code with the modifiers removed.
+
+If the user generates an event which is not a character (i.e. a mouse
+click or function key event), `read-char' signals an error.  As an
+exception, switch-frame events are put off until non-character events
+can be read.
+If you want to read non-character events, or ignore them, call
+`read-event' or `read-char-exclusive' instead.
+
+If the optional argument PROMPT is non-nil, display that as a prompt.
+If PROMPT is nil or the string \"\", the key sequence/events that led
+to the current command is used as the prompt.
+
+If the optional argument INHERIT-INPUT-METHOD is non-nil and some
+input method is turned on in the current buffer, that input method
+is used for reading a character.
+
+If the optional argument SECONDS is non-nil, it should be a number
+specifying the maximum number of seconds to wait for input.  If no
+input arrives in that time, return nil.  SECONDS may be a
+floating-point value.
+
+If `inhibit-interaction' is non-nil, this function will signal an
+`inhibited-interaction' error.  */)
+  (Lisp_Object prompt, Lisp_Object inherit_input_method, Lisp_Object seconds)
+{
+  Lisp_Object val;
+
+  barf_if_interaction_inhibited ();
+
+  if (! NILP (prompt))
+    {
+      cancel_echoing ();
+      message_with_string ("%s", prompt, 0);
+    }
+  val = read_filtered_event (1, 1, 1, ! NILP (inherit_input_method), seconds);
+
+  return (!FIXNUMP (val) ? Qnil
+	  : make_fixnum (char_resolve_modifier_mask (XFIXNUM (val))));
+}
+
+DEFUN ("read-event", Fread_event, Sread_event, 0, 3, 0,
+       doc: /* Read and return an event object from the input stream.
+
+If you want to read non-character events, consider calling `read-key'
+instead.  `read-key' will decode events via `input-decode-map' that
+`read-event' will not.  On a terminal this includes function keys such
+as <F7> and <RIGHT>, or mouse events generated by `xterm-mouse-mode'.
+
+If the optional argument PROMPT is non-nil, display that as a prompt.
+If PROMPT is nil or the string \"\", the key sequence/events that led
+to the current command is used as the prompt.
+
+If the optional argument INHERIT-INPUT-METHOD is non-nil and some
+input method is turned on in the current buffer, that input method
+is used for reading a character.
+
+If the optional argument SECONDS is non-nil, it should be a number
+specifying the maximum number of seconds to wait for input.  If no
+input arrives in that time, return nil.  SECONDS may be a
+floating-point value.
+
+If `inhibit-interaction' is non-nil, this function will signal an
+`inhibited-interaction' error.  */)
+  (Lisp_Object prompt, Lisp_Object inherit_input_method, Lisp_Object seconds)
+{
+  barf_if_interaction_inhibited ();
+
+  if (! NILP (prompt))
+    {
+      cancel_echoing ();
+      message_with_string ("%s", prompt, 0);
+    }
+  return read_filtered_event (0, 0, 0, ! NILP (inherit_input_method), seconds);
+}
+
+DEFUN ("read-char-exclusive", Fread_char_exclusive, Sread_char_exclusive, 0, 3, 0,
+       doc: /* Read a character event from the command input (keyboard or macro).
+Return the character as a number.  Non-character events are ignored.
+If the event has modifiers, they are resolved and reflected in the
+returned character code if possible (e.g. C-SPC yields 0 and C-a yields 97).
+If some of the modifiers cannot be reflected in the character code, the
+returned value will include those modifiers, and will not be a valid
+character code: it will fail the `characterp' test.  Use `event-basic-type'
+to recover the character code with the modifiers removed.
+
+If the optional argument PROMPT is non-nil, display that as a prompt.
+If PROMPT is nil or the string \"\", the key sequence/events that led
+to the current command is used as the prompt.
+
+If the optional argument INHERIT-INPUT-METHOD is non-nil and some
+input method is turned on in the current buffer, that input method
+is used for reading a character.
+
+If the optional argument SECONDS is non-nil, it should be a number
+specifying the maximum number of seconds to wait for input.  If no
+input arrives in that time, return nil.  SECONDS may be a
+floating-point value.
+
+If `inhibit-interaction' is non-nil, this function will signal an
+`inhibited-interaction' error.  */)
+  (Lisp_Object prompt, Lisp_Object inherit_input_method, Lisp_Object seconds)
+{
+  Lisp_Object val;
+
+  barf_if_interaction_inhibited ();
+
+  if (! NILP (prompt))
+    {
+      cancel_echoing ();
+      message_with_string ("%s", prompt, 0);
+    }
+
+  val = read_filtered_event (1, 1, 0, ! NILP (inherit_input_method), seconds);
+
+  return (!FIXNUMP (val) ? Qnil
+	  : make_fixnum (char_resolve_modifier_mask (XFIXNUM (val))));
+}
+
 
 /* Reading key sequences.  */
 
@@ -13341,10 +13606,15 @@ syms_of_keyboard (void)
   menu_bar_touch_id = Qnil;
   staticpro (&menu_bar_touch_id);
 
+  DEFSYM (Qread_char, "read-char");
+
   defsubr (&Scurrent_idle_time);
   defsubr (&Sevent_symbol_parse_modifiers);
   defsubr (&Sevent_convert_list);
   defsubr (&Sinternal_handle_focus_in);
+  defsubr (&Sread_char);
+  defsubr (&Sread_char_exclusive);
+  defsubr (&Sread_event);
   defsubr (&Sread_key_sequence);
   defsubr (&Sread_key_sequence_vector);
   defsubr (&Srecursive_edit);
diff --git a/src/lread.c b/src/lread.c
index c02060d3ea5..069a7162e12 100644
--- a/src/lread.c
+++ b/src/lread.c
@@ -737,271 +737,6 @@ read_emacs_mule_char (source_t *src, int c)
 static Lisp_Object substitute_object_recurse (struct subst *, Lisp_Object);
 static void substitute_in_interval (INTERVAL, void *);
 
-
-/* Get a character from the tty.  */
-
-/* Read input events until we get one that's acceptable for our purposes.
-
-   If NO_SWITCH_FRAME, switch-frame events are stashed
-   until we get a character we like, and then stuffed into
-   unread_switch_frame.
-
-   If ASCII_REQUIRED, check function key events to see
-   if the unmodified version of the symbol has a Qascii_character
-   property, and use that character, if present.
-
-   If ERROR_NONASCII, signal an error if the input we
-   get isn't an ASCII character with modifiers.  If it's false but
-   ASCII_REQUIRED is true, just re-read until we get an ASCII
-   character.
-
-   If INPUT_METHOD, invoke the current input method
-   if the character warrants that.
-
-   If SECONDS is a number, wait that many seconds for input, and
-   return Qnil if no input arrives within that time.
-
-   If text conversion is enabled and ASCII_REQUIRED, temporarily
-   disable any input method which wants to perform edits, unless
-   `disable-inhibit-text-conversion'.  */
-
-static Lisp_Object
-read_filtered_event (bool no_switch_frame, bool ascii_required,
-		     bool error_nonascii, bool input_method, Lisp_Object seconds)
-{
-  Lisp_Object val, delayed_switch_frame;
-  struct timespec end_time;
-#ifdef HAVE_TEXT_CONVERSION
-  specpdl_ref count;
-#endif
-
-#ifdef HAVE_WINDOW_SYSTEM
-  if (display_hourglass_p)
-    cancel_hourglass ();
-#endif
-
-#ifdef HAVE_TEXT_CONVERSION
-  count = SPECPDL_INDEX ();
-
-  /* Don't use text conversion when trying to just read a
-     character.  */
-
-  if (ascii_required && !disable_inhibit_text_conversion)
-    {
-      disable_text_conversion ();
-      record_unwind_protect_void (resume_text_conversion);
-    }
-#endif
-
-  delayed_switch_frame = Qnil;
-
-  /* Compute timeout.  */
-  if (NUMBERP (seconds))
-    {
-      double duration = XFLOATINT (seconds);
-      struct timespec wait_time = dtotimespec (duration);
-      end_time = timespec_add (current_timespec (), wait_time);
-    }
-
-  /* Read until we get an acceptable event.  */
- retry:
-  do
-    val = read_char (0, Qnil, (input_method ? Qnil : Qt), 0,
-		     NUMBERP (seconds) ? &end_time : NULL);
-  while (FIXNUMP (val) && XFIXNUM (val) == -2); /* wrong_kboard_jmpbuf */
-
-  if (BUFFERP (val))
-    goto retry;
-
-  /* `switch-frame' events are put off until after the next ASCII
-     character.  This is better than signaling an error just because
-     the last characters were typed to a separate minibuffer frame,
-     for example.  Eventually, some code which can deal with
-     switch-frame events will read it and process it.  */
-  if (no_switch_frame
-      && EVENT_HAS_PARAMETERS (val)
-      && EQ (EVENT_HEAD_KIND (EVENT_HEAD (val)), Qswitch_frame))
-    {
-      delayed_switch_frame = val;
-      goto retry;
-    }
-
-  if (ascii_required && !(NUMBERP (seconds) && NILP (val)))
-    {
-      /* Convert certain symbols to their ASCII equivalents.  */
-      if (SYMBOLP (val))
-	{
-	  Lisp_Object tem, tem1;
-	  tem = Fget (val, Qevent_symbol_element_mask);
-	  if (!NILP (tem))
-	    {
-	      tem1 = Fget (Fcar (tem), Qascii_character);
-	      /* Merge this symbol's modifier bits
-		 with the ASCII equivalent of its basic code.  */
-	      if (FIXNUMP (tem1) && FIXNUMP (Fcar (Fcdr (tem))))
-		XSETFASTINT (val, XFIXNUM (tem1) | XFIXNUM (Fcar (Fcdr (tem))));
-	    }
-	}
-
-      /* If we don't have a character now, deal with it appropriately.  */
-      if (!FIXNUMP (val))
-	{
-	  if (error_nonascii)
-	    {
-	      Vunread_command_events = list1 (val);
-	      error ("Non-character input-event");
-	    }
-	  else
-	    goto retry;
-	}
-    }
-
-  if (! NILP (delayed_switch_frame))
-    unread_switch_frame = delayed_switch_frame;
-
-#if 0
-
-#ifdef HAVE_WINDOW_SYSTEM
-  if (display_hourglass_p)
-    start_hourglass ();
-#endif
-
-#endif
-
-#ifdef HAVE_TEXT_CONVERSION
-  return unbind_to (count, val);
-#else
-  return val;
-#endif
-}
-
-DEFUN ("read-char", Fread_char, Sread_char, 0, 3, 0,
-       doc: /* Read a character event from the command input (keyboard or macro).
-Return the character as a number.
-If the event has modifiers, they are resolved and reflected in the
-returned character code if possible (e.g. C-SPC yields 0 and C-a yields 97).
-If some of the modifiers cannot be reflected in the character code, the
-returned value will include those modifiers, and will not be a valid
-character code: it will fail the `characterp' test.  Use `event-basic-type'
-to recover the character code with the modifiers removed.
-
-If the user generates an event which is not a character (i.e. a mouse
-click or function key event), `read-char' signals an error.  As an
-exception, switch-frame events are put off until non-character events
-can be read.
-If you want to read non-character events, or ignore them, call
-`read-event' or `read-char-exclusive' instead.
-
-If the optional argument PROMPT is non-nil, display that as a prompt.
-If PROMPT is nil or the string \"\", the key sequence/events that led
-to the current command is used as the prompt.
-
-If the optional argument INHERIT-INPUT-METHOD is non-nil and some
-input method is turned on in the current buffer, that input method
-is used for reading a character.
-
-If the optional argument SECONDS is non-nil, it should be a number
-specifying the maximum number of seconds to wait for input.  If no
-input arrives in that time, return nil.  SECONDS may be a
-floating-point value.
-
-If `inhibit-interaction' is non-nil, this function will signal an
-`inhibited-interaction' error.  */)
-  (Lisp_Object prompt, Lisp_Object inherit_input_method, Lisp_Object seconds)
-{
-  Lisp_Object val;
-
-  barf_if_interaction_inhibited ();
-
-  if (! NILP (prompt))
-    {
-      cancel_echoing ();
-      message_with_string ("%s", prompt, 0);
-    }
-  val = read_filtered_event (1, 1, 1, ! NILP (inherit_input_method), seconds);
-
-  return (!FIXNUMP (val) ? Qnil
-	  : make_fixnum (char_resolve_modifier_mask (XFIXNUM (val))));
-}
-
-DEFUN ("read-event", Fread_event, Sread_event, 0, 3, 0,
-       doc: /* Read and return an event object from the input stream.
-
-If you want to read non-character events, consider calling `read-key'
-instead.  `read-key' will decode events via `input-decode-map' that
-`read-event' will not.  On a terminal this includes function keys such
-as <F7> and <RIGHT>, or mouse events generated by `xterm-mouse-mode'.
-
-If the optional argument PROMPT is non-nil, display that as a prompt.
-If PROMPT is nil or the string \"\", the key sequence/events that led
-to the current command is used as the prompt.
-
-If the optional argument INHERIT-INPUT-METHOD is non-nil and some
-input method is turned on in the current buffer, that input method
-is used for reading a character.
-
-If the optional argument SECONDS is non-nil, it should be a number
-specifying the maximum number of seconds to wait for input.  If no
-input arrives in that time, return nil.  SECONDS may be a
-floating-point value.
-
-If `inhibit-interaction' is non-nil, this function will signal an
-`inhibited-interaction' error.  */)
-  (Lisp_Object prompt, Lisp_Object inherit_input_method, Lisp_Object seconds)
-{
-  barf_if_interaction_inhibited ();
-
-  if (! NILP (prompt))
-    {
-      cancel_echoing ();
-      message_with_string ("%s", prompt, 0);
-    }
-  return read_filtered_event (0, 0, 0, ! NILP (inherit_input_method), seconds);
-}
-
-DEFUN ("read-char-exclusive", Fread_char_exclusive, Sread_char_exclusive, 0, 3, 0,
-       doc: /* Read a character event from the command input (keyboard or macro).
-Return the character as a number.  Non-character events are ignored.
-If the event has modifiers, they are resolved and reflected in the
-returned character code if possible (e.g. C-SPC yields 0 and C-a yields 97).
-If some of the modifiers cannot be reflected in the character code, the
-returned value will include those modifiers, and will not be a valid
-character code: it will fail the `characterp' test.  Use `event-basic-type'
-to recover the character code with the modifiers removed.
-
-If the optional argument PROMPT is non-nil, display that as a prompt.
-If PROMPT is nil or the string \"\", the key sequence/events that led
-to the current command is used as the prompt.
-
-If the optional argument INHERIT-INPUT-METHOD is non-nil and some
-input method is turned on in the current buffer, that input method
-is used for reading a character.
-
-If the optional argument SECONDS is non-nil, it should be a number
-specifying the maximum number of seconds to wait for input.  If no
-input arrives in that time, return nil.  SECONDS may be a
-floating-point value.
-
-If `inhibit-interaction' is non-nil, this function will signal an
-`inhibited-interaction' error.  */)
-  (Lisp_Object prompt, Lisp_Object inherit_input_method, Lisp_Object seconds)
-{
-  Lisp_Object val;
-
-  barf_if_interaction_inhibited ();
-
-  if (! NILP (prompt))
-    {
-      cancel_echoing ();
-      message_with_string ("%s", prompt, 0);
-    }
-
-  val = read_filtered_event (1, 1, 0, ! NILP (inherit_input_method), seconds);
-
-  return (!FIXNUMP (val) ? Qnil
-	  : make_fixnum (char_resolve_modifier_mask (XFIXNUM (val))));
-}
-
 
 
 typedef enum {
@@ -5878,9 +5613,6 @@ syms_of_lread (void)
   defsubr (&Sload);
   defsubr (&Seval_buffer);
   defsubr (&Seval_region);
-  defsubr (&Sread_char);
-  defsubr (&Sread_char_exclusive);
-  defsubr (&Sread_event);
   defsubr (&Smapatoms);
   defsubr (&Slocate_file_internal);
   defsubr (&Sinternal__obarray_buckets);
@@ -6164,7 +5896,6 @@ syms_of_lread (void)
 
   DEFSYM (Qcurrent_load_list, "current-load-list");
   DEFSYM (Qstandard_input, "standard-input");
-  DEFSYM (Qread_char, "read-char");
 
   DEFSYM (Qget_file_char, "get-file-char");
 
-- 
2.50.0