Skip to content

Commit

Permalink
Add support for __builtin_verbose_trap (#79230)
Browse files Browse the repository at this point in the history
The builtin causes the program to stop its execution abnormally and
shows a human-readable description of the reason for the termination
when a debugger is attached or in a symbolicated crash log.

The motivation for the builtin is explained in the following RFC:

https://discourse.llvm.org/t/rfc-adding-builtin-verbose-trap-string-literal/75845

clang's CodeGen lowers the builtin to `llvm.trap` and emits debugging
information that represents an artificial inline frame whose name
encodes the category and reason strings passed to the builtin.
  • Loading branch information
ahatanak authored Jun 25, 2024
1 parent 731db06 commit 2604830
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 3 deletions.
54 changes: 54 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3483,6 +3483,60 @@ Query for this feature with ``__has_builtin(__builtin_trap)``.
``__builtin_arm_trap`` is lowered to the ``llvm.aarch64.break`` builtin, and then to ``brk #payload``.
``__builtin_verbose_trap``
--------------------------
``__builtin_verbose_trap`` causes the program to stop its execution abnormally
and shows a human-readable description of the reason for the termination when a
debugger is attached or in a symbolicated crash log.
**Syntax**:
.. code-block:: c++
__builtin_verbose_trap(const char *category, const char *reason)
**Description**
``__builtin_verbose_trap`` is lowered to the ` ``llvm.trap`` <https://llvm.org/docs/LangRef.html#llvm-trap-intrinsic>`_ builtin.
Additionally, clang emits debugging information that represents an artificial
inline frame whose name encodes the category and reason strings passed to the builtin,
prefixed by a "magic" prefix.
For example, consider the following code:
.. code-block:: c++
void foo(int* p) {
if (p == nullptr)
__builtin_verbose_trap("check null", "Argument must not be null!");
}
The debugging information would look as if it were produced for the following code:
.. code-block:: c++
__attribute__((always_inline))
inline void "__clang_trap_msg$check null$Argument must not be null!"() {
__builtin_trap();
}
void foo(int* p) {
if (p == nullptr)
"__clang_trap_msg$check null$Argument must not be null!"();
}
However, the generated code would not actually contain a call to the artificial
function — it only exists in the debugging information.
Query for this feature with ``__has_builtin(__builtin_verbose_trap)``. Note that
users need to enable debug information to enable this feature. A call to this
builtin is equivalent to a call to ``__builtin_trap`` if debug information isn't
enabled.
The optimizer can merge calls to trap with different messages, which degrades
the debugging experience.
``__builtin_allow_runtime_check``
---------------------------------
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,11 @@ class Expr : public ValueStmt {
const Expr *PtrExpression, ASTContext &Ctx,
EvalResult &Status) const;

/// If the current Expr can be evaluated to a pointer to a null-terminated
/// constant string, return the constant string (without the terminating
/// null).
std::optional<std::string> tryEvaluateString(ASTContext &Ctx) const;

/// Enumeration used to describe the kind of Null pointer constant
/// returned from \c isNullPointerConstant().
enum NullPointerConstantKind {
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,12 @@ def Trap : Builtin {
let Prototype = "void()";
}

def VerboseTrap : Builtin {
let Spellings = ["__builtin_verbose_trap"];
let Attributes = [NoThrow, NoReturn];
let Prototype = "void(char const*, char const*)";
}

def Debugtrap : Builtin {
let Spellings = ["__builtin_debugtrap"];
let Attributes = [NoThrow];
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -8956,6 +8956,8 @@ def err_expected_callable_argument : Error<
"expected a callable expression as %ordinal0 argument to %1, found %2">;
def note_building_builtin_dump_struct_call : Note<
"in call to printing function with arguments '(%0)' while dumping struct">;
def err_builtin_verbose_trap_arg : Error<
"argument to __builtin_verbose_trap must %select{be a pointer to a constant string|not contain $}0">;

def err_atomic_load_store_uses_lib : Error<
"atomic %select{load|store}0 requires runtime support that is not "
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/CodeGen/ModuleBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "clang/AST/ASTConsumer.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/StringRef.h"

namespace llvm {
class Constant;
Expand All @@ -27,6 +28,9 @@ namespace llvm {
}
}

// Prefix of the name of the artificial inline frame.
inline constexpr llvm::StringRef ClangTrapPrefix = "__clang_trap_msg";

namespace clang {
class CodeGenOptions;
class CoverageSourceInfo;
Expand Down
21 changes: 18 additions & 3 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1885,7 +1885,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result,
EvalInfo &Info);
static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result);
static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
EvalInfo &Info);
EvalInfo &Info,
std::string *StringResult = nullptr);

