(* raw continuation *)
type ('a,'b) stack
perform : 'a eff -> 'a
resume : ('a,'b) stack -> ('c -> 'a) -> 'c -> 'b
delegate : 'a eff -> ('a,'b) stack -> 'b
type ('c,'b) hval = 'c -> 'b
type 'b hexn = exn -> 'b
type ('c,'b) heff = 'c effect -> ('c,'b) stack -> 'b
caml_alloc_stack : ('c,'b) hval -> 'b hexn -> ('c,'b) heff -> ('a,'b) stack
perform : 1
resume : 3
delegate : 2
caml_start_program
is used both to start execution of the main OCaml code as well as for callbacks from C to OCaml. The interface has the following stack layout:
+--------------+
| |
/ OCaml /
+----->/ frames / (* Only if Callback from C *)
| / /
| | |
| +-->+--------------+
| | | |
| | / C /
| | / frames / (* Arbitrary size *)
| | / /
| | | |
| | +--------------+ <-- top of caml_start_program
| | | gc_regs | (* Pointer to register block *)
| | +--------------+
| | | last_retaddr | (* last return address in OCaml Code. == 1 if not callback *)
| | +--------------+
| +---| bot_of_stk | (* beginning of the OCaml stack chunk *)
| +--------------+
| | exn_ip | (* instruction pointer for the exception handler; a catch-all exception handler *)
| +--------------+
+------| exn_ptr | (* exception pointer of previous handler *)
+--------------+ <-- bottom of caml_start_program
| return_addr | (* amd64.S:LBL(107) *)
+--------------+
| |
/ OCaml /
/ frames /
/ /
| |
+--------------+
Stack grows downward from high to low. The address Stack_high(stack) > Stack_base(stack).
+------------+ <-- Stack_high(stack)
| |
/ valid /
/ frames / (* Arbitrary size *)
/ /
/~~~~~~~~~~~~/ <-- sp
/ /
/ garbage /
| |
+------------+ <-- Stack_threshold(stack)
| |
/ reserved / (* 16 words *)
| |
+------------+ <-- Stack_base(stack)
| parent |
+------------+
| heff |
+------------+
| hexn |
+------------+
| hval |
+------------+
| dirty |
+------------+
| sp |
+------------+ <-- stack
sp
value is valid when the stack is not active i.e, stack is a continuation. dirty
is true if the stack needs to be scanned by the GC; signifies that the stack has been mutated since the last GC. hval
/hexn
/heff
is the closure for handler returning value/exception/effect. parent
is the pointer to the parent stack.
integer arguments (in order): %rdi, %rsi, %rdx, %rcx, %r8, %r9
floating point arguments: %xmm0–%xmm7
integer: %rax, %rdx
floating point: %xmm0, %xmm1
%rbx, %rbp, %r12-r15
For more information, see [2].
This is makes exception handlers really cheap. There is no need to save registers when entering a try block other than saving the stack pointer. Raising an exception just needs to restore the stack pointer. For the same reason, context switches become really cheap. There is no need to save registers when capturing a continuation. For more information, see [1].
The information has been gleaned from asmcomp/amd64/proc.ml
.
Register map:
rax 0
rbx 1
rdi 2
rsi 3
rdx 4
rcx 5
r8 6
r9 7
r12 8
r13 9
rbp 12
r10 10
r11 11
r14 trap pointer
r15 allocation pointer
xmm0 - xmm15 100 - 115
- rax - r13: OCaml function arguments (upto 10). Note that this is specifically different from what is described in [3] due to (PR#5707): r11 should not be used for parameter passing, as it can be destroyed by the dynamic loader according to SVR4 ABI. Linux's dynamic loader also destroys r10. The rest are passed on the stack, leftmost-first-in.
- rax: OCaml function result
- xmm0 - xmm9: OCaml floating-point function arguments
- xmm0: OCaml floating-point function result
- first integer args in r0...r15
- first float args in d0...d15 remaining args on stack.
- Return values in r0...r15 or d0...d15.
- first integer args in r0...r7
- first float args in d0...d7 remaining args on stack.
- Return values in r0...r1 or d0.
- Instruction selection (selection.ml)
- Allocation combining (comballoc.ml)
- CSE (CSE.ml)
- Deadcode elimination / liveness analysis (deadcode.ml)
- Register spilling (spill.ml)
- Live range splitting (split.ml)
- Linearization (liner.ml)
- Instruction scheduling (scheduling.ml)
- Emit (emit.ml)
Exceptions in OCaml native builds upon the usual call/return semantics of the underlying machine. In particular, exception mechanism does not use setjmp()/longjmp()
, becuase of OCaml's calling convention which does not use any callee saved registers. Hence, exceptional return can appear just like a return.
-
push exception code location. Assuming LBL(108) has the exception handler code, the first step is
lea LBL(108)(%rip), %r13 pushq %r13
-
push previous exception pointer. The exception pointer is maintained in %r14. Hence,
push %r14
-
save the new exception pointer. This is the current stack pointer. Hence,
movq %rsp, %r14
-
unwind the stack. Since the exception pointer is maintained in %r14, simply perform
movq %r14, %rsp
-
restore the previous exception handler. Since the last thing that was pushed while setting up the exception handler is the previous exception pointer, just do
popq %r14
-
Now just return as if the function has returned.
ret
The ret
instruction pops the ip
from the stack and jumps to it. Since this is now the exception pointer code, the exection continues at LBL(108).
[1] https://github.com/whitequark/ocaml-llvm-ng/blob/master/doc/abi.md
[2] www.x86-64.org/documentation/abi.pdf
[3] http://stackoverflow.com/questions/11322163/ocaml-calling-convention-is-this-an-accurate-summary