[DebugInfo] Salvage debug info for allocations in SILSROA

This commit is contained in:
Emil Pedersen
2024-03-29 13:02:34 -07:00
parent eb0cdda46f
commit e8ce02e0a3
7 changed files with 129 additions and 15 deletions

View File

@@ -349,7 +349,12 @@ static void promoteDebugValueAddr(DebugValueInst *dvai, SILValue value,
auto varInfo = *dvai->getVarInfo();
if (isa<DebugValueInst>(dvai)) {
auto &diExpr = varInfo.DIExpr;
if (diExpr)
// FIXME: There should always be a DIExpr starting with an op_deref here
// The debug_value is attached to a pointer type, and those don't exist
// in Swift, so they should always be dereferenced.
// However, this rule is broken in a lot of spaces, so we have to leave
// this check to recover from wrong info
if (diExpr && diExpr.startsWithDeref())
diExpr.eraseElement(diExpr.element_begin());
}

View File

@@ -195,13 +195,14 @@ SROAMemoryUseAnalyzer::
createAllocas(llvm::SmallVector<AllocStackInst *, 4> &NewAllocations) {
SILBuilderWithScope B(AI);
SILType Type = AI->getType().getObjectType();
std::optional<SILDebugVariable> AIDebugVarInfo =
SILDebugVariable::createFromAllocation(AI);
// Intentionally dropping the debug location.
SILLocation Loc = RegularLocation::getAutoGeneratedLocation();
if (TT) {
for (unsigned EltNo : indices(TT->getElementTypes())) {
std::optional<SILDebugVariable> NewDebugVarInfo =
SILDebugVariable::createFromAllocation(AI);
std::optional<SILDebugVariable> NewDebugVarInfo = AIDebugVarInfo;
if (NewDebugVarInfo)
NewDebugVarInfo->DIExpr.append(
SILDebugInfoExpression::createTupleFragment(TT, EltNo));
@@ -215,8 +216,7 @@ createAllocas(llvm::SmallVector<AllocStackInst *, 4> &NewAllocations) {
"this point.");
SILModule &M = AI->getModule();
for (VarDecl *VD : SD->getStoredProperties()) {
std::optional<SILDebugVariable> NewDebugVarInfo =
SILDebugVariable::createFromAllocation(AI);
std::optional<SILDebugVariable> NewDebugVarInfo = AIDebugVarInfo;
if (NewDebugVarInfo)
NewDebugVarInfo->DIExpr.append(
SILDebugInfoExpression::createFragment(VD));
@@ -225,6 +225,10 @@ createAllocas(llvm::SmallVector<AllocStackInst *, 4> &NewAllocations) {
NewDebugVarInfo, AI->hasDynamicLifetime(), AI->isLexical()));
}
}
if (AIDebugVarInfo && NewAllocations.empty()) {
// Don't eliminate empty structs, we can use undef as there is no data
B.createDebugValue(Loc, SILUndef::get(AI), *AIDebugVarInfo);
}
}
void SROAMemoryUseAnalyzer::chopUpAlloca(std::vector<AllocStackInst *> &Worklist) {
@@ -274,7 +278,7 @@ void SROAMemoryUseAnalyzer::chopUpAlloca(std::vector<AllocStackInst *> &Worklist
}
// Find all dealloc instructions for AI and then chop them up.
llvm::SmallVector<DeallocStackInst *, 4> ToRemove;
llvm::SmallVector<SILInstruction *, 4> ToRemove;
for (auto *Operand : getNonDebugUses(SILValue(AI))) {
SILInstruction *User = Operand->getUser();
SILBuilderWithScope B(User);
@@ -290,12 +294,41 @@ void SROAMemoryUseAnalyzer::chopUpAlloca(std::vector<AllocStackInst *> &Worklist
}
}
// Remove the old DeallocStackInst instructions.
for (auto *Operand : getDebugUses(SILValue(AI))) {
SILInstruction *User = Operand->getUser();
auto *DVI = dyn_cast<DebugValueInst>(User);
assert(DVI && "getDebugUses should only return DebugValueInst");
SILBuilderWithScope B(DVI);
std::optional<SILDebugVariable> DVIVarInfo = DVI->getVarInfo();
assert(DVIVarInfo && "debug_value without debug info");
for (size_t i : indices(NewAllocations)) {
auto *NewAI = NewAllocations[i];
SILDebugVariable VarInfo = *DVIVarInfo;
if (TT) {
VarInfo.DIExpr.append(
SILDebugInfoExpression::createTupleFragment(TT, i));
} else {
VarInfo.DIExpr.append(
SILDebugInfoExpression::createFragment(SD->getStoredProperties()[i]));
}
if (!VarInfo.Type)
VarInfo.Type = AI->getElementType();
B.createDebugValue(DVI->getLoc(), NewAI, VarInfo);
}
if (NewAllocations.empty()) {
// Don't eliminate empty structs, we can use undef as there is no data
B.createDebugValue(DVI->getLoc(), SILUndef::get(AI), *DVIVarInfo);
}
ToRemove.push_back(DVI);
}
// Remove the old DeallocStackInst/DebugValueInst instructions.
for (auto *DSI : ToRemove) {
DSI->eraseFromParent();
}
eraseFromParentWithDebugInsts(AI);
AI->eraseFromParent();
}
/// Returns true, if values of \ty should be ignored, because \p ty is known

View File

@@ -0,0 +1,55 @@
// RUN: %target-sil-opt -enable-sil-verify-all -sil-print-debuginfo -sroa %s | %FileCheck --check-prefix=CHECK-SROA %s
sil_stage canonical
import Builtin
import Swift
struct MyStruct {
@_hasStorage var x: Int64 { get set }
@_hasStorage var y: Int64 { get set }
init(x: Int64, y: Int64)
}
sil_scope 1 { loc "sroa.swift":2:8 parent @MyStructInit : $@convention(method) (Int64, Int64, @thin MyStruct.Type) -> MyStruct }
// MyStruct.init(x:y:)
sil hidden @MyStructInit : $@convention(method) (Int64, Int64, @thin MyStruct.Type) -> MyStruct {
bb0(%0 : $Int64, %1 : $Int64, %2 : $@thin MyStruct.Type):
%3 = struct $MyStruct (%0 : $Int64, %1 : $Int64), loc "sroa.swift":2:8, scope 1
return %3 : $MyStruct, loc "sroa.swift":2:8, scope 1
} // end sil function 'MyStructInit'
sil_scope 2 { loc "sroa.swift":7:6 parent @foo : $@convention(thin) (Int64, Int64) -> Int64 }
// CHECK-SROA-LABEL: sil {{.+}} @foo
// foo(in_x:in_y:)
sil hidden @foo : $@convention(thin) (Int64, Int64) -> Int64 {
bb0(%0 : $Int64, %1 : $Int64):
%4 = alloc_stack $MyStruct, var, name "my_struct", loc "sroa.swift":8:9, scope 2
debug_value %4 : $*MyStruct, let, name "my_copy", expr op_deref, loc "sroa.swift":7:10, scope 2
// Make sure SROA propagate the debug info to the splitted alloc_stack/debug_value instructions
// CHECK-SROA: %[[ALLOC_X:[0-9]+]] = alloc_stack $Int64, var
// CHECK-SROA-SAME: (name "my_struct", loc "sroa.swift":8:9
// CHECK-SROA-SAME: type $*MyStruct, expr op_fragment:#MyStruct.x
// CHECK-SROA-SAME: loc * "<compiler-generated>":0:0
// CHECK-SROA: %[[ALLOC_Y:[0-9]+]] = alloc_stack $Int64, var
// CHECK-SROA-SAME: (name "my_struct", loc "sroa.swift":8:9
// CHECK-SROA-SAME: type $*MyStruct, expr op_fragment:#MyStruct.y
// CHECK-SROA-SAME: loc * "<compiler-generated>":0:0
// CHECK-SROA: debug_value %[[ALLOC_X]] : $*Int64, let
// CHECK-SROA-SAME: name "my_copy",
// CHECK-SROA-SAME: type $MyStruct, expr op_deref:op_fragment:#MyStruct.x
// CHECK-SROA-SAME: loc "sroa.swift":7:10
// CHECK-SROA: debug_value %[[ALLOC_Y]] : $*Int64, let
// CHECK-SROA-SAME: name "my_copy",
// CHECK-SROA-SAME: type $MyStruct, expr op_deref:op_fragment:#MyStruct.y
// CHECK-SROA-SAME: loc "sroa.swift":7:10
%13 = struct_element_addr %4 : $*MyStruct, #MyStruct.x, loc "sroa.swift":9:17, scope 2
store %0 to %13 : $*Int64, loc "sroa.swift":9:17, scope 2
// CHECK-SROA: store %0 to %[[ALLOC_X]]
%15 = struct_element_addr %4 : $*MyStruct, #MyStruct.y, loc "sroa.swift":10:17, scope 2
store %1 to %15 : $*Int64, loc "sroa.swift":10:17, scope 2
// CHECK-SROA: store %1 to %[[ALLOC_Y]]
dealloc_stack %4 : $*MyStruct, loc "sroa.swift":8:9, scope 2
return %0 : $Int64, loc "sroa.swift":11:5, scope 2
} // end sil function 'foo'

View File

@@ -0,0 +1,28 @@
// RUN: %target-sil-opt -enable-sil-verify-all -sil-print-debuginfo -sroa %s | %FileCheck --check-prefix=CHECK-SROA %s
sil_stage canonical
import Builtin
import Swift
struct Empty {}
sil_scope 1 { loc "sroa.swift":7:6 parent @bar : $@convention(thin) (Int64, Int64) -> Int64 }
// CHECK-SROA-LABEL: sil {{.+}} @bar
// bar(in_x:in_y:)
sil hidden @bar : $@convention(thin) (Int64, Int64) -> Int64 {
bb0(%0 : $Int64, %1 : $Int64):
%4 = alloc_stack $Empty, var, name "my_struct", loc "sroa.swift":8:9, scope 1
debug_value %4 : $*Empty, let, name "my_copy", expr op_deref, loc "sroa.swift":7:10, scope 1
// Make sure SROA keeps the debug info
// CHECK-SROA: debug_value undef : $*Empty, var
// CHECK-SROA-SAME: name "my_struct",
// CHECK-SROA-SAME: loc "sroa.swift":8:9
// CHECK-SROA: debug_value undef : $*Empty, let
// CHECK-SROA-SAME: name "my_copy",
// CHECK-SROA-SAME: loc "sroa.swift":7:10
%6 = struct $Empty (), loc "sroa.swift":9:8, scope 1
store %6 to %4 : $*Empty, loc "sroa.swift":10:8, scope 1
dealloc_stack %4 : $*Empty, loc "sroa.swift":8:9, scope 1
return %0 : $Int64, loc "sroa.swift":11:5, scope 1
} // end sil function 'foo'

View File

@@ -43,8 +43,6 @@ sil @use_int32 : $@convention(thin) (Builtin.Int32) -> ()
sil @struct_with_scalar_fields : $@convention(thin) (S1) -> () {
bb0(%0 : $S1):
%1 = alloc_stack $S1
debug_value %1 : $*S1 // should not prevent the optimization
debug_value %1 : $*S1 // should not prevent the optimization
store %0 to %1 : $*S1
%2 = function_ref @use_int32 : $@convention(thin) (Builtin.Int32) -> ()
%3 = struct_element_addr %1 : $*S1, #S1.y

View File

@@ -46,8 +46,6 @@ sil [ossa] @use_int32 : $@convention(thin) (Builtin.Int32) -> ()
sil [ossa] @struct_with_scalar_fields : $@convention(thin) (S1) -> () {
bb0(%0 : $S1):
%1 = alloc_stack [lexical] $S1
debug_value %1 : $*S1 // should not prevent the optimization
debug_value %1 : $*S1 // should not prevent the optimization
store %0 to [trivial] %1 : $*S1
%2 = function_ref @use_int32 : $@convention(thin) (Builtin.Int32) -> ()
%3 = struct_element_addr %1 : $*S1, #S1.y

View File

@@ -46,8 +46,6 @@ sil [ossa] @use_int32 : $@convention(thin) (Builtin.Int32) -> ()
sil [ossa] @struct_with_scalar_fields : $@convention(thin) (S1) -> () {
bb0(%0 : $S1):
%1 = alloc_stack $S1
debug_value %1 : $*S1 // should not prevent the optimization
debug_value %1 : $*S1 // should not prevent the optimization
store %0 to [trivial] %1 : $*S1
%2 = function_ref @use_int32 : $@convention(thin) (Builtin.Int32) -> ()
%3 = struct_element_addr %1 : $*S1, #S1.y
@@ -483,7 +481,6 @@ sil [ossa] @use_obj : $@convention(thin) (@owned Obj) -> ()
sil [ossa] @struct_with_obj_fields : $@convention(thin) (@owned NotTrivial) -> () {
bb0(%0 : @owned $NotTrivial):
%1 = alloc_stack $NotTrivial
debug_value %1 : $*NotTrivial // should not prevent the optimization
store %0 to [init] %1 : $*NotTrivial
%2 = function_ref @use_obj : $@convention(thin) (@owned Obj) -> ()
%3 = struct_element_addr %1 : $*NotTrivial, #NotTrivial.y