-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
calling convention for MSP430 interrupts #38465
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
This should probably be feature gated as well - https://github.com/rust-lang/rust/blob/master/src/libsyntax/feature_gate.rs#L963 (+ the |
I was wondering if we had feature gates for new ABIs. Thanks for the heads up, @petrochenkov. I'll add the gate in a bit. |
Yeah we'll definitely want to gate this, but otherwise seems ok to me. In that sense that name shouldn't matter too much for now. |
I have tested this PR, and it works fine. This rust code: #[no_mangle]
#[link_section = "__interrupt_vector_10"]
pub static TIM0_VECTOR: unsafe extern "msp430-interrupt" fn() = tim0;
unsafe extern "msp430-interrupt" fn tim0() {
P1OUT.write(0x00);
} Generates this assembly:
It looks good to me. |
@pftbest Excellent r? @alexcrichton Added the feature gate. Should I create a tracking issue for the stability of the new ABI? |
Looks like the travis failure is legitimate, and could you also add a test to ensure this ABI is gated? And yeah let's add a tracking issue. |
(ABI stability tests are centralized in |
@alexcrichton Updated. Still testing locally. Thanks for the info, @petrochenkov! |
Okay, so if my memory doesn’t mislead me, we added |
Before even considering using this ABI as an argument for stabilizing naked functions, we should first verify that this particular ABI can actually be implemented using naked functions (I don't know the answer). If that's the case, this unstable ABI could be removed in favor of that (but ergonomics should be considered before reaching a decision). Personally, I'd prefer to land a sure and ergonomic (but unstable) way to write interrupt handlers for this architecture, this ABI, right now and then consider whether such ABI should be stabilized or removed/deprecated in favor of (stable) naked functions. |
TBH I would prefer an |
@nagisa unsafe extern "msp430-interrupt" fn tim0() {
not_inlined_foo();
} I can write it using naked functions like this: #[naked]
unsafe extern "C" fn tim1() {
asm! {
"
push r15
push r14
push r13
push r12
"
}
not_inlined_foo();
asm! {
"
pop r12
pop r13
pop r14
pop r15
reti
"
}
} It does not look pretty but at least it works the same. unsafe extern "msp430-interrupt" fn tim0() {
P1OUT.write(0x00);
} This code I can't write as a naked function, because I don't know how many registers I need to save for this inlined Also we would like to have support for the |
☔ The latest upstream changes (presumably #38099) made this pull request unmergeable. Please resolve the merge conflicts. |
8f1bc9e
to
2de9578
Compare
rebased @eddyb an "interrupt" ABI makes sense to me. I'd like to have at least one more interrupt-ish ABI (e.g. "avr-interrupt") before prototyping that; that way we can test in properly. @pftbest those are my concerns exactly: ergonomics and error-proneness. In theory, it might be possible to create a syntax extension that implements "msp430-interrupt" on top of #[naked] but I don't know if that could have all the features that a real ABI enables (cf. the __bic_SR_register_on_exit intrinsic). |
The C ABI for your platform should have somewhere a list of caller-saved (volatile) and callee-saved (non-volatile) registers. The saving and restoring as well as call overhead are unfortunate, though and this is the reason why an interrupt ABI is strictly better than naked functions. That being said, I would still rather we only had either one (naked fns) or the other (interrupt ABIs). The issue with 2nd thing is that LLVM obviously does not support your arbitrary hardware interrupt ABI, which is why |
I was talking about it in my examples. If the code contains only call to non inlined function like in the first example, it is enough to save only caller-saved registers (r12-r15). Some architectures do not have special ABI for interrupts, so they use naked functions. But for MSP430 it is available, and it would be unfortunate not being able to use it. |
Just to give a perspective on the overhead of using naked functions, I did a caculation. If the code is using proposed ABI it will have If the code is using naked functions it will get this numbers: You can say that if you care about this numbers you should be writing in assembly, but I think It would be great if rust could be at least as efficient as C in this regard. |
☔ The latest upstream changes (presumably #38697) made this pull request unmergeable. Please resolve the merge conflicts. |
2de9578
to
e8e5d8d
Compare
☔ The latest upstream changes (presumably #38482) made this pull request unmergeable. Please resolve the merge conflicts. |
e8e5d8d
to
1149545
Compare
How is the C ABI (or any ABI) relevant here? An interrupt handler must save every register it uses, precisely because it is not invoked on a function boundary. |
@whitequark Since we’re talking trampolines here, you can avoid saving the non-volatile (aka. callee-saved) registers because no callee is allowed to modify them without also saving them beforehand and restoring register later, right? The Remember that I’m not arguing against interrupt ABIs. I’m arguing for having EITHER one or the other and not both. Honestly, though, I dislike naked functions and would rather have interrupt ABIs (having seen what mess |
Ah, so what you're suggesting is a naked function that only contains a single call instruction, which calls the actual interrupt handler. I see how this may work. Now, the $1M question: why not define the "interrupt" ABI to generate such a trampoline automatically? |
@nagisa What conflict do you see in having both of them? |
er nvmd, I'll check it for you, you've reviewed |
@japaric I believe this is ready to go after a rebase now. |
r=me w/ rebase |
04a88d6
to
f5c5bf6
Compare
This calling convention is used to define interrup handlers on MSP430 microcontrollers. Usage looks like this: ``` rust #[no_mangle] #[link_section = "__interrupt_vector_10"] pub static TIM0_VECTOR: unsafe extern "msp430-interrupt" fn() = tim0; unsafe extern "msp430-interrupt" fn tim0() { P1OUT.write(0x00); } ``` which generates the following assembly: ``` asm Disassembly of section __interrupt_vector_10: 0000fff2 <TIM0_VECTOR>: fff2: 10 c0 interrupt service routine at 0xc010 Disassembly of section .text: 0000c010 <_ZN3msp4tim017h3193b957fd6a4fd4E>: c010: c2 43 21 00 mov.b #0, &0x0021 ;r3 As==00 c014: 00 13 reti ... ```
f5c5bf6
to
6296d52
Compare
@bors r=eddyb
|
📌 Commit 6296d52 has been approved by |
⌛ Testing commit 6296d52 with merge d7c4093... |
💔 Test failed - status-travis |
This failed because you have no test for this feature gate (it's a new requirement). The very helpful error message by @est31 explains what you have to do to fix it =)
|
Thanks, @nikomatsakis. I had done that (the cfail test) for another PR but hadn't realize that it had to be done for this one too. @bors r=eddyb |
📌 Commit e928c75 has been approved by |
calling convention for MSP430 interrupts This calling convention is used to define interrup handlers on MSP430 microcontrollers. Usage looks like this: ``` rust #[no_mangle] #[link_section = "__interrupt_vector_10"] pub static TIM0_VECTOR: unsafe extern "msp430-interrupt" fn() = tim0; unsafe extern "msp430-interrupt" fn tim0() { P1OUT.write(0x00); } ``` which generates the following assembly: ``` asm Disassembly of section __interrupt_vector_10: 0000fff2 <TIM0_VECTOR>: fff2: 10 c0 interrupt service routine at 0xc010 Disassembly of section .text: 0000c010 <_ZN3msp4tim017h3193b957fd6a4fd4E>: c010: c2 43 21 00 mov.b #0, &0x0021 ;r3 As==00 c014: 00 13 reti ... ```
☀️ Test successful - status-appveyor, status-travis |
…ention, r=nagisa Add support for the x86-interrupt calling convention This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit x86 targets. The compiler then uses `iret` instead of `ret` for returning and ensures that all registers are restored to their original values. Usage: ```rust extern "x86-interrupt" fn handler(stack_frame: &ExceptionStackFrame) {…} ``` for interrupts and exceptions without error code and ```rust extern "x86-interrupt" fn handler_with_err_code(stack_frame: &ExceptionStackFrame, error_code: u64) {…} ``` for exceptions that push an error code (e.g., page faults or general protection faults). The programmer must ensure that the correct version is used for each interrupt. For more details see the [LLVM PR][1] and the corresponding [proposal][2]. [1]: https://reviews.llvm.org/D15567 [2]: http://lists.llvm.org/pipermail/cfe-dev/2015-September/045171.html It is also possible to implement interrupt handlers on x86 through [naked functions](https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md). In fact, almost all existing Rust OS projects for x86 use naked functions for this, including [Redox](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147), [IntermezzOS](https://github.com/intermezzOS/kernel/blob/f959cc18c78b1ba153f3ff7039d9ecc07f397628/interrupts/src/lib.rs#L28-L72), and [blog_os](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L49-L64). So support for the `x86-interrupt` calling convention isn't absolutely needed. However, it has a number of benefits to naked functions: - **No inline assembly needed**: [Inline assembly](https://doc.rust-lang.org/book/inline-assembly.html) is highly unstable and dangerous. It's pretty easy to mess things up. Also, it uses an arcane syntax and requires that the programmer knows x86 assembly. - **Higher performance**: A naked wrapper function always saves _all_ registers before calling the Rust function. This isn't needed for a compiler supported calling convention, since the compiler knows which registers are clobbered by the interrupt handler. Thus, only these registers need to be saved and restored. - **Safer interfaces**: We can write a `set_handler` function that takes a `extern "x86-interrupt" fn(&ExceptionStackFrame)` and the compiler ensures that we always use the right function type for all handler functions. This isn't possible with the `#[naked]` attribute. - **More convenient**: Instead of writing [tons of assembly boilerplate](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147) and desperately trying to improve things [through macros](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L17-L92), we can just write [code like this](https://github.com/phil-opp/blog_os/blob/e6a61f9507a4c4fef6fb4e3474bc596391bc97d2/src/interrupts/mod.rs#L85-L89). - **Naked functions are unreliable**: It is allowed to use Rust code inside a naked function, which sometimes works and sometimes not. For example, [calling a function](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L132) through Rust code seems to work fine without function prologue, but [code declaring a variable](https://is.gd/NQYXqE) silently adds a prologue even though the function is naked (look at the generated assembly, there is a `movl` instruction before the `nop`). **Edit**: See the [tracking issue](rust-lang#40180) for an updated list of issues. Unfortunately, the implementation of the `x86-interrupt` calling convention in LLVM has some issues that make it unsuitable for 64-bit kernels at the moment: - LLVM always tries to backup the `xmm` registers on 64-bit platforms even if the target doesn't support SSE. This leads to invalid opcode exceptions whenever an interrupt handler is invoked. I submitted a fix to LLVM in [D29959](https://reviews.llvm.org/D29959). The fix is really small (<10 lines), so maybe we could backport it to [Rust's LLVM fork](https://github.com/rust-lang/llvm)?. **Edit**: The fix was merged to LLVM trunk in [rL295347](https://reviews.llvm.org/rL295347). Backported in rust-lang/llvm#63. - On targets with SSE support, LLVM uses the `movaps` instruction for saving the `xmm` registers, which requires an alignment of 16. For handlers with error codes, however, the stack alignment is only 8, so a alignment exception occurs. This issue is tracked in [bug 26413](https://bugs.llvm.org/show_bug.cgi?id=26413). ~~Unfortunately, I don't know enough about LLVM to fix this.~~ **Edit**: Fix submitted in [D30049](https://reviews.llvm.org/D30049). This PR adds experimental support for this calling convention under the `abi_x86_interrupt` feature gate. The implementation is very similar to rust-lang#38465 and was surprisingly simple :). There is no accepted RFC for this change. In fact, the [RFC for interrupt calling convention](rust-lang/rfcs#1275) from 2015 was closed in favor of naked functions. However, the reactions to the recent [PR](rust-lang#38465) for a MSP430 interrupt calling convention were [in favor of experimental interrupt ABIs](rust-lang#38465 (comment)). - [x] Add compile-fail tests for the feature gate. - [x] Create tracking issue for the `abi_x86_interrupt` feature (and link it in code). **Edit**: Tracking issue: rust-lang#40180 - [x] Backport [rL295347](https://reviews.llvm.org/rL295347) to Rust's LLVM fork. **Edit**: Done in rust-lang/llvm#63 @tari @steveklabnik @jackpot51 @ticki @hawkw @thepowersgang, you might be interested in this.
…ention, r=nagisa Add support for the x86-interrupt calling convention This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit x86 targets. The compiler then uses `iret` instead of `ret` for returning and ensures that all registers are restored to their original values. Usage: ```rust extern "x86-interrupt" fn handler(stack_frame: &ExceptionStackFrame) {…} ``` for interrupts and exceptions without error code and ```rust extern "x86-interrupt" fn handler_with_err_code(stack_frame: &ExceptionStackFrame, error_code: u64) {…} ``` for exceptions that push an error code (e.g., page faults or general protection faults). The programmer must ensure that the correct version is used for each interrupt. For more details see the [LLVM PR][1] and the corresponding [proposal][2]. [1]: https://reviews.llvm.org/D15567 [2]: http://lists.llvm.org/pipermail/cfe-dev/2015-September/045171.html It is also possible to implement interrupt handlers on x86 through [naked functions](https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md). In fact, almost all existing Rust OS projects for x86 use naked functions for this, including [Redox](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147), [IntermezzOS](https://github.com/intermezzOS/kernel/blob/f959cc18c78b1ba153f3ff7039d9ecc07f397628/interrupts/src/lib.rs#L28-L72), and [blog_os](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L49-L64). So support for the `x86-interrupt` calling convention isn't absolutely needed. However, it has a number of benefits to naked functions: - **No inline assembly needed**: [Inline assembly](https://doc.rust-lang.org/book/inline-assembly.html) is highly unstable and dangerous. It's pretty easy to mess things up. Also, it uses an arcane syntax and requires that the programmer knows x86 assembly. - **Higher performance**: A naked wrapper function always saves _all_ registers before calling the Rust function. This isn't needed for a compiler supported calling convention, since the compiler knows which registers are clobbered by the interrupt handler. Thus, only these registers need to be saved and restored. - **Safer interfaces**: We can write a `set_handler` function that takes a `extern "x86-interrupt" fn(&ExceptionStackFrame)` and the compiler ensures that we always use the right function type for all handler functions. This isn't possible with the `#[naked]` attribute. - **More convenient**: Instead of writing [tons of assembly boilerplate](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147) and desperately trying to improve things [through macros](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L17-L92), we can just write [code like this](https://github.com/phil-opp/blog_os/blob/e6a61f9507a4c4fef6fb4e3474bc596391bc97d2/src/interrupts/mod.rs#L85-L89). - **Naked functions are unreliable**: It is allowed to use Rust code inside a naked function, which sometimes works and sometimes not. For example, [calling a function](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L132) through Rust code seems to work fine without function prologue, but [code declaring a variable](https://is.gd/NQYXqE) silently adds a prologue even though the function is naked (look at the generated assembly, there is a `movl` instruction before the `nop`). **Edit**: See the [tracking issue](rust-lang#40180) for an updated list of issues. Unfortunately, the implementation of the `x86-interrupt` calling convention in LLVM has some issues that make it unsuitable for 64-bit kernels at the moment: - LLVM always tries to backup the `xmm` registers on 64-bit platforms even if the target doesn't support SSE. This leads to invalid opcode exceptions whenever an interrupt handler is invoked. I submitted a fix to LLVM in [D29959](https://reviews.llvm.org/D29959). The fix is really small (<10 lines), so maybe we could backport it to [Rust's LLVM fork](https://github.com/rust-lang/llvm)?. **Edit**: The fix was merged to LLVM trunk in [rL295347](https://reviews.llvm.org/rL295347). Backported in rust-lang/llvm#63. - On targets with SSE support, LLVM uses the `movaps` instruction for saving the `xmm` registers, which requires an alignment of 16. For handlers with error codes, however, the stack alignment is only 8, so a alignment exception occurs. This issue is tracked in [bug 26413](https://bugs.llvm.org/show_bug.cgi?id=26413). ~~Unfortunately, I don't know enough about LLVM to fix this.~~ **Edit**: Fix submitted in [D30049](https://reviews.llvm.org/D30049). This PR adds experimental support for this calling convention under the `abi_x86_interrupt` feature gate. The implementation is very similar to rust-lang#38465 and was surprisingly simple :). There is no accepted RFC for this change. In fact, the [RFC for interrupt calling convention](rust-lang/rfcs#1275) from 2015 was closed in favor of naked functions. However, the reactions to the recent [PR](rust-lang#38465) for a MSP430 interrupt calling convention were [in favor of experimental interrupt ABIs](rust-lang#38465 (comment)). - [x] Add compile-fail tests for the feature gate. - [x] Create tracking issue for the `abi_x86_interrupt` feature (and link it in code). **Edit**: Tracking issue: rust-lang#40180 - [x] Backport [rL295347](https://reviews.llvm.org/rL295347) to Rust's LLVM fork. **Edit**: Done in rust-lang/llvm#63 @tari @steveklabnik @jackpot51 @ticki @hawkw @thepowersgang, you might be interested in this.
This calling convention is used to define interrup handlers on MSP430 microcontrollers. Usage looks like this:
which generates the following assembly: