Rust 1.88 稳定支持裸函数:更安全简洁的汇编函数写法

本文首发公众号 猩猩程序员 欢迎关注

本文来自 blog.rust-lang.org/2025/07/03/...

在Rust 1.88.0 稳定了 #[unsafe(naked)] 属性和 naked_asm! 宏,这两个功能可以用来定义"裸函数"。


什么是裸函数?

所谓裸函数 ,是使用 #[unsafe(naked)] 属性标记的函数,其函数体必须只包含一个 naked_asm! 宏调用。例如:

rust 复制代码
/// SAFETY: 遵循 64 位 System-V ABI 约定。
#[unsafe(naked)]
pub extern "sysv64" fn wrapping_add(a: u64, b: u64) -> u64 {
    // 相当于 `a.wrapping_add(b)`。
    core::arch::naked_asm!(
        "lea rax, [rdi + rsi]",
        "ret"
    );
}

裸函数之所以叫"裸",是因为它的整个函数体完全由你写的汇编语句构成------编译器不会自动插入任何参数处理或返回值相关的代码,也不会做 ABI 或栈帧管理。

这种方式比使用 global_asm! 定义函数更简洁易用。裸函数常用于底层场景,比如 Rust 的 compiler-builtins、操作系统开发和嵌入式系统编程。


为什么要用裸函数?

等等,如果裸函数只是 global_asm! 的语法糖,为什么还要引入它?

让我们通过一个例子来说明,用 global_asm! 重写上面的 wrapping_add 函数会变成这样:

less 复制代码
// SAFETY: `wrapping_add` 是在此模块中定义的,使用 64 位 System-V ABI。
unsafe extern "sysv64" {
    fn wrapping_add(a: u64, b: u64) -> u64;
}

core::arch::global_asm!(
    r#"
        // 用于设置函数的指令(平台相关)
        .section .text.wrapping_add,"ax",@progbits
        .p2align 2
        .globl wrapping_add
        .type wrapping_add,@function

wrapping_add:
        lea rax, [rdi + rsi]
        ret

.Ltmp0:
        .size wrapping_add, .Ltmp0-wrapping_add
    "#
);

