Skip to content

[DebugInfo] Emit debug info for witness tables #81661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: release/6.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -7319,7 +7319,10 @@ class GenericTypeParamType : public SubstitutableType,

/// Get the name of the generic type parameter.
Identifier getName() const;


/// Get the canonical <tau>_n_n name;
Identifier getCanonicalName() const;

/// The depth of this generic type parameter, i.e., the number of outer
/// levels of generic parameter lists that enclose this type parameter.
///
Expand Down
4 changes: 3 additions & 1 deletion lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2133,8 +2133,10 @@ Identifier GenericTypeParamType::getName() const {
if (!isCanonical())
return Name;

// Otherwise, we're canonical. Produce an anonymous '<tau>_n_n' name.
return getCanonicalName();
}

Identifier GenericTypeParamType::getCanonicalName() const {
// getASTContext() doesn't actually mutate an already-canonical type.
auto &C = const_cast<GenericTypeParamType*>(this)->getASTContext();
auto &names = C.CanonicalGenericTypeParamTypeNames;
Expand Down
73 changes: 54 additions & 19 deletions lib/IRGen/IRGenDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,16 @@ class IRGenDebugInfoImpl : public IRGenDebugInfo {
DebugTypeInfo DebugType,
bool IsLocalToUnit,
std::optional<SILLocation> Loc);

void emitArtificialVariable(IRGenFunction &IGF, llvm::Value *Metadata,
StringRef Name, StringRef Identifier);

void emitTypeMetadata(IRGenFunction &IGF, llvm::Value *Metadata,
unsigned Depth, unsigned Index, StringRef Name);
GenericTypeParamType *Type);

void emitWitnessTable(IRGenFunction &IGF, llvm::Value *Metadata,
StringRef Name, ProtocolDecl *protocol);

void emitPackCountParameter(IRGenFunction &IGF, llvm::Value *Metadata,
SILDebugVariable VarInfo);

Expand Down Expand Up @@ -3904,9 +3912,10 @@ void IRGenDebugInfoImpl::emitGlobalVariableDeclaration(
Var->addDebugInfo(GV);
}

