Skip to content

Commit 1dbdd38

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

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
+++
2+
path = "9999/12/31/stabilizing-naked-functions"
3+
title = "Stabilizing naked functions"
4+
authors = ["Folkert de Vries"]
5+
+++
6+
7+
Rust 1.88 stabilizes the `#[unsafe(naked)]` attribute and the `naked_asm!` macro, which are used to define naked functions.
8+
9+
A naked function is marked with the `#[unsafe(naked)]` attribute, and its body consists of a single `naked_asm!` call, for example:
10+
11+
```rust
12+
#[unsafe(naked)]
13+
pub extern "sysv64" fn wrapping_add(a: u64, b: u64) {
14+
// Equivalent to `a.wrapping_add(b)`.
15+
core::arch::naked_asm!(
16+
"add rax, rdi, rsi",
17+
"ret"
18+
);
19+
}
20+
```
21+
22+
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.
23+
24+
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.
25+
26+
## Why use naked functions?
27+
28+
But wait, if naked functions are just syntax sugar for `global_asm!`, why add them in the first place?
29+
30+
To see the benefits, let's rewrite the `wrapping_add` example from the introduction using `global_asm!`:
31+
32+
```rust
33+
// This `extern` block makes the function available in rust.
34+
unsafe extern "sysv64" {
35+
safe fn wrapping_add(a: u64, b: u64) -> u64
36+
}
37+
38+
core::arch::global_asm!(
39+
r#"
40+
// Platform-specific directives that set up a function.
41+
.section .text.wrapping_add,"ax",@progbits
42+
.p2align 2
43+
.globl wrapping_add
44+
.type wrapping_add,@function
45+
46+
wrapping_add:
47+
add rax, rdi, rsi
48+
ret
49+
50+
.Ltmp0:
51+
.size wrapping_add, .Ltmp0-wrapping_add
52+
"#
53+
);
54+
```
55+
56+
The assembly block starts and ends with the directives (`.section`, `.p2align`, etc.) that are required to define a function. These directives are mechanical, but they are different between object file formats. A naked function will automatically emit the right directives.
57+
58+
Next, the `wrapping_add` 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 prefixes symbols with `_`, linux does not). The unmangled symbol is also globally visible — so that the `extern` block can find it — which can cause symbol resolution conflicts. A naked function's name does participate in name mangling and won't run into these issues.
59+
60+
A further limitation that this example does not show is that functions defined using global assembly cannot use generics. Especially const generics are useful in combination with assembly.
61+
62+
Finally, having just one definition provides a consistent place for (safety) documentation and attributes, with less risk of them getting out of date. Proper safety comments are essential for naked functions: the `naked` attribute is unsafe because the ABI (`sysv64` in our example), the signature, and the implementation have to be consistent.
63+
64+
## How did we get here?
65+
66+
Naked functions have been in the works for a long time.
67+
68+
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.
69+
70+
Two additional features were implemented to make naked functions ready for stabilization.
71+
72+
##### the `naked_asm!` macro
73+
74+
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).
75+
76+
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.
77+
78+
##### Lowering to `global_asm!`
79+
80+
The initial implementation relied on LLVM to codegen naked functions. This approach had two issues:
81+
82+
- LLVM would sometimes add unexpected additional instructions to what the user wrote
83+
- rust has non-LLVM code generation backends now: they would have to implement LLVM's (unspecified!) behavior
84+
85+
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 the instructions that the user wrote.
86+
87+
## What's next for assembly?
88+
89+
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.
90+
91+
##### `extern "custom"` functions
92+
93+
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.
94+
95+
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):
96+
97+
```rust
98+
#![feature(abi_custom)]
99+
100+
#[unsafe(naked)]
101+
pub unsafe extern "custom" fn __aeabi_idivmod() {
102+
core::arch::naked_asm!(
103+
"push {{r0, r1, r4, lr}}",
104+
"bl {trampoline}",
105+
"pop {{r1, r2}}",
106+
"muls r2, r2, r0",
107+
"subs r1, r1, r2",
108+
"pop {{r4, pc}}",
109+
trampoline = sym crate::arm::__aeabi_idiv,
110+
);
111+
}
112+
```
113+
114+
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.
115+
116+
##### `cfg` on lines of inline assembly
117+
118+
The [`cfg_asm`](https://github.com/rust-lang/rust/issues/140364) feature adds the ability to annotate individual lines of an assembly block with `#[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:
119+
120+
```rust
121+
#![feature(cfg_asm)]
122+
123+
global_asm!(
124+
// ...
125+
126+
// If enabled, initialise the SP. This is normally initialised by the
127+
// CPU itself or by a bootloader, but some debuggers fail to set it when
128+
// resetting the target, leading to stack corruptions.
129+
#[cfg(feature = "set-sp")]
130+
"ldr r0, =_stack_start
131+
msr msp, r0",
132+
133+
// ...
134+
)
135+
```
136+
137+
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.

0 commit comments

Comments
 (0)