Difference between revisions of "Yhc/RTS/Exceptions"

From HaskellWiki
< Yhc‎ | RTS
Jump to navigation Jump to search
Line 49: Line 49:
 
</code>
 
</code>
   
The THROW instruction removes the value on the top of the data stack 'e' and removes the exception handler on the top of the exception stack. In then returns control to that exception handler, this strips the data stack back to the place where the exception handler was created. THROW then pushes 'e' on the new top of the data 'stack'.
+
The THROW instruction removes the value on the top of the program stack 'e' and removes the exception handler on the top of the exception stack. In then returns control to that exception handler, this strips the program stack back to the place where the exception handler was created. THROW then pushes 'e' on the new top of the program 'stack'.
   
 
==== catch ====
 
==== catch ====
Line 68: Line 68:
 
</haskell>
 
</haskell>
   
primCatch cannot be written in Haskell and is instead defined in bytecode as
+
primCatch also cannot be written in Haskell, and is instead defined directly in bytecode:
   
 
<code>
 
<code>
Line 90: Line 90:
 
If evaluation of the action succeeds without throwing an exception then CATCH_END is executed which removes the handler on the top of the stack (which is necessarily the same handler as was pushed by CATCH_BEGIN).
 
If evaluation of the action succeeds without throwing an exception then CATCH_END is executed which removes the handler on the top of the stack (which is necessarily the same handler as was pushed by CATCH_BEGIN).
   
However, if evaluation of the action results in a call to throw then execution returns to 'handler'. Here we need to remember that THROW pushes the exception thrown on the data stack after stripping back to the exception handler. Thus at 'handler' we know that the exception throw is on the top of the data stack.
+
However, if evaluation of the action results in a call to throw then execution returns to 'handler'. Here we need to remember that THROW pushes the exception thrown on the program stack after stripping back to the exception handler. Thus at 'handler' we know that the exception thrown is on the top of the program stack.
  +
  +
We thus 'PUSH_ZAP_ARG h' to push the handler function on the stack, and 'APPLY 1' to apply the handler function to the exception and finally 'RETURN_EVAL' to call the handler function.
  +
  +
=== Interpreter implementation ===
  +
  +
In the C interpreter exception handlers are stored in the heap as standard Haskell heap nodes. The structure is given in src/runtime/BCKernel/node.h
  +
  +
<code>
  +
/* an exception handler */
  +
typedef struct _ExceptionHandlerNode {
  +
NodeHeader node;
  +
struct _ExceptionHandlerNode* next; /* next exception handler in the stack */
  +
Node* vapptr; /* vapptr of the handler code */
  +
CodePtr ip; /* ip to jump to for the handler code */
  +
UInt spOffs; /* offset of sp from G_spBase, offset is easier than ptr here because of GC */
  +
UInt fpOffs; /* offset of fp from G_spBase, again offsets easier */
  +
}ExceptionHandlerNode;
  +
</code>
  +
  +
vapptr, ip and fpOffs is basically the same information as stored in stack frames (see [[Yhc/RTS/Machine]]). spOffs is included for completeness although in practice it isn't strictly necessary.
  +
  +
fpOffs and spOffs are offsets from G_spBase rather than direct pointers because program stacks are stored in the heap, and thus might be moved by the garbage collector. The offset from the base of the stack, however, will not be changed by the garbage collector.
  +
  +
The 'next' field allows us to setup a stack of exception handlers. Each process has its own exception stack, so the handler on the top of the stack is stored in the process information structure (see [[Yhc/RTS/Concurrency]]).

Revision as of 19:23, 9 March 2007

RTS Exceptions

Support for 'imprecise exceptions' has recently been added to Yhc. Imprecise exceptions allow any kind of exception (including 'error') to be thrown from pure code and caught in the IO monad.

This page attempts to describe how imprecise exceptions are implemented in the Runtime System.

The most important haskell functions for imprecise exceptions are 'catch' and 'throw'.

catch :: IO a -> (Exception -> IO a) -> IO a
throw :: Exception -> a

