Skip to content

[embedded] Make embedded swift_once thread-safe #69498

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

Merged
merged 3 commits into from
Nov 2, 2023

Conversation

kubamracek
Copy link
Contributor

In the embedded runtime, replace the current implementation of swift_once that doesn't attempt to handle multithreading at all, with a version that uses atomics on the predicate + spinning on concurrent calls (which is still far from perfect, but for now let's avoid introducing a dependency on lock APIs).

@kubamracek kubamracek requested a review from a team as a code owner October 30, 2023 03:36
@kubamracek
Copy link
Contributor Author

@swift-ci please test

@kubamracek kubamracek added the embedded Embedded Swift label Oct 30, 2023
}

fileprivate func compareExchangeRelaxed(_ atomic: UnsafeMutablePointer<Int>, expectedOldValue: Int, desiredNewValue: Int) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

we need to be careful with these: Cortex-M 0 don't have a lot when it comes to atomics. From my understanding all they have are DMB and register based spin locks (which the spin locks are not accessible by compiler intrinsics, and require library level access to those).

Copy link
Contributor Author

@kubamracek kubamracek Oct 30, 2023

Choose a reason for hiding this comment

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

Good point. Though these atomic builtins are valid LLVM builtins for any target and it's the LLVM backend's job to lower them to something that works, worst case a library call. I think that we should (1) keep using atomic builtins here in the embedded runtime code and rely on compiler-rt or a user-provided library to make them actually work, and if the user knows that the system is single-threaded, those can be implemented in a trivial way, and (2) eventually provide an option to use an (alternative) single-threaded embedded runtime which doesn't do any of the atomic stuff (and saves a little bit of codesize on that).

static var n = 0

init() {
print("MyStructWithAnExpensiveInitializer.init")
Copy link
Contributor

Choose a reason for hiding this comment

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

it might be good to have a recursive initializer test, or dependent ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a test for dependent initializers. Having recursion / a cycle in dependent initializers is not valid Swift, correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

Right:

<stdin>:1:1: error: circular reference
let a = a

If you manage to hide it from the compiler, I believe you get a runtime error.

// TODO/FIXME: The following only works in single-threaded environments.
if predicate.pointee == 0 {
predicate.pointee = 1
if loadAcquire(predicate) < 0 { return }
Copy link
Contributor

Choose a reason for hiding this comment

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

If the only valid values are -1, 0, and 1, then it might be good to raise a fatal error for other values, at least in debug builds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added!

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll be interested to see how the AVR back end handles this! I'm sort of assuming atomics are a noop in AVR! (all single core machines)

Copy link
Contributor Author

@kubamracek kubamracek Oct 30, 2023

Choose a reason for hiding this comment

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

AVR

I assume LLVM will lower all these into library calls, something like __atomic_load, __atomic_store, __atomic_compare_exchange, and expect that you either link compiler-rt that implements them, or that you provide your own definitions of them.

Though single-core doesn't necessarily mean single-threaded, but I assume all sane AVR setups are actually single-threaded?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah. Someone might contradict me but I'd say the only possible preemption on an AVR CPU is interrupt handlers.

I think we'll see how it lowers if and when we link to the new embedded runtime. And if the AVR backend doesn't have atomics it'll be an obvious compiler crash and an easy patch.

@kubamracek
Copy link
Contributor Author

@swift-ci please test

@kubamracek kubamracek merged commit 9d846a0 into swiftlang:main Nov 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
embedded Embedded Swift
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants