Subject: bug#78737: sit-for behavior changes when byte-compiled

From: Lynn Winebarger <[email-masked]>
Date: Fri, 13 Jun 2025 17:55:49 -0400

On Fri, Jun 13, 2025 at 10:02 AM Daniel Colascione <[email-masked]> wrote:
>
>
>
> On June 13, 2025 6:42:58 AM PDT, Daniel Colascione <[email-masked]> wrote:
> >
> >
> >On June 13, 2025 5:23:34 AM PDT, Lynn Winebarger <[email-masked]> wrote:
> >>On Fri, Jun 13, 2025, 2:26 AM Eli Zaretskii <[email-masked]> wrote:
> >>
> >>> > From: Daniel Colascione <[email-masked]>
> >>> > Cc: Eli Zaretskii <[email-masked]>,  [email-masked],
> >>> >   [email-masked]
> >>> > Date: Thu, 12 Jun 2025 11:48:50 -0700
> >>> >
> >>> > Pip Cet <[email-masked]> writes:
> >>> >
> >>> > > I'd like read-event, when called while inhibit-quit is t, to report
> >>> > > quits by setting quit-flag in addition to returning quit_char: it'll
> >>> > > simplify the C code, and it would make
> >>> > >
> >>> > > (while t
> >>> > >   (let ((inhibit-quit t))
> >>> > >     (read-event)))
> >>> >
> >>> > I strongly disagree.  read-event should read an event.
> >>> > Setting quit-flag by side effect when it happens to read one key and not
> >>> > others makes the interface less regular and understandable.
> >>>
> >>> We should start by agreeing that the capability of interrupting a
> >>> running Lisp program is a real need.  Are we in agreement about that?
> >>> If not, let's first hear the arguments why not.
> >>>
> >>> If we _are_ in agreement about that, we should discuss how this should
> >>> be possible if read-event (and perhaps others?) return C-g instead of
> >>> raising quit-flag.  The alternatives mentioned until now are:
> >>>
> >>>  . restore the old behavior, whereby C-g interrupts read-event
> >>>  . have a variable that, if set, will restore the old behavior in
> >>>    read-event and other affected primitives, to be interruptible by a
> >>>    single C-g
> >>>  . make two C-g presses "in quick succession" set quit-flag, IOW
> >>>    "C-g C-g" will have the same effect as C-g previously
> >>>
> >>> Are there other alternatives?
> >>>
> >>
> >>What about keeping a (possibly buffer-local?) lisp variable holding a list
> >>of keystrokes mapped to thunks that are treated as generating lisp machine
> >>"interrupts"?  The key strokes would be processed by C machinery and never
> >>seen directly by lisp code and not be considered "events".
> >>Then C-g could be bound to a thunk signalling quit, and the effect of
> >>"inhibit-quit" achieved by removing C-g from the list in a given dynamic
> >>scope.  Then user code could make other key-strokes "special" without
> >>resorting to read-event.  For example, this read-event call in term.el:
> >>(message "Hit space to flush")
> >>      (let ((ch (read-event)))
> >> (if (eq ch ?\s)
> >>     (set-window-configuration conf)
> >>   (push ch unread-command-events)))
> >>
> >>Could be replaced by something like
> >>(with-interrupts ((?\s (signal term-flush)))
> >>  (condition-case nil
> >>    (while t (sit-for 100))
> >>     (term-flush (set-window-configuration conf))))
> >>
> >>Then some of these use-case concerns could be mooted altogether.
> >
> >We already have something like that. :-) read-event already runs the events it reads through special-event-map, right? We don't even need to create a separate thunk list concept: we could just bind C-g in special event map and do what we want, right?
Would that provide the behavior Pip is looking for?  I can't pretend
to understand what is going on in read_char, but it looks like that
map isn't applied to *every* key entered, to ensure they are removed
from the normal event stream.

> >The only special thing about C-g is how we treat it when Lisp is running. When it's instead reading an event, it can and be a boring event processed the same way every other event is.
It seems to me what Pip is looking for is analogous to a "Quit" button
hardwired to a pin on a CPU, which is not something that can be
reliably implemented in the C primitives as they are now.  That would
mean adding "interrupts" to the lisp machine architecture.
If I think about it seriously, I guess I would be talking about a
mechanism incorporated into signal handlers that would do surgery on
the appropriate thread's C/Lisp machine stack so that when that
machine's operation is resumed, the active frame would be for a call
to the registered interrupt handler, arranged to return to cleanly
return to wherever the C code was before the signal, unless the
interrupt handler performed a non-local exit, like (signal 'quit).
Maybe the interrupt handler shouldn't be a thunk, but take two
arguments: the data delivered with the signal (in this case the
keystroke) and a timestamp, so your "<N> quits in <M> milliseconds"
could be implemented when there are multiple keystrokes before the
lisp thread resumes.  In this scenario, the signal handler would never
deliver "hard-wired" interrupt keystrokes to the event queue processed
by read_char.  It would also mean a C-g (or other designated
interrupt) could be processed earlier than preceding keystrokes, but
that seems like what Pip would want?
Theoretically, such interrupt handlers could be required to be
byte-code functions that don't use any operations that could produce
unbounded stack usage, most notably no call instructions, or certified
by the native compiler to be "safe" for use with interrupts.  Also,
byte-code ops for "signal" and "throw" would need to be added.  The
possibility of forcing a non-local exit on the lisp thread would be
the whole point of arranging a handler to run on that thread in the
first place.  Anyway, this kind of restriction would probably
scare-off any non-serious uses of the facility.

This approach is really too low-level for the use I suggested in
term.el, though I do think there should be some way of saying "wake me
up when this key is pressed" and either block until that happens
(queueing other keystrokes) or let the command loop do whatever it
normally would if that makes sense.

> Oh yeah, one thing I forgot to mention: for the *asynchronous* case, the C code is pretty deeply coupled to quit_char, and IIRC, even hard codes 7, C-g, in some places instead of quit_char. In your proposal, we'd references to quit_char and literal 7 to calls to a new bool is_quit_char(int c) function -- and this function would return true for any raw keys bound in special-event-map. In this way, you'd be able to define as many asynchronous-input characters as you wanted.
  I guess I would consider removing that kind of hard-coded logic a plus?
>
> We wouldn't get the special SIGINT behavior this way (because in termios there can be only one interrupt key, right?), but I think that's okay, since quitting works without the SIGINT wiring and I suspect that we could remove the SIGINT wiring and simplify the codebase without losing any actual functionality.

I suppose I am thinking from the perspective of making the lisp
machine more machine-like, to facilitate the possibility of running
multiple elisp "cpus" or "cores" in the same process, but that's all
pie in the sky at this point.  I'd like to see inputs and displays
become clean sources and sinks, with the threads processing the data
freed from concerns like whether redisplay will be called.  Like
having dedicated coprocessors for I/O devices, with the lisp machine
core primitives just concerned with the data structures (like buffers)
they manage.

I'm talking awfully big for someone struggling to make
read0/readevalloop re-entrant enough to interleave progressive
evaluation of forms in a string with loading a file or evaluating a
buffer.

Lynn