Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

folkertdev
Copy link
Contributor

@folkertdev folkertdev commented Dec 12, 2024

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 the naked_asm! macro.

An example of a naked function:

const THREE: usize = 3;

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

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 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#1153

Tests

Interaction with other (unstable) features

fn_align

Combining #[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:

  • the rust compiler can guarantee the behavior (no sneaky additional instructions, no inlining, etc.)
  • behavior is the same on all backends (llvm, cranelift, gcc, etc)

Finally, there is now an allow list of compatible attributes on naked functions, so that e.g. #[inline] is rejected with an error.

relevant PRs for these recent changes

Various historical notes

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.

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 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

@rustbot rustbot added A-run-make Area: port run-make Makefiles to rmake.rs PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 12, 2024
@rustbot
Copy link
Collaborator

rustbot commented Dec 12, 2024

Some changes occurred in src/doc/unstable-book/src/compiler-flags/sanitizer.md

cc @rust-lang/project-exploit-mitigations, @rcvalle

This PR modifies tests/run-make/. If this PR is trying to port a Makefile
run-make test to use rmake.rs, please update the
run-make port tracking issue
so we can track our progress. You can either modify the tracking issue
directly, or you can comment on the tracking issue and link this PR.

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

@traviscross traviscross added T-lang Relevant to the language team, which will review and decide on the PR/issue. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging needs-fcp This change is insta-stable, so needs a completed FCP to proceed. I-lang-nominated Nominated for discussion during a lang team meeting. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Dec 13, 2024
@bors
Copy link
Contributor

bors commented Dec 16, 2024

