Skip to content

Commit ff6252f

Browse files
authored
Merge pull request #1689 from folkertdev/stabilize-naked-functions
naked functions
2 parents f606493 + aca530a commit ff6252f

File tree

3 files changed

+174
-6
lines changed

3 files changed

+174
-6
lines changed

src/attributes.md

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The following attributes are unsafe:
4444

4545
* [`export_name`]
4646
* [`link_section`]
47+
* [`naked`]
4748
* [`no_mangle`]
4849

4950
r[attributes.kind]
@@ -290,6 +291,7 @@ The following is an index of all built-in attributes.
290291
- Code generation
291292
- [`inline`] --- Hint to inline code.
292293
- [`cold`] --- Hint that a function is unlikely to be called.
294+
- [`naked`] - Prevent the compiler from emitting a function prologue.
293295
- [`no_builtins`] --- Disables use of certain built-in functions.
294296
- [`target_feature`] --- Configure platform-specific code generation.
295297
- [`track_caller`] --- Pass the parent call location to `std::panic::Location::caller()`.
@@ -360,6 +362,7 @@ The following is an index of all built-in attributes.
360362
[`macro_export`]: macros-by-example.md#path-based-scope
361363
[`macro_use`]: macros-by-example.md#the-macro_use-attribute
362364
[`must_use`]: attributes/diagnostics.md#the-must_use-attribute
365+
[`naked`]: attributes/codegen.md#the-naked-attribute
363366
[`no_builtins`]: attributes/codegen.md#the-no_builtins-attribute
364367
[`no_implicit_prelude`]: names/preludes.md#the-no_implicit_prelude-attribute
365368
[`no_link`]: items/extern-crates.md#the-no_link-attribute

src/attributes/codegen.md

+44
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,42 @@ r[attributes.codegen.cold]
4646
The *`cold` [attribute]* suggests that the attributed function is unlikely to
4747
be called.
4848

