I'm curious to know how exceptions are dealt with in OCaml runtime to make them so lightweight. Do they use setjmp/longjmp or do they return a special value in each function, and propagate it?
It seems to me that longjmp would put a little strain on the system, but only when an exception is raised, while checking for each function return value would need to check for every and each value after calling a function, which seems to me would put a lot of checks and jumps, and it seems it would perform worst.
By looking at how OCaml interfaces with C ( http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html#toc142 ), and looking at callback.h, it seems that an exception is tagged by using the memory alignment of objects ( #define Is_exception_result(v) (((v) & 3) == 2) ). This seems to indicate that its implementation doesn't use longjmp and checks each function result after each function call. Is that it? Or the C function already tries to catch any exception, and then converts it to this format?
Thank you!
Exceptions are first-class values in OCaml, and can be stored and manipulated like any other value. There are a number of exceptions defined in the Core Library, and many modules in the Standard Library and from third-parties define their own exceptions.
A value Ok x means that the computation succeeded with x , and a value Error e means that it failed.
It doesn't use setjmp/longjmp
. When a try <expr> with <handle>
is evaluated, a "trap" is placed on the stack, that contains information about the handler. The address of the topmost trap is kept in a register¹, and when you raise, it jumps directly to this trap, unwinding several stack frames in one go (this is better than checking each return code). A trap also stores the address of the previous trap, which is restored in the register at raise time.
¹: or a global, on architectures with not enough registers
You can see for yourself in the code:
Kpushtrap/Kpoptrap
bytecodes surround the try..with
ed expressionLpushtrap/Lpoptrap
around the expressionPUSHTRAP
(places the trap/handler), POPTRAP
(remove it, non-error case) and RAISE
(jump to the trap)setjmp
Ocaml uses a non-standard calling convention with few or no callee-saved registers, which makes this (and tail-recursion) efficient. I suppose (but I'm no expert) that's the reason why C longjmp/setjmp
isn't as efficient on most architectures. See for example this x86_64 setjmp implementation that looks exactly like the previous trapping mechanism plus callee-registers save.
This is taken into account in the C/OCaml interface: the usual way to call a Caml function from C code, caml_callback
, doesn't catch OCaml-land exceptions; you have to use a specific caml_callback_exn
if you wish to, which setups its trap handler and saves/restores callee-saved registers of the C calling convention. See eg. the amd64 code, which saves the registers then jump to this label to setup the exception trap.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With