【问题标题】:OCaml internals: ExceptionsOCaml 内部结构:异常
【发布时间】:2011-12-19 16:06:51
【问题描述】:

我很想知道 OCaml 运行时如何处理异常以使它们变得如此轻量级。他们是使用 setjmp/longjmp 还是在每个函数中返回一个特殊值并传播它?

在我看来 longjmp 会给系统带来一点压力,但只有在引发异常时,在检查每个函数返回值时需要在调用函数后检查每个值,这似乎我会进行很多检查和跳转,似乎它会表现最差。

通过查看 OCaml 如何与 C 接口 (http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html#toc142) 并查看 callback.h,似乎使用对象的内存对齐来标记异常 (#define Is_exception_result(v) (((v) & 3) == 2))。这似乎表明它的实现不使用 longjmp 并在每次函数调用后检查每个函数结果。是这样吗?还是 C 函数已经尝试捕获任何异常,然后将其转换为这种格式?

谢谢!

【问题讨论】:

    标签: exception compiler-construction ocaml internals setjmp


    【解决方案1】:

    OCaml 异常处理

    它不使用setjmp/longjmp。当评估 try <expr> with <handle> 时,会在堆栈上放置一个“陷阱”,其中包含有关处理程序的信息。最顶层陷阱的地址保存在寄存器中¹,当你加注时,它会直接跳到这个陷阱,一次展开几个堆栈帧(这比检查每个返回码要好)。陷阱还存储前一个陷阱的地址,该地址在引发时在寄存器中恢复。

    ¹:或全局,在没有足够寄存器的架构上

    您可以在代码中自己查看:

    • bytecode compilation:第 635-641 行,两个 Kpushtrap/Kpoptrap 字节码围绕着 try..withed 表达式
    • native compilation:第 254-260 行,再次说明 Lpushtrap/Lpoptrap 围绕表达式
    • bytecode execution 用于字节码 PUSHTRAP(放置陷阱/处理程序)、POPTRAP(删除它,非错误情况)和 RAISE(跳转到陷阱)
    • 本机代码发射on mipson amd64(例如)

    setjmp比较

    Ocaml 使用非标准调用约定,很少或没有保存被调用者的寄存器,这使得这种(和尾递归)高效。我想(但我不是专家)这就是为什么 C longjmp/setjmp 在大多数架构上效率不高的原因。例如this x86_64 setjmp implementation,它看起来与之前的捕获机制和被调用者寄存器保存一模一样。

    C/OCaml interface 中考虑了这一点:从 C 代码caml_callback 调用 Caml 函数的常用方法不会捕获 OCaml-land 异常;如果你愿意,你必须使用一个特定的caml_callback_exn,它设置它的陷阱处理程序保存/恢复C调用约定的被调用者保存的寄存器。参见例如。 the amd64 code,保存寄存器然后跳转到this label设置异常陷阱。

    【讨论】:

    • 哇!非常好!谢谢!你知道为什么 setjmp/longjmp 不使用类似的方法吗?除了 asm 调用之外,还有其他方法可以从 C 中访问这些原语吗?再次感谢您!
    • @Waneck 我想setjmp/longjmp 在架构上使用了类似的实现,使其实用。但是调用约定的被调用者保存的寄存器使这些事情变得更加昂贵。我添加了关于它们的评论。
    猜你喜欢
    • 1970-01-01
    • 2018-10-22
    • 2012-08-23
    • 2012-12-12
    • 1970-01-01
    • 2012-06-13
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    相关资源
    最近更新 更多