-
Notifications
You must be signed in to change notification settings - Fork 167
[RFC] revamp the exception / interrupt registration mechanism #51
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
Conversation
I like the proposal a lot! My opinions:
|
Oh but one thing is we can simply fix #19 in LLVM, see my comment: #19 (comment). It's really not a big deal and if push comes to shove I'll write that patch I guess. So whether or not any change discussed here has any effect on #19 should not affect our decision on this issue. |
@japaric have you tried something like this? https://is.gd/6IqMsG |
Come on this is a straightforward LLVM fix, let's not add global_asm! to the mix |
@whitequark I don't like having yet another |
Do you mean that you see a problem with using
Makes sense. I agree with the argument and the re-exports in the device crate part.
nods
Actually, yes. LLVM yells at me (LLVM assertion) when I use that, override any exception and use LTO (it works fine without LTO). Some "assertion: symbol SYS_TICK is variable" nonsense IIRC. I can post the exact error message later.
Me neither. I would prefer to avoid it but also want to fix #19. |
I thought I did but, nevermind, I misunderstood a few things on how NVIC and exception handling worked. |
@pftbest This is the error message / LLVM assertion I mentioned: rustc: /checkout/src/llvm/lib/MC/MCStreamer.cpp:294: virtual void llvm::MCStreamer::EmitLabel(llvm::MCSymbol*): Assertion `!Symbol->isVariable() && "Cannot emit a variable symbol!"' failed. It happens if I remove the I'll see what changes are required to get rid of interrupts.x. I'll assume that #19 will get fixed some other way. |
Changing: extern "C" {
fn USAGE_FAULT();
fn HARD_FAULT();
..
} to #[allow(non_snake_case)]
#[allow(private_no_mangle_fns)]
#[linkage = "weak"]
#[naked]
#[no_mangle]
extern "C" fn USAGE_FAULT() {
unsafe {
asm!("b DEFAULT_HANDLER");
::core::intrinsics::unreachable()
}
}
#[allow(non_snake_case)]
#[allow(private_no_mangle_fns)]
#[linkage = "weak"]
#[naked]
#[no_mangle]
extern "C" fn HARD_FAULT() {
unsafe {
asm!("b DEFAULT_HANDLER");
::core::intrinsics::unreachable()
}
}
// .. seems to be enough to get rid of interrupts.x and seems to preserve semantics. Disassembly looks like:
Are we OK with the tradeoff? Replacing interrupts.x by #19 like bloat. |
Thanks @whitequark. Is the patch published somewhere? I'd like to test it locally. |
(emphasis not mine) But the documentation also calls it "exception frame", so maybe ExceptionFrame would be less ambiguous? |
Oh, that's pretty ambiguous on their part. I like ExceptionFrame. |
Thanks for the patch, @whitequark. I tested it but didn't solve the problem. Maybe because the mergefunc pass is not enabled by default? I used Then I tried to recompile with
This is the patch I applied. Hopefully I didn't mess it up. |
@japaric Thaaat is interesting; the patch passed the entire LLVM testsuite (but not bootstrap tests). Can you please call that cargo command with |
@whitequark Interestingly if I compile core with -C passes=mergefunc for x86_64-unknown-linux-gnu then I don't get any assertion. I get the assertion when cross compiling for thumbv7m-none-eabi. As for using --emit=llvm-ir it seems that the build crashes before rustc can emit any .ll file :-/ Here's the IR for the command |
I mean, of course it does if you keep |
(I wonder how is this different from generating the IR using an unpatched nightly given that the mergefunc pass is not enabled.) |
@japaric facedesk sorry. I have completely missed that I couldn't do that. |
[breaking-change]
check rust-embedded/cortex-m#51 for details
check rust-embedded/cortex-m#51 for details
I'm going to land this and all the pieces in other repos to ease exploring a solution for #50. I'm going to cut a release yet so feel free to comment on this or on the other PRs if you have any concern. @homunkulus r+ |
📌 Commit 6c642b7 has been approved by |
☀️ Test successful - status-travis |
check rust-embedded#51 for details
What
I propose that we change our exception / interrupt registration mechanism to
look like this:
This produces:
That is exceptions / interrupts can be overridden individually using the
exception!
/interrupt!
macro. And the default "catch everything" handlercan be overridden using the
default_handler!
macro.Why
Pros
Local
's borrow problem. It also makes using interrupt /exception local data much more ergonomic. No
Local
newtype or tokens; justdirect access to the local data through the
Locals
struct.This solves Default handlers get mono-morphized needlessly #19. So no duplicated handlers. All the exceptions / interrupts
handlers that have not been overridden point to the same default handler.
Using breakpoints is more uniform:
break HARD_FAULT
works regardless of howthe application is structured or whether is using RTFM (
break app::main::INTERRUPTS::TIM2
) or not.Less footgun-y. The current mechanism tries to be typed but requires the user
to place a variable with the right type at the right linker section. There's
no mechanism to prevent the user from placing wrong stuff in the right linker
section by mistake. Example:
misuses of the
exception!
/interrupt!
macros:Cons
Breaks the world. cortex-m, cortex-m-rt, cortex-m-quickstart, cortex-m-rtfm
and svd2rust would suffer breaking changes.
Requires that device crates provide an
interrupts.x
linker script (see the"how" section for details) so svd2rust would have to learn to generate that
linker script. We can drop this requirement if we are OK with not fixing Default handlers get mono-morphized needlessly #19
though (see alternatives).
How
The core idea is that, instead of requiring the user to place a variable /
struct at the right linker section, the cortex-m-rt and device crate will create
a vector table full of undefined symbols. The user can then override those
undefined symbols. The symbols that don't get overridden will resolve to a
default handler, which can also be overridden.
cortex-m-rt
This crate will place an array of undefined symbols in the
.vector_table.exceptions
section.The crate will also provide a default handler in the form of a weak
DEFAULT_HANDLER
symbol.Finally, the linker script provided by this crate will weakly bind the
undefined symbols to the
DEFAULT_HANDLER
symbol.cortex-m
The whole
ctxt
module will be removed, this includes theLocal
abstractionand the
Context
marker trait, as well asexception::default_handler
,exception::Handlers
,exception::DEFAULT_HANDLERS
and the exception "tokens"(
struct NMI { _0: () }
).This crate will provide a
default_handler!
and anexception!
macros thatprovide a type safe interface to override the default handler and an exception
handler respectively. The expansion of those macros is shown in the "expansion"
section.
svd2rust
svd2rust generated crates will no longer include interrupts tokens (
struct TIM2 { _0: () }
). They'll place an array of undefined symbols in the.vector_table.interrupts
section likecortex-m
does:And a linker script
interrupts.x
that weakly binds those undefined symbolsto
DEFAULT_HANDLER
.As well as an
interrupt!
macro that allows individually overriding thesesymbols. The expansion of that macro is shown in the next section.
Expansion
This is what the opening example expands to:
Unresolved questions
Check that this works: have a dependency override an exception handler. Check
that the top crate can't override the same handler. Check that the handler
makes it to the final binary.
fn SYS_TICK(local: &'static mut SYS_TICK::Locals)
orfn SYS_TICK(local: &mut SYS_TICK::Locals)
for the signature of exceptions / interrupts?.&mut T
and&'static mut T
are not equivalent. The latter has a bit strongerownership semantics. See below:
The reason I think we should not use
&'static mut
here is that some peoplemay create APIs that take a
&'static mut
argument assuming move semantics asin the receiver of the
&'static mut
assumes it has exclusive access to thestatic variable for the rest of the program. The problem is that exceptions
can be called more than once so something like this:
will effectively be mutably aliasing the
LOCALS
static variable every time theSYS_TICK
exception is called.Alternatives
default_handler!
andexception!
macros to thecortex-m-rt crate.
I'd prefer to keep the rt crate API-less. That way it's more likely that it will
only appear as a direct dependency of the top crate.
The downside of having those macros in the cortex-m crate is that if the
application doesn't link to the cortex-m-rt then the
default_handler!
andexception!
macros have no effect. The same applies to theinterrupt!
macrosin all the device crates though.
interrupts.x
requirement by having the cortex-m-rt and thedevice crates provide default weak implementations for their exceptions /
interrupts in their source code. Something like this:
However this brings back #19 :-(
Implementation bits
cc @whitequark @pftbest