☔ The latest upstream changes (presumably #134395) made this pull request unmergeable. Please resolve the merge conflicts.

@folkertdev folkertdev force-pushed the stabilize-naked-functions branch from ed0d0b9 to ae2fa18 Compare January 8, 2025 18:39
@tgross35 tgross35 added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 9, 2025
@Amanieu
Copy link
Member

Amanieu commented Jan 13, 2025

r? lang

@rustbot rustbot assigned tmandry and unassigned Amanieu Jan 13, 2025
@Amanieu
Copy link
Member

Amanieu commented Jan 13, 2025

This probably needs a new lang FCP since the old one is probably outdated (the implementation of naked function has changed signficantly).

@tmandry
Copy link
Member

tmandry commented Jan 18, 2025

Thanks for the thorough report @folkertdev!

@rfcbot fcp merge

@rfcbot
Copy link

rfcbot commented Jan 18, 2025

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.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jan 18, 2025
@tmandry
Copy link
Member

tmandry commented Jan 18, 2025

Actually, @rust-lang/libs-api does this need your FCP? I think the path of core::arch::naked_asm! is the only part that might.

@traviscross
Copy link
Contributor

traviscross commented Mar 6, 2025

RFC 2972 specifies that naked functions:

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

This doesn't seem to be required in nightly and isn't mentioned in the stabilization report. What's the story here?

In addition to it not be required, it's not even allowed. Why is that?

#![feature(naked_functions)]

#[unsafe(no_mangle)]
pub extern "sysv64" fn g() -> ! {
    loop {}
}

#[naked]
pub extern "sysv64" fn f() -> ! {
    unsafe {
        core::arch::naked_asm!(
            "jmp {}",
            "ud2",
            sym g,
            options(noreturn), //~ ERROR
        )
    }
}

fn main() {
    f();
}

Playground link

@rfcbot concern question-about-noreturn

@traviscross
Copy link
Contributor

#134213 (comment)

@rfcbot concern question-about-rust-abi

@traviscross
Copy link
Contributor

@rfcbot concern mention-nop-padding-and-ud2

There was a long discussion over in the tracking issue about nop padding and the emission of ud2 instructions. Probably the stabilization report should discuss the nature of the concern and what was decided here.

@tgross35
Copy link
Contributor

tgross35 commented Mar 6, 2025

I don't really see any benefit to extern "Rust" outside of experimentation or hacks, but I don't think there is a strong motivation to forbid it. The code in naked_asm! needs to respect the ABI to be sound, which for "Rust" presumably includes understanding that the ABI is subject to change across versions.

The lint could be deny-by-default but that probably doesn't gain much.

@traviscross
Copy link
Contributor

traviscross commented Mar 6, 2025

Actually unwinding out of a naked function is a perfectly valid thing to do, it's fundamentally just an FFI extern fn so you just need to use assembler directives to emit the correct unwinding metadata as required by the ABI on the function. Restrictions on unwinding only apply to asm! since that's a non-standard call operation and LLVM doesn't support invoke on inline asm reliably (I encountered incorrect register allocation when trying the nightly feature in corosensei).

Interesting. Probably we should update the Reference PR then, and it'd be good for that to have an example of what it looks like to do this correctly.

@deltragon
Copy link
Contributor

RFC 2972 specifies that naked functions:

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

This doesn't seem to be required in nightly and isn't mentioned in the stabilization report. What's the story here?

In addition to it not be required, it's not even allowed. Why is that?

This was decided to be implicit in #128651, when it was switched from asm! to naked_asm!. It is documented in the reference PR rust-lang/reference#1689, but it might make sense to more clearly spell it out in the stabilization request.

@folkertdev
Copy link
Contributor Author

Thanks for the detailed notes!

I'll update the stabilization reports with notes on noreturn and padding/ud2.

Something that I think should also be brought up here is the handling of target features.

The current implementation silently ignores target features on #[naked] functions, and that is a problem.
There is a PR to implement #[target_feature(enable = "...")] support here: #137720

but

  • on some targets, there are no assembler directives for toggling target features (notably webassembly)
  • on some targets, there is no clean mechanism for turning a feature on and then off again guaranteeing that you're now in the initial state (notably arm8)
  • rust target feature names need not correspond to the names that the assembler recognizes, and it's easy for such an inconsistency to slip through and give assembler errors (e.g. when new target features or targets are added).

So, I'm not exactly sure yet what to do with all that yet. I think we can make it work well for the targets where asm! is currently stable, but it's hard to know whether we're making guarantees that some future architecture can't keep.

One option is to split out target feature support and have that remain unstable on naked functions for the time being? Users of naked functions are writing assembly anyway, so adding the required assembler directives likely is something they can live with.

@traviscross
Copy link
Contributor

About #[target_feature(..)]...

cc @veluca93 @RalfJung @workingjubilee

@traviscross
Copy link
Contributor

Thanks for raising that question about target_feature. Agreed we should work that out and then describe the answer in the stabilization report.

#134213 (comment)

@rfcbot concern what-to-do-for-target-feature

About WASM here...

cc @alexcrichton @yoshuawuyts

@alexcrichton
Copy link
Member

For Wasm, IIRC naked functions require the use of inline assembly which is already unstable on WebAssembly anyway, so this stability wouldn't end up affecting wasm would it?

Regardless though my guess is that the wasm assembler in LLVM doesn't have stateful features like other architectures do. In that sense I (a) don't know enough about the assembler which is another reason to keep it unstable on wasm, which I think it already is, and (b) I suspect this probably isn't an issue for wasm and if it ever were we could add similar directives as other native platforms too.

@folkertdev
Copy link
Contributor Author

I used wasm just as an example of a target for which we can't set the target features in global inline assembly. There are also some platforms where it would be complex (i.e. high maintenance burden). We need a policy for what to do in those cases.

As a concrete example, it is unclear what this code should do, given that we just can't apply the target feature:

https://godbolt.org/z/esP11x8j8

#![feature(naked_functions, asm_experimental_arch)]

#[no_mangle]
#[naked]
#[cfg(target_arch = "wasm32")]
#[target_feature(enable = "simd128")]
unsafe extern "C" fn simd128() {
    std::arch::naked_asm!(
        "i8x16.shuffle 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15",
        "return"
    );
}

On nightly today, #[naked] silently ignores the target feature, resulting in this error:

error: instruction requires: simd128
  |
note: instantiated into assembly here
 --> <inline asm>:6:1
  |
6 | i8x16.shuffle 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
  | ^

That the target feature doesn't work is surprising, because standard (clothed?) functions can be defined with a target feature just fine (even though on wasm this does not make much sense)

use std::arch::wasm32::*;

#[no_mangle]
#[target_feature(enable = "simd128")]
unsafe extern "C" fn simd128_intrinsic(v: v128) -> v128 { 
    i8x16_add(v, v)
}

For webassembly this issue is not acute because inline assembly for it is unstable (but hopefully that won't be forever), and also LLVM might just implement a way to toggle target features using assembler directives. But at least right now there are targets for which we can't guarantee that we can toggle target features (correctly).

Currently the option I prefer is to disallow #[target_feature] on naked functions altogether by putting it behind a new feature gate. We can then gradually allow it on platforms where we can guarantee (and hopefully automatically test) that the target features rust accepts actually work (i.e., that #[naked] never generates broken assembly).

That route unblocks stabilization of #[naked], doesn't (as far as I can tell) close any doors, and it's probably fine for the target audience of naked functions, that is likely comfortable with manually inserting some assembler directives.

@veluca93
Copy link
Contributor

veluca93 commented Mar 6, 2025

Do we need to allow #[target_feature(enable = "...")] at all on #[naked] functions?

If we do allow it, I think it should be up to whoever writes the naked_asm block to enable any needed syntax extensions, and the attribute should not do this -- I would even consider the enabling happening automatically somewhat surprising (I don't think that's true for asm! blocks in tf functions today, but I could be wrong).

The main question IMO is to figure out how a naked function taking i.e. a __m256i as an argument should behave -- today such a function without an appropriate #[target_feature(enable = "...")] attribute will trigger the ABI checks that are supposed to prevent ABI issues, and if the attribute is not present the function will be considered fine to call in a context without the required features, which is likely to introduce UB due to ABI mismatch.

Because of this, I would propose that we allow target_feature on naked functions, but without it having any effect beyond influencing safety checks.

@alexcrichton
Copy link
Member

@folkertdev ah that all makes sense, I was unaware of this assembler behavior for wasm! My guess is that given that it's a relatively new assembler there just isn't support for dynamically enabling/disabling features like there is on other platforms. Regardless your proposed route sounds quite reasonable to me 👍

@traviscross
Copy link
Contributor

traviscross commented Mar 8, 2025

@rfcbot concern maybe-unsafe-attribute

When we accepted the relevant RFC, we hadn't yet done unsafe attributes. Looking at it now, I wonder if it shouldn't be #[unsafe(naked)]. It seems a bit strange for it not to be, given what it does. As the Reference PR says:

The naked attribute prevents the compiler from emitting a function prologue and epilogue for the attributed function.

That sounds rather unsafe!

Of course, we could argue that, "well, you can't actually create unsafety without the inner unsafe { .. }", but still.

More radically, we could ask whether maybe naked_asm! should not need to be wrapped in unsafe { .. } at all because we could consider the obligation discharged by the #[unsafe(naked)]. That's almost appealing in a way, since this is the only thing that's allowed (and required) in the body, and this macro call can only appear in such bodies, and it would have somewhat better ergonomics by avoiding an additional kind-of-useless level of indentation. E.g.:

#[unsafe(naked)]
#[unsafe(no_mangle)]
pub extern "sysv64" fn f() -> ! {
    core::arch::naked_asm!(
        "ud2",
    )
}

What do we think?

Copy link
Member

@jieyouxu jieyouxu Mar 8, 2025

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)

Copy link
Contributor Author

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

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).

