Skip to content

Commit 0704746

Browse files
authored
Merge pull request #41445 from lorentey/unconditional-debugPrecondition
[stdlib] Add build option to enable _debugPrecondition in Release mode
2 parents b3016c9 + 5274295 commit 0704746

File tree

11 files changed

+127
-6
lines changed

11 files changed

+127
-6
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ option(SWIFT_ENABLE_STDLIBCORE_EXCLUSIVITY_CHECKING
434434
"Build stdlibCore with exclusivity checking enabled"
435435
FALSE)
436436

437+
option(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
438+
"Enable _debugPrecondition checks in the stdlib in Release configurations"
439+
FALSE)
440+
437441
option(SWIFT_ENABLE_EXPERIMENTAL_DIFFERENTIABLE_PROGRAMMING
438442
"Enable experimental Swift differentiable programming features"
439443
FALSE)

stdlib/public/core/Assert.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,24 +261,32 @@ internal func _debugPrecondition(
261261
_ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(),
262262
file: StaticString = #file, line: UInt = #line
263263
) {
264+
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
265+
_precondition(condition(), message, file: file, line: line)
266+
#else
264267
// Only check in debug mode.
265268
if _slowPath(_isDebugAssertConfiguration()) {
266269
if !_fastPath(condition()) {
267270
_fatalErrorMessage("Fatal error", message, file: file, line: line,
268271
flags: _fatalErrorFlags())
269272
}
270273
}
274+
#endif
271275
}
272276

273277
@usableFromInline @_transparent
274278
internal func _debugPreconditionFailure(
275279
_ message: StaticString = StaticString(),
276280
file: StaticString = #file, line: UInt = #line
277281
) -> Never {
282+
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
283+
_preconditionFailure(message, file: file, line: line)
284+
#else
278285
if _slowPath(_isDebugAssertConfiguration()) {
279286
_precondition(false, message, file: file, line: line)
280287
}
281288
_conditionallyUnreachable()
289+
#endif
282290
}
283291

284292
/// Internal checks.

stdlib/public/core/AssertCommon.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ func _isStdlibInternalChecksEnabled() -> Bool {
5858
#endif
5959
}
6060

61+
@_transparent
62+
@_alwaysEmitIntoClient // Introduced in 5.7
63+
public // @testable
64+
func _isStdlibDebugChecksEnabled() -> Bool {
65+
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
66+
return !_isFastAssertConfiguration()
67+
#else
68+
return _isDebugAssertConfiguration()
69+
#endif
70+
}
71+
6172
@usableFromInline @_transparent
6273
internal func _fatalErrorFlags() -> UInt32 {
6374
// The current flags are:

stdlib/public/core/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ endif()
272272
if(SWIFT_STDLIB_ENABLE_STDLIBCORE_EXCLUSIVITY_CHECKING)
273273
list(APPEND swift_stdlib_compile_flags "-enforce-exclusivity=checked")
274274
endif()
275+
if(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE)
276+
list(APPEND swift_stdlib_compile_flags "-DSWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE")
277+
endif()
275278
set(compile_flags_for_final_build)
276279
if(SWIFT_ENABLE_ARRAY_COW_CHECKS)
277280
list(APPEND compile_flags_for_final_build "-DCOW_CHECKS_ENABLED")

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ normalize_boolean_spelling(SWIFT_OPTIMIZED)
202202
normalize_boolean_spelling(SWIFT_STDLIB_SINGLE_THREADED_RUNTIME)
203203
normalize_boolean_spelling(SWIFT_ENABLE_REFLECTION)
204204
normalize_boolean_spelling(SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS)
205+
normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE)
205206
normalize_boolean_spelling(SWIFT_HAVE_LIBXML2)
206207
normalize_boolean_spelling(SWIFT_INCLUDE_TOOLS)
207208
normalize_boolean_spelling(SWIFT_STDLIB_STATIC_PRINT)

test/lit.site.cfg.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ if "@SWIFT_STDLIB_ENABLE_UNICODE_DATA" == "TRUE":
143143
config.available_features.add('stdlib_unicode_data')
144144
if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE":
145145
config.available_features.add('string_processing')
146+
if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE":
147+
config.available_features.add('swift_stdlib_debug_preconditions_in_release')
146148

147149
config.swift_freestanding_is_darwin = "@SWIFT_FREESTANDING_IS_DARWIN@" == "TRUE"
148150
config.swift_enable_dispatch = "@SWIFT_ENABLE_DISPATCH@" == "TRUE"

