-
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
Support interrupt calling conventions #1275
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
- Feature Name: interrupt\_calling\_conventions | ||
- Start Date: 2015-09-02 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Add compiler support for hardware interrupt calling conventions. | ||
|
||
# Motivation | ||
|
||
Low-level systems software must often include interrupt service routines | ||
(ISRs) to implement certain functionality. In some cases this is a way to | ||
improve the system's efficiency, while in others it is required for correct | ||
operation. | ||
|
||
Most bare-metal software (such as a program designed to run on an ARM | ||
microcontroller) contains at least one custom-written ISR, supporting important | ||
system functionality. Oftentimes this is done in the interest of performance, so | ||
providing fast interrupt entry and exit in software is an important goal. | ||
|
||
In Rust today, the only option for implementation of such an ISR is to build the | ||
ISR entry point as assembly which may call into Rust code. For example, | ||
[RustOS][rustos], a simple x86 OS kernel implemented in Rust, currently | ||
implements ISRs as callbacks to a single "master" interrupt handler: | ||
|
||
[rustos]: https://github.com/ryanra/RustOS | ||
|
||
``` | ||
.macro make_callback num | ||
callback_\num\(): | ||
.endm | ||
|
||
.macro make_all_callbacks, num=50 | ||
.if \num+1 | ||
make_callback %num | ||
pushal | ||
pushl $\num | ||
call unified_handler | ||
|
||
addl $4, %esp | ||
popal | ||
iret | ||
make_all_callbacks \num-1 | ||
.endif | ||
.endm | ||
make_all_callbacks | ||
``` | ||
|
||
This pair of assembler macros generate 50 different ISRs each of which simply | ||
call the master handler with the interrupt vector number as a parameter, which | ||
in turn dispatches to code to actually handle the interrupt. This kind of double | ||
dispatch, while functional, is extremely inefficient in all of code size (even | ||
unused ISRs must be generated), runtime memory usage (the double dispatch for | ||
every interrupt requires a minimum of 12 additional bytes on the stack), and | ||
speed (many ISRs are relatively simple, and the overhead of double dispatch may | ||
have a significant effect on performance). | ||
|
||
This kind of pattern is common in current bare-metal Rust programs: because the | ||
language provides no satisfactory way to approach the need for ISRs, programmers | ||
resort to inefficient ad-hoc solutions, which are usable but unlikely to be | ||
competitive with other systems programming languages. By comparison, exposing | ||
this information to the compiler both eliminates these inefficiencies and | ||
exposes more information to the compiler's optimizer for even greater potential | ||
gains. | ||
|
||
Assembly is required for this task because typically hardware interrupts require | ||
that the ISR preserve the values of all registers. Failure to do so will lead to | ||
unpredictable (inconsistent, even) behavior of the interrupted code. On some | ||
architectures, interrupt exit also requires a special instruction, such as | ||
`iret` on x86. | ||
|
||
# Detailed design | ||
|
||
Add a family of `interrupt` calling conventions to Rust, one for each | ||
architecture supported by the compiler. Given the current set of machines | ||
supported officially (the `arch` field in target specifications), they are as | ||
follow: | ||
|
||
* `aarch64` | ||
* `arm` | ||
* `mips` | ||
* `powerpc` | ||
* `x86` | ||
* `x86_64` | ||
|
||
Each of these architectures combines with a `_interrupt` suffix to form the full | ||
name of a calling convention. | ||
|
||
Use of a calling convention which does not match the current target architecture | ||
is an error. For portable software, this implies that all ISRs will have | ||
`#[cfg]` guards. For example: | ||
|
||
``` | ||
#[cfg(target_arch = "x86_64")] | ||
extern "x86_64_interrupt" fn my_isr() { } | ||
|
||
#[cfg(target_arch = "arm")] | ||
extern "arm_interrupt" fn my_isr() { } | ||
``` | ||
|
||
Each of these calling conventions may impose its own requirements on the | ||
signature of a function declared with it. For example, `x86` and `x86_64` | ||
sometimes receive an "error code" from the hardware, which acts like a | ||
parameter to the function. The `x86_interrupt` and `x86_64_interrupt` calling | ||
conventions may thus take one 32-bit-wide parameter. | ||
|
||
On `arm`, by comparison, the hardware interrupt context follows the standard C | ||
ABI, where ISRs neither receive any parameters nor return any values. Defining | ||
a non-conforming function with this calling convention is an error. | ||
|
||
The requirements for ISR function signatures on any given architecture are | ||
deliberately left unspecified at this time (including the above examples, which | ||
should not be treated as binding contracts), and should be specified at | ||
implementation-time. | ||
|
||
# Drawbacks | ||
|
||
Support for more calling conventions (particularly platform-specific ones) adds | ||
to the maintenance burden for the Rust compiler (though not in a very large | ||
way), with minimal benefit to the majority of users (though the benefit to the | ||
small proportion of users who *do* implement ISRs is large). | ||
|
||
# Alternatives | ||
|
||
* Do nothing. ISRs may still be implemented with in assembly (either inline or | ||
not), but with some loss of efficiency and convenience. | ||
* Provide a more generic way to specify custom calling conventions, such as | ||
naked functions as proposed in [RFC 1201][naked-rfc]. This approach is | ||
applicable to more use cases than the one proposed here, but has a large | ||
number of associated safety concerns. | ||
|
||
[naked-rfc]: https://github.com/rust-lang/rfcs/pull/1201 | ||
|
||
# Unresolved questions | ||
|
||
It may be useful to provide a single portable interrupt calling convention in | ||
addition to platform-specific ones which are incompatible with the portable | ||
convention. Most systems have a simple expected function signature (no | ||
parameters and no return value), so Rust code need not be concerned with which | ||
calling convention should be used for a given platform in many cases. | ||
|
||
Generalizing the family of calling convention names to reserve a subset of | ||
possible names for architecture-specific calling conventions may be useful for | ||
further extension in the future. In such a case, the prefix `x86_` might be | ||
reserved for 32-bit x86 calling conventions, `arm_` for ARM calling | ||
conventions, and so forth. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Could you expand on the motivation for having separate ABIs for every architecture? It seems like this is entirely determined by the hardware (i.e. it doesn't make sense to use the ARM interrupt CC on x86), so we could have
extern "interrupt" fn ...
and have its behaviour do the right thing for the target platform, like theC
andSystem
CCs do.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.
This came up in discussion too; I like the idea of having a single default
interrupt
CC available, but there needs to be at least a little more flexibility. Some systems have multiple interrupt CCs, such as some (older) ARMs where there are 'fast' and 'slow' interrupts which differ in what resources the hardware will automatically preserve.We could certainly add a
"interrupt"
CC with defined defaults for each architecture, but that alone is not always sufficiently expressive. Some of the architecture prefixes could be done away with, but at that point I think it's appropriate to have the architecture-prefixed versions available for all systems regardless of which ones have multiple variants.I suppose some discussion of that (and possible future expansion within the interrupt families) is in order here.