/// Evaluate an integer or fixed point expression into an APResult.
static bool EvaluateFixedPointOrInteger(const Expr *E, APFixedPoint &Result,
Expand Down Expand Up @@ -17009,7 +17010,7 @@ bool Expr::tryEvaluateObjectSize(uint64_t &Result, ASTContext &Ctx,
}

static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
EvalInfo &Info) {
EvalInfo &Info, std::string *StringResult) {
if (!E->getType()->hasPointerRepresentation() || !E->isPRValue())
return false;

Expand All @@ -17036,6 +17037,8 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
Str = Str.substr(0, Pos);

Result = Str.size();
if (StringResult)
*StringResult = Str;
return true;
}

Expand All @@ -17051,12 +17054,24 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
if (!Char.getInt()) {
Result = Strlen;
return true;
}
} else if (StringResult)
StringResult->push_back(Char.getInt().getExtValue());
if (!HandleLValueArrayAdjustment(Info, E, String, CharTy, 1))
return false;
}
}

std::optional<std::string> Expr::tryEvaluateString(ASTContext &Ctx) const {
Expr::EvalStatus Status;
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold);
uint64_t Result;
std::string StringResult;

if (EvaluateBuiltinStrLen(this, Result, Info, &StringResult))
return StringResult;
return {};
}

bool Expr::EvaluateCharRangeAsString(std::string &Result,
const Expr *SizeExpression,
const Expr *PtrExpression, ASTContext &Ctx,
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3505,6 +3505,18 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
case Builtin::BI__builtin_trap:
EmitTrapCall(Intrinsic::trap);
return RValue::get(nullptr);
case Builtin::BI__builtin_verbose_trap: {
llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();
if (getDebugInfo()) {
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
TrapLocation, *E->getArg(0)->tryEvaluateString(getContext()),
*E->getArg(1)->tryEvaluateString(getContext()));
}
ApplyDebugLocation ApplyTrapDI(*this, TrapLocation);
// Currently no attempt is made to prevent traps from being merged.
EmitTrapCall(Intrinsic::trap);
return RValue::get(nullptr);
}
case Builtin::BI__debugbreak:
EmitTrapCall(Intrinsic::debugtrap);
return RValue::get(nullptr);
Expand Down
40 changes: 40 additions & 0 deletions clang/lib/CodeGen/CGDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/CodeGen/ModuleBuilder.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Lex/HeaderSearchOptions.h"
#include "clang/Lex/ModuleMap.h"
Expand Down Expand Up @@ -1731,6 +1732,28 @@ llvm::DIType *CGDebugInfo::createFieldType(
offsetInBits, flags, debugType, Annotations);
}

llvm::DISubprogram *
CGDebugInfo::createInlinedTrapSubprogram(StringRef FuncName,
llvm::DIFile *FileScope) {
// We are caching the subprogram because we don't want to duplicate
// subprograms with the same message. Note that `SPFlagDefinition` prevents
// subprograms from being uniqued.
llvm::DISubprogram *&SP = InlinedTrapFuncMap[FuncName];

if (!SP) {
llvm::DISubroutineType *DIFnTy = DBuilder.createSubroutineType(nullptr);
SP = DBuilder.createFunction(
/*Scope=*/FileScope, /*Name=*/FuncName, /*LinkageName=*/StringRef(),
/*File=*/FileScope, /*LineNo=*/0, /*Ty=*/DIFnTy,
/*ScopeLine=*/0,
/*Flags=*/llvm::DINode::FlagArtificial,
/*SPFlags=*/llvm::DISubprogram::SPFlagDefinition,
/*TParams=*/nullptr, /*ThrownTypes=*/nullptr, /*Annotations=*/nullptr);
}

return SP;
}

void CGDebugInfo::CollectRecordLambdaFields(
const CXXRecordDecl *CXXDecl, SmallVectorImpl<llvm::Metadata *> &elements,
llvm::DIType *RecordTy) {
Expand Down Expand Up @@ -3527,6 +3550,23 @@ llvm::DIMacroFile *CGDebugInfo::CreateTempMacroFile(llvm::DIMacroFile *Parent,
return DBuilder.createTempMacroFile(Parent, Line, FName);
}

llvm::DILocation *CGDebugInfo::CreateTrapFailureMessageFor(
llvm::DebugLoc TrapLocation, StringRef Category, StringRef FailureMsg) {
// Create a debug location from `TrapLocation` that adds an artificial inline
// frame.
SmallString<64> FuncName(ClangTrapPrefix);

FuncName += "$";
FuncName += Category;
FuncName += "$";
FuncName += FailureMsg;

llvm::DISubprogram *TrapSP =
createInlinedTrapSubprogram(FuncName, TrapLocation->getFile());
return llvm::DILocation::get(CGM.getLLVMContext(), /*Line=*/0, /*Column=*/0,
/*Scope=*/TrapSP, /*InlinedAt=*/TrapLocation);
}

static QualType UnwrapTypeForDebugInfo(QualType T, const ASTContext &C) {
Qualifiers Quals;
do {
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/CodeGen/CGDebugInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Support/Allocator.h"
#include <map>
#include <optional>
#include <string>

namespace llvm {
class MDNode;
Expand Down Expand Up @@ -346,6 +348,14 @@ class CGDebugInfo {
const FieldDecl *BitFieldDecl, const llvm::DIDerivedType *BitFieldDI,
llvm::ArrayRef<llvm::Metadata *> PreviousFieldsDI, const RecordDecl *RD);

/// A cache that maps names of artificial inlined functions to subprograms.
llvm::StringMap<llvm::DISubprogram *> InlinedTrapFuncMap;

/// A function that returns the subprogram corresponding to the artificial
/// inlined function for traps.
llvm::DISubprogram *createInlinedTrapSubprogram(StringRef FuncName,
llvm::DIFile *FileScope);

/// Helpers for collecting fields of a record.
/// @{
void CollectRecordLambdaFields(const CXXRecordDecl *CXXDecl,
Expand Down Expand Up @@ -608,6 +618,18 @@ class CGDebugInfo {
return CoroutineParameterMappings;
}

/// Create a debug location from `TrapLocation` that adds an artificial inline
/// frame where the frame name is
///
/// * `<Prefix>:<Category>:<FailureMsg>`
///
/// `<Prefix>` is "__clang_trap_msg".
///
/// This is used to store failure reasons for traps.
llvm::DILocation *CreateTrapFailureMessageFor(llvm::DebugLoc TrapLocation,
StringRef Category,
StringRef FailureMsg);

private:
/// Emit call to llvm.dbg.declare for a variable declaration.
/// Returns a pointer to the DILocalVariable associated with the
Expand Down
32 changes: 32 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,33 @@ bool Sema::checkArgCount(CallExpr *Call, unsigned DesiredArgCount) {
<< /*is non object*/ 0 << Call->getArg(1)->getSourceRange();
}

static bool checkBuiltinVerboseTrap(CallExpr *Call, Sema &S) {
bool HasError = false;

for (unsigned I = 0; I < Call->getNumArgs(); ++I) {
Expr *Arg = Call->getArg(I);

if (Arg->isValueDependent())
continue;

std::optional<std::string> ArgString = Arg->tryEvaluateString(S.Context);
int DiagMsgKind = -1;
// Arguments must be pointers to constant strings and cannot use '$'.
if (!ArgString.has_value())
DiagMsgKind = 0;
else if (ArgString->find('$') != std::string::npos)
DiagMsgKind = 1;

if (DiagMsgKind >= 0) {
S.Diag(Arg->getBeginLoc(), diag::err_builtin_verbose_trap_arg)
<< DiagMsgKind << Arg->getSourceRange();
HasError = true;
}
}

return !HasError;
}

static bool convertArgumentToType(Sema &S, Expr *&Value, QualType Ty) {
if (Value->isTypeDependent())
return false;
Expand Down Expand Up @@ -3351,6 +3378,11 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
case Builtin::BI__builtin_matrix_column_major_store:
return BuiltinMatrixColumnMajorStore(TheCall, TheCallResult);

case Builtin::BI__builtin_verbose_trap:
if (!checkBuiltinVerboseTrap(TheCall, *this))
return ExprError();
break;

case Builtin::BI__builtin_get_device_side_mangled_name: {
auto Check = [](CallExpr *TheCall) {
if (TheCall->getNumArgs() != 1)
Expand Down
54 changes: 54 additions & 0 deletions clang/test/CodeGenCXX/debug-info-verbose-trap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++20 -emit-llvm -debug-info-kind=limited %s -o - | FileCheck %s

// CHECK-LABEL: define void @_Z2f0v()
// CHECK: call void @llvm.trap(), !dbg ![[LOC17:.*]]

// CHECK: declare void @llvm.trap() #[[ATTR1:.*]]

// CHECK-LABEL: define void @_Z2f1v()
// CHECK: call void @llvm.trap(), !dbg ![[LOC23:.*]]
// CHECK: call void @llvm.trap(), !dbg ![[LOC25:.*]]

// CHECK-LABEL: define void @_Z2f3v()
// CHECK: call void @_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv()

// CHECK-LABEL: define internal void @_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv
// CHECK: call void @llvm.trap(), !dbg ![[LOC36:.*]]

// CHECK: attributes #[[ATTR1]] = { cold {{.*}}}

// CHECK: ![[FILESCOPE:.*]] = !DIFile(filename: "{{.*}}debug-info-verbose-trap.cpp"

char const constCat[] = "category2";
char const constMsg[] = "hello";

// CHECK: ![[SUBPROG14:.*]] = distinct !DISubprogram(name: "f0", linkageName: "_Z2f0v",
// CHECK: ![[LOC17]] = !DILocation(line: 0, scope: ![[SUBPROG18:.*]], inlinedAt: ![[LOC20:.*]])
// CHECK: ![[SUBPROG18]] = distinct !DISubprogram(name: "__clang_trap_msg$category1$Argument_must_not_be_null", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}})
// CHECK: ![[LOC20]] = !DILocation(line: [[@LINE+2]], column: 3, scope: ![[SUBPROG14]])
void f0() {
__builtin_verbose_trap("category1", "Argument_must_not_be_null");
}

// CHECK: ![[SUBPROG22:.*]] = distinct !DISubprogram(name: "f1", linkageName: "_Z2f1v",
// CHECK: ![[LOC23]] = !DILocation(line: 0, scope: ![[SUBPROG18]], inlinedAt: ![[LOC24:.*]])
// CHECK: ![[LOC24]] = !DILocation(line: [[@LINE+5]], column: 3, scope: ![[SUBPROG22]])
// CHECK: ![[LOC25]] = !DILocation(line: 0, scope: ![[SUBPROG26:.*]], inlinedAt: ![[LOC27:.*]])
// CHECK: ![[SUBPROG26]] = distinct !DISubprogram(name: "__clang_trap_msg$category2$hello", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}})
// CHECK: ![[LOC27]] = !DILocation(line: [[@LINE+3]], column: 3, scope: ![[SUBPROG22]])
void f1() {
__builtin_verbose_trap("category1", "Argument_must_not_be_null");
__builtin_verbose_trap("category2", "hello");
}

// CHECK: ![[SUBPROG32:.*]] = distinct !DISubprogram(name: "f2<constCat, constMsg>", linkageName: "_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv",
// CHECK: ![[LOC36]] = !DILocation(line: 0, scope: ![[SUBPROG26]], inlinedAt: ![[LOC37:.*]])
// CHECK: ![[LOC37]] = !DILocation(line: [[@LINE+3]], column: 3, scope: ![[SUBPROG32]])
template <const char * const category, const char * const reason>
void f2() {
__builtin_verbose_trap(category, reason);
}

void f3() {
f2<constCat, constMsg>();
}
Loading

0 comments on commit 2604830

Please sign in to comment.