Skip to content

Commit 5b49a04

Browse files
committed
New assertions support
This adds three new assertion macros: * `ASSERT` - always compiled in, always checked * `CONDITIONAL_ASSERT` - always compiled in, checked whenever the `-compiler-assertions` flag is provided * `DEBUG_ASSERT` - only compiled into debug builds, always checked when compiled in (functionally the same as Standard C `assert`) The new `-compiler-assertions` flag is recognized by both `swift-frontend` and `swiftc`. The goal is to eventually replace every use of `assert` in the compiler with one of the above: * Most assertions will use `ASSERT` (most assertions should always be present and checked, even in release builds) * Expensive assertions can use `CONDITIONAL_ASSERT` to be suppressed by default * A few very expensive and/or brittle assertions can use `DEBUG_ASSERT` to be compiled out of release builds This should: * Improve quality by catching errors earlier, * Accelerate compiler triage and debugging by providing more accurate crash dumps by default, and * Allow compiler engineers and end users alike to add `-compiler-assertions` to get more accurate failure diagnostics with any compiler
1 parent face056 commit 5b49a04

File tree

8 files changed

+252
-25
lines changed

8 files changed

+252
-25
lines changed

include/swift/Basic/Assertions.h

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

lib/Basic/Assertions.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 "swift/Basic/Assertions.h"
18+
#undef NDEBUG
19+
#include <cassert>
20+
#include <iostream>
21+
22+
int CONDITIONAL_ASSERT_Global_enable_flag =
23+
#ifdef NDEBUG
24+
0; // Default to `off` in release builds
25+
#else
26+
0; // TODO: Default to `on` in debug builds
27+
#endif
28+
29+
[[noreturn]] void ASSERT_failure(const char *expr, const char *file, int line, const char *func) {
30+
// Only print the last component of `file`
31+
const char *f = file;
32+
for (const char *p = file; *p != '\0'; p++) {
33+
if ((p[0] == '/' || p[0] == '\\')
34+
&& p[1] != '/' && p[1] != '\\' && p[1] != '\0') {
35+
f = p + 1;
36+
}
37+
}
38+
// Format here matches that used by `assert` on macOS:
39+
std::cerr
40+
<< "Assertion failed: "
41+
<< "(" << expr << "), "
42+
<< "function " << func << ", "
43+
<< "file " << f << ", "
44+
<< "line " << line << "."
45+
<< std::endl;
46+
abort();
47+
}
48+
49+
// This has to be callable in the same way as the macro version,
50+
// so we can't put it inside a namespace.
51+
#undef CONDITIONAL_ASSERT_enabled
52+
int CONDITIONAL_ASSERT_enabled() {
53+
return (CONDITIONAL_ASSERT_Global_enable_flag != 0);
54+
}
55+

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);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "ArgsToFrontendOptionsConverter.h"
1717
#include "swift/AST/DiagnosticsFrontend.h"
18+
#include "swift/Basic/Assertions.h"
1819
#include "swift/Basic/Feature.h"
1920
#include "swift/Basic/Platform.h"
2021
#include "swift/Option/Options.h"
@@ -328,6 +329,13 @@ bool CompilerInvocation::setModuleAliasMap(std::vector<std::string> args,
328329
return ModuleAliasesConverter::computeModuleAliases(args, FrontendOpts, diags);
329330
}
330331

332+
static void ParseAssertionArgs(ArgList &args) {
333+
using namespace options;
334+
if (args.hasArg(OPT_compiler_assertions)) {
335+
CONDITIONAL_ASSERT_Global_enable_flag = 1;
336+
}
337+
}
338+
331339
static bool ParseFrontendArgs(
332340
FrontendOptions &opts, ArgList &args, DiagnosticEngine &diags,
333341
SmallVectorImpl<std::unique_ptr<llvm::MemoryBuffer>> *buffers) {
@@ -3392,6 +3400,8 @@ bool CompilerInvocation::parseArgs(
33923400
return true;
33933401
}
33943402

3403+
ParseAssertionArgs(ParsedArgs);
3404+
33953405
if (ParseLangArgs(LangOpts, ParsedArgs, Diags, FrontendOpts)) {
33963406
return true;
33973407
}

0 commit comments

Comments
 (0)