OCaml调用约定:这是一个准确的摘要吗?

Wan*_*ang 20 ocaml

我一直在尝试找到OCaml调用约定,以便我可以手动解释gdb无法解析的堆栈跟踪.不幸的是,除了一般观察之外,似乎没有任何东西用英文写下来.例如,人们会在博客上评论OCaml在寄存器中传递了许多参数.(如果某处有英文文档,我们非常感谢链接.)

所以我一直试图从ocamlopt源码中解开它.任何人都可以确认这些猜测的准确性吗?

并且,如果我对在寄存器中传递的前十个参数是对的,那么通常不可能将参数恢复到函数调用吗?在C中,如果只是向后走到正确的帧,参数仍会被推到堆栈的某个地方.在OCaml中,似乎被调用者可以自由地破坏他们的呼叫者的论点.


注册分配(来自/asmcomp/amd64/proc.ml)

为了调用OCaml函数,

  • 前10个整数和指针参数在寄存器rax,rbx,rdi,rsi,rdx,rcx,r8,r9,r10和r11中传递
  • 前10个浮点参数在寄存器xmm0 - xmm9中传递
  • 其他参数被压入堆栈(最左边的第一个?),浮点数和整数和指针混合在一起
  • 陷阱指针(参见下面的例外)在r14中传递
  • 在r15中传递分配指针(可能是本博客文章中描述的次要堆)
  • 如果返回值是整数或指针,则返回值在rax中传递;如果是float,则返回xmm0
  • 所有寄存器都是来电保存?

要调用C函数,使用标准的amd64 C约定:

  • 前六个整数和指针参数在rdi,rsi,rdx,rcs,r8和r9中传递
  • 前8个浮点参数在xmm0 - xmm7中传递
  • 其他参数被压入堆栈
  • 返回值以rax或xmm0传回
  • 寄存器rbx,rbp和r12-r15是被调用者保存的

退货地址(自/asmcomp/amd64/emit.mlp)

返回地址是按照amd64 C约定推入调用帧的第一个指针.(我猜这个ret指令假定这个布局.)

例外(来自/asmcomp/linearize.ml)

代码try (...body...) with (...handler...); (...rest...)线性化如下:

Lsetuptrap .body
(...handler...)
Lbranch .join
Llabel .body
Lpushtrap
(...body...)
Lpoptrap
Llabel .join
(...rest...)
Run Code Online (Sandbox Code Playgroud)

然后像这样发出组件(右边的目的地):

call .body
(...handler...)
jmp .join
.body:
pushq %r14
movq %rsp, %r14
(...body...)
popq %r14
addq %rsp, 8
.join:
(...rest...)
Run Code Online (Sandbox Code Playgroud)

在身体的某个地方,有一个线性化的操作码Lraise,它作为这个精确的组件发出:

movq %r14, %rsp
popq %r14
ret
Run Code Online (Sandbox Code Playgroud)

哪个真的很整洁!我们创建了一个伪帧,而不是这个setjmp/longjmp业务,它的返回地址是异常处理程序,其唯一的本地是前一个这样的虚拟帧.该/asmcomp/amd64/proc.ml有评论呼吁$ R14"陷阱指针",所以我会打电话给这个假帧陷阱框架.当我们想要引发异常时,我们将堆栈指针设置为最近的陷阱帧,在此之前将陷阱指针设置为陷阱帧,然后"返回"到异常处理程序中.我敢打赌,如果异常处理程序无法处理此异常,它只会重新加载它.

例外是在%eax中.

gas*_*che 7

这是一个答案而不是一个问题!关于这个主题我知道的一点,我通过查看源代码学习,就像你一样,所以不要指望进一步的精确度比你的帖子更具权威性.

是的,我认为OCaml仅使用具有调用者保存寄存器的专用调用约定.这种选择的好处是它简化了尾调用:当你跳过尾调用¹时,你不必溢出或重新加载任何寄存器.

¹:对于非自我尾调用,这只适用于没有太多参数的情况,因此我们不需要泄漏.如果需要堆栈分配,则将呼叫转换为非尾调用.

请注意,调用约定仍然强烈依赖于目标体系结构.例如,在x86上,当寄存器耗尽并溢出堆栈之前,会使用少量的全局变量来保留尾调用.

我也同意"最左边的先在":参数,以便通过遍历calling_conventionsproc.ml,通过存储在偏置秩序slot_offsetemit.mlp; 他们从右到左计算,但按顺序返回selectgen.ml.