Here is the part of the compiler that produces code for a call to exit
(which is a normal
procedure, rather than a kind of statement):
PROCEDURE EXIT;
VAR LCP: CTP;
BEGIN
IF SY = IDENT THEN
BEGIN SEARCHID([PROC,FUNC],LCP); INSYMBOL END
ELSE
IF (SY = PROGSY) THEN
BEGIN LCP := OUTERBLOCK; INSYMBOL END
ELSE LCP := NIL;
IF LCP <> NIL THEN
IF LCP^.PFDECKIND = DECLARED THEN
BEGIN GENLDC(LCP^.PFSEG); GENLDC(LCP^.PFNAME);
IF INMODULE THEN
BEGIN LINKERREF(PROC,LCP^.PFSEG,IC-2);
IF SEPPROC THEN LINKERREF(PROC,-LCP^.PFNAME,IC-1);
END
END
ELSE ERROR(125)
ELSE ERROR(125);
GEN1(30(*CSP*),4(*XIT*))
END (*EXIT*) ;
Thus the generated P-code for exit(f)
is something like
LDCI ⟨segment of f⟩
LDCI ⟨procedure number of f⟩
CSP 4
The limited memory of many computers on which UCSD Pascal expected to run necessitated a way to
divide a large program into smaller pieces, which USCD Pascal calls “segments”. If a routine
declaration is preceded by the word SEGMENT
, its code will be loaded into memory only when it is
called.
Routines are assigned numbers (the value of pfname
) beginning at 1; counting starts over in each
segment, so to refer to a routine unambiguously a ⟨segment, pfname
⟩ pair must be used.
Here is the assembly code for the exit
procedure:*
XIT: ; EXIT PROCEDURE
MOV JTAB,IPC ; FIRST SET IPC TO EXIT FROM CURRENT
ADD #EXITIC,IPC ; PROC ... GET INFO FROM CUR JTAB
SUB @IPC,IPC ; NOW IPC IS SET TO EXIT MY CALLER
CMPB @JTAB,@SP ; IS IT THE PROC # TO EXIT ANYWAY?
BNE XCHAIN ; IF NOT THEN CHAIN DYN LINKS TO FIND
CMPB @SEG,2(SP) ; IF PROC OK, HOW ABOUT SEG#?
BNE XCHAIN ; IF WRONG, THEN CHAIN DYN TOO
CMP (SP)+,(SP)+ ; ELSE CHUCK STACK STUFF
MORE ; AND DO THE RETURN CODE
XCHAIN: MOV MP,R0 ; OK...START EXITING STACKED PROCS
XLOOP: CMP R0,@BASE ; ARE WE ABOUT TO EXIT SYSTEM BLOCK?
BEQ XBOMB ; IF SO THEN BIG BOOBOO
MOV MSJTAB(R0),R1 ; ELSE OK...GRAB JTAB AND FUDGE MS IPC
ADD #EXITIC,R1 ; TO EXIT CODE RATHER THAN NORMAL REENTRY
SUB @R1,R1 ; R1 NOW HAS EXIT POINT IPC
MOV R1,MSIPC(R0) ; SO PLACE IN STACK FRAME
CMPB @MSJTAB(R0),@SP ; IS THIS THE PROC# TO EXIT FROM?
BNE 1$ ; IF NOT THEN GO TO NEXT CALLED PROC
CMPB @MSSEG(R0),2(SP) ; AND RIGHT SEG#
BNE 1$
CMP (SP)+,(SP)+ ; WELL, FOUND IT...CHUCK PARAMS
MORE ; AND FALL OUT OF PROC
1$: MOV MSDYN(R0),R0 ; CHAIN DOWN DYNAMIC LINKS!
BR XLOOP
XBOMB: TRAP NOEXIT
I'll assume that you know how the P-system's “mark stack” works; if not, Pemberton and Daniels
explain it in the book Pascal Implementation.
The algorithm first sets the P-code instruction pointer to the epilogue of the caller of exit
.
Then we check whether we're exiting the current routine, to take care of a common case.
(Note: MORE
is a macro that transfers control to the next P-code instruction.)
Otherwise, it searches up all the open activation records to find one corresponding to the routine number
and segment that were pushed prior to the exit
call. If one is found, the “return address” field of the record
is changed to point to the routine's epilogue.
In any case, the epilogue of the caller of exit
is executed, which causes control to transfer to
the epilogue of the routine being exit
ed from. By transferring to the epilogue, rather than just
to the return address, an exit
ed function will return a value.
However, if the search reaches the top of the stack, error trap 3 is signaled.
So that is how exit
works. But a bigger mystery remains: Why didn't they just support nonlocal
goto
statements? The code for doing so would surely be no more complicated than for exit
, and
the feature is part of the standard language (while exit
is not). Furthermore, nonlocal goto
has
lexical scope, making it easier to reason about than exit
, which has indefinite scope.
Maybe exit
was considered more structured than goto
—but then why not add a return
statement
and keep it lexically scoped?
Its dynamic nature does make exit
more powerful than goto
. If UCSD Pascal supported
routines as arguments, you could implement a condition system:
type signaltype = (endoffile, overflow, syntaxerror);
var ok: boolean;
signaled: signaltype;
handlers: set of signaltype;
procedure signal(s: signaltype);
begin
if not (s in handlers) then
exit(defaulthandler);
ok := false;
signaled := s;
case s of
endoffile: exit(catchendoffile)
overflow: exit(catchoverflow)
syntaxerror: exit(catchsyntaxerror)
end
end;
procedure handle(s: signaltype; procedure p; procedure c);
begin
handlers := handlers + [s];
p;
handlers := handlers - [s];
if (not ok) and signaled = s then
c;
ok := true
end;
procedure endoffilehandler(procedure p; procedure c);
procedure catchendoffile;
begin
p
end;
begin
handle(endoffile, catchendoffile, c)
end;
procedure overflowhandler(procedure p; procedure c);
procedure catchoverflow;
begin
p
end;
begin
handle(overflow, catchoverflow, c)
end;
procedure syntaxerrorhandler(procedure p; procedure c);
procedure catchsyntaxerror;
begin
p
end;
begin
handle(syntaxerror, catchsyntaxerror, c)
end;
Then you could say, e.g., overflowhandler(addtwonumbers, reportoverflow)
,
where addtwonumbers
attempts to add two numbers and does signal(overflow)
if the result is too large, and reportoverflow
prints a message like “number
too big”. Only the most recently established handler will be invoked for any
given condition, although you could change this by having the handlers call
signal
again.
This provides capabilities similar to Common Lisp's handler-case
. If
signaled
held a record
, you could convey even more information and
support a fairly sophisticated exception handling mechanism.
* Unlike other P-system derivatives, the P-code generated by the compiler is interpreted by an
assembly program akin to the Apollo AGC “interpreter”,
rather than using the P-machine or generating native code directly.
The code is taken from the I.5 sources
(pdf,
zip), in file
USCD Pascal 1.5 ProcOp.txt
, because it seems that the PDP-11 interpreter isn't included with the
II.0 sources
(pdf,
zip).
The II.0 distribution does, however, include an implementation of the P-code interpreter for the Z80
(the exit
source is in the file z80_p-code_source/proc2.text
), which uses the same algorithm as
is described above.
exit
procedure in version I.5 can be found on page 191 of the source code listing pascal.hansotten.com/uploads/ucsd/ucsd/…XBOMB: TRAP NOEXIT