Skip to content

Commit bffda8a

Browse files
committed
Debugging: Extend the live ranges of local values at -Onone.
For many local values we can avoid a shadow alloca by directly describing them with a dbg.value. This also enables precise liveness so variables don't show up in the debugger before they are initialized. Unfortunately this also means that values will disappear when they are no longer needed. This patch inserts an empty inline assembler expression depending on the llvm::Value that is being described in the blocks dominated by it. This uses less stack space than full shadow copies *and* allows us to track the liveness of the variable completely. It may cause values to be spilled onto the stack, though. <rdar://problem/26627376>
1 parent 104a72f commit bffda8a

File tree

6 files changed

+244
-18
lines changed

6 files changed

+244
-18
lines changed

lib/IRGen/IRGenDebugInfo.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -969,9 +969,7 @@ void IRGenDebugInfo::emitDbgIntrinsic(llvm::BasicBlock *BB,
969969
// the variable that is live throughout the function. With SIL
970970
// optimizations this is not guaranteed and a variable can end up in
971971
// two allocas (for example, one function inlined twice).
972-
if (!Opts.Optimize &&
973-
(isa<llvm::AllocaInst>(Storage) ||
974-
isa<llvm::UndefValue>(Storage)))
972+
if (isa<llvm::AllocaInst>(Storage))
975973
DBuilder.insertDeclare(Storage, Var, Expr, DL, BB);
976974
else
977975
DBuilder.insertDbgValueIntrinsic(Storage, 0, Var, Expr, DL, BB);

lib/IRGen/IRGenSIL.cpp

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "llvm/ADT/TinyPtrVector.h"
2727
#include "llvm/Support/Debug.h"
2828
#include "clang/AST/ASTContext.h"
29+
#include "clang/Basic/TargetInfo.h"
2930
#include "swift/Basic/Fallthrough.h"
3031
#include "swift/Basic/Range.h"
3132
#include "swift/Basic/STLExtras.h"
@@ -343,7 +344,10 @@ class IRGenSILFunction :
343344
StackSlotKey;
344345
/// Keeps track of the mapping of source variables to -O0 shadow copy allocas.
345346
llvm::SmallDenseMap<StackSlotKey, Address, 8> ShadowStackSlots;
347+
llvm::SmallDenseMap<llvm::Type *, Address, 8> DebugScratchpads;
346348
llvm::SmallDenseMap<Decl *, SmallString<4>, 8> AnonymousVariables;
349+
llvm::SmallVector<std::pair<DominancePoint, llvm::Instruction *>, 8>
350+
ValueVariables;
347351
unsigned NumAnonVars = 0;
348352
unsigned NumCondFails = 0;
349353

@@ -577,7 +581,44 @@ class IRGenSILFunction :
577581
return Name;
578582
}
579583

580-
/// At -O0, emit a shadow copy of an Address in an alloca, so the
584+
/// At -Onone, forcibly keep all LLVM values that are tracked by
585+
/// debug variables alive by inserting an empty inline assembler
586+
/// expression depending on the value in the blocks dominated by the
587+
/// value.
588+
void emitDebugVariableRangeExtension(const SILBasicBlock *CurBB) {
589+
if (IGM.IRGen.Opts.Optimize)
590+
return;
591+
for (auto &Variable : ValueVariables) {
592+
auto VarDominancePoint = Variable.first;
593+
llvm::Value *Storage = Variable.second;
594+
if (getActiveDominancePoint() == VarDominancePoint ||
595+
isActiveDominancePointDominatedBy(VarDominancePoint)) {
596+
llvm::Type *ArgTys;
597+
auto *Ty = Storage->getType()->getScalarType();
598+
// Pointers and Floats are expected to fit into a register.
599+
if (Ty->isPointerTy() || Ty->isFloatingPointTy())
600+
ArgTys = { Storage->getType() };
601+
else {
602+
// The storage is guaranteed to be no larger than the register width.
603+
// Extend the storage so it would fit into a register.
604+
llvm::Type *IntTy;
605+
switch (IGM.getClangASTContext().getTargetInfo().getRegisterWidth()) {
606+
case 64: IntTy = IGM.Int64Ty; break;
607+
case 32: IntTy = IGM.Int32Ty; break;
608+
default: llvm_unreachable("unsupported register width");
609+
}
610+
ArgTys = { IntTy };
611+
Storage = Builder.CreateZExtOrBitCast(Storage, IntTy);
612+
}
613+
// Emit an empty inline assembler expression depending on the register.
614+
auto *AsmFnTy = llvm::FunctionType::get(IGM.VoidTy, ArgTys, false);
615+
auto *InlineAsm = llvm::InlineAsm::get(AsmFnTy, "", "r", true);
616+
Builder.CreateCall(InlineAsm, Storage);
617+
}
618+
}
619+
}
620+
621+
/// At -Onone, emit a shadow copy of an Address in an alloca, so the
581622
/// register allocator doesn't elide the dbg.value intrinsic when
582623
/// register pressure is high. There is a trade-off to this: With
583624
/// shadow copies, we lose the precise lifetime.
@@ -589,8 +630,23 @@ class IRGenSILFunction :
589630
if (IGM.IRGen.Opts.Optimize || (ArgNo == 0) ||
590631
isa<llvm::AllocaInst>(Storage) ||
591632
isa<llvm::UndefValue>(Storage) ||
592-
Ty == IGM.RefCountedPtrTy) // No debug info is emitted for refcounts.
593-
return Storage;
633+
Ty == IGM.RefCountedPtrTy) { // No debug info is emitted for refcounts.
634+
// Account for bugs in LLVM.
635+
//
636+
// - The LLVM type legalizer currently doesn't update debug
637+
// intrinsics when a large value is split up into smaller
638+
// pieces. Note that this heuristic as a bit too conservative
639+
// on 32-bit targets as it will also fire for doubles.
640+
//
641+
// - CodeGen Prepare may drop dbg.values pointing to PHI instruction.
642+
if (IGM.DataLayout.getTypeSizeInBits(Storage->getType()) <=
643+
IGM.getClangASTContext().getTargetInfo().getRegisterWidth() &&
644+
!isa<llvm::PHINode>(Storage)) {
645+
if (auto *Value = dyn_cast<llvm::Instruction>(Storage))
646+
ValueVariables.push_back({getActiveDominancePoint(), Value});
647+
return Storage;
648+
}
649+
}
594650

595651
if (Align.isZero())
596652
Align = IGM.getPointerAlignment();
@@ -1516,6 +1572,8 @@ void IRGenSILFunction::visitSILBasicBlock(SILBasicBlock *BB) {
15161572
}
15171573
}
15181574
}
1575+
if (isa<TermInst>(&I))
1576+
emitDebugVariableRangeExtension(BB);
15191577
visit(&I);
15201578

