Skip to content
This repository has been archived by the owner on Jan 24, 2022. It is now read-only.

turn macros into attributes #90

Merged
merged 13 commits into from
Sep 6, 2018
Merged

turn macros into attributes #90

merged 13 commits into from
Sep 6, 2018

Conversation

japaric
Copy link
Member

@japaric japaric commented Aug 18, 2018

This is a PoC implementation of RFC #82. Assuming that we are OK with this implementation we should
add some compile fail tests (e.g. using a function with the wrong signature as the entry point)
before landing this.

Look at the diff of the examples to get an overview of the changes.

cc @rust-embedded/cortex-m

@japaric
Copy link
Member Author

japaric commented Aug 18, 2018

These attributes can be used on the latest nightly without any feature gate (use_extern_macros was stabilized yesterday) and you will be able to use them on the next beta.

This is a breaking change because it removes the macros by example.

Instead of directly breaking the rt crate we could have an intermediate test phase where we first publish the rt-macros crate. Then people would be able to test out the attributes by changing their programs in the following way:

 #![no_main]
 #![no_std]

-#[macro_use(entry)]
 -extern crate cortex_m_rt as rt;
+extern crate cortex_m_rt_macros as rt;
 extern crate panic_semihosting;

-entry!(main);
+use rt::entry;

+#[entry]
 fn main() -> ! {
     loop {}
 }

Though I'm not sure if there's any advantage to doing the release that way.

pub fn #ident() {
extern crate cortex_m_rt;

cortex_m_rt::Exception::#name;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamgreig this is reason why an hypothetical #[interrupt] attribute would need the device crate name in its argument. We want to check that the interrupt exists and emit a compiler error if it doesn't and for that we need the full path to the Interrupt variant: e.g. stm32f103xx::Interrupt::EXTI0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I guess we could require that whatever is passed in (to some future #[interrupt]) is resolvable to an Interrupt, so it could be either a full path, or the user could use stm32f103::Interrupt::* and then just call #[interrupt(EXTI0)]?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the user could use stm32f103::Interrupt::* and then just call #[interrupt(EXTI0)]?

If we omit the full path that would kind of defeat the purpose of the check. For example, this would compile:

struct InterruptThatDoesntExist;

#[interrupt(InterruptThatDoesntExist)]
fn handler() { /* .. */ }

Though thinking about it more having the user enter the name of the device crate makes it possible to nullify the check. For example,

extern crate stm32f103xx; // real device crate

mod stm32f1337 {
    enum Interrupt {
        InterruptThatDoesntExist;
    }
}

#[interrupt(stm32f1337, InterruptThatDoesntExist)]
fn handler() { /* .. */ }


