Skip to content

Commit 4c8beef

Browse files
committed
[DebugInfo] Salvage debug info for stores removed by DeadObject-Elim
rdar://124283055
1 parent c256c1f commit 4c8beef

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,17 @@ class DeadObjectElimination : public SILFunctionTransform {
771771
DominanceInfo *domInfo = nullptr;
772772

773773
void removeInstructions(ArrayRef<SILInstruction*> toRemove);
774+
775+
/// Try to salvage the debug info for a dead instruction removed by
776+
/// DeadObjectElimination.
777+
///
778+
/// Dead stores will be replaced by a debug value for the object variable,
779+
/// using a fragment expression. By walking from the store to the allocation,
780+
/// we can know which member of the object is being assigned, and create
781+
/// fragments for each member. Other instructions are not salvaged.
782+
/// Currently only supports dead stack-allocated objects.
783+
void salvageDebugInfo(SILInstruction *toBeRemoved);
784+
std::optional<SILDebugVariable> buildDIExpression(SILInstruction *current);
774785

775786
bool processAllocRef(AllocRefInstBase *ARI);
776787
bool processAllocStack(AllocStackInst *ASI);
@@ -833,6 +844,52 @@ DeadObjectElimination::removeInstructions(ArrayRef<SILInstruction*> toRemove) {
833844
}
834845
}
835846

847+
void DeadObjectElimination::salvageDebugInfo(SILInstruction *toBeRemoved) {
848+
auto *SI = dyn_cast<StoreInst>(toBeRemoved);
849+
if (!SI)
850+
return;
851+
852+
auto *parent = SI->getDest()->getDefiningInstruction();
853+
auto varInfo = buildDIExpression(parent);
854+
if (!varInfo)
855+
return;
856+
857+
SILBuilderWithScope Builder(SI);
858+
Builder.createDebugValue(SI->getLoc(), SI->getSrc(), *varInfo);
859+
}
860+
861+
std::optional<SILDebugVariable>
862+
DeadObjectElimination::buildDIExpression(SILInstruction *current) {
863+
if (!current)
864+
return {};
865+
if (auto dvci = dyn_cast<AllocStackInst>(current)) {
866+
auto var = dvci->getVarInfo();
867+
if (!var)
868+
return {};
869+
var->Type = dvci->getType();
870+
return var;
871+
}
872+
if (auto *tupleAddr = dyn_cast<TupleElementAddrInst>(current)) {
873+
auto *definer = tupleAddr->getOperand().getDefiningInstruction();
874+
auto path = buildDIExpression(definer);
875+
if (!path)
876+
return {};
877+
path->DIExpr.append(SILDebugInfoExpression::createTupleFragment(
878+
tupleAddr->getTupleType(), tupleAddr->getFieldIndex()));
879+
return path;
880+
}
881+
if (auto *structAddr = dyn_cast<StructElementAddrInst>(current)) {
882+
auto *definer = structAddr->getOperand().getDefiningInstruction();
883+
auto path = buildDIExpression(definer);
884+
if (!path)
885+
return {};
886+
path->DIExpr.append(SILDebugInfoExpression::createFragment(
887+
structAddr->getField()));
888+
return path;
889+
}
890+
return {};
891+
}
892+
836893
bool DeadObjectElimination::processAllocRef(AllocRefInstBase *ARI) {
837894
// Ok, we have an alloc_ref. Check the cache to see if we have already
838895
// computed the destructor behavior for its SILType.
@@ -957,6 +1014,8 @@ bool DeadObjectElimination::processAllocStack(AllocStackInst *ASI) {
9571014
}
9581015
}
9591016

1017+
for (auto *I : UsersToRemove)
1018+
salvageDebugInfo(I);
9601019
// Remove the AllocRef and all of its users.
9611020
removeInstructions(
9621021
ArrayRef<SILInstruction*>(UsersToRemove.begin(), UsersToRemove.end()));

test/DebugInfo/dead-obj-elim.sil

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %target-sil-opt -enable-sil-verify-all -deadobject-elim %s | %FileCheck %s
2+
3+
sil_stage canonical
4+
5+
import Builtin
6+
import Swift
7+
8+
struct MyObject {
9+
var _storage: (UInt8, UInt8)
10+
}
11+
12+
// CHECK-LABEL: sil @$myFunc
13+
sil @$myFunc : $@convention(thin) (UInt8) -> UInt8 {
14+
[global: ]
15+
bb0(%0 : $UInt8):
16+
debug_value %0 : $UInt8, let, name "value", argno 1
17+
// CHECK-NOT: alloc_stack
18+
%2 = alloc_stack $MyObject, var, name "obj"
19+
%24 = integer_literal $Builtin.Int8, 3
20+
%25 = struct_extract %0 : $UInt8, #UInt8._value
21+
%26 = integer_literal $Builtin.Int1, -1
22+
%27 = builtin "uadd_with_overflow_Int8"(%25 : $Builtin.Int8, %24 : $Builtin.Int8, %26 : $Builtin.Int1) : $(Builtin.Int8, Builtin.Int1)
23+
%28 = tuple_extract %27 : $(Builtin.Int8, Builtin.Int1), 0
24+
%29 = tuple_extract %27 : $(Builtin.Int8, Builtin.Int1), 1
25+
cond_fail %29 : $Builtin.Int1, "arithmetic overflow"
26+
%31 = struct $UInt8 (%28 : $Builtin.Int8)
27+
%34 = struct_element_addr %2 : $*MyObject, #MyObject._storage
28+
%35 = tuple_element_addr %34 : $*(UInt8, UInt8), 0
29+
%36 = tuple_element_addr %34 : $*(UInt8, UInt8), 1
30+
// The store below should be replaced by a debug_value with the same operand as the store (%0)
31+
// The fragment refers to the member being modified
32+
// CHECK-NOT: store
33+
// CHECK: debug_value %0 : $UInt8, var, name "obj", type $*MyObject, expr op_fragment:#MyObject._storage:op_tuple_fragment:$(UInt8, UInt8):1
34+
store %0 to %36 : $*UInt8
35+
// CHECK: debug_value %{{[0-9]}} : $UInt8, var, name "obj", type $*MyObject, expr op_fragment:#MyObject._storage:op_tuple_fragment:$(UInt8, UInt8):0
36+
store %31 to %35 : $*UInt8
37+
dealloc_stack %2 : $*MyObject
38+
return %31 : $UInt8
39+
} // end sil function '$myFunc'

0 commit comments

Comments
 (0)