上面是Aria在Ubuntu 20.04 x64上运行的FFI abi-checker,她在这个相当重要的、表现良好的平台上测试了一些非常无聊的情况 。结果发现,一些整数参数在两个由Clang和GCC编译的静态库之间按值传递失败了!
Aria发现,Clang和GCC甚至不能就Linux x64上_int128
的ABI达成一致 。
Aria本来是为了检查rustc中的错误,没想到会在一个重要的、常用的ABI上发现两大主流C编译器的不一致 。
文章插图
试图驯服C
Aria认为,可怕的是对C头文件进行语义解析,只能由该平台的C编译器来完成 。即使C编译器告诉了你类型和如何理解注释,但实际上你仍然不知道所有内容的大小/对齐/惯例 。那如何与这些乱七八糟的东西进行互操作呢?Aria提供了两种选择 。
第一个选择是完全投降,将你的语言与C进行灵魂绑定,这可以是以下任何一种:
- 用C(++)编写你的编译器/运行时,这样它就可以用C了
- 让你的 "codegen "直接发出C(++),这样用户无论如何都需要一个C编译器
- 将你的编译器建立在一个成熟的主要C编译器(Clang或GCC)之上
unsigned long long
,否则你将继承C的巨大可移植性混乱 。这就让我们想到了第二个选择:撒谎、欺骗和偷窃 。
如果这一切是无论如何都无法避免的灾难,你还不如开始手工翻译类型和接口定义到你的语言中,基本上就是我们每天在Rust中所做的事情 。比如,人们使用rust-bindgen和friends自动化处理一些事,但很多时候,定义会被检查或手工调整 。因为人们不想浪费时间,去尝试Phantomderp的定制C构建系统可移植地工作 。
在Rust中,Linux x64上的
intmax_t
是什么?pub type intmax_t = i64;
在Nim中,Linux x64上的long long
是什么?clonglong {.importc: "long long", nodecl.} = int64
很多代码已经完全放弃将C保持在循环中,开始对核心类型的定义进行硬编码 。毕竟,它们显然只是平台ABI的一部分!他们要改变intmax_t
的大小吗?这显然是一个破坏ABI的变化!那phantomderp正在研究的又是什么?
我们讨论过为何Aria提出了她的疑问:编程语言如何处理这种变化?如何指定与哪个版本的intmax_t
不能被改变,因为如果我们从long long
(64位整数)改为_int128_t
(128位整数),某个地方的二进制会失控使用错误的调用约定/返回约定 。但有没有一种方法,如果代码选择了它或其他东西,我们可以为较新的应用程序升级函数调用,而让旧应用程序保持不变?让我们编写一些代码,测试一下透明别名可以帮助ABI的想法 。
intmax_t
互操作?如果你有一些C头文件提到intmax_t
,它使用的是哪个定义?在此讨论具有不同ABI的平台的主要机制是目标三元组 。你知道什么是目标三元组吗?你知道基本上涵盖了过去20年里所有主流桌面/服务器Linux发行版的
x86_64-unknown-linux-gnu
包括什么吗?现在,虽然表面上可以针对这个目标进行编译,并得到一个在所有这些平台上都能“正常工作”的二进制文件,但Aria不相信有些程序会被编译成intmax_t
大于int64_t
。任何试图做出这种改变的平台都会成为一个新的
x86_64-unknown-linux-gnu2
目标三元组吗?如果任何针对x86_64-unknown-linux-gnu
编译的东西都被允许在上面运行,这难道还不够吗?文章插图
在不破坏ABI的情况下更改签名"那又怎样,C永远不会再有进步吗?"不!但也是!因为他们提供了糟糕的设计 。
老实说,进行ABI兼容的修改是一种艺术形式 。这种艺术的一部分就是准备工作 。具体来说,如果你准备好了,做出不破坏ABI的修改就会容易得多 。
正如phantomderp的文章所指出的,像glibc(
g
是 x86_64-unknown-linux-gnu
中的 gnu
)早就明白了这一点,并使用符号版本化这样的机制来更新签名和API,同时为任何针对旧版本编译的人保留旧版本 。因此,如果你有
int32_t my_rad_symbol(int32_t)
,你告诉编译器将其导出为 my_rad_symbol_v1
,那么任何根据这个头文件进行编译的人,都会在他们的代码中写上
推荐阅读
- 桶排序算法
- 司马小七说说代理Proxy
- docker容器与传统虚拟机对比
- 带你了解什么是Web 2.0 和 Web 3.0
- 泡红茶用冷水还是热水,红茶是半发酵还是发酵
- 视网膜显示的意义是什么
- 切除子宫肌瘤的危害
- 羊水少多久复查一次
- 子宫肌瘤7厘米严重吗
- 宫颈粘黏症状