使用caml_c_call直接从OCaml调用C /汇编函数

时间:2011-09-26 14:46:27

标签: ocaml

OCaml允许从OCaml程序调用C函数,只要程序员遵循本手册“使用OCaml连接C”一章中的说明。

遵循这些说明时,本机编译器会将对C函数的调用转换为:

    movq    ml_as_z_sub@GOTPCREL(%rip), %rax
    call    caml_c_call@PLT

(amd64指令设置在这里,但看看其他架构,该方案看起来相当统一)。

函数caml_c_call最终会执行计算跳转call *%rax,但它会在之前和之后执行很多操作。来自asmrun / amd64.S:

/* Call a C function from Caml */

FUNCTION(G(caml_c_call))
.Lcaml_c_call:
    /* Record lowest stack address and return address */
        popq    %r12
        STORE_VAR(%r12, caml_last_return_address)
        STORE_VAR(%rsp, caml_bottom_of_stack)
    /* Make the exception handler and alloc ptr available to the C code */
        STORE_VAR(%r15, caml_young_ptr)
        STORE_VAR(%r14, caml_exception_pointer)
    /* Call the function (address in %rax) */
        call    *%rax
    /* Reload alloc ptr */
        LOAD_VAR(caml_young_ptr, %r15)
    /* Return to caller */
        pushq   %r12
        ret

当一个人想要经常执行一些既不分配也不引发异常的指令时,上面的内容有点矫枉过正。

有没有人有直接从OCaml调用小程序集例程的经验,而不通过caml_c_call存根?这可能涉及欺骗本机编译器以为它正在调用ML函数或修改编译器。

问题出在Zarith库的上下文中,其中代码的小组件可以直接计算并返回大多数结果,而不必通过caml_c_call,只跳转到caml_c_code需要分配或例外的困难论据。有关可以直接执行的汇编位的示例,请参阅this file

3 个答案:

答案 0 :(得分:8)

也许"noalloc"和“浮动”可能有用吗?

PS更多related links

答案 1 :(得分:5)

如果您调用的函数可以用汇编编写,听起来您不会介意OCaml函数调用的开销。我刚做了一些实验,你可以通过我上面概述的方法做到这一点。

这就是我的所作所为。为了获得可行的汇编语言模板,我在OCaml中定义了一个简单的函数,并使用-S标志进行编译。

$ cat sep.ml
let addto x = x + 1
$ /usr/local/ocaml312/bin/ocamlopt -inline 0 -c -S sep.ml

注意:您需要指定-inline 0以确保ocamlopt从生成的.o文件中获取代码,而不是从.cmx文件中的内联定义中获取代码。

现在你有了一个名为sep.s.的文件。 addto函数看起来像这样(非常好 代码,实际上):

_camlSep__addto_1030:
.L100:
        addq    $2, %rax
        ret

仅仅为了测试,我将2(在OCaml中代表1)更改为4(在OCaml中代表2)。所以你现在有:

_camlSep__addto_1030:
.L100:
        addq    $4, %rax
        ret

现在组装此文件,生成sep.o的异常版本。

$ as -o sep.o sep.s

从本质上讲,你已经欺骗了ocamlopt来处理sep.o中的代码,好像它是在OCaml中编码的。但是你可以自己编写代码(如果你小心不要违反任何架构假设)。

您可以将其链接到主程序并运行它:

$ cat main.ml
let main () =
    Printf.printf "%d\n" (Sep.addto 19)

let () = main ()
$ /usr/local/ocaml312/bin/ocamlopt -o main sep.cmx main.ml
$ main
21

如您所见,它运行修改后的汇编代码。

您可以按照此过程创建任何OCaml可调用函数 汇编代码。只要你不介意OCaml函数调用的开销,这种方法可能会做你想要的。

我不知道这个技巧会如何影响调试和垃圾收集的处理,所以我不会尝试使用任何分配的函数。

这些测试使用OCaml 3.12.0(股票64位版本)在Mac OS X 10.6.8上运行。当我运行“as”时,我正在运行Xcode 4.0.2中的库存OS X汇编程序,它默认使用x86_64架构。

答案 2 :(得分:2)

在我看来,欺骗编译器认为它正在调用OCaml函数是没有用的,除非你也欺骗它进行内联调用。据我所知,通过读取来源,内联函数表示为Ulambda代码,后者又包含原语。因此,无论如何,这种思路导致为您的Zarith操作添加原语。如果你这样做,你有一个好的(并非一点都不棘手)解决方案,但它可能比你想做的更多。

对于一个非常棘手的方法,您可以尝试对生成的asm代码进行后处理以删除函数调用并使用内联代码替换它们。这种技巧已被多次使用。它通常不会持久,但短期内可能已经足够好了。 为此,您只需为OCaml编译器提供要运行的其他汇编程序的名称,即在汇编之前进行修改。