SignalsImpl.mesa
This version for DRAGON!
Copyright Ó 1987 by Xerox Corporation. All rights reserved.
Carl Hauser, May 8, 1987 4:30:38 pm PDT
DIRECTORY
Basics,
DragonRuntimeSupport,
DragonStack,
RuntimeError;
SignalsImpl: CEDAR MONITOR
IMPORTS DRS: DragonRuntimeSupport, Process
EXPORTS RuntimeError
SHARES Process
Types and Related Declarations
Global Variables (protected by monitor)
ucsHandler: RuntimeError.UCSProc ← NIL;
Exported to RuntimeError
ExceptAny: TYPE ~ SIGNAL ANY RETURNS ANY;
SendMsgSignal: PUBLIC SIGNAL RETURNS [signal: ExceptAny, args: PTR] ~ CODE;
ResumeError: PUBLIC SIGNAL ~ CODE;
UnwindError: PUBLIC ERROR ~ CODE;
UNCAUGHT: PUBLIC ERROR [signal: ExceptAny, args: PTR] ~ CODE;
InformationalSignal:
PUBLIC
SAFE
PROC [signal: ExceptAny, args:
PTR] =
TRUSTED {
Who makes informational signals? What language feature? According to Dan Swinehart, this kind of signalling is done in code written by wizards and not produced by any Mesa language feature. The idea is to signal in such a way that if the signal is not caught, you don't end up in the debugger.
SignalHandler[NIL, signal, args, NIL, TRUE];
};
RegisterUncaughtSignalHandler:
PUBLIC
ENTRY
PROC [proc: RuntimeError.UCSProc]
RETURNS [old: RuntimeError.UCSProc] = {
old ← ucsHandler;
ucsHandler ← proc;
};
Language support routines -- Exported to DragonRuntimeSupport for type checking
CatchProc: TYPE ~ DRS.HandlerType;
HandlerAction: TYPE = DRS.HandlerAction;
BytePC: TYPE = DRS.BytePC;
RaiseError:
PUBLIC PROC [signal: ExceptAny, args:
PTR] = {
SignalHandler[NIL, signal, args, NIL];
ERROR ResumeError
};
RaiseSignal:
PUBLIC PROC [signal: ExceptAny, rtns:
PTR, args:
PTR] = {
SignalHandler[NIL, signal, args, rtns];
};
Internal Procedures
Frame: TYPE; -- here, the Frames are abstract. They contain the pc and the local variables of each procedure invocation. The machine dependent code section below implements the various operations on abstract frames in terms of the concrete frame type for the particular machine involved.
SignalHandlerState:
TYPE ~
RECORD [
start: Frame ← NIL,
signal: ExceptAny ← NIL,
frame, nextFrame: Frame ← NIL,
target, nextTarget: Frame ← NIL,
unwinding: BOOL ← FALSE,
previousHandler: SigState ← NIL
];
SigState: TYPE = POINTER TO SignalHandlerState;
Putting self as the first argument ensures that it will be found in register 0, which is very convenient later when SignalHandler must interpret the Nacho corresponding to another instance of itself. However, it is a machine dependent trick.
SignalHandler:
PROC [self: SigState, sig: ExceptAny, arg:
PTR, rtns:
PTR, informational:
BOOL ←
FALSE] ~ {
action: HandlerAction;
catchPhrase: BOOL;
catchFSIndex: BYTE;
catchProc: CatchProc;
self ← NewSignalHandlerState[signal: sig];
MarkSignalHandlerFrame[];
{
OPEN self;
unwinding ← FALSE;
Capture the state of the process down to this point (not including this procedure execution's frame).
start ← ExternalizeStack[];
First pass: find catch phrases, looking for GOTO out of one
unwinding = FALSE
nextFrame ← start;
UNTIL (nextFrame = target)
OR unwinding
DO
walk from nextFrame toward "cool" end of stack, looking for target
frame ← nextFrame;
IF IsSignalHandlerFrame[frame]
THEN {
frame is for another instance of this (SignalHandler) procedure.
OPEN thisSignaller: FindSigState[frame]^;
IF thisSignaller.unwinding
THEN
nextFrame ← thisSignaller.nextTarget
choose our nextFrame
ELSE nextFrame ←
SELECT
TRUE
FROM
signal ~= thisSignaller.signal => Parent[frame],
different signals propagate through intervening frames
signal = thisSignaller.signal => thisSignaller.nextFrame,
from the Mesa 11.0 manual: "all of the routines whose frames lie beyond thisSignaller, up to the frame containing the catch phrase called by thisSignaller, have already had a chance to handle signal, so they are not given it again."
ENDCASE => ERROR;
}
ELSE
frame is NOT for another SignalHandler - just walk the stack to nextFrame
nextFrame ← Parent[frame];
Will this frame catch any signals? It may, depending on the body of the catch phrase (it may also raise signals!)
catchProc ← CheckCatch[frame];
IF catchProc #
NIL
THEN {
call the catch phrase
[action, exitPC, levels] ← catchProc[regsPtr: GetRegs[frame], except: signal, rtnPtr: rtns, argPtr: arg
! SendMsgSignal => RESUME[sig, arg] -- SendMsgSignal is SIGNALled by an ANY-catcher to request the details of the signal it is processing -- ];
SELECT action
FROM
reject => NULL;
resume => {
the handler has filled rtns^ with the results for signal's raiser
exit =>
IF ~informational
THEN {
target ← frame;
unwinding ← TRUE;
arg ← 0;
};
ELSE
Apparently, non-local GOTOs from a handlers for signals originated as informational are ignored and treated as REJECTs.
ENDCASE;
};
ENDLOOP;
What happened?
SELECT
TRUE
FROM
unwinding => NULL;
informational OR signal = LOOPHOLE[UNCAUGHT] => NULL; -- does an implicit RESUME
Informational signals are resumed either by an explicit resume in a catch phrase or when the signal has propagated to the top of the stack. IF signal = LOOPHOLE[UNCAUGHT] at this point, it was raised in the following ENDCASE (in a different invocation of SignalHandler), so upon resumption, that SignalHandler calls ucsHandler.
ENDCASE => {
We want to be able to RESUME UNCAUGHT, but we don't want a random catch phrase to do so. So, we declare it to be an ERROR in the interface, but raise it as a SIGNAL here. When (if) UNCAUGHT percolates to the top of the stack, it is implicitly resumed (see the preceding line of code). Control then passes to the statement following the invocation of UNCAUGHT, which calls the debugger (or whatever).
Uncaught: TYPE = SIGNAL [SIGNAL ANY RETURNS ANY, WORD];
LOOPHOLE[UNCAUGHT, Uncaught][signal, message];
ucsHandler[message, signal, frame];
};
Now unwind
IF unwinding
THEN DO
-- unless a GOTO is done out of an UNWIND catch phrase, this loop is traversed at most once.
unwinding = TRUE;
nextTarget ← Parent[target];
nextFrame ← start;
UNTIL nextFrame = target
DO
walk from nextFrame toward "cool" end of stack, looking for target
frame ← nextFrame;
IF IsSignalHandlerFrame[frame]
THEN {
frame is for another instance of this (SignalHandler) procedure.
OPEN thisSignaller: FindSigState[frame]^;
IF thisSignaller.unwinding
THEN {
SetParent[frame, thisSignaller.nextFrame];
FreeFrame[thisSignaller.frame]; -- free the frame he is examining (i.e. has passed UNWIND to; it should not see another UNWIND).
};
};
nextFrame ← Parent[frame];
Offer UNWIND to frame. If it catches UNWIND it may raise more signals in the catch phrase!
catchProc ← CheckCatch[frame];
IF catchProc #
NIL
THEN {
call the catch phrase
action ← catchProc[regsPtr: GetRegs[frame], except: signal, rtnPtr:
NIL, argPtr:
NIL
! SendMsgSignal => RESUME[sig, arg] -- SendMsgSignal is SIGNALled an ANY-catcher to request the details of the signal and message it is processing -- ];
SELECT action
FROM
reject => NULL;
resume => ERROR ResumeError;
exit => ERROR UnwindError;
ENDCASE;
};
Isn't frame ALWAYS equal start here?
IF frame = start THEN start ← nextFrame;
Isn't frame ALWAYS equal self.link here?
IF frame = LOOPHOLE[self.link] THEN self.link ← nextFrame;
FreeFrame[frame];
REPEAT
FINISHED -- The inner loop -- => EXIT -- the outer loop --
ENDLOOP;
ENDLOOP;
};
};
Dragon machine dependent procedures
Frame: TYPE = DragonStack.Nacho;
CheckCatch:
PROC [frame: Frame]
RETURNS [catchProc: CatchProc] ~ {
Input: frame identifies a localFrame
Output:
catchProc # NIL iff frame has a catch phrase
NYI[];
RETURN[NIL];
};
MarkStack:
PROC []
RETURNS [] ~ {
sets the "signalHandler" bit in the IFU status for its caller
MUST NOT BE INLINE
NYI[];
};
IsSignalHandlerFrame:
PROC [frame: Frame]
RETURNS [
BOOL ←
FALSE] ~
INLINE {
returns the "signalHandler" bit in the IFU status of frame
RETURN [frame.status.signalHandler];
};
FindSigState:
PROC [frame: Frame]
RETURNS [sigState: SigState] ~
INLINE {
returns register 0 of frame
RETURN[LOOPHOLE[frame.regs[0]]];
};
Parent:
PROC [frame: Frame]
RETURNS [parent: Frame] ~
INLINE {
follows the caller pointer of frame
RETURN[frame.link];
};
SetParent:
PROC [frame: Frame, newParent: Frame] ~
INLINE {
frame.link ← newParent;
};
GetRegs:
PROC [frame: Frame]
RETURNS [RegArrayPtr] ~
INLINE {
pointer arithmetic computing the address of the regArray within the Nacho pointed to by frame
RETURN[LOOPHOLE[@frame.regs]];
};
Initialization
Initialize:
PROC = {
NYI[];
};
Initialize[];
END.