Disable 'cannot be sent between threads safely'

In attempting a project on the target 'Arm Cortex-M0+ RP2040', I would like to disable this thread safety check. The hardware fails the test '#[cfg(target_has_atomic = "ptr")]'. That means I can't use 'use alloc::sync::Arc;'. This then means, that I can't keep a vector of function pointers for callback patterns of programming.

For example, with type:

pub enum ObjectCatalog<'a> {
    String(String),
    Callback(&'a dyn FnMut()),
}

I get ... "Rc<RefCell<Vec<ObjectCatalog<'static>>>> cannot be sent between threads safely"

I'm aware of the limitations of the CPU, I'm purposely using coroutine style of programming with RTIC -- all processes are at the same priority level so there is no arbitrary task switching. My program is only in one core, the other core will have C code in it and they communicate by the hardware supported inter-core message passing mechanism.

Without being able to disable this check on the compiler, I basically can't use Rust on an M0 CPU. This is one of the goals of Rust yes/no? Embedded programming used at a systems/hardware level?

You can manually implement Send for a wrapper around Rc to effectively accomplish turning off the error. Whether or not that could lead to issues even if the program is limited to a single core, I'm not sure.

2 Likes

I guess it will, most operating systems in the embedded field has thread features even if there is only one core on the board, impl Send and Sync without consideration is really a footgun, that's my experience.

2 Likes

This will also effectively remove any !Send protection for the closures, which is less than ideal. It’s probably best to define the Callback variant as &'a dyn Fn() + Send + Sync, so that non-threadsafe callbacks are rejected.

1 Like

Arc isn't a fundamental part of Rust. It's just a library type, defined in std, which itself is a safe layer over the capabilities of an "average" OS. As safe defaults, it behaves as it should. But you are using an OS with special behavour which requires special consideration. On embedded, it's common to use #![no_std] to entirely disable the standard OS wrappers. Then you write yourself or download a library which is compatible with your hardware and OS capabilities.

Whether it is sufficient for your use case to just wrap an Rc in a Send wrapper, or whether you need some more complex primitive, I can't say without digging into the details of your use case and OS.

2 Likes

Well thanks for all the replies. Some callback cases can be satisfied with &'a dyn Fn() + Send + Sync, for others' I'll try doing my own false Send implementation.

Just to clarify, there is no OS in this implementation. I've defined my own allocator and am using crate 'cortex-m-rtic' to manage tasks. With all tasks at the same priority, it is like time sharing, one function will not begin until the current one finishes. There is exception to this, handling hardware interrupts for USB device and that has to be managed by dev. This issue is that M0+ ARM is armv6 and only armv7 has the atomic pointer feature needed by create alloc::sync::Arc (yes, no std Arc use attempt was made as no std lib is used here).

Only very early ARM CPUs couldn't implement Arc. As in: precisely two models in the whole history of ARM. ARM1 and ARM2. That's it.

Because only original ARM1 and ARM2 CPUs don't include swp/swpb needed for that.

The simplest implementation would just use low byte for mutex (like ARM documentations recommends) and then you can use remaining three bytes for counter.

Would mean that Arc would only support 16777215 simultaneous pointers… should be enough for embedded.

Afaik this is not correct.

You're second link says that SWP is not supported in the Thump instruction set, which is the only IS supported by Cortex-M(0+).

Alternatively, you could use the atomic-polyfill crate, which uses critical sections to emulate atomics on embedded targets without atomic support. (The regular atomics are reexported where they're supported or critical sections aren't available to Rust yet.) They claim full support for thumbv6m (and thumbv4t).

If there's targets with byte atomics but not word atomics, it might be useful for atomic-polyfill to offer atomics implemented using the low-byte mutex trick (if they don't already) so it's easier to get non-global-mutex atomics on those targets.

embassy-sync (discovered via atomic-polyfill rdeps) offers primitives that might be helpful. It has channels, at least. heapless has an Arc and also uses atomic-polyfill.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.