Skip to content

Commit

Permalink
Merge pull request swiftlang#73675 from tbkka/tbkka-assertions-v2
Browse files Browse the repository at this point in the history
New assertions support
  • Loading branch information
tbkka committed May 29, 2024
2 parents 53cfd44 + bb8c96c commit 65f78e6
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 25 deletions.
188 changes: 188 additions & 0 deletions include/swift/Basic/Assertions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//===--- Assertions.h - Assertion macros ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file provides three alternatives to the C/C++ standard `assert()` macro
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_BASIC_ASSERTIONS_H
#define SWIFT_BASIC_ASSERTIONS_H

#include <stdint.h>

// Only for use in this header
#if __has_builtin(__builtin_expect)
#define ASSERT_UNLIKELY(expression) (__builtin_expect(!!(expression), 0))
#else
#define ASSERT_UNLIKELY(expression) ((expression))
#endif

// ================================ Mandatory Asserts ================================

// `ASSERT(expr)`:
// * is always compiled into the executable and
// * always checks the condition, regardless of build or runtime settings.
// This should be used for most assertions, which is why it
// deserves the short name. In particular, for simple checks
// (e.g., validating that something is non-null), this is just as
// fast as a disabled `CONDITIONAL_ASSERT`, so there's no point in
// using the conditional form.
//
// You can globally replace `assert` with `ASSERT` in a piece of code
// to have all your assertions enabled in all builds. If you do this,
// please do a little profiling first, just in case you have some checks
// that are more expensive than you think. You can switch those to
// `CONDITIONAL_ASSERT` or `DEBUG_ASSERT` as needed.

