Skip to content
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

app! macro take 2 #34

Merged
merged 49 commits into from
Jul 29, 2017
Merged

app! macro take 2 #34

merged 49 commits into from
Jul 29, 2017

Conversation

japaric
Copy link
Collaborator

@japaric japaric commented Jul 4, 2017

Update

More ports from RTFM v1 to this macro:


Full example

^ This is semantically the same example presented in #31, but with this new
macro 33% less code is required.

Next iteration of the rtfm! macro presented in #31. The most notable change wrt
the previous version is that the macro has become a compiler plugin procedural macro.

Pros

  • Automatic ceiling derivation. The ceiling will be computed from the
    information about which tasks use which resources.

  • Simpler task and resource specification. There's no split between interrupts
    used as tasks and exceptions used as tasks; there's only tasks now. Likewise
    there's no distinction between peripherals used as resources and data
    resources; there's only resources now.

  • More flexible syntax. The fields can be shuffled around without breaking the
    parser.

  • Simpler signatures as there's no Peripherals / Resources split.

  • Adaptive signatures. The signature of idle will be fn() -> ! if it makes no
    uses of resources and has no local data, but it will change to
    fn(idle::Local) if local data is used, etc.

  • Less claims. Full access to resources is given to tasks whose priority matches
    the resource ceiling.

  • More control over error messages related to the parsing and expansion of
    rtfm!.

Cons

rtfm! is now a compiler plugin. This makes the chance of it breaking across
nightly updates greater than zero. However, I've implemented the compiler plugin
on top of token streams so we should be safe from compiler changes related to
the language syntax and parser. Once function style macros becomes available via
the more stable proc-macros feature it should be straightforward to move to it.
And yes, I tried proc-macro-hack which allows function style proc macros on
top of custom derive proc macros but it doesn't work for no-std / cross
compilation because the hack requires a proc-macro crate compiled for the target
(which is not possible for thumb because proc-macro contains Strings).

Other changes

  • I realized that the change to scope based safety fixes Interoperation with cortex_m::peripheral::Peripheral.borrow is unsound #13 so I've added a
    borrow / borrow_mut method to resources to get direct access to them
    within a global critical section.

  • Given that we pretty much generate the implementation of the claim methods in
    the application crate I have changed things such that (NVIC) PRIORITY_BITS is
    no longer in the cortex-m-rtfm, but rather it's expected to be provided by the
    device crate. This way we don't need a bunch of P{2,3,..} Cargo features in
    the cortex-m-rtfm crate. OTOH, device generated crates now must provide a
    NVIC_PRIO_BITS constant that provides this information. SVD files contain
    this information in the form of the optional <nvicPrioBits> field.
    svd2rust will be taught to generate the NVIC_PRIO_BITS constant.

  • The declaration of task local data and the task function path has been moved
    into a task! macro. This macro doesn't have to be in the root of the crate.

cc @whitequark

