-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Constrained Naked Functions #2972
Conversation
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.
Just a few minor nits but otherwise great work!
text/0000-constrained-naked.md
Outdated
|
||
# Custom Calling Convention | ||
|
||
In order to expand the usefulness of naked functions, this document also define a new calling convention, `extern "custom"`, that can be used only with naked functions. Functions with `extern "custom"` are not callable from Rust and must be marked as`unsafe`. The function definition must contain no arguments or return type. For example: |
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.
I don't see why we should require such functions to be unsafe
. They can't be called by Rust code at all so it shouldn't matter whether they are safe or unsafe.
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.
I was originally going to say the same thing, but it actually somehow makes sense: these functions always have a caller requirement (using the right calling convention), so it's kind of logical that passing around extern "custom" fn()
pointers should be forbidden to force the use of unsafe extern "custom" fn()
.
This being said, it is totally possible to imagine a custom calling convention whereby eg. the argument is passed in a global variable and all bitpatterns are valid, so any call would actually be valid, and thus it wouldn't require unsafe
.
So, with all the above considered, I also believe that it'd be best to remove this requirement, that AFAIU doesn't bring much additional value.
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.
I added that merely to be defensive, though I had the same thought. I'm curious what others think about this.
text/0000-constrained-naked.md
Outdated
|
||
# Custom Calling Convention | ||
|
||
In order to expand the usefulness of naked functions, this document also define a new calling convention, `extern "custom"`, that can be used only with naked functions. Functions with `extern "custom"` are not callable from Rust and must be marked as`unsafe`. The function definition must contain no arguments or return type. For example: |
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.
To me,
that can be used only with naked functions
means that you can't declare an extern "custom"
function that is defined in a different language. I assume you intended for this to be allowed, and maybe the wording should change.
As for the unsafe
issue, it occurs to me that extern "custom"
cheats the type system a bit. It's possible and even likely for multiple extern "custom"
functions that appear identically typed to Rust to have different custom calling conventions. This could be awkwardly addressed by something like extern "custom/x86_idt"
and extern "custom/x86_syscall"
, or it could be addressed by encouraging people who pass around pointers to extern "custom"
functions to wrap them in more descriptive structs. To that end, perhaps unsafe
is the right choice.
As another way of looking at this, I can write a safe function (safe to call and implemented without unsafe
) that takes a pointer to a function with a known calling convention and calls it. The called function may itself be problematic, but one might argue that this is a problem with the callee. But I can't write a function that is safe to call and takes an extern "custom"
pointer because nothing verifies that the passed pointer is the correct type of custom.
(As an aside, it's not quite clear to me what "memory safety" means in the context of an operating system. Dereferencing a wild pointer is obviously memory unsafe, but what about mis-programming the CPU or associated hardware in a way that causes crashes? I suppose this boils down to the preference of whomever designs the OS.)
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.
Yes, I think we should definitely think about the calling convention as a "function type." This is, I think, behind my intuition that these should be marked as unsafe
. Basically, extern "custom"
is a form of type erasure.
I like the idea of a custom
prefix which allows you to declare your own name. This sort of adds type safety. Unless there's a collision, of course.
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.
I like the idea of a
custom
prefix which allows you to declare your own name. This sort of adds type safety. Unless there's a collision, of course.
What about going all out and having non-string ABIs -- similar to generics in that the ABI is a type parameter to the generic class of functions. extern "The-ABI"
would be syntax sugar for extern<core::abi::The_ABI>
where The-ABI
is translated by replacing all -
with _
. So extern "system-unwind"
would be syntax sugar for extern<core::abi::system_unwind>
.
// declare a custom ABI
#[derive(ExternABI)]
struct MyABI;
// use a custom ABI
fn f(fn_ptr: unsafe extern<MyABI> fn()) {}
fn generic_over_abi<ABI: ExternABI>(fn_ptr: unsafe extern<ABI> fn()) {}
// both the same type
type ExternCFnPtr = unsafe extern<core::abi::C> fn();
type ExternCFnPtr2 = unsafe extern "C" fn();
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.
I feel that this is over-extending the scope of the RFC. Simple newtype wrappers around an extern "custom"
function pointer are enough to build type-safe abstractions, there is no need to add complexity to the language for such a niche feature.
Regarding unsafe
, it really doesn't make a difference in practice since the only difference between a fn
and unsafe fn
is that the latter can only be called from within an unsafe
block. This doesn't apply to extern "custom"
since it can't be called from Rust at all.
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.
I feel that this is over-extending the scope of the RFC. Simple newtype wrappers around an
extern "custom"
function pointer are enough to build type-safe abstractions, there is no need to add complexity to the language for such a niche feature.
Ok, though my proposal is also useful for writing code that is generic over the ABI, which is useful when trying to handle generic fn
types. This is more related to the RFC for adding a trait that is implemented by all fn
types, which I can't find at the moment.
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.
@Amanieu To me, there is some difference in meaning between having the function unsafe
or not. If I see:
extern "C" { fn foo(f: extern "custom" fn()); }
, then I expect any non-unsafeextern "custom"
function to be possible to put there without memory safety impact — meaning that non-unsafeextern "custom"
functions are necessarily functions that never return and that don't assume anything about the caller, to make it possible to have such a signatureextern "C" { fn foo(f: unsafe extern "custom" fn()); }
, then I expectfoo
to runf
in some kind of virtual machine to protect itself against misuse, as it cannot know for sure what ABI to callf
inextern "C" { unsafe fn foo(f: [unsafe] extern "custom" fn()); }
, then I readfoo
's documentation on what kinds ofextern "custom"
functions it accepts
So I do believe that there is a semantic difference between unsafe
and non-unsafe
extern "custom"
functions, but the non-unsafe extern "custom"
functions are restricted to the set of functions that never return and don't assume anything from their caller (apart from what all rust programs assume by default, eg. “there is a stack and it doesn't overlap with the heap”)
I'd be interested in other views of which extern "custom"
functions should or should not be unsafe
, that said
I am preparing an update which addresses the feedback thus far. Most of the feedback has been straightforward. However, most of the unresolved discussion has centered around defining custom calling conventions. In the spirit of moving things forward, I propose that we move custom calling conventions to the future possibilities section as an idea for future improvement. Constrained naked functions are useful without custom calling conventions. Further, dealing with custom calling conventions separately eases the path to acceptance and stabilization for constrained naked functions. |
An update on implementation of this RFC is in order. I fixed a case where The remaining tasks basically fall to warnings and the generation of the implicit We also still need to resolve the question of implicit |
|
Is this RFC actually useful with the |
To me, yes, very. It means we can stabilize naked sooner rather than later. Custom calling conventions can go through their own design process and added to naked without disruption. |
I’m trying to understand how one might create a |
https://github.com/enarx/frenetic/blob/master/src/arch/x86_64/sysv.rs#L30-L50 While it might be true that the uses which strictly speaking require One important point is that I'm working on this because I need |
Resolved in the latest commit. |
I would like to use it for the |
In my case, I'd like to use it for a low-level context switching function (saving and restoring register states) in my kernel. My current implementation doesn't use naked function (because naked functions have historically been too buggy to use), and takes a slight performance penalty as a result (the compiler inserts a useless prelude). I have no need for It's generally possible to replace naked functions with an |
I can see how this type of context switching would make sense in a |
This would need Also, I find it surprising that you are |
|
Yes, I guess so.
yeah, maybe I'll change that.
Thanks for the heads-up.
Yeah, that was the intention. When we will do performance optimizations, we will turn on |
Validate naked functions definitions Validate that naked functions are defined in terms of a single inline assembly block that uses only `const` and `sym` operands and has `noreturn` option. Implemented as future incompatibility lint with intention to migrate it into hard error. When it becomes a hard error it will ensure that naked functions are either unsafe or contain an unsafe block around the inline assembly. It will guarantee that naked functions do not reference functions parameters (obsoleting part of existing checks from rust-lang#79411). It will limit the definitions of naked functions to what can be reliably supported. It will also reject naked functions implemented using legacy LLVM style assembly since it cannot satisfy those conditions. rust-lang/rfcs#2774 rust-lang/rfcs#2972
Could we extend this to allow multiple #[naked]
pub unsafe extern "C" fn start64() {
#[cfg(feature = "init_data")]
asm!(
"movabs rsi, offset __init_data",
"movabs rdi, offset __start_data",
"movabs rcx, offset __stop_data",
"sub rcx, rdi",
"rep movsb [rdi], [rsi]",
);
#[cfg(feature = "zero_bss")]
asm!(
"movabs rdi, offset __start_bss",
"movabs rcx, offset __stop_bss",
"sub rcx, rdi",
"xor eax, eax",
"rep stosb [rdi], al",
);
asm!(
"movabs rsp, offset __rust_stack_top",
"movabs rax, offset __rust_start",
"jmp rax",
options(noreturn)
)
} which seems to meet all the requirements we want to have (only |
@josephlr this code block doesn't meet this requirement:
As if one of the feature is enabled, there are multiple asm statements. |
Exactly. That's why I was wondering about the feasibility of (now or as a future extension) allowing multiple Otherwise, you would need weird macros to get the above code to compile without warnings. |
I've been using #[cfg(feature = "zero_bss")]
macro_rules! zero_bss {
() => {
r#"
movabs rdi, offset __start_bss
movabs rcx, offset __stop_bss
sub rcx, rdi
xor eax, eax
rep stosb [rdi], al
"#
}
}
#[cfg(not(feature = "zero_bss"))]
macro_rules! zero_bss {
() => { "" }
}
asm!(
zero_bss!(),
"movabs rsp, offset __rust_stack_top",
"movabs rax, offset __rust_start",
"jmp rax",
options(noreturn)
); |
I'd like to propose one small modification to this RFC for consideration. Currently, the RFC defines that the constrained naked functions "must not specify the I'd like to change the wording instead to define that constrained naked functions "must additionally define the Thoughts? |
@npmccallum That sounds reasonable. In general, I think "requires specifying" seems preferable to "implies" unless we're absolutely certain that it'll never ever make sense to do otherwise. And I can imagine wanting to inline a naked function that follows the calling convention. |
Validate FFI-safety warnings on naked functions Test that FFI-safety warnings don't get accidentally dropped on naked functions. The big picture is that if you implement a naked function with the Rust ABI you'll get a warning. Further, if you implement a naked function with a standardized ABI, but use non-FFI-safe types you will still get a warning. rust-lang/rfcs#2774 rust-lang/rfcs#2972 cc `````@joshtriplett````` `````@Amanieu````` `````@haraldh`````
Validate FFI-safety warnings on naked functions Test that FFI-safety warnings don't get accidentally dropped on naked functions. The big picture is that if you implement a naked function with the Rust ABI you'll get a warning. Further, if you implement a naked function with a standardized ABI, but use non-FFI-safe types you will still get a warning. rust-lang/rfcs#2774 rust-lang/rfcs#2972 cc ``````@joshtriplett`````` ``````@Amanieu`````` ``````@haraldh``````
Move naked function ABI check to its own lint This check was previously categorized under the lint named `UNSUPPORTED_NAKED_FUNCTIONS`. That lint is future incompatible and will be turned into an error in a future release. However, as defined in the Constrained Naked Functions RFC, this check should only be a warning. This is because it is possible for a naked function to be implemented in such a way that it does not break even the undefined ABI. For example, a `jmp` to a `const`. Therefore, this patch defines a new lint named `UNDEFINED_NAKED_FUNCTION_ABI` which contains just this single check. Unlike `UNSUPPORTED_NAKED_FUNCTIONS`, `UNDEFINED_NAKED_FUNCTION_ABI` will not be converted to an error in the future. rust-lang/rfcs#2774 rust-lang/rfcs#2972
This seems reasonable, it doesn't have any apparent outstanding issues, and it's actually implemented in nightly. Thus, I would propose we accept this RFC @rfcbot merge |
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. 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! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
This is a truly excellent RFC. I have wanted this feature for a long time, and this RFC resolves a lot of open discussions and questions in a simple and flexible way. 🎉 |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
Sorry, I'm a little late to the party, but there's a concern that I just raised in the Tracking issue: currently, naked functions have an extra
See this comment. To workaround this, we may want to ensure naked functions are additionally marked nounwind. I don't think I've ever seen a naked function that called an unwinding function, and if it did, I'd expect it to deal with the unwinding itself, through assembler directives (that's rather the point of naked functions - they are supposed to deal with everything). I'm not sure if this would be enough to allow naked functions to guarantee to only generated the exact asm block (and have no suffix instructions) though. |
If @roblabla 's suggestion actually allows us to discard the trailing I propose that we accept the RFC as it stands and explore this option as an additional requirement after the RFC lands. If we do this, we could simply enforce this with a test. This would delay the stabilization of naked functions, but not necessarily this RFC. Thoughts? |
Co-authored-by: Andrea Canciani <ranma42@gmail.com>
Huzzah! The @rust-lang/lang team has decided to accept this RFC. Tracking issue: rust-lang/rust#90957 If you'd like to keep following the development of this feature, please subscribe to that issue, thanks! :) |
Rendered
Summary
This is a quick attempt at improving the usefulness of naked functions by constraining their use.
Tasks
extern "Rust"
- WARNconst
orsym
- WARNoptions(noreturn)
- WARNatt_syntax
are used - WARN and WARNasm!()
CC
rust-lang/rust#32408
rust-lang/rust#72016
#2774
@amluto @comex @programmerjake @Amanieu @haraldh @bjorn3 @joshtriplett