从OCaml调用C/assembly函数比使用caml\u C\u调用更直接

从OCaml调用C/assembly函数比使用caml\u C\u调用更直接,ocaml,Ocaml,OCaml允许从OCaml程序调用C函数,只要程序员遵循手册“将C与OCaml接口”一章中的说明即可 遵循这些指令时,本机编译器会将对C函数的调用转换为: movq ml_as_z_sub@GOTPCREL(%rip), %rax call caml_c_call@PLT (此处设置了amd64指令集,但从其他体系结构来看,该方案似乎相当统一) 函数caml\u c\u call最终执行一个计算跳转调用*%rax,但它在调用前后做了很多事情。从asmrun/amd6

OCaml允许从OCaml程序调用C函数,只要程序员遵循手册“将C与OCaml接口”一章中的说明即可

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

    movq    ml_as_z_sub@GOTPCREL(%rip), %rax
    call    caml_c_call@PLT
(此处设置了amd64指令集,但从其他体系结构来看,该方案似乎相当统一)

函数
caml\u c\u 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\u c\u调用
stub?这可能涉及诱使本机编译器认为它正在调用ML函数,或修改编译器

问题是在Zarith库的上下文中,代码的小汇编位可以直接计算并返回大多数结果,而不必经过
caml\u c\u调用
,对于需要分配或异常的困难参数,只需跳到
caml\u c\u code
。有关可直接执行的汇编位的示例,请参阅。

可能和“float”有一些用处吗


PS更多。

在我看来,诱使编译器认为它正在调用OCaml函数并没有什么帮助,除非您还诱使它将调用内联。就我仔细阅读源代码所知,内联函数是用一种称为Ulambda代码的东西表示的,而Ulambda代码又包含原语。因此,无论如何,这条思路导致为您的Zarith操作添加原语。如果你这样做,你就有了一个很好的(一点也不棘手的)解决方案,但它可能比你想做的要多

对于一种非常棘手的方法,您可以尝试对生成的asm代码进行后处理,以删除函数调用,并用内联代码替换它们。这种把戏已经用过很多次了。它通常不会持续很长时间,但在短期内可能已经足够好了。
要做到这一点,您只需为OCaml编译器指定要运行的另一个汇编程序的名称,该汇编程序在汇编之前进行修改。

如果要调用的函数可以在汇编中编写,您似乎不会介意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
函数看起来像这样(非常好 代码(实际上):

为了进行测试,我将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函数调用的开销,这种方法可能会满足您的需要

我不知道这个诡计会如何影响调试和垃圾收集的处理,所以我不会对执行任何分配的函数进行尝试


这些测试是在MacOSX10.6.8上使用OCAML3.12.0(64位版本)运行的。当我运行“as”时,我运行的是来自Xcode 4.0.2的stock OS X assembler,默认情况下使用x86_64体系结构。

我不想在我的问题中讨论一些无关紧要的问题,但我最近也在试验ocamlopt的-compact选项。此选项扩展分配指令(其中一些指令,例如6或7),而不是使用对分配例程的调用。事实证明,使用-compact,我的代码更小更快。是的,对于Zarith的长度相似的汇编代码段,我很乐意避免存储和计算跳转,并且不会觉得我错过了内联。为了进行快速测试,可以用OCaml编写存根函数并编译所有内容。然后在最后一分钟,用您自己在汇编中编写的文件替换.o文件。(或者,您可以对生成的.S文件进行处理并进行汇编。)然后将所有内容链接在一起。如果它能工作,您可以将.o和.cmx文件配对,而对于ocamlopt来说,它看起来像是一个普通的单独编译的模块(不过,一些调试信息可能是错误的)。或者这失败了,因为ocamlopt做了一些校验和来检查.cmx和.o文件的一致性?这正是我最初的想法,但在ygrek的链接中,这一点表明,对于外部函数的“noalloc”注释,您可以得到几乎相同的想法:
$ 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