【问题标题】:At a language level, what exactly is `ccall`?在语言层面,“ccall”到底是什么?
【发布时间】:2017-07-04 20:47:10
【问题描述】:

我是 Julia 的新手,我试图在语言层面上了解 ccall 是什么。在语法级别,它看起来像一个普通函数,但它在获取参数方面的行为显然不一样:

注意参数类型元组必须是字面元组,而不是 元组值变量或表达式。

此外,如果我评估绑定到 Julia REPL 中函数的变量,我会得到类似

julia> max
max (generic function with 15 methods)

但如果我尝试对 ccall 做同样的事情:

julia> ccall
ERROR: syntax: invalid "ccall" syntax

显然,ccall 是一种特殊的语法,但它也不是宏(没有@ 前缀,并且无效的宏使用会产生更具体的错误)。那么,它是什么?它是语言中融入的东西,还是我可以用一些我不熟悉的语言结构来定义自己的东西?

如果它是一些内置的语法,为什么决定使用函数调用表示法,而不是将其实现为宏或设计更具可读性和独特的语法?

【问题讨论】:

    标签: function syntax macros julia ffi


    【解决方案1】:

    在当前的 nightly(以及即将发布的 0.6 版本)中,您观察到的许多特殊行为 has been removed(请参阅 this pull-request)。 ccall 不再是保留字,因此可以用作函数或宏名称。

    然而还有一点点奇怪:定义一个名为ccall 的3 或4 参数函数是允许的,但实际上调用这样的函数会给出关于ccall argument types 的错误(其他参数的数量是可以的)。原因直接指向您的问题:

    那么,它是什么?是语言中融入的东西吗

    是的,ccall,尽管它在 0.6 中不再是关键字,但仍以多种方式“融入”该语言:

    • :ccall([four args...]) 表达式形式被识别,specially handled 在语法降低期间被识别。这个降低步骤做了几件事,包括在对unsafe_convert 的调用中包装参数,这允许从 Julia 对象到 C 兼容对象的自定义转换;以及提取可能需要为rooted 的参数,以防止在ccall 期间对引用对象进行垃圾收集。 (参见code_lowered 输出,或尝试expand 函数;有关编译器的更多信息here)。
    • ccall 在代码生成后端需要extensive handling,包括:在指定的共享库中查找请求的函数名,以及生成LLVM call 指令——最终翻译成平台特定的机器码由 LLVM 即时编译器。 (使用code_llvmcode_native 查看不同阶段)。

    如果它是一些内置的语法,为什么决定使用 函数调用表示法,而不是将其实现为宏或 设计更易读、更独特的语法?

    由于上面详述的原因,ccall 需要特殊处理,无论它看起来像宏还是函数。在this mailing list thread 中,Julia 的创建者之一 (Stefan Karpinski) 评论了为什么不将其设为宏:

    我想我们可以将它重新实现为一个宏,但这只会进一步降低魔法。

    就“更具可读性和独特的语法”而言,也许这是一个品味问题。我不清楚为什么其他一些语法会更可取(除了 LuaJIT/CFFI 风格的内联 C 语法解析的便利性,我是其中的粉丝)。我对ccall 的唯一强烈个人愿望是将参数和类型输入相邻(例如ccall((:foo, :libbar), Void, (x::Int, y::Float))),因为使用更长的参数列表可能不方便。在 0.6 中可以将这种形式实现为宏!

    【讨论】:

    【解决方案2】:

    在 Julia 0.5 及更早版本中。 它不是函数,也不是宏。 这确实是一种特殊的语言。 这是一个内在的。 In julia 0.6 this changes

    它在很多方面更像是宏而不是函数调用。 但在其他方面它不是——它不返回 AST。 它确实调用了一个函数,并且在足够低的级别上看起来类似于调用 julia 函数。

    它为什么看起来如此的历史我无法理解,您需要听听一位从事该语言最早代码的人的意见。 现在它无处不在,是最难改变的事情之一——但并非不可能。 It would trigger up for 3 years of bikeshedding though :-P .

    我喜欢将ccall 视为两件事。

    • 外来函数接口,用于 C 和其他编译语言(例如 Fortran、Rust 显然可以工作)
    • 访问语言“运行时”原始内容的方法。

    外来函数接口(FFI)

    大多数时候,当一个人在一个包中使用ccall 时,想调用编译库中的一些代码。从这个意义上说,它是 C-Call,就像 R-Call 或 Py-Call。 我认为mlewe/BlossomV.jl 是一个很好的紧凑示例。 更激烈的例子oxinabox/SLEEF.jl

    作为 FFI,它不必与 julia 共享内存空间/进程—— PyCall.jl 可以,RCall.jl 和 Matlab.jl 不必。 只要结果回来就没有关系。 在这些情况下,理论上可以用某种safe_ccall 替换ccall,这将在一个单独的进程中运行被调用的库,并且如果库被称为segfaulted,则不会出现segfault julia。 但到目前为止,还没有人编写过这样的方法/包。

    使用 ccall 进行 FFI 甚至在 Base 中完成,例如访问 MPFR to define BigFloat。 但这并不是在Base 中使用ccall 的主要原因。

    深入了解语言。

    ccall 确实是驱动大部分程序“做某事”的原因。 它在整个Base 中使用,用于调用src 中的函数。

    为此,ccall 基本上会在编译级别触发函数调用,将指令指针直接转移到 ccalled 函数的编译代码中。就像调用一个函数一样,如果整个事情都是用 C 语言编写的。

    您可以在 base/threadingconstructs.jl 中看到 ccall 被用于管理线程上的工作——这会触发来自 src/threading.c 的代码。

    它用于将磁盘的一部分映射到内存。 mmap.jl。 -- 显然不能从另一个进程中完成。

    用于制作一段代码non-intruptable

    它用于调用LibC 来执行诸如malloc 之类的事情来分配内存(尽管目前这主要用作FFI 的一部分)。

    在分配变量后,您可以使用ccall#undef 执行一些技巧。 ccall 在很多方面都是该语言的“主”钥匙。

    结论

    我在这里将ccall 描述为两件事,一个 FFI 函数和语言“运行时”的核心部分。这种二元性是不真实的,并且有很多重叠,比如文件处理(是 FFI 吗?)。 许多人期望 ccall 的行为来自其 FFI 用途。 这里 ccall 可能只是一个函数。 它实际具有的行为来自它作为语言的核心部分的使用——将Base 中标准库的julia 代码链接到src 中的低级C 代码。 允许直接控制 julia 进程的运行。

    【讨论】:

    • 这个答案可以通过显示 ccall 在 LLVM 和 ASM 中变成什么来增强。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-26
    • 2010-10-22
    • 2010-09-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多