-
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
Add support for naked functions. #1201
Conversation
👍 This would allow using only Rust + inline assembly in a kernel. |
How does this interact with the calling convention? I recently looked into writing some inline assembly, using If all this does is let us replace the contents of an I'm inclined to suggest that |
This doesn't seem like a terribly good idea, LLVM lacked this for a very long time due to the undefined and extremely iffy semantics involved, essentially the only thing that can safely be done in a naked function is using a single asm block. LLVM already has had support for module-level assembly blocks as long as I can remember. Although I wouldn't let any remotely complicated hand-written code get within 50 feet of LLVMs built-in assembler either (it tends to be somewhat broken) |
I think you're right that anything but parameterless functions returning
Without Rust support though, that doesn't get us very far. The advantage of putting this directly on functions is that we allow the compiler to generate a function definition, including name mangling. Despite the much greater implementation complexity, I'm coming around to the |
@tari: My point wasn't "you can't write naked functions that take arguments", it was "you can't write naked functions that take arguments using the undefined I think the good solution is to make sure that naked functions aren't "desugared" into
|
Either way, the compiler could provide name mangling for module-level assembly blocks, which would be much saner. |
Isn't name mangling seperate from the ABI already in rust, ie. One problem with For this reason, I think it would be better to leave Finally, there may be cases where the naked function doesn't follow any standard ABI, in which case it shouldn't be callable at all from rust code. This "unspecified" ABI could be selected via |
The trouble with that approach I see is that it becomes impossible to safely call such a naked function from Rust. We could choose to forbid native rust calls to such a function, but that seems to me like an unusual exception to the rule that all declared functions are callable.
Right; I started conflating the goal and means a bit here. Clarifying: my ultimate goal is to support writing interrupt handlers in Rust-only. The proposed means to that is introducing naked functions which allow the programmer to use an arbitrary calling convention. As Changing this approach would have the implication that some calls to functions defined in Rust become wrong because they assume a different calling convention- a hypothetical Given the previous points, the simplest approach to supporting arbitrary ABIs appears to be implementing them in the compiler. This is much less flexible than supporting naked functions, but permits more efficient code generation since the compiler has detailed knowledge of what the system is doing. For the motivating use case this could be Addition of calling conventions doesn't preclude a generic mechanism for naked functions, but it does sidestep the current issues involved in making naked functions useful. |
A naked function is no less safe to call than any other non-local I get the feeling that we're starting to talk across purposes, here, so let's clarify. I'm talking about something like: #[cfg(target_arch="x86")]
extern "C" fn add_two(a: i32, b: i32) -> i32 {
#![naked]
asm!(r#"
mov eax, [esp+4]
mov ecx, [esp+8]
add eax, ecx
"#)
}
fn main() {
assert_eq!(add_two(4, 6), 10);
} Unless I've got the assembler wrong (it's been a while), the above should be perfectly safe to call. It's a function that uses the C ABI... that it's written in assembler and doesn't have the standard function prelude is just an implementation detail the user doesn't need to worry about. I mean, |
The difference is that the only reason to use a naked function is when the compiler does not understand the ABI you need to use and is thus virtually guaranteed to generate code that will do something unexpected (and wrong) if you try to call it. You can implement a naked function that uses the C ABI, but there's no reason to do so because the compiler already understands how to deal with that for you. |
@tari That's simply not true - there are many reasons to require a naked function even when using an ABI that the compiler understands. |
Can you provide some examples of situations you'd want to do so? The way I see it, naked functions are useful to fill holes in the compiler's ABI support and I don't see the point of using one when the compiler is capable of doing what you need. |
Sometimes you can implement a method more efficiently than the compiler's default prolog/epilog would allow. For example, I needed to write a function which returned the address of the caller. With a naked function, this boils down to two instructions: mov eax, [ebp+4]
ret There's just no way to generate code like that without getting rid of the default prolog and epilog. |
@Diggsey While specific stack layout (e.g. caller address being at #![feature(asm)]
pub fn myfunc() -> u32 {
let ret: u32;
unsafe { asm!("mov $0, [ebp+4]" : "=r"(ret) ::: "intel"); }
return ret;
}
// yes, I'm compiling this on x86_64, so it won't ever work like this anyways but it really doesn't matter for this example
And there you go. Exactly what you wanted, without a naked function. |
It seems we have two somewhat distinct use cases for naked functions, where I only had the first in mind while writing this RFC:
Neither of these works well with the current proposal, because the ABI of function calls generated by the compiler is unspecified. For a compiler-generated call to such a function, we should require that the ABI in use be well-defined (specifically it must not use the Rust ABI, and we may want to restrict it to If the calling convention of a naked function which is called from Rust code is constrained to be well-defined, the problems are purely in implementation- @main-- that may be technically possible, but it's too fragile for me to be comfortable encouraging such a thing. We can make the static guarantees of such a function stronger by allowing it to be declared as naked without affecting the rest of the language, so I believe we should. |
I agree that especially for things like interrupt handlers, naked functions are simply a necessity. Of course one could try and hack around that by calling into an embedded inline asm block, but that's far from perfect. I do have doubts regarding ABI emulation (providing functions in a specific ABI without compiler support): Every function needs its own trampoline, right? So one would probably write a macro to create all those trampolines which ... surprisingly isn't even that messy, but still less pretty than it could be ( When calling a method, we need to know how. In case of a "standard" naked function, this gets us into a funny situation: Just like with any normal function, we're supposed to use the Rust ABI which is fine for us - but at the same time we're sure that they (callee) can never handle this correctly due to that ABI being undefined. I'd suggest making those functions uncallable and forcing them to be parameterless. On the other hand, when a naked function's ABI has a well-known definition (like The other difficult part are local variables. Should you write "heavy" rust code within naked functions? Probably not. But it should definitely be possible within certain limits. Why else would you use Rust if you could just write your function in plain asm (much better tooling than inline asm)? Straight up banning locals is not a great solution, but since the compiler can't really reserve stack space either (naked --> no prolog) something like MSVC's __LOCAL_SIZE is probably inevitable. The really ugly part about that particular approach is that the compiler is forced to emit even useless prologs and epilogs (when no stack spilling is required). So one can either sacrifice performance by having them or sacrifice non-optimized builds altogether since rustc (like many compilers) likes to just hold everything on the stack and then rely heavily on the optimizer to eliminate these useless stack operations, simply resulting in broken code without that. |
Sorry for this, but... is this a problem that needs solving? I do a lot of systems programming, and do my best to avoid inline assembly where I can. Usually, what I'll do instead (especially for something like an interrupt handler), is write an assembly file and have it call out to a C-ABI routine to get a trampoline to more maintainable code. I don't yet see how |
@aidancully arbitrary codegen driven by Rust code, basically. static HANDLERS: [AtomicPtr<()>; 256] = [AtomicPtr::new(); 256];
#[naked]
unsafe fn handle_interrupt<F: Fn()>(number: u8) {
(*(HANDLERS[number as usize].load(Relaxed) as *const F))();
asm!("iret");
} |
Are there links to LLVM discussion on this topic? |
I haven't found any meaningful LLVM discussion on it. Support was added in 2009, r76198, which may be a good starting point for searching. |
Also expand motivation beyond ISRs per RFC discussion.
@eddyb That code is most certainly broken. If the compiler ever spills a register, you're fucked. Since it's also an interrupt handler, it should avoid clobbering registers, which is impossible to do efficiently in any way that will please LLVM. In general the only way you're ever going to get the correct output is by having a single asm block within the function, which is just a hacky version of module-level assembly. |
While the RFC currently doesn't address this issue it's not a problem with naked functions in general as solving it is possible (and probably necessary; see my previous comment). |
@eddyb For what it's worth, I find that type of use plausible as a motivator, but I also worry that it might be very challenging to make static HANDLERS: [AtomicPtr<()>; 256] = [AtomicPtr::new(); 256];
#[naked]
unsafe fn handle_interrupt<F: Fn()>(number: u8)
{
// F::ABI's static functions are #[naked] #[inline].
F::ABI::create_calling_env();
(*(HANDLERS[number as usize].load(Relaxed) as *const F))();
F::ABI::destroy_calling_env();
asm!("iret");
} But if this sort of thing is necessary to safely support generic naked functions, it'd probably need to be reflected in the RFC. (Note that I don't think the whole change would need to land at once, the initial patch sounds pretty small and may be acceptable in the short term. But the design space in which the feature would be used probably needs to be explored better... I think there's a significant risk that, even though |
@aidancully if you don't generate multiple monomorphizations, you need to use indirect calls, which might be a measurable cost during interrupt handling - but it is a micro-optimization and I could very well be wrong about it. |
If I understand the current tack of the discussion now, the main concern is that the effect of writing code that is not inline assembly in a naked function is unpredictable. The correctness of the A way to ask the compiler how much space it needs for locals would permit a solution, but there's no mechanism to enable that in LLVM. I see a way forward in having an LLVM intrinsic function that is guaranteed to lower to a constant load representing the necessary size of locals, but I think such a significant change is beyond the scope of this proposal. With the |
Both cases could be accommodated by putting support for Rust statements in naked functions behind a feature gate; something like |
In a recent lang subteam meeting, we decided to close this PR. This RFC seems to have a lot in common with #55, which was closed by @brson with the following rationale:
The situation seems not to have changed very much -- basically it seems a bit premature to be introducing these sorts of constructs. The concerns about running Rust code in naked functions seem particularly salient. (Taking off my lang subteam hat for a moment and speaking personally, I think I would prefer to address this by writing naked functions in pure assembly and linking them in with LTO -- or having a variant on inline assembly that generates an entire function.) |
I've only worked with interrupts in microcontrollers, but why can't you have the interrupt just use the existing stack? It is the nature of interrupts that they have to return before anything else can run. (this is how it works on a microcontroller) |
I've added an RFC (#1548) for module-level inline assembly. I think this feature can replace the need for naked functions in many cases. |
@Amanieu - That sounds like a much better idea. I've been pretty hesitant on naked functions, because they're either unknown magic, or exceptionally limited. All you get over module-level inline assembly is free naming and symbol size (assuming the pure-bare version of naked functions). |
As I touched on in #1201 (comment), I personally feel like this is actually the main advantage of the RFC. You should be able to simulate both module-level asm and asm-only naked functions with a macro, so mostly I see this as an ergonomics discussion. Using rust code in naked functions is something I'm not really missing and don't have as much of an opinion on, but maybe following the llvm/clang/gcc semantics is good enough... I'd also support module-level inline assembly, as it's a rather useful feature, though if you can't use symbols/constants/etc. that's a pretty unfortunate limitation... Both module-level asm and naked functions are just plain things that should exist in Rust IMO. All too often the lower level aspects get neglected despite being branded as a systems language... |
I have to admit the comments on this RFC have kind of lost me. Perhaps this just needs the ELI5 treatment. I started off following the interrupt calling convention RFC and followed the pointer here; I got the impression there were other unrelated problems and naked functions are a more general solution to both domains. Now I'm here, this RFC is in FCP and I still don't really understand why naked functions which can only contain a single block of inline assembly are useful. That's enough to call a proper function, sure, but at that point I might as well just make a small assembly file—I already have a larger assembly file which does the necessary prep work to start running Rust code in the first place, so it's not like I can avoid invoking an assembler at all. By comparison with the Visual C++ example on the OSdev wiki (the only listed compiler which uses naked functions rather than a special naked fn interrupt_handler() {
asm!("push rax");
asm!("push rcx");
// push everything ...
// Rust code to service the interrupt goes here
// pop everything ...
asm!("pop rcx");
asm!("pop rax");
asm!("iret");
} Am I mistaken? |
Note that this isn't really true in all cases, so there's value in being able to avoid bringing in an external assembler entirely! I have an armv7m kernel that only uses asm for interrupt handler trampolines, all inline, and the entry point is pure rust, for example. |
The proposed #[naked] attribute indicates that the specified function does not need prologue/epilogue sequences generated in the compilation process. It is up to the programmer to provide these sequences. In basic terms, this makes rustc capable of doing all the low-level stuff which C is able to. Such as:
All in Rust, without needing external files. This is especially important when dealing with low-level stuff, such as OS development. We for example need this in Redox (an OS written in pure Rust) to get x86 support working. So this is really an important feature to us. An example for the curious would be a custom interrupt handler function:
This function will currently generate code as:
(truncated for simplicity) With the #[naked] attribute (which is implemented with PR #29189), it will bypass the prologue and epilogue sequences. And the output will be like:
Namely, it is a way of bypassing the automatically generated sequences, which pop/push to the stack. |
Given that rust is meant to be a system level programming language, I think it is needed to have this feature (or a similar on) in rust. Through it is needed for low level tasks for many people given that this feature is not needed or even bad style in less low level code I would propose that (if available in stable) there is per default lint like Also even through module level asm seems to* be able to emulate naked fn's I think naked fn's are much clearer for use cases like interrupts. (* I might have missed something, but naked fn's can contains (some) "normal" rust code, but module level asm can't, or?) |
@Naicode Naked functions have to be assembly-only. |
Ok, so module level asm and naked functions most likely can emulate each other. Hm, I't might be able to implement platform-specific interrupt handling by combining macros with naked functions, leaving naked functions still there for the other use cases. |
@Naicode You would want to implement the whole Rust language in macros? |
@eddyb No! that would be horribly ;=) What I meant, is that, if the naked fn's can only contain asm, you would either write the complete ISR in assembly or delegate to a rust-fn at some point. This boiler part could be be written as a macro... e.g. the example from the rfc: #[naked]
#[cfg(target_arch="x86")]
unsafe fn isr_3() {
asm!("pushad
call increment_breakpoint_count
popad
iretd" :::: "volatile");
intrinsics::unreachable();
} could be written like: delegate_isr_3!(increment_breakpoint_count) Through in the end having a rust native ISR ABI might be more performant/useful Also you could pass a codeblock to a macro, but it probably wouldn't be a good idea to use it without wrapping it in a function because it might contain a return statement (and possible other "bad" thinks), but you could also have a way to forbid return statements in code block passed to macro, well that is a different topic. |
Huzzah! The @rust-lang/lang team has decided to accept this RFC. As previously stated, this is being accepted on a particularly experimental basis, especially with respect to the semantics (or lack thereof) of Rust code embedded in a naked function. One plausible route for stability would be to stabilize the |
Tracking issue rust-lang/rust#32408. |
Thanks, thanks, thanks, @rust-lang/lang. |
PR submitted: rust-lang/rust#32410 |
Add support for naked functions See rust-lang/rfcs#1201 (comment) This PR adds `#[naked]` for marking naked functions.
Rendered.
[edited to link to the final rendered version]