pieces.push(tt);
}
} else {
panic!("expected type, found EOM");

Choose a reason for hiding this comment

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

Nit: maybe expand EOM to end of macro? It took me a few moments to recognize.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will do

interrupts.push(quote! {
let prio_bits = #device::NVIC_PRIO_BITS;
let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits);
nvic.set_priority(#device::Interrupt::#name, hw);

Choose a reason for hiding this comment

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

Nit: could this be extracted as another function on NVIC? Seems useful.

Copy link
Collaborator Author

@japaric japaric Jul 5, 2017

Choose a reason for hiding this comment

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

you mean adding a set_logical_priority to NVIC or adding a logical2hw function to map a logical priority to a "hardware" value that can be directly written to an NVIC register?

Choose a reason for hiding this comment

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

former

@whitequark
Copy link

Outstanding work @japaric! You've addressed nearly every concern of mine, except:

  • Regarding explicit specification of resources for the init function, I still think it's somewhat pointless, but I concede that it's not worth the added implementation complexity. One could argue that there's the benefit of seeing an overview of every resource initialized.
  • The naming of Local. It's not really important.

@japaric
Copy link
Collaborator Author

japaric commented Jul 6, 2017

@whitequark Thanks

Regarding explicit specification of resources for the init function, I still think it's somewhat pointless, but I concede that it's not worth the added implementation complexity.

I was thinking about this and it would be easy to change the implementation such that init has access to all the resources declared in other tasks. The issue I see with this approach is that init is likely going to require more resources, specially peripherals (e.g. RCC), than what the other tasks use so you would still need to list more resources in init.resources.

The problem with giving init access to all the peripherals is that there's no such list that the rtfm! macro can use. At least not right now. I was thinking of adding a Peripherals struct to device crates which contains a reference to each and every peripheral / register block and make its constructor unsafe. With that change the init signature could be changed to fn($device::Peripherals, Resources) to make it have access to all the device peripheral and all the application resources. I'll give this a try.

@japaric
Copy link
Collaborator Author

japaric commented Jul 6, 2017

the last commit removes init.resources from rtfm! and makes idle.local optional. The change depends on rust-embedded/svd2rust#123

Diff of the example shown below:

--- a/0-example.rs	2017-07-06 17:54:13.908406632 -0500
+++ b/0-example.rs	2017-07-06 17:55:23.428268225 -0500
@@ -42,25 +41,23 @@

     init: {
         path: init,
-        resources: [AFIO, GPIOA, GPIOC, TIM2, RCC, USART1],
     },

     idle: {
-        local: {},
         path: idle,
         resources: [DWT, SLEEP_CYCLES],
     },
 }

-fn init(r: init::Resources) {
-    let timer = Timer(r.TIM2);
-    let serial = Serial(r.USART1);
+fn init(p: init::Peripherals, _: init::Resources) {
+    let timer = Timer(p.TIM2);
+    let serial = Serial(p.USART1);

-    led::init(r.GPIOC, r.RCC);
+    led::init(p.GPIOC, p.RCC);

-    serial.init(BAUD_RATE.invert(), r.AFIO, None, r.GPIOA, r.RCC);
+    serial.init(BAUD_RATE.invert(), p.AFIO, None, p.GPIOA, p.RCC);

-    timer.init(FREQUENCY.invert(), r.RCC);
+    timer.init(FREQUENCY.invert(), p.RCC);
     timer.resume();
 }

@@ -122,4 +119,4 @@

     let byte = serial.read().unwrap();
     serial.write(byte).unwrap();
-}
\ No newline at end of file
+}

- allow trailing commas in list of resources

- make task.resources optional

- add rtfm::set_pending function which can be used to force an interrupt into
  the pending state. This is a replacement of the old rtfm::request.
  rtfm::set_pending takes the Interrupt enum provided by the device crate as
  argument. (The old rtfm::request took a task function as argument)

- the user may want to use types they imported into the root of the crate. These
  types are not available in e.g. `mod idle` so `idle::Resources` *can't* be
  defined in that module. To workaround this problem `idle::Resources` will be
  defined in the root, with some other name, and then be re-exported in the
  `idle` module.

- remove the "a resource only used by one task should be local data" check. In
  some cases you do want a resource owned by a single task instead of local
  data since `init` can access resources but not a task local data.
@japaric
Copy link
Collaborator Author

japaric commented Jul 7, 2017

I have ported two non trivial applications to the latest rtfm! version from cortex-m-rtfm v0.1.1. I'm quite happy with the result. The Cells and RefCells are gone; the task code is shorter, mainly because no access calls are needed with the new rtfm!; I even saw a drop in CPU usage of 0.5% (15% -> 14.5%) in my most demanding app just from eliminating the RefCells. (I also saw a fixed drop in binary size of about 480 bytes but I think this is mainly due to changes in cortex-m-rt)

@whitequark
Copy link

nice!

@japaric
Copy link
Collaborator Author

japaric commented Jul 11, 2017

I have now ported all the examples in the blue-pill crate to this macro (japaric/stm32f103xx-hal#19). The result in a reduction in LoC of about 30% in average.

In another news this has already broke due to a change in rustc:

error[E0425]: cannot find function `set_parse_sess` in module `proc_macro::__internal`

@whitequark
Copy link

@japaric Why are you using proc macro internals?

@japaric
Copy link
Collaborator Author

japaric commented Jul 11, 2017

@whitequark I thought that functionlike! proc macros weren't implemented given the existence of proc-macro-hack and the lack of documentation about macros 2.0 so I was using a compiler plugin. But I figured out how to use #![proc_macro] so I'll switch to that.

@japaric
Copy link
Collaborator Author

japaric commented Jul 15, 2017

Some changes:

  • the rtfm! macro has been renamed to app!
  • the macro parser has been refactored into its own crate. The main motivation for this is to share code between this crate and the port of the tasks and resources model to the MSP430 architecture. Yup, you can now use this very same macro to write concurrent applications on MSP430 🎉.
  • I have made the fields init.path and idle.path optional. If omitted these fields default to the paths init and idle respectively, which is what most people will use, I expect.
  • I have replaced all the panic! / unwraps in the proc macro implementation with proper error handling (error-chain ❤️). You now get nice backtraces when there's an error in the contens of the app! macro. For example:
app! {
    device: blue_pill::stm32f103xx,

    tasks: {
        SYS_TICK: {
            priority: 1,
            resources: { SYST }, // <- wrong delimiter
        },
    },
}
error: proc macro panicked
  --> examples/blinky.rs:16:1
   |
16 | / app! {
17 | |     device: blue_pill::stm32f103xx,
18 | |
19 | |     tasks: {
...  |
24 | |     },
25 | | }
   | |_^
   |
   = help: message: Error: parsing
           Caused by: parsing `tasks`
           Caused by: parsing task `SYS_TICK`
           Caused by: parsing `resources`
           Caused by: expected Bracket, found Brace

I believe this is good as the error messages can get without proper span information.

@whitequark
Copy link

This is amazing work.

@homunkulus
Copy link
Contributor

⌛ Trying commit 7d0c07c with merge 7d0c07c...

@homunkulus
Copy link
Contributor

💔 Test failed - status-travis

japaric added 5 commits July 27, 2017 15:15
and move the logic that differentiates interrupts from exceptions from the crate
to the procedural macro logic
@japaric
Copy link
Collaborator Author

japaric commented Jul 29, 2017

@homunkulus try

@japaric
Copy link
Collaborator Author

japaric commented Jul 29, 2017

@homunkulus r+

@homunkulus
Copy link
Contributor

📌 Commit 3d2be6d has been approved by japaric

@japaric
Copy link
Collaborator Author

japaric commented Jul 29, 2017

@homunkulus r+

@homunkulus
Copy link
Contributor

📌 Commit 697c2d6 has been approved by japaric

@japaric
Copy link
Collaborator Author

japaric commented Jul 29, 2017

@homunkulus r+

@homunkulus
Copy link
Contributor

📌 Commit 2d80f36 has been approved by japaric

@homunkulus
Copy link
Contributor

⌛ Testing commit 2d80f36 with merge 2d80f36...

@homunkulus
Copy link
Contributor

☀️ Test successful - status-travis
Approved by: japaric
Pushing 2d80f36 to master...

@homunkulus homunkulus merged commit 2d80f36 into master Jul 29, 2017
@japaric japaric deleted the take2 branch July 29, 2017 05:51
japaric pushed a commit that referenced this pull request Sep 10, 2021
34: [RFC] Mod instead of const r=korken89 a=AfoHT

Link to [rendered RFC](https://github.com/AfoHT/rfcs/blob/mod_const/text/0000-use-mod-over-const.md)

Implemented and ran the test suites and examples, noticed no side-effecs.

https://github.com/AfoHT/rtfm-syntax/tree/mod_const

https://github.com/AfoHT/cortex-m-rtfm/tree/mod_const

Co-authored-by: Henrik Tjäder <henrik@tjaders.com>
andrewgazelka pushed a commit to andrewgazelka/cortex-m-rtic that referenced this pull request Nov 3, 2021
This is one possible solution to the stack overflow problem described in rtic-rs#34. This approach uses a
linker wrapper, called [swap-ld], to generate the desired memory layout. See rtic-rs#34 for a description
of the desired memory layout and rtic-rs#41 for a description of how `swap-ld` works.

The observable effects of this change in cortex-m programs are:

- the `_sbss` symbol is now override-able.
- there is now a `.stack` linker section that denotes the span of the call stack. `.stack` won't be
  loaded into the program; it just exists for informative purposes (`swap-ld` uses this
  information).

Given the following program:

``` rust
fn main() {
    static mut X: u32 = 0;
    static mut Y: u32 = 1;

    loop {
        unsafe {
            ptr::write_volatile(&mut X, X + 1);
            ptr::write_volatile(&mut Y, Y + 1);
        }
    }
}
```

If you link this program using the `arm-none-eabi-ld` linker, which is the cortex-m-quickstart
default, you'll get the following memory layout:

``` console
$ console
section                                                                   size         addr
.vector_table                                                            0x130    0x8000000
.text                                                                     0x94    0x8000130
.rodata                                                                    0x0    0x80001c4
.stack                                                                  0x5000   0x20000000
.bss                                                                       0x4   0x20000000
.data                                                                      0x4   0x20000004
```

Note how the space reserved for the stack (depicted by the `.stack` linker section) overlaps with
the space where .bss and .data reside.

If you, instead, link this program using `swap-ld` you'll get the following memory layout:

``` console
$ arm-none-eabi-size -Ax app
section                                                                   size         addr
.vector_table                                                            0x130    0x8000000
.text                                                                     0x94    0x8000130
.rodata                                                                    0x0    0x80001c4
.stack                                                                  0x4ff8   0x20000000
.bss                                                                       0x4   0x20004ff8
.data                                                                      0x4   0x20004ffc
```

Note that no overlap exists in this case and that the call stack size has reduced to accommodate the
.bss and .data sections.

Unlike rtic-rs#41 the addresses of static variables is now correct:

``` console
$ arm-none-eabi-objdump -CD app
Disassembly of section .vector_table:

08000000 <_svector_table>:
 8000000:       20004ff8        strdcs  r4, [r0], -r8 ; initial Stack Pointer

08000004 <cortex_m_rt::RESET_VECTOR>:
 8000004:       08000131        stmdaeq r0, {r0, r4, r5, r8}

08000008 <EXCEPTIONS>:
 8000008:       080001bd        stmdaeq r0, {r0, r2, r3, r4, r5, r7, r8}
 (..)

Disassembly of section .stack:

20000000 <.stack>:
        ...

Disassembly of section .bss:

20004ff8 <cortex_m_quickstart::main::X>:
20004ff8:       00000000        andeq   r0, r0, r0

Disassembly of section .data:

20004ffc <_sdata>:
20004ffc:       00000001        andeq   r0, r0, r1
```

closes rtic-rs#34

[swap-ld]: https://github.com/japaric/swap-ld
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Interoperation with cortex_m::peripheral::Peripheral.borrow is unsound
3 participants