【Hacker News搬运】我们应得的Rust呼叫大会
-
Title: The Rust calling convention we deserve
我们应得的Rust呼叫大会
Text:
Url: https://mcyoung.xyz/2024/04/17/calling-convention/
文章标题为《The Rust Calling Convention We Deserve》,作者是 mcyoung。文章主要讨论了 Rust 语言的调用约定,特别是与 C ABI 相比的优缺点。作者认为 C ABI 在传递复杂类型时相对不够有效和缺乏想象力,因此提出了一个改进的方案,即使用 Go 语言的注册 ABI。 文章详细解释了调用约定如何处理函数参数的传递和返回,包括哪些寄存器用于传递参数,哪些寄存器用于返回值,以及函数的前言和后语(prologue and epilogue)是什么样的。作者强调,这个特定的帖子主要关于 x86 架构,但他试图保持一定的通用性,以便所写的内容也适用于 ARM、RISC-V 等架构。 作者指出,今天像 Rust 这样的原生编译语言定义了一个未指定的调用约定,可以让它以自己喜欢的方式调用函数。在实践中,Rust 降级到 LLVM 的内置 C 调用约定,LLVM 的前言/后语代码生成器为这些调用生成代码。 然而,作者认为 Rust 过于保守,它尝试生成 LLVM 函数签名,这些签名 Clang 可能已经生成。这种保守有两个显著的好处:1. 良好的概率调试器不会因为这种代码而崩溃。2. 由于使用 LLVM 没有锻炼到的 ABI 代码生成,因此不太可能触发 LLVM 错误。 尽管如此,作者认为我们还可以做得更好。他提出了一种新的调用约定设计,称为 "fast" 调用约定,旨在提高性能。这个设计包括以下几个步骤: 1. 为给定的目标三元组确定可以“通过寄存器”传递的最大值数量。 2. 决定如何传递返回值。它将适合输出寄存器,或者需要以“通过引用”的方式返回,在这种情况下,我们向函数传递一个额外的 `ptr` 参数(带有 `sret` 属性),实际返回值是该指针。 3. 决定哪些按值传递的参数需要降级为通过引用传递。这将是启发式方法,但通常将是“大于按寄存器空间的参数”。例如,在 x86 上,这将得出 176 字节。 4. 决定哪些参数通过寄存器传递,以便最大化寄存器空间使用。这个问题是 NP-hard(它是背包问题),因此将需要启发式方法。所有其他参数都在堆栈上传递。 5. 在 LLVM IR 中生成函数签名。这将是在寄存器上传递的所有参数,编码为各种非聚合体,如 `i64`、`ptr`、`double` 和 `<2 x i64>`。哪些有效的选择取决于目标,但上述是在 64 位架构上通常会得到的内容。在堆栈上传递的参数将遵循“寄存器输入”。 6. 生成函数前言。这是从寄存器输入解码每个 Rust 级参数的代码,以便存在 `%ssa` 值,这些值与使用 `-Zcallconv=legacy` 时会出现的值相对应。这允许我们无论调用约定如何,都能生成函数主体相同的代码。DCE 遍历将消除冗余的解码代码。 7. 生成函数退出块。这是一个包含单个 `phi` 指令的块,该指令用于返回类型,如 `-Zcallconv=legacy` 时的返回类型。此块将将其编码为所需输出格式,然后适当 `ret`。函数的所有退出路径都应该 `br` 到此块,而不是 `ret`。 8. 如果一个非多态、非内联函数可能被取地址(作为函数指针),这是因为它被导出到 crate 外部,或者 crate 取了一个函数指针到它,生成一个使用 `-Zcallconv=legacy` 的垫片,并立即将真实实现作为尾部调用。这是为了保持函数指针等价性所必需的。 作者指出,这个方案需要为确定哪些参数放入寄存器(因为我们允许重新排列参数以获得更好的吞吐量)制定启发式方法。这相当于背包问题;背包启发式超出了本文的范围。这应该发生在足够早的时候,以便可以将这些信息塞入 `rmeta`,避免需要重新计算。我们可能希望根据 `-Copt-level` 使用不同的、更快的启发式方法。请注意,正确性要求我们禁止链接由多个不同 Rust 编译器生成的代码,这是已经的情况,因为 Rust 从一个版本到另一个版本会破坏 ABI。 作者还讨论了 LLVM 愿意做什么,以及如何实际上让 LLVM 以我们想要的方式传递参数。他提出了一个 LLVM 程序,用于确定 LLVM 在特定版本上允许的最大“
Post by: matt_d
Comments:
JonChesterfield: Reasonable sketch. This is missing the caller/called save distinction and makes the usual error of assigning a subset of the input registers to output.<p>It's optimistic about debuggers understanding non-C-like calling conventions which I'd expect to be an abject failure, regardless of what dwarf might be able to encode.<p>Changing ABI with optimization setting interacts really badly with separate compilation.<p>Shuffling arguments around in bin packing fashion does work but introduces a lot of complexity in the compiler, not sure it's worth it relative to left to right first fit. It also makes it difficult for the developer to predict where arguments will end up.<p>The general plan of having different calling conventions for addresses that escape than for those that don't is sound. Peeling off a prologue that does the impedance matching works well.<p>Rust probably should be willing to have a different calling convention to C, though I'm not sure it should be a hardcoded one that every function uses. Seems an obvious thing to embed in the type system to me and allowing developer control over calling convention removes one of the performance advantages of assembly.
JonChesterfield: 合理的草图。这缺少呼叫者;称为保存区分,并产生将输入寄存器的子集分配给输出的常见错误<p> 它;s对调试器理解非类C调用约定持乐观态度;不管侏儒能编码什么,我都认为这是一个可悲的失败<p> 使用优化设置更改ABI与单独编译的交互非常糟糕<p> 以bin packing的方式处理争论确实有效,但在编译器中引入了很多复杂性,不确定它是不是;相对于从左到右的第一次适合来说,这是值得的。这也使得开发人员很难预测争论的结局<p> 对转义的地址与不转义的地址具有不同的调用约定的总体方案是;t是健全的。剥离一个序言,做阻抗匹配工作得很好<p> Rust可能应该愿意对C有不同的调用约定,尽管我;我不确定它应该是每个函数都使用的硬编码的。对我来说,嵌入类型系统似乎是一件显而易见的事情,允许开发人员控制调用约定消除了汇编的一个性能优势。
dwattttt: > If a non-polymorphic, non-inline function may have its address taken (as a function pointer), either because it is exported out of the crate or the crate takes a function pointer to it, generate a shim that uses -Zcallconv=legacy and immediately tail-calls the real implementation. This is necessary to preserve function pointer equality.<p>If the legacy shim tail calls the Rust-calling-convention function, won't that prevent it from fixing any return value differences in the calling convention?
dwattttt: >;如果一个非多态、非内联函数的地址可能被占用(作为函数指针),无论是因为它是从机箱中导出的,还是机箱将函数指针指向它,请生成一个使用-Zcallconv=legacy的填充程序,并立即尾调用实际实现。这对于保持函数指针相等是必要的<p> 如果遗留的shim-tail调用Rust调用约定函数;这不会阻止它修复调用约定中的任何返回值差异吗?
yogorenapan: Tangentially related: Is it currently possible to have interop between Go and Rust? I remember seeing someone achieving it with Zig in the middle but can’t for the sake of me find it. Have some legacy Rust code (what??) that I’m hoping to slowly port to Go piece by piece
yogorenapan: 切向相关:目前Go和Rust之间是否有可能进行互操作?我记得看到有人在Zig位于中间的情况下实现了这一目标,但为了我的缘故,却找不到它。有一些遗留的Rust代码(什么??),我希望慢慢地移植到Go中
sheepscreek: Very interesting but pretty quickly went over my head. I have a question that is slightly related to SIMD and LLVM.<p>Can someone explain simply where does MLIR fit into all of this? Does it standardize more advanced operations across programming languages - such as linear algebra and convolutions?<p>Side-note: Mojo has been designed by the creator of LLVM and MLIR to prioritize and optimize vector hardware use, as a language that is similar to Python (and somewhat syntax compatible).
sheepscreek: 非常有趣,但很快就超出了我的想象。我有一个问题与SIMD和LLVM略有关联<p> 有人能简单地解释一下MLIR在这一切中的作用吗?它是否标准化了编程语言中更高级的操作,如线性代数和卷积<p> 附带说明:Mojo是由LLVM和MLIR的创建者设计的,用于优先考虑和优化矢量硬件的使用,作为一种类似于Python的语言(在一定程度上与语法兼容)。
AceJohnny2: In contrast: "How Swift Achieved Dynamic Linking Where Rust Couldn't " (2019) [1]<p>On the one hand I'm disappointed that Rust still doesn't have a calling convention for Rust-level semantics. On the other hand the above article demonstrates the tremendous amount of work that's required to get there. Apple was deeply motivated to build this as a requirement to make Swift a viable system language that applications could rely on, but Rust does not have that kind of backing.<p>[1] <a href="https://faultlore.com/blah/swift-abi/" rel="nofollow">https://faultlore.com/blah/swift-abi/</a><p>HN discussion: <a href="https://news.ycombinator.com/item?id=21488415">https://news.ycombinator.com/item?id=21488415</a>
AceJohnny2: 相反地:“;Swift如何实现Rust无法实现的动态链接;t〃;(2019)[1]<p>一方面;我很失望Rust仍然没有;没有Rust级别语义的调用约定。另一方面,上面的文章展示了巨大的工作量;It’去那里是必须的。苹果公司非常有动力将其构建为一种要求,使Swift成为应用程序可以依赖的可行系统语言,但Rust没有这种支持<p> [1]<a href=“https://;/;faultlore.com/!blah/:swift abi/”rel=“nofollow”>https:///;faultlore.com/;blah/;swift abi/</a> <p>HN讨论:<a href=“https://;/;news.ycombinator.com/?id=21488415”>https:///;news.ycombinator.com/;项目id=21488415</a>