Skip to content

Commit 2604830

Browse files
authored
Add support for __builtin_verbose_trap (#79230)
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.
1 parent 731db06 commit 2604830

File tree

12 files changed

+296
-3
lines changed

12 files changed

+296
-3
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3483,6 +3483,60 @@ Query for this feature with ``__has_builtin(__builtin_trap)``.
34833483
34843484
``__builtin_arm_trap`` is lowered to the ``llvm.aarch64.break`` builtin, and then to ``brk #payload``.
34853485
3486+
``__builtin_verbose_trap``
3487+
--------------------------
3488+
3489+
``__builtin_verbose_trap`` causes the program to stop its execution abnormally
3490+
and shows a human-readable description of the reason for the termination when a
3491+
debugger is attached or in a symbolicated crash log.
3492+
3493+
**Syntax**:
3494+
3495+
.. code-block:: c++
3496+
3497+
__builtin_verbose_trap(const char *category, const char *reason)
3498+
3499+
**Description**
3500+
3501+
``__builtin_verbose_trap`` is lowered to the ` ``llvm.trap`` <https://llvm.org/docs/LangRef.html#llvm-trap-intrinsic>`_ builtin.
3502+
Additionally, clang emits debugging information that represents an artificial
3503+
inline frame whose name encodes the category and reason strings passed to the builtin,
3504+
prefixed by a "magic" prefix.
3505+
3506+
For example, consider the following code:
3507+
3508+
.. code-block:: c++
3509+
3510+
void foo(int* p) {
3511+
if (p == nullptr)
3512+
__builtin_verbose_trap("check null", "Argument must not be null!");
3513+
}
3514+
3515+
The debugging information would look as if it were produced for the following code:
3516+
3517+
.. code-block:: c++
3518+
3519+
__attribute__((always_inline))
3520+
inline void "__clang_trap_msg$check null$Argument must not be null!"() {
3521+
__builtin_trap();
3522+
}
3523+
3524+
void foo(int* p) {
3525+
if (p == nullptr)
3526+
"__clang_trap_msg$check null$Argument must not be null!"();
3527+
}
3528+
3529+
However, the generated code would not actually contain a call to the artificial
3530+
function — it only exists in the debugging information.
3531+
3532+
Query for this feature with ``__has_builtin(__builtin_verbose_trap)``. Note that
3533+
users need to enable debug information to enable this feature. A call to this
3534+
builtin is equivalent to a call to ``__builtin_trap`` if debug information isn't
3535+
enabled.
3536+
3537+
The optimizer can merge calls to trap with different messages, which degrades
3538+
the debugging experience.
3539+
34863540
``__builtin_allow_runtime_check``
34873541
---------------------------------
34883542

clang/include/clang/AST/Expr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,11 @@ class Expr : public ValueStmt {
787787
const Expr *PtrExpression, ASTContext &Ctx,
788788
EvalResult &Status) const;
789789

790+
/// If the current Expr can be evaluated to a pointer to a null-terminated
791+
/// constant string, return the constant string (without the terminating
792+
/// null).
793+
std::optional<std::string> tryEvaluateString(ASTContext &Ctx) const;
794+
790795
/// Enumeration used to describe the kind of Null pointer constant
791796
/// returned from \c isNullPointerConstant().
792797
enum NullPointerConstantKind {

clang/include/clang/Basic/Builtins.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,12 @@ def Trap : Builtin {
11521152
let Prototype = "void()";
11531153
}
11541154

1155+
def VerboseTrap : Builtin {
1156+
let Spellings = ["__builtin_verbose_trap"];
1157+
let Attributes = [NoThrow, NoReturn];
1158+
let Prototype = "void(char const*, char const*)";
1159+
}
1160+
11551161
def Debugtrap : Builtin {
11561162
let Spellings = ["__builtin_debugtrap"];
11571163
let Attributes = [NoThrow];

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8956,6 +8956,8 @@ def err_expected_callable_argument : Error<
89568956
"expected a callable expression as %ordinal0 argument to %1, found %2">;
89578957
def note_building_builtin_dump_struct_call : Note<
89588958
"in call to printing function with arguments '(%0)' while dumping struct">;
8959+
def err_builtin_verbose_trap_arg : Error<
8960+
"argument to __builtin_verbose_trap must %select{be a pointer to a constant string|not contain $}0">;
89598961

89608962
def err_atomic_load_store_uses_lib : Error<
89618963
"atomic %select{load|store}0 requires runtime support that is not "

clang/include/clang/CodeGen/ModuleBuilder.h

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

1616
#include "clang/AST/ASTConsumer.h"
1717
#include "clang/Basic/LLVM.h"
18+
#include "llvm/ADT/StringRef.h"
1819

1920
namespace llvm {
2021
class Constant;
@@ -27,6 +28,9 @@ namespace llvm {
2728
}
2829
}
2930

31+
// Prefix of the name of the artificial inline frame.
32+
inline constexpr llvm::StringRef ClangTrapPrefix = "__clang_trap_msg";
33+
3034
namespace clang {
3135
class CodeGenOptions;
3236
class CoverageSourceInfo;

clang/lib/AST/ExprConstant.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,7 +1885,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result,
18851885
EvalInfo &Info);
18861886
static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result);
18871887
static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
1888-
EvalInfo &Info);
1888+
EvalInfo &Info,
1889+
std::string *StringResult = nullptr);
18891890

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

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

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

