[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Exceptions and SYSTEM definition
Hi all,
I've done a first pass attempt at defining the SYSTEM extensions required
to support exceptions, garbage collection, and finalization. Here's what
I have so far:
Following is an explanation of how exceptions will be handled in the OOC compiler in
a system-independent manner.
Introduction
------------
Generally, exceptions will be generated by passing a negative parameter to the HALT
procedure. The HALT procedure then causes an exception to occur in a platform-dependent
manner (e.g., on most compilers tying into a UNIX-like OS the exception will be
simulated by a Signal). The exception handling mechanism is tailored by installing
a different exception handler into the SYSTEM module via the SYSTEM.Halt procedure
variable. The exception customization feature is primarily intended to help port OOC
to new targets; users are not normally expected to make use of the exception customization
features.
Following are the exception codes (i.e., HALT arguments) which must be recognized by all
systems which support OOC:
>0 -- normal HALT termination
0 -- silent HALT
-1 -- ASSERT failed (see SYSTEM.assert code)
-2 -- index out of range
-3 -- function without return
-4 -- invalid case
-5 -- type guard failed
-6 -- implicit type guard failed
-7 -- with guard failed
-8 -- value out of range
-9 -- delayed interrupt (see below)
Note that this exception handling mechanism is general enough to handle ASSERTs,
abnormal program terminations, and user-initiated program aborts. Arithmetic
exceptions and other errors are caught by the hardware which will usually generate
a signal which will be caught by the installed exception handler. The HALT
exceptions "piggy-back" on top of the "ILLEGAL INSTRUCTION" exception and are
distinguished by having a SYSTEM.halt value (the argument value of the HALT
function) which is not equal to -128. Assertion arguments are stored in the
SYSTEM.assert value so the exception handler can respond to different kinds
of assertions. A typical ASSERT would be defined as:
PROCEDURE ASSERT (c: BOOLEAN; n: LONGINT);
BEGIN
IF ~c THEN SYSTEM.assert:=n; HALT(-1) END
END ASSERT;
An ASSERT with no second argument would assign a default of 0 to SYSTEM.assert.
Finalization
------------
A general-purpose finalization system must also be tied into the exception handler
so that when a user aborts the program execution with a CTRL-C, the normal
termination procedures can be called to close any files which are open and
free other resources. The SYSTEM module defines a finalization procedure
variable called SYSTEM.FINALL() which can be called at the end of the main
program to release all resources. An associated procedure variable called
SYSTEM.REGFIN(obj, finalize) is called each time a new object is created which
requires finalization.
Garbage Collection
------------------
The garbage collector is another runtime feature which must be controlled via
the SYSTEM module. It makes use of the finalization list which is registered
with the SYSTEM module in order to automatically finalize any data associated
with freed memory blocks (e.g., a freed file block will automatically close
the associated file on the hard disk). The garbage collector can be called
programmatically via a SYSTEM.GC(markstack) procedure. The markstack argument
specifies whether the run-time stack should be checked for valid heap pointers.
If this argument is FALSE, it can be guaranteed that no objects are anchored on
the stack. A SYSTEM.gclock variable can be used to tailor the garbage collector's
behaviour as follows:
SYSTEM.gclock GC behaviour
0 Default (garbage collection before expanding the heap)
1 no garbage collection with markstack=TRUE as is the case
with no heap left
2 no garbage collection at all
Interrupts
----------
To support keyboard interrupts it is necessary to disable exceptions during
critical routines which cannot be interrupted. For example, the garbage
collection process cannot be interrupted since it operates on global data nor
can certain non-reentrant procedures found in the X11 library be interrupted
since the previous display operation may have left the X-server connection in
an inconsistent state when it was aborted.
A locking mechanism is used to handle these cases where a SYSTEM.lock variable
is incremented when entering a critical code region and decremented when leaving
the critical code. If an interrupt occurs while SYSTEM.lock > 0 then a
SYSTEM.interrupt boolean is set. The interrupt will be ignored temporarily
and then will be serviced once SYSTEM.lock = 0. Typically this final check
would be performed by the unlocking code which can then call HALT(-9) to
service the interrupt.
The SYSTEM definition extensions are:
MODULE SYSTEM;
(* standard system stuff omitted *)
(* various proposed SYSTEM extensions below *)
TYPE
Finalizer = PROCEDURE (obj: PTR);
Halter = PROCEDURE (n: LONGINT);
VAR
gclock*: SHORTINT; (* garbage collection mode (see above) *)
interrupt*: BOOLEAN; (* an interrupt is pending *)
lock*: LONGINT; (* number of active locked scopes *)
halt*: LONGINT; (* current HALT argument *)
assert*: LONGINT; (* current ASSERT argument *)
Halt*: Halter; (* installed HALT/exception handler *)
PROCEDURE GC* (markStack: BOOLEAN); (* invoke garbage collector *)
PROCEDURE REGFIN* (obj: PTR; finalize: Finalizer); (* register finalization for an object *)
PROCEDURE FINALL*; (* invoke all finalizations *)
END SYSTEM.
Examples
--------
Following are some examples of a typical installation for an exception handler:
PROCEDURE Halt(n: LONGINT);
VAR res: LONGINT;
BEGIN
SYSTEM.halt:=n;
res:=Unix.kill(Unix.gepid(), 4) (* raise an illegal instruction exception *)
END Halt;
where this exception handler would be installed by:
SYSTEM.Halt:=Halt;
In a UNIX OS, the exception handler could be defined by:
PROCEDURE Trap* (sig, code: LONGINT; scp: Unix.SigCtxPtr; addr: LONGINT);
BEGIN
Unix.signal(sig, Trap);
IF trapLevel = 0 THEN
trapLevel := 1;
CASE sig OF
| Unix.SIGINT:
IF lock > 0 THEN (* delay interrupt until lock = 0 *)
interrupted := TRUE; trapLevel := 0; RETURN
ELSE Out.String("INTERRUPT")
END
| Unix.SIGQUIT:
SYSTEM.FINALL(); Unix.exit(0)
| Unix.SIGILL:
CASE SYSTEM.halt OF
| 0: (* silent halt *)
SYSTEM.halt := -128; trapLevel := 0;
Unix.siglongjmp(trapEnv, 1);
| -1: Out.String("ASSERT(");
Out.Int(SYSTEM.assert, 1); Out.String(") FAILED")
| -2: Out.String("INDEX OUT OF RANGE")
| -3: Out.String("FUNCTION WITHOUT RETURN")
| -4: Out.String("INVALID CASE")
| -5: Out.String("TYPE GUARD FAILED")
| -6: Out.String("IMPLICIT TYPE GUARD FAILED")
| -7: Out.String("WITH GUARD FAILED")
| -8: Out.String("VALUE OUT OF RANGE")
| -9: SYSTEM.interrupted := FALSE; Out.String("DELAYED INTERRUPT")
ELSE
IF (SYSTEM.halt > 0) & (SYSTEM.halt < 256) THEN
Out.String("HALT("); Out.Int(SYSTEM.halt, 1); Out.Char(")")
ELSE Out.String("ILLEGAL INSTRUCTION")
END
END;
SYSTEM.halt := -128
| Unix.SIGFPE:
Out.String("ARITHMETIC EXCEPTION, code = "); Out.Int(code, 1)
| Unix.SIGBUS:
Out.String("BUS ERROR")
| Unix.SIGSEGV:
Out.String("SEGMENTATION VIOLATION")
| Unix.SIGPIPE:
Out.String("UNCONNECTED PIPE")
ELSE Out.String("SIGNAL "); Out.Int(sig, 0)
END;
Out.Ln
END;
trapLevel := 0;
Unix.siglongjmp(trapEnv, 1)
END Trap;
And this exception handler would be installed as:
trapLevel := 0;
Unix.signal(Unix.SIGINT, Trap); (* keyboard interrupt *)
Unix.signal(Unix.SIGQUIT, Trap); (* quit *)
Unix.signal(Unix.SIGILL, Trap); (* illegal instruction *)
Unix.signal(Unix.SIGFPE, Trap); (* arithmetic error *)
Unix.signal(Unix.SIGBUS, Trap); (* bus error *)
Unix.signal(Unix.SIGSEGV, Trap); (* segmentation violation *)
Unix.signal(Unix.SIGPIPE, Trap) (* unconnected pipe *)
A typical set of locking routines would be:
PROCEDURE Lock ();
BEGIN
INC(SYSTEM.lock)
END Lock;
PROCEDURE Unlock ();
BEGIN
DEC(SYSTEM.lock);
IF SYSTEM.interrupted & (SYSTEM.lock=0) THEN
HALT(-9)
END
END Unlock;
If anyone has any comments, additions, corrections, etc to add to this document
please start a discussion via the ooc-list.
Thanks,
Michael G.