Skip to content

Commit 182c1a4

Browse files
authored
Merge pull request #62672 from atrick/fix-splitload-debug
At -Onone preserve debug info after splitting loads
2 parents abfd790 + 02f7450 commit 182c1a4

File tree

3 files changed

+122
-13
lines changed

3 files changed

+122
-13
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1907,14 +1907,16 @@ class AccessUseDefChainCloner
19071907
/// Clone all projections and casts on the access use-def chain until the
19081908
/// checkBase predicate returns a valid base.
19091909
///
1910+
/// Returns the cloned value equivalent to \p addr.
1911+
///
19101912
/// This will not clone ref_element_addr or ref_tail_addr because those aren't
19111913
/// part of the access chain.
19121914
///
19131915
/// CheckBase is a unary predicate that takes the next source address and either
19141916
/// returns a valid SILValue to use as the base of the cloned access path, or an
19151917
/// invalid SILValue to continue cloning.
19161918
///
1917-
/// CheckBase must return a valid SILValue either before attempting to clone the
1919+
/// CheckBase must return a valid SILValue before attempting to clone the
19181920
/// access base. The most basic valid predicate is:
19191921
///
19201922
/// auto checkBase = [&](SILValue srcAddr) {
@@ -1929,6 +1931,8 @@ SILValue cloneUseDefChain(SILValue addr, SILInstruction *insertionPoint,
19291931

19301932
/// Analog to cloneUseDefChain to check validity. begin_borrow and
19311933
/// mark_dependence currently cannot be cloned.
1934+
///
1935+
/// Returns the cloned value equivalent to \p addr.
19321936
template <typename CheckBase>
19331937
bool canCloneUseDefChain(SILValue addr, CheckBase checkBase) {
19341938
return AccessUseDefChainCloner<CheckBase>(checkBase, nullptr)

lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
2222
#include "swift/SIL/DebugUtils.h"
2323
#include "swift/SIL/InstructionUtils.h"
24+
#include "swift/SIL/MemAccessUtils.h"
2425
#include "swift/SIL/Projection.h"
2526
#include "swift/SIL/SILBuilder.h"
2627
#include "swift/SIL/SILFunction.h"
@@ -137,6 +138,77 @@ static void replaceUsesOfExtract(SingleValueInstruction *extract,
137138
extract->replaceAllUsesWith(loadedVal);
138139
}
139140

141+
// If \p loadInst has an debug uses, then move it into a separate unsafe access
142+
// scope. This hides it from the exclusivity checker.
143+
//
144+
// If \p loadInst was successfully hidden, then this returns the next
145+
// instruction following \p loadInst and following any newly inserted
146+
// instructions. Otherwise this returns nullptr. Returning nullptr is a signal
147+
// to delete \p loadInst.
148+
//
149+
// Before:
150+
//
151+
// %a = begin_access %0 [read] [unknown]
152+
// %proj = some_projections %a
153+
// %whole = load %proj // <-- loadInst
154+
// %field = struct_element_addr %proj, #field
155+
// %part = load %field
156+
//
157+
// After:
158+
//
159+
// %a = begin_access %0 [read] [unknown]
160+
// %proj = some_projections %a
161+
// %a2 = begin_access %0 [read] [unsafe] // NEW
162+
// %proj2 = some_projections %a // CLONED
163+
// %whole = load %proj2 // <-- loadInst
164+
// end_access %a2 // NEW
165+
// %field = struct_element_addr %proj, #field
166+
// %part = load %field
167+
//
168+
static SILInstruction *
169+
moveLoadToUnsafeAccessScope(LoadInst *loadInst,
170+
CanonicalizeInstruction &pass) {
171+
if (llvm::none_of(loadInst->getUses(), [](Operand *use) {
172+
return use->getUser()->isDebugInstruction();
173+
})) {
174+
return nullptr;
175+
}
176+
SILValue accessScope = getAccessScope(loadInst->getOperand());
177+
auto *access = dyn_cast<BeginAccessInst>(accessScope);
178+
if (access && access->getEnforcement() == SILAccessEnforcement::Unsafe)
179+
return nullptr;
180+
181+
auto checkBaseAddress = [=](SILValue addr) {
182+
if (addr != accessScope)
183+
return SILValue();
184+
185+
// the base of the new unsafe scope
186+
if (access)
187+
return access->getOperand();
188+
189+
return accessScope;
190+
};
191+
192+
if (!canCloneUseDefChain(loadInst->getOperand(), checkBaseAddress))
193+
return nullptr;
194+
195+
SILValue newBase =
196+
cloneUseDefChain(loadInst->getOperand(), loadInst, checkBaseAddress);
197+
198+
auto *beginUnsafe = SILBuilderWithScope(loadInst).createBeginAccess(
199+
loadInst->getLoc(), newBase, SILAccessKind::Read,
200+
SILAccessEnforcement::Unsafe, true, false);
201+
loadInst->setOperand(beginUnsafe);
202+
auto nextInst = loadInst->getNextInstruction();
203+
auto *endUnsafe = SILBuilderWithScope(nextInst).createEndAccess(
204+
loadInst->getLoc(), beginUnsafe, false);
205+
206+
pass.notifyNewInstruction(beginUnsafe);
207+
pass.notifyNewInstruction(endUnsafe);
208+
209+
return nextInst;
210+
}
211+
140212
// Given a load with multiple struct_extracts/tuple_extracts and no other uses,
141213
// canonicalize the load into several (struct_element_addr (load)) pairs.
142214
//
@@ -301,16 +373,9 @@ splitAggregateLoad(LoadOperation loadInst, CanonicalizeInstruction &pass) {
301373
}
302374
pass.notifyNewInstruction(**lastNewLoad);
303375

304-
// FIXME: This drops debug info at -Onone load-splitting is required at
305-
// -Onone for exclusivity diagnostics. Fix this by
306-
//
307-
// 1. At -Onone, preserve the original load when pass.preserveDebugInfo is
308-
// true, but moving it out of its current access scope and into an "unknown"
309-
// access scope, which won't be enforced as an exclusivity violation.
310-
//
311-
// 2. At -O, create "debug fragments" recover as much debug info as possible
312-
// by creating debug_value fragments for each new partial load. Currently
313-
// disabled because of LLVM back-end crashes.
376+
// FIXME: At -O, create "debug fragments" recover as much debug info as
377+
// possible by creating debug_value fragments for each new partial
378+
// load. Currently disabled because it caused on LLVM back-end crash.
314379
if (!pass.preserveDebugInfo && EnableLoadSplittingDebugInfo) {
315380
createDebugFragments(*loadInst, proj, lastNewLoad->getLoadInst());
316381
}
@@ -340,13 +405,23 @@ splitAggregateLoad(LoadOperation loadInst, CanonicalizeInstruction &pass) {
340405
for (auto *borrow : borrows)
341406
nextII = killInstAndIncidentalUses(borrow, nextII, pass);
342407

408+
// When pass.preserveDebugInfo is true, keep the original load so that debug
409+
// info refers to the loaded value, rather than a memory location which may
410+
// not be reused. Move the wide load out of its current access scope and into
411+
// an "unknown" access scope, which won't be enforced as an exclusivity
412+
// violation.
413+
if (pass.preserveDebugInfo) {
414+
if (auto *regularLoad = dyn_cast<LoadInst>(loadInst.getLoadInst())) {
415+
if (auto *nextInst = moveLoadToUnsafeAccessScope(regularLoad, pass))
416+
return nextInst->getIterator();
417+
}
418+
}
343419
// Erase the old load.
344420
for (auto *destroy : lifetimeEndingInsts)
345421
nextII = killInstruction(destroy, nextII, pass);
346422

347423
// FIXME: remove this temporary hack to advance the iterator beyond
348-
// debug_value. A soon-to-be merged commit migrates CanonicalizeInstruction to
349-
// use InstructionDeleter.
424+
// debug_value.
350425
while (nextII != loadInst->getParent()->end()
351426
&& nextII->isDebugInstruction()) {
352427
++nextII;

test/SILOptimizer/silgen_cleanup.sil

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,33 @@ bb9(%0 : @owned $Klass):
362362
destroy_value %0 : $Klass
363363
return %v : $Builtin.Int64
364364
}
365+
366+
// debug_value must be preserved after splitting loads
367+
368+
struct IntWrapper {
369+
var _value : Int
370+
}
371+
372+
sil_scope 5 { loc "./test.swift":3:6 parent @testSplitLoadDebug : $@convention(thin) (@in_guaranteed IntWrapper) -> Builtin.Int32 }
373+
sil_scope 6 { loc "./test.swift":3:10 parent 5 }
374+
375+
// CHECK-LABEL: sil hidden [ossa] @testSplitLoadDebug : $@convention(thin) (@in_guaranteed IntWrapper) -> Builtin.Int32 {
376+
// CHECK: bb0(%0 : $*IntWrapper):
377+
// CHECKDEB: [[A1:%.*]] = struct_element_addr %0 : $*IntWrapper, #IntWrapper._value
378+
// CHECKDEB: [[A2:%.*]] = struct_element_addr [[A1]] : $*Int, #Int._value
379+
// CHECKDEB: [[SPLIT:%.*]] = load [trivial] %2 : $*Builtin.Int32
380+
// CHECKDEB: [[A3:%.*]] = struct_element_addr %0 : $*IntWrapper, #IntWrapper._value
381+
// CHECKDEB: [[A4:%.*]] = begin_access [read] [unsafe] [no_nested_conflict] [[A3]] : $*Int
382+
// CHECKDEB: [[OLD:%.*]] = load [trivial] [[A4]] : $*Int
383+
// CHECKDEB: end_access [[A4]] : $*Int
384+
// CHECKDEB: debug_value [[OLD]] : $Int, let, name "flag"
385+
// CHECKDEB: return [[SPLIT]] : $Builtin.Int32
386+
// CHECK-LABEL: } // end sil function 'testSplitLoadDebug'
387+
sil hidden [ossa] @testSplitLoadDebug : $@convention(thin) (@in_guaranteed IntWrapper) -> Builtin.Int32 {
388+
bb0(%0: $*IntWrapper):
389+
%1 = struct_element_addr %0 : $*IntWrapper, #IntWrapper._value
390+
%2 = load [trivial] %1 : $*Int
391+
debug_value %2 : $Int, let, name "flag", loc "./test.swift":4:7, scope 6
392+
%4 = struct_extract %2 : $Int, #Int._value
393+
return %4 : $Builtin.Int32
394+
}

0 commit comments

Comments
 (0)