diff --git a/CMakeLists.txt b/CMakeLists.txt index d19c2810c4c25..327aa16240343 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,10 @@ option(SWIFT_ENABLE_STDLIBCORE_EXCLUSIVITY_CHECKING "Build stdlibCore with exclusivity checking enabled" FALSE) +option(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE + "Enable _debugPrecondition checks in the stdlib in Release configurations" + FALSE) + option(SWIFT_ENABLE_EXPERIMENTAL_DIFFERENTIABLE_PROGRAMMING "Enable experimental Swift differentiable programming features" FALSE) diff --git a/stdlib/public/core/Assert.swift b/stdlib/public/core/Assert.swift index 9d3ac42a41c41..db674fe0da38d 100644 --- a/stdlib/public/core/Assert.swift +++ b/stdlib/public/core/Assert.swift @@ -261,6 +261,9 @@ internal func _debugPrecondition( _ condition: @autoclosure () -> Bool, _ message: StaticString = StaticString(), file: StaticString = #file, line: UInt = #line ) { +#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE + _precondition(condition(), message, file: file, line: line) +#else // Only check in debug mode. if _slowPath(_isDebugAssertConfiguration()) { if !_fastPath(condition()) { @@ -268,6 +271,7 @@ internal func _debugPrecondition( flags: _fatalErrorFlags()) } } +#endif } @usableFromInline @_transparent @@ -275,10 +279,14 @@ internal func _debugPreconditionFailure( _ message: StaticString = StaticString(), file: StaticString = #file, line: UInt = #line ) -> Never { +#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE + _preconditionFailure(message, file: file, line: line) +#else if _slowPath(_isDebugAssertConfiguration()) { _precondition(false, message, file: file, line: line) } _conditionallyUnreachable() +#endif } /// Internal checks. diff --git a/stdlib/public/core/AssertCommon.swift b/stdlib/public/core/AssertCommon.swift index a082cb0f574a1..2fc4de2161506 100644 --- a/stdlib/public/core/AssertCommon.swift +++ b/stdlib/public/core/AssertCommon.swift @@ -58,6 +58,17 @@ func _isStdlibInternalChecksEnabled() -> Bool { #endif } +@_transparent +@_alwaysEmitIntoClient // Introduced in 5.7 +public // @testable +func _isStdlibDebugChecksEnabled() -> Bool { +#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE + return !_isFastAssertConfiguration() +#else + return _isDebugAssertConfiguration() +#endif +} + @usableFromInline @_transparent internal func _fatalErrorFlags() -> UInt32 { // The current flags are: diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 2394f0dcfe245..8cad9b1013e17 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -272,6 +272,9 @@ endif() if(SWIFT_STDLIB_ENABLE_STDLIBCORE_EXCLUSIVITY_CHECKING) list(APPEND swift_stdlib_compile_flags "-enforce-exclusivity=checked") endif() +if(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE) + list(APPEND swift_stdlib_compile_flags "-DSWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE") +endif() set(compile_flags_for_final_build) if(SWIFT_ENABLE_ARRAY_COW_CHECKS) list(APPEND compile_flags_for_final_build "-DCOW_CHECKS_ENABLED") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2a3b8d92e9b8c..04436afe5809f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -202,6 +202,7 @@ normalize_boolean_spelling(SWIFT_OPTIMIZED) normalize_boolean_spelling(SWIFT_STDLIB_SINGLE_THREADED_RUNTIME) normalize_boolean_spelling(SWIFT_ENABLE_REFLECTION) normalize_boolean_spelling(SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS) +normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE) normalize_boolean_spelling(SWIFT_HAVE_LIBXML2) normalize_boolean_spelling(SWIFT_INCLUDE_TOOLS) normalize_boolean_spelling(SWIFT_STDLIB_STATIC_PRINT) diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index d8acb9bf35af2..31402817a8913 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -143,6 +143,8 @@ if "@SWIFT_STDLIB_ENABLE_UNICODE_DATA" == "TRUE": config.available_features.add('stdlib_unicode_data') if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE": config.available_features.add('string_processing') +if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE": + config.available_features.add('swift_stdlib_debug_preconditions_in_release') config.swift_freestanding_is_darwin = "@SWIFT_FREESTANDING_IS_DARWIN@" == "TRUE" config.swift_enable_dispatch = "@SWIFT_ENABLE_DISPATCH@" == "TRUE" diff --git a/test/stdlib/RangeTraps.swift b/test/stdlib/RangeTraps.swift index 608764c84f7db..3954079e8ef7c 100644 --- a/test/stdlib/RangeTraps.swift +++ b/test/stdlib/RangeTraps.swift @@ -129,7 +129,7 @@ if #available(SwiftStdlib 5.5, *) { // Debug check was introduced in https://github.com/apple/swift/pull/34961 RangeTraps.test("UncheckedHalfOpen") .xfail(.custom( - { !_isDebugAssertConfiguration() }, + { !_isStdlibDebugChecksEnabled() }, reason: "assertions are disabled in Release and Unchecked mode")) .code { expectCrashLater() @@ -138,7 +138,7 @@ if #available(SwiftStdlib 5.5, *) { RangeTraps.test("UncheckedClosed") .xfail(.custom( - { !_isDebugAssertConfiguration() }, + { !_isStdlibDebugChecksEnabled() }, reason: "assertions are disabled in Release and Unchecked mode")) .code { expectCrashLater() diff --git a/validation-test/lit.site.cfg.in b/validation-test/lit.site.cfg.in index 769c49fcbadde..6e4718d9bff30 100644 --- a/validation-test/lit.site.cfg.in +++ b/validation-test/lit.site.cfg.in @@ -122,6 +122,8 @@ if "@SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED@" == "TRUE": config.available_features.add('distributed') if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE": config.available_features.add('string_processing') +if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE": + config.available_features.add('swift_stdlib_debug_preconditions_in_release') config.swift_freestanding_is_darwin = "@SWIFT_FREESTANDING_IS_DARWIN@" == "TRUE" config.swift_enable_dispatch = "@SWIFT_ENABLE_DISPATCH@" == "TRUE" diff --git a/validation-test/stdlib/Assert-debugPrecondition-off.swift b/validation-test/stdlib/Assert-debugPrecondition-off.swift new file mode 100644 index 0000000000000..5d80f7eeed10e --- /dev/null +++ b/validation-test/stdlib/Assert-debugPrecondition-off.swift @@ -0,0 +1,34 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Debug -Onone +// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Release -O +// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Unchecked -Ounchecked +// RUN: %target-codesign %t/Assert_Debug +// RUN: %target-codesign %t/Assert_Release +// RUN: %target-codesign %t/Assert_Unchecked +// RUN: %target-run %t/Assert_Debug | %FileCheck --check-prefixes=DEBUG %s +// RUN: %target-run %t/Assert_Release | %FileCheck --check-prefixes=RELEASE %s +// RUN: %target-run %t/Assert_Unchecked | %FileCheck --check-prefixes=UNCHECKED %s + +// UNSUPPORTED: swift_stdlib_debug_preconditions_in_release + +// DEBUG: _isStdlibDebugChecksEnabled: true +// RELEASE: _isStdlibDebugChecksEnabled: false +// UNCHECKED: _isStdlibDebugChecksEnabled: false +print("_isStdlibDebugChecksEnabled: \(_isStdlibDebugChecksEnabled())") + + +func check() -> Bool { + print("Debug preconditions are active") + return true +} + +// DEBUG-NEXT: Debug preconditions are active +// RELEASE-NOT: Debug preconditions are active +// UNCHECKED-NOT: Debug preconditions are active +_debugPrecondition(check()) // Note: side effects in an assert are a terrible + // idea; do not emulate this pattern in real code. + +// DEBUG-NEXT: Done +// RELEASE-NEXT: Done +// UNCHECKED-NEXT: Done +print("Done") diff --git a/validation-test/stdlib/Assert-debugPrecondition-on.swift b/validation-test/stdlib/Assert-debugPrecondition-on.swift new file mode 100644 index 0000000000000..78fce37f1cdac --- /dev/null +++ b/validation-test/stdlib/Assert-debugPrecondition-on.swift @@ -0,0 +1,34 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Debug -Onone +// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Release -O +// RUN: %target-build-swift %s -Xfrontend -disable-access-control -o %t/Assert_Unchecked -Ounchecked +// RUN: %target-codesign %t/Assert_Debug +// RUN: %target-codesign %t/Assert_Release +// RUN: %target-codesign %t/Assert_Unchecked +// RUN: %target-run %t/Assert_Debug | %FileCheck --check-prefixes=DEBUG %s +// RUN: %target-run %t/Assert_Release | %FileCheck --check-prefixes=RELEASE %s +// RUN: %target-run %t/Assert_Unchecked | %FileCheck --check-prefixes=UNCHECKED %s + +// REQUIRES: swift_stdlib_debug_preconditions_in_release + +// DEBUG: _isStdlibDebugChecksEnabled: true +// RELEASE: _isStdlibDebugChecksEnabled: true +// UNCHECKED: _isStdlibDebugChecksEnabled: false +print("_isStdlibDebugChecksEnabled: \(_isStdlibDebugChecksEnabled())") + + +func check() -> Bool { + print("Debug preconditions are active") + return true +} + +// DEBUG-NEXT: Debug preconditions are active +// RELEASE-NEXT: Debug preconditions are active +// UNCHECKED-NOT: Debug preconditions are active +_debugPrecondition(check()) // Note: side effects in an assert are a terrible + // idea; do not emulate this pattern in real code. + +// DEBUG-NEXT: Done +// RELEASE-NEXT: Done +// UNCHECKED-NEXT: Done +print("Done") diff --git a/validation-test/stdlib/Assert.swift b/validation-test/stdlib/Assert.swift index bfd6e1bd39eb5..7df90cb4ba57b 100644 --- a/validation-test/stdlib/Assert.swift +++ b/validation-test/stdlib/Assert.swift @@ -174,10 +174,32 @@ Assert.test("preconditionFailure") preconditionFailure("this should fail") } +Assert.test("_precondition") + .xfail(.custom( + { _isFastAssertConfiguration() }, + reason: "preconditions are disabled in Unchecked mode")) + .crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "") + .code { + var x = 2 + _precondition(x * 21 == 42, "should not fail") + expectCrashLater() + _precondition(x == 42, "this should fail") +} + +Assert.test("_preconditionFailure") + .skip(.custom( + { _isFastAssertConfiguration() }, + reason: "optimizer assumes that the code path is unreachable")) + .crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "") + .code { + expectCrashLater() + _preconditionFailure("this should fail") +} + Assert.test("_debugPrecondition") .xfail(.custom( - { !_isDebugAssertConfiguration() }, - reason: "debug preconditions are disabled in Release and Unchecked mode")) + { !_isStdlibDebugChecksEnabled() }, + reason: "debug preconditions are disabled")) .crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "") .code { var x = 2 @@ -188,9 +210,9 @@ Assert.test("_debugPrecondition") Assert.test("_debugPreconditionFailure") .skip(.custom( - { !_isDebugAssertConfiguration() }, + { !_isStdlibDebugChecksEnabled() }, reason: "optimizer assumes that the code path is unreachable")) - .crashOutputMatches("this should fail") + .crashOutputMatches(_isDebugAssertConfiguration() ? "this should fail" : "") .code { expectCrashLater() _debugPreconditionFailure("this should fail")