Description
Summary
Extend the syntax of the resources
field to let the user specify when shared,
instead of exclusive (today's default), access to a resource is required.
Specifying shared access can reduce the number of required critical sections in
some scenarios.
Background
Consider this example:
#[rtfm::app(device = ..)]
const APP: () = {
// NOTE: `ComplexThing` implements the `Sync` trait
static mut FOO: ComplexThing = /* .. */;
// ..
#[task(priority = 1, resources = [FOO])]
fn foo() {
resources.FOO.lock(mutate);
}
#[task(priority = 2, resources = [FOO])]
fn bar() {
resources.FOO.lock(print);
}
#[task(priority = 3, resources = [FOO])]
fn baz() {
print(resources.FOO);
}
// ..
};
fn mutate(foo: &mut ComplexThing) {
// ..
}
fn print(foo: &ComplexThing) {
println!("{}", foo);
}
With the current rules, the task bar
needs a critical section to access FOO
.
However, that critical section is not really needed because task baz
cannot
modify FOO
.
Design
The parser will be modified to accept both $ident
and &$ident
in lists of
resources. The former syntax, which exists today, indicates exclusive access to
the resource -- its semantics are unchanged; the latter syntax indicates shared
access to the resource.
When shared access is specified the task will receive a shared reference (&_
)
to the resource data rather than a Mutex
proxy.
If shared access is requested from two or more tasks that run at different
priorities then the resource type must implement the Sync
trait. This is
required to prevent types with interior mutability (like Cell
and RefCell
)
from being accessed without a critical section, and potentially modified, from
those tasks as that could result in UB.
With the proposed changes our opening example can be updated as follows:
#[rtfm::app(device = ..)]
const APP: () = {
// ..
#[task(priority = 1, resources = [FOO])]
fn foo() {
resources.FOO.lock(mutate);
}
// NOTE: `&FOO` instead of `FOO` in `resources`
#[task(priority = 2, resources = [&FOO])]
fn bar() {
// NOTE: no critical section is needed this time
let reference: &ComplexThing = resources.FOO;
print(reference);
}
// NOTE: `&FOO` instead of `FOO` in `resources`
#[task(priority = 3, resources = [&FOO])]
fn baz() {
print(resources.FOO);
}
// ..
};
Unresolved questions
It's unclear what to do in this scenario, other than raise a compiler error:
#[rtfm::app(device = ..)]
const APP: () = {
// ..
#[task(priority = 1, resources = [&FOO])]
fn foo() { /* .. */ }
#[task(priority = 2, resources = [FOO])]
fn bar() { /* .. */ }
No optimization can be applied here. The task foo
needs a critical section to
access the resource data, in any form, but the lock
API doesn't make sense
here because that grants an exclusive reference (&mut _
) to the data and
shared access was requested.