void IRGenDebugInfoImpl::emitTypeMetadata(IRGenFunction &IGF,
llvm::Value *Metadata, unsigned Depth,
unsigned Index, StringRef Name) {
void IRGenDebugInfoImpl::emitArtificialVariable(IRGenFunction &IGF,
llvm::Value *Metadata,
StringRef Name,
StringRef Identifier) {
if (Opts.DebugInfoLevel <= IRGenDebugInfoLevel::LineTables)
return;

Expand All @@ -3915,23 +3924,44 @@ void IRGenDebugInfoImpl::emitTypeMetadata(IRGenFunction &IGF,
if (!DS || DS->getInlinedFunction()->isTransparent())
return;

llvm::SmallString<8> Buf;
static const char *Tau = SWIFT_UTF8("\u03C4");
llvm::raw_svector_ostream OS(Buf);
OS << '$' << Tau << '_' << Depth << '_' << Index;
uint64_t PtrWidthInBits = CI.getTargetInfo().getPointerWidth(clang::LangAS::Default);
uint64_t PtrWidthInBits =
CI.getTargetInfo().getPointerWidth(clang::LangAS::Default);
assert(PtrWidthInBits % 8 == 0);
auto DbgTy = DebugTypeInfo::getTypeMetadata(
getMetadataType(Name)->getDeclaredInterfaceType().getPointer(),
Size(PtrWidthInBits / 8),
Alignment(CI.getTargetInfo().getPointerAlign(clang::LangAS::Default)));
emitVariableDeclaration(IGF.Builder, Metadata, DbgTy, IGF.getDebugScope(),
{}, {OS.str().str(), 0, false},
// swift.type is already a pointer type,
// having a shadow copy doesn't add another
// layer of indirection.
IGF.isAsync() ? CoroDirectValue : DirectValue,
ArtificialValue);
emitVariableDeclaration(
IGF.Builder, Metadata, DbgTy, IGF.getDebugScope(), {},
{Identifier, 0, false}, // swift.type is already a pointer type,
// having a shadow copy doesn't add another
// layer of indirection.
IGF.isAsync() ? CoroDirectValue : DirectValue, ArtificialValue);
}

void IRGenDebugInfoImpl::emitTypeMetadata(IRGenFunction &IGF,
llvm::Value *Metadata,
GenericTypeParamType *Type) {
llvm::SmallString<8> Buf;
llvm::raw_svector_ostream OS(Buf);
OS << "$" << Type->getCanonicalName().str();
auto Name = Type->getName().str();

emitArtificialVariable(IGF, Metadata, Name, OS.str());
}

void IRGenDebugInfoImpl::emitWitnessTable(IRGenFunction &IGF,
llvm::Value *Metadata, StringRef Name,
ProtocolDecl *protocol) {
llvm::SmallString<32> Buf;
llvm::raw_svector_ostream OS(Buf);
DebugTypeInfo DbgTy(protocol->getDeclaredType());
auto MangledName = getMangledName(DbgTy).Canonical;
OS << "$WT" << Name << "$$" << MangledName;;
// Make sure this ID lives long enough.
auto Id = IGF.getSwiftModule()->getASTContext().getIdentifier(OS.str());

emitArtificialVariable(IGF, Metadata, Name, Id.str());
}

void IRGenDebugInfoImpl::emitPackCountParameter(IRGenFunction &IGF,
Expand Down Expand Up @@ -4070,10 +4100,15 @@ void IRGenDebugInfo::emitGlobalVariableDeclaration(
}

void IRGenDebugInfo::emitTypeMetadata(IRGenFunction &IGF, llvm::Value *Metadata,
unsigned Depth, unsigned Index,
StringRef Name) {
GenericTypeParamType *Type) {
static_cast<IRGenDebugInfoImpl *>(this)->emitTypeMetadata(IGF, Metadata,
Depth, Index, Name);
Type);
}

void IRGenDebugInfo::emitWitnessTable(IRGenFunction &IGF, llvm::Value *Metadata,
StringRef Name, ProtocolDecl *protocol) {
static_cast<IRGenDebugInfoImpl *>(this)->emitWitnessTable(IGF, Metadata, Name,
protocol);
}

void IRGenDebugInfo::emitPackCountParameter(IRGenFunction &IGF,
Expand Down
6 changes: 5 additions & 1 deletion lib/IRGen/IRGenDebugInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ class IRGenDebugInfo {

/// Emit debug metadata for type metadata (for generic types). So meta.
void emitTypeMetadata(IRGenFunction &IGF, llvm::Value *Metadata,
unsigned Depth, unsigned Index, StringRef Name);
GenericTypeParamType *Type);

/// Emit debug metadata for a (protocol) witness table.
void emitWitnessTable(IRGenFunction &IGF, llvm::Value *Metadata,
StringRef Name, ProtocolDecl *protocol);

/// Emit debug info for the IR function parameter holding the size of one or
/// more parameter / type packs.
Expand Down
36 changes: 35 additions & 1 deletion lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1163,8 +1163,42 @@ class IRGenSILFunction :
emitPackCountDebugVariable(Shape);
}
});

