The following paragraphs describe Data Structures used by the Run-Time System to manage all the exceptions.
GNAT implements the exception identifier as an access to a record (Exception_Data_Ptr7.1). Figure presents the fields of this record.
The field Handled_By_Others is used to differentiate the user-defined exception from the run-time internal exceptions (i.e. task abortion) which can not be handled by the user-defined exception handlers. The field Lang defines the language where the exception is declared (by default ``Ada''). The next two fields are used to store the full name of the exception. This name is composed of a prefix (the full path of the scope where the exception is declared) and the exception name. The last field is used to create linked lists of exception identifiers (describe in section ).
When an exception is raised, the corresponding exception occurrence is stored by the GNAT run-time in the Compiler_Data field of the ATCB. The data type of this field is a record; the Current_Excep7.2 field of this record stores the exception occurrence.
The Exception_Raised field is set to True to indicate that this exception occurrence has actually been raised. When an exception occurrence is first created, it is set to false; then, when it is later processed by the GNARL subprogram Raise_Current_Exception7.3, it is set to True. This allows the run-time to distinguish if it is dealing with an exception re-raise.
Because the visibility rules of Ada exceptions (an exception may not be visible, though handled by the others handler, re-raised and then again visible to some other calling scope) a global table must be used (Exceptions_Table7.4). In order to handle the exceptions in an efficient way, the Ada run-time uses a hash table (cf. Figure ).
As the reader can see, an accesses table to the exception identifiers is used. A simple linked list of exception identifiers is used to handle collisions. The field HTable_Ptr7.5 is used to link the exception identifiers.
When an exception is raised in a task, the corresponding exception identifier must be found. Therefore the hash function is evaluated, and the resulting linked list is traversed to look for the exception identifier. Then its reference is stored in the ATCB of the task. This reference is kept in the ATCB until the exception is handled (though the exception may not be visible in some exception handlers).
The elements in the hash table are never removed; they are kept until the end of the program.
The compiler translates an exception declaration to a variable declaration of type Exception_Data. In order to distinguish among the exceptions with the same name, the name of an exception is made up of the complete path of its scope. For instance, if we declare the exception My_Error in the function Locate, the name assigned by the compiler will be Locate.My_Error. Since it is necessary to include the associated data in the exceptions hash table, the compiler inserts a call to the GNARL subprogram Register_Exception7.6. This subprogram evaluates the hash function from the exception name and then inserts its data record at the beginning of the corresponding linked list. This record remains in the list until the end of the program.
This section presents a general overview of the exception handlers implementation done in GNAT via the POSIX setjump/longjmp mechanism. The long jump is a POSIX operation which transfers control from the current point of execution to the point of a previous call to setjmp() at a lower nesting level of stack frames. Specifically, setjmp() can be called as the condition of an if statement. In essence, setjmp() saves the state of the thread at the point of call into a jump buffer variable and returns the value zero. The longjmp() operation reloads the state from the jump buffer which transfers control to the setjmp() call causing setjmp() to return again, but this time with a non-zero value. Thus the longjmp() can be used to roll back a computation to an earlier conditional branching point and take the other branch (in our case, the exception handler) [GMB93, section 4.2].
The GNAT compiler translates the following Ada code
begin User_Code; exception when Exception1 => Exception1_Code; when Exception2 => Exception2_Code; . . . when others => Others_Code; end;
...to the following code:
declare Env : Jmp_Buf; begin if setjmp(Env) then User_Code; else declare Exception_ID: GNARL.Exception_ID; begin Identify the raised exception; GNARL.Undefer_Abortion; case Exception_ID is when Exception1 => Exception1_Code; when Exception2 => Exception2_Code; . . . when others => Others_Code; end case; end; -- declare end if; end;
Ada allows an exception to be raised in two different ways: (1) by means of the raise statement and (2) by means of the procedure Ada.Exceptions.Raise_Exception which allows the programmer to associate a message to the exception. In both cases, the compiler generates a call to a GNARL function which carries out the following actions: