[Debug Info] Prevent infinite recursion when emitting debug info

for recursive classes. This is achieved by treating types created with
DebugTypeInfo::createFrowardDecl() as unconditional forward
declarations when emitting debug info instead of applying a heuristic
to determine this.

rdar://146688269
This commit is contained in:
Adrian Prantl
2025-03-11 14:04:18 -07:00
parent f5760ec3da
commit 34cda58239
12 changed files with 100 additions and 28 deletions

View File

@@ -101,6 +101,7 @@ DebugTypeInfo DebugTypeInfo::getTypeMetadata(swift::Type Ty, Size size,
DebugTypeInfo DebugTypeInfo::getForwardDecl(swift::Type Ty) {
DebugTypeInfo DbgTy(Ty.getPointer());
DbgTy.IsForwardDecl = true;
return DbgTy;
}
@@ -182,6 +183,8 @@ TypeDecl *DebugTypeInfo::getDecl() const {
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
LLVM_DUMP_METHOD void DebugTypeInfo::dump() const {
llvm::errs() << "[";
if (isForwardDecl())
llvm::errs() << "forward ";
llvm::errs() << "Alignment " << Align.getValue() << "] ";
if (auto *Type = getType())
Type->dump(llvm::errs());
@@ -190,7 +193,8 @@ LLVM_DUMP_METHOD void DebugTypeInfo::dump() const {
std::optional<CompletedDebugTypeInfo>
CompletedDebugTypeInfo::getFromTypeInfo(swift::Type Ty, const TypeInfo &Info,
IRGenModule &IGM) {
IRGenModule &IGM,
std::optional<Size::int_type> Size) {
if (!Ty || Ty->hasTypeParameter())
return {};
auto *StorageType = IGM.getStorageTypeForUnlowered(Ty);
@@ -201,6 +205,8 @@ CompletedDebugTypeInfo::getFromTypeInfo(swift::Type Ty, const TypeInfo &Info,
const FixedTypeInfo &FixTy = *cast<const FixedTypeInfo>(&Info);
Size::int_type Size = FixTy.getFixedSize().getValue() * 8;
SizeInBits = Size;
} else if (Size) {
SizeInBits = *Size * 8;
}
return CompletedDebugTypeInfo::get(

View File

@@ -48,6 +48,7 @@ protected:
bool DefaultAlignment = true;
bool IsMetadataType = false;
bool IsFixedBuffer = false;
bool IsForwardDecl = false;
public:
DebugTypeInfo() = default;
@@ -100,6 +101,7 @@ public:
bool isMetadataType() const { return IsMetadataType; }
bool hasDefaultAlignment() const { return DefaultAlignment; }
bool isFixedBuffer() const { return IsFixedBuffer; }
bool isForwardDecl() const { return IsForwardDecl; }
std::optional<uint32_t> getNumExtraInhabitants() const {
return NumExtraInhabitants;
}
@@ -127,7 +129,8 @@ public:
}
static std::optional<CompletedDebugTypeInfo>
getFromTypeInfo(swift::Type Ty, const TypeInfo &Info, IRGenModule &IGM);
getFromTypeInfo(swift::Type Ty, const TypeInfo &Info, IRGenModule &IGM,
std::optional<Size::int_type> SizeInBits = {});
Size::int_type getSizeInBits() const { return SizeInBits; }
};

View File

@@ -1326,11 +1326,19 @@ private:
}
}
// Recursive types such as `class A<B> { let a : A<A<B>> }` would produce an
// infinite chain of expansions for the type of `a`. Break these cycles by
// emitting any bound generics that still have type parameters as forward
// declarations.
if (Type->hasTypeParameter() || Type->hasPrimaryArchetype())
return createOpaqueStructWithSizedContainer(
Scope, Decl ? Decl->getNameStr() : "", File, Line, SizeInBits,
AlignInBits, Flags, MangledName, collectGenericParams(Type, true),
UnsubstitutedDITy);
// Create the substituted type.
auto *OpaqueType =
createStructType(Type, Decl, Scope, File, Line, SizeInBits, AlignInBits,
Flags, MangledName, UnsubstitutedDITy);
return OpaqueType;
return createStructType(Type, Decl, Scope, File, Line, SizeInBits,
AlignInBits, Flags, MangledName, UnsubstitutedDITy);
}
/// Create debug information for an enum with a raw type (enum E : Int {}).
@@ -1411,6 +1419,11 @@ private:
PayloadTy = ElemDecl->getParentEnum()->mapTypeIntoContext(PayloadTy);
auto &TI = IGM.getTypeInfoForUnlowered(PayloadTy);
ElemDbgTy = CompletedDebugTypeInfo::getFromTypeInfo(PayloadTy, TI, IGM);
// FIXME: This is not correct, but seems to be the only way to emit
// children for opaque-sized payload-carrying enums.
if (!ElemDbgTy)
ElemDbgTy =
CompletedDebugTypeInfo::getFromTypeInfo(PayloadTy, TI, IGM, 0);
if (!ElemDbgTy) {
// Without complete type info we can only create a forward decl.
return DBuilder.createForwardDecl(
@@ -1511,9 +1524,9 @@ private:
}
llvm::DIType *getOrCreateDesugaredType(Type Ty, DebugTypeInfo DbgTy) {
DebugTypeInfo BlandDbgTy(Ty, DbgTy.getAlignment(),
DbgTy.hasDefaultAlignment(),
DbgTy.isMetadataType(), DbgTy.isFixedBuffer());
DebugTypeInfo BlandDbgTy(
Ty, DbgTy.getAlignment(), DbgTy.hasDefaultAlignment(), false,
DbgTy.isFixedBuffer(), DbgTy.getNumExtraInhabitants());
return getOrCreateType(BlandDbgTy);
}
@@ -1525,8 +1538,8 @@ private:
/// Collect the type parameters of a bound generic type. This is needed to
/// anchor any typedefs that may appear in parameters so they can be
/// resolved in the debugger without needing to query the Swift module.
llvm::DINodeArray
collectGenericParams(NominalOrBoundGenericNominalType *BGT, bool AsForwardDeclarations = false) {
llvm::DINodeArray collectGenericParams(NominalOrBoundGenericNominalType *BGT,
bool AsForwardDeclarations = false) {
// Collect the generic args from the type and its parent.
std::vector<Type> GenericArgs;
@@ -2164,7 +2177,8 @@ private:
unsigned FwdDeclLine = 0;
if (Opts.DebugInfoLevel > IRGenDebugInfoLevel::ASTTypes) {
if (EnumTy->isSpecialized())
if (EnumTy->isSpecialized() && !EnumTy->hasTypeParameter() &&
!EnumTy->hasPrimaryArchetype())
return createSpecializedEnumType(EnumTy, Decl, MangledName,
SizeInBits, AlignInBits, Scope, File,
FwdDeclLine, Flags);
@@ -2561,7 +2575,7 @@ private:
if (DbgTy.getType()->getKind() != swift::TypeKind::TypeAlias) {
// A type with the same canonical type already exists, emit a typedef.
// This extra step is necessary to break out of loops: We don't
// canoncialize types before mangling to preserver sugared types. But
// canoncialize types before mangling to preserve sugared types. But
// some types can also have different equivalent non-canonical
// representations with no sugar involved, for example a type
// recursively that appears iniside itself. To deal with the latter we
@@ -2577,6 +2591,19 @@ private:
UID = llvm::MDString::get(IGM.getLLVMContext(), Mangled.Sugared);
}
// Fall through and create the sugared type.
} else if (auto *AliasTy =
llvm::dyn_cast<TypeAliasType>(DbgTy.getType())) {
// An alias type, but the mangler failed to produce a sugared type, just
// return the desugared type.
llvm::DIType *Desugared =
getOrCreateDesugaredType(AliasTy->getSinglyDesugaredType(), DbgTy);
StringRef Name;
if (auto *AliasDecl = AliasTy->getDecl())
Name = AliasDecl->getName().str();
if (!Name.empty())
return DBuilder.createTypedef(Desugared, Name, MainFile, 0,
updateScope(Scope, DbgTy));
return Desugared;
} else if (llvm::Metadata *CachedTy = DIRefMap.lookup(UID)) {
auto *DITy = cast<llvm::DIType>(CachedTy);
assert(sanityCheckCachedType(DbgTy, DITy));
@@ -2594,13 +2621,14 @@ private:
// If this is a forward decl, create one for this mangled name and don't
// cache it.
if (!isa<PrimaryArchetypeType>(DbgTy.getType()) &&
(DbgTy.isFixedBuffer() || !completeType(DbgTy))) {
!isa<TypeAliasType>(DbgTy.getType()) &&
(DbgTy.isForwardDecl() || DbgTy.isFixedBuffer() ||
!completeType(DbgTy))) {
// In LTO type uniquing is performed based on the UID. Forward
// declarations may not have a unique ID to avoid a forward declaration
// winning over a full definition.
auto *FwdDecl = DBuilder.createReplaceableCompositeType(
llvm::dwarf::DW_TAG_structure_type, MangledName, Scope, 0, 0,
llvm::dwarf::DW_LANG_Swift);
FwdDeclTypes.emplace_back(
std::piecewise_construct, std::make_tuple(MangledName),

View File

@@ -10,13 +10,13 @@ public let s = S<Int>(t: 0)
// CHECK-SAME: templateParams: ![[PARAMS:[0-9]+]]
// CHECK: ![[PARAMS]] = !{![[INTPARAM:[0-9]+]]}
// CHECK: ![[INTPARAM]] = !DITemplateTypeParameter(type: ![[INT:[0-9]+]])
// CHECK: ![[INT]] = !DICompositeType(tag: DW_TAG_structure_type, {{.*}}identifier: "$sSiD"
// CHECK: ![[INT]] = !DICompositeType(tag: DW_TAG_structure_type, {{.*}}"$sSiD"
// DWARF-DAG: !DICompositeType(tag: DW_TAG_structure_type, {{.*}}templateParams: ![[PARAMS:[0-9]+]]{{.*}}identifier: "$s18BoundGenericStruct1SVySiGD"{{.*}}specification:
// DWARF-DAG: ![[PARAMS]] = !{![[INTPARAM:[0-9]+]]}
// DWARF-DAG: ![[INTPARAM]] = !DITemplateTypeParameter(type: ![[INT:[0-9]+]])
// DWARF-DAG: ![[INT]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Int", {{.*}}identifier: "$sSiD"
// DWARF-DAG: ![[INT]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Int", {{.*}}"$sSiD"
// DWARF-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "S", {{.*}}identifier: "$s18BoundGenericStruct1SVyxGD")
// DWARF-DAG: !DIDerivedType(tag: DW_TAG_member, name: "t"

View File

@@ -27,8 +27,8 @@ func main() -> Int64 {
// CHECK-DAG: ![[BAZPTR]] = !DIDerivedType(tag: DW_TAG_pointer_type,{{.*}} baseType: ![[BAZT:[0-9]+]]
// CHECK-DAG: ![[BAZT]] = !DISubroutineType(types: ![[BAZARGS:.*]])
// CHECK-DAG: ![[BAZARGS]] = !{![[INT:.*]], ![[FLOAT:.*]]}
// CHECK-DAG: ![[INT]] = {{.*}}identifier: "$ss5Int64VD"
// CHECK-DAG: ![[FLOAT]] = {{.*}}identifier: "$sSfD"
// CHECK-DAG: ![[INT]] = {{.*}}"$ss5Int64VD"
// CHECK-DAG: ![[FLOAT]] = {{.*}}"$sSfD"
var baz_fnptr = baz
baz_fnptr(2.89)

View File

@@ -7,7 +7,7 @@ import local_type_originally_defined_in_other
public func definedInOtherModule() {
let s = Sheep()
// CHECK-DAG: DICompositeType(tag: DW_TAG_structure_type, name: "Sheep"{{.*}}identifier: "$s4Barn5SheepCD
// CHECK-DAG: DICompositeType(tag: DW_TAG_structure_type, {{.*}}"$s4Barn5SheepCD
}
public func localTypeAliasTest(horse: Horse) {
// The local type mangling for 'A' mentions 'Horse', which must
@@ -18,7 +18,7 @@ public func localTypeAliasTest(horse: Horse) {
let info = UnsafeMutablePointer<A>.allocate(capacity: 1)
_ = info
// CHECK-DAG: DIDerivedType(tag: DW_TAG_typedef, name: "$s32local_type_originally_defined_in0A13TypeAliasTest5horsey4Barn5HorseV_tF1AL_aD"
// CHECK-DAG: name: "$s32local_type_originally_defined_in0A13TypeAliasTest5horsey4Barn5HorseV_tF1AL_aD"
}
@@ -31,7 +31,7 @@ public func localTypeTest(horse: Horse) {
let info = UnsafeMutablePointer<LocalStruct>.allocate(capacity: 1)
_ = info
// CHECK-DAG: DICompositeType(tag: DW_TAG_structure_type, {{.*}}identifier: "$s32local_type_originally_defined_in0A8TypeTest5horsey4Barn5HorseV_tF11LocalStructL_VD"
// CHECK-DAG: DICompositeType(tag: DW_TAG_structure_type, {{.*}}: "$s32local_type_originally_defined_in0A8TypeTest5horsey4Barn5HorseV_tF11LocalStructL_VD"
}
public func localTypeAliasTest() -> Horse {
@@ -40,7 +40,7 @@ public func localTypeAliasTest() -> Horse {
let info = UnsafeMutablePointer<B>.allocate(capacity: 1)
_ = info
return Horse()
// CHECK-DAG: DIDerivedType(tag: DW_TAG_typedef, name: "$s32local_type_originally_defined_in0A13TypeAliasTest4Barn5HorseVyF1BL_aD"
// CHECK-DAG: name: "$s32local_type_originally_defined_in0A13TypeAliasTest4Barn5HorseVyF1BL_aD"
}
public func localTypeAliasTestGeneric<T: Cow>(cow: T) {

View File

@@ -1,3 +1,4 @@
// RUN: %target-swift-frontend -parse-stdlib %s -emit-ir -g -o - | %FileCheck %s
// CHECK: !DIDerivedType(tag: DW_TAG_typedef, name: "$sBbD",
// CHECK: !DIDerivedType(tag: DW_TAG_typedef, name: "BridgeObject", {{.*}}baseType: ![[BT:[0-9]+]]
// CHECK: ![[BT]] = {{.*}}"$sBbD"
var bo : Builtin.BridgeObject

View File

@@ -0,0 +1,23 @@
// RUN: %target-swift-frontend %s -emit-ir -g -o - | %FileCheck %s
// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "A",{{.*}}elements: ![[A_ELTS:[0-9]+]], runtimeLang: DW_LANG_Swift, identifier: "$s15recursive_actor1ACyxGD")
// CHECK: ![[A_ELTS]] = !{![[M_CHILD:[0-9]+]]}
// CHECK: ![[M_CHILD]] = !DIDerivedType(tag: DW_TAG_member, name: "children", {{.*}}baseType: ![[CONT_AB:[0-9]+]]
// CHECK: ![[CONT_AB]] = !DICompositeType(tag: DW_TAG_structure_type, {{.*}}elements: ![[C_ELTS:[0-9]+]]
// CHECK: ![[C_ELTS]] = !{![[M_AB:[0-9]+]]}
// CHECK: ![[M_AB]] = !DIDerivedType(tag: DW_TAG_member, {{.*}}baseType: ![[AB:[0-9]+]]
// CHECK: ![[AB:[0-9]+]] = !DICompositeType(tag: DW_TAG_structure_type, name: "$sSay15recursive_actor1ACyACyxGGGD"
public actor A<Parent>: MutableA where Parent: MutableA {
public let children: [A<A>] = []
}
public protocol MutableA: Actor {
associatedtype Child where Child: MutableA
var children: [Child] { get }
}
public actor B: MutableA {
public private(set) var children: [A<B>] = []
}

View File

@@ -2,7 +2,7 @@
func markUsed<T>(_ t: T) {}
// CHECK-DAG: ![[INTTYPE:.*]] = !DICompositeType(tag: DW_TAG_structure_type, name: "Int", {{.*}})
// CHECK-DAG: ![[INTTYPE:.*]] = !DICompositeType(tag: DW_TAG_structure_type, {{.*}}"$sSiD"
public class DWARF {
// CHECK-DAG: ![[BASE:.*]] = !DICompositeType({{.*}}identifier: "$ss6UInt32VD"

View File

@@ -4,8 +4,8 @@
// and we can preserve that MyClass<LocalAlias, Bool> = MyClass<Bool, Bool>
// we cannot preserve that LocalAlias = Bool.
// CHECK: !DIDerivedType(tag: DW_TAG_typedef, name: "$s1a7MyClassCyAA10LocalAliasaSbGD",{{.*}}baseType: ![[BOOLBOOLTY:[0-9]+]]
// CHECK: ![[BOOLBOOLTY]] = !DICompositeType(tag: DW_TAG_structure_type, name: "MyClass", {{.*}}identifier: "$s1a7MyClassCyS2bGD"
// CHECK: !DIDerivedType(tag: DW_TAG_typedef, name: "$s1a10ClassAliasaD"{{.*}}baseType: ![[LOCAL_BOOLTY:[0-9]+]]
// CHECK: ![[LOCAL_BOOLTY]] = !DICompositeType(tag: DW_TAG_structure_type, name: "MyClass", {{.*}}identifier: "$s1a7MyClassCyAA10LocalAliasaSbGD"
// FIXME: !DIDerivedType(tag: DW_TAG_typedef, name: "$s1a10LocalAliasaD", {{.*}}baseType: ![[BASETY:[0-9]+]]
// FIXME: ![[BASETY]]{{.*}}$sSbD

View File

@@ -24,5 +24,5 @@ func concreteBA(_: Builtin.FixedArray<4, Int>) {}
// CHECK-DAG: ![[COUNT_PARAM]] = !DITemplateTypeParameter(type: ![[COUNT_TYPE:.*]])
// CHECK-DAG: ![[COUNT_TYPE]] = !DICompositeType({{.*}}name: "$s$1_D"
// CHECK-DAG: ![[ELEMENT_PARAM]] = !DITemplateTypeParameter(type: ![[ELEMENT_TYPE:.*]])
// CHECK-DAG: ![[ELEMENT_TYPE]] = !DICompositeType({{.*}}identifier: "$sSiD"
// CHECK-DAG: ![[ELEMENT_TYPE]] = !DICompositeType({{.*}}"$sSiD"
func concreteV(_: Slab<2, Int>) {}

View File

@@ -0,0 +1,11 @@
// RUN: %target-swift-frontend -primary-file %s -emit-ir -gdwarf-types -o - | %FileCheck %s
// CHECK: !DICompositeType(tag: DW_TAG_variant_part, {{.*}}elements: ![[ELTS:[0-9]+]])
// CHECK: ![[ELTS]] = !{![[ML:[0-9]+]], ![[MR:[0-9]+]]}
// CHECK: ![[ML]] = !DIDerivedType(tag: DW_TAG_member, name: "left",
// CHECK: ![[MR]] = !DIDerivedType(tag: DW_TAG_member, name: "right",
enum Either<Left, Right> {
case left(Left)
case right(Right)
}
let either = Either<Int, Double>.left(1234)