if (auto *BGT = llvm::dyn_cast<BoundGenericType>(Ty)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also nest non-generic nominals inside other generic nominals, eg

struct G<T> {
  struct Inner {}
 }

Eg, G<Int>.Inner is not a BoundGenericType.

Just use getAnyNominal() instead

auto Decl = BGT->getDecl();
auto GE = Decl->getGenericEnvironment();
auto Requirements = BGT->getDecl()
->getGenericEnvironment()
->getGenericSignature()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace ->getGenericEnvironment()->getGenericSignature() with ->getGenericSignature()

.getRequirements();
for (auto Requirement : Requirements) {
if (Requirement.getKind() == RequirementKind::Conformance) {
auto ProtocolDecl = Requirement.getProtocolDecl();
auto ConformingType = Requirement.getFirstType();
Type Archetype;
if (auto GTPT = llvm::dyn_cast<GenericTypeParamType>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These conditionals are unnecessary. mapTypeIntoContext() takes a Type. Also, we use ConformingType->getAs<...> and not llvm::dyn_cast<>(ConformingType.getPointer())

ConformingType.getPointer()))
Archetype = GE->mapTypeIntoContext(GTPT);
else if (auto DMT = llvm::dyn_cast<DependentMemberType>(
ConformingType.getPointer()))
Archetype = GE->mapTypeIntoContext(DMT);
Copy link
Contributor

@slavapestov slavapestov May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this code is trying to do, but it's not correct. The types Requirement are written with the generic signature of the BoundGenericType. The GE you have here on the other hand is a generic environment for the generic signature of the current function. So the mapTypeIntoContext() call will just return an ErrorType.

Also, you're not looking at the generic arguments of the BGT here. Presumably it should do that?


if (Lowering::TypeConverter::protocolRequiresWitnessTable(
ProtocolDecl) &&
tryGetLocalTypeData(
Archetype->getCanonicalType(),
LocalTypeDataKind::forAbstractProtocolWitnessTable(
ProtocolDecl))) {
auto Conformance =
ProtocolConformanceRef::forAbstract(Archetype, ProtocolDecl);

emitWitnessTableRef(*this, Archetype->getCanonicalType(),
Conformance);
}
}
}
}
}

