代码生成属性

codegen.md
commit: 646ef8d240a798da5891deb5dbdbebe557f878b8
本章译文最后维护日期:2020-11-10

下述属性用于控制代码生成。

优化提示

cold属性inline属性给出了某种代码生成方式的提示建议,这种方式可能比没有此提示时更快。这些属性只是提示,可能会被忽略。

这两个属性都可以在函数上使用。当这类属性应用于 trait 中的函数上时,它们只在那些没有被 trait实现所覆盖的默认函数上生效,而不是所有 trait实现中用到的函数上都生效。这两属性对 trait 中那些没有函数体的函数没有影响。

内联(inline)属性

inline属性 的意义是暗示在调用者(caller)中放置此(属性限定的)函数的副本,而不是在定义此(属性限定的)函数的地方生此函数的代码,然后去让别处代码来调用此函数。

注意rustc 编译器会根据启发式算法(internal heuristics)1自动内联函数。不正确的内联函数会使程序变慢,所以应该小心使用此属性。

使用内联(inline)属性有三种方法:

  • #[inline] 暗示执行内联扩展。
  • #[inline(always)] 暗示应该一直执行内联扩展。
  • #[inline(never)] 暗示应该从不执行内联扩展。

注意: #[inline] 在每种形式中都是一个提示,不是必须要在调用者放置此属性限定的函数的副本。

cold属性

cold属性 暗示此(属性限定的)函数不太可能被调用。

no_builtins属性

no_builtins属性 可以应用在 crate 级别,用以禁用对假定存在的库函数调用的某些代码模式优化。2

target_feature属性

target_feature[属性] 可应用于非安全(unsafe)函数上,用来为特定的平台架构特性(platform architecture features)启用该函数的代码生成功能。它使用 MetaListNameValueStr元项属性句法来启用(该平台支持的)特性,但这次要求这个句法里只能有一个 enable键,其对应值是一个逗号分隔的由平台特性名字组成的符串。

#![allow(unused)]
fn main() {
#[cfg(target_feature = "avx2")]
#[target_feature(enable = "avx2")]
unsafe fn foo_avx2() {}
}

每个目标架构都有一组可以被启用的特性。为不是当前 crate 的编译目标下的CPU架构指定需启用的特性是错误的。

调用一个编译时启用了某特性的函数,但当前程序运行的平台并不支持该特性,那这将导致出现未定义行为

应用了 target_feature 的函数不会内联到不支持给定特性的上下文中。#[inline(always)] 属性不能与 target_feature属性一起使用。

可用特性

下面是可用特性列表。

x86 or x86_64

特性隐式启用描述中文描述
aessse2AES — Advanced Encryption Standard高级加密标准
avxsse4.2AVX — Advanced Vector Extensions高级矢量扩展指令集
avx2avxAVX2 — Advanced Vector Extensions 2高级矢量扩展指令集2
bmi1BMI1 — Bit Manipulation Instruction Sets位操作指令集
bmi2BMI2 — Bit Manipulation Instruction Sets 2位操作指令集2
fmaavxFMA3 — Three-operand fused multiply-add三操作乘加指令
fxsrfxsave and fxrstor — Save and restore x87 FPU, MMX Technology, and SSE State保存/恢复 x87 FPU、MMX技术,SSE状态
lzcntlzcnt — Leading zeros count前导零计数
pclmulqdqsse2pclmulqdq — Packed carry-less multiplication quadword压缩的四字(16字节)无进位乘法,主用于加解密处理
popcntpopcnt — Count of bits set to 1位1计数,即统计有多少个“为1的位”
rdrandrdrand — Read random number从芯片上的硬件随机数生成器中获取随机数
rdseedrdseed — Read random seed从芯片上的硬件随机数生成器中获取为伪随机数生成器设定的种子
shasse2SHA — Secure Hash Algorithm安全哈希算法
sseSSE — Streaming SIMD Extensions单指令多数据流扩展指令集
sse2sseSSE2 — Streaming SIMD Extensions 2单指令多数据流扩展指令集2
sse3sse2SSE3 — Streaming SIMD Extensions 3单指令多数据流扩展指令集3
sse4.1ssse3SSE4.1 — Streaming SIMD Extensions 4.1单指令多数据流扩展指令集4.1
sse4.2sse4.1SSE4.2 — Streaming SIMD Extensions 4.2单指令多数据流扩展指令集4.2
ssse3sse3SSSE3 — Supplemental Streaming SIMD Extensions 3增补单指令多数据流扩展指令集3
xsavexsave — Save processor extended states保存处理器扩展状态
xsavecxsavec — Save processor extended states with compaction压缩保存处理器扩展状态
xsaveoptxsaveopt — Save processor extended states optimizedxsave 指令集的优化版
xsavesxsaves — Save processor extended states supervisor保存处理器扩展状态监视程序

附加信息

请参阅 target_feature-条件编译选项,了解如何基于编译时的设置来有选择地启用或禁用对某些代码的编译。注意,条件编译选项不受 target_feature属性的影响,只是被整个 crate 启用的特性所驱动。

有关 x86 平台上的运行时特性检测,请参阅标准库中的 is_x86_feature_detected宏。

注意:rustc 为每个编译目标和 CPU 启用了一组默认特性。编译时,可以使用命令行参数 -C target-cpu 选择目标 CPU。可以通过命令行参数 -C target-feature 来为整个 crate 启用或禁用某些单独的特性。

track_caller属性

track_caller属性可以应用于除程序入口函数 fn main 之外的任何带有 "Rust" ABI 的函数。当此属性应用于 trait声明中的函数或方法时,该属性将应用在其所有的实现上。如果 trait 本身提供了带有该属性的默认函数实现,那么该属性也应用于其覆盖实现(override implementations)。

当应用于外部(extern)块中的函数上时,该属性也必须应用于此函数的任何链接实现(linked implementations)上,否则将导致未定义行为。当此属性应用在一个外部(extern)块内可用的函数上时,该外部(extern)块中的对该函数的声明也必须带上此属性,否则将导致未定义行为。

表现

将此属性应用到函数 f 上将允许 f 内的代码获得 f 被调用时建立的调用栈的“最顶层”的调用的位置(Location)信息的提示。从观察的角度来看,此属性的实现表现地就像从 f 所在的帧向上遍历调用栈,定位找到最近的有非此属性限定的调用函数 outer,并返回 outer 调用时的位置(Location)信息。

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
}

注意:core 提供了 core::panic::Location::caller 来观察调用者的位置。它封装(wrap)了由 rustc 实现的内部函数(intrinsic) core::intrinsics::caller_location

注意:由于结果 Location 是一个提示,所以具体实现可能会提前终止对堆栈的遍历。请参阅限制以了解重要的注意事项。

示例

f 直接被 calls_f 调用时,f 中的代码观察其在 calls_f 内的调用位置:

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
fn calls_f() {
    f(); // <-- f() 将打印此处的位置信息
}
}

f 被另一个有此属性限定的函数 g 调用,g 又被 calls_g' 调用,fg 内的代码又同时观察 gcalls_g 内的调用位置:

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
#[track_caller]
fn g() {
    println!("{}", std::panic::Location::caller());
    f();
}

fn calls_g() {
    g(); // <-- g() 将两次打印此处的位置信息,一次是它自己,一次是此 f() 里来的
}
}

g 又被另一个有此属性限定的函数 h 调用,而g 又被 calls_h' 调用,fgh 内的代码又同时观察 hcalls_h 内的调用位置:

#![allow(unused)]
fn main() {
#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}
#[track_caller]
fn g() {
    println!("{}", std::panic::Location::caller());
    f();
}
#[track_caller]
fn h() {
    println!("{}", std::panic::Location::caller());
    g();
}

fn calls_h() {
    h(); // <-- 将三次打印此处的位置信息,一次是它自己,一次是此 g() 里来,一次是从 f() 里来的
}
}

以此类推。

限制

track_caller属性获取的信息是只是一个提示信息,实现不需要维护它。

特别是,将带有 #[track_caller] 的函数自动强转为函数指针会创建一个填充对象,该填充对象在观察者看来似乎是在此(属性限定的)函数的定义处调用的,从而在这层虚拟调用中丢失了实际的调用者信息。这种自动强转情况的一个常见示例是创建方法被此属性限定的 trait对象3

注意:前面提到的函数指针填充对象是必需的,因为 rustc 会通过向函数的 ABI 附加一个隐式参数来实现代码生成(codegen)上下文中的 track_caller,但这种添加是不健壮的(unsound),因为该隐式参数不是函数类型的一部分,那给定的函数指针类型可能引用也可能不引用具有此属性的函数。这里创建一个填充对象会对函数指针的调用方隐藏隐式参数,从而保持可靠性。

1

可字面理解为内部反复试探。

2

默认情况下,Rust 编译器会默认某些标准库函数在编译时可用,编译器也会把当前编译的代码往这些库函数可用的方向去优化。

3

因为 trait对象的值不能直接使用,只能自动强转为指针引用,那这里的调用就无法观察到真实的调用位置。