From 34214079f9403c2d1983c7d7d4eb774d0a44b4f5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 19 Feb 2022 12:03:10 +0100 Subject: [PATCH 01/13] Start the interrupt calling convention RFC --- text/0000-interrupt-calling-conventions.md | 119 +++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 text/0000-interrupt-calling-conventions.md diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md new file mode 100644 index 00000000000..55627fc62b2 --- /dev/null +++ b/text/0000-interrupt-calling-conventions.md @@ -0,0 +1,119 @@ +# Interrupt Calling Conventions + +- Feature Name: `interrupt_calling_conventions` +- Start Date: 2022-02-19 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +Add compiler support for interrupt calling conventions that are specific to an architecture or target. This way, interrupt handler functions can be written directly in Rust without needing assembly shims. + +# Motivation +[motivation]: #motivation + +## Calling Conventions +Calling conventions define how function calls are performed, including: + +- how function arguments are passed, e.g. in specific CPU registers, on the stack, as a pointer, etc. +- how the function returns its result +- which registers must be preserved by the function +- setup and clean-up of the stack frame, e.g. whether the caller or callee restores the stack to it's previous state again + +Calling conventions are a large part of a function's ABI ([application binary interface](https://en.wikipedia.org/wiki/Application_binary_interface)), so the terms are sometimes used interchangeably. + +## Current Support +By default, Rust uses an internal `"Rust"` calling convention, which is not standardized and might change in the future. For interoperating with external code, Rust allows to set the calling convention of a function explicitly through an `extern "calling_conv" fn foo() {}` function qualifier. The calling convention of external functions can be specified through `extern "calling_conv" { fn bar(); }`. + +The most common alternative calling convention supported by Rust is `extern "C"`, which can be used to interface with most code written in C. In addition, Rust supports various [other calling conventions](https://doc.rust-lang.org/stable/reference/items/external-blocks.html#abi), which are required in more specific cases. Most alternative calling conventions are only supported on a single architecture, for example the `"aapcs"` ABI that is only supported on ARM systems. + +## Interrupt Handlers +While most functions are invoked by other software, there are some cases where the hardware (or its firmware) invokes a function directly. The most common example are interrupt handler functions defined in embedded systems or operating system kernels. These functions are set up to be called directly by the hardware when a specific interrupt fires (e.g. when a network packet arrives). + +Depending on the architecture, a special calling convention is required for such interrupt handler functions. For example, interrupt handlers are often required to restore all registers to their previous state before returning because interrupts happen asynchronously while other code is running. Also, they often receive additional state as input and need to follow a special procedure on return (e.g. use the `iretq` instruction on `x86_64`). + + + +_Why are we doing this? What use cases does it support? What is the expected outcome?_ + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Introducing new named concepts. +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? + +# Prior art +[prior-art]: #prior-art + +Discuss prior art, both the good and the bad, in relation to this proposal. +A few examples of what this can include are: + +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? +- For community proposals: Is this done by some other community and what were their experiences with it? +- For other teams: What lessons can we learn from what other communities have done here? +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. + +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. +Please also take into consideration that rust sometimes intentionally diverges from common language features. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +# Future possibilities +[future-possibilities]: #future-possibilities + +Think about what the natural extension and evolution of your proposal would +be and how it would affect the language and project as a whole in a holistic +way. Try to use this section as a tool to more fully consider all possible +interactions with the project and language in your proposal. +Also consider how this all fits into the roadmap for the project +and of the relevant sub-team. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +If you have tried and cannot think of any future possibilities, +you may simply state that you cannot think of anything. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +The section merely provides additional information. From 8442e71d4a3dfcc4a4ca7ce9b1c6e832deca57ac Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 19 Feb 2022 15:06:50 +0100 Subject: [PATCH 02/13] First draft on motivation and guide-level explanation sections --- text/0000-interrupt-calling-conventions.md | 110 ++++++++++++++++++--- 1 file changed, 98 insertions(+), 12 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 55627fc62b2..950d639d62d 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -10,8 +10,9 @@ Add compiler support for interrupt calling conventions that are specific to an architecture or target. This way, interrupt handler functions can be written directly in Rust without needing assembly shims. -# Motivation -[motivation]: #motivation +# Background + +This section gives some introduction to calling conventions in general, summarizes the current support of alternative calling conventions in Rust, and explains why interrupt handlers require special calling conventions. ## Calling Conventions Calling conventions define how function calls are performed, including: @@ -29,30 +30,72 @@ By default, Rust uses an internal `"Rust"` calling convention, which is not stan The most common alternative calling convention supported by Rust is `extern "C"`, which can be used to interface with most code written in C. In addition, Rust supports various [other calling conventions](https://doc.rust-lang.org/stable/reference/items/external-blocks.html#abi), which are required in more specific cases. Most alternative calling conventions are only supported on a single architecture, for example the `"aapcs"` ABI that is only supported on ARM systems. ## Interrupt Handlers -While most functions are invoked by other software, there are some cases where the hardware (or its firmware) invokes a function directly. The most common example are interrupt handler functions defined in embedded systems or operating system kernels. These functions are set up to be called directly by the hardware when a specific interrupt fires (e.g. when a network packet arrives). +While most functions are invoked by other software, there are some cases where the hardware (or its firmware) invokes a function directly. The most common example are [interrupt handler](https://en.wikipedia.org/wiki/Interrupt_handler) functions defined in embedded systems or operating system kernels. These functions are set up to be called directly by the hardware when a specific interrupt fires (e.g. when a network packet arrives). Interrupt handlers are also called _interrupt service routines_ (ISRs). Depending on the architecture, a special calling convention is required for such interrupt handler functions. For example, interrupt handlers are often required to restore all registers to their previous state before returning because interrupts happen asynchronously while other code is running. Also, they often receive additional state as input and need to follow a special procedure on return (e.g. use the `iretq` instruction on `x86_64`). +# Motivation +[motivation]: #motivation + +Since the hardware platform requires a special calling convention for interrupt handlers, we cannot define the handler functions directly in Rust using any of the currently supported calling conventions. Instead, we need to define a wrapper function in raw assembly that acts as a compatibilty layer between the interrupt calling convention and the calling convention of the Rust function. For example, with an `extern "C"` Rust function using the System V AMD64 ABI, the wrapper would need to do the following steps on `x86_64`: + +- Backup all registers on the stack that are not preserved by the C calling convention + - This includes all registers except `RBX`, `RSP`, `RBP`, and `R12`–`R15` (these are restored by `extern "C"` functions) + - This also includes floating point and SSE state, which can be huge (unless we are sure that the interrupt handler does not use them) +- Align the stack on a 16-byte boundary (the C calling convention requies) +- Copy the arguments (passed on the stack) into registers (where the C calling convention expects them) +- Call the Rust function +- Clean up the stack, including the alignment bytes and arguments. +- Restore all registers +- Invoke the `iretq` instruction to return from the interrupt +This approach has lots of issues. For one, assembly code is difficult to write and especially difficult to write _correctly_. Errors can easily lead to silent undefined behavior, for example when mixing up two registers when restoring their values. What makes things worse is that the correctness also depends on the compilation settings. For example, there are multiple variants of the C calling convention for `x86_64`, depending on whether the target system is specified as Windows or Unix-compatible. -_Why are we doing this? What use cases does it support? What is the expected outcome?_ +The other issue of the above approach is its performance overhead. Interrupt handlers are often invoked with a very high frequency and at a high priority, so they should be as efficient as possible. However, custom assembly code cannot be optimized by Rust or LLVM, so no inlining or copy elision happens. Also, the wrapper function needs to save all registers that the Rust function could _possibly_ use, because it does not know which registers are actually written by the function. + +To avoid these issues, this RFC proposes to add support for _interrupt calling conventions_ to the Rust language. This makes it possible to define interrupt handlers directly as Rust functions, without requiring any wrapper functions or custom assembly code. + +Rust already supports three different interrupt calling conventions as experimental features: `msp430-interrupt`, `x86-interrupt`, and `avr-interrupt`. They are already widely used in embedded and operating system kernel projects, so this feature also seems to be useful in practice. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: +In addition to ABIs for interfacing with external code, Rust also supports so-called _interrupt ABIs_ to define interrupt handler functions that can interface directly with the hardware. These ABIs are only needed for bare-metal applications such as embedded systems or operating system kernels. The ABIs are special because they impose requirements on the whole signature of the function, including arguments and return values. + +The following interrupt ABIs are currently supported: + +- _(unstable)_ `extern "msp430-interrupt"`: Allows to create interrupt handlers MSP430 microcontrollers. Functions must have the signature `unsafe extern "msp430-interrupt" fn()`. To add a function to the interrupt table, use the following snippet: + + ```rust + #[no_mangle] + #[link_section = "__interrupt_vector_10"] + pub static TIM0_VECTOR: unsafe extern "msp430-interrupt" fn() = tim0; + + unsafe extern "msp430-interrupt" fn tim0() {...} + ``` +- _(unstable)_ `extern "x86-interrupt"`: This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit `x86` targets. Functions must have one of the following two signatures, depending on the interrupt vector: -- Introducing new named concepts. -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. + ```rust + extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame); + extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame, error_code: u64); + ``` + The `error_code` argument is _not_ an optional argument. It set by the hardware for some interrupt vector, but not for others. The programmer must make sure to always use the correct signature for each interrupt vector. +- _(unstable)_ `extern "avr-interrupt"` and `extern "avr-non-blocking-interrupt"` -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +_(The above calling conventions are just listed as an example. They are **not** part of this RFC.)_ + +By using these ABIs, it is possible to implement interrupt handlers directly in Rust, without writing any custom assembly code. This is not only safer and more convenient, it also often results in better performance. The reason for this is that the compiler can employ (cross-function) optimization techniques this way, for example to only backup the CPU registers that are actually used. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +- lowered to llvm +- functions should not be called by software +- functions are safe, but not fool-proof +- some calling conventions are only available for certain platforms + - e.g.: `x86-interrupt`: no red-zone, no SSE? + + # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? + + +- more work for alternative Rust compilers/code generators such as [`cranelift`](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift), [`gccrs`](https://github.com/Rust-GCC/gccrs), or [`mrustc`](https://github.com/thepowersgang/mrustc) +- the implemenations in LLVM have been quite fragile in the past, i.e. there was repeated breakage + - this might make it more difficult to update `rustc` to newer LLVM versions + - leads to higher maintenance overhead # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives +- use a single `interrupt` ABI whose meaning changes depending on the target platform + - pro: less ABI variants in the list + - con: some targets have multiple interrupt calling conventions (e.g. avr and avr-non-blocking) + - con: different targets often require different function signatures -> functions are not cross platform + - con: interrupt handler implementations are often highly target specific -> there is not much value in cross-platform handlers anyway + +## Use the `PreserveAll` Calling Convention + +Many of the advantages of compiler-supported interrupt calling conventions come from the automated register handling, i.e. that all registers are restored to their previous state before returning. We might also be able achieve this by using LLVM's [`preserve_all`](https://llvm.org/docs/LangRef.html#calling-conventions) calling convention. While this calling convention is also marked as experimental, it is at least mentioned in the reference and is probably more stable. It is also relatively platform-independent and might have use cases outside of interrupt handling. + +The remaining parts of interrupt calling conventions could then be implemented in small [naked](https://github.com/rust-lang/rust/issues/90957) wrapper function. These wrapper functions could also be provided by libraries using macros. Depending on the architecture, the wrapper functions would need to implement the following steps: + +- stack alignment +- argument preprocessing +- calling the `extern "preserve_all" fn` +- stack cleanup +- interrupt return + +**Open question:** Would it work as described? + + + # Prior art [prior-art]: #prior-art +- some interrupt calling conventions are already implemented (see above) +- old RFC +- naked functions +- interrupt attribute in C + # Unresolved questions [unresolved-questions]: #unresolved-questions +- What are the requirements for stabilizing an interrupt calling convention? + + # Future possibilities [future-possibilities]: #future-possibilities +- support for more platforms + + From f54ce9903e890434eda216a74e0d0fa739c05e32 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 20 Feb 2022 19:29:48 +0100 Subject: [PATCH 03/13] Write reference-level explanation, drawbacks, and alternatives section --- text/0000-interrupt-calling-conventions.md | 78 ++++++++++++---------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 950d639d62d..990e74791f1 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -89,61 +89,67 @@ By using these ABIs, it is possible to implement interrupt handlers directly in # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -- lowered to llvm -- functions should not be called by software -- functions are safe, but not fool-proof -- some calling conventions are only available for certain platforms - - e.g.: `x86-interrupt`: no red-zone, no SSE? +The exact requirements and properties of the different interrupt calling conventions must be defined and documented before stabilizing them. However, there are some properties and requirements that apply to all interrupt calling conventions. - +If any of these conditions are violated, the compiler throws an error. It should not be possible to cause LLVM errors using interrupt calling conventions. + +## Stability +Since interrupt calling conventions are closely tied to a target architecture, they are only as stable as the corresponding target triple, even if the interrupt calling convention is stabilized. If support for a target triple is removed from Rust, removing support for corresponding interrupt calling conventions is _not_ considered a breaking change. + +Apart from this limitation, interrupt calling conventions fall under Rust's normal stability guarantees. For this reason, special care must be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). + +## Safety +Functions with interrupt calling conventions are considered normal Rust functions. No `unsafe` annotations are required to declare them and thera are no restrictions on their implementation. However, it is not allowed to call such functions from (Rust) code since the custom prelude and epilogue of the functions could lead to memory safety violations. For this reason, the attempt to call a function defined with an interrupt calling convention must result in an hard error that cannot be circumvented through `unsafe` blocks or by allowing some lints. + +The only valid way to invoke a function with an interrupt calling convention is to register them as an interrupt handler directly on the hardware, for example by placing their address in an _interrupt descriptor table_ on `x86_64`. There is no way for the compiler to verify that this operation is correct, so special care needs to be taken by the programmer to ensure that no violation of memory safety can occur. # Drawbacks [drawbacks]: #drawbacks - +Interrupt calling conventions can be quite complex. So even though they are a very isolated feature, they still **add a considerable amount of complexity to the Rust language**. This added complexity could lead to considerable work for alternative Rust compilers/code generators that don't build on top of LLVM. Examples are [`cranelift`](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift), [`gccrs`](https://github.com/Rust-GCC/gccrs), or [`mrustc`](https://github.com/thepowersgang/mrustc). + +Most interrupt calling conventions are still unstable/undocumented features of LLVM, so we need to be cautious about stabilizing them in Rust. Stabilizing them too early could lead to maintenance problems and **might make LLVM updates more difficult**, e.g. when some barely maintained calling convention is accidentally broken in the latest LLVM release. There is also the danger that LLVM drops support for an interrupt calling convention at some point. If the calling convention is already stabilized in Rust, we would need to find an alternative way to provide that functionality. -- more work for alternative Rust compilers/code generators such as [`cranelift`](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift), [`gccrs`](https://github.com/Rust-GCC/gccrs), or [`mrustc`](https://github.com/thepowersgang/mrustc) -- the implemenations in LLVM have been quite fragile in the past, i.e. there was repeated breakage - - this might make it more difficult to update `rustc` to newer LLVM versions - - leads to higher maintenance overhead +The proposed feature is **only needed for applications in a specific niche**, namely embedded programs and operating system kernel. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- use a single `interrupt` ABI whose meaning changes depending on the target platform - - pro: less ABI variants in the list - - con: some targets have multiple interrupt calling conventions (e.g. avr and avr-non-blocking) - - con: different targets often require different function signatures -> functions are not cross platform - - con: interrupt handler implementations are often highly target specific -> there is not much value in cross-platform handlers anyway +As described in the [_Motivation_](#motivation), the main alternative to interrupt calling conventions are wrapper functions written in assembly, e.g. in a naked function. This reduces the maintenance burden for the Rust compiler, but makes interrupt handlers unconvienent to write, more dangerous, and less performant. -## Use the `PreserveAll` Calling Convention +## Alternative: Calling Convention that Preserves all Registers -Many of the advantages of compiler-supported interrupt calling conventions come from the automated register handling, i.e. that all registers are restored to their previous state before returning. We might also be able achieve this by using LLVM's [`preserve_all`](https://llvm.org/docs/LangRef.html#calling-conventions) calling convention. While this calling convention is also marked as experimental, it is at least mentioned in the reference and is probably more stable. It is also relatively platform-independent and might have use cases outside of interrupt handling. +Many of the advantages of compiler-supported interrupt calling conventions come from the automated register handling, i.e. that all registers are restored to their previous state before returning. We might also be able achieve this using a calling convention that preserves all registers, for example LLVM's [`preserve_all`](https://llvm.org/docs/LangRef.html#calling-conventions) calling convention. -The remaining parts of interrupt calling conventions could then be implemented in small [naked](https://github.com/rust-lang/rust/issues/90957) wrapper function. These wrapper functions could also be provided by libraries using macros. Depending on the architecture, the wrapper functions would need to implement the following steps: +Such a calling convention could be platform independent and should be much easier to maintain. It could also be called normally from Rust code and might thus have use cases outside of interrupt handling, e.g. similar to functions annotated as [`#[cold]`](https://doc.rust-lang.org/reference/attributes/codegen.html#the-cold-attribute). -- stack alignment -- argument preprocessing -- calling the `extern "preserve_all" fn` -- stack cleanup -- interrupt return +Using such a calling convention, it should be able to create interrupt handler wrappers in assembly with comparable performance. These wrapper function would handle the platform-specific steps of interrupt handling, such as stack alignment, argument preprocessing, and the interrupt epilogue. Since they don't require language support for this, they don't impact the maintainability of the compiler and can evolve independently in libraries. Using proc macros, they could even provide a similar level of usability to users. -**Open question:** Would it work as described? +While this approach could be considered a good middle ground, full compiler support for interrupt calling convetions is still be the better solution from a usability and performance perspective. - +## Alternative: Implementation in `rustc` + +Instead of relying on LLVM (or alternative code generators) to implement the interrupt calling conventions, we could also try to implement support for the calling conventions in `rustc` directly. This way, LLVM upgrades would not be affected by this feature and we would be less dependent on LLVM in general. One possible implementation approach for this could be to build upon a calling convention that preserves all registers (see the previous section). + +The drawback of this approach is inreased complexity and maintenance cost for `rustc` this way. + +## Alternative: Single `interrupt` ABI that depends on the target + +Instead of adding multiple target-specific interrupt calling conventions under different names, we could add support for a single cross-platform `interrupt` calling convention. This calling convention would be an alias for the interrupt calling convention of the target system, e.g. `x86-interrupt` when compiling for an `x86` target. + +The main advantage of this approach would be that we keep the list of supported ABI variants short, which might make the documentation clearer. However, there are also several drawbacks: +- Some targets have multiple interrupt calling conventions (e.g. avr and avr-non-blocking). This would be difficult to represent with a single `interrupt` calling convention. +- Interrupt handlers on targets require different function signatures. It would be difficult to abstract this cleanly. +- Interrupt handler implementations are often highly target-specific, so that there is not much value in cross-platform handlers. In fact, it could even lead to bugs when an interrupt handler is accidentally reused on a different platform. # Prior art [prior-art]: #prior-art From 5ae3d65a301d7f2057c6ab80abe908c48ddbbdb4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 20 Feb 2022 20:05:36 +0100 Subject: [PATCH 04/13] Prior art, open questions, and future possibilities --- text/0000-interrupt-calling-conventions.md | 52 +++------------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 990e74791f1..36a87d25a19 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -154,58 +154,18 @@ The main advantage of this approach would be that we keep the list of supported # Prior art [prior-art]: #prior-art -- some interrupt calling conventions are already implemented (see above) -- old RFC -- naked functions -- interrupt attribute in C - +The three interrupt calling conventions that are mentioned in this RFC are already implemented as experimental features in `rustc` since multiple years (`msp430-interrupt` and `x86-interrupt` since 2017, `avr-interrupt` since 2020). They are already in use in several projects and were deemed useful enough that the Rust language team [decided](https://github.com/rust-lang/rust/issues/40180#issuecomment-1022507941) to consider this feature for proper inclusion. + +There was already a [prior RFC](https://github.com/rust-lang/rfcs/pull/1275) for interrupt calling conventions in 2015. The RFC was [closed](https://github.com/rust-lang/rfcs/pull/1275#issuecomment-154494283) for the time being to explore naked functions as a potential alternative first. Naked functions are now on the [path to stabilization](https://github.com/rust-lang/rust/issues/90957#issuecomment-1028297041). + +GCC supports a cross-platform [`__interrupt__` attribute](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) for creating interrupt handlers. The behavior is target-specific and very similar to the proposal of this RFC. The LLVM-based Clang compiler also supports this attribute for a [subset of targets](https://clang.llvm.org/docs/AttributeReference.html#interrupt-arm). # Unresolved questions [unresolved-questions]: #unresolved-questions - What are the requirements for stabilizing an interrupt calling convention? - - # Future possibilities [future-possibilities]: #future-possibilities -- support for more platforms - - +This feature is relatively isolated in limited in scope, so it is not expected that this feature will be extended in the future. From 5e8cb568bdd6a3ba4274b1c7e5e52e0e56e5582a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 20 Feb 2022 20:10:20 +0100 Subject: [PATCH 05/13] Fix typos --- text/0000-interrupt-calling-conventions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 36a87d25a19..a9496d6da9d 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -37,12 +37,12 @@ Depending on the architecture, a special calling convention is required for such # Motivation [motivation]: #motivation -Since the hardware platform requires a special calling convention for interrupt handlers, we cannot define the handler functions directly in Rust using any of the currently supported calling conventions. Instead, we need to define a wrapper function in raw assembly that acts as a compatibilty layer between the interrupt calling convention and the calling convention of the Rust function. For example, with an `extern "C"` Rust function using the System V AMD64 ABI, the wrapper would need to do the following steps on `x86_64`: +Since the hardware platform requires a special calling convention for interrupt handlers, we cannot define the handler functions directly in Rust using any of the currently supported calling conventions. Instead, we need to define a wrapper function in raw assembly that acts as a compatibility layer between the interrupt calling convention and the calling convention of the Rust function. For example, with an `extern "C"` Rust function using the System V AMD64 ABI, the wrapper would need to do the following steps on `x86_64`: - Backup all registers on the stack that are not preserved by the C calling convention - This includes all registers except `RBX`, `RSP`, `RBP`, and `R12`–`R15` (these are restored by `extern "C"` functions) - This also includes floating point and SSE state, which can be huge (unless we are sure that the interrupt handler does not use them) -- Align the stack on a 16-byte boundary (the C calling convention requies) +- Align the stack on a 16-byte boundary (required by the C calling convention) - Copy the arguments (passed on the stack) into registers (where the C calling convention expects them) - Call the Rust function - Clean up the stack, including the alignment bytes and arguments. @@ -73,7 +73,7 @@ The following interrupt ABIs are currently supported: unsafe extern "msp430-interrupt" fn tim0() {...} ``` -- _(unstable)_ `extern "x86-interrupt"`: This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit `x86` targets. Functions must have one of the following two signatures, depending on the interrupt vector: +- _(unstable)_ `extern "x86-interrupt"`: This calling convention can be used for defining interrupt handlers on 32-bit and 64-bit `x86` targets. Functions must have one of the following two signatures, depending on the interrupt vector: ```rust extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame); @@ -107,7 +107,7 @@ Since interrupt calling conventions are closely tied to a target architecture, t Apart from this limitation, interrupt calling conventions fall under Rust's normal stability guarantees. For this reason, special care must be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). ## Safety -Functions with interrupt calling conventions are considered normal Rust functions. No `unsafe` annotations are required to declare them and thera are no restrictions on their implementation. However, it is not allowed to call such functions from (Rust) code since the custom prelude and epilogue of the functions could lead to memory safety violations. For this reason, the attempt to call a function defined with an interrupt calling convention must result in an hard error that cannot be circumvented through `unsafe` blocks or by allowing some lints. +Functions with interrupt calling conventions are considered normal Rust functions. No `unsafe` annotations are required to declare them and there are no restrictions on their implementation. However, it is not allowed to call such functions from (Rust) code since the custom prelude and epilogue of the functions could lead to memory safety violations. For this reason, the attempt to call a function defined with an interrupt calling convention must result in an hard error that cannot be circumvented through `unsafe` blocks or by allowing some lints. The only valid way to invoke a function with an interrupt calling convention is to register them as an interrupt handler directly on the hardware, for example by placing their address in an _interrupt descriptor table_ on `x86_64`. There is no way for the compiler to verify that this operation is correct, so special care needs to be taken by the programmer to ensure that no violation of memory safety can occur. @@ -123,7 +123,7 @@ The proposed feature is **only needed for applications in a specific niche**, na # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -As described in the [_Motivation_](#motivation), the main alternative to interrupt calling conventions are wrapper functions written in assembly, e.g. in a naked function. This reduces the maintenance burden for the Rust compiler, but makes interrupt handlers unconvienent to write, more dangerous, and less performant. +As described in the [_Motivation_](#motivation), the main alternative to interrupt calling conventions are wrapper functions written in assembly, e.g. in a naked function. This reduces the maintenance burden for the Rust compiler, but makes interrupt handlers inconvenient to write, more dangerous, and less performant. ## Alternative: Calling Convention that Preserves all Registers @@ -133,13 +133,13 @@ Such a calling convention could be platform independent and should be much easie Using such a calling convention, it should be able to create interrupt handler wrappers in assembly with comparable performance. These wrapper function would handle the platform-specific steps of interrupt handling, such as stack alignment, argument preprocessing, and the interrupt epilogue. Since they don't require language support for this, they don't impact the maintainability of the compiler and can evolve independently in libraries. Using proc macros, they could even provide a similar level of usability to users. -While this approach could be considered a good middle ground, full compiler support for interrupt calling convetions is still be the better solution from a usability and performance perspective. +While this approach could be considered a good middle ground, full compiler support for interrupt calling conventions is still be the better solution from a usability and performance perspective. ## Alternative: Implementation in `rustc` Instead of relying on LLVM (or alternative code generators) to implement the interrupt calling conventions, we could also try to implement support for the calling conventions in `rustc` directly. This way, LLVM upgrades would not be affected by this feature and we would be less dependent on LLVM in general. One possible implementation approach for this could be to build upon a calling convention that preserves all registers (see the previous section). -The drawback of this approach is inreased complexity and maintenance cost for `rustc` this way. +The drawback of this approach is increased complexity and maintenance cost for `rustc` this way. ## Alternative: Single `interrupt` ABI that depends on the target From d72e615bcc16e106d8252ef76c8bf4eb18049272 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 20 Feb 2022 20:29:12 +0100 Subject: [PATCH 06/13] Various improvements after proof-reading --- text/0000-interrupt-calling-conventions.md | 31 ++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index a9496d6da9d..87f70d08c6f 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -27,7 +27,7 @@ Calling conventions are a large part of a function's ABI ([application binary in ## Current Support By default, Rust uses an internal `"Rust"` calling convention, which is not standardized and might change in the future. For interoperating with external code, Rust allows to set the calling convention of a function explicitly through an `extern "calling_conv" fn foo() {}` function qualifier. The calling convention of external functions can be specified through `extern "calling_conv" { fn bar(); }`. -The most common alternative calling convention supported by Rust is `extern "C"`, which can be used to interface with most code written in C. In addition, Rust supports various [other calling conventions](https://doc.rust-lang.org/stable/reference/items/external-blocks.html#abi), which are required in more specific cases. Most alternative calling conventions are only supported on a single architecture, for example the `"aapcs"` ABI that is only supported on ARM systems. +The most common alternative calling convention supported by Rust is `extern "C"`, which can be used to interface with most code written in C. In addition, Rust supports various [other calling conventions](https://doc.rust-lang.org/stable/reference/items/external-blocks.html#abi), which are required in more specific cases. Most alternative calling conventions are only supported on a single architecture, for example the `"aapcs"` ABI is only supported on ARM systems. ## Interrupt Handlers While most functions are invoked by other software, there are some cases where the hardware (or its firmware) invokes a function directly. The most common example are [interrupt handler](https://en.wikipedia.org/wiki/Interrupt_handler) functions defined in embedded systems or operating system kernels. These functions are set up to be called directly by the hardware when a specific interrupt fires (e.g. when a network packet arrives). Interrupt handlers are also called _interrupt service routines_ (ISRs). @@ -41,7 +41,7 @@ Since the hardware platform requires a special calling convention for interrupt - Backup all registers on the stack that are not preserved by the C calling convention - This includes all registers except `RBX`, `RSP`, `RBP`, and `R12`–`R15` (these are restored by `extern "C"` functions) - - This also includes floating point and SSE state, which can be huge (unless we are sure that the interrupt handler does not use them) + - This also includes floating point and SSE state, which can be huge (unless we are sure that the interrupt handler does not use the corresponding registers) - Align the stack on a 16-byte boundary (required by the C calling convention) - Copy the arguments (passed on the stack) into registers (where the C calling convention expects them) - Call the Rust function @@ -64,7 +64,7 @@ In addition to ABIs for interfacing with external code, Rust also supports so-ca The following interrupt ABIs are currently supported: -- _(unstable)_ `extern "msp430-interrupt"`: Allows to create interrupt handlers MSP430 microcontrollers. Functions must have the signature `unsafe extern "msp430-interrupt" fn()`. To add a function to the interrupt table, use the following snippet: +- _(unstable)_ `extern "msp430-interrupt"`: Allows to create interrupt handlers on MSP430 microcontrollers. Functions must have the signature `unsafe extern "msp430-interrupt" fn()`. To add a function to the interrupt table, use the following snippet: ```rust #[no_mangle] @@ -73,29 +73,31 @@ The following interrupt ABIs are currently supported: unsafe extern "msp430-interrupt" fn tim0() {...} ``` + + Then place the `__interrupt_vector_10` section in the interrupt handler table using a linker script. - _(unstable)_ `extern "x86-interrupt"`: This calling convention can be used for defining interrupt handlers on 32-bit and 64-bit `x86` targets. Functions must have one of the following two signatures, depending on the interrupt vector: ```rust extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame); extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame, error_code: u64); ``` - The `error_code` argument is _not_ an optional argument. It set by the hardware for some interrupt vector, but not for others. The programmer must make sure to always use the correct signature for each interrupt vector. + The `error_code` argument is _not_ an optional argument. It is set by the hardware for some interrupt vector, but not for others. The programmer must make sure to always use the correct signature for each interrupt vector, otherwise undefined behavior occurs. - _(unstable)_ `extern "avr-interrupt"` and `extern "avr-non-blocking-interrupt"` _(The above calling conventions are just listed as an example. They are **not** part of this RFC.)_ -By using these ABIs, it is possible to implement interrupt handlers directly in Rust, without writing any custom assembly code. This is not only safer and more convenient, it also often results in better performance. The reason for this is that the compiler can employ (cross-function) optimization techniques this way, for example to only backup the CPU registers that are actually used. +By using these ABIs, it is possible to implement interrupt handlers directly in Rust, without writing any custom assembly code. This is not only safer and more convenient, it also often results in better performance. The reason for this is that the compiler can employ (cross-function) optimization techniques this way, for example to only backup the CPU registers that are actually overwritten by the interrupt handler. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -The exact requirements and properties of the different interrupt calling conventions must be defined and documented before stabilizing them. However, there are some properties and requirements that apply to all interrupt calling conventions. +The exact requirements and properties of the individual interrupt calling conventions must be defined and documented before stabilizing them. However, there are some properties and requirements that apply to all interrupt calling conventions. ## Compiler Checks Interrupt calling conventions have strict requirements that are checked by the Rust compiler: - They must not be called by Rust code. -- The function signature must satisfy the requirements. +- The function signature must match a specific template. - They are only available on specific targets and might require specific target settings. - All other requirements imposed by the implementation of the calling convention in LLVM. @@ -131,7 +133,7 @@ Many of the advantages of compiler-supported interrupt calling conventions come Such a calling convention could be platform independent and should be much easier to maintain. It could also be called normally from Rust code and might thus have use cases outside of interrupt handling, e.g. similar to functions annotated as [`#[cold]`](https://doc.rust-lang.org/reference/attributes/codegen.html#the-cold-attribute). -Using such a calling convention, it should be able to create interrupt handler wrappers in assembly with comparable performance. These wrapper function would handle the platform-specific steps of interrupt handling, such as stack alignment, argument preprocessing, and the interrupt epilogue. Since they don't require language support for this, they don't impact the maintainability of the compiler and can evolve independently in libraries. Using proc macros, they could even provide a similar level of usability to users. +Using such a calling convention, it should be possible to create interrupt handler wrappers in assembly with comparable performance. These wrapper functions would handle the platform-specific steps of interrupt handling, such as stack alignment, argument preprocessing, and the interrupt epilogue. Since no language support is required for these wrapper functions, they don't impact the maintainability of the compiler and can evolve independently in libraries. Using proc macros, they could even provide a similar level of usability to users. While this approach could be considered a good middle ground, full compiler support for interrupt calling conventions is still be the better solution from a usability and performance perspective. @@ -139,16 +141,16 @@ While this approach could be considered a good middle ground, full compiler supp Instead of relying on LLVM (or alternative code generators) to implement the interrupt calling conventions, we could also try to implement support for the calling conventions in `rustc` directly. This way, LLVM upgrades would not be affected by this feature and we would be less dependent on LLVM in general. One possible implementation approach for this could be to build upon a calling convention that preserves all registers (see the previous section). -The drawback of this approach is increased complexity and maintenance cost for `rustc` this way. +The drawback of this approach is increased complexity and maintenance cost for `rustc`. ## Alternative: Single `interrupt` ABI that depends on the target -Instead of adding multiple target-specific interrupt calling conventions under different names, we could add support for a single cross-platform `interrupt` calling convention. This calling convention would be an alias for the interrupt calling convention of the target system, e.g. `x86-interrupt` when compiling for an `x86` target. +Instead of adding multiple target-specific interrupt calling conventions under different names, we could add support for a single cross-platform `extern "interrupt"` calling convention. This calling convention would be an alias for the interrupt calling convention of the target system, e.g. `x86-interrupt` when compiling for an `x86` target. The main advantage of this approach would be that we keep the list of supported ABI variants short, which might make the documentation clearer. However, there are also several drawbacks: -- Some targets have multiple interrupt calling conventions (e.g. avr and avr-non-blocking). This would be difficult to represent with a single `interrupt` calling convention. -- Interrupt handlers on targets require different function signatures. It would be difficult to abstract this cleanly. +- Some targets have multiple interrupt calling conventions (e.g. `avr` and `avr-non-blocking`). This would be difficult to represent with a single `extern "interrupt"` calling convention. +- Interrupt handlers on different targets require different function signatures. It would be difficult to abstract this cleanly. - Interrupt handler implementations are often highly target-specific, so that there is not much value in cross-platform handlers. In fact, it could even lead to bugs when an interrupt handler is accidentally reused on a different platform. # Prior art @@ -156,14 +158,15 @@ The main advantage of this approach would be that we keep the list of supported The three interrupt calling conventions that are mentioned in this RFC are already implemented as experimental features in `rustc` since multiple years (`msp430-interrupt` and `x86-interrupt` since 2017, `avr-interrupt` since 2020). They are already in use in several projects and were deemed useful enough that the Rust language team [decided](https://github.com/rust-lang/rust/issues/40180#issuecomment-1022507941) to consider this feature for proper inclusion. -There was already a [prior RFC](https://github.com/rust-lang/rfcs/pull/1275) for interrupt calling conventions in 2015. The RFC was [closed](https://github.com/rust-lang/rfcs/pull/1275#issuecomment-154494283) for the time being to explore naked functions as a potential alternative first. Naked functions are now on the [path to stabilization](https://github.com/rust-lang/rust/issues/90957#issuecomment-1028297041). +There was already a [prior RFC](https://github.com/rust-lang/rfcs/pull/1275) for interrupt calling conventions in 2015. The RFC was [closed](https://github.com/rust-lang/rfcs/pull/1275#issuecomment-154494283) for the time being to explore naked functions as a potential alternative first. Naked functions are now on the [path to stabilization](https://github.com/rust-lang/rust/issues/90957#issuecomment-1028297041), but there is still value in support for interrupt calling conventions, as described in this RFC. GCC supports a cross-platform [`__interrupt__` attribute](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) for creating interrupt handlers. The behavior is target-specific and very similar to the proposal of this RFC. The LLVM-based Clang compiler also supports this attribute for a [subset of targets](https://clang.llvm.org/docs/AttributeReference.html#interrupt-arm). # Unresolved questions [unresolved-questions]: #unresolved-questions -- What are the requirements for stabilizing an interrupt calling convention? +- What are the exact requirements for stabilizing an interrupt calling convention? What level of stability of the LLVM implementation is required? +- Is there a way to implement interrupt calling conventions directly in `rustc` without LLVM support? # Future possibilities [future-possibilities]: #future-possibilities From 92bf2fe1a1e591548722cc69f560027fb1057791 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 22 Feb 2022 13:24:51 +0100 Subject: [PATCH 07/13] Stabilizations for tier 3 should only be done with caution --- text/0000-interrupt-calling-conventions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 87f70d08c6f..051ed8445f7 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -106,7 +106,9 @@ If any of these conditions are violated, the compiler throws an error. It should ## Stability Since interrupt calling conventions are closely tied to a target architecture, they are only as stable as the corresponding target triple, even if the interrupt calling convention is stabilized. If support for a target triple is removed from Rust, removing support for corresponding interrupt calling conventions is _not_ considered a breaking change. -Apart from this limitation, interrupt calling conventions fall under Rust's normal stability guarantees. For this reason, special care must be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). +Apart from this limitation, interrupt calling conventions fall under Rust's normal stability guarantees. As soon as a calling convention is stabilized, breaking changes are longer allowed. For this reason, special care must be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). + +It is also worth noting that special caution must be taken before stabilizing interrupt calling conventions for _tier 3_ targets, as these targets might still evolve. ## Safety Functions with interrupt calling conventions are considered normal Rust functions. No `unsafe` annotations are required to declare them and there are no restrictions on their implementation. However, it is not allowed to call such functions from (Rust) code since the custom prelude and epilogue of the functions could lead to memory safety violations. For this reason, the attempt to call a function defined with an interrupt calling convention must result in an hard error that cannot be circumvented through `unsafe` blocks or by allowing some lints. From a2f8c296907244ffc59f5705275f1a398ad5855a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 24 Feb 2022 09:28:31 +0100 Subject: [PATCH 08/13] Clarify the stability and platform support of interrupt calling conventions --- text/0000-interrupt-calling-conventions.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 051ed8445f7..65bf7f3898d 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -103,12 +103,15 @@ Interrupt calling conventions have strict requirements that are checked by the R If any of these conditions are violated, the compiler throws an error. It should not be possible to cause LLVM errors using interrupt calling conventions. -## Stability -Since interrupt calling conventions are closely tied to a target architecture, they are only as stable as the corresponding target triple, even if the interrupt calling convention is stabilized. If support for a target triple is removed from Rust, removing support for corresponding interrupt calling conventions is _not_ considered a breaking change. +## Platform Support +Since interrupt calling conventions are closely tied to a target and only available on that specific target, they are treated as a platform feature and fall under Rust's [target tier policy](https://doc.rust-lang.org/nightly/rustc/target-tier-policy.html). + +This means that calling conventions are only as well supported as the corresponding target. So on _tier 3_ targets, there are no guarantees that corresponding interrupt calling conventions will build, even if they're stabilized. On _tier 2_ targets, interrupt calling coventions are guaranteed to build, but no automated tests are run. Only on _tier 1_ targets, there is a guarantee that interrupt calling conventions will work. -Apart from this limitation, interrupt calling conventions fall under Rust's normal stability guarantees. As soon as a calling convention is stabilized, breaking changes are longer allowed. For this reason, special care must be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). +## Stability +Interrupt calling conventions are a language feature, so they fall under Rust's normal stability guarantees, with one exception: If official support for a target is dropped, the corresponding interrupt calling convention can be removed from the Rust language, even if it is stabilized. This is not considered a breaking change because no code on other targets is broken by this, since it was never possible to use the calling convention on other targets. -It is also worth noting that special caution must be taken before stabilizing interrupt calling conventions for _tier 3_ targets, as these targets might still evolve. +As soon as a calling convention is stabilized, breaking changes are longer allowed, independent of the target tier. For this reason, special caution must be taken before stabilizing interrupt calling conventions for _tier 3_ targets, as these targets might still evolve. Special care must also be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). ## Safety Functions with interrupt calling conventions are considered normal Rust functions. No `unsafe` annotations are required to declare them and there are no restrictions on their implementation. However, it is not allowed to call such functions from (Rust) code since the custom prelude and epilogue of the functions could lead to memory safety violations. For this reason, the attempt to call a function defined with an interrupt calling convention must result in an hard error that cannot be circumvented through `unsafe` blocks or by allowing some lints. From fe789a80469f5476a5b601e82a2c91d4fc2121c6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 23 Mar 2022 13:39:05 +0100 Subject: [PATCH 09/13] Some architectures might have multiple different interrupt calling conventions --- text/0000-interrupt-calling-conventions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 65bf7f3898d..5e0698d3620 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -88,6 +88,8 @@ _(The above calling conventions are just listed as an example. They are **not** By using these ABIs, it is possible to implement interrupt handlers directly in Rust, without writing any custom assembly code. This is not only safer and more convenient, it also often results in better performance. The reason for this is that the compiler can employ (cross-function) optimization techniques this way, for example to only backup the CPU registers that are actually overwritten by the interrupt handler. +On some platforms, there might be multiple interrupt calling conventions with different behavior. For example, on AVR there are separate calling conventions that either disable global interrupts while the interrupt handler is running, or keep them enabled. Another example is the ARM architecture, where it might make sense to add multiple interrupt calling conventions to allow switching to a specific CPU mode before invoking the interrupt handler. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From c584ec11da0bd55490ca8ac0c5cff1e425130678 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 23 Mar 2022 16:07:21 +0100 Subject: [PATCH 10/13] Fix typo --- text/0000-interrupt-calling-conventions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 5e0698d3620..38d2eb9f531 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -113,7 +113,7 @@ This means that calling conventions are only as well supported as the correspond ## Stability Interrupt calling conventions are a language feature, so they fall under Rust's normal stability guarantees, with one exception: If official support for a target is dropped, the corresponding interrupt calling convention can be removed from the Rust language, even if it is stabilized. This is not considered a breaking change because no code on other targets is broken by this, since it was never possible to use the calling convention on other targets. -As soon as a calling convention is stabilized, breaking changes are longer allowed, independent of the target tier. For this reason, special caution must be taken before stabilizing interrupt calling conventions for _tier 3_ targets, as these targets might still evolve. Special care must also be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). +As soon as a calling convention is stabilized, breaking changes are no longer allowed, independent of the target tier. For this reason, special caution must be taken before stabilizing interrupt calling conventions for _tier 3_ targets, as these targets might still evolve. Special care must also be taken before stabilizing interrupt calling conventions that are implemented outside of `rustc` (e.g. in LLVM). ## Safety Functions with interrupt calling conventions are considered normal Rust functions. No `unsafe` annotations are required to declare them and there are no restrictions on their implementation. However, it is not allowed to call such functions from (Rust) code since the custom prelude and epilogue of the functions could lead to memory safety violations. For this reason, the attempt to call a function defined with an interrupt calling convention must result in an hard error that cannot be circumvented through `unsafe` blocks or by allowing some lints. From b2ffad4b24986465f838c9501f1e55beb5285544 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 23 Mar 2022 16:07:33 +0100 Subject: [PATCH 11/13] Mention `rust_codegen_gcc` --- text/0000-interrupt-calling-conventions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 38d2eb9f531..7e186c69875 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -123,7 +123,7 @@ The only valid way to invoke a function with an interrupt calling convention is # Drawbacks [drawbacks]: #drawbacks -Interrupt calling conventions can be quite complex. So even though they are a very isolated feature, they still **add a considerable amount of complexity to the Rust language**. This added complexity could lead to considerable work for alternative Rust compilers/code generators that don't build on top of LLVM. Examples are [`cranelift`](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift), [`gccrs`](https://github.com/Rust-GCC/gccrs), or [`mrustc`](https://github.com/thepowersgang/mrustc). +Interrupt calling conventions can be quite complex. So even though they are a very isolated feature, they still **add a considerable amount of complexity to the Rust language**. This added complexity could lead to considerable work for alternative Rust compilers/code generators that don't build on top of LLVM. Examples are [`cranelift`](https://github.com/bytecodealliance/wasmtime/tree/main/cranelift), [`gccrs`](https://github.com/Rust-GCC/gccrs), [`rust_codegen_gcc`](https://github.com/rust-lang/rustc_codegen_gcc), or [`mrustc`](https://github.com/thepowersgang/mrustc). Most interrupt calling conventions are still unstable/undocumented features of LLVM, so we need to be cautious about stabilizing them in Rust. Stabilizing them too early could lead to maintenance problems and **might make LLVM updates more difficult**, e.g. when some barely maintained calling convention is accidentally broken in the latest LLVM release. There is also the danger that LLVM drops support for an interrupt calling convention at some point. If the calling convention is already stabilized in Rust, we would need to find an alternative way to provide that functionality. From 719b02c30361106d0523a2b15c816a618103fd94 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 23 Mar 2022 16:09:17 +0100 Subject: [PATCH 12/13] Add more details on x86-interrupt calling convention - the error code is only u64 on 64-bit systems, on 32-bit systems it needs to be u32 - mention the Rust compiler doesn't check these types yet --- text/0000-interrupt-calling-conventions.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 7e186c69875..1aff59b0ff5 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -78,10 +78,12 @@ The following interrupt ABIs are currently supported: - _(unstable)_ `extern "x86-interrupt"`: This calling convention can be used for defining interrupt handlers on 32-bit and 64-bit `x86` targets. Functions must have one of the following two signatures, depending on the interrupt vector: ```rust - extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame); - extern "x86-interrupt" fn(stack_frame: &ExceptionStackFrame, error_code: u64); + extern "x86-interrupt" fn(stack_frame: &StackFrame); + extern "x86-interrupt" fn(stack_frame: &StackFrame, error_code: ErrorCode); ``` The `error_code` argument is _not_ an optional argument. It is set by the hardware for some interrupt vector, but not for others. The programmer must make sure to always use the correct signature for each interrupt vector, otherwise undefined behavior occurs. + + The `StackFrame` type must be a struct that matches the stack frame pushed by the CPU. The `ErrorCode` type must be `u64` on 64-bit targets and `u32` on 32-bit targets. These types are currently _not_ checked by `rustc`. - _(unstable)_ `extern "avr-interrupt"` and `extern "avr-non-blocking-interrupt"` _(The above calling conventions are just listed as an example. They are **not** part of this RFC.)_ From 38a2a9fdc14aad71d1e44a03d95841815e741437 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 5 Apr 2022 18:41:48 +0200 Subject: [PATCH 13/13] Extend paragraph about register backup/restore in motivation --- text/0000-interrupt-calling-conventions.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/0000-interrupt-calling-conventions.md b/text/0000-interrupt-calling-conventions.md index 1aff59b0ff5..62c57c3ba6a 100644 --- a/text/0000-interrupt-calling-conventions.md +++ b/text/0000-interrupt-calling-conventions.md @@ -51,9 +51,11 @@ Since the hardware platform requires a special calling convention for interrupt This approach has lots of issues. For one, assembly code is difficult to write and especially difficult to write _correctly_. Errors can easily lead to silent undefined behavior, for example when mixing up two registers when restoring their values. What makes things worse is that the correctness also depends on the compilation settings. For example, there are multiple variants of the C calling convention for `x86_64`, depending on whether the target system is specified as Windows or Unix-compatible. -The other issue of the above approach is its performance overhead. Interrupt handlers are often invoked with a very high frequency and at a high priority, so they should be as efficient as possible. However, custom assembly code cannot be optimized by Rust or LLVM, so no inlining or copy elision happens. Also, the wrapper function needs to save all registers that the Rust function could _possibly_ use, because it does not know which registers are actually written by the function. +The other issue of the above approach is its performance overhead. Interrupt handlers are often invoked with a very high frequency and at a high priority, so they should be as efficient as possible. However, custom assembly code cannot be optimized by Rust or LLVM, so no inlining or copy elision happens. -To avoid these issues, this RFC proposes to add support for _interrupt calling conventions_ to the Rust language. This makes it possible to define interrupt handlers directly as Rust functions, without requiring any wrapper functions or custom assembly code. +Another source of additional performance overhead is caused by the register saving step. The assembly wrapper function has no way to know which registers are actually used by the wrapped function, so it has to save and restore all registers that the Rust function could _possibly_ use, even if only a subset of them is actually overwritten. + +To avoid these issues, this RFC proposes to add native support for _interrupt calling conventions_ to the Rust language. This makes it possible to define interrupt handlers directly as Rust functions, without requiring any wrapper functions or custom assembly code. Because all code is generated by the compiler, full code optimization is possible. Rust already supports three different interrupt calling conventions as experimental features: `msp430-interrupt`, `x86-interrupt`, and `avr-interrupt`. They are already widely used in embedded and operating system kernel projects, so this feature also seems to be useful in practice.