From: Eli Gottlieb
Subject: Implementation of control structures
Date: 
Message-ID: <ngXXf.39408$jf2.14260@twister.nyroc.rr.com>
For the reason that the ECLS folks to their sweet time in creating and 
emailing documentation on their Lisp system to me, I am creating my own 
Lisp system.

I'm up to the point where I'd like to implement tagbody/go and 
block/return-from structures, but not exactly sure how to do so.  I have 
a global function called Eval() which acts as my evaluator, and 
functions are stored in Pascal objects.  This means that hacking up the 
evaluator to make it act correctly for two structures is loathsome.

How can I make sure that when return-from is called execution returns to 
the correct place?
--
The science of economics is the cleverest proof of free will yet 
constructed.

From: Frode Vatvedt Fjeld
Subject: Re: Implementation of control structures
Date: 
Message-ID: <2hpsjymlzb.fsf@vserver.cs.uit.no>
Eli Gottlieb <···········@gmail.com> writes:

> How can I make sure that when return-from is called execution
> returns to the correct place?

The way I've done this is to set up an environment where the
block-name is bound to an exit-point (see below) in the block
namespace.

The exit-point would in lisp be e.g. a generated catch-tag, such that
evaluating a return-from in that scope would amount to a throw to that
catch-tag. Something like this:

  (defun eval-block (block-name body env)
    (let ((catch-tag (gensym)))
      (catch catch-tag
         (eval-implicit-progn body
                              (acons `(,block-name :block . ,catch-tag))))))

..assuming the lexical environment is represented as an assoc-list
with elements on the form (name namespace . value). Evaluating
return-from would now look something like this:

  (defun eval-return-from (block-name-form result-form env)
    (let* ((block-name (eval-form block-name-form env))
           (catch-tag (loop for (name namespace . value) in env
                         when (and (eq block-name name)
                                   (eq :block namespace))
                         return value)))
      (assert block-binding (block-name)
         "No block named ~S in sight." block-name)
      (throw block-binding
             (eval-form result-form env))))

This is the general idea. For C etc. I suppose you'd emulate the
catch/throw pattern with setjmp/longjmp. And for the env-list perhaps
a linked list of stack-allocated struct elements.

A different approach entirely would be based on continuation-passing,
but I don't have experience with that personally. (I suppose you'd
bind the block-name to a continuation rather than a catch-tag.)

-- 
Frode Vatvedt Fjeld
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <s4gYf.13972$Mj.13516@twister.nyroc.rr.com>
Frode Vatvedt Fjeld wrote:
> Eli Gottlieb <···········@gmail.com> writes:
> 
> 
>>How can I make sure that when return-from is called execution
>>returns to the correct place?
> 
> 
> The way I've done this is to set up an environment where the
> block-name is bound to an exit-point (see below) in the block
> namespace.
> 
> The exit-point would in lisp be e.g. a generated catch-tag, such that
> evaluating a return-from in that scope would amount to a throw to that
> catch-tag. Something like this:
> 
>   (defun eval-block (block-name body env)
>     (let ((catch-tag (gensym)))
>       (catch catch-tag
>          (eval-implicit-progn body
>                               (acons `(,block-name :block . ,catch-tag))))))
> 
> ..assuming the lexical environment is represented as an assoc-list
> with elements on the form (name namespace . value). Evaluating
> return-from would now look something like this:
> 
>   (defun eval-return-from (block-name-form result-form env)
>     (let* ((block-name (eval-form block-name-form env))
>            (catch-tag (loop for (name namespace . value) in env
>                          when (and (eq block-name name)
>                                    (eq :block namespace))
>                          return value)))
>       (assert block-binding (block-name)
>          "No block named ~S in sight." block-name)
>       (throw block-binding
>              (eval-form result-form env))))
> 
> This is the general idea. For C etc. I suppose you'd emulate the
> catch/throw pattern with setjmp/longjmp. And for the env-list perhaps
> a linked list of stack-allocated struct elements.
> 
> A different approach entirely would be based on continuation-passing,
> but I don't have experience with that personally. (I suppose you'd
> bind the block-name to a continuation rather than a catch-tag.)
> 
setjmp() and longjmp()?  Hmmm... Rather kludgier than I hoped, but that 
should work.

-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Pascal Bourguignon
Subject: Re: Implementation of control structures
Date: 
Message-ID: <8764lqi8ro.fsf@thalassa.informatimago.com>
Eli Gottlieb <···········@gmail.com> writes:

> Frode Vatvedt Fjeld wrote:
>> Eli Gottlieb <···········@gmail.com> writes:
>> 
>>>How can I make sure that when return-from is called execution
>>>returns to the correct place?
>>[...]
>> This is the general idea. For C etc. I suppose you'd emulate the
>> catch/throw pattern with setjmp/longjmp. 
>> [...]
> setjmp() and longjmp()?  Hmmm... Rather kludgier than I hoped, but
> that should work.

That's  a little more  complicated than  that.  You  have to  unroll a
number of  lexical and dynamic bindings when  you process RETURN-FROM.
longjmp is a single assembly instruction here.

(block :example
  (let ((*print-readably* t)
        (a 2)
        (potatoe (cons 1 'kg)))
    (catch potatoe
      (handler-case 
          (let ((a 3))
            (unwind-protect 
                 (return-from :example 3)
              (print :unwound))
            (throw potatoe))
        (error (err) (format t "~&~A~%~A" a err))))))

; prints:
:|UNWOUND| 
; returns:
3

Here, in processing RETURN-FROM:
- the cleanup clauses of UNWIND-PROTECT are executed (:unwound is printed),
- the innermost lexical scope for (a 3) is destroyed,
- the dynamic scope of error handlers is destroyed,
- the dynamic scope of catch is destroyed,
- the lexical scope for (a 2) is destroyed,
- the dynamic scope for *print-readably* is reset,
- and finally the block is exited. (well, here you could use longjmp,
  but really you're already here...).

In addition, you may have to process FLET/LABELS lexical scopes too.
Also, if you have an interpreter instead of a compiler, you may have
other scopes to pop, like TAGBODY, MACROLET, and SYMBOL-MACROLET.


For one thing, since it's a lexical construct, you know exactly the
block from which you must return:

(block :name ;1
  v
  (block :name ;2
    x
    (return-from :name) ; exits from block 2
    y
    (return-from :name) ; exits from block 2
    z)
    (return-from :name) ; exits from block 1
  w)

There are two movements to execute RETURN-FROM.  

First as described above, you must unwind the scope up to the block
you must exit. In a compiler, you could have a stack offset compiled
in the RETURN-FROM instructions, which would compute the address on
the stack, and the unwinding of the scopes would be complete when the
stack have been poped down to that pointer, or you could keep track of
the number of frames to unwind.  Perhaps the later would be easier.

Next, for each block frame you know the address to which the program
must jump to exit from the block.  This address could be put on the
stack and returned to, or stored in the RETURN-FROM instructions.
Actually, the later would probably be better, since then you could
avoid stacking block frames altogether.  The compiler could choose one
or the other depending on the number of RETURN-FROM in each block.

So you could compile a RETURN-FROM as:

   <store-the-result-to-the-multiple-value-registers>
   (pop-frames <number-of-frames-to-pop>)
   (jump <address-to-jump-to>)

If amongst the frames to be poped there are unwind cleanup forms to
execute, they'll have to save the multiple-value registers.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

"Remember, Information is not knowledge; Knowledge is not Wisdom;
Wisdom is not truth; Truth is not beauty; Beauty is not love;
Love is not music; Music is the best." -- Frank Zappa
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <KujYf.14004$Mj.4252@twister.nyroc.rr.com>
Pascal Bourguignon wrote:
> Eli Gottlieb <···········@gmail.com> writes:
> 
> 
>>Frode Vatvedt Fjeld wrote:
>>
>>>Eli Gottlieb <···········@gmail.com> writes:
>>>
>>>
>>>>How can I make sure that when return-from is called execution
>>>>returns to the correct place?
>>>
>>>[...]
>>>This is the general idea. For C etc. I suppose you'd emulate the
>>>catch/throw pattern with setjmp/longjmp. 
>>>[...]
>>
>>setjmp() and longjmp()?  Hmmm... Rather kludgier than I hoped, but
>>that should work.
> 
> 
> That's  a little more  complicated than  that.  You  have to  unroll a
> number of  lexical and dynamic bindings when  you process RETURN-FROM.
> longjmp is a single assembly instruction here.
> 
> (block :example
>   (let ((*print-readably* t)
>         (a 2)
>         (potatoe (cons 1 'kg)))
>     (catch potatoe
>       (handler-case 
>           (let ((a 3))
>             (unwind-protect 
>                  (return-from :example 3)
>               (print :unwound))
>             (throw potatoe))
>         (error (err) (format t "~&~A~%~A" a err))))))
> 
> ; prints:
> :|UNWOUND| 
> ; returns:
> 3
> 
> Here, in processing RETURN-FROM:
> - the cleanup clauses of UNWIND-PROTECT are executed (:unwound is printed),
> - the innermost lexical scope for (a 3) is destroyed,
> - the dynamic scope of error handlers is destroyed,
> - the dynamic scope of catch is destroyed,
> - the lexical scope for (a 2) is destroyed,
> - the dynamic scope for *print-readably* is reset,
> - and finally the block is exited. (well, here you could use longjmp,
>   but really you're already here...).
> 
> In addition, you may have to process FLET/LABELS lexical scopes too.
> Also, if you have an interpreter instead of a compiler, you may have
> other scopes to pop, like TAGBODY, MACROLET, and SYMBOL-MACROLET.
> 
> 
> For one thing, since it's a lexical construct, you know exactly the
> block from which you must return:
> 
> (block :name ;1
>   v
>   (block :name ;2
>     x
>     (return-from :name) ; exits from block 2
>     y
>     (return-from :name) ; exits from block 2
>     z)
>     (return-from :name) ; exits from block 1
>   w)
> 
> There are two movements to execute RETURN-FROM.  
> 
> First as described above, you must unwind the scope up to the block
> you must exit. In a compiler, you could have a stack offset compiled
> in the RETURN-FROM instructions, which would compute the address on
> the stack, and the unwinding of the scopes would be complete when the
> stack have been poped down to that pointer, or you could keep track of
> the number of frames to unwind.  Perhaps the later would be easier.
> 
> Next, for each block frame you know the address to which the program
> must jump to exit from the block.  This address could be put on the
> stack and returned to, or stored in the RETURN-FROM instructions.
> Actually, the later would probably be better, since then you could
> avoid stacking block frames altogether.  The compiler could choose one
> or the other depending on the number of RETURN-FROM in each block.
> 
> So you could compile a RETURN-FROM as:
> 
>    <store-the-result-to-the-multiple-value-registers>
>    (pop-frames <number-of-frames-to-pop>)
>    (jump <address-to-jump-to>)
> 
> If amongst the frames to be poped there are unwind cleanup forms to
> execute, they'll have to save the multiple-value registers.
> 
Actually, I'm not implementing Common Lisp.  It's too much of a pain in 
the ass, and a duplication of effort as well.

Second of all, I'm working in an interpreter.  Every Lisp function is 
run by a single Pascal function, which either runs it (compiled 
primitive) or evals its function expression (defined Lisp function).  It 
then uses its own return value to return an array of values.

Anyway, I was all set to use setjmp()/longjmp() for the implementation 
until I realized that under my reference-counting system that would 
probably result in a memory leak.  Blargh.

-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Pascal Bourguignon
Subject: Re: Implementation of control structures
Date: 
Message-ID: <87irpqgl42.fsf@thalassa.informatimago.com>
Eli Gottlieb <···········@gmail.com> writes:
> Actually, I'm not implementing Common Lisp.  It's too much of a pain
> in the ass, and a duplication of effort as well.
>
> Second of all, I'm working in an interpreter.  Every Lisp function is
> run by a single Pascal function, which either runs it (compiled
> primitive) or evals its function expression (defined Lisp function).
> It then uses its own return value to return an array of values.

Well, an interpreter is not necessarily easier to implement.

At first, it may seem easy enough to use the interpreter stack for the
interpreted program too.  But since the frames and their management
can be different in the interpreted language than in the interpreter,
this introduces often an additionnal complexity.

Let's take an example:

(let ((i 0))
  (tagbody
   :loop
     (when (< (incf i) 3)
       (map nil (lambda (x) (if (zerop x) (go :loop)) (print x))
            '(1 2 0 3 4)))))

So assuming you implement each operator and primitive as a Pascal function.
When you reach the GO, you'll have a Pascal call stack like:

    spGO
    spIF
    applyLAMBDA
    fnAPPLY
    fnMAP
    spWHEN
    spTAGBODY

with probably more functions interspersed.

You must exit from all these functions transmitting a message for
spTAGBODY, which must be prepared to receive it from any function it
calls to process its body, telling it to jump to the tag :LOOP.

To all the primitives, you'll have to pass a stack of bindings, and
expect as return various kind of messages. A normal return would give
the results, but in all the primitive functions, for all the
primitives subcalls you'll have to test whether you have an abnormal
exit, and process it.

function spIF(b:bindings;c,t,e:sexp):message;
    var m:message;
begin
    m:=evaluate(b,c);
    if(message_normal_p(m))then
        if(generalized_true_p(message_value(m)))then
            m:=evaluate(b,t);
        else
            m:=evaluate(b,e);
    spIF:=m;
end;

function spBLOCK(b:bindings;body:list):message;
    var m:message;
        target:object;
begin
    target:=first(body);
    b:=binding_cons(block_frame(target),b);
    body:=rest(body);
    while(not(null(body)))do begin
       m:=evaluate(b,first(body));
       if((message_return_p(m))and(message_return_target(m)=target))then begin
          body:=nil;
          spBLOCK:=message_normal(message_value(m));
       end else if(message_normal_p(m))then 
          body:=rest(body);
       else begin
          body:=nil;
          spBLOCK:=m;
       end;
    end;
end;

function spRETURNFROM(b:bindings;t:sexp;value:sexp);
    var f: frame;
        m: message;
        target:object;
begin
    m:=evaluate(b,t);
    if(message_normal_p(m))then begin
       target:=message_value(m);
       f:=binding_find_block(b,target);
       if(null(f))then
          spRETURNFROM:=debugger('There is no active BLOCK named ~s',
                                 message_value(m));
       else begin
           m:=evaluate(b,value);
           if(message_normal_p(m))then
               spRETURNFROM:=message_return(target,message_values(m));
           else
               spRETURNFROM:=m;
       end;
    end else
       spRETURNFROM:=m;
end;

Of course, you may have to add some garbage collecting, for this
binding_cons, and these messages, etc.

          
It feels like writing in assembler.  
You're sure you don't want to write this interpreter in CL? 


> Anyway, I was all set to use setjmp()/longjmp() for the implementation
> until I realized that under my reference-counting system that would
> probably result in a memory leak.  Blargh.

Yes, with the above scheme you have an opportunity to do all the
cleaning up needed.  On the other hand, you could manage your own
heap, with a true garbage collector.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

This universe shipped by weight, not volume.  Some expansion may have
occurred during shipment.
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <hHkYf.39588$jf2.7357@twister.nyroc.rr.com>
Pascal Bourguignon wrote:
> Eli Gottlieb <···········@gmail.com> writes:
> 
>>Actually, I'm not implementing Common Lisp.  It's too much of a pain
>>in the ass, and a duplication of effort as well.
>>
>>Second of all, I'm working in an interpreter.  Every Lisp function is
>>run by a single Pascal function, which either runs it (compiled
>>primitive) or evals its function expression (defined Lisp function).
>>It then uses its own return value to return an array of values.
> 
> 
> Well, an interpreter is not necessarily easier to implement.
> 
> At first, it may seem easy enough to use the interpreter stack for the
> interpreted program too.  But since the frames and their management
> can be different in the interpreted language than in the interpreter,
> this introduces often an additionnal complexity.

Actually, I don't pass Lisp args on the Pascal stack.  I have a series 
of objects called Environments which contain symbol/value bindings, and 
a parent environment to look in if a symbol is unbound here. Each Pascal 
function to perform a Lisp operation receives an environment object.

So to call a function a new environment is created that binds the 
appropriate argument values and then just points to the calling 
environment.  The calling environment when no function has been called 
is the dynamic environment.

> 
> Let's take an example:
> 
> (let ((i 0))
>   (tagbody
>    :loop
>      (when (< (incf i) 3)
>        (map nil (lambda (x) (if (zerop x) (go :loop)) (print x))
>             '(1 2 0 3 4)))))
> 
> So assuming you implement each operator and primitive as a Pascal function.
> When you reach the GO, you'll have a Pascal call stack like:
> 
>     spGO
>     spIF
>     applyLAMBDA
>     fnAPPLY
>     fnMAP
>     spWHEN
>     spTAGBODY
> 
> with probably more functions interspersed.

My problem exactly.

> 
> You must exit from all these functions transmitting a message for
> spTAGBODY, which must be prepared to receive it from any function it
> calls to process its body, telling it to jump to the tag :LOOP.
> 
> To all the primitives, you'll have to pass a stack of bindings, and
> expect as return various kind of messages. A normal return would give
> the results, but in all the primitive functions, for all the
> primitives subcalls you'll have to test whether you have an abnormal
> exit, and process it.
> 
> ...
> 
> Of course, you may have to add some garbage collecting, for this
> binding_cons, and these messages, etc.
> 

That could work, passing messages back up the Pascal stack.

What I was also thinking was to create a structure on the heap 
containing 1) The return value(s) of the return-from, 2) The form that 
caused this abnormal return (go or return-from, whatever), and 3) A 
reference to the environment object of the abnormally returning form.  A 
pointer to this object would be passed through longjmp() back up the 
stack.  The returned-to form could then use that object to release the 
environment object the returning-from form was passed.

>           
> It feels like writing in assembler.  
> You're sure you don't want to write this interpreter in CL? 

Can I compile CL to an independent binary on different operating 
systems?  I'm writing this thing in Pascal, because the kernel of my 
hobby OS (the target system) is also written in Pascal.  Hence, I would 
port one compiler and one binary toolchain to get two languages, a 
kernel, and a usable userspace application running.

And in the meantime Free Pascal also compiles on every mainstream OS 
known to mankind.

> 
> 
> 
>>Anyway, I was all set to use setjmp()/longjmp() for the implementation
>>until I realized that under my reference-counting system that would
>>probably result in a memory leak.  Blargh.
> 
> 
> Yes, with the above scheme you have an opportunity to do all the
> cleaning up needed.  On the other hand, you could manage your own
> heap, with a true garbage collector.
> 

See above.
-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Pascal Bourguignon
Subject: Re: Implementation of control structures
Date: 
Message-ID: <87ek0egjem.fsf@thalassa.informatimago.com>
Eli Gottlieb <···········@gmail.com> writes:
> Can I compile CL to an independent binary on different operating
> systems?  

Perhaps with ECL?  It should run everywhere gcc compiles to, I guess.

> I'm writing this thing in Pascal, because the kernel of my
> hobby OS (the target system) is also written in Pascal.  Hence, I
> would port one compiler and one binary toolchain to get two languages,
> a kernel, and a usable userspace application running.

But then, if it's for a new OS, why not implement Common Lisp?  You'd
be the sole provider of a Common Lisp implementation on your hobby OS!

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

HEALTH WARNING: Care should be taken when lifting this product,
since its mass, and thus its weight, is dependent on its velocity
relative to the user.
From: Juanjo
Subject: Re: Implementation of control structures
Date: 
Message-ID: <1144139995.511028.308080@t31g2000cwb.googlegroups.com>
Pascal Bourguignon schrieb:

> Eli Gottlieb <···········@gmail.com> writes:
> > Can I compile CL to an independent binary on different operating
> > systems?
>
> Perhaps with ECL?  It should run everywhere gcc compiles to, I guess.

Not only GCC. ECL builds also using Microsoft Visual C++, and some time
ago it built using Sun's compiler -- no longer tested because I lack a
platform with developing tools --. ECL basically builds on any platform
where GNU MP and Boehm-Weiser garbage collector runs, the former being
a more important requisite.

Juanjo
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <6evYf.15743$Mj.1318@twister.nyroc.rr.com>
Pascal Bourguignon wrote:
> Eli Gottlieb <···········@gmail.com> writes:
> 
>>Can I compile CL to an independent binary on different operating
>>systems?  
> 
> 
> Perhaps with ECL?  It should run everywhere gcc compiles to, I guess.
> 
> 
>>I'm writing this thing in Pascal, because the kernel of my
>>hobby OS (the target system) is also written in Pascal.  Hence, I
>>would port one compiler and one binary toolchain to get two languages,
>>a kernel, and a usable userspace application running.
> 
> 
> But then, if it's for a new OS, why not implement Common Lisp?  You'd
> be the sole provider of a Common Lisp implementation on your hobby OS!
> 
Because I want to add customized features to this language, but when I 
emailed the ECLS maintainers for further documentation than I found on 
their website, I got back the same docs and some not-too-helpful advice.

With this dialect I've already added one of those features (a bit of 
reflection), and can add the other (shell capabilities for use on the 
command line) by installing a hook into or redefining eval.

Don't worry about it.  As long as the original remains accessible 
through some mechanism you can overwrite and/or redefine any function 
you like, and the language is designed to be easily translatable from 
Common Lisp.

Anyways, I've figured out a way to implement tagbody/go and 
block/return-from.  Whenever something that could have an abnormal 
return to it is created, a structure for it (depending on whether 
tagbody or block, different struct) is entered into a list in a new 
environment object (whose parent, of course, is the old/calling 
environment).  This struct contains setjmp() buffers for everywhere the 
code could go/return-from to.  When the code actually returns, it sends 
back via longjmp() a structure containing the return values and its 
environment.  The original (returned-to) code releases this environment, 
  which deletes it, and releases its parent, if it only has one reference.
The return values are then returned.

Actually, for tagbody I suppose holding an "extra" reference to keep the 
tagbody's environment from deleting on a go will be needed, but whatever.

Note that since tags and blocks will now be part of the environment this 
allows continuations for free.
-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Pascal Bourguignon
Subject: Re: Implementation of control structures
Date: 
Message-ID: <8764lpgznu.fsf@thalassa.informatimago.com>
Eli Gottlieb <···········@gmail.com> writes:
> Anyways, I've figured out a way to implement tagbody/go and
> block/return-from.  Whenever something that could have an abnormal
> return to it is created, a structure for it (depending on whether
> tagbody or block, different struct) is entered into a list in a new
> environment object (whose parent, of course, is the old/calling
> environment).  This struct contains setjmp() buffers for everywhere
> the code could go/return-from to.  When the code actually returns, it
> sends back via longjmp() a structure containing the return values and
> its environment. [...]

I think I should mention that setjump/longjump is rather costly.  They
have to save and restore all the processor registers, which would be
on the order of 100 bytes on 32-bit CISC processors, 200 bytes on
64-bit CISC processors, and even more on RISC processors, even not
counting floating point registers, several L1 cache lines in any case.
I'm afraid you'd have almost one setjump buffer to fill for one in two
open parenthesis.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

This is a signature virus.  Add me to your signature and help me to live.
From: eligottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <1144181442.811651.217860@z34g2000cwc.googlegroups.com>
Pascal Bourguignon wrote:
>
> I think I should mention that setjump/longjump is rather costly.  They
> have to save and restore all the processor registers, which would be
> on the order of 100 bytes on 32-bit CISC processors, 200 bytes on
> 64-bit CISC processors, and even more on RISC processors, even not
> counting floating point registers, several L1 cache lines in any case.
> I'm afraid you'd have almost one setjump buffer to fill for one in two
> open parenthesis.
>
>
> --
> __Pascal Bourguignon__                     http://www.informatimago.com/
>
> This is a signature virus.  Add me to your signature and help me to live.

http://community.freepascal.org:10000/docs-html/rtl/system/jmp_buf.html

As it turns out, Free Pascal only spends 3 pointers and 3 longints (on
an x86 longint = 32 bits) on its jmp_buf type, which stores register
data for setjmp()/longjmp() purposes.

On my system that's just 24 bytes per jump.  Not too expensive on a
modern system.
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <nTvYf.39613$jf2.34053@twister.nyroc.rr.com>
Pascal Bourguignon wrote:
> Eli Gottlieb <···········@gmail.com> writes:
> 
>>Anyways, I've figured out a way to implement tagbody/go and
>>block/return-from.  Whenever something that could have an abnormal
>>return to it is created, a structure for it (depending on whether
>>tagbody or block, different struct) is entered into a list in a new
>>environment object (whose parent, of course, is the old/calling
>>environment).  This struct contains setjmp() buffers for everywhere
>>the code could go/return-from to.  When the code actually returns, it
>>sends back via longjmp() a structure containing the return values and
>>its environment. [...]
> 
> 
> I think I should mention that setjump/longjump is rather costly.  They
> have to save and restore all the processor registers, which would be
> on the order of 100 bytes on 32-bit CISC processors, 200 bytes on
> 64-bit CISC processors, and even more on RISC processors, even not
> counting floating point registers, several L1 cache lines in any case.
> I'm afraid you'd have almost one setjump buffer to fill for one in two
> open parenthesis.
> 
> 
You mean because (defun) wraps a block around its body?  Hmmm...

-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Pascal Bourguignon
Subject: Re: Implementation of control structures
Date: 
Message-ID: <87mzf1fk7v.fsf@thalassa.informatimago.com>
Eli Gottlieb <···········@gmail.com> writes:

> Pascal Bourguignon wrote:
>> Eli Gottlieb <···········@gmail.com> writes:
>> 
>>>Anyways, I've figured out a way to implement tagbody/go and
>>>block/return-from.  Whenever something that could have an abnormal
>>>return to it is created, a structure for it (depending on whether
>>>tagbody or block, different struct) is entered into a list in a new
>>>environment object (whose parent, of course, is the old/calling
>>>environment).  This struct contains setjmp() buffers for everywhere
>>>the code could go/return-from to.  When the code actually returns, it
>>>sends back via longjmp() a structure containing the return values and
>>>its environment. [...]
>> I think I should mention that setjump/longjump is rather costly.
>> They
>> have to save and restore all the processor registers, which would be
>> on the order of 100 bytes on 32-bit CISC processors, 200 bytes on
>> 64-bit CISC processors, and even more on RISC processors, even not
>> counting floating point registers, several L1 cache lines in any case.
>> I'm afraid you'd have almost one setjump buffer to fill for one in two
>> open parenthesis.
>> 
> You mean because (defun) wraps a block around its body?  Hmmm...

And because while you don't often use unwind-protect, tagbody, catch,
block, etc, yourself, they are used a lot inside macros
(with-open-file, handler-case, etc) that are used often.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Our enemies are innovative and resourceful, and so are we. They never
stop thinking about new ways to harm our country and our people, and
neither do we. -- Georges W. Bush
From: Marcin 'Qrczak' Kowalczyk
Subject: Re: Implementation of control structures
Date: 
Message-ID: <87hd5al0b3.fsf@qrnik.zagroda>
Pascal Bourguignon <······@informatimago.com> writes:

> First as described above, you must unwind the scope up to the block
> you must exit. In a compiler, you could have a stack offset compiled
> in the RETURN-FROM instructions, which would compute the address on
> the stack, and the unwinding of the scopes would be complete when the
> stack have been poped down to that pointer, or you could keep track of
> the number of frames to unwind.  Perhaps the later would be easier.

Even though block names are lexical, the number of stack frames can't
be computed statically, because RETURN-FROM may be executed in a local
function called from other functions called from the BLOCK.

It's even possible that an erroneous code does RETURN-FROM after
the corresponding BLOCK has exited. This is an error which can't be
detected statically.

(By "can't be done statically" I meant that this is so in general.)

-- 
   __("<         Marcin Kowalczyk
   \__/       ······@knm.org.pl
    ^^     http://qrnik.knm.org.pl/~qrczak/
From: Pascal Bourguignon
Subject: Re: Implementation of control structures
Date: 
Message-ID: <87psjygrt6.fsf@thalassa.informatimago.com>
Marcin 'Qrczak' Kowalczyk <······@knm.org.pl> writes:

> Pascal Bourguignon <······@informatimago.com> writes:
>
>> First as described above, you must unwind the scope up to the block
>> you must exit. In a compiler, you could have a stack offset compiled
>> in the RETURN-FROM instructions, which would compute the address on
>> the stack, and the unwinding of the scopes would be complete when the
>> stack have been poped down to that pointer, or you could keep track of
>> the number of frames to unwind.  Perhaps the later would be easier.
>
> Even though block names are lexical, the number of stack frames can't
> be computed statically, because RETURN-FROM may be executed in a local
> function called from other functions called from the BLOCK.

True. 
Ah, you really need to consider all the CL special operators at once...

> It's even possible that an erroneous code does RETURN-FROM after
> the corresponding BLOCK has exited. This is an error which can't be
> detected statically.
>
> (By "can't be done statically" I meant that this is so in general.)


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

COMPONENT EQUIVALENCY NOTICE: The subatomic particles (electrons,
protons, etc.) comprising this product are exactly the same in every
measurable respect as those used in the products of other
manufacturers, and no claim to the contrary may legitimately be
expressed or implied.
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <w3GYf.15977$Mj.5620@twister.nyroc.rr.com>
Block and return-from actually work now, and as soon as I find out where 
an environment is being released I'll be using them to make continuations.

The tough part is going to be tagbody/go.

-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Rob Warnock
Subject: Re: Implementation of control structures
Date: 
Message-ID: <qo-dnTiGz7g0oq7ZnZ2dnUVZ_t6dnZ2d@speakeasy.net>
Eli Gottlieb  <···········@gmail.com> wrote:
+---------------
| Block and return-from actually work now, and as soon as I find out where 
| an environment is being released I'll be using them to make continuations.
| 
| The tough part is going to be tagbody/go.
+---------------

Indeed. I would have started there first, actually, since once
you have the full generality[1] of TAGBODY/GO working you can
easily implement BLOCK and RETURN-FROM in terms of TAGBODY/GO:

    http://home.pipeline.com/~hbaker1/MetaCircular.html

Besides, you need TAGBODY working to implement DO, DOTIMES,
DOLIST, DO-{,ALL-,EXPERNAL-}SYMBOLS and others which have
"implicit tagbodies" in their bodies.


-Rob

[1] Specifically, the ability to GO from within a nested LAMBDA:

      > (tagbody
	  (mapc (lambda (x)
		  (print x)
		  (when (eql x 13)
		    (go bye-bye)))
		'(3 5 6 13 1 2 7 8))
	  bye-bye)

      3 
      5 
      6 
      13 
      NIL
      > 

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <ZQPYf.16605$Mj.4119@twister.nyroc.rr.com>
Eli Gottlieb wrote:
> Block and return-from actually work now, and as soon as I find out where 
> an environment is being released I'll be using them to make continuations.
> 
> The tough part is going to be tagbody/go.
> 
I correct myself.  There's an excellent reason that it can't be used to 
make continuations (whatever, imho), and that's that longjmp() cannot 
rewind an unwound call stack.

Feh.

-- 
The science of economics is the cleverest proof of free will yet 
constructed.
From: Eli Gottlieb
Subject: Re: Implementation of control structures
Date: 
Message-ID: <ZuRYf.35281$Da7.24256@twister.nyroc.rr.com>
Eli Gottlieb wrote:
> Block and return-from actually work now, and as soon as I find out where 
> an environment is being released I'll be using them to make continuations.
> 
> The tough part is going to be tagbody/go.
> 
And now tagbody and go are implemented, using some of the ugliest Pascal 
I've read or written in my life.

-- 
The science of economics is the cleverest proof of free will yet 
constructed.