diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -184,6 +184,10 @@ the compilation of the foreign language sources (e.g. Swift). - The ``__has_attribute``, ``__has_c_attribute`` and ``__has_cpp_attribute`` preprocessor operators now return 1 also for attributes defined by plugins. +- Introduced a new function attribute ``__attribute__((debug_trampoline))`` + which is intended as a hint to debuggers that they should not stop at the annotated + function, but instead step through it when stepping in, and continuing directly to + its caller when stepping out. Improvements to Clang's diagnostics ----------------------------------- diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -771,6 +771,13 @@ let SimpleHandler = 1; } +def DebugTrampoline: InheritableAttr { + let Spellings = [Clang<"debug_trampoline">]; + let Subjects = SubjectList<[Function, ObjCMethod]>; + let Documentation = [DebugTrampolineDocs]; + let SimpleHandler = 1; +} + def XRayInstrument : InheritableAttr { let Spellings = [Clang<"xray_always_instrument">, Clang<"xray_never_instrument">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6903,6 +6903,66 @@ }]; } +def DebugTrampolineDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``debug_trampoline`` attribute is intended as a hint for debuggers that this +function itself is not interesting, but it calls a function that might be. So, when +stepping in arrives at a function with this attribute, debuggers should transparently +step-in through it into the functions called by the annotated function (but not by +subsequent calls made by those functions), stopping at the first one its normal rules +for whether to stop says to stop at - or stepping out again if none qualify. Also, when +stepping out arrives at a function with this attribute, the debugger should continue +stepping out to its caller. + +For example: + +.. code-block:: c + + int bar(void) { + return 42; + } + + __attribute__((debug_trampoline)) + int foo(void) { + return bar(); + } + + int caller(void) { + return foo(); + } + +Stepping into ``foo`` should step directly into ``bar`` instead, and stepping out of ``bar`` +should stop in ``caller``. + +Functions with the ``debug_trampoline`` attribute can be chained together: + +.. code-block:: c + + int baz(void) { + return 42; + } + + __attribute__((debug_trampoline)) + int bar(void) { + return baz(); + } + + __attribute__((debug_trampoline)) + int foo(void) { + return bar(); + } + + int caller(void) { + return foo(); + } + +In this example, stepping into ``foo`` should step directly into ``baz``, and stepping out of +``baz`` should stop in ``caller``. + }]; +} + + def ReadOnlyPlacementDocs : Documentation { let Category = DocCatType; let Content = [{This attribute is attached to a structure, class or union declaration. diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -69,6 +69,12 @@ return D->hasAttr() ? D->getMaxAlignment() : 0; } +static bool usesDebugTrampoline(const Decl *D) { + if (!D) + return false; + return D->hasAttr(); +} + CGDebugInfo::CGDebugInfo(CodeGenModule &CGM) : CGM(CGM), DebugKind(CGM.getCodeGenOpts().getDebugInfo()), DebugTypeExtRefs(CGM.getCodeGenOpts().DebugTypeExtRefs), @@ -1987,6 +1993,8 @@ SPFlags |= llvm::DISubprogram::SPFlagLocalToUnit; if (CGM.getLangOpts().Optimize) SPFlags |= llvm::DISubprogram::SPFlagOptimized; + if (usesDebugTrampoline(Method)) + SPFlags |= llvm::DISubprogram::SPFlagIsDebugTrampoline; // In this debug mode, emit type info for a class when its constructor type // info is emitted. @@ -3928,6 +3936,8 @@ if (Stub) { Flags |= getCallSiteRelatedAttrs(); SPFlags |= llvm::DISubprogram::SPFlagDefinition; + if (usesDebugTrampoline(FD)) + SPFlags |= llvm::DISubprogram::SPFlagIsDebugTrampoline; return DBuilder.createFunction( DContext, Name, LinkageName, Unit, Line, getOrCreateFunctionType(GD.getDecl(), FnType, Unit), 0, Flags, SPFlags, @@ -4077,6 +4087,8 @@ if (It == TypeCache.end()) return nullptr; auto *InterfaceType = cast(It->second); + if (usesDebugTrampoline(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsDebugTrampoline; llvm::DISubprogram *FD = DBuilder.createFunction( InterfaceType, getObjCMethodName(OMD), StringRef(), InterfaceType->getFile(), LineNo, FnType, LineNo, Flags, SPFlags); @@ -4244,6 +4256,8 @@ SPFlags |= llvm::DISubprogram::SPFlagLocalToUnit; if (CGM.getLangOpts().Optimize) SPFlags |= llvm::DISubprogram::SPFlagOptimized; + if (usesDebugTrampoline(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsDebugTrampoline; llvm::DINode::DIFlags FlagsForDef = Flags | getCallSiteRelatedAttrs(); llvm::DISubprogram::DISPFlags SPFlagsForDef = @@ -4330,6 +4344,9 @@ llvm::DINodeArray Annotations = CollectBTFDeclTagAnnotations(D); llvm::DISubroutineType *STy = getOrCreateFunctionType(D, FnType, Unit); + if (usesDebugTrampoline(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsDebugTrampoline; + llvm::DISubprogram *SP = DBuilder.createFunction( FDContext, Name, LinkageName, Unit, LineNo, STy, ScopeLine, Flags, SPFlags, TParamsArray.get(), nullptr, nullptr, Annotations); diff --git a/clang/test/CodeGen/attr-debug-trampoline-method.cpp b/clang/test/CodeGen/attr-debug-trampoline-method.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-debug-trampoline-method.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -debug-info-kind=limited -emit-llvm -o - %s | FileCheck %s + +void bar(void) {} + +struct A { +[[clang::debug_trampoline()]] +void foo(void) { + bar(); +} +}; + +int main() { + A().foo(); +} + +// CHECK: DISubprogram(name: "foo"{{.*}} DISPFlagIsDebugTrampoline diff --git a/clang/test/CodeGen/attr-debug-trampoline-objc.m b/clang/test/CodeGen/attr-debug-trampoline-objc.m new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-debug-trampoline-objc.m @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -debug-info-kind=limited -emit-llvm -o - %s | FileCheck %s + + +@interface ObjCClass +- (void)foo __attribute__((debug_trampoline)); +@end + +@implementation ObjCClass +- (void)foo {} +@end + + +// CHECK: DISubprogram(name: "-[ObjCClass foo]"{{.*}} DISPFlagIsDebugTrampoline diff --git a/clang/test/CodeGen/attr-debug-trampoline.c b/clang/test/CodeGen/attr-debug-trampoline.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-debug-trampoline.c @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -debug-info-kind=limited -emit-llvm -o - %s | FileCheck %s + +void bar(void) {} + +__attribute__((debug_trampoline)) +void foo(void) { + bar(); +} + +// CHECK: DISubprogram(name: "foo"{{.*}} DISPFlagIsDebugTrampoline diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -186,6 +186,7 @@ // CHECK-NEXT: TargetClones (SubjectMatchRule_function) // CHECK-NEXT: TargetVersion (SubjectMatchRule_function) // CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member) +// CHECK-NEXT: TransparentStepping (SubjectMatchRule_function) // CHECK-NEXT: TrivialABI (SubjectMatchRule_record) // CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local) // CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function) diff --git a/clang/test/Sema/attr-debug-trampoline.c b/clang/test/Sema/attr-debug-trampoline.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-debug-trampoline.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +__attribute__((debug_trampoline)) +void correct(void) {} + +__attribute__((debug_trampoline(1))) // expected-error {{'debug_trampoline' attribute takes no arguments}} +void wrong_arg(void) {} diff --git a/clang/test/SemaCXX/attr-debug-trampoline-method.cpp b/clang/test/SemaCXX/attr-debug-trampoline-method.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-debug-trampoline-method.cpp @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + + +struct S { +[[clang::debug_trampoline]] +void correct(void) {} + +[[clang::debug_trampoline(1)]] // expected-error {{'debug_trampoline' attribute takes no arguments}} +void one_arg(void) {} +}; + diff --git a/clang/test/SemaObjC/attr-debug-trampoline-objc.m b/clang/test/SemaObjC/attr-debug-trampoline-objc.m new file mode 100644 --- /dev/null +++ b/clang/test/SemaObjC/attr-debug-trampoline-objc.m @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + + + +@interface ObjCClass +- (void)correct __attribute__((debug_trampoline)); +- (void)one_arg __attribute__((debug_trampoline(1))); // expected-error {{'debug_trampoline' attribute takes no arguments}} +@end + diff --git a/llvm/include/llvm/IR/DebugInfoFlags.def b/llvm/include/llvm/IR/DebugInfoFlags.def --- a/llvm/include/llvm/IR/DebugInfoFlags.def +++ b/llvm/include/llvm/IR/DebugInfoFlags.def @@ -91,11 +91,12 @@ // for defaulted functions HANDLE_DISP_FLAG((1u << 9), Deleted) HANDLE_DISP_FLAG((1u << 11), ObjCDirect) +HANDLE_DISP_FLAG((1u << 12), IsDebugTrampoline) #ifdef DISP_FLAG_LARGEST_NEEDED // Intended to be used with ADT/BitmaskEnum.h. // NOTE: Always must be equal to largest flag, check this when adding new flags. -HANDLE_DISP_FLAG((1 << 11), Largest) +HANDLE_DISP_FLAG((1 << 12), Largest) #undef DISP_FLAG_LARGEST_NEEDED #endif diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h --- a/llvm/include/llvm/IR/DebugInfoMetadata.h +++ b/llvm/include/llvm/IR/DebugInfoMetadata.h @@ -1760,6 +1760,9 @@ bool isElemental() const { return getSPFlags() & SPFlagElemental; } bool isRecursive() const { return getSPFlags() & SPFlagRecursive; } bool isObjCDirect() const { return getSPFlags() & SPFlagObjCDirect; } + bool isDebugTrampoline() const { + return getSPFlags() & SPFlagIsDebugTrampoline; + } /// Check if this is deleted member function. /// diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp --- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp @@ -1345,6 +1345,9 @@ if (!SP->getTargetFuncName().empty()) addString(SPDie, dwarf::DW_AT_trampoline, SP->getTargetFuncName()); + if (SP->isDebugTrampoline()) + addFlag(SPDie, dwarf::DW_AT_trampoline); + if (DD->getDwarfVersion() >= 5 && SP->isDeleted()) addFlag(SPDie, dwarf::DW_AT_deleted); } diff --git a/llvm/test/Assembler/disubprogram-debug-trampoline.ll b/llvm/test/Assembler/disubprogram-debug-trampoline.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Assembler/disubprogram-debug-trampoline.ll @@ -0,0 +1,39 @@ +; This test verifies that the DISPFlagIsDebugTrampoline attribute in a DISubprogram +; is assembled/disassembled correctly. +; +; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s +; +; CHECK: !DISubprogram(name: "baz",{{.*}} DISPFlagIsDebugTrampoline +; +; ModuleID = 't.c' +source_filename = "t.c" +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-macosx13.0.0" + +; Function Attrs: noinline nounwind optnone ssp uwtable(sync) +define void @baz() #0 !dbg !10 { +entry: + ret void, !dbg !14 +} + +attributes #0 = { noinline nounwind optnone ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } + +!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6} +!llvm.dbg.cu = !{!7} +!llvm.ident = !{!9} + +!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 2]} +!1 = !{i32 7, !"Dwarf Version", i32 4} +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = !{i32 1, !"wchar_size", i32 4} +!4 = !{i32 8, !"PIC Level", i32 2} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{i32 7, !"frame-pointer", i32 1} +!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !8, producer: "clang version 17.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!8 = !DIFile(filename: "t.c", directory: "/") +!9 = !{!"clang version 17.0.0"} +!10 = distinct !DISubprogram(name: "baz", scope: !8, file: !8, line: 3, type: !11, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagIsDebugTrampoline, unit: !7, retainedNodes: !13) +!11 = !DISubroutineType(types: !12) +!12 = !{null} +!13 = !{} +!14 = !DILocation(line: 4, column: 1, scope: !10) diff --git a/llvm/test/DebugInfo/AArch64/disubprogram-debug-trampoline.ll b/llvm/test/DebugInfo/AArch64/disubprogram-debug-trampoline.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/AArch64/disubprogram-debug-trampoline.ll @@ -0,0 +1,42 @@ +; This test verifies that the proper DWARF debug info is emitted +; for a trampoline function with no target. +; +; RUN: llc -filetype=obj %s -o - | llvm-dwarfdump - | FileCheck %s +; +; CHECK: DW_TAG_subprogram +; CHECK: DW_AT_name ("baz") +; CHECK: DW_AT_trampoline (true) +; +; ModuleID = 't.c' +source_filename = "t.c" +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-macosx13.0.0" + +; Function Attrs: noinline nounwind optnone ssp uwtable(sync) +define void @baz() #0 !dbg !10 { +entry: + ret void, !dbg !14 +} + +attributes #0 = { noinline nounwind optnone ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } + +!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6} +!llvm.dbg.cu = !{!7} +!llvm.ident = !{!9} + +!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 13, i32 2]} +!1 = !{i32 7, !"Dwarf Version", i32 4} +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = !{i32 1, !"wchar_size", i32 4} +!4 = !{i32 8, !"PIC Level", i32 2} +!5 = !{i32 7, !"uwtable", i32 1} +!6 = !{i32 7, !"frame-pointer", i32 1} +!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !8, producer: "clang version 17.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!8 = !DIFile(filename: "t.c", directory: "/") +!9 = !{!"clang version 17.0.0"} +!10 = distinct !DISubprogram(name: "baz", scope: !8, file: !8, line: 3, type: !11, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagIsDebugTrampoline, unit: !7, retainedNodes: !13) +!11 = !DISubroutineType(types: !12) +!12 = !{null} +!13 = !{} +!14 = !DILocation(line: 4, column: 1, scope: !10) + diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp --- a/llvm/unittests/IR/MetadataTest.cpp +++ b/llvm/unittests/IR/MetadataTest.cpp @@ -2521,6 +2521,7 @@ assert(!IsLocalToUnit && IsDefinition && !IsOptimized && "bools and SPFlags have to match"); SPFlags |= DISubprogram::SPFlagDefinition; + SPFlags |= DISubprogram::SPFlagIsDebugTrampoline; auto *N = DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, ScopeLine, @@ -2604,12 +2605,17 @@ Flags, SPFlags ^ DISubprogram::SPFlagDefinition, Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes, Annotations, TargetFuncName)); - EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, - Type, ScopeLine + 1, ContainingType, - VirtualIndex, ThisAdjustment, Flags, SPFlags, - Unit, TemplateParams, Declaration, - RetainedNodes, ThrownTypes, Annotations, - TargetFuncName)); + EXPECT_NE(N, DISubprogram::get( + Context, Scope, Name, LinkageName, File, Line, Type, + ScopeLine, ContainingType, VirtualIndex, ThisAdjustment, + Flags, SPFlags ^ DISubprogram::SPFlagIsDebugTrampoline, + Unit, TemplateParams, Declaration, RetainedNodes, + ThrownTypes, Annotations, TargetFuncName)); + EXPECT_NE(N, DISubprogram::get( + Context, Scope, Name, LinkageName, File, Line, Type, + ScopeLine + 1, ContainingType, VirtualIndex, ThisAdjustment, + Flags, SPFlags, Unit, TemplateParams, Declaration, + RetainedNodes, ThrownTypes, Annotations, TargetFuncName)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, ScopeLine, getCompositeType(), VirtualIndex, ThisAdjustment, Flags, SPFlags,