Skip to content

How should we expose atomic load/store on targets that don't support full atomics #99668

Description

@Amanieu

Some embedded targets (thumbv6m, riscv32i) don't support general atomic operations (compare_exchange, fetch_add, etc) but do support atomic load and store operations. We previously supported this on thumbv6m (but not riscv32i, see #98333) by cfging out most of the methods on Atomic* except for load and store. However recent changes to LLVM (#99595) cause it to emit calls to libatomic for load and store (it already does this for the other atomic operations).

It seems that LLVM's support for lowering atomic loads and stores on ARM was an accident that is being reverted. However the reason for this revert is that the directly lowered load/store cannot interoperate correctly with the other atomic operations which are lowered to libcalls. This concern doesn't apply to Rust since we don't expose emulated (read: not lock free) atomics, so there is no interoperability concern (except maybe for FFI with C that uses atomics?).

I see 2 ways we can move forward:

  1. Continue supporting atomic load/store on such targets by lowering them in rustc to a volatile load/store. This will avoid breakage in the embeded Rust ecosystem.
  2. Remove support for atomic load/store on targets that don't support full atomics. Users are expected to switch to using volatile load/store instead.

Activity

  1. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jul 24, 2022
  2. jamesmunns commented on Jul 24, 2022

    @jamesmunns
    Contributor

    Just wanted to add my $0.02, option two, "Remove support for atomic load/store on targets that don't support full atomics", would be a breaking change to (at least) one Tier 2 target: thumbv6m-none-eabi.

    It would break a non-trivial number of embedded projects (there are lots of Cortex-M0+ cores out there, including the very popular RP2040), and might not be picked up by a crater run, as crate may not exercise specific targets during the run.

  3. jamesmunns commented on Jul 24, 2022

    @jamesmunns
    Contributor

    Also just as a note, we might want to check in that armv5te still builds with these relevant changes. It has "native" load/stores, but no CAS operations. These are provided by an OS-level polyfill for the linux target.

    It's likely their load/stores will also be affected by any change here.

  4. Amanieu commented on Jul 24, 2022

    @Amanieu
    MemberAuthor

    AFAIK armv5te should not be affected by this change, but we should double check with the upgrade to LLVM 15.

  5. adamgreig commented on Jul 24, 2022

    @adamgreig
    Contributor

    Currently the core::ptr docs say:

    All accesses performed by functions in this module are non-atomic in the sense of atomic operations used to synchronize between threads. This means it is undefined behavior to perform two concurrent accesses to the same location from different threads unless both accesses only read from memory. Notice that this explicitly includes read_volatile and write_volatile: Volatile accesses cannot be used for inter-thread synchronization.

    One downside of requiring users on these platforms to use volatile ops instead is that (in Rust) volatile is a property of an access (so a static UnsafeCell/static mut could still be accessed with a non-volatile operation) while the Atomic types only have atomic operations (via AtomicU8, AtomicBool, etc), and I don't think there's any appetite for VolatileU32 or similar.

    There is the atomic-polyfill crate which is quite popular in embedded Rust and provides CAS operations on thumbv6 etc while directly forwarding core::sync::atomic on other platforms. If option 2 was chosen, probably this crate could help smooth out a transition.

  6. taiki-e commented on Jul 24, 2022

    @taiki-e
    Member

    the atomic-polyfill crate

    That is unsound on multi-core systems. (See tokio-rs/bytes#461 (comment) for an approach to handle such cases soundly.)

  7. adamgreig commented on Jul 24, 2022

    @adamgreig
    Contributor

    That is unsound on multi-core systems.

    I thought it used the critical-section crate, which requires users to provide a critical section implementation for their system - so on a multi-core system you need to provide something with the appropriate guarantees, and it won't compile if no implementation is provided. It doesn't simply disable all interrupts on the CM0 core to obtain the CAS locks.

    Edit: The currently-released critical-section 0.2.7 does by default provide a "disable all interrupts" critical section for thumbv* platforms, which is unsound on multi-core, though users on multi-core systems can still provide their own implementation which is sound. The next release of critical-section removes all the built-in implementations entirely, and it's expected most platform support crates will provide an implementation instead, which should be sound for their respective platform. In any event I don't think it gets in the way of having atomic-polyfill provide replacement Atomic* types that can do load/store on thumbv6 if need be.

  8. apiraino commented on Jul 24, 2022

    @apiraino
    Contributor

    WG-prioritization assigning priority (Zulip discussion).

    @rustbot label -I-prioritize +P-critical +T-compiler

  9. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jul 24, 2022
  10. taiki-e commented on Jul 24, 2022

    @taiki-e
    Member

    requires users to provide a critical section implementation for their system - so on a multi-core system you need to provide something with the appropriate guarantees, and it won't compile if no implementation is provided

    Note that rust-embedded/critical-section@a3cdfd8 has not been released yet. The releases available on crates.io do not work this way.

    And even if it were released, atomic-polyfill is still unsound on multi-core systems because it mixes native atomic load/store and critical-section.

  11. taiki-e commented on Jul 24, 2022

    @taiki-e
    Member

    FWIW, my portable-atomic crate implements the equivalent of the atomic types that was be removed in #99595 (by using inline assembly). It also provides equivalents on other platforms where atomic load/store is not provided (riscv without A-extension, msp430, avr).

  12. RalfJung commented on Jul 24, 2022

    @RalfJung
    Member

    Remove support for atomic load/store on targets that don't support full atomics. Users are expected to switch to using volatile load/store instead.

    That seems in conflict with us telling people for years now that volatile operations are not atomic. And they aren't, so this strategy risks subtle miscompilations.

  13. 38 remaining items

  14. Lokathor commented on Sep 3, 2022

    @Lokathor
    Contributor

    load/store appears to still not work with ARMv4T, though it should

    See #101300

  15. pnkfelix commented on Oct 28, 2022

    @pnkfelix
    Contributor

    T-compiler reviewed this as part of 2022 Q3 P-high review

    What is current situation here? It seems like there are still unresolved questions about what we want to tell people to do;

    but also, I see that @nikic added target changes on the LLVM side for ARM and RISC-V: https://reviews.llvm.org/D130480 and https://reviews.llvm.org/D130621
    do we need to be leveraging the +atomic-32 and +forced-atomics features in some fashion within the rustc interface to LLVM in order to make progress here?

  16. nikic commented on Oct 28, 2022

    @nikic
    Contributor

    We already use +atomic-32 for relevant ARM/Thumb targets.

    Once we update to LLVM 16, we can consider using +forced-atomics on RISC-V (it would allow landing the change from #98333).

    I think at this point in time, the only thing that can be done here is adding upstream LLVM support for +forced-atomics to more targets, like MSP430 and AVR mentioned above.

  17. Lokathor commented on Oct 28, 2022

    @Lokathor
    Contributor

    #101300 is still open. The TLDR of that issue is that, at least on some targets, the fake atomic access has extremely bad performance, to the point that it might drive people to use inline assembly instead.

  18. cr1901 commented on Oct 28, 2022

    @cr1901
    Contributor

    @nikic I have no bandwidth at this time to add LLVM support for +forced-atomics or lowering atomics for MSP430, but I'm not against the idea and neither is the maintainer last I checked.

    If this is only simple thing that just drops some bits and lowers atomic stuff to normal loads / stores, then probably it could be fine

    Right now, MSP430 doesn't lower atomic operations correctly in LLVM (long story)

    This is still true.

  19. pkubaj commented on Nov 9, 2022

    @pkubaj
    Contributor

    powerpc (32-bit) is also affected.

  20. pnkfelix commented on Apr 14, 2023

    @pnkfelix
    Contributor

    Discussed at 2023 Q1 P-high review

    My current understanding is that there is no one objecting to adding support to +forced-atomics for the targets that need it, but no one is taking up the task of actually doing so for all such targets.

    I'm not clear on who would be best to take ownership of that, but maybe that doesn't matter yet; the first order of business is to collectively agree that is our strategy going forward.

    @Amanieu @nikic am I right in inferring that you're both aligned on the plan of leveraging +forced-atomics where we need it, and the main work items here are adding support for it upstream with LLVM? (And I guess maybe there are some efficiency issues with the current support, as flagged e.g. in #101300 ?)

  21. nikic commented on Apr 14, 2023

    @nikic
    Contributor

    We have +forced-atomics support for RISCV in LLVM 16, so we could give adding that to our target specs (and enabling atomic load/store) a try.

    I believe the only real concern is that it makes Rust's atomics ABI-incompatible with C's atomics (unless the C code also uses +forced-atomics), but I think people value having atomics in Rust at all much more than being able to work with atomics across an ABI boundary.

  22. cr1901 commented on Apr 14, 2023

    @cr1901
    Contributor

    I believe the only real concern is that it makes Rust's atomics ABI-incompatible with C's atomics (unless the C code also uses +forced-atomics)

    I've been thinking about this; does anyone have a concrete example of where calling C code using libatomic from Rust code using Rust atomics is unsound/memory-unsafe (single core and multi core case )? Or is it more a "precaution that interop will subtly break"?

    I can't visualize an example after thinking about it, because I already can't think of a way to share Rust atomics and use as if they were C _Atomic_s; Rust uses repr(C) on a newtype, and C _Atomics are not guaranteed to be the same size as the underlying type (do gcc and clang accommodate this?).

  23. asl commented on Apr 23, 2023

    @asl

    We have +forced-atomics support for RISCV in LLVM 16, so we could give adding that to our target specs (and enabling atomic load/store) a try.

    FWIW, I'm planning to find some spare time to address some atomic weirdness on MSP430 in LLVM.

  24. added a commit that references this issue on Aug 7, 2023
  25. wesleywiser commented on Nov 3, 2023

    @wesleywiser
    Member

    Visited during the compiler team's P-high review. Based on the most recent discussion points, we believe this can relabeled P-medium as +forced-atomic support has landed in LLVM for all relevant Tier 1 or Tier 2 targets and interoperability of passing pointers to atomic values between C and Rust in this context does not seem to be commonly used.

  26. added and removed
    P-highHigh priority
    on Nov 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-atomicArea: Atomics, barriers, and sync primitivesO-bare-metalTarget: Rust without an operating systemP-mediumMedium priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions