在语言层面,`ccall` 到底是什么?
At a language level, what exactly is `ccall`?
我是 Julia 的新手,我想在语言层面上理解 ccall
是什么。在语法层面上,它看起来像一个普通函数,但显然它在接受参数方面的行为方式不同:
Note that the argument type tuple must be a literal tuple, and not a
tuple-valued variable or expression.
此外,如果我在 Julia REPL 中评估绑定到函数的变量,我会得到类似
的结果
julia> max
max (generic function with 15 methods)
但是如果我尝试对 ccall
做同样的事情:
julia> ccall
ERROR: syntax: invalid "ccall" syntax
显然,ccall
是一种特殊的语法,但它也不是宏(没有 @
前缀,无效的宏使用会产生更具体的错误)。那么,它是什么?它是语言中固有的东西,还是我可以用我不熟悉的某种语言结构来定义自己的东西?
如果它是一些内置语法,为什么决定使用函数调用表示法,而不是将其实现为宏或设计更具可读性和独特性的语法?
在 Julia 0.5 及更早版本中。
它不是函数,也不是宏。
这确实是一种特殊的语言。
这是一个内在的。
In julia 0.6 this changes
它在很多方面更像是宏而不是函数调用。
但在其他方面它不是——它不是 return AST。
它确实调用了一个函数,并且在足够低的级别上它看起来类似于调用 julia 函数。
我不了解为什么它看起来像这样的历史,您需要听取一位从事该语言最早代码工作的人的意见。
现在它无处不在,是最难改变的事情之一——但并非不可能。 It would trigger up for 3 years of bikeshedding though :-P .
我喜欢将 ccall
视为两件事。
- 外部函数接口,用于 C 和其他编译语言(例如 Fortran、Rust 显然有效)
- 访问语言原始内容的方法 "runtime"。
外部函数接口 (FFI)
大多数时候,当一个人在一个包中使用 ccall
时,他想调用编译库中的一些代码。从这个意义上说,它是 C-Call,如 R-Call,或 Py-Call。
我认为 mlewe/BlossomV.jl 是一个很好的紧凑示例。
举个更激烈的例子 oxinabox/SLEEF.jl.
作为 FFI,它不必与 julia 共享内存 space/a 进程——PyCall.jl 会,RCall.jl 和 Matlab.jl 不会。
只要有结果回来就无所谓了。
在这些情况下,理论上可以用某种 safe_ccall
替换 ccall
,这将 运行 在单独的进程中调用的库,如果库被调用段错误,则不会对 julia 进行段错误。
但是到现在为止,no-one已经写了这样一篇method/package。
FFI 使用 ccall
甚至可以在 Base 中完成,例如访问 MPFR to define BigFloat。
但这不是ccall
用于Base
的主要原因。
进入语言的核心。
ccall
确实驱动了大部分程序 "doing a thing"。
它在整个 Base, to call the functions from src.
中使用
为此,ccall
基本上在编译级别触发函数调用,将指令指针直接转移到 ccall
ed 函数的编译代码中。就像调用一个函数一样,如果整个事情都是用 C 写的。
你可以在base/threadingconstructs.jl ccall being used to manage work on threads -- that triggers code from src/threading.c.
中看到
用于将一段磁盘映射到内存。 mmap.jl。 -- 显然不能从另一个进程完成。
用于制作一段代码non-intruptable
它用于调用 LibC 来执行 malloc
之类的事情来分配内存(尽管现在这主要用作 FFI 的一部分)。
在变量已经赋值后,您可以使用 ccall
到 #undef
一些技巧。
ccall
在很多方面都是 "master" 语言的关键。
结论
我在这里将 ccall
描述为两件事,一个 FFI 函数和语言的核心部分 "runtime"。这种二元性不是真实的,并且有很多重叠,比如文件处理(是 FFI 吗?)。
许多人期望 ccall 具有的行为来自其 FFI 使用。
这里 ccall 可能只是一个函数。
它实际具有的行为来自于它作为语言的核心部分的使用——链接 Base to the low level C code from src 中标准库的 julia 代码。
允许非常直接地控制 julia 进程的 运行ning。
在当前的每晚(以及即将发布的 0.6 版本)中,您观察到的许多特殊行为 has been removed (see this pull-request)。 ccall
不再是保留字,因此可以用作函数名或宏名。
但是仍然有一点奇怪:允许定义一个名为 ccall
的 3 或 4 参数函数,但实际上调用这样的函数将给出一个关于 ccall argument types
的错误(其他数量的参数都可以)。原因直接针对您的问题:
So, what is it? Is it something baked into the language
是的,ccall
,虽然它在 0.6 中不再是关键字,但仍然 "baked in" 在几个方面对语言来说:
-
:ccall([four args...])
表达形式被识别并且 specially handled during syntax lowering. This lowering step does several things including wrapping arguments in a call to unsafe_convert
, which allows for customized conversion from Julia objects to C-compatible objects; as well as pulling out arguments that might need to be rooted to prevent garbage collection of a referenced object during the ccall
. (see code_lowered
output, or try the expand
function; more info on the compiler here).
ccall
需要 extensive handling in the code generation backend, including: look-up of the requested function name in the specified shared library, and generation of an LLVM call
指令——最终由 LLVM Just-In-Time 编译器翻译成 platform-specific 机器码。 (查看 code_llvm
和 code_native
的不同阶段)。
And if it is some baked-in piece of syntax, why was it decided to use
function call notation, instead of implementing it as a macro or
designing a more readable and distinct syntax?
由于上面详述的原因,ccall
需要特殊处理,无论它看起来像宏还是函数。在 this mailing list thread 中,Julia 的一位创建者 (Stefan Karpinski) 评论了为什么不将其设为宏:
I suppose we could reimplement it as a macro, but that would really just be pushing the magic further down.
至于 "a more readable and distinct syntax",也许这是一个品味问题。我不清楚为什么其他一些语法会更可取(除了 LuaJIT/CFFI-style 内联 C 语法解析的便利性,我是它的粉丝)。我对 ccall
的唯一强烈个人愿望是让参数和类型相邻输入(例如 ccall((:foo, :libbar), Void, (x::Int, y::Float))
),因为使用更长的参数列表可能会很不方便。在 0.6 中,可以将此表单实现为宏!
我是 Julia 的新手,我想在语言层面上理解 ccall
是什么。在语法层面上,它看起来像一个普通函数,但显然它在接受参数方面的行为方式不同:
Note that the argument type tuple must be a literal tuple, and not a tuple-valued variable or expression.
此外,如果我在 Julia REPL 中评估绑定到函数的变量,我会得到类似
的结果julia> max
max (generic function with 15 methods)
但是如果我尝试对 ccall
做同样的事情:
julia> ccall
ERROR: syntax: invalid "ccall" syntax
显然,ccall
是一种特殊的语法,但它也不是宏(没有 @
前缀,无效的宏使用会产生更具体的错误)。那么,它是什么?它是语言中固有的东西,还是我可以用我不熟悉的某种语言结构来定义自己的东西?
如果它是一些内置语法,为什么决定使用函数调用表示法,而不是将其实现为宏或设计更具可读性和独特性的语法?
在 Julia 0.5 及更早版本中。 它不是函数,也不是宏。 这确实是一种特殊的语言。 这是一个内在的。 In julia 0.6 this changes
它在很多方面更像是宏而不是函数调用。 但在其他方面它不是——它不是 return AST。 它确实调用了一个函数,并且在足够低的级别上它看起来类似于调用 julia 函数。
我不了解为什么它看起来像这样的历史,您需要听取一位从事该语言最早代码工作的人的意见。 现在它无处不在,是最难改变的事情之一——但并非不可能。 It would trigger up for 3 years of bikeshedding though :-P .
我喜欢将 ccall
视为两件事。
- 外部函数接口,用于 C 和其他编译语言(例如 Fortran、Rust 显然有效)
- 访问语言原始内容的方法 "runtime"。
外部函数接口 (FFI)
大多数时候,当一个人在一个包中使用 ccall
时,他想调用编译库中的一些代码。从这个意义上说,它是 C-Call,如 R-Call,或 Py-Call。
我认为 mlewe/BlossomV.jl 是一个很好的紧凑示例。
举个更激烈的例子 oxinabox/SLEEF.jl.
作为 FFI,它不必与 julia 共享内存 space/a 进程——PyCall.jl 会,RCall.jl 和 Matlab.jl 不会。
只要有结果回来就无所谓了。
在这些情况下,理论上可以用某种 safe_ccall
替换 ccall
,这将 运行 在单独的进程中调用的库,如果库被调用段错误,则不会对 julia 进行段错误。
但是到现在为止,no-one已经写了这样一篇method/package。
FFI 使用 ccall
甚至可以在 Base 中完成,例如访问 MPFR to define BigFloat。
但这不是ccall
用于Base
的主要原因。
进入语言的核心。
ccall
确实驱动了大部分程序 "doing a thing"。
它在整个 Base, to call the functions from src.
为此,ccall
基本上在编译级别触发函数调用,将指令指针直接转移到 ccall
ed 函数的编译代码中。就像调用一个函数一样,如果整个事情都是用 C 写的。
你可以在base/threadingconstructs.jl ccall being used to manage work on threads -- that triggers code from src/threading.c.
中看到用于将一段磁盘映射到内存。 mmap.jl。 -- 显然不能从另一个进程完成。
用于制作一段代码non-intruptable
它用于调用 LibC 来执行 malloc
之类的事情来分配内存(尽管现在这主要用作 FFI 的一部分)。
在变量已经赋值后,您可以使用 ccall
到 #undef
一些技巧。
ccall
在很多方面都是 "master" 语言的关键。
结论
我在这里将 ccall
描述为两件事,一个 FFI 函数和语言的核心部分 "runtime"。这种二元性不是真实的,并且有很多重叠,比如文件处理(是 FFI 吗?)。
许多人期望 ccall 具有的行为来自其 FFI 使用。
这里 ccall 可能只是一个函数。
它实际具有的行为来自于它作为语言的核心部分的使用——链接 Base to the low level C code from src 中标准库的 julia 代码。
允许非常直接地控制 julia 进程的 运行ning。
在当前的每晚(以及即将发布的 0.6 版本)中,您观察到的许多特殊行为 has been removed (see this pull-request)。 ccall
不再是保留字,因此可以用作函数名或宏名。
但是仍然有一点奇怪:允许定义一个名为 ccall
的 3 或 4 参数函数,但实际上调用这样的函数将给出一个关于 ccall argument types
的错误(其他数量的参数都可以)。原因直接针对您的问题:
So, what is it? Is it something baked into the language
是的,ccall
,虽然它在 0.6 中不再是关键字,但仍然 "baked in" 在几个方面对语言来说:
-
:ccall([four args...])
表达形式被识别并且 specially handled during syntax lowering. This lowering step does several things including wrapping arguments in a call tounsafe_convert
, which allows for customized conversion from Julia objects to C-compatible objects; as well as pulling out arguments that might need to be rooted to prevent garbage collection of a referenced object during theccall
. (seecode_lowered
output, or try theexpand
function; more info on the compiler here). ccall
需要 extensive handling in the code generation backend, including: look-up of the requested function name in the specified shared library, and generation of an LLVMcall
指令——最终由 LLVM Just-In-Time 编译器翻译成 platform-specific 机器码。 (查看code_llvm
和code_native
的不同阶段)。
And if it is some baked-in piece of syntax, why was it decided to use function call notation, instead of implementing it as a macro or designing a more readable and distinct syntax?
由于上面详述的原因,ccall
需要特殊处理,无论它看起来像宏还是函数。在 this mailing list thread 中,Julia 的一位创建者 (Stefan Karpinski) 评论了为什么不将其设为宏:
I suppose we could reimplement it as a macro, but that would really just be pushing the magic further down.
至于 "a more readable and distinct syntax",也许这是一个品味问题。我不清楚为什么其他一些语法会更可取(除了 LuaJIT/CFFI-style 内联 C 语法解析的便利性,我是它的粉丝)。我对 ccall
的唯一强烈个人愿望是让参数和类型相邻输入(例如 ccall((:foo, :libbar), Void, (x::Int, y::Float))
),因为使用更长的参数列表可能会很不方便。在 0.6 中,可以将此表单实现为宏!