Skip to content

Commit 9d846a0

Browse files
authored
Merge pull request #69498 from kubamracek/embedded-once-thread-safe
[embedded] Make embedded swift_once thread-safe
2 parents c10dae9 + f205f2c commit 9d846a0

File tree

3 files changed

+152
-12
lines changed

3 files changed

+152
-12
lines changed

stdlib/public/core/EmbeddedRuntime.swift

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,31 @@ fileprivate func refcountPointer(for object: UnsafeMutablePointer<HeapObject>) -
196196
return UnsafeMutablePointer<Int>(UnsafeRawPointer(object).advanced(by: MemoryLayout<Int>.size)._rawValue)
197197
}
198198

199-
fileprivate func loadRelaxed(_ refcount: UnsafeMutablePointer<Int>) -> Int {
200-
Int(Builtin.atomicload_monotonic_Word(refcount._rawValue))
199+
fileprivate func loadRelaxed(_ atomic: UnsafeMutablePointer<Int>) -> Int {
200+
Int(Builtin.atomicload_monotonic_Word(atomic._rawValue))
201201
}
202202

203-
fileprivate func loadAcquire(_ refcount: UnsafeMutablePointer<Int>) -> Int {
204-
Int(Builtin.atomicload_acquire_Word(refcount._rawValue))
203+
fileprivate func loadAcquire(_ atomic: UnsafeMutablePointer<Int>) -> Int {
204+
Int(Builtin.atomicload_acquire_Word(atomic._rawValue))
205205
}
206206

207-
fileprivate func subFetchAcquireRelease(_ refcount: UnsafeMutablePointer<Int>, n: Int) -> Int {
208-
let oldValue = Int(Builtin.atomicrmw_sub_acqrel_Word(refcount._rawValue, n._builtinWordValue))
207+
fileprivate func subFetchAcquireRelease(_ atomic: UnsafeMutablePointer<Int>, n: Int) -> Int {
208+
let oldValue = Int(Builtin.atomicrmw_sub_acqrel_Word(atomic._rawValue, n._builtinWordValue))
209209
return oldValue - n
210210
}
211211

212-
fileprivate func addRelaxed(_ refcount: UnsafeMutablePointer<Int>, n: Int) {
213-
_ = Builtin.atomicrmw_add_monotonic_Word(refcount._rawValue, n._builtinWordValue)
212+
fileprivate func addRelaxed(_ atomic: UnsafeMutablePointer<Int>, n: Int) {
213+
_ = Builtin.atomicrmw_add_monotonic_Word(atomic._rawValue, n._builtinWordValue)
214214
}
215215

216+
fileprivate func compareExchangeRelaxed(_ atomic: UnsafeMutablePointer<Int>, expectedOldValue: Int, desiredNewValue: Int) -> Bool {
217+
let (_, won) = Builtin.cmpxchg_monotonic_monotonic_Word(atomic._rawValue, expectedOldValue._builtinWordValue, desiredNewValue._builtinWordValue)
218+
return Bool(won)
219+
}
220+
221+
fileprivate func storeRelease(_ atomic: UnsafeMutablePointer<Int>, newValue: Int) {
222+
Builtin.atomicstore_release_Word(atomic._rawValue, newValue._builtinWordValue)
223+
}
216224

217225

218226
/// Exclusivity checking
@@ -233,11 +241,24 @@ public func swift_endAccess(buffer: UnsafeMutableRawPointer) {
233241

234242
@_silgen_name("swift_once")
235243
public func swift_once(predicate: UnsafeMutablePointer<Int>, fn: (@convention(c) (UnsafeMutableRawPointer)->()), context: UnsafeMutableRawPointer) {
236-
// TODO/FIXME: The following only works in single-threaded environments.
237-
if predicate.pointee == 0 {
238-
predicate.pointee = 1
244+
let checkedLoadAcquire = { predicate in
245+
let value = loadAcquire(predicate)
246+
assert(value == -1 || value == 0 || value == 1)
247+
return value
248+
}
249+
250+
if checkedLoadAcquire(predicate) < 0 { return }
251+
252+
let won = compareExchangeRelaxed(predicate, expectedOldValue: 0, desiredNewValue: 1)
253+
if won {
239254
fn(context)
240-
predicate.pointee = -1
255+
storeRelease(predicate, newValue: -1)
256+
return
257+
}
258+
259+
// TODO: This should really use an OS provided lock
260+
while checkedLoadAcquire(predicate) >= 0 {
261+
// spin
241262
}
242263
}
243264

test/embedded/once-dependent.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// RUN: %target-swift-frontend %s %S/Inputs/print.swift -enable-experimental-feature Embedded -c -o %t/main.o
2+
// RUN: %target-clang %t/main.o -o %t/a.out -dead_strip
3+
// RUN: %target-run %t/a.out | %FileCheck %s
4+
5+
// REQUIRES: executable_test
6+
// REQUIRES: optimized_stdlib
7+
// REQUIRES: VENDOR=apple
8+
// REQUIRES: OS=macosx
9+
10+
public struct MyStructA {
11+
static var singleton = MyStructA()
12+
13+
init() {
14+
print("MyStructA.init")
15+
_ = MyStructB.singleton
16+
print("MyStructA.init done")
17+
}
18+
}
19+
20+
public struct MyStructB {
21+
static var singleton = MyStructB()
22+
23+
init() {
24+
print("MyStructB.init")
25+
_ = MyStructC.singleton
26+
print("MyStructB.init done")
27+
}
28+
}
29+
30+
public struct MyStructC {
31+
static var singleton = MyStructC()
32+
33+
init() {
34+
print("MyStructC.init")
35+
print("MyStructC.init done")
36+
}
37+
}
38+
39+
@main
40+
struct Main {
41+
static func main() {
42+
print("Start")
43+
_ = MyStructA.singleton
44+
45+
// CHECK: Start
46+
// CHECK-NEXT: MyStructA.init
47+
// CHECK-NEXT: MyStructB.init
48+
// CHECK-NEXT: MyStructC.init
49+
// CHECK-NEXT: MyStructC.init done
50+
// CHECK-NEXT: MyStructB.init done
51+
// CHECK-NEXT: MyStructA.init done
52+
}
53+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %{python} %utils/split_file.py -o %t %s
3+
4+
// RUN: %target-swift-frontend %t/Main.swift %S/Inputs/print.swift -import-bridging-header %t/BridgingHeader.h -enable-experimental-feature Embedded -c -o %t/main.o
5+
// RUN: %target-clang %t/main.o -o %t/a.out -dead_strip
6+
// RUN: %target-run %t/a.out | %FileCheck %s
7+
8+
// REQUIRES: executable_test
9+
// REQUIRES: optimized_stdlib
10+
// REQUIRES: VENDOR=apple
11+
// REQUIRES: OS=macosx
12+
13+
// BEGIN BridgingHeader.h
14+
15+
typedef void *pthread_t;
16+
int pthread_create(pthread_t *thread, const void *attr, void *(*start_routine)(void *), void *arg);
17+
int pthread_join(pthread_t thread, void **value_ptr);
18+
unsigned int sleep(unsigned int);
19+
int usleep(unsigned int);
20+
21+
// BEGIN Main.swift
22+
23+
public struct MyStructWithAnExpensiveInitializer {
24+
static var singleton = MyStructWithAnExpensiveInitializer()
25+
static var n = 0
26+
27+
init() {
28+
print("MyStructWithAnExpensiveInitializer.init")
29+
usleep(100_000)
30+
MyStructWithAnExpensiveInitializer.n += 1
31+
print("MyStructWithAnExpensiveInitializer.init done")
32+
}
33+
34+
func foo() {
35+
precondition(MyStructWithAnExpensiveInitializer.n == 1)
36+
}
37+
}
38+
39+
@main
40+
struct Main {
41+
static func main() {
42+
print("Start")
43+
44+
var t1 = pthread_t(bitPattern: 0)
45+
pthread_create(&t1, nil, { _ in
46+
MyStructWithAnExpensiveInitializer.singleton.foo()
47+
return nil
48+
}, nil)
49+
50+
var t2 = pthread_t(bitPattern: 0)
51+
pthread_create(&t2, nil, { _ in
52+
MyStructWithAnExpensiveInitializer.singleton.foo()
53+
return nil
54+
}, nil)
55+
56+
pthread_join(t1, nil)
57+
pthread_join(t2, nil)
58+
59+
print("All done")
60+
61+
// CHECK: Start
62+
// CHECK-NEXT: MyStructWithAnExpensiveInitializer.init
63+
// CHECK-NEXT: MyStructWithAnExpensiveInitializer.init done
64+
// CHECK-NEXT: All done
65+
}
66+
}

0 commit comments

Comments
 (0)