Add support for an assert_configuration builtin function

This patch adds support for a builtin function assert_configuration that is
replaced by constant progpagation by an appropriate value dependent on a compile
time setting. This replacement can also be disabled when serializing sil for a
library.

Using this mechanism we implement assertions that can  be disabled (or whose
behavior changes) depending on compile time build settings (Debug, Release,
DisableReplacement).

In the standard library we can now write one assert function that uses this
builtin function to provide different compile time selectable runtime behavior.

Example

Assert.swift:

@transparent
func assert<T : LogicValue>(
  condition: @auto_closure () -> T, message: StaticString = StaticString(),

  // Do not supply these parameters explicitly; they will be filled in
  // by the compiler and aren't even present when asserts are disabled
  file: StaticString = __FILE__, line: UWord = __LINE__
) {
  // Only in debug mode.
  if _isDebug() {
    assert(condition().getLogicValue(), message, file, line)
  }
}

AssertCommon.swift:

@transparent
func _isDebug() -> Bool {
  return Int32(Builtin.assert_configuration()) == 0;
}

rdar://16458612

Swift SVN r16472
This commit is contained in:
Arnold Schwaighofer
2014-04-17 22:05:42 +00:00
parent 527a3149a8
commit 989d554a45
11 changed files with 239 additions and 22 deletions

View File

@@ -3590,3 +3590,54 @@ Performs a checked conversion from ``$A`` to ``$B``. If the conversion succeeds,
control is transferred to ``bb1``, and the result of the cast is passed into control is transferred to ``bb1``, and the result of the cast is passed into
``bb1`` as an argument. If the conversion fails, control is transferred to ``bb1`` as an argument. If the conversion fails, control is transferred to
``bb2``. ``bb2``.
Assertion configuration
~~~~~~~~~~~~~~~~~~~~~~~
To be able to support disabling assertions at compile time there is a builtin
``assertion_configuration`` function. A call to this function can be replaced at
compile time by a constant or can stay opaque.
All calls to the ``assert_configuration`` function are replaced by the constant
propagation pass to the appropriate constant depending on compile time settings.
Subsequent passes remove dependent unwanted control flow. Using this mechanism
we support conditionally enabling/disabling of code in SIL libraries depending
on the assertion configuration selected when the library is linked into user
code.
There are three assertion configurations: Debug (0), Release (1) and
DisableReplacement (-1).
The optimization flag or a special assert configuration flag determines the
value. Depending on the configuration value assertions in the standard library
will be executed or not.
The standard library uses this builtin to define an assert that can be
disabled at compile time.
::
func assert(...) {
if (Int32(Builtin.assert_configuration()) == 0) {
_fatal_error_message(message, ...)
}
}
The ``assert_configuration`` function application is serialized when we build
the standard library (we recognize the ``-parse-stdlib`` option and don't do the
constant replacement but leave the function application to be serialized to
sil).
The compiler flag that influences the value of the ``assert_configuration``
funtion application is the optimization flag: at ``-O0`` the application will be
replaced by ``Debug`` at higher optimization levels the instruction will be
replaced by ``Release``. Optionally, the value to use for replacement can be
specified with the ``-AssertConf`` flag which overwrites the value selected by
the optimization flag (possible values are ``Debug``, ``Release``,
``DisableReplacement``).
If the call to the ``assert_configuration`` function stays opaque until IRGen,
IRGen will replace the application by the constant representing Debug mode (0).
This happens we can build the standard library .dylib. The generate sil will
retain the function call but the generated .dylib will contain code with
assertions enabled.

View File

@@ -241,6 +241,10 @@ BUILTIN_MISC_OPERATION(InsertElement, "insertelement", "n", Special)
/// StaticReport has type (Builtin.Int1, Builtin.Int1, Builtin.RawPointer) -> () /// StaticReport has type (Builtin.Int1, Builtin.Int1, Builtin.RawPointer) -> ()
BUILTIN_MISC_OPERATION(StaticReport, "staticReport", "", Special) BUILTIN_MISC_OPERATION(StaticReport, "staticReport", "", Special)
/// assert_configuration has type () -> Builtin.Int32
/// Returns the selected assertion configuration.
BUILTIN_MISC_OPERATION(AssertConf, "assert_configuration", "n", Special)
/// Special truncation builtins that check for sign and overflow errors. These /// Special truncation builtins that check for sign and overflow errors. These
/// take an integer as an input and return a tuple of the truncated result and /// take an integer as an input and return a tuple of the truncated result and

View File

@@ -64,6 +64,20 @@ public:
/// Are we debugging sil serialization. /// Are we debugging sil serialization.
bool DebugSerialization = false; bool DebugSerialization = false;
enum AssertConfiguration: unsigned {
// Used by standard library code to distinguish between a debug and release
// build.
Debug = 0, // Enables asserts.
Release = 1, // Disables asserts.
// Leave the assert_configuration instruction around.
DisableReplacement = UINT_MAX
};
/// The assert configuration controls how assertions behave.
unsigned AssertConfig = DisableReplacement;
}; };
} // end namespace swift } // end namespace swift

View File

@@ -193,6 +193,15 @@ def O_Group : OptionGroup<"<optimization level options>">;
def O0 : Flag<["-"], "O0">, Group<O_Group>, Flags<[FrontendOption]>; def O0 : Flag<["-"], "O0">, Group<O_Group>, Flags<[FrontendOption]>;
def O : Joined<["-"], "O">, Group<O_Group>, Flags<[FrontendOption]>; def O : Joined<["-"], "O">, Group<O_Group>, Flags<[FrontendOption]>;
// Assert configuration identifiers.
def AssertConfig_Group : OptionGroup<"<Assert configuration>">;
def AssertConfig : JoinedOrSeparate<["-"], "AssertConfig">,
Group<AssertConfig_Group>, Flags<[FrontendOption]>;
def AssertConfigEq : Joined<["-"], "AssertConfig=">,
Group<AssertConfig_Group>, Alias<AssertConfig>, Flags<[FrontendOption]>;
// File types // File types
def parse_as_library : Flag<["-"], "parse-as-library">, def parse_as_library : Flag<["-"], "parse-as-library">,

View File

@@ -604,6 +604,14 @@ static ValueDecl *getCondFailOperation(ASTContext &C, Identifier Id) {
return getBuiltinFunction(Id, CondElt, VoidTy); return getBuiltinFunction(Id, CondElt, VoidTy);
} }
static ValueDecl *getAssertConfOperation(ASTContext &C, Identifier Id) {
// () -> Int32
auto Int32Ty = BuiltinIntegerType::get(32, C);
auto VoidTy = TupleType::getEmpty(C);
TupleTypeElt EmptyElt(VoidTy);
return getBuiltinFunction(Id, EmptyElt, Int32Ty);
}
static ValueDecl *getFixLifetimeOperation(ASTContext &C, Identifier Id) { static ValueDecl *getFixLifetimeOperation(ASTContext &C, Identifier Id) {
// <T> T -> () // <T> T -> ()
Type GenericTy; Type GenericTy;
@@ -1165,6 +1173,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
case BuiltinValueKind::CondFail: case BuiltinValueKind::CondFail:
return getCondFailOperation(Context, Id); return getCondFailOperation(Context, Id);
case BuiltinValueKind::AssertConf:
return getAssertConfOperation(Context, Id);
case BuiltinValueKind::FixLifetime: case BuiltinValueKind::FixLifetime:
return getFixLifetimeOperation(Context, Id); return getFixLifetimeOperation(Context, Id);

View File

@@ -640,6 +640,7 @@ static void PrintArg(raw_ostream &OS, const char *Arg, bool Quote) {
} }
static bool ParseSILArgs(SILOptions &Opts, ArgList &Args, static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
IRGenOptions &IRGenOpts,
DiagnosticEngine &Diags) { DiagnosticEngine &Diags) {
using namespace options; using namespace options;
@@ -675,7 +676,54 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
llvm_unreachable("Unknown SIL linking option!"); llvm_unreachable("Unknown SIL linking option!");
} }
Opts.RemoveRuntimeAsserts = Args.hasArg(OPT_remove_runtime_asserts); // Parse the optimization level.
if (const Arg *A = Args.getLastArg(OPT_O_Group)) {
// The maximum optimization level we currently support.
unsigned MaxLevel = 3;
if (A->getOption().matches(OPT_O0)) {
IRGenOpts.OptLevel = 0;
} else {
unsigned OptLevel;
if (StringRef(A->getValue()).getAsInteger(10, OptLevel) ||
OptLevel > MaxLevel) {
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());
return true;
}
IRGenOpts.OptLevel = OptLevel;
}
} else {
IRGenOpts.OptLevel = 0;
}
// Parse the build configuration identifier.
if (const Arg *A = Args.getLastArg(OPT_AssertConfig_Group)) {
// We currently understand build configuration up to 3 of which we only use
// 0 and 1 in the standard library.
unsigned MaxId = 3;
StringRef Configuration = A->getValue();
if (Configuration == "DisableReplacement") {
Opts.AssertConfig = SILOptions::DisableReplacement;
} else if (Configuration == "Debug") {
Opts.AssertConfig = SILOptions::Debug;
} else if (Configuration == "Release") {
Opts.AssertConfig = SILOptions::Release;
} else {
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());
return true;
}
} else {
// Set the assert configuration according to the optimization level.
Opts.AssertConfig =
IRGenOpts.OptLevel > 0 ? SILOptions::Release : SILOptions::Debug;
}
// OFast might also set removal of runtime asserts (cond_fail).
Opts.RemoveRuntimeAsserts |= Args.hasArg(OPT_remove_runtime_asserts);
Opts.EnableARCOptimizations = !Args.hasArg(OPT_disable_arc_opts); Opts.EnableARCOptimizations = !Args.hasArg(OPT_disable_arc_opts);
Opts.VerifyAll = Args.hasArg(OPT_sil_verify_all); Opts.VerifyAll = Args.hasArg(OPT_sil_verify_all);
Opts.PrintAll = Args.hasArg(OPT_sil_print_all); Opts.PrintAll = Args.hasArg(OPT_sil_print_all);
@@ -746,24 +794,6 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
Opts.LinkLibraries.push_back(LinkLibrary(A->getValue(), Kind)); Opts.LinkLibraries.push_back(LinkLibrary(A->getValue(), Kind));
} }
if (const Arg *A = Args.getLastArg(OPT_O_Group)) {
if (A->getOption().matches(OPT_O0)) {
Opts.OptLevel = 0;
}
else {
unsigned OptLevel;
if (StringRef(A->getValue()).getAsInteger(10, OptLevel) || OptLevel > 3) {
Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value,
A->getAsString(Args), A->getValue());
return true;
}
Opts.OptLevel = OptLevel;
}
} else {
Opts.OptLevel = 0;
}
if (const Arg *A = Args.getLastArg(OPT_target_cpu)) { if (const Arg *A = Args.getLastArg(OPT_target_cpu)) {
Opts.TargetCPU = A->getValue(); Opts.TargetCPU = A->getValue();
} }
@@ -849,7 +879,7 @@ bool CompilerInvocation::parseArgs(ArrayRef<const char *> Args,
return true; return true;
} }
if (ParseSILArgs(SILOpts, *ParsedArgs, Diags)) { if (ParseSILArgs(SILOpts, *ParsedArgs, IRGenOpts, Diags)) {
return true; return true;
} }

View File

@@ -1813,6 +1813,18 @@ if (Builtin.ID == BuiltinValueKind::id) { \
// No return value. // No return value.
return; return;
} }
if (Builtin.ID == BuiltinValueKind::AssertConf) {
// Replace the call to assert_configuration by the Debug configuration
// value.
// TODO: assert(IGF.IGM.getOptions().AssertConfig ==
// SILOptions::DisableReplacement);
// Make sure this only happens in a mode where we build a library dylib.
llvm::Value *DebugAssert = IGF.Builder.getInt32(SILOptions::Debug);
out->add(DebugAssert);
return;
}
llvm_unreachable("IRGen unimplemented for this builtin!"); llvm_unreachable("IRGen unimplemented for this builtin!");
} }