使用 naked 更方便的几个理由:

  1. 自动生成平台相关指令
    使用 global_asm! 时你必须自己写 .section.type 等平台特有的指令,而裸函数会自动生成这些内容。
  2. 名字参与 Rust 的名称修饰(mangling)
    global_asm! 中硬编码的函数名不会参与 Rust 的名字修饰机制,这会导致跨平台开发困难。裸函数则遵循 Rust 的名字修饰规则,避免符号冲突。
  3. 支持泛型
    global_asm! 定义的函数不能使用泛型(包括 const 泛型),而裸函数可以。
  4. 统一的注释与文档位置
    裸函数只有一个定义位置,因此注释、文档和属性(如 #[doc]#[inline])不容易漏掉或过时,安全注释可以紧跟函数体,非常重要。

裸函数之所以是 unsafe 的,是因为函数签名、ABI 和实现必须严格匹配,一旦不一致可能造成未定义行为。


发展历程

裸函数这个功能,其实早在 2015 年就提出过 RFC

但后来在 2020 年被 RFC 2972 取代。当时 Rust 的内联汇编语法已有重大改进,因此新的 RFC 限制裸函数的函数体必须是单个 asm! 调用,并加入了一些限制条件。

现在,距离最初提议已经 10 年,裸函数终于稳定了。

在这个过程中,有两个关键改进为稳定打下了基础:


裸函数背后的技术实现

引入 naked_asm!

裸函数的函数体必须是一个 naked_asm! 宏。这是介于 asm!global_asm! 之间的一种新形式:

  • asm! 一样,它在函数体中使用;
  • 但像 global_asm! 一样,它只接受部分操作数类型。

原本的实现尝试在 asm! 上加 lint,但难以精确定义行为和报错提示。而 naked_asm! 作为专门为裸函数设计的宏,更容易规范和理解。


改用生成 global_asm! 作为底层实现

最初的实现是直接让 LLVM 处理裸函数。但这带来两个问题:

  1. LLVM 有时会偷偷插入额外指令,导致和用户写的不一致;
  2. Rust 现在有多个非 LLVM 的后端(如 Cranelift、gcc),要复制 LLVM 的行为很困难。

现在的稳定实现是:直接将裸函数转换为一段 global_asm! 汇编代码,这样所有后端都能处理,并且用户写什么就生成什么。


汇编相关的未来展望

我们还在持续改进汇编语言的易用性。如果你喜欢裸函数,欢迎测试并反馈!


新特性预告

extern "custom" 调用约定

虽然裸函数通常使用 extern "C",但这在很多情况下是假的------它们用的是自定义 ABI。

为此,Rust 引入了 abi_custom 特性,支持 extern "custom"。比如 Rust 的 compiler-builtins 中就有如下用法:

rust 复制代码
#![feature(abi_custom)]

/// 使用 ARM 的非标准 ABI 实现除法和取模
/// ```c
/// typedef struct { int quot; int rem; } idiv_return;
///  __value_in_regs idiv_return __aeabi_idivmod(int num, int denom);
/// ```
/// SAFETY: 汇编实现了预期的 ABI,且 "custom" 避免被 Rust 代码直接调用
#[unsafe(naked)]
pub unsafe extern "custom" fn __aeabi_idivmod() {
    core::arch::naked_asm!(
        "push {{r0, r1, r4, lr}}", // 保存寄存器
        "bl {trampoline}",         // 调用除法函数
        "pop {{r1, r2}}",
        "muls r2, r2, r0",         // 求余
        "subs r1, r1, r2",
        "pop {{r4, pc}}",          // 恢复寄存器并返回(设置 pc)
        trampoline = sym crate::arm::__aeabi_idiv,
    );
}

由于 Rust 编译器不理解 custom 的调用约定,所以不能通过常规函数调用去调用它,只能用汇编间接执行。


在汇编行上使用 #[cfg(...)]

新特性 cfg_asm 允许给每一行汇编加条件编译,非常适合做平台适配。例如:

rust 复制代码
#![feature(cfg_asm)]

global_asm!(
    // ...
    // 如果启用,则设置栈指针
    #[cfg(feature = "set-sp")]
    "ldr r0, =_stack_start
     msr msp, r0",
    // ...
);

这个例子来自 cortex-m 库,之前他们为了条件编译,必须复制整段汇编块,而现在只需要加一行 #[cfg] 就行。


Rust 1.88.0 的稳定版裸函数,是对底层开发者非常重要的一步:

  • 提供更简洁安全的汇编函数定义方式;
  • 支持泛型、符号修饰、自动注释等;
  • 替代 global_asm! 的繁琐写法;
  • 未来还将支持自定义 ABI 和汇编条件编译等更强功能。

如果你在开发嵌入式、内核或需要控制 ABI 的场景中,不妨试试这个新功能!

本文首发公众号 猩猩程序员 欢迎关注

相关推荐
夕水9 分钟前
ew-vue-component:Vue 3 动态组件渲染解决方案的使用介绍
前端·vue.js
我麻烦大了12 分钟前
实现一个简单的Vue响应式
前端·vue.js
独立开阀者_FwtCoder20 分钟前
你用 Cursor 写公司的代码安全吗?
前端·javascript·github
Cacciatore->30 分钟前
React 基本介绍与项目创建
前端·react.js·arcgis
摸鱼仙人~32 分钟前
React Ref 指南:原理、实现与实践
前端·javascript·react.js
teeeeeeemo33 分钟前
回调函数 vs Promise vs async/await区别
开发语言·前端·javascript·笔记
贵沫末1 小时前
React——基础
前端·react.js·前端框架
aklry1 小时前
uniapp三步完成一维码的生成
前端·vue.js
Rubin931 小时前
判断元素在可视区域?用于滚动加载,数据埋点等
前端
爱学习的茄子1 小时前
AI驱动的单词学习应用:从图片识别到语音合成的完整实现
前端·深度学习·react.js