#define ASSERT(expr) \
do { \
if (ASSERT_UNLIKELY(!expr)) { \
ASSERT_failure(#expr, __FILE__, __LINE__, __func__); \
} \
} while (0)

// Function that reports the actual failure when it occurs.
void ASSERT_failure(const char *expr, const char *file, int line, const char *func);

// ================================ Conditional Asserts ================================

// `CONDITIONAL_ASSERT(expr)`:
// * is always compiled into the executable, but
// * only checks the condition if a runtime flag is defined.
// That runtime flag is disabled by default in release builds
// but can be enabled with the command-line flag `-compiler-assertions`
//
// Use this for asserts that are comparatively expensive to check.
//
// You can globally change `assert` to `CONDITIONAL_ASSERT` to make all your
// assertions _optionally_ available in release builds. Anyone can then add
// `-compiler-assertions` to their build flags to get more information about a
// compiler problem. Before you check it in, do a little checking for
// assertions that might be checked huge numbers of times (e.g., invariants
// for inner loops or core utilities); those may need to become `DEBUG_ASSERT`
// or else refactored to be checked more selectively.
//
// Over time, plan to change most of the resulting `CONDITIONAL_ASSERT` into
// plain `ASSERT` to enable them by default.

#define CONDITIONAL_ASSERT(expr) \
do { \
if (ASSERT_UNLIKELY(CONDITIONAL_ASSERT_enabled())) { \
ASSERT(expr); \
} \
} while (0)

// Use `CONDITIONAL_ASSERT_enabled()` to guard complex, expensive code that
// should only run when assertions are enabled. This is exactly the
// same check that's used to enable `CONDITIONAL_ASSERT()` at runtime.
// This is not often used -- if you are just setting a flag or updating
// a counter, it's likely cheaper just to do it than to test whether or not
// to do it. Only use this for relatively complex operations.
//
// if (CONDITIONAL_ASSERT_enabled()) {
// ... stuff ...
// }

// Declare a callable function version of this runtime test.
int CONDITIONAL_ASSERT_enabled();

// Define a macro version of this test
extern int CONDITIONAL_ASSERT_Global_enable_flag;
#define CONDITIONAL_ASSERT_enabled() \
(CONDITIONAL_ASSERT_Global_enable_flag != 0)

// Profiling note: If you uncomment the line below to #undef the macro, then
// we'll always call the function, which lets you profile assertions by
// counting calls to this function.

// #undef CONDITIONAL_ASSERT_enabled

// ================================ Debug Asserts ================================

// `DEBUG_ASSERT(expr)`:
// * is only compiled into the executable in debug or "asserts enabled" builds, and
// * always performs the check (whenever it is compiled in).
//
// This basically makes it a synonym for the Standard C `assert(expr)`.
//
// You should mostly avoid this except for occasional experiments in your
// local tree. It can be useful in two situations:
//
// * Assertions that are known to mis-fire.
// Such assertions should not be `ASSERT` (since that will cause unnecessary
// broken compiles) and `CONDITIONAL_ASSERT` gets enabled a lot by people who
// are not compiler experts. So `DEBUG_ASSERT` is appropriate there until the
// check can be fixed so it doesn't mis-fire.
//
// * Inner loops that can run billions of times.
// For these, even the cost of testing whether `CONDITIONAL_ASSERT` is enabled
// can be prohibitive. Use `DEBUG_ASSERT`, but also look for ways to refactor
// so you can verify correctness without having an assertion in an inner loop.
//
// P.S. Please do not bulk replace `assert` with `DEBUG_ASSERT`. The whole
// point of this package is to move us toward having assertions always compiled
// in and always enabled.
#ifdef NDEBUG
#define DEBUG_ASSERT(expr) do { } while (0)
#undef DEBUG_ASSERT_enabled
#else
#define DEBUG_ASSERT(expr) ASSERT(expr)
#define DEBUG_ASSERT_enabled 1
#endif

// Code that's only needed within `DEBUG_ASSERT` can be guarded as follows:
//
// #ifndef NDEBUG
// ... code that's only needed for DEBUG_ASSERT ...
// #endif
//
// or with the equivalent
//
// #ifdef DEBUG_ASSERT_enabled
// ... code that's only needed for DEBUG_ASSERT ...
// #endif
//
// For example, you may need this for variables or functions that
// are only used within DEBUG_ASSERT statements.

// A common case is to declare a variable or perform a simple
// expression. These can be used to avoid some boilerplate:
//
// void doThings() {
// DEBUG_ASSERT_DECL(std::vector<Things> thingsToVerify;);
// while (!done) {
// // ... do each thing ...
// DEBUG_ASSERT_EXPR(thingsToVerify.append(item));
// }
// DEBUG_ASSERT(verifyAllThe(thingsToVerify));
// }

#ifdef DEBUG_ASSERT_enabled
#define DEBUG_ASSERT_DECL(...) __VA_ARGS__
#define DEBUG_ASSERT_EXPR(...) do { __VA_ARGS__; } while (false)
#else
#define DEBUG_ASSERT_DECL(...)
#define DEBUG_ASSERT_EXPR(...) do { } while (false)
#endif

// Older version of the same idea:
#define SWIFT_ASSERT_ONLY_DECL DEBUG_ASSERT_DECL
#define SWIFT_ASSERT_ONLY DEBUG_ASSERT_EXPR

// ================================ Utility and Helper Functions ================================

// Utility function to print out help information for
// various command-line options that affect the assertion
// behavior.
void ASSERT_help();

#endif // SWIFT_BASIC_ASSERTIONS_H
25 changes: 0 additions & 25 deletions include/swift/Basic/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,31 +117,6 @@
#define SWIFT_CRASH_BUG_REPORT_MESSAGE \
"Please " SWIFT_BUG_REPORT_MESSAGE_BASE " and include the crash backtrace."

// Conditionally exclude declarations or statements that are only needed for
// assertions from release builds (NDEBUG) without cluttering the surrounding
// code by #ifdefs.
//
// struct DoThings {
// SWIFT_ASSERT_ONLY_DECL(unsigned verifyCount = 0);
// DoThings() {
// SWIFT_ASSERT_ONLY(verifyCount = getNumberOfThingsToDo());
// }
// void doThings() {
// do {
// // ... do each thing
// SWIFT_ASSERT_ONLY(--verifyCount);
// } while (!done());
// assert(verifyCount == 0 && "did not do everything");
// }
// };
#ifdef NDEBUG
#define SWIFT_ASSERT_ONLY_DECL(...)
#define SWIFT_ASSERT_ONLY(...) do { } while (false)
#else
#define SWIFT_ASSERT_ONLY_DECL(...) __VA_ARGS__
#define SWIFT_ASSERT_ONLY(...) do { __VA_ARGS__; } while (false)
#endif

#if defined(__LP64__) || defined(_WIN64)
#define SWIFT_POINTER_IS_8_BYTES 1
#define SWIFT_POINTER_IS_4_BYTES 0
Expand Down
6 changes: 6 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ def no_strict_implicit_module_context :
Flags<[FrontendOption, HelpHidden]>,
HelpText<"Disable the strict forwarding of compilation context to downstream implicit module dependencies">;

def compiler_assertions :
Flag<["-"], "compiler-assertions">,
Group<internal_debug_Group>,
Flags<[FrontendOption, DoesNotAffectIncrementalBuild, CacheInvariant]>,
HelpText<"Enable internal self-checks while compiling">;

def validate_clang_modules_once :
Flag<["-"], "validate-clang-modules-once">,
Flags<[FrontendOption]>,
Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/FieldSensitivePrunedLiveness.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#define SWIFT_SIL_FIELDSENSITIVEPRUNTEDLIVENESS_H

#include "swift/AST/TypeExpansionContext.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Debug.h"
#include "swift/Basic/FrozenMultiMap.h"
#include "swift/Basic/STLExtras.h"
Expand Down
1 change: 1 addition & 0 deletions include/swift/SILOptimizer/Analysis/ClosureScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_CLOSURESCOPE_H
#define SWIFT_SILOPTIMIZER_ANALYSIS_CLOSURESCOPE_H

#include "swift/Basic/Assertions.h"
#include "swift/Basic/BlotSetVector.h"
#include "swift/SIL/SILFunction.h"
#include "swift/SIL/SILModule.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_NONLOCALACCESSBLOCKS_H
#define SWIFT_SILOPTIMIZER_ANALYSIS_NONLOCALACCESSBLOCKS_H

#include "swift/Basic/Assertions.h"
#include "swift/Basic/Compiler.h"
#include "swift/SILOptimizer/Analysis/Analysis.h"
#include "llvm/ADT/SmallPtrSet.h"
Expand Down
93 changes: 93 additions & 0 deletions lib/Basic/Assertions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//===--- Assertions.cpp - Swift Version Number -------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines custom assertion support functions
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/CommandLine.h"
#include "swift/Basic/Assertions.h"
#undef NDEBUG
#include <cassert>
#include <iostream>

llvm::cl::opt<bool> AssertContinue(
"assert-continue", llvm::cl::init(false),
llvm::cl::desc("Do not stop on an assertion failure"));

llvm::cl::opt<bool> AssertHelp(
"assert-help", llvm::cl::init(false),
llvm::cl::desc("Print help for managing assertions"));

int CONDITIONAL_ASSERT_Global_enable_flag =
#ifdef NDEBUG
0; // Default to `off` in release builds
#else
0; // TODO: Default to `on` in debug builds
#endif

void ASSERT_failure(const char *expr, const char *file, int line, const char *func) {
// Only print the last component of `file`
const char *f = file;
for (const char *p = file; *p != '\0'; p++) {
if ((p[0] == '/' || p[0] == '\\')
&& p[1] != '/' && p[1] != '\\' && p[1] != '\0') {
f = p + 1;
}
}

if (AssertHelp) {
ASSERT_help();
} else {
std::cerr << "Assertion help: -Xllvm -assert-help" << std::endl;
}


// Format here matches that used by `assert` on macOS:
std::cerr
<< "Assertion failed: "
<< "(" << expr << "), "
<< "function " << func << ", "
<< "file " << f << ", "
<< "line " << line << "."
<< std::endl;

if (AssertContinue) {
std::cerr << "Continuing after failed assertion (-Xllvm -assert-continue)" << std::endl;
return;
}

abort();
}

void ASSERT_help() {
static int ASSERT_help_shown = 0;
if (ASSERT_help_shown) {
return;
}
ASSERT_help_shown = 1;

std::cerr << std::endl;
std::cerr << "Control assertion behavior with one or more of the following options:" << std::endl;
std::cerr << std::endl;
std::cerr << " -Xllvm -assert-continue" << std::endl;
std::cerr << " Continue after any failed assertion" << std::endl;
std::cerr << std::endl;
}

// This has to be callable in the same way as the macro version,
// so we can't put it inside a namespace.
#undef CONDITIONAL_ASSERT_enabled
int CONDITIONAL_ASSERT_enabled() {
return (CONDITIONAL_ASSERT_Global_enable_flag != 0);
}

1 change: 1 addition & 0 deletions lib/Basic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ generate_revision_inc(llvm_revision_inc LLVM "${LLVM_MAIN_SRC_DIR}")
generate_revision_inc(swift_revision_inc Swift "${SWIFT_SOURCE_DIR}")

add_swift_host_library(swiftBasic STATIC
Assertions.cpp
BasicBridging.cpp
BasicSourceInfo.cpp
Cache.cpp
Expand Down
5 changes: 5 additions & 0 deletions lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsDriver.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/LangOptions.h"
#include "swift/Basic/OutputFileMap.h"
Expand Down Expand Up @@ -1942,6 +1943,10 @@ bool Driver::handleImmediateArgs(const ArgList &Args, const ToolChain &TC) {
return false;
}

if (Args.hasArg(options::OPT_compiler_assertions)) {
CONDITIONAL_ASSERT_Global_enable_flag = 1;
}

if (Args.hasArg(options::OPT_version)) {
// Follow gcc/clang behavior and use stdout for --version and stderr for -v.
printVersion(TC, llvm::outs());
Expand Down
1 change: 1 addition & 0 deletions lib/Driver/ToolChains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
inputArgs.AddLastArg(arguments, options::OPT_enable_experimental_cxx_interop);
inputArgs.AddLastArg(arguments, options::OPT_cxx_interoperability_mode);
inputArgs.AddLastArg(arguments, options::OPT_enable_builtin_module);
inputArgs.AddLastArg(arguments, options::OPT_compiler_assertions);

// Pass on any build config options
inputArgs.AddAllArgs(arguments, options::OPT_D);
Expand Down
Loading

0 comments on commit 65f78e6

Please sign in to comment.