Skip to content

Commit 02f7450

Browse files
committed
At -Onone preserve debug info after splitting loads
Load splitting converts an aggregate load into a set of subobject loads. This is required at -Onone for exclusivity diagnostics. We cannot preserve the original debug information by redirecting debug info to the memory address, because that might result in incorrect debug values if the memory is reused. Before this fix, we "conservatively" drop debug info in those cases. This fix preserves full debug info by keeping the original aggregate load intact alongside the new subobject loads. To avoid exclusivity violations, it create a new unsafe access scope for the old load. Fixes LLDB missing variables in certain case #62241
1 parent b206c76 commit 02f7450

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)