Contrary to many other compiled languages, Ada requires the support of exceptions and makes very heavy use of them.
Using a processor specific functionality to support exceptions creates very complicated implementations since such must unwind the processor stack knowingly. This is a big problem if you cannot control 100% of the processor stack. Was this function implemented the same way as all the others? If not, the stack may just include a couple of registers instead of the frame register followed by the local variables.
Not only that, the stack may be used dynamically (grow and shrink with time.)
A solution to avoid all such problems is to implement our own stack (which I call AAda Stack at this time.)
The idea doesn't resolves the underlying processor or operating system exceptions, however, using only low level functions, those should be really rare and can be implemented in a slower manner.
The concept of a processor stack is very simplistic: it allows you to call a sub-routine and come back right after the point of the call. In general, it looks like this in assembly:
The call instruction pushes the current value of PC on the stack so later the return (ret) can pop it and save it back in PC.
This means you can have only one point of return. In case of Ada, it would be preferable to have two points of return: the exception handler at that point in time and the normal return point for the call. This means each declare that include an exception handler adds a new context which includes the new exception handler pointer. Then when you call a function, you actually jump to it adding the return pointer to the context and passing the AAda context to the sub-function.
With this in mind, the assembly code will instead look something like this:
; when the exception handler pointer changes call allocate_new_exception_handler mov exception_handler_ptr, (context, exception_handler) [...] ; when calling a sub-function mov return_ptr, (context, return) jmp sub_routine return_ptr: [...] exception_handler_ptr: [...] sub_routine: [...] ; raise an exception call setup_exception jmp (context, exception_handler) [...] ; normal return ; (after saving result or out variables) jmp (context, return)
The context can include everything we need such as the parameters of the function being called, including the return value. This way we completely eliminate the need for a processor stack. (the return value is especially welcome here since that way the caller can specify the pointer where the result of a function needs to be saved without having some special case on the call.)
The call setup_exception and call allocate_new_exception_handler would require error handling too and we may actually want to transform these calls into AAda Stack calls too. After all, it would make sense. On the other hand, we want to avoid capturing the lower frames information in our exceptions.
On the other hand, it may become inline code (since there isn't that much to do in both circumstances. The context allocation may very well work like a stack, for example.)
One thing to mention, the contexts are used to record all the local variables. This means we can unwind the stack knowingly since each function (or more precisely, each declared context) knows how to destroy its local variables and these are right in that context and we cleanly jump to the exit code of each function (without the need for special catch mechanisms.)