Copy link
Contributor

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.

@traviscross traviscross assigned traviscross and unassigned tmandry Mar 8, 2025
@veluca93
Copy link
Contributor

veluca93 commented Mar 8, 2025

@rfcbot concern maybe-unsafe-attribute

When we accepted the relevant RFC, we hadn't yet done unsafe attributes. Looking at it now, I wonder if it shouldn't be #[unsafe(naked)]. It seems a bit strange for it not to be, given what it does. As the Reference PR says:

The naked attribute prevents the compiler from emitting a function prologue and epilogue for the attributed function.

That sounds rather unsafe!

Of course, we could argue that, "well, you can't actually create unsafety without the inner unsafe { .. }", but still.

More radically, we could ask whether maybe naked_asm! should not need to be wrapped in unsafe { .. } at all because we could consider the obligation discharged by the #[unsafe(naked)]. That's almost appealing in a way, since this is the only thing that's allowed in the body, and this macro call can only appear in such bodies, and it would have somewhat better ergonomics by avoiding an additional kind-of-useless level of indentation. E.g.:

#[unsafe(naked)]
#[unsafe(no_mangle)]
pub extern "sysv64" fn f() -> ! {
    core::arch::naked_asm!(
        "ud2",
    )
}

What do we think?

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...

@RalfJung
Copy link
Member

RalfJung commented Mar 8, 2025

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?

