First of all, in the last version of the intermediate language there was a very important omission, namely UNSETJMP, an instruction to remove jump frames from the stack, so that they are no longer active. Furthermore, the version of GO that we implemented did not undo special variable bindings, nor calls to SETJMP.
In addition to this we have changed the semantics of SETJMP, so that it now behaves as an assignment to a variable, which is immediately followed by a JMP-IF-NOT that tests whether we arrive from a nonlocal LONGJMP or just from the preceding statements. This will make our life easier because we will only have three jump statments, JMP, JMP-IF-NOT and JMP-NTH, a kind of computed goto which is used by TAGBODY.
I have fixed this and added three more passes. One of them looks for calls to SETJMP and eliminates them if the jump point that it creates has not been referenced by any RETURN or GO statement. This will happen whenever a BLOCK or a TAGBODY is not referenced from a child function.
The other pass looks for local variables added using GROW-ENV and sees whether these variables are actually referenced by children functions. If this is not the case, we can eliminate the call to GROW-ENV all together and replace this environment variable with the immediately previous one. At the end of this pass we may call a function that identifies which functions do actually have a closure environment and which ones do not. This step is tricky to get right and we have mostly borrowed the code from current ECL.
The final pass runs over the code from the end to the beginning, eliminating occurrences of SET and CALL whose destination is an unused variable, but in the case of CALL only if the function has no side effects. By doing it from bottom to top we can eliminate temporaries that were created, as in the following two statements
SET <TMP-1> 10 SET <X> <TMP-1>
All together these three passes compactify the code significantly, and leave it ready for analyzing the code flow graph and removing unused branches. Note the difference between the two pieces of code, before,
;;;
;;; Compiling:
;;; (LET ((X 1))
;;; (TAGBODY
;;; FOO
;;; (TAGBODY
;;; BEGIN
;;; (LET ((X 1))
;;; (DECLARE (SPECIAL X)))
;;; (GO FOO))))
;;;
;;; Function name: ANONYMOUS-1
;;; Required arguments: 1
;;; Needs closure: NIL
CALL <TMP-1> [GLOBAL/GET-REQUIRED] 0
GROW-ENV <ENV-1> <CBLOCK> NIL
SET <CBLOCK> <TMP-1>
GROW-ENV <ENV-2> <X> NIL
SET <X> 1
GROW-ENV <ENV-3> :TAGBODY-4 OLD-ENV-VAR
CALL :TAGBODY-4 [GLOBAL/UNIQUE-ID]
SETJMP <TMP-3> <TMP-2> :TAGBODY-4
JMP-IF-NOT <TMP-3> :L1-1
JMP-NTH <TMP-2> :TAGBODY-4 :FOO
:L1-1:
:FOO:
GROW-ENV <ENV-4> :TAGBODY-5 OLD-ENV-VAR
CALL :TAGBODY-5 [GLOBAL/UNIQUE-ID]
SETJMP <TMP-5> <TMP-4> :TAGBODY-5
JMP-IF-NOT <TMP-5> :L2-2
JMP-NTH <TMP-4> :TAGBODY-5 :BEGIN
:L2-2:
:BEGIN:
GLOBAL-BIND X 1
UNBIND-GLOBAL X
UNSETJMP :TAGBODY-5
JMP :FOO
UNSETJMP :TAGBODY-5
UNSETJMP :TAGBODY-4
SET <MV-1275>
and after the two pases
JMP :L1-1
:L1-1:
:FOO:
JMP :L2-2
:L2-2:
GLOBAL-BIND X 1
UNBIND-GLOBAL X
JMP :FOO
> I have fixed this and added three more passes. One of them
> looks for calls to SETJMP and eliminates them if the jump point
> that it creates has not been referenced by any RETURN or GO
> statement. This will happen whenever a BLOCK or a TAGBODY is
> not referenced from a child function.
Forgive me if I’m missing something, but, what if the child function gets redefined — does it mean that the user is expected to recompile all callers binding nonlocal control transfer destinations?
Posted by Samium Gromoff on June 22nd, 2009.
When referring to “child function” I mean those created via FLET or LABELS. Those can not be redefined and the semantics of GO, TAGBODY, RETURN, etc, are well defined. In other words, if you find that no child function (ie no function in a FLET/LABELS) uses a label that is defined in the body of the main function, then you do not need to establish a global return point for GO statements and it all can be simplified to ordinary labels.
Posted by Juan Jose Garcia Ripoll on June 22nd, 2009.