Skip to content

Commit 46d7cde

Browse files
committed
Make the Regex type Sendable
Regex stores a `Program` instance, which lazily lowers the DSLTree into a compiled program. In unprotected, this lazy compilation is under concurrency. This change uses UnsafeAtomicLazyReference from `swift-atomics` as storage for the lowered program, which makes storing the compiled program an atomic operation.
1 parent c9a757b commit 46d7cde

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

Package.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ let package = Package(
4444
],
4545
dependencies: [
4646
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
47+
.package(url: "https://github.com/apple/swift-atomics", from: "1.0.0"),
4748
],
4849
targets: [
4950
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
@@ -62,7 +63,11 @@ let package = Package(
6263
dependencies: []),
6364
.target(
6465
name: "_StringProcessing",
65-
dependencies: ["_RegexParser", "_CUnicode"],
66+
dependencies: [
67+
.product(name: "Atomics", package: "swift-atomics"),
68+
"_RegexParser",
69+
"_CUnicode",
70+
],
6671
swiftSettings: publicStdlibSettings),
6772
.target(
6873
name: "RegexBuilder",

Sources/_StringProcessing/Regex/Core.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
@_implementationOnly import _RegexParser
13-
13+
@_implementationOnly import Atomics
1414

1515
/// A type that represents a regular expression.
1616
@available(SwiftStdlib 5.7, *)
@@ -68,20 +68,35 @@ extension Regex {
6868
}
6969
}
7070

71+
7172
@available(SwiftStdlib 5.7, *)
7273
extension Regex {
7374
/// A program representation that caches any lowered representation for
7475
/// execution.
75-
internal class Program {
76+
internal final class Program: @unchecked Sendable {
7677
/// The underlying IR.
7778
///
7879
/// FIXME: If Regex is the unit of composition, then it should be a Node instead,
7980
/// and we should have a separate type that handled both global options and,
8081
/// likely, compilation/caching.
8182
let tree: DSLTree
8283

84+
private final class ProgramBox {
85+
let value: MEProgram<String>
86+
init(_ value: MEProgram<String>) { self.value = value }
87+
}
88+
89+
private var _loweredProgramStorage: UnsafeAtomicLazyReference<ProgramBox>
90+
= .create()
91+
8392
/// The program for execution with the matching engine.
84-
lazy private(set) var loweredProgram = try! Compiler(tree: tree).emit()
93+
var loweredProgram: MEProgram<String> {
94+
if let lowered = _loweredProgramStorage.load() {
95+
return lowered.value
96+
}
97+
let lowered = try! ProgramBox(Compiler(tree: tree).emit())
98+
return _loweredProgramStorage.storeIfNilThenLoad(lowered).value
99+
}
85100

86101
init(ast: AST) {
87102
self.tree = ast.dslTree
@@ -90,6 +105,10 @@ extension Regex {
90105
init(tree: DSLTree) {
91106
self.tree = tree
92107
}
108+
109+
deinit {
110+
_loweredProgramStorage.destroy()
111+
}
93112
}
94113

95114
/// The set of matching options that applies to the start of this regex.

0 commit comments

Comments
 (0)