-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stabilize naked_functions
#134213
base: master
Are you sure you want to change the base?
Stabilize naked_functions
#134213
Conversation
Some changes occurred in src/doc/unstable-book/src/compiler-flags/sanitizer.md cc @rust-lang/project-exploit-mitigations, @rcvalle This PR modifies cc @jieyouxu rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead. cc @rust-lang/rust-analyzer Some changes occurred in compiler/rustc_codegen_cranelift cc @bjorn3 |
☔ The latest upstream changes (presumably #134395) made this pull request unmergeable. Please resolve the merge conflicts. |
6183807
to
ed0d0b9
Compare
ed0d0b9
to
ae2fa18
Compare
r? lang |
This probably needs a new lang FCP since the old one is probably outdated (the implementation of naked function has changed signficantly). |
Thanks for the thorough report @folkertdev! @rfcbot fcp merge |
Team member @tmandry has proposed to merge this. The next step is review by the rest of the tagged team members: Concerns:
Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
Actually, @rust-lang/libs-api does this need your FCP? I think the path of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: does #![feature(naked_functions)]
have any iteractions with #![feature(fn_align)]
? In particular, are there any potential caveats or gotchas that stabilizing naked_functions
may have for fn_align
in the future?
(The test is irrelevant, I just wanted to force an inline comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Combining #[naked]
with #[repr(align(N))]
works well, and is tested e.g. here
- https://github.com/rust-lang/rust/blob/master/tests/codegen/naked-fn/aligned.rs
- https://github.com/rust-lang/rust/blob/master/tests/codegen/naked-fn/min-function-alignment.rs
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be worth including what you just mentioned here in the stabilization report.
My 2 cents: +1 on making the attribute unsafe; I am not sure if I would then want to see the naked_asm macro not require unsafe (it's somewhat redundant, but as this is wildly unsafe...) Also, just to make sure I understand correctly, the following would not compile, right? #[naked]
pub extern "sysv64" fn f() {} It is obviously unsound, without actually containing an unsafe block, as calling this function would start executing padding bytes, so it should be disallowed. If that's the case, then it's impossible to write a naked function without writing unsafe inside it, which sounds like a pretty convincing argument to make it an unsafe attribute... |
Could someone explain the interaction with target features? I would have expected that attribute to be completely useless on a naked function -- if I want to use AVX instructions, I just put them in the asm block, and that implicitly imposes a requirement that AVX must be available, but the compiler doesn't actually have to know about this? |
@rfcbot resolve question-about-unwind As @Amanieu mentioned, naked functions do support unwinding, and the Reference PR has been updated to reflect and describe this. It looks like this: #![feature(naked_functions)]
#[unsafe(no_mangle)]
pub extern "sysv64-unwind" fn g() {
panic!();
}
#[cfg(target_arch = "x86_64")]
#[naked]
#[unsafe(no_mangle)]
pub extern "sysv64-unwind" fn f() {
unsafe {
core::arch::naked_asm!(
".cfi_startproc",
"push rbp",
".cfi_adjust_cfa_offset 8",
".cfi_offset rbp, -16",
"mov rbp, rsp",
".cfi_def_cfa_register rbp",
"call {f}",
"pop rbp",
".cfi_def_cfa rsp, 8",
"ret",
".cfi_endproc",
f = sym g,
)
}
} Playground link (with further annotations) |
On many targets LLVM refuses to assemble instructions unless the target feature that they got introduced with is enabled. Target features can also affect the exact encoding of instructions I believe. |
Isn't that something that can be toggled within a |
In most cases yes, using assembler directives. Those depend on the architecture. E.g.
To me, the appeal of It does not seem feasible to me to make target features work seamlessly for naked functions: on wasm LLVM gives us no tools for it, for arm it's horribly complex, and even for the other targets every target feature would need to be tested to make sure it actually works (because rust target features don't map one-to-one with what the assembler understands). But (and I overlooked this before) So I think that leaves 2 plausible options
I have concerns about option 1 because it can cause some extremely confusing errors, e.g. this would error https://godbolt.org/z/89GhGzqW4 #[naked]
#[no_mangle]
#[target_feature(enable = "aes")]
unsafe extern "C" fn aes_encrypt(a: uint8x16_t, round_key: uint8x16_t) -> uint8x16_t {
core::arch::naked_asm!(
"aese v0.16b, v1.16b",
"ret",
);
} Saying that
moreover, the non-naked version of this function does work! #[no_mangle]
#[target_feature(enable = "aes")]
unsafe extern "C" fn non_naked_inline_asm(a: uint8x16_t, round_key: uint8x16_t) -> uint8x16_t {
core::arch::asm!(
"aese {0:v}.16b, {1:v}.16b",
"ret",
in(vreg) a, in(vreg) round_key, options(noreturn)
);
} I suppose we could try to parse the LLVM error and try to emit a custom error message mentioning target features (and specifically how they behave on naked functions)? I brought up parsing the error before though #121496 and others seemed sceptical. |
I'm afraid the situation is somewhat more complex than that -- if you use a SIMD type by value without declaring the target feature on the function, nowadays you get a warning, but we're planning for that to become a hard error. I'd argue that it should be sufficient to write in the documentation of |
Is there any reason for not just moving |
Okay this seem to mostly be about the technical difficulties of passing targey features to the assemblee then, I have no idea at all about things like that.
We can map our tatget feature names to the ones LLVM uses easily, we already do that. Is that not enough?
If LLVM does not let us set the targey features on naked functions the same way we set it on all other functions, then we'd have to duplicate a lot of the backend specific logic inside LLVM, right? That sounds terrible.
But (and I overlooked this before) #[target_feature] is also relevant for ABI reasons: it influences which registers are used to pass arguments and return values, e.g. https://godbolt.org/z/xW4cW3dor. That is highly relevant for naked functions which rely on the ABI of the function to get at their arguments and put the return value in the right place.
Every such case is a soundness bug (and we should we emitting a future compat warning already). Some types require certain target features to be passable, but the presence or absence of a target feature must only affect whether the code builds.
|
catching up to some comments
Yes that is what I would argue :) from the edition guide
There are no soundness requirements to a naked function itself. The requirements are on the
Correct, that is tested here: rust/tests/ui/asm/naked-functions.rs Lines 79 to 81 in ed897d5
Sadly not. LLVM target features are also just made-up names that don't always map to the names that the assembler uses. E.g. the arm
That is my proposed course of action. So far nobody seems to really be against this approach, so if things stay that way I'll make a PR. |
And I guess we can't send the naked function "through" LLVM to let it do the translation? That might be a good feature request for them...
Sounds good to me. |
No that was the original design, but it was rejected for various reasons:
Some further context from the tracking issue: |
As they should. They have to deal with target feature names anyway for regular code. Anyway, I am mostly clueless here, just poking a bit in the dark to feel out the design space. @Amanieu knows a lot better than me how to make inline assembly work well. :) |
☔ The latest upstream changes (presumably #138450) made this pull request unmergeable. Please resolve the merge conflicts. |
ae2fa18
to
1687743
Compare
…eature-gate, r=Amanieu add `naked_functions_target_feature` unstable feature tracking issue: rust-lang#138568 tagging rust-lang#134213 rust-lang#90957 This PR puts `#[target_feature(/* ... */)]` on `#[naked]` functions behind its own feature gate, so that naked functions can be stabilized. It turns out that supporting `target_feature` on naked functions is tricky on some targets, so we're splitting it out to not block stabilization of naked functions themselves. See the tracking issue for more information and workarounds. Note that at the time of writing, the `target_features` attribute is ignored when generating code for naked functions. r? `@Amanieu`
…eature-gate, r=Amanieu add `naked_functions_target_feature` unstable feature tracking issue: rust-lang#138568 tagging rust-lang#134213 rust-lang#90957 This PR puts `#[target_feature(/* ... */)]` on `#[naked]` functions behind its own feature gate, so that naked functions can be stabilized. It turns out that supporting `target_feature` on naked functions is tricky on some targets, so we're splitting it out to not block stabilization of naked functions themselves. See the tracking issue for more information and workarounds. Note that at the time of writing, the `target_features` attribute is ignored when generating code for naked functions. r? ``@Amanieu``
Rollup merge of rust-lang#138570 - folkertdev:naked-function-target-feature-gate, r=Amanieu add `naked_functions_target_feature` unstable feature tracking issue: rust-lang#138568 tagging rust-lang#134213 rust-lang#90957 This PR puts `#[target_feature(/* ... */)]` on `#[naked]` functions behind its own feature gate, so that naked functions can be stabilized. It turns out that supporting `target_feature` on naked functions is tricky on some targets, so we're splitting it out to not block stabilization of naked functions themselves. See the tracking issue for more information and workarounds. Note that at the time of writing, the `target_features` attribute is ignored when generating code for naked functions. r? ``@Amanieu``
☔ The latest upstream changes (presumably #138791) made this pull request unmergeable. Please resolve the merge conflicts. |
1687743
to
02cff1f
Compare
The tracking issue: #138568 Looking at the other concerns, The current remaining concerns are then:
Ah, I misunderstood this earlier. What I meant is that we could have used llvm's native support for naked functions, which has the downsides that I and Amanieu mentioned. I think you propose to still use global assembly, but somehow inform the backend of the target features. That would be neat, but can't currently be done even in LLVM. |
Is there a practical reason to allow I think unless there is a specific need for it it would be best to disallow it (From an mostly outsider perspective, I'm not really familiar with the state of Rust ABI and the current state of naked functions). |
Linting on |
Sounds right. Thanks for filing the new tracking issue. @rfcbot resolve what-to-do-for-target-feature
They are. Thanks for adding these notes. The rationales and outcomes there seem right to me. @rfcbot resolve question-about-noreturn
These probably need to be discussed with the team. We'll take them up in a lang call. |
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#[naked]
function attribute, and thenaked_asm!
macro.An example of a naked function:
When the
#[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 theasm!
macro: the only legal operands areconst
andsym
, and the only legal options areraw
andatt_syntax
. In lieu of specifying operands, thenaked_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#1153
Tests
pub
,#[no_mangle]
and#[linkage = "..."]
work correctly for naked functionsnaked_asm!
, etcInteraction with other (unstable) features
fn_align
Combining
#[naked]
with#[repr(align(N))]
works well, and is tested e.g. hereIt'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 thenaked
attribute.More recently, the
naked_asm!
macro was added to replace the earlier use of a heavily restrictedasm!
invocation. Thenaked_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
is emitted as something similar to
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
#[naked]
: report incompatible attributes #127853naked_asm!
macro for use in#[naked]
functions #128651#[naked]
functions using global asm #128004naked_functions_target_feature
unstable feature #138570Various historical notes
noreturn
RFC 2972 mentions that naked functions
Instead of
asm!
, the current implementation mandates that the body contain a singlenaked_asm!
statement. Thenaked_asm!
macro is a heavily restricted version of theasm!
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 theasm!
'snoreturn
option is implicit. Thenoreturn
option means that it is UB for control flow to fall through the end of the assembly block. Withasm!
, this option is usually used for blocks that diverge (and thus have no return and can be typed as!
). Withnaked_asm!
, the intent is different: usually naked funtions do return, but they must do so from within the assembly block. Thenoreturn
option was used so that the compiler would not itself also insert aret
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 thenaked_asm!
block to at least fail early in the case of an invalidnaked_asm!
. It was however decided that it is more useful to guarantee that#[naked]
functions NEVER contain any instructions besides those in thenaked_asm!
block.unresolved questions
None
r? @Amanieu
I've validated the tests on x86_64 and aarch64