/// Emit debug info for a function argument or a local variable.
template <typename StorageType>
void emitDebugVariableDeclaration(
Expand Down
59 changes: 47 additions & 12 deletions lib/IRGen/LocalTypeData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,26 +408,59 @@ static void maybeEmitDebugInfoForLocalTypeData(IRGenFunction &IGF,
// functions that were inlined into transparent functions. Correct would be to
// check which instruction requests the type metadata and see whether its
// inlined function is transparent.
auto * DS = IGF.getDebugScope();
auto *DS = IGF.getDebugScope();
if (DS && DS->getInlinedFunction() &&
DS->getInlinedFunction()->isTransparent())
return;

// Only for formal type metadata.
if (key.Kind != LocalTypeDataKind::forFormalTypeMetadata())
// For formal type metadata and witness tables.
ProtocolDecl *proto = nullptr;

if (key.Kind.isAbstractProtocolConformance())
proto = key.Kind.getAbstractProtocolConformance();
else if (key.Kind.isConcreteProtocolConformance())
proto = key.Kind.getConcreteProtocolConformance()->getProtocol();
else if (key.Kind != LocalTypeDataKind::forFormalTypeMetadata())
return;

// Only for archetypes, and not for opened/opaque archetypes.
auto type = dyn_cast<ArchetypeType>(key.Type);
if (!type)
return;
if (!type->isRoot())
if (!type->isRoot() && !proto)
return;
if (!isa<PrimaryArchetypeType>(type) && !isa<PackArchetypeType>(type))
return;

auto *typeParam = type->getInterfaceType()->castTo<GenericTypeParamType>();
auto name = typeParam->getName().str();
auto interfaceType = type->getInterfaceType();
llvm::SmallString<16> name;
llvm::SmallString<16> displayName;
if (auto DMT =
llvm::dyn_cast<DependentMemberType>(interfaceType.getPointer())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about dyn_cast

std::function<void(DependentMemberType *)> visitDependentMembers =
[&](DependentMemberType *member) {
if (member == nullptr)
return;
if (auto *parent =
llvm::dyn_cast<DependentMemberType>(member->getBase())) {
visitDependentMembers(parent);
name.append("$");
displayName.append(".");
}
name.append(member->getName().str());
displayName.append(member->getName().str());
};
name.append(DMT->getRootGenericParam()->getCanonicalName().str());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just print the generic param as the base case of the recursion? (where member->getBase() is not another DMT)

name.append("$");
displayName.append(DMT->getRootGenericParam()->getName().str());
displayName.append(".");
visitDependentMembers(DMT);
} else if (auto GTPT = llvm::dyn_cast<GenericTypeParamType>(
interfaceType.getPointer())) {
name = GTPT->getCanonicalName().str();
displayName = GTPT->getName().str();
} else {
return;
}

llvm::Value *data = value.getMetadata();

Expand All @@ -438,7 +471,7 @@ static void maybeEmitDebugInfoForLocalTypeData(IRGenFunction &IGF,
// though; see the comment in IRGenFunctionSIL::emitShadowCopyIfNeeded().
if (!IGF.IGM.IRGen.Opts.shouldOptimize() && !IGF.isAsync()) {
auto alloca =
IGF.createAlloca(data->getType(), IGF.IGM.getPointerAlignment(), name);
IGF.createAlloca(data->getType(), IGF.IGM.getPointerAlignment(), displayName);
IGF.Builder.CreateStore(data, alloca);
data = alloca.getAddress();
}
Expand All @@ -447,10 +480,12 @@ static void maybeEmitDebugInfoForLocalTypeData(IRGenFunction &IGF,
if (!IGF.IGM.DebugInfo)
return;

IGF.IGM.DebugInfo->emitTypeMetadata(IGF, data,
typeParam->getDepth(),
typeParam->getIndex(),
name);
if (proto) {
IGF.IGM.DebugInfo->emitWitnessTable(IGF, data, name, proto);
} else {
auto *typeParam = type->getInterfaceType()->castTo<GenericTypeParamType>();
IGF.IGM.DebugInfo->emitTypeMetadata(IGF, data, typeParam);
}
}

void
Expand Down
7 changes: 7 additions & 0 deletions test/DebugInfo/move_function_dbginfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ public func copyableVarArgTest(_ k: inout Klass) {
// DWARF-NEXT: DW_AT_artificial (true)
//
// DWARF: DW_TAG_variable
// DWARF: DW_AT_location
// DWARF: DW_AT_name ("$WT\317\204_0_0$$$s3out1P_pD")
// DWARF: DW_AT_type (
// DWARF: DW_AT_artificial (true)
//
// DWARF: DW_TAG_variable
// DWARF-NEXT: DW_AT_location (0x{{[a-z0-9]+}}:
// DWARF-NEXT: [0x{{[a-z0-9]+}}, 0x{{[a-z0-9]+}}):
// DWARF-NEXT: DW_AT_name ("k")
Expand Down Expand Up @@ -668,3 +674,4 @@ public func addressOnlyVarArgTestCCFlowReinitInBlockTest<T : P>(_ k: inout (any
// CHECK-DAG: ![[K_ADDRESSONLY_VAR_CCFLOW_REINIT_OUT_BLOCK_METADATA]] = !DILocalVariable(name: "k",
// CHECK-DAG: ![[K_ADDRESSONLY_VAR_CCFLOW_REINIT_IN_BLOCK_METADATA]] = !DILocalVariable(name: "k",
// CHECK-DAG: ![[K_COPYABLE_LET_CCFLOW_METADATA]] = !DILocalVariable(name: "k",

73 changes: 73 additions & 0 deletions test/DebugInfo/witness_table.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// RUN: %target-swift-frontend %s -emit-ir -g -o -
import StdlibUnittest
protocol P1 {}
protocol P2 {}
protocol P3 {}
protocol P4 {}
protocol P5 {}

struct I: P1, P2, P4 {}
struct I2: P3 {}
struct I3: P5 {}

struct S<T, U: P3> where T: P1, T: P2 {
let t: T
let u: U

func foo() {
}
//CHECK: ![[foo:[0-9]+]] = distinct !DISubprogram(name: "foo", linkageName: "$s13witness_table1SV3fooyyF",
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P1_pmD", scope: ![[foo]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P2_pmD", scope: ![[foo]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_0", scope: ![[foo]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_U_$s13witness_table2P3_pmD", scope: ![[foo]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_1", scope: ![[foo]], {{.*}}, flags: DIFlagArtificial)

func bar<V: P4>(v: V) {
}
//CHECK: ![[bar:[0-9]+]] = distinct !DISubprogram(name: "bar", linkageName: "$s13witness_table1SV3bar1vyqd___tAA2P4Rd__lF",
//CHECK: !DILocalVariable(name: "$\CF\84_1_0", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_V_$s13witness_table2P4_pmD", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P1_pmD", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P2_pmD", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_0", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_U_$s13witness_table2P3_pmD", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_1", scope: ![[bar]], {{.*}}, flags: DIFlagArtificial)
}

extension S where T: P5 {
func baz() {
}

//CHECK: ![[baz:[0-9]+]] = distinct !DISubprogram(name: "baz", linkageName: "$s13witness_table1SVA2A2P5RzrlE3bazyyF",
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P5_pmD", scope: ![[baz]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P1_pmD", scope: ![[baz]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table2P2_pmD", scope: ![[baz]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_0", scope: ![[baz]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_U_$s13witness_table2P3_pmD", scope: ![[baz]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_1", scope: ![[baz]], {{.*}}, flags: DIFlagArtificial)
}

S(t: I(), u: I2())

func freeFunc<T1: P1, T2>(t1: T1, t2: T2) where T2: P3, T2: P4 {
}
//CHECK: ![[freeFunc:[0-9]+]] = distinct !DISubprogram(name: "freeFunc", linkageName: "$s13witness_table8freeFunc2t12t2yx_q_tAA2P1RzAA2P3R_AA2P4R_r0_lF",
//CHECK: !DILocalVariable(name: "$\CF\84_0_0", scope: ![[freeFunc]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$\CF\84_0_1", scope: ![[freeFunc]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T1_$s13witness_table2P1_pmD", scope: ![[freeFunc]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T2_$s13witness_table2P3_pmD", scope: ![[freeFunc]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T2_$s13witness_table2P4_pmD", scope: ![[freeFunc]], {{.*}}, flags: DIFlagArtificial)

protocol A {
associatedtype Element
}

func withAssociatedType<T: A>(_: T) where T.Element: A {
}

//CHECK: ![[withAssociatedType:[0-9]+]] = distinct !DISubprogram(name: "withAssociatedType", linkageName: "$s13witness_table18withAssociatedTypeyyxAA1ARzAaC7ElementRpzlF"
//CHECK: !DILocalVariable(name: "$\CF\84_0_0", scope: ![[withAssociatedType]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T_$s13witness_table1A_pmD", scope: ![[withAssociatedType]], {{.*}}, flags: DIFlagArtificial)
//CHECK: !DILocalVariable(name: "$WT_T.Element_$s13witness_table1A_pmD", scope: ![[withAssociatedType]], {{.*}}, flags: DIFlagArtificial)

2 changes: 2 additions & 0 deletions test/IRGen/associated_type_witness.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ struct Fulfilled<T : P & Q> : Assocked {
// CHECK-LABEL: define internal swiftcc ptr @"$s23associated_type_witness9FulfilledVyxGAA8AssockedAA5AssocAaEP_AA1PPWT"(ptr %"Fulfilled<T>.Assoc", ptr %"Fulfilled<T>", ptr %"Fulfilled<T>.Assocked")
// CHECK: [[T1:%.*]] = getelementptr inbounds ptr, ptr %"Fulfilled<T>", i64 3
// CHECK-NEXT: [[T2:%.*]] = load ptr, ptr [[T1]], align 8, !invariant.load
// CHECK-NEXT: store ptr %T.P, ptr %T
// CHECK-NEXT: ret ptr [[T2]]

// Associated type witness table access function for Fulfilled.Assoc : Q.
// CHECK-LABEL: define internal swiftcc ptr @"$s23associated_type_witness9FulfilledVyxGAA8AssockedAA5AssocAaEP_AA1QPWT"(ptr %"Fulfilled<T>.Assoc", ptr %"Fulfilled<T>", ptr %"Fulfilled<T>.Assocked")
// CHECK: [[T1:%.*]] = getelementptr inbounds ptr, ptr %"Fulfilled<T>", i64 4
// CHECK-NEXT: [[T2:%.*]] = load ptr, ptr [[T1]], align 8, !invariant.load
// CHECK-NEXT: store ptr %T.Q, ptr %T
// CHECK-NEXT: ret ptr [[T2]]

struct Pair<T, U> : P, Q {}
Expand Down
Loading