Skip to content

Commit 2ed6f81

Browse files
rintaroCodaFi
authored andcommitted
Allocate lock object in the allocator
Passing `os_unfair_lock` with inout expression is considered "mutation" by thread sanitizer. Instead, keep the lock object with `let UnsafeMutablePointer` form, and pass it.
1 parent 845b50d commit 2ed6f81

File tree

2 files changed

+45
-23
lines changed

2 files changed

+45
-23
lines changed

Sources/SwiftSyntax/SyntaxArena.swift

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,51 @@
1313
#if canImport(Darwin)
1414
import Darwin
1515

16-
class ScopeGuard {
17-
private var lock: os_unfair_lock
18-
init() {
19-
self.lock = os_unfair_lock()
16+
struct ScopeGuard {
17+
private let lock: os_unfair_lock_t
18+
init(allocator: BumpPtrAllocator) {
19+
let storage = allocator.allocate(os_unfair_lock.self, count: 1).baseAddress!
20+
storage.initialize(to: os_unfair_lock())
21+
self.lock = os_unfair_lock_t(storage)
2022
}
23+
24+
func deinitialize() {}
25+
2126
func withGuard<T>(body: () throws -> T) rethrows -> T {
22-
os_unfair_lock_lock(&lock)
23-
defer { os_unfair_lock_unlock(&lock)}
27+
os_unfair_lock_lock(lock)
28+
defer { os_unfair_lock_unlock(lock)}
2429
return try body()
2530
}
2631
}
2732

2833
#elseif canImport(Glibc)
2934
import Glibc
3035

31-
class ScopeGuard {
32-
var lock: pthread_mutex_t
33-
init() {
34-
self.lock = pthread_mutex_t()
35-
pthread_mutex_init(&self.lock, nil)
36+
struct ScopeGuard {
37+
private let lock: UnsafeMutablePointer<pthread_mutex_t>
38+
init(allocator: BumpPtrAllocator) {
39+
let storage = allocator.allocate(pthread_mutex_t.self, count: 1).baseAddress!
40+
storage.initialize(to: pthread_mutex_t())
41+
pthread_mutex_init(storage, nil)
42+
self.lock = storage
3643
}
37-
deinit {
38-
pthread_mutex_destroy(&self.lock)
44+
func deinitialize() {
45+
pthread_mutex_destroy(self.lock)
3946
}
4047
func withGuard<T>(body: () throws -> T) rethrows -> T {
41-
pthread_mutex_lock(&lock)
42-
defer { pthread_mutex_unlock(&lock) }
48+
pthread_mutex_lock(self.lock)
49+
defer { pthread_mutex_unlock(self.lock) }
4350
return try body()
4451
}
4552
}
53+
4654
#else
4755
// FIXME: Support other platforms.
4856

4957
/// Dummy mutex that doesn't actually guard at all.
5058
class ScopeGuard {
5159
init() {}
60+
func deinitialize() {}
5261
func withGuard<T>(body: () throws -> T) rethrows -> T {
5362
return try body()
5463
}
@@ -79,20 +88,28 @@ public class SyntaxArena {
7988

8089
@_spi(RawSyntax)
8190
public init(parseTriviaFunction: @escaping ParseTriviaFunction) {
82-
lock = ScopeGuard()
91+
let allocator = BumpPtrAllocator()
92+
self.lock = ScopeGuard(allocator: allocator)
8393
self.singleThreadMode = false
84-
allocator = BumpPtrAllocator()
94+
self.allocator = allocator
8595
children = []
8696
sourceBuffer = .init(start: nil, count: 0)
8797
hasParent = false
8898
self.parseTriviaFunction = parseTriviaFunction
8999
}
90100

101+
deinit {
102+
// NOTE: We don't make `ScopeGuard` a class and `deinit` in it to
103+
// deinitialize it because the actual lock value is in `allocator`, and we
104+
// want to make sure to deinitialize the lock before destroying the allocator.
105+
lock.deinitialize()
106+
}
107+
91108
public convenience init() {
92109
self.init(parseTriviaFunction: _defaultParseTriviaFunction(_:_:))
93110
}
94111

95-
private func withGuard<R>(body: () throws -> R) rethrows -> R {
112+
private func withGuard<R>(_ body: () throws -> R) rethrows -> R {
96113
if self.singleThreadMode {
97114
return try body()
98115
} else {
@@ -114,7 +131,7 @@ public class SyntaxArena {
114131
/// `contains(address _:)` is faster if the address is inside the memory
115132
/// range this function returned.
116133
public func internSourceBuffer(_ buffer: UnsafeBufferPointer<UInt8>) -> UnsafeBufferPointer<UInt8> {
117-
let allocated = lock.withGuard {
134+
let allocated = self.withGuard {
118135
allocator.allocate(UInt8.self, count: buffer.count + /* for NULL */1)
119136
}
120137
precondition(sourceBuffer.baseAddress == nil, "SourceBuffer should only be set once.")
@@ -189,7 +206,7 @@ public class SyntaxArena {
189206
/// Copies a `RawSyntaxData` to the memory this arena manages, and retuns the
190207
/// pointer to the destination.
191208
func intern(_ value: RawSyntaxData) -> UnsafePointer<RawSyntaxData> {
192-
let allocated = lock.withGuard {
209+
let allocated = self.withGuard {
193210
allocator.allocate(RawSyntaxData.self, count: 1).baseAddress!
194211
}
195212
allocated.initialize(to: value)
@@ -236,7 +253,7 @@ public class SyntaxArena {
236253
public func contains(text: SyntaxText) -> Bool {
237254
return (text.isEmpty ||
238255
sourceBufferContains(text.baseAddress!) ||
239-
allocator.contains(address: text.baseAddress!))
256+
self.withGuard({allocator.contains(address: text.baseAddress!)}))
240257
}
241258

242259
@_spi(RawSyntax)

Tests/SwiftSyntaxTest/MultithreadingTests.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ public class MultithreadingTests: XCTestCase {
2929
presence: .present,
3030
arena: arena)
3131
}
32-
let token = Syntax(raw: RawSyntax(tokenRaw)).as(TokenSyntax.self)!
33-
XCTAssertEqual(token.text, "ident\(i)")
32+
let identifierExprRaw = RawIdentifierExprSyntax(
33+
identifier: tokenRaw,
34+
declNameArguments: nil,
35+
arena: arena)
36+
37+
let expr = Syntax(raw: RawSyntax(identifierExprRaw)).as(IdentifierExprSyntax.self)!
38+
XCTAssertEqual(expr.identifier.text, "ident\(i)")
3439
}
3540
}
3641

0 commit comments

Comments
 (0)