fn sys_tick(state: &mut u32) {
*state += 1;
#[exception(SysTick, static STATE: u32 = 0)]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative syntax would be to have the second argument be a string. i.e.

#[exception(SysTick, "static STATE: u32 = 0")]
fn sys_tick() {}

But the current syntax seems to parse without problems and have better spans for error messages. e.g.

#[exception(SysTick, static STATE: u32 = true)]
//                                       ~~~~ error: expected `u32`, found `bool`
fn sys_tick() {}

@therealprof
Copy link
Contributor

@japaric Lovely, I like it!

@japaric
Copy link
Member Author

japaric commented Aug 18, 2018

Error messages are terrible by default because all the span information is lost. By this I mean that any compile error caused by the body of e.g. an exception function will be reported with the span of the attribute. Example:

#[entry]
// ~~~~~ error: mismatched types, expected `!`, found `()`
fn main() -> ! {
    asm::bkpt()
}

To improve you can opt into the nightly feature of the proc-macro2 crate like this:

# Cargo.toml
[build-dependencies.proc-macro2]
version = "0.4"
features = ["nightly"]

But, as the name of the feature implies, this requires a nightly compiler. The error message then becomes what you would expect:

#[entry]
fn main() -> ! {
    asm::bkpt()
//  ~~~~~~~~~~~ error: mismatched types, expected `!`, found `()`
}

I don't know if it's planned to stabilize these span improvements for the edition release.

@japaric
Copy link
Member Author

japaric commented Aug 31, 2018

I have rebased this PR.

@rust-embedded/cortex-m Please vote on RFC #82 by reviewing (approving) this PR.

Amendments to #82

  • The unresolved question about how to support state in the #[exception] attribute has been solved with the following syntax:
#[exception(SysTick, static STATE: u32 = 0)]
fn sys_tick() { // <- note that the function has no argument
    *STATE += 1; // but that `STATE` can be referenced only within this function
}

Also a reminder that both the proc_macro and the use_extern_macro features has been already stabilized in nightly so you'll be able to use these attributes in the next beta release (2 weeks from now) so I think it's OK to land this right now -- it'll be in a new minor version (v0.6.0) since it's a breaking change.

@japaric japaric mentioned this pull request Aug 31, 2018
@japaric
Copy link
Member Author

japaric commented Aug 31, 2018

I just thought of an alternative syntax for adding state that may feel less strange:

#[exception(SysTick)]
fn sys_tick() {
    static mut STATE: u32 = 0;

    // user code
    *STATE += 1;
}

The attribute will magically make static mut variables safe (no unsafe) to access. It will also cause their type to change to &mut Type. The expansion would look like this:

#[export_name = "SysTick"]
pub fn sys_tick() {
    // <expansion>
    static mut STATE_: u32 = 0;

    let STATE: &mut u32 = unsafe { &mut STATE_ };
    // </expansion>

    // user code
    *STATE += 1;
}

@japaric
Copy link
Member Author

japaric commented Aug 31, 2018

And now I'm seeing a problem with the current implementation: it allows data races in safe code. For example:

#[exception(SysTick)]
fn sys_tick() {
    static mut STATE: u64 = 0;

    // user code
    *STATE += 1;
}

#[entry]
fn main() -> ! {
    loop {
        sys_tick();
    }
}

Here main and SysTick handler race for access to STATE -- this is UB.

The fix is to either (a) make sys_tick unsafe to call, or (b) make sys_tick impossible to call. If we do the latter then we can simplify the attribute a bit:

#[exception]
fn SysTick() {
    static mut STATE: u32 = 0;

    // user code
    *STATE += 1;
}

#[entry]
fn main() -> ! {
    loop {
        SysTick(); //~ error: this function doesn't exist
    }
}

We can use the function name as the exception name; that way we don't need an argument. The above code would expand to:

#[export_name = "SysTick"]
pub fn cf1dglqbt3jr7uiu() { // some randomly generated string that the user can't rely on
    // <expansion>
    static mut STATE_: u32 = 0;

    let STATE: &mut u32 = unsafe { &mut STATE_ };
    // </expansion>

    // user code
    *STATE += 1;
}

#[entry]
fn main() -> ! {
    SysTick(); // error: this function doesn't exist
}

@korken89
Copy link
Contributor

I'm not sure I'm a fan of "magically" making statics safe, but I also do not have any good input for a better solution, though the original idea was quite nice on the eyes and properly conveys that there is a state.
Why not keep this syntax? I can't quite see the issue with it.

@japaric
Copy link
Member Author

japaric commented Aug 31, 2018

@korken89 with the static in the attribute it's not clear that the static is only visible to the function or if it's in the global / outer scope since it's between those two spaces. Also there's no precedent for attributes that have statics as arguments so that syntax will look odd to most people.

With the static inside the function it's immediately clear that it can't be referred to from outside the function. I believe that making it safe to access it's not surprising exactly because it's local to the function.

@korken89
Copy link
Contributor

I see, and agree. Having the static in the function would indeed be cleaner.

@therealprof
Copy link
Contributor

@japaric Yes, please! The local state definition for the interrupt handlers was very weird in the past and caused lots of weird syntactic problems with non-integral types which should be all gone with the new syntax. This is very similar to something a C/C++ programmer might do, too, so that's also a big plus. And last but not least this reduces the boilerplating quite a bit and makes things safer (by preventing calling an interrupt handler from another function) so goodies all around!

@japaric
Copy link
Member Author

japaric commented Aug 31, 2018

OK, I have made the exception handlers uncallable and also change the syntax of #[exception]: no arguments required and the state must now be declared as static mut variables at the beginning of the function body.

@japaric
Copy link
Member Author

japaric commented Aug 31, 2018

Also, we can use the static mut trick in #[entry] too. That will turn local static mut variables into &'static mut references. We would have to make the #[entry] function uncallable as well.

@japaric
Copy link
Member Author

japaric commented Sep 3, 2018

latest commit makes it safe to access local static mut variables in the entry point.

@therealprof @adamgreig @korken89 in #82 it sounded like you were in favor of this change. Could you confirm?

@thejpster @ithinuel I'd love to get your input on RFC #82 and this implementation. Are you in favor of that RFC or do you have concerns about it?

@adamgreig
Copy link
Member

I really like the new no-argument attributes and the new syntax for state. Having mutable statics work in entry would solve a bunch of nuisance problems I've had in the past with initialising statics, for example setting up buffers for smoltcp. As far as I can tell this will actually be extremely useful for a lot of embedded development and maybe warrants a little more exposition in the documentation, but that can come later when people have a bit more experience with it.

👍 from me!

therealprof
therealprof previously approved these changes Sep 3, 2018
Copy link
Contributor

@therealprof therealprof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic! 👍

korken89
korken89 previously approved these changes Sep 4, 2018
Copy link
Contributor

@korken89 korken89 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, great work!

ithinuel
ithinuel previously approved these changes Sep 4, 2018
Copy link
Member

@ithinuel ithinuel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :) The macros are definitely fancier.

My only "concern" would be about the stability of proc-macro that I don't know. Other than that it looks great !

@japaric
Copy link
Member Author

japaric commented Sep 5, 2018

@ithinuel

The proc macros I'm using here are planned to be stable by the 2018 edition; they don't require feature gates in nightly; and they won't require features gates in RC1 (next beta) and RC2 (next next beta).


Alright, thanks for the input everyone. We have majority so this is officially accepted.

I'm going to add some compile fail tests then any of you can review and land this.

we'll test on beta when 1.30-beta is out
they are no longer required due to rust-lang/rust#52467
@japaric japaric dismissed stale reviews from ithinuel, korken89, and therealprof via de35ebb September 5, 2018 22:27
@japaric japaric changed the title [DO NOT MERGE] turn macros into attributes turn macros into attributes Sep 5, 2018
@japaric
Copy link
Member Author

japaric commented Sep 5, 2018

This should be ready to land, but while adding the compile-fail tests I noticed that we could relax some constraints. I'll leave some inline comments about that.


#[exception] //~ ERROR custom attribute panicked
//~^ HELP `DefaultHandler` exception must have signature `fn(i16)`
unsafe fn DefaultHandler(_irqn: i16) {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this? I don't see a problem with letting the user state this is unsafe because this function can't be called from software anyways.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 from me, no reason to not permit this, and I wouldn't be surprised if a lot of unsafe code in embedded projects is in the ISRs.


#[exception] //~ ERROR custom attribute panicked
//~^ HELP `DefaultHandler` exception must have signature `fn(i16)`
fn DefaultHandler(_irqn: i16) -> ! {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this? I see no problem with letting this be a divergent function.


#[entry] //~ ERROR custom attribute panicked
//~^ HELP `#[entry]` function must have signature `fn() -> !`
unsafe fn foo() -> ! {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this? Same rationale as allowing unsafe DefaultHandler.


#[exception] //~ ERROR custom attribute panicked
//~^ HELP `#[exception]` functions other than `DefaultHandler` and `HardFault` must have signature `fn()`
unsafe fn SysTick() {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this? Same rationale as unsafe DefaultHandler


#[exception] //~ ERROR custom attribute panicked
//~^ HELP `#[exception]` functions other than `DefaultHandler` and `HardFault` must have signature `fn()`
fn SysTick() -> ! {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this? Same rationale as divergent DefaultHandler


#[exception] //~ ERROR custom attribute panicked
//~^ HELP `HardFault` exception must have signature `fn(&ExceptionFrame) -> !`
unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow this? Same rationale as unsafe DefaultHandler

@adamgreig
Copy link
Member

Rather than commenting inline on each one: I'm happy to allow all of those unsafe/divergent functions. There's no safety reasons not to, so I'd rather let the user decide.

@japaric
Copy link
Member Author

japaric commented Sep 5, 2018

Ok, I have relaxed the signature checks and added more compile-fail tests and examples (which I'm using as compile-pass tests). I have also made the reachability requirement more prominent in the docs.

@adamgreig
Copy link
Member

Looks great! I've gone through the new code and also tested this branch on a few small examples on hardware with exceptions and pre_init and everything seems to work as expected.

bors r+

bors bot added a commit that referenced this pull request Sep 6, 2018
90: turn macros into attributes r=adamgreig a=japaric

This is a PoC implementation of RFC #82. Assuming that we are OK with this implementation we should
add some compile fail tests (e.g. using a function with the wrong signature as the entry point)
before landing this.

Look at the diff of the examples to get an overview of the changes.

cc @rust-embedded/cortex-m

Co-authored-by: Jorge Aparicio <jorge@japaric.io>
@bors
Copy link
Contributor

bors bot commented Sep 6, 2018

Build succeeded

@bors bors bot merged commit 82e985b into master Sep 6, 2018
@bors bors bot deleted the attr branch September 6, 2018 00:49
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants