Skip to content

Commit 31e4465

Browse files
authored
[DebugInfo] Merge fragments from SIL with fragments created in IRGen (#66448)
SIL variables can be split by SILSROA into separate allocations, each having op_fragment expressions in debug_value (VarInfo.DIExpr). These allocations can be further split by IRGen (multiple values in Storage argument). These "nested" fragments refer to the same DI variable, so it is important to merge them for the LLVM IR DI expression. The compiler used to ignore fragment expressions from SIL when IRGen fragments were also present. This led to incorrect DI info generation, and for some cases even triggered assertions in LLVM X86 CodeGen: DwarfExpression.cpp:679: void llvm::DwarfExpression::addFragmentOffset(const llvm::DIExpression *): Assertion `FragmentOffset >= OffsetInBits && "overlapping or duplicate fragments"' failed. The patch fixes issue #64642. The LIT test is a reduced reproducer from that issue.
1 parent fff1c5d commit 31e4465

File tree

3 files changed

+173
-30
lines changed

3 files changed

+173
-30
lines changed

lib/IRGen/IRGenDebugInfo.cpp

+68-30
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,11 @@ class IRGenDebugInfoImpl : public IRGenDebugInfo {
215215

216216
/// Return false if we fail to create the right DW_OP_LLVM_fragment operand.
217217
bool handleFragmentDIExpr(const SILDIExprOperand &CurDIExprOp,
218-
SmallVectorImpl<uint64_t> &Operands);
218+
llvm::DIExpression::FragmentInfo &Fragment);
219219
/// Return false if we fail to create the desired !DIExpression.
220220
bool buildDebugInfoExpression(const SILDebugVariable &VarInfo,
221-
SmallVectorImpl<uint64_t> &Operands);
221+
SmallVectorImpl<uint64_t> &Operands,
222+
llvm::DIExpression::FragmentInfo &Fragment);
222223

223224
/// Emit a dbg.declare at the current insertion point in Builder.
224225
void emitVariableDeclaration(IRBuilder &Builder,
@@ -2516,7 +2517,8 @@ void IRGenDebugInfoImpl::emitOutlinedFunction(IRBuilder &Builder,
25162517
}
25172518

25182519
bool IRGenDebugInfoImpl::handleFragmentDIExpr(
2519-
const SILDIExprOperand &CurDIExprOp, SmallVectorImpl<uint64_t> &Operands) {
2520+
const SILDIExprOperand &CurDIExprOp,
2521+
llvm::DIExpression::FragmentInfo &Fragment) {
25202522
assert(CurDIExprOp.getOperator() == SILDIExprOperator::Fragment);
25212523
// Expecting a VarDecl that points to a field in an struct
25222524
auto DIExprArgs = CurDIExprOp.args();
@@ -2548,23 +2550,31 @@ bool IRGenDebugInfoImpl::handleFragmentDIExpr(
25482550
uint64_t OffsetInBits =
25492551
Offset->getUniqueInteger().getLimitedValue() * SizeOfByte;
25502552

2551-
// Translate to LLVM dbg intrinsic operands
2552-
Operands.push_back(llvm::dwarf::DW_OP_LLVM_fragment);
2553-
Operands.push_back(OffsetInBits);
2554-
Operands.push_back(SizeInBits);
2553+
// Translate to DW_OP_LLVM_fragment operands
2554+
Fragment = {SizeInBits, OffsetInBits};
25552555

25562556
return true;
25572557
}
25582558

25592559
bool IRGenDebugInfoImpl::buildDebugInfoExpression(
2560-
const SILDebugVariable &VarInfo, SmallVectorImpl<uint64_t> &Operands) {
2560+
const SILDebugVariable &VarInfo, SmallVectorImpl<uint64_t> &Operands,
2561+
llvm::DIExpression::FragmentInfo &Fragment) {
25612562
assert(VarInfo.DIExpr && "SIL debug info expression not found");
25622563

2564+
#ifndef NDEBUG
2565+
bool HasFragment = VarInfo.DIExpr.hasFragment();
2566+
#endif
2567+
25632568
const auto &DIExpr = VarInfo.DIExpr;
25642569
for (const SILDIExprOperand &ExprOperand : DIExpr.operands()) {
25652570
switch (ExprOperand.getOperator()) {
25662571
case SILDIExprOperator::Fragment:
2567-
if (!handleFragmentDIExpr(ExprOperand, Operands))
2572+
assert(HasFragment && "Fragment must be the last part of a DIExpr");
2573+
#ifndef NDEBUG
2574+
// Trigger the assert above if we have more than one fragment expression.
2575+
HasFragment = false;
2576+
#endif
2577+
if (!handleFragmentDIExpr(ExprOperand, Fragment))
25682578
return false;
25692579
break;
25702580
case SILDIExprOperator::Dereference:
@@ -2671,24 +2681,54 @@ void IRGenDebugInfoImpl::emitVariableDeclaration(
26712681
LocalVarCache.insert({Key, llvm::TrackingMDNodeRef(Var)});
26722682
}
26732683

2674-
auto appendDIExpression =
2675-
[&VarInfo, this](llvm::DIExpression *DIExpr) -> llvm::DIExpression * {
2676-
if (VarInfo.DIExpr) {
2677-
llvm::SmallVector<uint64_t, 2> Operands;
2678-
if (!buildDebugInfoExpression(VarInfo, Operands))
2679-
return nullptr;
2680-
if (Operands.size())
2681-
return llvm::DIExpression::append(DIExpr, Operands);
2682-
}
2683-
return DIExpr;
2684-
};
2685-
26862684
// Running variables for the current/previous piece.
26872685
bool IsPiece = Storage.size() > 1;
26882686
uint64_t SizeOfByte = CI.getTargetInfo().getCharWidth();
26892687
unsigned AlignInBits = SizeOfByte;
26902688
unsigned OffsetInBits = 0;
26912689
unsigned SizeInBits = 0;
2690+
llvm::DIExpression::FragmentInfo Fragment = {0, 0};
2691+
2692+
auto appendDIExpression =
2693+
[&VarInfo, this](llvm::DIExpression *DIExpr,
2694+
llvm::DIExpression::FragmentInfo PieceFragment)
2695+
-> llvm::DIExpression * {
2696+
if (!VarInfo.DIExpr) {
2697+
if (!PieceFragment.SizeInBits)
2698+
return DIExpr;
2699+
2700+
return llvm::DIExpression::createFragmentExpression(
2701+
DIExpr, PieceFragment.OffsetInBits, PieceFragment.SizeInBits)
2702+
.getValueOr(nullptr);
2703+
}
2704+
2705+
llvm::SmallVector<uint64_t, 2> Operands;
2706+
llvm::DIExpression::FragmentInfo VarFragment = {0, 0};
2707+
if (!buildDebugInfoExpression(VarInfo, Operands, VarFragment))
2708+
return nullptr;
2709+
2710+
if (!Operands.empty())
2711+
DIExpr = llvm::DIExpression::append(DIExpr, Operands);
2712+
2713+
// Add the fragment of the SIL variable.
2714+
if (VarFragment.SizeInBits)
2715+
DIExpr = llvm::DIExpression::createFragmentExpression(
2716+
DIExpr, VarFragment.OffsetInBits, VarFragment.SizeInBits)
2717+
.getValueOr(nullptr);
2718+
2719+
if (!DIExpr)
2720+
return nullptr;
2721+
2722+
// When the fragment of the SIL variable is further split into other
2723+
// fragments (PieceFragment), merge them into one DW_OP_LLVM_Fragment
2724+
// expression.
2725+
if (PieceFragment.SizeInBits)
2726+
return llvm::DIExpression::createFragmentExpression(
2727+
DIExpr, PieceFragment.OffsetInBits, PieceFragment.SizeInBits)
2728+
.getValueOr(nullptr);
2729+
2730+
return DIExpr;
2731+
};
26922732

26932733
for (llvm::Value *Piece : Storage) {
26942734
SmallVector<uint64_t, 3> Operands;
@@ -2717,16 +2757,12 @@ void IRGenDebugInfoImpl::emitVariableDeclaration(
27172757
}
27182758
#endif
27192759

2720-
// Add the piece DWARF expression.
2721-
Operands.push_back(llvm::dwarf::DW_OP_LLVM_fragment);
2722-
Operands.push_back(OffsetInBits);
2723-
Operands.push_back(SizeInBits);
2760+
// Add the piece DW_OP_LLVM_fragment operands
2761+
Fragment.OffsetInBits = OffsetInBits;
2762+
Fragment.SizeInBits = SizeInBits;
27242763
}
27252764
llvm::DIExpression *DIExpr = DBuilder.createExpression(Operands);
2726-
// DW_OP_LLVM_fragment must be the last part of an DIExpr
2727-
// so we can't append more if IsPiece is true.
2728-
if (!IsPiece)
2729-
DIExpr = appendDIExpression(DIExpr);
2765+
DIExpr = appendDIExpression(DIExpr, Fragment);
27302766
if (DIExpr)
27312767
emitDbgIntrinsic(
27322768
Builder, Piece, Var, DIExpr, DInstLine, DInstLoc.column, Scope, DS,
@@ -2736,7 +2772,9 @@ void IRGenDebugInfoImpl::emitVariableDeclaration(
27362772

27372773
// Emit locationless intrinsic for variables that were optimized away.
27382774
if (Storage.empty()) {
2739-
if (auto *DIExpr = appendDIExpression(DBuilder.createExpression()))
2775+
llvm::DIExpression::FragmentInfo NoFragment = {0, 0};
2776+
if (auto *DIExpr =
2777+
appendDIExpression(DBuilder.createExpression(), NoFragment))
27402778
emitDbgIntrinsic(Builder, llvm::ConstantInt::get(IGM.Int64Ty, 0), Var,
27412779
DIExpr, DInstLine, DInstLoc.column, Scope, DS,
27422780
Indirection == CoroDirectValue ||

test/IRGen/debug_fragment_merge.sil

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %target-swift-frontend -disable-availability-checking -primary-file %s -emit-ir -disable-llvm-optzns -O -g | %FileCheck %s
2+
3+
// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 192, 64){{.*}} !dbg ![[LOC1:[0-9]+]]
4+
// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64){{.*}} !dbg ![[LOC1]]
5+
// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64){{.*}} !dbg ![[LOC1]]
6+
// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64){{.*}} !dbg ![[LOC1]]
7+
8+
sil_stage canonical
9+
10+
import Builtin
11+
import Swift
12+
import SwiftShims
13+
14+
protocol External {
15+
func use(str: String)
16+
func decode<T>(_: T.Type) -> T
17+
}
18+
19+
struct Data {
20+
@_hasStorage var a: String { get set }
21+
@_hasStorage var b: String { get set }
22+
init(a: String, b: String)
23+
}
24+
25+
sil_scope 9 { loc "debug_fragment_merge.swift":14:6 parent @$s20debug_fragment_merge4test4cond8externalySi_AA8External_ptYaF : $@convention(thin) (Int, @in_guaranteed any External) -> () }
26+
sil_scope 10 { loc "debug_fragment_merge.swift":16:7 parent 9 }
27+
sil_scope 11 { loc "debug_fragment_merge.swift":17:3 parent 10 }
28+
sil_scope 12 { loc "debug_fragment_merge.swift":19:3 parent 11 }
29+
sil_scope 13 { loc "debug_fragment_merge.swift":19:12 parent 12 }
30+
sil_scope 14 { loc "debug_fragment_merge.swift":21:3 parent 11 }
31+
sil_scope 15 { loc "debug_fragment_merge.swift":21:12 parent 14 }
32+
33+
sil @$side_effect : $@convention(thin) (@owned String) -> ()
34+
sil @$side_effect_data : $@convention(thin) (@owned Data) -> ()
35+
sil @$cond : $@convention(thin) () -> (Builtin.Int1)
36+
37+
// test(cond:external:), loc "debug_fragment_merge.swift":14:6, scope 9
38+
sil hidden @$s20debug_fragment_merge4test4cond8externalySi_AA8External_ptYaF : $@convention(thin) (Int, @in_guaranteed any External) -> () {
39+
[%1: read v**, write v**, copy v**, destroy v**]
40+
[global: read,write,copy,destroy,allocate,deinit_barrier]
41+
bb0(%0 : $Int, %1 : $*any External):
42+
br bb1 // cond_br %cond, bb1, bb2
43+
44+
bb1: // Preds: bb0
45+
%10 = open_existential_addr immutable_access %1 : $*any External to $*@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self, loc "debug_fragment_merge.swift":19:28, scope 12 // users: %14, %14, %13
46+
%11 = alloc_stack $Data, loc * "debug_fragment_merge.swift":19:28, scope 13 // users: %17, %15, %21, %14
47+
%12 = metatype $@thick Data.Type, loc "debug_fragment_merge.swift":19:35, scope 13 // user: %14
48+
%13 = witness_method $@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self, #External.decode : <Self where Self : External><T> (Self) -> (T.Type) -> T, %10 : $*@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self : $@convention(witness_method: External) <τ_0_0 where τ_0_0 : External><τ_1_0> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @out τ_1_0, loc "debug_fragment_merge.swift":19:28, scope 13 // type-defs: %10; user: %14
49+
%14 = apply %13<@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self, Data>(%11, %12, %10) : $@convention(witness_method: External) <τ_0_0 where τ_0_0 : External><τ_1_0> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @out τ_1_0, loc "debug_fragment_merge.swift":19:28, scope 13 // type-defs: %10
50+
%15 = struct_element_addr %11 : $*Data, #Data.a, loc "debug_fragment_merge.swift":19:28, scope 13 // user: %16
51+
%16 = load %15 : $*String, loc "debug_fragment_merge.swift":19:28, scope 13 // users: %19, %22
52+
%17 = struct_element_addr %11 : $*Data, #Data.b, loc "debug_fragment_merge.swift":19:28, scope 13 // user: %18
53+
%18 = load %17 : $*String, loc "debug_fragment_merge.swift":19:28, scope 13 // users: %22, %20
54+
debug_value %16 : $String, let, (name "data", loc "debug_fragment_merge.swift":16:7, scope 10), type $*Data, expr op_fragment:#Data.a, loc "debug_fragment_merge.swift":19:17, scope 12 // id: %19
55+
debug_value %18 : $String, let, (name "data", loc "debug_fragment_merge.swift":16:7, scope 10), type $*Data, expr op_fragment:#Data.b, loc "debug_fragment_merge.swift":19:17, scope 12 // id: %20
56+
dealloc_stack %11 : $*Data, loc "debug_fragment_merge.swift":19:44, scope 13 // id: %21
57+
br bb3(%18 : $String, %16 : $String), loc * "debug_fragment_merge.swift":19:44, scope 13 // id: %22
58+
59+
bb3(%36 : $String, %37 : $String): // Preds: bb2 bb1
60+
%side_effect_ref = function_ref @$side_effect : $@convention(thin) (@owned String) -> ()
61+
apply %side_effect_ref(%36) : $@convention(thin) (@owned String) -> ()
62+
apply %side_effect_ref(%37) : $@convention(thin) (@owned String) -> ()
63+
%44 = tuple ()
64+
return %44 : $()
65+
}

test/IRGen/debug_fragment_merge.swift

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %target-swift-frontend -disable-availability-checking -primary-file %s -emit-sil -O -g | %FileCheck %s --check-prefix CHECK-SIL
2+
// RUN: %target-swift-frontend -disable-availability-checking -primary-file %s -emit-ir -disable-llvm-optzns -O -g | %FileCheck %s
3+
4+
protocol External {
5+
func use(str: String);
6+
func decode<T>(_: T.Type) -> T
7+
}
8+
9+
struct Data {
10+
var a: String
11+
var b: String
12+
}
13+
14+
func test(cond: Int, external: External) async {
15+
// CHECK-DAG: ![[VAR:[0-9]+]] = !DILocalVariable(name: "data", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: [[# @LINE + 1]], type: !{{[0-9]+}})
16+
let data: Data
17+
switch cond {
18+
// CHECK-DAG: ![[LOC1:[0-9]+]] = !DILocation(line: [[# @LINE + 1]], column: 12, scope: !{{.*}})
19+
case 42: data = external.decode(Data.self)
20+
// CHECK-DAG: ![[LOC2:[0-9]+]] = !DILocation(line: [[# @LINE + 1]], column: 12, scope: !{{.*}})
21+
default: data = external.decode(Data.self)
22+
}
23+
external.use(str: data.a)
24+
external.use(str: data.b)
25+
}
26+
27+
// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.a
28+
// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.b
29+
// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.a
30+
// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.b
31+
32+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 192, 64){{.*}} !dbg ![[LOC1]]
33+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64){{.*}} !dbg ![[LOC1]]
34+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64){{.*}} !dbg ![[LOC1]]
35+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64){{.*}} !dbg ![[LOC1]]
36+
//
37+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 192, 64){{.*}} !dbg ![[LOC2]]
38+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64){{.*}} !dbg ![[LOC2]]
39+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64){{.*}} !dbg ![[LOC2]]
40+
// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64){{.*}} !dbg ![[LOC2]]

0 commit comments

Comments
 (0)