Subject: Re: bug#78898: Make read/readevalloop reentrant

From: Lynn Winebarger <[email-masked]>
Date: Sun, 29 Jun 2025 00:36:21 -0400

On Sat, Jun 28, 2025 at 10:38 PM Lynn Winebarger <[email-masked]> wrote:
>
>
>
> On Sat, Jun 28, 2025, 12:23 PM Stefan Monnier <[email-masked]> wrote:
>>
>> > I want to use readevalloop to process command-line arguments, and my
>> > initial attempts failed to handle '"(load "loadup.el") (princ
>> > "Done")'.  So I took a stab at making all the entry points synchronize
>> > with an explicit stack of continuation frames shared between read0 and
>> > readevalloop.
>>
>> I'm afraid with the `Subject:` and the above paragraph, I'm still not
>> able to figure out what you're trying to achieve (and the patch is
>> quite large).
>>
>> Also `read` and `readevalloop` are quite different.  Do you want both to
>> be re-entrant?  Is it in both cases for the same reason?
>> AFAIK `readevalloop` is also re-entrant in that you can call it
>> recursively from its "eval" part.  What do you mean by "process
>> command-line arguments"?
>
>
> It's true that read and readevalloop are distinct, but, ignoring the readfun parameter of readevalloop, any static variables read0 is dependent on, readevalloop is also dependent on.  It's also true that not every entry to read0 is an entry to readevalloop, but practically every entry to readevalloop will enter read0.  And they both depend on the state accessed by readchar and unreadchar.  So, readevalloop can never really be reentrant as long as read0/readchar/unreadchar are not.  My initial (current) approach has been to identify all the private state in lread.c accessed (directly or indirectly) and create a single data structure representing that state as a collective stack frame for essentially all the procedures of lread.c in the transitive closure of readevalloop 's call graph.  It's true that this data structure is bigger than necessary, but readevalloop only has to allocate one frame that will be used by each invocation of read0 it makes in that activation frame of readevalloop.
>
Just to clarify my original wording - I approached this conceptually
using the technique for transforming an imperative-style program with
global state into a purely functional one.  Transforming the original
program into continuation-passing style makes every function a
continuation, then making every global a parameter of every function,
with assignments/side-effects being achieved by changing the value
being passed for the corresponding parameter.  Then, the continuations
(which means the functions, there is no difference in CPS) can be
simplified to only pass the globals required for themselves or the
continuations they call.  Then you can take an entry point
(readevalloop in this case), and its parameter set will be the union
of the parameters of the transitive closure of its continuations, plus
whatever parameters it alone requires.  That's why I referred to the
"frame" as a continuation frame, as it captures the union of all the
continuations called by readevalloop, excepting the one from the eval
call.

Lynn