Skip to content

Commit 93decb6

Browse files
authored
[Concurrency] Complement assume... APIs with the assert and precondition ones (#64062)
1 parent fb6974e commit 93decb6

File tree

4 files changed

+252
-3
lines changed

4 files changed

+252
-3
lines changed

stdlib/public/Concurrency/ExecutorAssertions.swift

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -11,6 +11,160 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Swift
14+
import SwiftShims
15+
16+
// ==== -----------------------------------------------------------------------
17+
// MARK: Precondition executors
18+
19+
/// Unconditionally if the current task is executing on the expected serial executor,
20+
/// and if not crash the program offering information about the executor mismatch.
21+
///
22+
/// This function's effect varies depending on the build flag used:
23+
///
24+
/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug
25+
/// configuration), stops program execution in a debuggable state after
26+
/// printing `message`.
27+
///
28+
/// * In `-O` builds (the default for Xcode's Release configuration), stops
29+
/// program execution.
30+
///
31+
/// * In `-Ounchecked` builds, the optimizer may assume that this function is
32+
/// never called. Failure to satisfy that assumption is a serious
33+
/// programming error.
34+
///
35+
/// - Parameter executor: the expected current executor
36+
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
37+
public
38+
func preconditionTaskOnExecutor(
39+
_ executor: some SerialExecutor,
40+
message: @autoclosure () -> String = String(),
41+
file: StaticString = #fileID, line: UInt = #line
42+
) {
43+
guard _isDebugAssertConfiguration() || _isReleaseAssertConfiguration() else {
44+
return
45+
}
46+
47+
let expectationCheck = _taskIsCurrentExecutor(executor.asUnownedSerialExecutor().executor)
48+
49+
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
50+
precondition(expectationCheck,
51+
// TODO: offer information which executor we actually got
52+
"Incorrect actor executor assumption; Expected '\(executor)' executor. \(message())",
53+
file: file, line: line) // short-cut so we get the exact same failure reporting semantics
54+
}
55+
56+
/// Unconditionally if the current task is executing on the serial executor of the passed in `actor`,
57+
/// and if not crash the program offering information about the executor mismatch.
58+
///
59+
/// This function's effect varies depending on the build flag used:
60+
///
61+
/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug
62+
/// configuration), stops program execution in a debuggable state after
63+
/// printing `message`.
64+
///
65+
/// * In `-O` builds (the default for Xcode's Release configuration), stops
66+
/// program execution.
67+
///
68+
/// * In `-Ounchecked` builds, the optimizer may assume that this function is
69+
/// never called. Failure to satisfy that assumption is a serious
70+
/// programming error.
71+
///
72+
/// - Parameter actor: the actor whose serial executor we expect to be the current executor
73+
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
74+
public
75+
func preconditionTaskOnActorExecutor(
76+
_ actor: some Actor,
77+
message: @autoclosure () -> String = String(),
78+
file: StaticString = #fileID, line: UInt = #line
79+
) {
80+
guard _isDebugAssertConfiguration() || _isReleaseAssertConfiguration() else {
81+
return
82+
}
83+
84+
let expectationCheck = _taskIsCurrentExecutor(actor.unownedExecutor.executor)
85+
86+
// TODO: offer information which executor we actually got
87+
precondition(expectationCheck,
88+
// TODO: figure out a way to get the typed repr out of the unowned executor
89+
"Incorrect actor executor assumption; Expected '\(actor.unownedExecutor)' executor. \(message())",
90+
file: file, line: line)
91+
}
92+
93+
// ==== -----------------------------------------------------------------------
94+
// MARK: Assert executors
95+
96+
/// Performs an executor check in debug builds.
97+
///
98+
/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug
99+
/// configuration): If `condition` evaluates to `false`, stop program
100+
/// execution in a debuggable state after printing `message`.
101+
///
102+
/// * In `-O` builds (the default for Xcode's Release configuration),
103+
/// `condition` is not evaluated, and there are no effects.
104+
///
105+
/// * In `-Ounchecked` builds, `condition` is not evaluated, but the optimizer
106+
/// may assume that it *always* evaluates to `true`. Failure to satisfy that
107+
/// assumption is a serious programming error.
108+
///
109+
/// - Parameter executor: the expected current executor
110+
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
111+
public
112+
func assertTaskOnExecutor(
113+
_ executor: some SerialExecutor,
114+
_ message: @autoclosure () -> String = String(),
115+
file: StaticString = #fileID, line: UInt = #line
116+
) {
117+
guard _isDebugAssertConfiguration() else {
118+
return
119+
}
120+
121+
guard _taskIsCurrentExecutor(executor.asUnownedSerialExecutor().executor) else {
122+
// TODO: offer information which executor we actually got
123+
let msg = "Incorrect actor executor assumption; Expected '\(executor)' executor. \(message())"
124+
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
125+
assertionFailure(msg, file: file, line: line)
126+
return
127+
}
128+
}
129+
130+
/// Performs an executor check in debug builds.
131+
///
132+
/// * In playgrounds and `-Onone` builds (the default for Xcode's Debug
133+
/// configuration): If `condition` evaluates to `false`, stop program
134+
/// execution in a debuggable state after printing `message`.
135+
///
136+
/// * In `-O` builds (the default for Xcode's Release configuration),
137+
/// `condition` is not evaluated, and there are no effects.
138+
///
139+
/// * In `-Ounchecked` builds, `condition` is not evaluated, but the optimizer
140+
/// may assume that it *always* evaluates to `true`. Failure to satisfy that
141+
/// assumption is a serious programming error.
142+
///
143+
///
144+
/// - Parameter actor: the actor whose serial executor we expect to be the current executor
145+
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
146+
public
147+
func assertTaskOnActorExecutor(
148+
_ actor: some Actor,
149+
_ message: @autoclosure () -> String = String(),
150+
file: StaticString = #fileID, line: UInt = #line
151+
) {
152+
guard _isDebugAssertConfiguration() else {
153+
return
154+
}
155+
156+
guard _taskIsCurrentExecutor(actor.unownedExecutor.executor) else {
157+
// TODO: offer information which executor we actually got
158+
// TODO: figure out a way to get the typed repr out of the unowned executor
159+
let msg = "Incorrect actor executor assumption; Expected '\(actor.unownedExecutor)' executor. \(message())"
160+
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
161+
assertionFailure(msg, file: file, line: line) // short-cut so we get the exact same failure reporting semantics
162+
return
163+
}
164+
}
165+
166+
// ==== -----------------------------------------------------------------------
167+
// MARK: Assume Executor
14168

15169
/// A safe way to synchronously assume that the current execution context belongs to the MainActor.
16170
///

stdlib/public/core/AssertCommon.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ func _isDebugAssertConfiguration() -> Bool {
2929
return Int32(Builtin.assert_configuration()) == 0
3030
}
3131

32-
@usableFromInline @_transparent
33-
internal func _isReleaseAssertConfiguration() -> Bool {
32+
@_transparent
33+
public // @testable, used in _Concurrency executor preconditions
34+
func _isReleaseAssertConfiguration() -> Bool {
3435
// The values for the assert_configuration call are:
3536
// 0: Debug
3637
// 1: Release
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -Xfrontend -disable-availability-checking -parse-as-library %s -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %target-run %t/a.out
5+
6+
// REQUIRES: executable_test
7+
// REQUIRES: concurrency
8+
// REQUIRES: concurrency_runtime
9+
10+
// UNSUPPORTED: back_deployment_runtime
11+
// UNSUPPORTED: back_deploy_concurrency
12+
// UNSUPPORTED: use_os_stdlib
13+
// UNSUPPORTED: freestanding
14+
15+
import StdlibUnittest
16+
17+
func checkPreconditionMainActor() /* synchronous! */ {
18+
preconditionTaskOnActorExecutor(MainActor.shared)
19+
}
20+
21+
func checkPreconditionFamousActor() /* synchronous! */ {
22+
preconditionTaskOnActorExecutor(FamousActor.shared)
23+
}
24+
25+
@MainActor
26+
func mainActorCallCheck() {
27+
checkPreconditionMainActor()
28+
}
29+
30+
@globalActor actor FamousActor: GlobalActor {
31+
public static let shared = FamousActor()
32+
33+
func callCheckFamousActor() {
34+
checkPreconditionFamousActor() // ok
35+
}
36+
37+
func callCheckMainActor() {
38+
checkPreconditionMainActor() // bad
39+
}
40+
}
41+
42+
actor MainFriend {
43+
nonisolated var unownedExecutor: UnownedSerialExecutor {
44+
MainActor.sharedUnownedExecutor
45+
}
46+
47+
func callCheckMainActor() {
48+
checkPreconditionMainActor()
49+
}
50+
}
51+
52+
actor Someone {
53+
func callCheckMainActor() {
54+
checkPreconditionMainActor()
55+
}
56+
}
57+
58+
@main struct Main {
59+
static func main() async {
60+
let tests = TestSuite("AssertPreconditionActorExecutor")
61+
62+
if #available(SwiftStdlib 5.9, *) {
63+
// === MainActor --------------------------------------------------------
64+
65+
tests.test("preconditionTaskOnActorExecutor(main): from 'main() async', with await") {
66+
await checkPreconditionMainActor()
67+
}
68+
69+
// FIXME: calling without await from main() should also succeed, we must set the executor while we're kicking off main
70+
71+
tests.test("preconditionTaskOnActorExecutor(main): from Main friend") {
72+
await MainFriend().callCheckMainActor()
73+
}
74+
75+
tests.test("preconditionTaskOnActorExecutor(main): wrongly assume the main executor, from actor on other executor") {
76+
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected 'MainActor' executor.")
77+
await Someone().callCheckMainActor()
78+
}
79+
80+
// === Global actor -----------------------------------------------------
81+
82+
tests.test("preconditionTaskOnActorExecutor(main): assume FamousActor, from FamousActor") {
83+
await FamousActor.shared.callCheckFamousActor()
84+
}
85+
86+
87+
}
88+
89+
await runAllTestsAsync()
90+
}
91+
}

test/SourceKit/Indexing/index_with_clang_module.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ func foo(a: FooClassDerived) {
1616
// CHECK: key.kind: source.lang.swift.import.module.clang
1717
// CHECK-NEXT: key.name: "_SwiftConcurrencyShims"
1818

19+
// CHECK: key.kind: source.lang.swift.import.module.clang
20+
// CHECK-NEXT: key.name: "SwiftShims"
21+
1922
// CHECK: key.kind: source.lang.swift.import.module.clang
2023
// CHECK-NEXT: key.name: "Foo"
2124
// CHECK-NEXT: key.filepath: "{{.*[/\\]}}Foo{{.*}}.pcm"

0 commit comments

Comments
 (0)