1703817039
Result = Str.size();
17040+
if (StringResult)
17041+
*StringResult = Str;
1703917042
return true;
1704017043
}
1704117044

@@ -17051,12 +17054,24 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result,
1705117054
if (!Char.getInt()) {
1705217055
Result = Strlen;
1705317056
return true;
17054-
}
17057+
} else if (StringResult)
17058+
StringResult->push_back(Char.getInt().getExtValue());
1705517059
if (!HandleLValueArrayAdjustment(Info, E, String, CharTy, 1))
1705617060
return false;
1705717061
}
1705817062
}
1705917063

17064+
std::optional<std::string> Expr::tryEvaluateString(ASTContext &Ctx) const {
17065+
Expr::EvalStatus Status;
17066+
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold);
17067+
uint64_t Result;
17068+
std::string StringResult;
17069+
17070+
if (EvaluateBuiltinStrLen(this, Result, Info, &StringResult))
17071+
return StringResult;
17072+
return {};
17073+
}
17074+
1706017075
bool Expr::EvaluateCharRangeAsString(std::string &Result,
1706117076
const Expr *SizeExpression,
1706217077
const Expr *PtrExpression, ASTContext &Ctx,

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,18 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
35053505
case Builtin::BI__builtin_trap:
35063506
EmitTrapCall(Intrinsic::trap);
35073507
return RValue::get(nullptr);
3508+
case Builtin::BI__builtin_verbose_trap: {
3509+
llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();
3510+
if (getDebugInfo()) {
3511+
TrapLocation = getDebugInfo()->CreateTrapFailureMessageFor(
3512+
TrapLocation, *E->getArg(0)->tryEvaluateString(getContext()),
3513+
*E->getArg(1)->tryEvaluateString(getContext()));
3514+
}
3515+
ApplyDebugLocation ApplyTrapDI(*this, TrapLocation);
3516+
// Currently no attempt is made to prevent traps from being merged.
3517+
EmitTrapCall(Intrinsic::trap);
3518+
return RValue::get(nullptr);
3519+
}
35083520
case Builtin::BI__debugbreak:
35093521
EmitTrapCall(Intrinsic::debugtrap);
35103522
return RValue::get(nullptr);

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "clang/Basic/FileManager.h"
3333
#include "clang/Basic/SourceManager.h"
3434
#include "clang/Basic/Version.h"
35+
#include "clang/CodeGen/ModuleBuilder.h"
3536
#include "clang/Frontend/FrontendOptions.h"
3637
#include "clang/Lex/HeaderSearchOptions.h"
3738
#include "clang/Lex/ModuleMap.h"
@@ -1731,6 +1732,28 @@ llvm::DIType *CGDebugInfo::createFieldType(
17311732
offsetInBits, flags, debugType, Annotations);
17321733
}
17331734

