Skip to content

Commit 65f78e6

Browse files
authored
Merge pull request swiftlang#73675 from tbkka/tbkka-assertions-v2
New assertions support
2 parents 53cfd44 + bb8c96c commit 65f78e6

File tree

13 files changed

+309
-25
lines changed

13 files changed

+309
-25
lines changed

include/swift/Basic/Assertions.h

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
//===--- Assertions.h - Assertion macros ----===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file provides three alternatives to the C/C++ standard `assert()` macro
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_BASIC_ASSERTIONS_H
18+
#define SWIFT_BASIC_ASSERTIONS_H
19+
20+
#include <stdint.h>
21+
22+
// Only for use in this header
23+
#if __has_builtin(__builtin_expect)
24+
#define ASSERT_UNLIKELY(expression) (__builtin_expect(!!(expression), 0))
25+
#else
26+
#define ASSERT_UNLIKELY(expression) ((expression))
27+
#endif
28+
29+
// ================================ Mandatory Asserts ================================
30+
31+
// `ASSERT(expr)`:
32+
// * is always compiled into the executable and
33+
// * always checks the condition, regardless of build or runtime settings.
34+
// This should be used for most assertions, which is why it
35+
// deserves the short name. In particular, for simple checks
36+
// (e.g., validating that something is non-null), this is just as
37+
// fast as a disabled `CONDITIONAL_ASSERT`, so there's no point in
38+
// using the conditional form.
39+
//
40+
// You can globally replace `assert` with `ASSERT` in a piece of code
41+
// to have all your assertions enabled in all builds. If you do this,
42+
// please do a little profiling first, just in case you have some checks
43+
// that are more expensive than you think. You can switch those to
44+
// `CONDITIONAL_ASSERT` or `DEBUG_ASSERT` as needed.
45+
46+
#define ASSERT(expr) \
47+
do { \
48+
if (ASSERT_UNLIKELY(!expr)) { \
49+
ASSERT_failure(#expr, __FILE__, __LINE__, __func__); \
50+
} \
51+
} while (0)
52+
53+
// Function that reports the actual failure when it occurs.
54+
void ASSERT_failure(const char *expr, const char *file, int line, const char *func);
55+
56+
// ================================ Conditional Asserts ================================
57+
58+
// `CONDITIONAL_ASSERT(expr)`:
59+
// * is always compiled into the executable, but
60+
// * only checks the condition if a runtime flag is defined.
61+
// That runtime flag is disabled by default in release builds
62+
// but can be enabled with the command-line flag `-compiler-assertions`
63+
//
64+
// Use this for asserts that are comparatively expensive to check.
65+
//
66+
// You can globally change `assert` to `CONDITIONAL_ASSERT` to make all your
67+
// assertions _optionally_ available in release builds. Anyone can then add
68+
// `-compiler-assertions` to their build flags to get more information about a
69+
// compiler problem. Before you check it in, do a little checking for
70+
// assertions that might be checked huge numbers of times (e.g., invariants
71+
// for inner loops or core utilities); those may need to become `DEBUG_ASSERT`
72+
// or else refactored to be checked more selectively.
73+
//
74+
// Over time, plan to change most of the resulting `CONDITIONAL_ASSERT` into
75+
// plain `ASSERT` to enable them by default.
76+
77+
#define CONDITIONAL_ASSERT(expr) \
78+
do { \
79+
if (ASSERT_UNLIKELY(CONDITIONAL_ASSERT_enabled())) { \
80+
ASSERT(expr); \
81+
} \
82+
} while (0)
83+
84+
// Use `CONDITIONAL_ASSERT_enabled()` to guard complex, expensive code that
85+
// should only run when assertions are enabled. This is exactly the
86+
// same check that's used to enable `CONDITIONAL_ASSERT()` at runtime.
87+
// This is not often used -- if you are just setting a flag or updating
88+
// a counter, it's likely cheaper just to do it than to test whether or not
89+
// to do it. Only use this for relatively complex operations.
90+
//
91+
// if (CONDITIONAL_ASSERT_enabled()) {
92+
// ... stuff ...
93+
// }
94+
95+
// Declare a callable function version of this runtime test.
96+
int CONDITIONAL_ASSERT_enabled();
97+
98+
// Define a macro version of this test
99+
extern int CONDITIONAL_ASSERT_Global_enable_flag;
100+
#define CONDITIONAL_ASSERT_enabled() \
101+
(CONDITIONAL_ASSERT_Global_enable_flag != 0)
102+
103+
// Profiling note: If you uncomment the line below to #undef the macro, then
104+
// we'll always call the function, which lets you profile assertions by
105+
// counting calls to this function.
106+
107+
// #undef CONDITIONAL_ASSERT_enabled
108+
109+
// ================================ Debug Asserts ================================
110+
111+
// `DEBUG_ASSERT(expr)`:
112+
// * is only compiled into the executable in debug or "asserts enabled" builds, and
113+
// * always performs the check (whenever it is compiled in).
114+
//
115+
// This basically makes it a synonym for the Standard C `assert(expr)`.
116+
//
117+
// You should mostly avoid this except for occasional experiments in your
118+
// local tree. It can be useful in two situations:
119+
//
120+
// * Assertions that are known to mis-fire.
121+
// Such assertions should not be `ASSERT` (since that will cause unnecessary
122+
// broken compiles) and `CONDITIONAL_ASSERT` gets enabled a lot by people who
123+
// are not compiler experts. So `DEBUG_ASSERT` is appropriate there until the
124+
// check can be fixed so it doesn't mis-fire.
125+
//
126+
// * Inner loops that can run billions of times.
127+
// For these, even the cost of testing whether `CONDITIONAL_ASSERT` is enabled
128+
// can be prohibitive. Use `DEBUG_ASSERT`, but also look for ways to refactor
129+
// so you can verify correctness without having an assertion in an inner loop.
130+
//
131+
// P.S. Please do not bulk replace `assert` with `DEBUG_ASSERT`. The whole
132+
// point of this package is to move us toward having assertions always compiled
133+
// in and always enabled.
134+
#ifdef NDEBUG
135+
#define DEBUG_ASSERT(expr) do { } while (0)
136+
#undef DEBUG_ASSERT_enabled
137+
#else
138+
#define DEBUG_ASSERT(expr) ASSERT(expr)
139+
#define DEBUG_ASSERT_enabled 1
140+
#endif
141+
142+
// Code that's only needed within `DEBUG_ASSERT` can be guarded as follows:
143+
//
144+
// #ifndef NDEBUG
145+
// ... code that's only needed for DEBUG_ASSERT ...
146+
// #endif
147+
//
148+
// or with the equivalent
149+
//
150+
// #ifdef DEBUG_ASSERT_enabled
151+
// ... code that's only needed for DEBUG_ASSERT ...
152+
// #endif
153+
//
154+
// For example, you may need this for variables or functions that
155+
// are only used within DEBUG_ASSERT statements.
156+
157+
// A common case is to declare a variable or perform a simple
158+
// expression. These can be used to avoid some boilerplate:
159+
//
160+
// void doThings() {
161+
// DEBUG_ASSERT_DECL(std::vector<Things> thingsToVerify;);
162+
// while (!done) {
163+
// // ... do each thing ...
164+
// DEBUG_ASSERT_EXPR(thingsToVerify.append(item));
165+
// }
166+
// DEBUG_ASSERT(verifyAllThe(thingsToVerify));
167+
// }
168+
169+
#ifdef DEBUG_ASSERT_enabled
170+
#define DEBUG_ASSERT_DECL(...) __VA_ARGS__
171+
#define DEBUG_ASSERT_EXPR(...) do { __VA_ARGS__; } while (false)
172+
#else
173+
#define DEBUG_ASSERT_DECL(...)
174+
#define DEBUG_ASSERT_EXPR(...) do { } while (false)
175+
#endif
176+
177+
// Older version of the same idea:
178+
#define SWIFT_ASSERT_ONLY_DECL DEBUG_ASSERT_DECL
179+
#define SWIFT_ASSERT_ONLY DEBUG_ASSERT_EXPR
180+
181+
// ================================ Utility and Helper Functions ================================
182+
183+
// Utility function to print out help information for
184+
// various command-line options that affect the assertion
185+
// behavior.
186+
void ASSERT_help();
187+
188+
#endif // SWIFT_BASIC_ASSERTIONS_H

include/swift/Basic/Compiler.h

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -117,31 +117,6 @@
117117
#define SWIFT_CRASH_BUG_REPORT_MESSAGE \
118118
"Please " SWIFT_BUG_REPORT_MESSAGE_BASE " and include the crash backtrace."
119119

120-
// Conditionally exclude declarations or statements that are only needed for
121-
// assertions from release builds (NDEBUG) without cluttering the surrounding
122-
// code by #ifdefs.
123-
//
124-
// struct DoThings {
125-
// SWIFT_ASSERT_ONLY_DECL(unsigned verifyCount = 0);
126-
// DoThings() {
127-
// SWIFT_ASSERT_ONLY(verifyCount = getNumberOfThingsToDo());
128-
// }
129-
// void doThings() {
130-
// do {
131-
// // ... do each thing
132-
// SWIFT_ASSERT_ONLY(--verifyCount);
133-
// } while (!done());
134-
// assert(verifyCount == 0 && "did not do everything");
135-
// }
136-
// };
137-
#ifdef NDEBUG
138-
#define SWIFT_ASSERT_ONLY_DECL(...)
139-
#define SWIFT_ASSERT_ONLY(...) do { } while (false)
140-
#else
141-
#define SWIFT_ASSERT_ONLY_DECL(...) __VA_ARGS__
142-
#define SWIFT_ASSERT_ONLY(...) do { __VA_ARGS__; } while (false)
143-
#endif
144-
145120
#if defined(__LP64__) || defined(_WIN64)
146121
#define SWIFT_POINTER_IS_8_BYTES 1
147122
#define SWIFT_POINTER_IS_4_BYTES 0

include/swift/Option/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ def no_strict_implicit_module_context :
193193
Flags<[FrontendOption, HelpHidden]>,
194194
HelpText<"Disable the strict forwarding of compilation context to downstream implicit module dependencies">;
195195

196+
def compiler_assertions :
197+
Flag<["-"], "compiler-assertions">,
198+
Group<internal_debug_Group>,
199+
Flags<[FrontendOption, DoesNotAffectIncrementalBuild, CacheInvariant]>,
200+
HelpText<"Enable internal self-checks while compiling">;
201+
196202
def validate_clang_modules_once :
197203
Flag<["-"], "validate-clang-modules-once">,
198204
Flags<[FrontendOption]>,

include/swift/SIL/FieldSensitivePrunedLiveness.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#define SWIFT_SIL_FIELDSENSITIVEPRUNTEDLIVENESS_H
2222

2323
#include "swift/AST/TypeExpansionContext.h"
24+
#include "swift/Basic/Assertions.h"
2425
#include "swift/Basic/Debug.h"
2526
#include "swift/Basic/FrozenMultiMap.h"
2627
#include "swift/Basic/STLExtras.h"

include/swift/SILOptimizer/Analysis/ClosureScope.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_CLOSURESCOPE_H
5151
#define SWIFT_SILOPTIMIZER_ANALYSIS_CLOSURESCOPE_H
5252

53+
#include "swift/Basic/Assertions.h"
5354
#include "swift/Basic/BlotSetVector.h"
5455
#include "swift/SIL/SILFunction.h"
5556
#include "swift/SIL/SILModule.h"

include/swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_NONLOCALACCESSBLOCKS_H
2929
#define SWIFT_SILOPTIMIZER_ANALYSIS_NONLOCALACCESSBLOCKS_H
3030

31+
#include "swift/Basic/Assertions.h"
3132
#include "swift/Basic/Compiler.h"
3233
#include "swift/SILOptimizer/Analysis/Analysis.h"
3334
#include "llvm/ADT/SmallPtrSet.h"

lib/Basic/Assertions.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//===--- Assertions.cpp - Swift Version Number -------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file defines custom assertion support functions
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "llvm/Support/CommandLine.h"
18+
#include "swift/Basic/Assertions.h"
19+
#undef NDEBUG
20+
#include <cassert>
21+
#include <iostream>
22+
23+
llvm::cl::opt<bool> AssertContinue(
24+
"assert-continue", llvm::cl::init(false),
25+
llvm::cl::desc("Do not stop on an assertion failure"));
26+
27+
llvm::cl::opt<bool> AssertHelp(
28+
"assert-help", llvm::cl::init(false),
29+
llvm::cl::desc("Print help for managing assertions"));
30+
31+
int CONDITIONAL_ASSERT_Global_enable_flag =
32+
#ifdef NDEBUG
33+
0; // Default to `off` in release builds
34+
#else
35+
0; // TODO: Default to `on` in debug builds
36+
#endif
37+
38+
void ASSERT_failure(const char *expr, const char *file, int line, const char *func) {
39+
// Only print the last component of `file`
40+
const char *f = file;
41+
for (const char *p = file; *p != '\0'; p++) {
42+
if ((p[0] == '/' || p[0] == '\\')
43+
&& p[1] != '/' && p[1] != '\\' && p[1] != '\0') {
44+
f = p + 1;
45+
}
46+
}
47+
48+
if (AssertHelp) {
49+
ASSERT_help();
50+
} else {
51+
std::cerr << "Assertion help: -Xllvm -assert-help" << std::endl;
52+
}
53+
54+
55+
// Format here matches that used by `assert` on macOS:
56+
std::cerr
57+
<< "Assertion failed: "
58+
<< "(" << expr << "), "
59+
<< "function " << func << ", "
60+
<< "file " << f << ", "
61+
<< "line " << line << "."
62+
<< std::endl;
63+
64+
if (AssertContinue) {
65+
std::cerr << "Continuing after failed assertion (-Xllvm -assert-continue)" << std::endl;
66+
return;
67+
}
68+
69+
abort();
70+
}
71+
72+
void ASSERT_help() {
73+
static int ASSERT_help_shown = 0;
74+
if (ASSERT_help_shown) {
75+
return;
76+
}
77+
ASSERT_help_shown = 1;
78+
79+
std::cerr << std::endl;
80+
std::cerr << "Control assertion behavior with one or more of the following options:" << std::endl;
81+
std::cerr << std::endl;
82+
std::cerr << " -Xllvm -assert-continue" << std::endl;
83+
std::cerr << " Continue after any failed assertion" << std::endl;
84+
std::cerr << std::endl;
85+
}
86+
87+
// This has to be callable in the same way as the macro version,
88+
// so we can't put it inside a namespace.
89+
#undef CONDITIONAL_ASSERT_enabled
90+
int CONDITIONAL_ASSERT_enabled() {
91+
return (CONDITIONAL_ASSERT_Global_enable_flag != 0);
92+
}
93+

lib/Basic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ generate_revision_inc(llvm_revision_inc LLVM "${LLVM_MAIN_SRC_DIR}")
4242
generate_revision_inc(swift_revision_inc Swift "${SWIFT_SOURCE_DIR}")
4343

4444
add_swift_host_library(swiftBasic STATIC
45+
Assertions.cpp
4546
BasicBridging.cpp
4647
BasicSourceInfo.cpp
4748
Cache.cpp

lib/Driver/Driver.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/AST/DiagnosticEngine.h"
2121
#include "swift/AST/DiagnosticsDriver.h"
2222
#include "swift/AST/DiagnosticsFrontend.h"
23+
#include "swift/Basic/Assertions.h"
2324
#include "swift/Basic/LLVM.h"
2425
#include "swift/Basic/LangOptions.h"
2526
#include "swift/Basic/OutputFileMap.h"
@@ -1942,6 +1943,10 @@ bool Driver::handleImmediateArgs(const ArgList &Args, const ToolChain &TC) {
19421943
return false;
19431944
}
19441945

1946+
if (Args.hasArg(options::OPT_compiler_assertions)) {
1947+
CONDITIONAL_ASSERT_Global_enable_flag = 1;
1948+
}
1949+
19451950
if (Args.hasArg(options::OPT_version)) {
19461951
// Follow gcc/clang behavior and use stdout for --version and stderr for -v.
19471952
printVersion(TC, llvm::outs());

lib/Driver/ToolChains.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
343343
inputArgs.AddLastArg(arguments, options::OPT_enable_experimental_cxx_interop);
344344
inputArgs.AddLastArg(arguments, options::OPT_cxx_interoperability_mode);
345345
inputArgs.AddLastArg(arguments, options::OPT_enable_builtin_module);
346+
inputArgs.AddLastArg(arguments, options::OPT_compiler_assertions);
346347

347348
// Pass on any build config options
348349
inputArgs.AddAllArgs(arguments, options::OPT_D);

0 commit comments

Comments
 (0)