test/stdlib/RangeTraps.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ if #available(SwiftStdlib 5.5, *) {
129129
// Debug check was introduced in https://github.com/apple/swift/pull/34961
130130
RangeTraps.test("UncheckedHalfOpen")
131131
.xfail(.custom(
132-
{ !_isDebugAssertConfiguration() },
132+
{ !_isStdlibDebugChecksEnabled() },
133133
reason: "assertions are disabled in Release and Unchecked mode"))
134134
.code {
135135
expectCrashLater()
@@ -138,7 +138,7 @@ if #available(SwiftStdlib 5.5, *) {
138138

139139
RangeTraps.test("UncheckedClosed")
140140
.xfail(.custom(
141-
{ !_isDebugAssertConfiguration() },
141+
{ !_isStdlibDebugChecksEnabled() },
142142
reason: "assertions are disabled in Release and Unchecked mode"))
143143
.code {
144144
expectCrashLater()

validation-test/lit.site.cfg.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ if "@SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED@" == "TRUE":
122122
config.available_features.add('distributed')
123123
if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE":
124124
config.available_features.add('string_processing')
125+
if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE":
126+
config.available_features.add('swift_stdlib_debug_preconditions_in_release')
125127

126128
config.swift_freestanding_is_darwin = "@SWIFT_FREESTANDING_IS_DARWIN@" == "TRUE"
127129
config.swift_enable_dispatch = "@SWIFT_ENABLE_DISPATCH@" == "TRUE"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Debug -Onone
3+
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Release -O
4+
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Unchecked -Ounchecked
5+
// RUN: %target-codesign %t/Assert_Debug
6+
// RUN: %target-codesign %t/Assert_Release
7+
// RUN: %target-codesign %t/Assert_Unchecked
8+
// RUN: %target-run %t/Assert_Debug | %FileCheck --check-prefixes=DEBUG %s
9+
// RUN: %target-run %t/Assert_Release | %FileCheck --check-prefixes=RELEASE %s
10+
// RUN: %target-run %t/Assert_Unchecked | %FileCheck --check-prefixes=UNCHECKED %s
11+
12+
// UNSUPPORTED: swift_stdlib_debug_preconditions_in_release
13+
14+
// DEBUG: _isStdlibDebugChecksEnabled: true
15+
// RELEASE: _isStdlibDebugChecksEnabled: false
16+
// UNCHECKED: _isStdlibDebugChecksEnabled: false
17+
print("_isStdlibDebugChecksEnabled: \(_isStdlibDebugChecksEnabled())")
18+
19+
20+
func check() -> Bool {
21+
print("Debug preconditions are active")
22+
return true
23+
}
24+
25+
// DEBUG-NEXT: Debug preconditions are active
26+
// RELEASE-NOT: Debug preconditions are active
27+
// UNCHECKED-NOT: Debug preconditions are active
28+
_debugPrecondition(check()) // Note: side effects in an assert are a terrible
29+
// idea; do not emulate this pattern in real code.
30+
31+
// DEBUG-NEXT: Done
32+
// RELEASE-NEXT: Done
33+
// UNCHECKED-NEXT: Done
34+
print("Done")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Debug -Onone
3+
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Release -O
4+
// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Unchecked -Ounchecked
5+
// RUN: %target-codesign %t/Assert_Debug
6+
// RUN: %target-codesign %t/Assert_Release
7+
// RUN: %target-codesign %t/Assert_Unchecked
8+
// RUN: %target-run %t/Assert_Debug | %FileCheck --check-prefixes=DEBUG %s
9+
// RUN: %target-run %t/Assert_Release | %FileCheck --check-prefixes=RELEASE %s
10+
// RUN: %target-run %t/Assert_Unchecked | %FileCheck --check-prefixes=UNCHECKED %s
11+
12+
// REQUIRES: swift_stdlib_debug_preconditions_in_release
13+
14+
// DEBUG: _isStdlibDebugChecksEnabled: true
15+
// RELEASE: _isStdlibDebugChecksEnabled: true
16+
// UNCHECKED: _isStdlibDebugChecksEnabled: false
17+
print("_isStdlibDebugChecksEnabled: \(_isStdlibDebugChecksEnabled())")
18+
19+
20+
func check() -> Bool {
21+
print("Debug preconditions are active")
22+
return true
23+
}
24+
25+
// DEBUG-NEXT: Debug preconditions are active
26+
// RELEASE-NEXT: Debug preconditions are active
27+
// UNCHECKED-NOT: Debug preconditions are active
28+
_debugPrecondition(check()) // Note: side effects in an assert are a terrible
29+
// idea; do not emulate this pattern in real code.
30+
31+
// DEBUG-NEXT: Done
32+
// RELEASE-NEXT: Done
33+
// UNCHECKED-NEXT: Done
34+
print("Done")

validation-test/stdlib/Assert.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,32 @@ Assert.test("preconditionFailure")
174174
preconditionFailure("this should fail")
175175
}
176176

177+
Assert.test("_precondition")
178+
.xfail(.custom(
179+
{ _isFastAssertConfiguration() },
180+
reason: "preconditions are disabled in Unchecked mode"))
181+
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
182+
.code {
183+
var x = 2
184+
_precondition(x * 21 == 42, "should not fail")
185+
expectCrashLater()
186+
_precondition(x == 42, "this should fail")
187+
}
188+
189+
Assert.test("_preconditionFailure")
190+
.skip(.custom(
191+
{ _isFastAssertConfiguration() },
192+
reason: "optimizer assumes that the code path is unreachable"))
193+
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
194+
.code {
195+
expectCrashLater()
196+
_preconditionFailure("this should fail")
197+
}
198+
177199
Assert.test("_debugPrecondition")
178200
.xfail(.custom(
179-
{ !_isDebugAssertConfiguration() },
180-
reason: "debug preconditions are disabled in Release and Unchecked mode"))
201+
{ !_isStdlibDebugChecksEnabled() },
202+
reason: "debug preconditions are disabled"))
181203
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
182204
.code {
183205
var x = 2
@@ -188,9 +210,9 @@ Assert.test("_debugPrecondition")
188210

189211
Assert.test("_debugPreconditionFailure")
190212
.skip(.custom(
191-
{ !_isDebugAssertConfiguration() },
213+
{ !_isStdlibDebugChecksEnabled() },
192214
reason: "optimizer assumes that the code path is unreachable"))
193-
.crashOutputMatches("this should fail")
215+
.crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "")
194216
.code {
195217
expectCrashLater()
196218
_debugPreconditionFailure("this should fail")

0 commit comments

Comments
 (0)