Yhc/RTS/Exceptions: Difference between revisions
TomShackell (talk | contribs) No edit summary |
TomShackell (talk | contribs) No edit summary |
||
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 | ||
throw | <haskell> | ||
... | |||
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 === | 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 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 | catchException action handler = IO $ \ w -> primCatch | ||
(unsafePerformIO action) | |||
(\e -> unsafePerformIO (handler e)) | |||
</haskell> | </haskell> | ||
Line 36: | Line 71: | ||
<code> | <code> | ||
primCatch | primCatch act h | ||
NEED_HEAP_32 | |||
CATCH_BEGIN handler | CATCH_BEGIN handler | ||
PUSH_ZAP_ARG | 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.