Skip to content

Commit ac7e28a

Browse files
committed
[alloc-stack-hoisting] Handle alloc_stack [move] correctly.
This is just a quick fix to stop us from dropping live values such as m in the following example: ``` public func addressOnlyVarTest<T : P>(_ x: T) { var k = x k.doSomething() let m = _move(k) m.doSomething() k = x k.doSomething() } ``` Before this change, we would just drop m and one wouldn't even see it in the debugger. I am only doing this currently for cases where when we merge at least one alloc_stack was moved. The reason why is that to implement this correctly, I need to use llvm.dbg.addr and changing the debug info from using llvm.dbg.declare -> llvm.dbg.addr requires statistics and needs to be done a little later in the swift development process. If one of these alloc_stack had the [moved] marker attached to it, we know the user /did/ use move so they have in a sense opted into having a move function effect its program so we are only changing how new code appears in the debugger.
1 parent 210e135 commit ac7e28a

File tree

4 files changed

+420
-38
lines changed

4 files changed

+420
-38
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,22 @@ class AllocStackInst final
19631963
/// any point of the program.
19641964
bool wasMoved = false;
19651965

1966+
/// Set to true if this AllocStack has var info that a pass purposely
1967+
/// invalidated.
1968+
///
1969+
/// NOTE:
1970+
///
1971+
/// 1. We don't print this state. It is just a way to invalidate the debug
1972+
/// info. When we parse back in whatever we printed, we will parse it without
1973+
/// debug var info since none will be printed.
1974+
///
1975+
/// 2. Since we do not serialize debug info today, we do not need to serialize
1976+
/// this state.
1977+
///
1978+
/// TODO: If we begin serializing debug info, we will need to begin
1979+
/// serializing this!
1980+
bool hasInvalidatedVarInfo = false;
1981+
19661982
AllocStackInst(SILDebugLocation Loc, SILType elementType,
19671983
ArrayRef<SILValue> TypeDependentOperands, SILFunction &F,
19681984
Optional<SILDebugVariable> Var, bool hasDynamicLifetime,
@@ -2019,6 +2035,12 @@ class AllocStackInst final
20192035

20202036
/// Return the debug variable information attached to this instruction.
20212037
Optional<SILDebugVariable> getVarInfo() const {
2038+
// If we used to have debug info attached but our debug info is now
2039+
// invalidated, just bail.
2040+
if (hasInvalidatedVarInfo) {
2041+
return None;
2042+
}
2043+
20222044
Optional<SILType> AuxVarType;
20232045
Optional<SILLocation> VarDeclLoc;
20242046
const SILDebugScope *VarDeclScope = nullptr;
@@ -2039,6 +2061,14 @@ class AllocStackInst final
20392061
VarDeclScope, DIExprElements);
20402062
}
20412063

2064+
bool isVarInfoInvalidated() const { return hasInvalidatedVarInfo; }
2065+
2066+
/// Invalidate the debug info in an alloc_stack. This is useful in cases where
2067+
/// we one is merging alloc_stack and wants to split the debug info on an
2068+
/// alloc_stack into a separate debug_value instruction from the merged
2069+
/// alloc_stack.
2070+
void invalidateVarInfo() { hasInvalidatedVarInfo = true; }
2071+
20422072
bool isLet() const {
20432073
if (auto varInfo = getVarInfo())
20442074
return varInfo->isLet();

lib/IRGen/AllocStackHoisting.cpp

Lines changed: 130 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,21 @@
1212

1313
#define DEBUG_TYPE "alloc-stack-hoisting"
1414

15-
#include "swift/IRGen/IRGenSILPasses.h"
1615
#include "swift/AST/Availability.h"
17-
#include "swift/SILOptimizer/Analysis/Analysis.h"
18-
#include "swift/SILOptimizer/PassManager/Passes.h"
19-
#include "swift/SILOptimizer/PassManager/Transforms.h"
16+
#include "swift/AST/IRGenOptions.h"
17+
#include "swift/AST/SemanticAttrs.h"
18+
#include "swift/IRGen/IRGenSILPasses.h"
2019
#include "swift/SIL/DebugUtils.h"
20+
#include "swift/SIL/Dominance.h"
21+
#include "swift/SIL/LoopInfo.h"
22+
#include "swift/SIL/SILArgument.h"
2123
#include "swift/SIL/SILBuilder.h"
2224
#include "swift/SIL/SILInstruction.h"
23-
#include "swift/SIL/SILArgument.h"
24-
#include "swift/AST/SemanticAttrs.h"
25+
#include "swift/SILOptimizer/Analysis/Analysis.h"
26+
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
27+
#include "swift/SILOptimizer/PassManager/Passes.h"
28+
#include "swift/SILOptimizer/PassManager/Transforms.h"
29+
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
2530

2631
#include "IRGenModule.h"
2732
#include "NonFixedTypeInfo.h"
@@ -95,7 +100,22 @@ class Partition {
95100
///
96101
/// This assumes that the live ranges of the alloc_stack instructions are
97102
/// non-overlapping.
98-
void assignStackLocation(SmallVectorImpl<SILInstruction *> &FunctionExits);
103+
void assignStackLocation(
104+
SmallVectorImpl<SILInstruction *> &FunctionExits,
105+
SmallVectorImpl<DebugValueInst *> &DebugValueToBreakBlocksAt);
106+
107+
/// Returns true if any of the alloc_stack that we are merging were
108+
/// moved. Causes us to insert extra debug addr.
109+
///
110+
/// TODO: In the future we want to do this for /all/ alloc_stack but that
111+
/// would require us moving /most of/ swift's IRGen emission to use
112+
/// llvm.dbg.addr instead of llvm.dbg.declare and that would require us to do
113+
/// statistics to make sure that we haven't hurt debuggability by making the
114+
/// change.
115+
bool hasMovedElt() const {
116+
return llvm::any_of(Elts,
117+
[](AllocStackInst *asi) { return asi->getWasMoved(); });
118+
}
99119
};
100120
} // end anonymous namespace
101121

@@ -125,21 +145,40 @@ insertDeallocStackAtEndOf(SmallVectorImpl<SILInstruction *> &FunctionExits,
125145

126146
/// Hack to workaround a clang LTO bug.
127147
LLVM_ATTRIBUTE_NOINLINE
128-
void moveAllocStackToBeginningOfBlock(AllocStackInst* AS, SILBasicBlock *BB) {
148+
void moveAllocStackToBeginningOfBlock(
149+
AllocStackInst *AS, SILBasicBlock *BB, bool haveMovedElt,
150+
SmallVectorImpl<DebugValueInst *> &DebugValueToBreakBlocksAt) {
151+
// If we have var info, create the debug_value at the alloc_stack position and
152+
// invalidate the alloc_stack's var info. This transfers the debug info state
153+
// of the debug_value to the original position.
154+
if (haveMovedElt) {
155+
if (auto varInfo = AS->getVarInfo()) {
156+
SILBuilderWithScope Builder(AS);
157+
auto *DVI = Builder.createDebugValue(AS->getLoc(), AS, *varInfo);
158+
DVI->markAsMoved();
159+
DebugValueToBreakBlocksAt.push_back(DVI);
160+
AS->invalidateVarInfo();
161+
AS->markAsMoved();
162+
}
163+
}
129164
AS->moveFront(BB);
130165
}
131166

132167
/// Assign a single alloc_stack instruction to all the alloc_stacks in the
133168
/// partition.
134169
void Partition::assignStackLocation(
135-
SmallVectorImpl<SILInstruction *> &FunctionExits) {
170+
SmallVectorImpl<SILInstruction *> &FunctionExits,
171+
SmallVectorImpl<DebugValueInst *> &DebugValueToBreakBlocksAt) {
136172
assert(!Elts.empty() && "Must have a least one location");
173+
bool hasAtLeastOneMovedElt = hasMovedElt();
174+
137175
// The assigned location is the first alloc_stack in our partition.
138176
auto *AssignedLoc = Elts[0];
139177

140178
// Move this assigned location to the beginning of the entry block.
141179
auto *EntryBB = AssignedLoc->getFunction()->getEntryBlock();
142-
moveAllocStackToBeginningOfBlock(AssignedLoc, EntryBB);
180+
moveAllocStackToBeginningOfBlock(AssignedLoc, EntryBB, hasAtLeastOneMovedElt,
181+
DebugValueToBreakBlocksAt);
143182

144183
// Erase the dealloc_stacks.
145184
eraseDeallocStacks(AssignedLoc);
@@ -153,6 +192,15 @@ void Partition::assignStackLocation(
153192
if (AssignedLoc == AllocStack) continue;
154193
eraseDeallocStacks(AllocStack);
155194
AllocStack->replaceAllUsesWith(AssignedLoc);
195+
if (hasAtLeastOneMovedElt) {
196+
if (auto VarInfo = AllocStack->getVarInfo()) {
197+
SILBuilderWithScope Builder(AllocStack);
198+
auto *DVI = Builder.createDebugValue(AllocStack->getLoc(), AssignedLoc,
199+
*VarInfo);
200+
DVI->markAsMoved();
201+
DebugValueToBreakBlocksAt.push_back(DVI);
202+
}
203+
}
156204
AllocStack->eraseFromParent();
157205
}
158206
}
@@ -234,14 +282,21 @@ class MergeStackSlots {
234282
SmallVector<Partition, 2> PartitionByType;
235283
/// The function exits.
236284
SmallVectorImpl<SILInstruction *> &FunctionExits;
285+
/// If we are merging any alloc_stack that were moved, to work around a bug in
286+
/// SelectionDAG that sinks to llvm.dbg.addr, we need to break blocks right
287+
/// after each llvm.dbg.addr.
288+
///
289+
/// TODO: Once we have /any/ FastISel/better SelectionDAG support, this can be
290+
/// removed.
291+
SmallVector<DebugValueInst *, 4> DebugValueToBreakBlocksAt;
237292

238293
public:
239294
MergeStackSlots(SmallVectorImpl<AllocStackInst *> &AllocStacks,
240295
SmallVectorImpl<SILInstruction *> &FuncExits);
241296

242297
/// Merge alloc_stack instructions if possible and hoist them to the entry
243298
/// block.
244-
void mergeSlots();
299+
SILAnalysis::InvalidationKind mergeSlots(DominanceInfo *domToUpdate);
245300
};
246301
} // end anonymous namespace
247302

@@ -264,7 +319,10 @@ MergeStackSlots::MergeStackSlots(SmallVectorImpl<AllocStackInst *> &AllocStacks,
264319

265320
/// Merge alloc_stack instructions if possible and hoist them to the entry
266321
/// block.
267-
void MergeStackSlots::mergeSlots() {
322+
SILAnalysis::InvalidationKind
323+
MergeStackSlots::mergeSlots(DominanceInfo *DomToUpdate) {
324+
auto Result = SILAnalysis::InvalidationKind::Instructions;
325+
268326
for (auto &PartitionOfOneType : PartitionByType) {
269327
Liveness Live(PartitionOfOneType);
270328

@@ -312,11 +370,26 @@ void MergeStackSlots::mergeSlots() {
312370
// Assign stack locations to disjoint partition hoisting alloc_stacks to the
313371
// entry block at the same time.
314372
for (auto &Par : DisjointPartitions) {
315-
Par.assignStackLocation(FunctionExits);
373+
Par.assignStackLocation(FunctionExits, DebugValueToBreakBlocksAt);
316374
}
317375
}
318-
}
319376

377+
// Now that we have finished merging slots/hoisting, break any blocks that we
378+
// need to.
379+
if (!DebugValueToBreakBlocksAt.empty()) {
380+
auto &Mod = DebugValueToBreakBlocksAt.front()->getModule();
381+
SILBuilderContext Context(Mod);
382+
do {
383+
auto *Next = DebugValueToBreakBlocksAt.pop_back_val();
384+
splitBasicBlockAndBranch(Context, Next->getNextInstruction(), DomToUpdate,
385+
nullptr);
386+
} while (!DebugValueToBreakBlocksAt.empty());
387+
388+
Result = SILAnalysis::InvalidationKind::BranchesAndInstructions;
389+
}
390+
391+
return Result;
392+
}
320393

321394
namespace {
322395
/// Hoist alloc_stack instructions to the entry block and merge them.
@@ -329,13 +402,20 @@ class HoistAllocStack {
329402
SmallVector<AllocStackInst *, 16> AllocStackToHoist;
330403
SmallVector<SILInstruction *, 8> FunctionExits;
331404

405+
Optional<SILAnalysis::InvalidationKind> InvalidationKind = None;
406+
407+
DominanceInfo *DomInfoToUpdate = nullptr;
408+
332409
public:
333410
HoistAllocStack(SILFunction *F, irgen::IRGenModule &Mod)
334411
: F(F), IRGenMod(Mod) {}
335412

336-
/// Try to hoist generic alloc_stack instructions to the entry block.
337-
/// Returns true if the function was changed.
338-
bool run();
413+
/// Try to hoist generic alloc_stack instructions to the entry block. Returns
414+
/// none if the function was not changed. Otherwise, returns the analysis
415+
/// invalidation kind to use if the function was changed.
416+
Optional<SILAnalysis::InvalidationKind> run();
417+
418+
void setDominanceToUpdate(DominanceInfo *DI) { DomInfoToUpdate = DI; }
339419

340420
private:
341421
/// Collect generic alloc_stack instructions that can be moved to the entry
@@ -395,37 +475,38 @@ void HoistAllocStack::collectHoistableInstructions() {
395475
/// Hoist the alloc_stack instructions to the entry block and sink the
396476
/// dealloc_stack instructions to the function exists.
397477
void HoistAllocStack::hoist() {
398-
399478
if (SILUseStackSlotMerging) {
400479
MergeStackSlots Merger(AllocStackToHoist, FunctionExits);
401-
Merger.mergeSlots();
402-
} else {
403-
// Hoist alloc_stacks to the entry block and delete dealloc_stacks.
404-
auto *EntryBB = F->getEntryBlock();
405-
for (auto *AllocStack : AllocStackToHoist) {
406-
// Insert at the beginning of the entry block.
407-
AllocStack->moveFront(EntryBB);
408-
// Delete dealloc_stacks.
409-
eraseDeallocStacks(AllocStack);
410-
}
411-
// Insert dealloc_stack in the exit blocks.
412-
for (auto *AllocStack : AllocStackToHoist) {
413-
insertDeallocStackAtEndOf(FunctionExits, AllocStack);
414-
}
480+
InvalidationKind = Merger.mergeSlots(DomInfoToUpdate);
481+
return;
482+
}
483+
484+
// Hoist alloc_stacks to the entry block and delete dealloc_stacks.
485+
auto *EntryBB = F->getEntryBlock();
486+
for (auto *AllocStack : AllocStackToHoist) {
487+
// Insert at the beginning of the entry block.
488+
AllocStack->moveFront(EntryBB);
489+
// Delete dealloc_stacks.
490+
eraseDeallocStacks(AllocStack);
491+
InvalidationKind = SILAnalysis::InvalidationKind::Instructions;
492+
}
493+
// Insert dealloc_stack in the exit blocks.
494+
for (auto *AllocStack : AllocStackToHoist) {
495+
insertDeallocStackAtEndOf(FunctionExits, AllocStack);
415496
}
416497
}
417498

418499
/// Try to hoist generic alloc_stack instructions to the entry block.
419500
/// Returns true if the function was changed.
420-
bool HoistAllocStack::run() {
501+
Optional<SILAnalysis::InvalidationKind> HoistAllocStack::run() {
421502
collectHoistableInstructions();
422503

423504
// Nothing to hoist?
424505
if (AllocStackToHoist.empty())
425-
return false;
506+
return {};
426507

427508
hoist();
428-
return true;
509+
return InvalidationKind;
429510
}
430511

431512
namespace {
@@ -434,9 +515,20 @@ class AllocStackHoisting : public SILFunctionTransform {
434515
auto *F = getFunction();
435516
auto *Mod = getIRGenModule();
436517
assert(Mod && "This pass must be run as part of an IRGen pipeline");
437-
bool Changed = HoistAllocStack(F, *Mod).run();
438-
if (Changed) {
439-
PM->invalidateAnalysis(F, SILAnalysis::InvalidationKind::Instructions);
518+
519+
HoistAllocStack Hoist(F, *Mod);
520+
521+
// Update DomInfo when breaking. We don't use loop info right now this late,
522+
// so we don't need to do that.
523+
auto *DA = getAnalysis<DominanceAnalysis>();
524+
if (DA->hasFunctionInfo(F))
525+
Hoist.setDominanceToUpdate(DA->get(F));
526+
527+
auto InvalidationKind = Hoist.run();
528+
529+
if (InvalidationKind) {
530+
AnalysisPreserver preserveDominance(DA);
531+
PM->invalidateAnalysis(F, *InvalidationKind);
440532
}
441533
}
442534
};

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,9 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
14841484

14851485
verifyOpenedArchetype(AI, AI->getElementType().getASTType());
14861486

1487+
require(!AI->isVarInfoInvalidated() || !bool(AI->getVarInfo()),
1488+
"AllocStack Var Info should be None if invalidated");
1489+
14871490
// There used to be a check if all uses of ASI are inside the alloc-dealloc
14881491
// range. But apparently it can be the case that ASI has uses after the
14891492
// dealloc_stack. This can come up if the source contains a

0 commit comments

Comments
 (0)