@traviscross
Copy link
Contributor

traviscross commented Mar 8, 2025

@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)

@bjorn3
Copy link
Member

bjorn3 commented Mar 8, 2025

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?

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.

@veluca93
Copy link
Contributor

veluca93 commented Mar 8, 2025

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?

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 naked_asm block?

@folkertdev
Copy link
Contributor Author

Isn't that something that can be toggled within a naked_asm block?

In most cases yes, using assembler directives. Those depend on the architecture. E.g.

  • x86_64 just accepts anything anywhere
  • aarch64 has e.g. .arch_extension neon and .arch_extension noneon
  • riscv has .option push and .option pop and then .option +somefeature
  • wasm currently has nothing
  • arm is a mess with fpu, arch, cpu and arch_extension interacting in a bunch of ways and no clean way of getting back to the original state

To me, the appeal of #[naked] over just some global assembly is that it looks and feels just like a normal function. Target features currently break the illusion by emitting unexpected errors.

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) #[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.

So I think that leaves 2 plausible options

  1. allow #[target_feature], but only have it influence ABI (no assembler directives are emitted)
  2. keep #[target_feature] on naked functions unstable
    • we could have an allowlist for target features and architectures where we can guarantee correct behavior. This list could at least cover the common vector ABI cases.
    • there could be a custom error pointing to documentation on the right assembler directives for common targets

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 aes is not enabled, but it clearly is in the source!

error: instruction requires: aes
  |
note: instantiated into assembly here
 --> <inline asm>:6:1
  |
6 | aese v0.16b, v1.16b
  | ^

error: aborting due to 1 previous error; 9 warnings emitted

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.

@veluca93
Copy link
Contributor

veluca93 commented Mar 8, 2025

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 naked_asm that it just emits raw ASM, independent of the context (or some variation thereof) -- I'd imagine that naked functions and naked_asm are fairly niche features, and I think I would be OK with users needing to read the documentation quite carefully.

@onestacked
Copy link
Contributor

onestacked commented Mar 8, 2025

Is there any reason for not just moving #[naked] with a #[target_feature] to a separate feature gate and stabilize naked_function without this for now?

@RalfJung
Copy link
Member

RalfJung commented Mar 9, 2025 via email

@folkertdev
Copy link
Contributor Author

catching up to some comments


Of course, we could argue that, "well, you can't actually create unsafety without the inner unsafe { .. }", but still.

Yes that is what I would argue :)

from the edition guide

Rust 1.82 added the ability in all editions to mark certain attributes as unsafe to indicate that they have soundness requirements that must be upheld

There are no soundness requirements to a naked function itself. The requirements are on the naked_asm!, which is already covered by an unsafe block. What additional value does making the attribute itself unsafe have?


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.

Correct, that is tested here:

#[naked]
pub extern "C" fn missing_assembly() {
//~^ ERROR naked functions must contain a single `naked_asm!` invocation


We can map our target feature names to the ones LLVM uses easily, we already do that. Is that not enough?

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 trustzone feature is known in the assembler as sec (for security, I guess). Most targets behave much more reasonably but some clearly don't.


Is there any reason for not just moving #[naked] with a #[target_feature] to a separate feature gate and stabilize naked_function without this for now?

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.

@RalfJung
Copy link
Member

RalfJung commented Mar 9, 2025

LLVM target features are also just made-up names that don't always map to the names that the assembler uses.

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...

Is there any reason for not just moving #[naked] with a #[target_feature] to a separate feature gate and stabilize naked_function without this for now?

Sounds good to me.

@folkertdev
Copy link
Contributor Author

And I guess we can't send the naked function "through" LLVM to let it do the translation?

No that was the original design, but it was rejected for various reasons:

  • LLVM would insert additional instructions in some cases (or at least did not guarantee that it would not)
  • we have non-LLVM backends that would need to handle this logic too

Some further context from the tracking issue:

#90957 (comment)
#90957 (comment)

@RalfJung
Copy link
Member

RalfJung commented Mar 9, 2025

we have non-LLVM backends that would need to handle this logic too

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. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-run-make Area: port run-make Makefiles to rmake.rs disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. I-lang-nominated Nominated for discussion during a lang team meeting. needs-fcp This change is insta-stable, so needs a completed FCP to proceed. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-waiting-on-documentation Status: Waiting on approved PRs to documentation before merging S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.