Skip to content

Commit d01096f

Browse files
committed
add blog post "Stabilizing naked functions"
1 parent ad93bec commit d01096f

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
+++
2+
path = "inside-rust/9999/12/31/stabilizing-naked-functions"
3+
title = "Stabilizing naked functions"
4+
authors = ["Folkert de Vries"]
5+
6+
[extra]
7+
team = "the Lang team"
8+
team_url = "https://www.rust-lang.org/governance/teams/lang#team-lang"
9+
+++
10+
11+
Rust 1.88 stabilizes the `#[unsafe(naked)]` attribute and the `naked_asm!` macro, which are used to define naked functions.
12+
13+
A naked function is marked with the `#[unsafe(naked)]` attribute, and its body consists of a single `naked_asm!` call, for example:
14+
15+
```rust
16+
#[unsafe(naked)]
17+
pub extern "sysv64" fn wrapping_add(a: u64, b: u64) {
18+
// Equivalent to `a.wrapping_add(b)`.
19+
core::arch::naked_asm!(
20+
"add rax, rdi, rsi",
21+
"ret"
22+
);
23+
}
24+
```
25+
26+
What makes naked functions special is that the handwritten assembly block defines the _entire_ function body: unlike non-naked functions, the compiler does not add any special handling for arguments or return values.
27+
28+
This feature is a more ergonomic alternative to defining functions using `global_asm!`. Naked functions are used in low-level settings like rust's [`compiler-builtins`](https://github.com/rust-lang/compiler-builtins), operating systems, and embedded applications.
29+
30+
## Why use naked functions?
31+
32+
But wait, if naked functions are just syntax sugar for `global_asm!`, why add them in the first place?
33+
34+
To illustrate why, let's consider an example of using `global_asm!` to define a function, and an `extern` block to make the function available in rust:
35+
36+
```rust
37+
unsafe extern "sysv64" {
38+
safe fn wrapping_add(a: u64, b: u64) -> u64
39+
}
40+
41+
global_asm!(
42+
r#"
43+
// Platform-specific directives that set up a function.
44+
.section .text.wrapping_add,"ax",@progbits
45+
.p2align 2
46+
.globl wrapping_add
47+
.type wrapping_add,@function
48+
49+
wrapping_add:
50+
add rax, rdi, rsi
51+
ret
52+
53+
.Ltmp0:
54+
.size wrapping_add, .Ltmp0-wrapping_add
55+
"#
56+
);
57+
```
58+
59+
The `wrapping_add` function here requires a bunch of platform-specific ceremony and has a number of limitations: using naked functions provides much better ergonomics.
60+
61+
Firstly, functions defined using global assembly cannot use generics. Especially const generics are useful in combination with assembly.
62+
63+
Next, the symbol name is hardcoded, and will not participate in rust's name mangling. That makes it harder to write cross-platform code, because different targets have different name mangling schemes (e.g. x86_64 macOS versus linux). The unmangled symbol is also globally visible (so that the `extern` block can find it) which can cause symbol resolution conflicts.
64+
65+
Finally, having just one definition provides a consistent place for (safety) documentation and attributes, with less risk of them getting out of date.
66+
67+
## How did we get here?
68+
69+
Naked functions have been in the works for a long time.
70+
71+
The [original RFC](https://github.com/rust-lang/rfcs/pull/1201) for naked functions is from 2015. That RFC was superceded by [RFC 2972](https://github.com/rust-lang/rfcs/pull/2972) in 2020: inline assembly in rust had changed substantially at that point, and the new RFC limited the body of naked functions to a single `asm!` call, with some additional constraints. And now, 10 years after the initial proposal, naked functions are stable.
72+
73+
Two additional features were implemented to make naked functions ready for stabilization.
74+
75+
##### the `naked_asm!` macro
76+
77+
The body of a naked function must be a single `naked_asm!` call. This macro is a blend between `asm!` (it is in a function body) and `global_asm!` (only some [operand types](https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.operand-type) are accepted).
78+
79+
The initial implementation of RFC 2972 added lints onto a standard `asm!` call in a naked function. This approach made it hard to write clear error messages and documentation. With the dedicated `naked_asm!` macro the behavior is much easier to specify.
80+
81+
##### Lowering to `global_asm!`
82+
83+
The initial implementation of naked functions relied on LLVM to codegen naked functions. This approach had two issues:
84+
85+
- LLVM would sometimes add unexpected additional instructions to what the user wrote
86+
- rust has non-LLVM code generation backends now: they would have to implement LLVM's (unspecified!) behavior
87+
88+
The implementation that is stabilized now instead converts the naked function into a piece of global assembly. The code generation backends can already emit global assembly, and this strategy guarantees that the whole body of the function is just instructions that the user wrote.
89+
90+
## What's next for assembly?
91+
92+
We're working on further assembly ergonomics improvements. If naked functions are something you are excited about and (may) use, we'd appreciate you testing these new features and providing feedback on their design.
93+
94+
##### `extern "custom"` functions
95+
96+
Naked functions usually get the `extern "C"` calling convention. But often that calling convention is a lie: in many cases, naked functions don't implement an abi that rust knows about. Instead they use some custom calling convention that is specific to that function.
97+
98+
The [`abi_custom`](https://github.com/rust-lang/rust/issues/140829) feature adds `extern "custom` functions and blocks, so that we can write this in [compiler-builtins](https://github.com/rust-lang/compiler-builtins/blob/267ae1fa43785448bfb0aebafc4e352c936dd4cf/compiler-builtins/src/arm.rs#L52-L63):
99+
100+
```rust
101+
#![feature(abi_custom)]
102+
103+
#[unsafe(naked)]
104+
pub unsafe extern "custom" fn __aeabi_idivmod() {
105+
core::arch::naked_asm!(
106+
"push {{r0, r1, r4, lr}}",
107+
"bl {trampoline}",
108+
"pop {{r1, r2}}",
109+
"muls r2, r2, r0",
110+
"subs r1, r1, r2",
111+
"pop {{r4, pc}}",
112+
trampoline = sym crate::arm::__aeabi_idiv,
113+
);
114+
}
115+
```
116+
117+
A consequence of using a custom calling convention is that such functions cannot be called using a rust call expression: the compiler simply does not know how to generate correct code for such a call. Instead the compiler will error when the program does try to call an `extern "custom"` function, and the only way to execute the function is using inline assembly.
118+
119+
##### `cfg` on lines of inline assembly
120+
121+
The [`cfg_asm`](https://github.com/rust-lang/rust/issues/140364) feature adds the ability to annotate individual lines of an assymbly block `#[cfg(...)]` or `#[cfg_attr(..., ...)]`. Configuring specific sections of assembly is useful to make assembly depend on for instance the target, target features, or feature flags. For example:
122+
123+
```rust
124+
#![feature(cfg_asm)]
125+
126+
global_asm!(
127+
// ...
128+
129+
// If enabled, initialise the SP. This is normally initialised by the
130+
// CPU itself or by a bootloader, but some debuggers fail to set it when
131+
// resetting the target, leading to stack corruptions.
132+
#[cfg(feature = "set-sp")]
133+
"ldr r0, =_stack_start
134+
msr msp, r0",
135+
136+
// ...
137+
)
138+
```
139+
140+
This example is from the [cortex-m](https://github.com/rust-embedded/cortex-m/blob/c3d664bba1148cc2d0f963ebeb788aa347ba81f7/cortex-m-rt/src/lib.rs#L528-L636) crate that currently has to use a custom macro that duplicates the whole assembly block for every use of `#[cfg(...)]`. With `cfg_asm`, that won't be necessary any more.
141+

0 commit comments

Comments
 (0)