Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OCaml internals: Exceptions

Tags:

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!

like image 261
Waneck Avatar asked Dec 19 '11 16:12

Waneck


People also ask

Does OCaml have exceptions?

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.

What is OK in OCaml?

A value Ok x means that the computation succeeded with x , and a value Error e means that it failed.


1 Answers

OCaml exception handling

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:

  • bytecode compilation: lines 635-641, two Kpushtrap/Kpoptrap bytecodes surround the try..withed expression
  • native compilation: lines 254-260, again instructions Lpushtrap/Lpoptrap around the expression
  • bytecode execution for the bytecode PUSHTRAP (places the trap/handler), POPTRAP (remove it, non-error case) and RAISE (jump to the trap)
  • native code emission on mips and on amd64 (for example)

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

like image 177
gasche Avatar answered Oct 25 '22 09:10

gasche