diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempLValueElimination.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempLValueElimination.swift index 83bfdb1411371..07e299f4d61f0 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempLValueElimination.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempLValueElimination.swift @@ -180,6 +180,12 @@ private func tryEliminate(copy: CopyLikeInstruction, _ context: FunctionPassCont use.set(to: copy.destinationAddress, context) } } + + // Salvage the debug variable attribute, if present. + if let debugVariable = allocStack.debugVariable { + let builder = Builder(before: firstUseOfAllocStack, location: allocStack.location, context) + builder.createDebugValue(value: copy.destinationAddress, debugVariable: debugVariable) + } context.erase(instruction: allocStack) context.erase(instructionIncludingAllUsers: copy.loadingInstruction) } diff --git a/SwiftCompilerSources/Sources/SIL/Context.swift b/SwiftCompilerSources/Sources/SIL/Context.swift index 22186f02fa3b1..3a3fec15ab177 100644 --- a/SwiftCompilerSources/Sources/SIL/Context.swift +++ b/SwiftCompilerSources/Sources/SIL/Context.swift @@ -210,6 +210,7 @@ extension MutatingContext { public func erase(instructionIncludingDebugUses inst: Instruction) { precondition(inst.results.allSatisfy { $0.uses.ignoreDebugUses.isEmpty }) + salvageDebugInfo(of: inst) erase(instructionIncludingAllUsers: inst) } @@ -217,4 +218,9 @@ extension MutatingContext { _bridged.eraseBlock(block.bridged) } + /// Transfer debug info associated with (the result of) `instruction` to a + /// new `debug_value` instruction before `instruction` is deleted. + public func salvageDebugInfo(of instruction: Instruction) { + BridgedContext.salvageDebugInfo(instruction.bridged) + } } diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index e2b729ab23902..13cc0b3ee7b70 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -1555,6 +1555,7 @@ struct BridgedContext { BRIDGED_INLINE void eraseBlock(BridgedBasicBlock block) const; static BRIDGED_INLINE void moveInstructionBefore(BridgedInstruction inst, BridgedInstruction beforeInst); static BRIDGED_INLINE void copyInstructionBefore(BridgedInstruction inst, BridgedInstruction beforeInst); + static BRIDGED_INLINE void salvageDebugInfo(BridgedInstruction inst); // SSAUpdater diff --git a/include/swift/SIL/SILBridgingImpl.h b/include/swift/SIL/SILBridgingImpl.h index 36b256bdd1ddf..e50c7ddbda7cd 100644 --- a/include/swift/SIL/SILBridgingImpl.h +++ b/include/swift/SIL/SILBridgingImpl.h @@ -41,6 +41,7 @@ #include "swift/SIL/SILVTable.h" #include "swift/SIL/SILWitnessTable.h" #include "swift/SILOptimizer/Utils/ConstExpr.h" +#include "swift/SILOptimizer/Utils/DebugOptUtils.h" #include "swift/SIL/SILConstants.h" #include #include @@ -3217,6 +3218,10 @@ void BridgedContext::copyInstructionBefore(BridgedInstruction inst, BridgedInstr inst.unbridged()->clone(beforeInst.unbridged()); } +void BridgedContext::salvageDebugInfo(BridgedInstruction inst) { + swift::salvageDebugInfo(inst.unbridged()); +} + OptionalBridgedFunction BridgedContext::lookupStdlibFunction(BridgedStringRef name) const { return {context->lookupStdlibFunction(name.unbridged())}; } diff --git a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp index dd9b2a7f384a7..830c32cc9ab48 100644 --- a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp +++ b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp @@ -1968,11 +1968,16 @@ void MemoryToRegisters::removeSingleBlockAllocation(AllocStackInst *asi) { if (!runningVals) { // Loading from uninitialized memory is only acceptable if the type is // empty--an aggregate of types without storage. + const auto initialValue = + createEmptyAndUndefValue(asi->getElementType(), inst, ctx); runningVals = { LiveValues::toReplace(asi, - /*replacement=*/createEmptyAndUndefValue( - asi->getElementType(), inst, ctx)), + /*replacement=*/initialValue), /*isStorageValid=*/!doesLoadInvalidateStorage(inst)}; + if (auto varInfo = asi->getVarInfo()) { + SILBuilderWithScope(inst, ctx).createDebugValue( + inst->getLoc(), initialValue, *varInfo); + } } auto *loadInst = dyn_cast(inst); if (loadInst && diff --git a/lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp b/lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp index e4165502ba9d1..22cdfa8ca8e2f 100644 --- a/lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp +++ b/lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp @@ -321,9 +321,8 @@ splitAggregateLoad(LoadOperation loadInst, CanonicalizeInstruction &pass) { } // Preserve the original load's debug information. - if (pass.preserveDebugInfo) { - swift::salvageLoadDebugInfo(loadInst); - } + swift::salvageLoadDebugInfo(loadInst); + // Remove the now unused borrows. for (auto *borrow : borrows) nextII = killInstAndIncidentalUses(borrow, nextII, pass); diff --git a/test/DebugInfo/self-nostorage.swift b/test/DebugInfo/self-nostorage.swift index 9bd02dc419244..3ee21b0237648 100644 --- a/test/DebugInfo/self-nostorage.swift +++ b/test/DebugInfo/self-nostorage.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -primary-file %s -emit-ir -g -o - | %FileCheck %s +// RUN: %target-swift-frontend -primary-file %s -emit-ir -g -O -o - | %FileCheck %s public struct S { func f() { diff --git a/test/DebugInfo/sil_combine.sil b/test/DebugInfo/sil_combine.sil index e27f77d3acdbf..a0ea5b66854f6 100644 --- a/test/DebugInfo/sil_combine.sil +++ b/test/DebugInfo/sil_combine.sil @@ -71,3 +71,26 @@ bb3: // CHECK-IR: ![[DBG_VAR]] = !DILocalVariable(name: "hello" + +struct T { + @_hasStorage let x: Builtin.Int32 { get } + init(x: Builtin.Int32) +} + +// This test verifies that splitAggregateLoad in CanonicalizeInstruction.cpp +// salvages debug info attached to the loaded aggregate in optimized builds. +// +// CHECK-LABEL: sil @split_aggregate_load : $@convention(thin) (@in T) -> Builtin.Int32 { +// CHECK: bb0([[ARG:%[0-9]+]] : $*T): +// CHECK-NEXT: [[ELEM_ADDR:%[0-9]+]] = struct_element_addr [[ARG]] : $*T, #T.x +// CHECK-NEXT: [[ELEM:%[0-9]+]] = load [[ELEM_ADDR]] +// CHECK-NEXT: debug_value [[ARG]] : $*T, let, name "var", expr op_deref +// CHECK-NEXT: return [[ELEM]] : $Builtin.Int32 +// CHECK-LABEL: } // end sil function 'split_aggregate_load' +sil @split_aggregate_load : $@convention(thin) (@in T) -> Builtin.Int32 { +bb0(%0 : $*T): + %1 = load %0 + debug_value %1, let, name "var" + %2 = struct_extract %1, #T.x + return %2 +} diff --git a/test/DebugInfo/simplify_builtin.sil b/test/DebugInfo/simplify_builtin.sil new file mode 100644 index 0000000000000..270b648fffb3b --- /dev/null +++ b/test/DebugInfo/simplify_builtin.sil @@ -0,0 +1,43 @@ +// RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all %s -simplification -simplify-instruction=builtin -sil-print-debuginfo | %FileCheck %s + +// REQUIRES: swift_in_compiler + +import Swift +import Builtin + +// This verifies that debug information attached to the arguments of builtin +// instructions is preserved when those instructions are constant-folded. +// +// CHECK-LABEL: sil @preserve_bitwise_operands : $@convention(thin) () -> Builtin.Int32 { +// CHECK: bb0: +// CHECK-NEXT: debug_value undef : $Builtin.Int32, let, name "x", expr op_constu:1 +// CHECK-NEXT: debug_value undef : $Builtin.Int32, let, name "y", expr op_constu:2 +// CHECK-NEXT: [[THREE:%[0-9]+]] = integer_literal $Builtin.Int32, 3 +// CHECK-NEXT: return [[THREE]] : $Builtin.Int32 +// CHECK-LABEL: } // end sil function 'preserve_bitwise_operands' +sil @preserve_bitwise_operands : $@convention(thin) () -> Builtin.Int32 { +bb0: + %0 = integer_literal $Builtin.Int32, 1 + debug_value %0, let, name "x" + %1 = integer_literal $Builtin.Int32, 2 + debug_value %1, let, name "y" + %2 = builtin "or_Int32"(%0, %1) : $Builtin.Int32 + return %2 +} + +// CHECK-LABEL: sil @preserve_add_operands : $@convention(thin) () -> Builtin.Int32 { +// CHECK: bb0: +// CHECK-NEXT: debug_value undef : $Builtin.Int32, let, name "x", expr op_consts:4294967295 +// CHECK-NEXT: debug_value undef : $Builtin.Int32, let, name "y", expr op_constu:2 +// CHECK-NEXT: [[ONE:%[0-9]+]] = integer_literal $Builtin.Int32, 1 +// CHECK-NEXT: return [[ONE]] : $Builtin.Int32 +// CHECK-LABEL: } // end sil function 'preserve_add_operands' +sil @preserve_add_operands : $@convention(thin) () -> Builtin.Int32 { +bb0: + %0 = integer_literal $Builtin.Int32, -1 + debug_value %0, let, name "x" + %1 = integer_literal $Builtin.Int32, 2 + debug_value %1, let, name "y" + %2 = builtin "add_Int32"(%0, %1) : $Builtin.Int32 + return %2 +} diff --git a/test/DebugInfo/simplify_struct_extract.sil b/test/DebugInfo/simplify_struct_extract.sil new file mode 100644 index 0000000000000..c8204301b2e94 --- /dev/null +++ b/test/DebugInfo/simplify_struct_extract.sil @@ -0,0 +1,27 @@ +// RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all %s -simplification -simplify-instruction=struct_extract -sil-print-debuginfo | %FileCheck %s + +// REQUIRES: swift_in_compiler + +import Builtin +import Swift + +struct T { + var x: Builtin.Int32 +} + +// This verifies that tryReplaceRedundantInstructionPair in OptUtils.swift +// salvages debug info attached to the first instruction of the pair, +// in this case the struct. +// +// CHECK-LABEL: sil @redundant_pair : $@convention(thin) (Builtin.Int32) -> Builtin.Int32 { +// CHECK: bb0([[VAR:%[0-9]+]] : $Builtin.Int32): +// CHECK-NEXT: debug_value [[VAR]] : $Builtin.Int32, let, name "var", type $T, expr op_fragment:#T.x +// CHECK-NEXT: return [[VAR]] +// CHECK-LABEL: } // end sil function 'redundant_pair' +sil @redundant_pair : $@convention(thin) (Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32): + %1 = struct $T(%0) + debug_value %1, let, name "var" + %2 = struct_extract %1, #T.x + return %2 +} diff --git a/test/DebugInfo/temp_lvalue_elimination.sil b/test/DebugInfo/temp_lvalue_elimination.sil new file mode 100644 index 0000000000000..69eb151842661 --- /dev/null +++ b/test/DebugInfo/temp_lvalue_elimination.sil @@ -0,0 +1,26 @@ +// RUN: %target-sil-opt -sil-print-types -sil-verify-all -temp-lvalue-elimination %s | %FileCheck %s +sil_stage canonical + +// REQUIRES: swift_in_compiler + +import Builtin + +// This verifies that the TempLValueElimination pass correctly salvages the +// debug variable attribute of alloc_stack instructions. +// +// CHECK-LABEL: sil @stack_var_elimination : $@convention(thin) (Builtin.Int32) -> @out Builtin.Int32 { +// CHECK: bb0([[X:%[0-9]+]] : $*Builtin.Int32, [[VAL:%[0-9]+]] : $Builtin.Int32): +// CHECK-NEXT: debug_value [[X]] : $*Builtin.Int32, var, name "x" +// CHECK-NEXT: store [[VAL]] to [[X]] : $*Builtin.Int32 +// CHECK-NEXT: [[E:%[0-9]+]] = tuple () +// CHECK-NEXT: return [[E]] : $() +// CHECK-LABEL: } // end sil function 'stack_var_elimination' +sil @stack_var_elimination : $@convention(thin) (Builtin.Int32) -> @out Builtin.Int32 { +bb0(%0 : $*Builtin.Int32, %1 : $Builtin.Int32): + %2 = alloc_stack [var_decl] $Builtin.Int32, var, name "x" + store %1 to %2 + copy_addr %2 to %0 + dealloc_stack %2 + %3 = tuple () + return %3 +}