View File

@@ -742,15 +742,29 @@ static SILValue constantFoldInstruction(SILInstruction &I,
return SILValue(); return SILValue();
} }
static bool isAssertConfigurationApply(SILInstruction &I) {
if (auto *AI = dyn_cast<ApplyInst>(&I))
if (auto *FR = dyn_cast<BuiltinFunctionRefInst>(AI->getCallee()))
if (FR->getBuiltinInfo().ID == BuiltinValueKind::AssertConf)
return true;
return false;
}
static bool isFoldable(SILInstruction *I) { static bool isFoldable(SILInstruction *I) {
return isa<IntegerLiteralInst>(I) || isa<FloatLiteralInst>(I); return isa<IntegerLiteralInst>(I) || isa<FloatLiteralInst>(I);
} }
static bool CCPFunctionBody(SILFunction &F, bool EnableDiagnostics) { static bool CCPFunctionBody(SILFunction &F, bool EnableDiagnostics,
unsigned AssertConfiguration) {
DEBUG(llvm::dbgs() << "*** ConstPropagation processing: " << F.getName() DEBUG(llvm::dbgs() << "*** ConstPropagation processing: " << F.getName()
<< "\n"); << "\n");
bool Changed = false; bool Changed = false;
// Should we replace calls to assert_configuration by the assert
// configuration.
bool InstantiateAssertConfiguration =
(AssertConfiguration != SILOptions::DisableReplacement);
// The list of instructions whose evaluation resulted in errror or warning. // The list of instructions whose evaluation resulted in errror or warning.
// This is used to avoid duplicate error reporting in case we reach the same // This is used to avoid duplicate error reporting in case we reach the same
// instruction from different entry points in the WorkList. // instruction from different entry points in the WorkList.
@@ -763,6 +777,8 @@ static bool CCPFunctionBody(SILFunction &F, bool EnableDiagnostics) {
for (auto &I : BB) { for (auto &I : BB) {
if (isFoldable(&I) && !I.use_empty()) if (isFoldable(&I) && !I.use_empty())
WorkList.insert(&I); WorkList.insert(&I);
else if (InstantiateAssertConfiguration && isAssertConfigurationApply(I))
WorkList.insert(&I);
} }
} }
@@ -774,6 +790,23 @@ static bool CCPFunctionBody(SILFunction &F, bool EnableDiagnostics) {
DEBUG(llvm::dbgs() << "Visiting: " << *I); DEBUG(llvm::dbgs() << "Visiting: " << *I);
// Replace assert_configuration instructions by their constant value. We
// want them to be replace even if we can't fully propagate the constant.
if (InstantiateAssertConfiguration)
if (auto *AI = dyn_cast<ApplyInst>(I))
if (isAssertConfigurationApply(*AI)) {
// Instantiate the constant.
SILBuilder B(AI);
auto AssertConfInt = B.createIntegerLiteral(
AI->getLoc(), AI->getType(0), AssertConfiguration);
AI->replaceAllUsesWith(AssertConfInt);
// Schedule users for constant folding.
WorkList.insert(AssertConfInt);
// Delete the call.
recursivelyDeleteTriviallyDeadInstructions(AI);
continue;
}
// Go through all users of the constant and try to fold them. // Go through all users of the constant and try to fold them.
FoldedUsers.clear(); FoldedUsers.clear();
for (auto Use : I->getUses()) { for (auto Use : I->getUses()) {
@@ -888,7 +921,8 @@ public:
private: private:
/// The entry point to the transformation. /// The entry point to the transformation.
void run() { void run() {
if (CCPFunctionBody(*getFunction(), EnableDiagnostics)) if (CCPFunctionBody(*getFunction(), EnableDiagnostics,
getOptions().AssertConfig))
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
} }

View File

@@ -0,0 +1,20 @@
// RUN: %swift -AssertConfig=DisableReplacement -emit-ir %s | FileCheck %s --check-prefix=DISABLED
// RUN: %swift -AssertConfig=Release -emit-ir %s | FileCheck %s --check-prefix=RELEASE
// RUN: %swift -AssertConfig=Debug -emit-ir %s | FileCheck %s --check-prefix=DEBUG
import Builtin
sil @remove_assert_configuration : $@thin () -> Builtin.Int32 {
%0 = builtin_function_ref "assert_configuration" : $@thin () -> Builtin.Int32
%1 = apply %0() : $@thin () -> Builtin.Int32
return %1 : $Builtin.Int32
}
// DISABLED-LABEL: @remove_assert_configuration
// DISABLED: ret i32 0
// DEBUG-LABEL: @remove_assert_configuration
// DEBUG: ret i32 0
// RELEASE-LABEL: @remove_assert_configuration
// RELEASE: ret i32 1

View File

@@ -0,0 +1,27 @@
// RUN: %sil-opt %s -diagnostic-constant-propagation -assert-conf-id 1 | FileCheck %s --check-prefix=ONE
// RUN: %sil-opt %s -diagnostic-constant-propagation -assert-conf-id 4294967295 | FileCheck %s --check-prefix=DISABLED
// RUN: %sil-opt %s -performance-constant-propagation -assert-conf-id 1 | FileCheck %s --check-prefix=PERFONE
import Builtin
sil @assert_configuration : $@thin () -> Builtin.Int32 {
%0 = builtin_function_ref "assert_configuration" : $@thin () -> Builtin.Int32
%1 = apply %0() : $@thin () -> Builtin.Int32
return %1 : $Builtin.Int32
}
// Test whether we can enable/disable replacement of the assert configuration
// id.
// DISABLED-LABEL: @assert_configuration
// DISABLED: [[REF:%.*]] = builtin_function_ref "assert_configuration"
// DISABLED: [[AP:%.*]] = apply [[REF]]
// DISABLED: return [[AP]]
// ONE-LABEL: @assert_configuration
// ONE: [[RES:%.*]] = integer_literal $Builtin.Int32, 1
// ONE: return [[RES]]
// PERFONE-LABEL: @assert_configuration
// PERFONE: [[RES:%.*]] = integer_literal $Builtin.Int32, 1
// PERFONE: return [[RES]]

View File

@@ -198,6 +198,10 @@ VerifyMode("verify",
llvm::cl::desc("verify diagnostics against expected-" llvm::cl::desc("verify diagnostics against expected-"
"{error|warning|note} annotations")); "{error|warning|note} annotations"));
static llvm::cl::opt<unsigned>
AssertConfId("assert-conf-id", llvm::cl::Hidden,
llvm::cl::init(0));
static llvm::cl::opt<unsigned> static llvm::cl::opt<unsigned>
SILInlineThreshold("sil-inline-threshold", llvm::cl::Hidden, SILInlineThreshold("sil-inline-threshold", llvm::cl::Hidden,
llvm::cl::init(50)); llvm::cl::init(50));
@@ -325,6 +329,7 @@ int main(int argc, char **argv) {
SILOpts.VerifyAll = EnableSILVerifyAll; SILOpts.VerifyAll = EnableSILVerifyAll;
SILOpts.PrintAll = EnableSILPrintAll; SILOpts.PrintAll = EnableSILPrintAll;
SILOpts.RemoveRuntimeAsserts = RemoveRuntimeAsserts; SILOpts.RemoveRuntimeAsserts = RemoveRuntimeAsserts;
SILOpts.AssertConfig = AssertConfId;
SILPassManager PM(CI.getSILModule(), SILOpts); SILPassManager PM(CI.getSILModule(), SILOpts);
PM.registerAnalysis(createCallGraphAnalysis(CI.getSILModule())); PM.registerAnalysis(createCallGraphAnalysis(CI.getSILModule()));