catch takes some IO code to run, and an exception handler and it runs the IO code and if an exception occurs it runs the exception handler. throw simply throws an exception. For example

    ...
    catch doStuff $ \ e -> case e of 
                  ErrorCall _ -> return ()
                  _           -> throw e
    ...

This code will execute the IO action doStuff, and if an exception occurs it will catch it. If that exception resulted from a call to the 'error' function then it does nothing, otherwise it rethrows the exception.

The exception stack

catch blocks can be nested inside each other and throw returns control to the handler for the inner-most catch block. This structure naturally leads us to having a stack of exception handlers with each new catch block pushing a new handler on the stack at the beginning of the block and removing the top handler at the end of the block.

Haskell level implementation

throw

throw is implemented very simply

throw :: Exception -> a
throw = primThrow

primThrow :: a -> b

primCatch cannot be written in Haskell and is instead defined directly in bytecode (see src/runtime/BCKernel/primitive.c) as

primThrow e

  PUSH_ZAP_ARG e
  THROW

The THROW instruction removes the value on the top of the program stack 'e' and removes the exception handler on the top of the exception stack. In then returns control to that exception handler, this strips the program stack back to the place where the exception handler was created. THROW then pushes 'e' on the new top of the program 'stack'.

catch

catch is implemented using YHC.Exception.catchException

catchException :: IO a -> (Exception -> IO a) -> IO a
catchException action handler = IO $ \ w -> primCatch
                               (unsafePerformIO action)
                               (\e -> unsafePerformIO (handler e))

catchException simply converts from the IO monad into standard closures and passes them to the primitive 'primCatch'

primCatch :: a -> (b -> a) -> a

primCatch also cannot be written in Haskell, and is instead defined directly in bytecode:

primCatch act h

    NEED_HEAP_32
    CATCH_BEGIN handler
    PUSH_ZAP_ARG act
    EVAL
    CATCH_END
    RETURN
 handler:
    PUSH_ZAP_ARG h
    APPLY 1
    RETURN_EVAL

'CATCH_BEGIN label' creates a new exception handler and pushes it on the top of the exception stack. The new exception handler will return control to the function that executed the CATCH_BEGIN instruction and resume execution at the code given by 'label'.

Having pushed the new exception handler on the stack, primCatch forces evaluation of the action, causing the code inside the catch block to be executed.

If evaluation of the action succeeds without throwing an exception then CATCH_END is executed which removes the handler on the top of the stack (which is necessarily the same handler as was pushed by CATCH_BEGIN).

However, if evaluation of the action results in a call to throw then execution returns to 'handler'. Here we need to remember that THROW pushes the exception thrown on the program stack after stripping back to the exception handler. Thus at 'handler' we know that the exception thrown is on the top of the program stack.

We thus 'PUSH_ZAP_ARG h' to push the handler function on the stack, and 'APPLY 1' to apply the handler function to the exception and finally 'RETURN_EVAL' to call the handler function.

Interpreter implementation

In the C interpreter exception handlers are stored in the heap as standard Haskell heap nodes. The structure is given in src/runtime/BCKernel/node.h

/* an exception handler */ typedef struct _ExceptionHandlerNode {

 NodeHeader                        node;
 struct _ExceptionHandlerNode*     next;           /* next exception handler in the stack */
 Node*                             vapptr;         /* vapptr of the handler code */
 CodePtr                           ip;             /* ip to jump to for the handler code */
 UInt                              spOffs;         /* offset of sp from G_spBase, offset is easier than ptr here because of GC */
 UInt                              fpOffs;         /* offset of fp from G_spBase, again offsets easier */

}ExceptionHandlerNode;

vapptr, ip and fpOffs is basically the same information as stored in stack frames (see Yhc/RTS/Machine). spOffs is included for completeness although in practice it isn't strictly necessary.

fpOffs and spOffs are offsets from G_spBase rather than direct pointers because program stacks are stored in the heap, and thus might be moved by the garbage collector. The offset from the base of the stack, however, will not be changed by the garbage collector.

The 'next' field allows us to setup a stack of exception handlers. Each process has its own exception stack, so the handler on the top of the stack is stored in the process information structure (see Yhc/RTS/Concurrency).