Difference between revisions of "Yhc/RTS/Exceptions"

From HaskellWiki
< Yhc‎ | RTS
Jump to navigation Jump to search
 
Line 12: Line 12:
 
</haskell>
 
</haskell>
   
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.
+
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
   
  +
<haskell>
throw simply throws an exception.
 
  +
...
  +
catch doStuff $ \ e -> case e of
  +
ErrorCall _ -> return ()
  +
_ -> throw e
  +
...
  +
</haskell>
  +
  +
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
  +
  +
<haskell>
  +
throw :: Exception -> a
  +
throw = primThrow
  +
  +
primThrow :: a -> b
  +
</haskell>
  +
  +
primCatch cannot be written in Haskell and is instead defined directly in bytecode (see src/runtime/BCKernel/primitive.c) as
  +
  +
<code>
  +
primThrow e
  +
PUSH_ZAP_ARG e
  +
THROW
  +
</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'.
   
=== catch ===
+
==== catch ====
   
 
catch is implemented using YHC.Exception.catchException
 
catch is implemented using YHC.Exception.catchException
Line 22: Line 57:
 
<haskell>
 
<haskell>
 
catchException :: IO a -> (Exception -> IO a) -> IO a
 
catchException :: IO a -> (Exception -> IO a) -> IO a
catchException io handler = IO $ \ w -> primCatch
+
catchException action handler = IO $ \ w -> primCatch
(unsafePerformIO io)
+
(unsafePerformIO action)
(\e -> unsafePerformIO (handler e))
+
(\e -> unsafePerformIO (handler e))
 
</haskell>
 
</haskell>
   
Line 36: Line 71:
   
 
<code>
 
<code>
primCatch e h
+
primCatch act h
  +
NEED_HEAP_32
 
CATCH_BEGIN handler
 
CATCH_BEGIN handler
PUSH_ZAP_ARG e
+
PUSH_ZAP_ARG act
 
EVAL
 
EVAL
 
CATCH_END
 
CATCH_END
Line 47: Line 83:
 
RETURN_EVAL
 
RETURN_EVAL
 
</code>
 
</code>
  +
  +
'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 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.

Revision as of 19:09, 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 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'.

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 cannot be written in Haskell and is instead defined in bytecode as

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 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.