Skip to content

Resource proxy mutabability, mutex Trait and closure captures (async fn)  #36

@perlindgren

Description

@perlindgren

Resource proxy mutability

Mutable references to resource proxies

Currently in RTIC/RTFM we need a mutable reference to the resource proxies:

#[task(binds = GPIOA, resources = [shared])]
    fn gpioa(mut c: gpioa::Context) {
        // the lower priority task requires a critical section to access the data
        c.resources.shared.lock(|shared| {
            // data can only be modified within this critical section (closure)
            *shared += 1;
            c.resources.shared.lock(|shared2| { // <- here Rust would spot the error (*ERROR*)
                // both shared and shared2 would point to the same location

Rust requires that no two references in scope points to the same location. We use the borrow checker to ensure that at compile time.

Pros

  • Statically checked, error messages upfront.
  • Zero run-time cost (no ref counting).
  • ...

Cons

  • The corresponding Mutex trait requires a mutable reference to the resource proxy. This has proven difficult to adopt for general purpose use.
  • Mutable references cannot be captured by multiple closures, making it difficult for async functions to use RTIC resources.
  • ...

Alternative approach

Currently lock optimizations is done by the llvm backend using "meta data" holding the current priority. This is done by passing an additional parameter (priority), that is optimized out.

pub unsafe fn lock<T, R>(
    ptr: *mut T,
    priority: &Priority,
    ceiling: u8,
    nvic_prio_bits: u8,
    f: impl FnOnce(&mut T) -> R,
) -> R {
    let current = priority.get();

    if current < ceiling {
        if ceiling == (1 << nvic_prio_bits) {
            priority.set(u8::max_value());
            let r = interrupt::free(|_| f(&mut *ptr));
            priority.set(current);
            r
        } else {
            priority.set(ceiling);
            basepri::write(logical2hw(ceiling, nvic_prio_bits));
            let r = f(&mut *ptr);
            basepri::write(logical2hw(current, nvic_prio_bits));
            priority.set(current);
            r
        }
    } else {
        f(&mut *ptr)
    }
}

We could think of a similar approach, but applied to the resource proxies. In this case it would be reference counter (in fact a locked: bool would do). All resource proxies in the context could be initialised to false, and when locked set to true and when unlocked reset to false. When locking we could panic in case the (locked == true), indicating *ERROR* in the code above.

Pros

  • We could adhere to a relaxed requirement on the resource proxy (now &resource_proxy, instead of &mut resource_proxy), allowing the Mutex trait to be relaxed consequently.
  • Would allow sharing of resource proxies to closures, such as async functions.

Cons

  • Runt time checking instead of static analysis. (We cannot be sure without further analysis that code will not panic.)
  • Memory OH (likely a byte for each resource, or maybe even a word depending on padding). Even if llvm optimizes out the check, the memory for the locked field will still be allocated (but never touched).
  • Run-time OH in case llvm fails to optimize out the check.

Soundness

As we panic if a "double lock" occurs it should be safe.

Complexity of implementation

Fairly low.

Breaking change?

The change will likely not break current code, since mutable references will be downgraded to immutable references.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions