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 @@ -770,6 +770,12 @@ let SimpleHandler = 1; } +def TransparentStepping: InheritableAttr { + let Spellings = [Clang<"transparent_stepping">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [TransparentSteppingDocs]; +} + 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 @@ -6900,6 +6900,66 @@ }]; } +def TransparentSteppingDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``transparent_stepping`` 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__((transparent_stepping)) + 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 ``transparent_stepping`` attribute can be chained together: + +.. code-block:: c + + int baz(void) { + return 42; + } + + __attribute__((transparent_stepping)) + int bar(void) { + return baz(); + } + + __attribute__((transparent_stepping)) + 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 @@ -68,6 +68,12 @@ return D->hasAttr() ? D->getMaxAlignment() : 0; } +static bool getIsTransparentStepping(const Decl *D) { + if (!D) + return false; + return D->hasAttr(); +} + CGDebugInfo::CGDebugInfo(CodeGenModule &CGM) : CGM(CGM), DebugKind(CGM.getCodeGenOpts().getDebugInfo()), DebugTypeExtRefs(CGM.getCodeGenOpts().DebugTypeExtRefs), @@ -1910,6 +1916,8 @@ SPFlags |= llvm::DISubprogram::SPFlagLocalToUnit; if (CGM.getLangOpts().Optimize) SPFlags |= llvm::DISubprogram::SPFlagOptimized; + if (getIsTransparentStepping(Method)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; // In this debug mode, emit type info for a class when its constructor type // info is emitted. @@ -3849,6 +3857,8 @@ if (Stub) { Flags |= getCallSiteRelatedAttrs(); SPFlags |= llvm::DISubprogram::SPFlagDefinition; + if (getIsTransparentStepping(FD)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; return DBuilder.createFunction( DContext, Name, LinkageName, Unit, Line, getOrCreateFunctionType(GD.getDecl(), FnType, Unit), 0, Flags, SPFlags, @@ -3998,6 +4008,8 @@ if (It == TypeCache.end()) return nullptr; auto *InterfaceType = cast(It->second); + if (getIsTransparentStepping(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; llvm::DISubprogram *FD = DBuilder.createFunction( InterfaceType, getObjCMethodName(OMD), StringRef(), InterfaceType->getFile(), LineNo, FnType, LineNo, Flags, SPFlags); @@ -4165,6 +4177,8 @@ SPFlags |= llvm::DISubprogram::SPFlagLocalToUnit; if (CGM.getLangOpts().Optimize) SPFlags |= llvm::DISubprogram::SPFlagOptimized; + if (getIsTransparentStepping(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; llvm::DINode::DIFlags FlagsForDef = Flags | getCallSiteRelatedAttrs(); llvm::DISubprogram::DISPFlags SPFlagsForDef = @@ -4251,6 +4265,9 @@ llvm::DINodeArray Annotations = CollectBTFDeclTagAnnotations(D); llvm::DISubroutineType *STy = getOrCreateFunctionType(D, FnType, Unit); + if (getIsTransparentStepping(D)) + SPFlags |= llvm::DISubprogram::SPFlagIsTransparentStepping; + llvm::DISubprogram *SP = DBuilder.createFunction( FDContext, Name, LinkageName, Unit, LineNo, STy, ScopeLine, Flags, SPFlags, TParamsArray.get(), nullptr, nullptr, Annotations); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6770,6 +6770,12 @@ D->addAttr(::new (S.Context) SwiftAsyncNameAttr(S.Context, AL, Name)); } +static void handleTransparentStepping(Sema &S, Decl *D, + const ParsedAttr &AL) { + D->addAttr(::new (S.Context) + TransparentSteppingAttr(S.Context, AL)); +} + static void handleSwiftNewType(Sema &S, Decl *D, const ParsedAttr &AL) { // Make sure that there is an identifier as the annotation's single argument. if (!AL.checkExactlyNumArgs(S, 1)) @@ -9081,6 +9087,9 @@ case ParsedAttr::AT_NoDebug: handleNoDebugAttr(S, D, AL); break; + case ParsedAttr::AT_TransparentStepping: + handleTransparentStepping(S, D, AL); + break; case ParsedAttr::AT_CmseNSEntry: handleCmseNSEntryAttr(S, D, AL); break; diff --git a/clang/test/CodeGen/attr-transparent-stepping-method.cpp b/clang/test/CodeGen/attr-transparent-stepping-method.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-transparent-stepping-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::transparent_stepping()]] +void foo(void) { + bar(); +} +}; + +int main() { + A().foo(); +} + +// CHECK: DISubprogram(name: "foo"{{.*}} DISPFlagIsTransparentStepping diff --git a/clang/test/CodeGen/attr-transparent-stepping.c b/clang/test/CodeGen/attr-transparent-stepping.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-transparent-stepping.c @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -debug-info-kind=limited -emit-llvm -o - %s | FileCheck %s + +void bar(void) {} + +__attribute__((transparent_stepping)) +void foo(void) { + bar(); +} + +// CHECK: DISubprogram(name: "foo"{{.*}} DISPFlagIsTransparentStepping 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 @@ -185,6 +185,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-transparent-stepping.c b/clang/test/Sema/attr-transparent-stepping.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-transparent-stepping.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +__attribute__((transparent_stepping)) +void correct(void) {} + +__attribute__((transparent_stepping(1))) // expected-error {{'transparent_stepping' attribute takes no arguments}} +void wrong_arg(void) {} diff --git a/clang/test/SemaCXX/attr-transparent-stepping-method.cpp b/clang/test/SemaCXX/attr-transparent-stepping-method.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-transparent-stepping-method.cpp @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + + +struct S { +[[clang::transparent_stepping]] +void correct(void) {} + +[[clang::transparent_stepping(1)]] // expected-error {{'transparent_stepping' attribute takes no arguments}} +void one_arg(void) {} +}; + 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), IsTransparentStepping) #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 getIsTransparentStepping() const { + return getSPFlags() & SPFlagIsTransparentStepping; + } /// 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->getIsTransparentStepping()) + 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-transparent-stepping.ll b/llvm/test/Assembler/disubprogram-transparent-stepping.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Assembler/disubprogram-transparent-stepping.ll @@ -0,0 +1,39 @@ +; This test verifies that the DISPFlagIsTransparentStepping attribute in a DISubprogram +; is assembled/disassembled correctly. +; +; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s +; +; CHECK: !DISubprogram(name: "baz",{{.*}} DISPFlagIsTransparentStepping +; +; 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 | DISPFlagIsTransparentStepping, 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-transparent-stepping.ll b/llvm/test/DebugInfo/AArch64/disubprogram-transparent-stepping.ll new file mode 100644 --- /dev/null +++ b/llvm/test/DebugInfo/AArch64/disubprogram-transparent-stepping.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 | DISPFlagIsTransparentStepping, 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::SPFlagIsTransparentStepping; 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::SPFlagIsTransparentStepping, + 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,