49+
r[attributes.codegen.naked]
50+
## The `naked` attribute
51+
52+
r[attributes.codegen.naked.intro]
53+
The *`naked` [attribute]* prevents the compiler from emitting a function prologue and epilogue for the attributed function.
54+
55+
r[attributes.codegen.naked.body]
56+
The [function body] must consist of exactly one [`naked_asm!`] macro invocation.
57+
58+
r[attributes.codegen.naked.prologue-epilogue]
59+
No function prologue or epilogue is generated for the attributed function. The assembly code in the `naked_asm!` block constitutes the full body of a naked function.
60+
61+
r[attributes.codegen.naked.unsafe-attribute]
62+
The `naked` attribute is an [unsafe attribute]. Annotating a function with `#[unsafe(naked)]` comes with the safety obligation that the body must respect the function's calling convention, uphold its signature, and either return or diverge (i.e., not fall through past the end of the assembly code).
63+
64+
r[attributes.codegen.naked.call-stack]
65+
The assembly code may assume that the call stack and register state are valid on entry as per the signature and calling convention of the function.
66+
67+
r[attributes.codegen.naked.no-duplication]
68+
The assembly code may not be duplicated by the compiler except when monomorphizing polymorphic functions.
69+
70+
> [!NOTE]
71+
> Guaranteeing when the assembly code may or may not be duplicated is important for naked functions that define symbols.
72+
73+
r[attributes.codegen.naked.unused-variables]
74+
The [`unused_variables`] lint is suppressed within naked functions.
75+
76+
r[attributes.codegen.naked.inline]
77+
The [`inline`](#the-inline-attribute) attribute cannot by applied to a naked function.
78+
79+
r[attributes.codegen.naked.track_caller]
80+
The [`track_caller`](#the-track_caller-attribute) attribute cannot be applied to a naked function.
81+
82+
r[attributes.codegen.naked.testing]
83+
The [testing attributes](testing.md) cannot be applied to a naked function.
84+
4985
r[attributes.codegen.no_builtins]
5086
## The `no_builtins` attribute
5187

@@ -497,15 +533,23 @@ trait object whose methods are attributed.
497533
498534
[`-C target-cpu`]: ../../rustc/codegen-options/index.html#target-cpu
499535
[`-C target-feature`]: ../../rustc/codegen-options/index.html#target-feature
536+
[`inline`]: #the-inline-attribute
500537
[`is_x86_feature_detected`]: ../../std/arch/macro.is_x86_feature_detected.html
501538
[`is_aarch64_feature_detected`]: ../../std/arch/macro.is_aarch64_feature_detected.html
539+
[`naked_asm!`]: ../inline-assembly.md
502540
[`target_feature` conditional compilation option]: ../conditional-compilation.md#target_feature
541+
[`track_caller`]: #the-track-caller-attribute
542+
[`unused_variables`]: ../../rustc/lints/listing/warn-by-default.html#unused-variables
503543
[attribute]: ../attributes.md
504544
[attributes]: ../attributes.md
545+
[FFI-safe]: ../../rustc/lints/listing/warn-by-default.html#improper-ctypes-definitions
546+
[function body]: ../items/functions.md#function-body
505547
[functions]: ../items/functions.md
548+
[rules for inline assembly]: ../inline-assembly.md#rules-for-inline-assembly
506549
[target architecture]: ../conditional-compilation.md#target_arch
507550
[trait]: ../items/traits.md
508551
[undefined behavior]: ../behavior-considered-undefined.md
552+
[unsafe attribute]: ../attributes.md#r-attributes.safety
509553
[rust-abi]: ../items/external-blocks.md#abi
510554
[`Location`]: core::panic::Location
511555

src/inline-assembly.md

+127-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ r[asm]
22
# Inline assembly
33

44
r[asm.intro]
5-
Support for inline assembly is provided via the [`asm!`] and [`global_asm!`] macros.
5+
Support for inline assembly is provided via the [`asm!`], [`naked_asm!`], and [`global_asm!`] macros.
66
It can be used to embed handwritten assembly in the assembly output generated by the compiler.
77

88
[`asm!`]: core::arch::asm
9+
[`naked_asm!`]: core::arch::naked_asm
910
[`global_asm!`]: core::arch::global_asm
1011

1112
r[asm.stable-targets]
@@ -58,14 +59,15 @@ option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nost
5859
options := "options(" option *("," option) [","] ")"
5960
operand := reg_operand / clobber_abi / options
6061
asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")"
62+
naked_asm := "naked_asm!(" format_string *("," format_string) *("," operand) [","] ")"
6163
global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")"
6264
```
6365

6466
r[asm.scope]
6567
## Scope
6668

6769
r[asm.scope.intro]
68-
Inline assembly can be used in one of two ways.
70+
Inline assembly can be used in one of three ways.
6971

7072
r[asm.scope.asm]
7173
With the `asm!` macro, the assembly code is emitted in a function scope and integrated into the compiler-generated assembly code of a function.
@@ -78,6 +80,18 @@ unsafe { core::arch::asm!("/* {} */", in(reg) 0); }
7880
# }
7981
```
8082

83+
r[asm.scope.naked_asm]
84+
With the `naked_asm!` macro, the assembly code is emitted in a function scope and constitutes the full assembly code of a function. The `naked_asm!` macro is only allowed in [naked functions](attributes/codegen.md#the-naked-attribute).
85+
86+
```rust
87+
# #[cfg(target_arch = "x86_64")] {
88+
# #[unsafe(naked)]
89+
# extern "C" fn wrapper() {
90+
core::arch::naked_asm!("/* {} */", const 0);
91+
# }
92+
# }
93+
```
94+
8195
r[asm.scope.global_asm]
8296
With the `global_asm!` macro, the assembly code is emitted in a global scope, outside a function.
8397
This can be used to hand-write entire functions using assembly code, and generally provides much more freedom to use arbitrary registers and assembler directives.
@@ -384,8 +398,11 @@ assert_eq!(y, 1);
384398
# }
385399
```
386400

401+
r[asm.operand-type.naked_asm-restriction]
402+
Because `naked_asm!` defines a whole function body and the compiler cannot emit any additional code to handle operands, it can only use `sym` and `const` operands.
403+
387404
r[asm.operand-type.global_asm-restriction]
388-
Since `global_asm!` exists outside a function, it can only use `sym` and `const` operands.
405+
Because `global_asm!` exists outside a function, it can only use `sym` and `const` operands.
389406

390407
```rust,compile_fail
391408
# fn main() {}
@@ -1206,9 +1223,11 @@ unsafe { core::arch::asm!("mov {:e}, 1", out(reg) z, options(noreturn)); }
12061223
# #[cfg(not(target_arch = "x86_64"))] core::compile_error!("Test not supported on this arch");
12071224
```
12081225

1226+
r[asm.options.naked_asm-restriction]
1227+
`naked_asm!` only supports the `att_syntax` and `raw` options. The remaining options are not meaningful because the inline assembly defines the whole function body.
1228+
12091229
r[asm.options.global_asm-restriction]
1210-
`global_asm!` only supports the `att_syntax` and `raw` options.
1211-
The remaining options are not meaningful for global-scope inline assembly
1230+
`global_asm!` only supports the `att_syntax` and `raw` options. The remaining options are not meaningful for global-scope inline assembly.
12121231

12131232
```rust,compile_fail
12141233
# fn main() {}
@@ -1242,7 +1261,6 @@ r[asm.rules.unwind]
12421261

12431262
r[asm.rules.mem-same-as-ffi]
12441263
- The set of memory locations that assembly code is allowed to read and write are the same as those allowed for an FFI function.
1245-
- Refer to the unsafe code guidelines for the exact rules.
12461264
- If the `readonly` option is set, then only memory reads are allowed.
12471265
- If the `nomem` option is set then no reads or writes to memory are allowed.
12481266
- These rules do not apply to memory which is private to the assembly code, such as stack space allocated within it.
@@ -1362,6 +1380,109 @@ r[asm.rules.preserves_flags]
13621380
> [!NOTE]
13631381
> As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call.
13641382
1383+
r[asm.naked-rules]
1384+
## Rules for naked inline assembly
1385+
1386+
r[asm.naked-rules.intro]
1387+
To avoid undefined behavior, these rules must be followed when using function-scope inline assembly in naked functions (`naked_asm!`):
1388+
1389+
r[asm.naked-rules.reg-not-input]
1390+
- Any registers not used for function inputs according to the calling convention and function signature will contain an undefined value on entry to the `naked_asm!` block.
1391+
- An "undefined value" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture. Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code).
1392+
1393+
r[asm.naked-rules.callee-saved-registers]
1394+
- All callee-saved registers must have the same value upon return as they had on entry.
1395+
1396+
r[asm.naked-rules.caller-saved-registers]
1397+
- Caller-saved registers may be used freely.
1398+
1399+
r[asm.naked-rules.noreturn]
1400+
- Behavior is undefined if execution falls through past the end of the assembly code.
1401+
- Every path through the assembly code is expected to terminate with a return instruction or to diverge.
1402+
1403+
r[asm.naked-rules.mem-same-as-ffi]
1404+
- The set of memory locations that assembly code is allowed to read and write are the same as those allowed for an FFI function.
1405+
1406+
r[asm.naked-rules.black-box]
1407+
- The compiler cannot assume that the instructions in the `naked_asm!` block are the ones that will actually be executed.
1408+
- This effectively means that the compiler must treat the `naked_asm!` as a black box and only take the interface specification into account, not the instructions themselves.
1409+
- Runtime code patching is allowed, via target-specific mechanisms.
1410+
1411+
r[asm.naked-rules.unwind]
1412+
- Unwinding out of a `naked_asm!` block is allowed.
1413+
- For correct behavior, the appropriate assembler directives that emit unwinding metadata must be used.
1414+
1415+
```rust
1416+
# #[cfg(target_arch = "x86_64")] {
1417+
#[unsafe(naked)]
1418+
extern "sysv64-unwind" fn unwinding_naked() {
1419+
core::arch::naked_asm!(
1420+
// "CFI" here stands for "call frame information".
1421+
".cfi_startproc",
1422+
// The CFA (canonical frame address) is the value of `rsp`
1423+
// before the `call`, i.e. before the return address, `rip`,
1424+
// was pushed to `rsp`, so it's eight bytes higher in memory
1425+
// than `rsp` upon function entry (after `rip` has been
1426+
// pushed).
1427+
//
1428+
// This is the default, so we don't have to write it.
1429+
//".cfi_def_cfa rsp, 8",
1430+
//
1431+
// The traditional thing to do is to preserve the base
1432+
// pointer, so we'll do that.
1433+
"push rbp",
1434+
// Since we've now extended the stack downward by 8 bytes in
1435+
// memory, we need to adjust the offset to the CFA from `rsp`
1436+
// by another 8 bytes.
1437+
".cfi_adjust_cfa_offset 8",
1438+
// We also then annotate where we've stored the caller's value
1439+
// of `rbp`, relative to the CFA, so that when unwinding into
1440+
// the caller we can find it, in case we need it to calculate
1441+
// the caller's CFA relative to it.
1442+
//
1443+
// Here, we've stored the caller's `rbp` starting 16 bytes
1444+
// below the CFA. I.e., starting from the CFA, there's first
1445+
// the `rip` (which starts 8 bytes below the CFA and continues
1446+
// up to it), then there's the caller's `rbp` that we just
1447+
// pushed.
1448+
".cfi_offset rbp, -16",
1449+
// As is traditional, we set the base pointer to the value of
1450+
// the stack pointer. This way, the base pointer stays the
1451+
// same throughout the function body.
1452+
"mov rbp, rsp",
1453+
// We can now track the offset to the CFA from the base
1454+
// pointer. This means we don't need to make any further
1455+
// adjustments until the end, as we don't change `rbp`.
1456+
".cfi_def_cfa_register rbp",
1457+
// We can now call a function that may panic.
1458+
"call {f}",
1459+
// Upon return, we restore `rbp` in preparation for returning
1460+
// ourselves.
1461+
"pop rbp",
1462+
// Now that we've restored `rbp`, we must specify the offset
1463+
// to the CFA again in terms of `rsp`.
1464+
".cfi_def_cfa rsp, 8",
1465+
// Now we can return.
1466+
"ret",
1467+
".cfi_endproc",
1468+
f = sym may_panic,
1469+
)
1470+
}
1471+
1472+
extern "sysv64-unwind" fn may_panic() {
1473+
panic!("unwind");
1474+
}
1475+
# }
1476+
```
1477+
1478+
> [!NOTE]
1479+
>
1480+
> For more information on the `cfi` assembler directives above, see these resources:
1481+
>
1482+
> - [Using `as` - CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html)
1483+
> - [DWARF Debugging Information Format Version 5](https://dwarfstd.org/doc/DWARF5.pdf)
1484+
> - [ImperialViolet - CFI directives in assembly files](https://www.imperialviolet.org/2017/01/18/cfi.html)
1485+
13651486
r[asm.validity]
13661487
### Correctness and Validity
13671488

0 commit comments

Comments
 (0)