From 5ac76704ed7a2d28bc68d39fd2d912107fb29329 Mon Sep 17 00:00:00 2001 From: Emil Pedersen Date: Tue, 26 May 2026 15:22:11 +0100 Subject: [PATCH 1/3] [DebugInfo] Always store op_deref in prependDeref bit for debug_value Rather than having two ways of storing an op_deref (prependDeref bit, or start of DIExpr), always use the prependDeref bit when creating a debug_value instruction. The stored DIExpr should only have fragments. --- include/swift/SIL/SILInstruction.h | 11 +++++++++-- lib/SIL/IR/SILInstructions.cpp | 26 ++++++++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 03a759e4878d5..55fac878c97f2 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -5626,7 +5626,8 @@ class DebugValueInst final DebugValueInst(SILDebugLocation DebugLoc, SILValue Operand, SILDebugVariable Var, PoisonRefs_t poisonRefs, - UsesMoveableValueDebugInfo_t operandWasMoved, bool trace); + UsesMoveableValueDebugInfo_t operandWasMoved, bool trace, + bool prependDeref); static DebugValueInst *create(SILDebugLocation DebugLoc, SILValue Operand, SILModule &M, SILDebugVariable Var, PoisonRefs_t poisonRefs, @@ -5704,7 +5705,7 @@ class DebugValueInst final // Make a temporary copy to prepend a deref. This is safe as // SILDebugVariable contains a copy of the expression. llvm::SmallVector DIExprCopy; - if (sharedUInt8().DebugValueInst.prependDeref) { + if (hasDeref()) { DIExprCopy.push_back(SILDIExprElement::createOperator( SILDIExprOperator::Dereference)); DIExprCopy.append(DIExprElements.begin(), DIExprElements.end()); @@ -5742,6 +5743,12 @@ class DebugValueInst final return DVI && DVI->hasAddrVal()? DVI : nullptr; } + /// Whether this debug value has a DIExpr with a deref. If this instruction + /// has a debug reconstruction block, this returns false. + bool hasDeref() const { + return sharedUInt8().DebugValueInst.prependDeref; + } + /// Prepends a deref operator to this debug_value in place. /// This must be called when the operand is changed from an object type to /// an address type (when moved to the stack, for example). diff --git a/lib/SIL/IR/SILInstructions.cpp b/lib/SIL/IR/SILInstructions.cpp index 0fa565b1a4aba..a6d1f82e66772 100644 --- a/lib/SIL/IR/SILInstructions.cpp +++ b/lib/SIL/IR/SILInstructions.cpp @@ -438,7 +438,7 @@ SILType AllocBoxInst::getAddressType() const { DebugValueInst::DebugValueInst( SILDebugLocation DebugLoc, SILValue Operand, SILDebugVariable Var, PoisonRefs_t poisonRefs, - UsesMoveableValueDebugInfo_t usesMoveableValueDebugInfo, bool trace) + UsesMoveableValueDebugInfo_t usesMoveableValueDebugInfo, bool trace, bool prependDeref) : UnaryInstructionBase(DebugLoc, Operand), SILDebugVariableSupplement(Var.DIExpr.getNumElements(), Var.Type.has_value(), Var.Loc.has_value(), @@ -451,6 +451,8 @@ DebugValueInst::DebugValueInst( if (usesMoveableValueDebugInfo || Operand->getType().isMoveOnly()) setUsesMoveableValueDebugInfo(); setTrace(trace); + if (prependDeref) + this->prependDeref(); } DebugValueInst *DebugValueInst::create(SILDebugLocation DebugLoc, @@ -466,14 +468,18 @@ DebugValueInst *DebugValueInst::create(SILDebugLocation DebugLoc, Var.Scope = nullptr; if (Var.Type == Operand->getType().getObjectType()) Var.Type = {}; + // Use the prependDeref bit rather than storing it in the DIExpr. + bool prependDeref = Var.DIExpr.startsWithDeref(); + if (prependDeref) { + Var.DIExpr.eraseElement(Var.DIExpr.element_begin()); + } void *buf = allocateDebugVarCarryingInst(M, Var); return ::new (buf) - DebugValueInst(DebugLoc, Operand, Var, poisonRefs, wasMoved, trace); + DebugValueInst(DebugLoc, Operand, Var, poisonRefs, wasMoved, trace, prependDeref); } void DebugValueInst::prependDeref() { - ASSERT(!sharedUInt8().DebugValueInst.prependDeref && - "Debug value cannot have two derefs!"); + ASSERT(!hasDeref() && "Debug value cannot have two derefs!"); if (!ReconstructionBlock) { sharedUInt8().DebugValueInst.prependDeref = true; return; @@ -509,7 +515,7 @@ SILBasicBlock *DebugValueInst::getOrCreateDebugReconstructionBlock() { if (isa(operand)) { // No arguments, return the same undef directly. retVal = operand; - } else if (sharedUInt8().DebugValueInst.prependDeref) { + } else if (hasDeref()) { // Convert the deref to a load. SILArgument *arg = block->createPhiArgument( operand->getType().getAddressType(), OwnershipKind::None); @@ -556,6 +562,9 @@ bool DebugValueInst::isExprTypeValid() const { if (debugBB->getArgument(0)->getType() != valueType) return false; } + // Cannot have both an op_deref and a debug reconstruction block. + if (hasDeref()) + return false; auto *terminator = cast(debugBB->getTerminator()); valueType = terminator->getOperand()->getType(); } @@ -580,7 +589,8 @@ bool DebugValueInst::isExprTypeValid() const { break; } default: - break; + // Invalid operator + return false; } } @@ -589,6 +599,10 @@ bool DebugValueInst::isExprTypeValid() const { if (derefCount != valueType.isAddress()) return false; + // The op_deref must be stored in the prependDeref bit. + if (derefCount != hasDeref()) + return false; + return RunningType.removingMoveOnlyWrapper() == valueType.getObjectType().removingMoveOnlyWrapper(); } From 7a356966ad21df4631f1065c4ace116ff8528e86 Mon Sep 17 00:00:00 2001 From: Emil Pedersen Date: Mon, 11 May 2026 14:57:21 +0100 Subject: [PATCH 2/3] [DebugInfo] Add killOperand function to DebugValueInst --- include/swift/SIL/SILInstruction.h | 7 +++++++ lib/SIL/IR/SILInstructions.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 55fac878c97f2..a295ee9415d60 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -5788,6 +5788,13 @@ class DebugValueInst final /// has no arguments and returns undef directly. SILBasicBlock *getOrCreateDebugReconstructionBlock(); + /// Drops the operand from this debug value. + /// This function must be called by passes whenever the operand of this debug + /// value is no longer valid and cannot be salvaged. + /// This will replace the operand with an undef, and clear any DIExpr or debug + /// reconstruction block. + void killOperand(); + /// True if all references within this debug value will be overwritten with a /// poison sentinel at this point in the program. This is used in debug builds /// when shortening non-trivial value lifetimes to ensure the debugger cannot diff --git a/lib/SIL/IR/SILInstructions.cpp b/lib/SIL/IR/SILInstructions.cpp index a6d1f82e66772..0a7743c21862a 100644 --- a/lib/SIL/IR/SILInstructions.cpp +++ b/lib/SIL/IR/SILInstructions.cpp @@ -533,6 +533,31 @@ SILBasicBlock *DebugValueInst::getOrCreateDebugReconstructionBlock() { return block; } +void DebugValueInst::killOperand() { + if (isa(getOperand())) { + // Already undef: no operand to kill. + return; + } + + // Use the object type because we may be stripping the deref. + SILValue undef = + SILUndef::get(getFunction(), getOperand()->getType().getObjectType()); + setOperand(undef); + + // Strip prependDeref. + // The stored DIExpr only contains fragments, which we want to keep. + sharedUInt8().DebugValueInst.prependDeref = false; + + // Debug reconstruction block: rather than completely removing it, remove its + // argument, as a part of the variable might be constant and recoverable. + if (auto bb = getDebugReconstructionBlock()) { + ASSERT(bb->getNumArguments() == 1); + auto argument = bb->getArgument(0); + argument->replaceAllUsesWithUndef(); + bb->eraseArgument(0); + } +} + bool DebugValueInst::isExprTypeValid() const { auto varInfo = getCompleteVarInfo(); From e85ea0b93d7cfb334304023345312afae6121f41 Mon Sep 17 00:00:00 2001 From: Emil Pedersen Date: Tue, 12 May 2026 16:15:24 +0100 Subject: [PATCH 3/3] [Debuginfo] Create killAllDebugUses function This function drops debug_value operands by making them undef, rather than deleting them. --- include/swift/SIL/DebugUtils.h | 21 +++++++++++++++++++++ lib/SIL/IR/SILInstructions.cpp | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/swift/SIL/DebugUtils.h b/include/swift/SIL/DebugUtils.h index afe74d4fd333e..2c5806cf7667c 100644 --- a/include/swift/SIL/DebugUtils.h +++ b/include/swift/SIL/DebugUtils.h @@ -63,6 +63,27 @@ inline void deleteAllDebugUses(SILInstruction *inst) { } } +/// Drops all of the debug uses of \p value. +/// Unlike deleteAllDebugUses, this preserves the debug_value instruction +/// but replaces its operand with undef and strips non-fragment DIExpr parts. +/// Use this when salvage has NOT already created a replacement debug_value. +inline void killAllDebugUses(SILValue value) { + SmallVector debugUsers; + for (auto *use : value->getUses()) { + if (auto *dvi = dyn_cast(use->getUser())) + debugUsers.push_back(dvi); + } + for (auto *dvi : debugUsers) + dvi->killOperand(); +} + +/// Drops all of the debug uses of any result of \p inst. +inline void killAllDebugUses(SILInstruction *inst) { + for (SILValue v : inst->getResults()) { + killAllDebugUses(v); + } +} + /// This iterator filters out any debug (or non-debug) instructions from a range /// of uses, provided by the underlying ValueBaseUseIterator. /// If \p nonDebugInsts is true, then the iterator provides a view to all non- diff --git a/lib/SIL/IR/SILInstructions.cpp b/lib/SIL/IR/SILInstructions.cpp index 0a7743c21862a..fbcf330eead13 100644 --- a/lib/SIL/IR/SILInstructions.cpp +++ b/lib/SIL/IR/SILInstructions.cpp @@ -548,7 +548,7 @@ void DebugValueInst::killOperand() { // The stored DIExpr only contains fragments, which we want to keep. sharedUInt8().DebugValueInst.prependDeref = false; - // Debug reconstruction block: rather than completely removing it, remove its + // Rather than completely removing the debug reconstruction block, remove its // argument, as a part of the variable might be constant and recoverable. if (auto bb = getDebugReconstructionBlock()) { ASSERT(bb->getNumArguments() == 1);