Skip to content

Commit 6ecbeef

Browse files
committed
Add AccessedStorage::Tail access kind and remove more checks.
Distinguish ref_tail_addr storage from the other storage classes. We didn't have this originally because be don't expect a begin_access to directly operate on tail storage. It could occur after inlining, at least with static access markers. More importantly it helps ditinguish regular formal accesses from other unidentified access, so we probably should have always had this. At any rate, it's particularly important when AccessedStorage is generalized to arbitrary memory access. The immediate motivation is to add an AccessPath utility, which will need to distinguish tail storage. In the process, rewrite AccessedStorage::isDistinct. This could have a large positive impact on exclusivity performance.
1 parent 5091aee commit 6ecbeef

File tree

8 files changed

+113
-25
lines changed

8 files changed

+113
-25
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ namespace swift {
189189
/// the base of a formal access. It may be from a ref_tail_addr, undef, or some
190190
/// recognized memory initialization pattern. Unidentified valid storage cannot
191191
/// represent any arbitrary base address--it must at least been proven not to
192-
/// correspond to any class or global variable access.
192+
/// correspond to any class or global variable access, unless it's nested within
193+
/// another access to the same object. So, Unidentified can overlap with
194+
/// Class/Global access, but it cannot be the only formal access to that memory.
193195
///
194196
/// An *invalid* AccessedStorage object is Unidentified and associated with an
195197
/// invalid SILValue. This signals that analysis has failed to recognize an
@@ -219,6 +221,7 @@ class AccessedStorage {
219221
Stack,
220222
Global,
221223
Class,
224+
Tail,
222225
Argument,
223226
Yield,
224227
Nested,
@@ -228,10 +231,16 @@ class AccessedStorage {
228231

229232
static const char *getKindName(Kind k);
230233

231-
/// Directly create an AccessedStorage for class property access.
234+
// Give object tail storage a fake property index for convenience.
235+
static constexpr unsigned TailIndex = ~0U;
236+
237+
/// Directly create an AccessedStorage for class or tail property access.
232238
static AccessedStorage forClass(SILValue object, unsigned propertyIndex) {
233239
AccessedStorage storage;
234-
storage.initKind(Class, propertyIndex);
240+
if (propertyIndex == TailIndex)
241+
storage.initKind(Tail);
242+
else
243+
storage.initKind(Class, propertyIndex);
235244
storage.value = object;
236245
return storage;
237246
}
@@ -301,7 +310,8 @@ class AccessedStorage {
301310
union {
302311
// For non-class storage, 'value' is the access base. For class storage
303312
// 'value' is the object base, where the access base is the class' stored
304-
// property.
313+
// property. For tail storage 'value' is the object base and there is no
314+
// value for the access base.
305315
SILValue value;
306316
SILGlobalVariable *global;
307317
};
@@ -334,7 +344,7 @@ class AccessedStorage {
334344
}
335345

336346
SILValue getValue() const {
337-
assert(getKind() != Global && getKind() != Class);
347+
assert(getKind() != Global && getKind() != Class && getKind() != Tail);
338348
return value;
339349
}
340350

@@ -354,7 +364,7 @@ class AccessedStorage {
354364
}
355365

356366
SILValue getObject() const {
357-
assert(getKind() == Class);
367+
assert(getKind() == Class || getKind() == Tail);
358368
return value;
359369
}
360370
unsigned getPropertyIndex() const {
@@ -374,6 +384,7 @@ class AccessedStorage {
374384
switch (getKind()) {
375385
case Box:
376386
case Stack:
387+
case Tail:
377388
case Argument:
378389
case Yield:
379390
case Nested:
@@ -396,6 +407,7 @@ class AccessedStorage {
396407
return true;
397408
case Global:
398409
case Class:
410+
case Tail:
399411
case Argument:
400412
case Yield:
401413
case Nested:
@@ -405,6 +417,11 @@ class AccessedStorage {
405417
llvm_unreachable("unhandled kind");
406418
}
407419

420+
/// Return trye if the given access is guaranteed to be within a heap object.
421+
bool isObjectAccess() const {
422+
return getKind() == Class || getKind() == Tail;
423+
}
424+
408425
/// Return true if the given access is on a 'let' lvalue.
409426
bool isLetAccess(SILFunction *F) const;
410427

@@ -420,6 +437,7 @@ class AccessedStorage {
420437
case Global:
421438
return true;
422439
case Class:
440+
case Tail:
423441
case Argument:
424442
case Yield:
425443
case Nested:
@@ -455,20 +473,47 @@ class AccessedStorage {
455473
// Return true if this storage is guaranteed not to overlap with \p other's
456474
// storage.
457475
bool isDistinctFrom(const AccessedStorage &other) const {
458-
if (isUniquelyIdentified() && other.isUniquelyIdentified()) {
459-
return !hasIdenticalBase(other);
476+
if (isUniquelyIdentified()) {
477+
if (other.isUniquelyIdentified() && !hasIdenticalBase(other))
478+
return true;
479+
480+
if (other.isObjectAccess())
481+
return true;
482+
483+
// We currently assume that Unidentified storage may overlap with
484+
// Box/Stack storage.
485+
return false;
460486
}
461-
if (getKind() != Class || other.getKind() != Class)
462-
// At least one side is an Argument or Yield, or is unidentified.
487+
if (other.isUniquelyIdentified())
488+
return other.isDistinctFrom(*this);
489+
490+
// Neither storage is uniquely identified.
491+
if (isObjectAccess()) {
492+
if (other.isObjectAccess()) {
493+
// Property access cannot overlap with Tail access.
494+
if (getKind() != other.getKind())
495+
return true;
496+
497+
// We could also check if the object types are distinct, but that only
498+
// helps if we know the relationships between class types.
499+
return getKind() == Class
500+
&& getPropertyIndex() != other.getPropertyIndex();
501+
}
502+
// Any type of nested/argument address may be within the same object.
503+
//
504+
// We also currently assume Unidentified access may be within an object
505+
// purely to handle KeyPath accesses. The deriviation of the KeyPath
506+
// address must separately appear to be a Class access so that all Class
507+
// accesses are accounted for.
463508
return false;
464-
465-
// Classes are not uniquely identified by their base. However, if the
466-
// underling objects have identical types and distinct property indices then
467-
// they are distinct storage locations.
468-
if (getObject()->getType() == other.getObject()->getType()
469-
&& getPropertyIndex() != other.getPropertyIndex()) {
470-
return true;
471509
}
510+
if (other.isObjectAccess())
511+
return other.isDistinctFrom(*this);
512+
513+
// Neither storage is from a class or tail.
514+
//
515+
// Unidentified values may alias with each other or with any kind of
516+
// nested/argument access.
472517
return false;
473518
}
474519

@@ -530,10 +575,11 @@ template <> struct DenseMapInfo<swift::AccessedStorage> {
530575
return storage.getParamIndex();
531576
case swift::AccessedStorage::Global:
532577
return DenseMapInfo<void *>::getHashValue(storage.getGlobal());
533-
case swift::AccessedStorage::Class: {
578+
case swift::AccessedStorage::Class:
534579
return llvm::hash_combine(storage.getObject(),
535580
storage.getPropertyIndex());
536-
}
581+
case swift::AccessedStorage::Tail:
582+
return DenseMapInfo<swift::SILValue>::getHashValue(storage.getObject());
537583
}
538584
llvm_unreachable("Unhandled AccessedStorageKind");
539585
}
@@ -673,6 +719,9 @@ class AccessUseDefChainVisitor {
673719
Result visitClassAccess(RefElementAddrInst *field) {
674720
return asImpl().visitBase(field, AccessedStorage::Class);
675721
}
722+
Result visitTailAccess(RefTailAddrInst *tail) {
723+
return asImpl().visitBase(tail, AccessedStorage::Tail);
724+
}
676725
Result visitArgumentAccess(SILFunctionArgument *arg) {
677726
return asImpl().visitBase(arg, AccessedStorage::Argument);
678727
}
@@ -808,7 +857,7 @@ Result AccessUseDefChainVisitor<Impl, Result>::visit(SILValue sourceAddr) {
808857
// This is a valid address producer for nested @inout argument
809858
// access, but it is never used for formal access of identified objects.
810859
case ValueKind::RefTailAddrInst:
811-
return asImpl().visitUnidentified(sourceAddr);
860+
return asImpl().visitTailAccess(cast<RefTailAddrInst>(sourceAddr));
812861

813862
// Inductive single-operand cases:
814863
// Look through address casts to find the source address.

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ AccessedStorage::AccessedStorage(SILValue base, Kind kind) {
119119
auto *REA = cast<RefElementAddrInst>(base);
120120
value = stripBorrow(REA->getOperand());
121121
setElementIndex(REA->getFieldNo());
122+
break;
123+
}
124+
case Tail: {
125+
auto *RTA = cast<RefTailAddrInst>(base);
126+
value = stripBorrow(RTA->getOperand());
127+
break;
122128
}
123129
}
124130
}
@@ -152,6 +158,9 @@ const ValueDecl *AccessedStorage::getDecl() const {
152158
auto *decl = getObject()->getType().getNominalOrBoundGenericNominal();
153159
return decl->getStoredProperties()[getPropertyIndex()];
154160
}
161+
case Tail:
162+
return nullptr;
163+
155164
case Argument:
156165
return getArgument()->getDecl();
157166

@@ -185,6 +194,8 @@ const char *AccessedStorage::getKindName(AccessedStorage::Kind k) {
185194
return "Global";
186195
case Class:
187196
return "Class";
197+
case Tail:
198+
return "Tail";
188199
}
189200
llvm_unreachable("unhandled kind");
190201
}
@@ -215,6 +226,9 @@ void AccessedStorage::print(raw_ostream &os) const {
215226
getDecl()->print(os);
216227
os << " Index: " << getPropertyIndex() << "\n";
217228
break;
229+
case Tail:
230+
os << getObject();
231+
os << " Tail\n";
218232
}
219233
}
220234

@@ -578,6 +592,9 @@ bool swift::isPossibleFormalAccessBase(const AccessedStorage &storage,
578592
break;
579593
case AccessedStorage::Class:
580594
break;
595+
case AccessedStorage::Tail:
596+
return false;
597+
581598
case AccessedStorage::Yield:
582599
// Yields are accessed by the caller.
583600
return false;

lib/SIL/Verifier/LoadBorrowInvalidationChecker.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,11 @@ bool LoadBorrowNeverInvalidatedAnalysis::isNeverInvalidated(
486486
// validate that all uses of the project_box are not writes that overlap with
487487
// our load_borrow's result. These are things that may not be a formal access
488488
// base.
489+
//
490+
// FIXME: we don't seem to verify anywhere that a pointer_to_address cannot
491+
// itself be derived from another address that is accessible in the same
492+
// function, either because it was returned from a call or directly
493+
// address_to_pointer'd.
489494
if (isa<PointerToAddressInst>(address)) {
490495
return doesAddressHaveWriteThatInvalidatesLoadBorrow(lbi, endBorrowUses,
491496
address);
@@ -525,6 +530,9 @@ bool LoadBorrowNeverInvalidatedAnalysis::isNeverInvalidated(
525530
}
526531
case AccessedStorage::Yield: {
527532
// For now, do this. Otherwise, check for in_guaranteed, etc.
533+
//
534+
// FIXME: The yielded address could overlap with another address in this
535+
// function.
528536
return true;
529537
}
530538
case AccessedStorage::Box: {
@@ -534,9 +542,18 @@ bool LoadBorrowNeverInvalidatedAnalysis::isNeverInvalidated(
534542
case AccessedStorage::Class: {
535543
// Check that the write to the class's memory doesn't overlap with our
536544
// load_borrow.
545+
//
546+
// FIXME: how do we know that other projections of the same object don't
547+
// occur within the same function?
537548
return doesAddressHaveWriteThatInvalidatesLoadBorrow(lbi, endBorrowUses,
538549
address);
539550
}
551+
case AccessedStorage::Tail: {
552+
// This should be as strong as the Class address case, but to handle it we
553+
// need to find all aliases of the object and all tail projections within
554+
// that object.
555+
return false;
556+
}
540557
case AccessedStorage::Global: {
541558
// Check that the write to the class's memory doesn't overlap with our
542559
// load_borrow.

lib/SILOptimizer/Analysis/AccessedStorageAnalysis.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,17 @@ transformCalleeStorage(const StorageAccessInfo &storage,
230230
case AccessedStorage::Global:
231231
// Global accesses is universal.
232232
return storage;
233-
case AccessedStorage::Class: {
233+
case AccessedStorage::Class:
234+
case AccessedStorage::Tail: {
234235
// If the object's value is an argument, translate it into a value on the
235236
// caller side.
236237
SILValue obj = storage.getObject();
237238
if (auto *arg = dyn_cast<SILFunctionArgument>(obj)) {
238239
SILValue argVal = getCallerArg(fullApply, arg->getIndex());
239240
if (argVal) {
240-
unsigned idx = storage.getPropertyIndex();
241+
unsigned idx = (storage.getKind() == AccessedStorage::Class)
242+
? storage.getPropertyIndex()
243+
: AccessedStorage::TailIndex;
241244
// Remap this storage info. The argument source value is now the new
242245
// object. The old storage info is inherited.
243246
return StorageAccessInfo(AccessedStorage::forClass(argVal, idx),

lib/SILOptimizer/Transforms/AccessEnforcementOpts.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ void AccessConflictAndMergeAnalysis::visitMayRelease(SILInstruction *instr,
743743
// accesses can be affected by a deinitializer.
744744
auto isHeapAccess = [](AccessedStorage::Kind accessKind) {
745745
return accessKind == AccessedStorage::Class
746+
|| accessKind == AccessedStorage::Class
746747
|| accessKind == AccessedStorage::Global;
747748
};
748749
// Mark the in-scope accesses as having a nested conflict

lib/SILOptimizer/Transforms/AccessEnforcementWMO.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const VarDecl *getDisjointAccessLocation(const AccessedStorage &storage) {
8585
}
8686
case AccessedStorage::Box:
8787
case AccessedStorage::Stack:
88+
case AccessedStorage::Tail:
8889
case AccessedStorage::Argument:
8990
case AccessedStorage::Yield:
9091
case AccessedStorage::Unidentified:

test/SILOptimizer/accessed_storage.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ struct MyArray<T> {
123123

124124
// CHECK-LABEL: @arrayValue
125125
// CHECK: load [trivial] %{{.*}} : $*Builtin.Int64
126-
// CHECK: Unidentified %{{.*}} = ref_tail_addr [immutable] [[REF:%[0-9]+]] : $__ContiguousArrayStorageBase, $Int64
126+
// CHECK: Tail %{{.*}} = unchecked_ref_cast [[REF:%[0-9]+]] : $Builtin.BridgeObject to $__ContiguousArrayStorageBase
127127
// CHECK: load [trivial] %{{.*}} : $*Builtin.Int64
128-
// CHECK: Unidentified %{{.*}} = ref_tail_addr [immutable] [[REF]] : $__ContiguousArrayStorageBase, $Int64
128+
// CHECK: Tail %{{.*}} = unchecked_ref_cast [[REF:%[0-9]+]] : $Builtin.BridgeObject to $__ContiguousArrayStorageBase
129129
sil [ossa] @arrayValue : $@convention(thin) (@guaranteed MyArray<Int64>) -> Int64 {
130130
bb0(%0 : @guaranteed $MyArray<Int64>):
131131
%1 = integer_literal $Builtin.Word, 3

test/SILOptimizer/optimize_keypath.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ func testGetOptionalForceClass(_ s: SimpleClass) -> Int {
561561
// CHECK-LABEL: sil {{.*}}testGetComplex
562562
// opt
563563
// CHECK: [[E1:%[0-9]+]] = ref_element_addr
564-
// CHECK: [[B1:%[0-9]+]] = begin_access [read] [dynamic] [[E1]]
564+
// CHECK: [[B1:%[0-9]+]] = begin_access [read] [dynamic] [no_nested_conflict] [[E1]]
565565
// !
566566
// CHECK: [[F:%[0-9]+]] = select_enum [[O:%[0-9]+]]
567567
// CHECK: cond_fail [[F]]

0 commit comments

Comments
 (0)