Stabilize naked_functions by folkertdev · Pull Request #134213 · rust-lang/rust (original) (raw)

tracking issue: #90957
request for stabilization on tracking issue: #90957 (comment)
reference PR: rust-lang/reference#1689

Request for Stabilization

Two years later, we're ready to try this again. Even though this issue is already marked as having passed FCP, given the amount of time that has passed and the changes in implementation strategy, we should follow the process again.

Summary

The naked_functions feature has two main parts: the #[unsafe(naked)] function attribute, and the naked_asm! macro.

An example of a naked function:

const THREE: usize = 3;

// SAFETY: the validity of the used registers // is guaranteed according to the "sysv64" ABI #[unsafe(naked)] pub extern "sysv64" fn add_n(number: usize) -> usize { core::arch::naked_asm!( "add rdi, {}", "mov rax, rdi", "ret", const THREE, ) }

When the #[unsafe(naked)] attribute is applied to a function, the compiler won't emit a function prologue or epilogue when generating code for this function. This attribute is analogous to __attribute__((naked)) in C. The use of this feature allows the programmer to have precise control over the assembly that is generated for a given function.

The body of a naked function must consist of a single naked_asm! invocation, a heavily restricted variant of the asm! macro: the only legal operands are const and sym, and the only legal options are raw and att_syntax. In lieu of specifying operands, the naked_asm! within a naked function relies on the function's calling convention to determine the validity of registers.

Documentation

The Rust Reference: rust-lang/reference#1689
(Previous PR: rust-lang/reference#1153)

Tests

Interaction with other (unstable) features

fn_align

Combining #[unsafe(naked)] with #[repr(align(N))] works well, and is tested e.g. here

It's tested extensively because we do need to explicitly support the repr(align) attribute (and make sure we e.g. don't mistake powers of two for number of bytes).

History

This feature was originally proposed in RFC 1201, filed on 2015-07-10 and accepted on 2016-03-21. Support for this feature was added in #32410, landing on 2016-03-23. Development languished for several years as it was realized that the semantics given in RFC 1201 were insufficiently specific. To address this, a minimal subset of naked functions was specified by RFC 2972, filed on 2020-08-07 and accepted on 2021-11-16. Prior to the acceptance of RFC 2972, all of the stricter behavior specified by RFC 2972 was implemented as a series of warn-by-default lints that would trigger on existing uses of the naked attribute; these lints became hard errors in #93153 on 2022-01-22. As a result, today RFC 2972 has completely superseded RFC 1201 in describing the semantics of the naked attribute.

More recently, the naked_asm! macro was added to replace the earlier use of a heavily restricted asm! invocation. The naked_asm! name is clearer in error messages, and provides a place for documenting the specific requirements of inline assembly in naked functions.

The implementation strategy was changed to emitting a global assembly block. In effect, an extern function

extern "C" fn foo() { core::arch::naked_asm!("ret") }

is emitted as something similar to

core::arch::global_asm!( "foo:", "ret" );

extern "C" { fn foo(); }

The codegen approach was chosen over the llvm naked function attribute because:

Finally, there is now an allow list of compatible attributes on naked functions, so that e.g. #[inline] is rejected with an error. The #[target_feature] attribute on naked functions was later made separately unstable, because implementing it is complex and we did not want to block naked functions themselves on how target features work on them. See also #138568.

relevant PRs for these recent changes

Various historical notes

noreturn

RFC 2972 mentions that naked functions

must have a body which contains only a single asm!() statement which:
iii. must contain the noreturn option.

Instead of asm!, the current implementation mandates that the body contain a single naked_asm! statement. The naked_asm! macro is a heavily restricted version of the asm! macro, making it easier to talk about and document the rules of assembly in naked functions and give dedicated error messages.

For naked_asm!, the behavior of the asm!'s noreturn option is implicit. The noreturn option means that it is UB for control flow to fall through the end of the assembly block. With asm!, this option is usually used for blocks that diverge (and thus have no return and can be typed as !). With naked_asm!, the intent is different: usually naked funtions do return, but they must do so from within the assembly block. The noreturn option was used so that the compiler would not itself also insert a ret instruction at the very end.

padding / ud2

A naked_asm! block that violates the safety assumption that control flow must not fall through the end of the assembly block is UB. Because no return instruction is emitted, whatever bytes follow the naked function will be executed, resulting in truly undefined behavior. There has been discussion whether rustc should emit an invalid instruction (e.g. ud2 on x86) after the naked_asm! block to at least fail early in the case of an invalid naked_asm!. It was however decided that it is more useful to guarantee that #[naked] functions NEVER contain any instructions besides those in the naked_asm! block.

unresolved questions

None

r? @Amanieu

I've validated the tests on x86_64 and aarch64