1735+
llvm::DISubprogram *
1736+
CGDebugInfo::createInlinedTrapSubprogram(StringRef FuncName,
1737+
llvm::DIFile *FileScope) {
1738+
// We are caching the subprogram because we don't want to duplicate
1739+
// subprograms with the same message. Note that `SPFlagDefinition` prevents
1740+
// subprograms from being uniqued.
1741+
llvm::DISubprogram *&SP = InlinedTrapFuncMap[FuncName];
1742+
1743+
if (!SP) {
1744+
llvm::DISubroutineType *DIFnTy = DBuilder.createSubroutineType(nullptr);
1745+
SP = DBuilder.createFunction(
1746+
/*Scope=*/FileScope, /*Name=*/FuncName, /*LinkageName=*/StringRef(),
1747+
/*File=*/FileScope, /*LineNo=*/0, /*Ty=*/DIFnTy,
1748+
/*ScopeLine=*/0,
1749+
/*Flags=*/llvm::DINode::FlagArtificial,
1750+
/*SPFlags=*/llvm::DISubprogram::SPFlagDefinition,
1751+
/*TParams=*/nullptr, /*ThrownTypes=*/nullptr, /*Annotations=*/nullptr);
1752+
}
1753+
1754+
return SP;
1755+
}
1756+
17341757
void CGDebugInfo::CollectRecordLambdaFields(
17351758
const CXXRecordDecl *CXXDecl, SmallVectorImpl<llvm::Metadata *> &elements,
17361759
llvm::DIType *RecordTy) {
@@ -3527,6 +3550,23 @@ llvm::DIMacroFile *CGDebugInfo::CreateTempMacroFile(llvm::DIMacroFile *Parent,
35273550
return DBuilder.createTempMacroFile(Parent, Line, FName);
35283551
}
35293552

3553+
llvm::DILocation *CGDebugInfo::CreateTrapFailureMessageFor(
3554+
llvm::DebugLoc TrapLocation, StringRef Category, StringRef FailureMsg) {
3555+
// Create a debug location from `TrapLocation` that adds an artificial inline
3556+
// frame.
3557+
SmallString<64> FuncName(ClangTrapPrefix);
3558+
3559+
FuncName += "$";
3560+
FuncName += Category;
3561+
FuncName += "$";
3562+
FuncName += FailureMsg;
3563+
3564+
llvm::DISubprogram *TrapSP =
3565+
createInlinedTrapSubprogram(FuncName, TrapLocation->getFile());
3566+
return llvm::DILocation::get(CGM.getLLVMContext(), /*Line=*/0, /*Column=*/0,
3567+
/*Scope=*/TrapSP, /*InlinedAt=*/TrapLocation);
3568+
}
3569+
35303570
static QualType UnwrapTypeForDebugInfo(QualType T, const ASTContext &C) {
35313571
Qualifiers Quals;
35323572
do {

clang/lib/CodeGen/CGDebugInfo.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
#include "llvm/IR/DebugInfo.h"
3030
#include "llvm/IR/ValueHandle.h"
3131
#include "llvm/Support/Allocator.h"
32+
#include <map>
3233
#include <optional>
34+
#include <string>
3335

3436
namespace llvm {
3537
class MDNode;
@@ -346,6 +348,14 @@ class CGDebugInfo {
346348
const FieldDecl *BitFieldDecl, const llvm::DIDerivedType *BitFieldDI,
347349
llvm::ArrayRef<llvm::Metadata *> PreviousFieldsDI, const RecordDecl *RD);
348350

351+
/// A cache that maps names of artificial inlined functions to subprograms.
352+
llvm::StringMap<llvm::DISubprogram *> InlinedTrapFuncMap;
353+
354+
/// A function that returns the subprogram corresponding to the artificial
355+
/// inlined function for traps.
356+
llvm::DISubprogram *createInlinedTrapSubprogram(StringRef FuncName,
357+
llvm::DIFile *FileScope);
358+
349359
/// Helpers for collecting fields of a record.
350360
/// @{
351361
void CollectRecordLambdaFields(const CXXRecordDecl *CXXDecl,
@@ -608,6 +618,18 @@ class CGDebugInfo {
608618
return CoroutineParameterMappings;
609619
}
610620

621+
/// Create a debug location from `TrapLocation` that adds an artificial inline
622+
/// frame where the frame name is
623+
///
624+
/// * `<Prefix>:<Category>:<FailureMsg>`
625+
///
626+
/// `<Prefix>` is "__clang_trap_msg".
627+
///
628+
/// This is used to store failure reasons for traps.
629+
llvm::DILocation *CreateTrapFailureMessageFor(llvm::DebugLoc TrapLocation,
630+
StringRef Category,
631+
StringRef FailureMsg);
632+
611633
private:
612634
/// Emit call to llvm.dbg.declare for a variable declaration.
613635
/// Returns a pointer to the DILocalVariable associated with the

clang/lib/Sema/SemaChecking.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,33 @@ bool Sema::checkArgCount(CallExpr *Call, unsigned DesiredArgCount) {
183183
<< /*is non object*/ 0 << Call->getArg(1)->getSourceRange();
184184
}
185185

186+
static bool checkBuiltinVerboseTrap(CallExpr *Call, Sema &S) {
187+
bool HasError = false;
188+
189+
for (unsigned I = 0; I < Call->getNumArgs(); ++I) {
190+
Expr *Arg = Call->getArg(I);
191+
192+
if (Arg->isValueDependent())
193+
continue;
194+
195+
std::optional<std::string> ArgString = Arg->tryEvaluateString(S.Context);
196+
int DiagMsgKind = -1;
197+
// Arguments must be pointers to constant strings and cannot use '$'.
198+
if (!ArgString.has_value())
199+
DiagMsgKind = 0;
200+
else if (ArgString->find('$') != std::string::npos)
201+
DiagMsgKind = 1;
202+
203+
if (DiagMsgKind >= 0) {
204+
S.Diag(Arg->getBeginLoc(), diag::err_builtin_verbose_trap_arg)
205+
<< DiagMsgKind << Arg->getSourceRange();
206+
HasError = true;
207+
}
208+
}
209+
210+
return !HasError;
211+
}
212+
186213
static bool convertArgumentToType(Sema &S, Expr *&Value, QualType Ty) {
187214
if (Value->isTypeDependent())
188215
return false;
@@ -3351,6 +3378,11 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
33513378
case Builtin::BI__builtin_matrix_column_major_store:
33523379
return BuiltinMatrixColumnMajorStore(TheCall, TheCallResult);
33533380

3381+
case Builtin::BI__builtin_verbose_trap:
3382+
if (!checkBuiltinVerboseTrap(TheCall, *this))
3383+
return ExprError();
3384+
break;
3385+
33543386
case Builtin::BI__builtin_get_device_side_mangled_name: {
33553387
auto Check = [](CallExpr *TheCall) {
33563388
if (TheCall->getNumArgs() != 1)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %clang_cc1 -triple arm64-apple-ios -std=c++20 -emit-llvm -debug-info-kind=limited %s -o - | FileCheck %s
2+
3+
// CHECK-LABEL: define void @_Z2f0v()
4+
// CHECK: call void @llvm.trap(), !dbg ![[LOC17:.*]]
5+
6+
// CHECK: declare void @llvm.trap() #[[ATTR1:.*]]
7+
8+
// CHECK-LABEL: define void @_Z2f1v()
9+
// CHECK: call void @llvm.trap(), !dbg ![[LOC23:.*]]
10+
// CHECK: call void @llvm.trap(), !dbg ![[LOC25:.*]]
11+
12+
// CHECK-LABEL: define void @_Z2f3v()
13+
// CHECK: call void @_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv()
14+
15+
// CHECK-LABEL: define internal void @_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv
16+
// CHECK: call void @llvm.trap(), !dbg ![[LOC36:.*]]
17+
18+
// CHECK: attributes #[[ATTR1]] = { cold {{.*}}}
19+
20+
// CHECK: ![[FILESCOPE:.*]] = !DIFile(filename: "{{.*}}debug-info-verbose-trap.cpp"
21+
22+
char const constCat[] = "category2";
23+
char const constMsg[] = "hello";
24+
25+
// CHECK: ![[SUBPROG14:.*]] = distinct !DISubprogram(name: "f0", linkageName: "_Z2f0v",
26+
// CHECK: ![[LOC17]] = !DILocation(line: 0, scope: ![[SUBPROG18:.*]], inlinedAt: ![[LOC20:.*]])
27+
// CHECK: ![[SUBPROG18]] = distinct !DISubprogram(name: "__clang_trap_msg$category1$Argument_must_not_be_null", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}})
28+
// CHECK: ![[LOC20]] = !DILocation(line: [[@LINE+2]], column: 3, scope: ![[SUBPROG14]])
29+
void f0() {
30+
__builtin_verbose_trap("category1", "Argument_must_not_be_null");
31+
}
32+
33+
// CHECK: ![[SUBPROG22:.*]] = distinct !DISubprogram(name: "f1", linkageName: "_Z2f1v",
34+
// CHECK: ![[LOC23]] = !DILocation(line: 0, scope: ![[SUBPROG18]], inlinedAt: ![[LOC24:.*]])
35+
// CHECK: ![[LOC24]] = !DILocation(line: [[@LINE+5]], column: 3, scope: ![[SUBPROG22]])
36+
// CHECK: ![[LOC25]] = !DILocation(line: 0, scope: ![[SUBPROG26:.*]], inlinedAt: ![[LOC27:.*]])
37+
// CHECK: ![[SUBPROG26]] = distinct !DISubprogram(name: "__clang_trap_msg$category2$hello", scope: ![[FILESCOPE]], file: ![[FILESCOPE]], type: !{{.*}}, flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: !{{.*}})
38+
// CHECK: ![[LOC27]] = !DILocation(line: [[@LINE+3]], column: 3, scope: ![[SUBPROG22]])
39+
void f1() {
40+
__builtin_verbose_trap("category1", "Argument_must_not_be_null");
41+
__builtin_verbose_trap("category2", "hello");
42+
}
43+
44+
// CHECK: ![[SUBPROG32:.*]] = distinct !DISubprogram(name: "f2<constCat, constMsg>", linkageName: "_Z2f2IXadsoKcL_ZL8constCatEEEXadsoS0_L_ZL8constMsgEEEEvv",
45+
// CHECK: ![[LOC36]] = !DILocation(line: 0, scope: ![[SUBPROG26]], inlinedAt: ![[LOC37:.*]])
46+
// CHECK: ![[LOC37]] = !DILocation(line: [[@LINE+3]], column: 3, scope: ![[SUBPROG32]])
47+
template <const char * const category, const char * const reason>
48+
void f2() {
49+
__builtin_verbose_trap(category, reason);
50+
}
51+
52+
void f3() {
53+
f2<constCat, constMsg>();
54+
}

0 commit comments

Comments
 (0)