15211579
assert(!EmissionNotes.count(&I) &&

test/DebugInfo/WeakCapture.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ func function() {
1010
let b = B()
1111

1212
// Ensure that the local b and its weak copy are distinct local variables.
13-
// CHECK: call void @llvm.dbg.value(metadata %C11WeakCapture1B*
14-
// CHECK-SAME: metadata [[B:.*]], metadata
15-
// CHECK: call void @llvm.dbg.value(metadata %swift.weak*
16-
// CHECK-NOT: metadata [[B]]
13+
// CHECK: call void @llvm.dbg.{{.*}}(metadata %C11WeakCapture1B*
14+
// CHECK-SAME: metadata [[B:.*]], metadata
15+
// CHECK: call void @llvm.dbg.{{.*}}(metadata %swift.weak*
16+
// CHECK-NOT: metadata [[B]]
1717
// CHECK: call
1818
A(handler: { [weak b] _ in
1919
if b != nil { }
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %target-swift-frontend %s -g -emit-ir -o - | FileCheck %s
2+
3+
func use<T>(_ x: T) {}
4+
5+
func getInt32() -> Int32 { return -1 }
6+
7+
public func rangeExtension(_ b: Bool) {
8+
// CHECK: define {{.*}}rangeExtension
9+
let i = getInt32()
10+
// CHECK: llvm.dbg.value(metadata i32 [[I:.*]], i64 0, metadata
11+
use(i)
12+
if b {
13+
let j = getInt32()
14+
// CHECK: llvm.dbg.value(metadata i32 [[J:.*]], i64 0, metadata
15+
use(j)
16+
// CHECK: asm sideeffect "", "r"
17+
// CHECK: {{(asm sideeffect "", "r".*)|(zext i32)}} [[J]]
18+
}
19+
let z = getInt32()
20+
use(z)
21+
// CHECK: llvm.dbg.value(metadata i32 [[Z:.*]], i64 0, metadata
22+
// CHECK: {{(asm sideeffect "", "r".*)|(zext i32)}} [[I]]
23+
// CHECK: asm sideeffect "", "r"
24+
}

test/DebugInfo/local-vars.swift.gyb

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// An end-to-end test to ensure local variables have debug info. This
2+
// test only verifies that the variables show up in the debug info at
3+
// all. There are other tests testing liveness and representation.
4+
5+
// RUN: %gyb %s -o %t.swift
6+
// RUN: %target-swift-frontend %t.swift -g -emit-ir -o - | FileCheck %t.swift
7+
// RUN: %target-swift-frontend %t.swift -g -c -o %t.o
8+
// RUN: llvm-dwarfdump --debug-dump=info %t.o \
9+
// RUN: | FileCheck %t.swift --check-prefix=DWARF
10+
11+
public class C {
12+
let member : Int
13+
init(_ i : Int) { member = i }
14+
func isZero() -> Boolean { return member == 0 }
15+
}
16+
17+
public struct S {
18+
let i : Int32 = -1
19+
let j : Int32 = 2
20+
}
21+
22+
func use<T>(_ x: T) {}
23+
func variable_use<T>(_ x: inout T) {}
24+
25+
% def derive_name((type, val)):
26+
% return (type.replace('<', '_').replace(' ', '_').replace(',', '_')
27+
% .replace('?', '_').replace('>', '_')
28+
% .replace('[', '_').replace(']', '_'), type, val)
29+
% for name, type, val in map(derive_name,
30+
% [("UInt64", "64"), ("UInt32", "32"), ("Int64", "64"), ("Int32", "32"),
31+
% ("Int", "42"), ("UInt", "42"), ("C", "C(42)"), ("String", '"string"'),
32+
% ("Dictionary<UInt64, String>", '[1:"entry"]'),
33+
% ("Float", "2.71"), ("Double", "3.14"), ("[UInt64]", "[1, 2, 3]"),
34+
% ("S", "S()")]):
35+
36+
public func constant_${name}() -> ${type} {
37+
let v : ${type} = ${val}
38+
// CHECK: !DILocalVariable(name: "v",{{.*}} line: [[@LINE-1]]
39+
// DWARF: DW_TAG_subprogram
40+
// DWARF: DW_AT_name {{.*}}constant_${name}
41+
// DWARF-NOT: DW_TAG_subprogram
42+
// DWARF: DW_TAG_variable
43+
// DWARF-NOT: DW_TAG
44+
// DWARF: {{(DW_AT_location)|(DW_AT_const_value)}}
45+
// DWARF-NOT: DW_TAG
46+
// DWARF: DW_AT_name {{.*}}"v"
47+
return v
48+
}
49+
50+
public func constvar_${name}() {
51+
var v : ${type} = ${val}
52+
// CHECK: !DILocalVariable(name: "v",{{.*}} line: [[@LINE-1]]
53+
// DWARF: DW_TAG_subprogram
54+
// DWARF: DW_AT_name {{.*}}constvar_${name}
55+
// DWARF-NOT: DW_TAG_subprogram
56+
// DWARF: DW_TAG_variable
57+
// DWARF-NOT: DW_TAG
58+
// DWARF: {{(DW_AT_location)|(DW_AT_const_value)}}
59+
// DWARF-NOT: DW_TAG
60+
// DWARF: DW_AT_name {{.*}}"v"
61+
variable_use(&v)
62+
}
63+
64+
public func let_${name}() {
65+
let v : ${type} = constant_${name}()
66+
// CHECK: !DILocalVariable(name: "v",{{.*}} line: [[@LINE-1]]
67+
// DWARF: DW_TAG_subprogram
68+
// DWARF: DW_AT_name {{.*}}let_${name}
69+
// DWARF-NOT: DW_TAG_subprogram
70+
// DWARF: DW_TAG_variable
71+
// DWARF-NOT: DW_TAG
72+
// DWARF: {{(DW_AT_location)|(DW_AT_const_value)}}
73+
// DWARF-NOT: DW_TAG
74+
// DWARF: DW_AT_name {{.*}}"v"
75+
use(v)
76+
}
77+
78+
public func optional_${name}() -> ${type}? {
79+
return constant_${name}();
80+
}
81+
82+
public func guard_let_${name}() {
83+
let opt : ${type}? = optional_${name}()
84+
// CHECK: !DILocalVariable(name: "opt",{{.*}} line: [[@LINE-1]]
85+
// DWARF: DW_TAG_subprogram
86+
// DWARF: DW_AT_name {{.*}}guard_let_${name}
87+
// DWARF-NOT: DW_TAG_subprogram
88+
// DWARF: DW_TAG_variable
89+
// DWARF-NOT: DW_TAG
90+
// DWARF: DW_AT_location
91+
// DWARF-NOT: DW_TAG
92+
// DWARF: DW_AT_name
93+
// DWARF-SAME: "{{(opt)|(val)}}"
94+
guard let val = opt else {
95+
use(opt)
96+
fatalError()
97+
}
98+
// DWARF-NOT: DW_TAG
99+
// DWARF: DW_TAG_variable
100+
// DWARF-NOT: DW_TAG
101+
// DWARF: DW_AT_location
102+
// DWARF-NOT: DW_TAG
103+
// DWARF: DW_AT_name
104+
// DWARF-SAME: "{{(opt)|(val)}}"
105+
use(val)
106+
}
107+
108+
public func var_${name}() {
109+
var v : ${type} = constant_${name}()
110+
// CHECK: !DILocalVariable(name: "v",{{.*}} line: [[@LINE-1]]
111+
// DWARF: DW_TAG_subprogram
112+
// DWARF: DW_AT_name {{.*}}var_${name}
113+
// DWARF-NOT: DW_TAG_subprogram
114+
// DWARF: DW_TAG_variable
115+
// DWARF-NOT: DW_TAG
116+
// DWARF: DW_AT_location
117+
// DWARF-NOT: DW_TAG
118+
// DWARF: DW_AT_name {{.*}}"v"
119+
variable_use(&v)
120+
}
121+
122+
public func arg_${name}(_ v: ${type}) {
123+
// CHECK: !DILocalVariable(name: "v",{{.*}} line: [[@LINE-1]]
124+
// DWARF: DW_TAG_subprogram
125+
// DWARF: DW_AT_name {{.*}}arg_${name}
126+
// DWARF-NOT: DW_TAG_subprogram
127+
// DWARF: DW_TAG_formal_parameter
128+
// DWARF-NOT: DW_TAG
129+
// DWARF: DW_AT_location
130+
// DWARF-NOT: DW_TAG
131+
// DWARF: DW_AT_name {{.*}}"v"
132+
use(v)
133+
}
134+
135+
public func arg_inout_${name}(_ v: inout ${type}) {
136+
// CHECK: !DILocalVariable(name: "v",{{.*}} line: [[@LINE-1]]
137+
// DWARF: DW_TAG_subprogram
138+
// DWARF: DW_AT_name {{.*}}arg_inout_${name}
139+
// DWARF-NOT: DW_TAG_subprogram
140+
// DWARF: DW_TAG_formal_parameter
141+
// DWARF-NOT: DW_TAG
142+
// DWARF: DW_AT_location
143+
// DWARF-NOT: DW_TAG
144+
// DWARF: DW_AT_name {{.*}}"v"
145+
variable_use(&v)
146+
}

test/DebugInfo/patternmatching.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ switch p {
2525
// Verify that the branch has a location >= the cleanup.
2626
// SIL-CHECK-NEXT: br{{.*}}line:[[@LINE-3]]:17:cleanup
2727
// CHECK-SCOPES: call {{.*}}markUsed
28-
// CHECK-SCOPES: call void @llvm.dbg.value({{.*}}metadata ![[X1:[0-9]+]]
29-
// CHECK-SCOPES-SAME: !dbg ![[X1LOC:[0-9]+]]
30-
// CHECK-SCOPES: call void @llvm.dbg.value
31-
// CHECK-SCOPES: call void @llvm.dbg.value({{.*}}metadata ![[X2:[0-9]+]]
32-
// CHECK-SCOPES-SAME: !dbg ![[X2LOC:[0-9]+]]
33-
// CHECK-SCOPES: call void @llvm.dbg.value
34-
// CHECK-SCOPES: call void @llvm.dbg.value({{.*}}metadata ![[X3:[0-9]+]]
35-
// CHECK-SCOPES-SAME: !dbg ![[X3LOC:[0-9]+]]
28+
// CHECK-SCOPES: call void @llvm.dbg{{.*}}metadata ![[X1:[0-9]+]],
29+
// CHECK-SCOPES-SAME: !dbg ![[X1LOC:[0-9]+]]
30+
// CHECK-SCOPES: call void @llvm.dbg
31+
// CHECK-SCOPES: call void @llvm.dbg{{.*}}metadata ![[X2:[0-9]+]],
32+
// CHECK-SCOPES-SAME: !dbg ![[X2LOC:[0-9]+]]
33+
// CHECK-SCOPES: call void @llvm.dbg
34+
// CHECK-SCOPES: call void @llvm.dbg{{.*}}metadata ![[X3:[0-9]+]],
35+
// CHECK-SCOPES-SAME: !dbg ![[X3LOC:[0-9]+]]
3636
// CHECK-SCOPES: !DILocalVariable(name: "x",
3737
case (let x, let y) where x == -y:
3838
// Verify that all variables end up in separate appropriate scopes.

0 commit comments

Comments
 (0)