Skip to content

Commit 4d2debd

Browse files
committed
[MoveOnlyAddressChecker] Fix enum repr.
Change FieldSensitive's enum representation to allow distinguishing among the elements with associated value. Consider `unchecked_take_enum_data_addr` to consume all other fields than that taken. rdar://125113258
1 parent 53bffd2 commit 4d2debd

File tree

6 files changed

+159
-56
lines changed

6 files changed

+159
-56
lines changed

include/swift/SIL/FieldSensitivePrunedLiveness.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,19 @@ struct SubElementOffset {
228228
computeForValue(SILValue projectionFromRoot, SILValue rootValue);
229229
};
230230

231-
/// Given a type T, this is the number of leaf field types in T's type tree. A
232-
/// leaf field type is a descendent field of T that does not have any
233-
/// descendent's itself.
231+
/// Counts the leaf fields aggregated together into a particular type.
232+
///
233+
/// Defined in such a way as to enable walking up the tree of aggregations
234+
/// node-by-node, visiting each type along the way.
235+
///
236+
/// The definition is given recursively as follows:
237+
/// a an atom => count(a) := 1
238+
/// t a tuple => count(t) := sum(t.elements, { elt in count(type(elt)) })
239+
/// s a struct => count(s) := sum(s.fields, { f in count(type(f)) })
240+
/// + s.hasDeinit
241+
/// e an enum => count(e) := sum(e.elements, { elt in count(type(elt)) })
242+
/// + e.hasDeinit
243+
/// + 1 // discriminator
234244
struct TypeSubElementCount {
235245
unsigned number;
236246

@@ -308,7 +318,7 @@ struct TypeTreeLeafTypeRange {
308318
SmallVectorImpl<TypeTreeLeafTypeRange> &ranges);
309319

310320
static void constructProjectionsForNeededElements(
311-
SILValue rootValue, SILInstruction *insertPt,
321+
SILValue rootValue, SILInstruction *insertPt, DominanceInfo *domTree,
312322
SmallBitVector &neededElements,
313323
SmallVectorImpl<std::pair<SILValue, TypeTreeLeafTypeRange>>
314324
&resultingProjections);
@@ -386,6 +396,7 @@ struct TypeTreeLeafTypeRange {
386396
/// common with filterBitVector.
387397
void constructFilteredProjections(
388398
SILValue value, SILInstruction *insertPt, SmallBitVector &filterBitVector,
399+
DominanceInfo *domTree,
389400
llvm::function_ref<bool(SILValue, TypeTreeLeafTypeRange)> callback);
390401

391402
void print(llvm::raw_ostream &os) const {

lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,20 @@ TypeSubElementCount::TypeSubElementCount(SILType type, SILModule &mod,
8383
return;
8484
}
8585

86-
// If we have an enum, we add one for tracking if the base enum is set and use
87-
// the remaining bits for the max sized payload. This ensures that if we have
88-
// a smaller sized payload, we still get all of the bits set, allowing for a
89-
// homogeneous representation.
9086
if (auto *enumDecl = type.getEnumOrBoundGenericEnum()) {
9187
unsigned numElements = 0;
9288
for (auto *eltDecl : enumDecl->getAllElements()) {
9389
if (!eltDecl->hasAssociatedValues())
9490
continue;
9591
auto elt = type.getEnumElementType(eltDecl, mod, context);
96-
numElements = std::max(numElements,
97-
unsigned(TypeSubElementCount(elt, mod, context)));
92+
numElements += unsigned(TypeSubElementCount(elt, mod, context));
9893
}
9994
number = numElements + 1;
95+
if (type.isValueTypeWithDeinit()) {
96+
// 'self' has its own liveness represented as an additional field at the
97+
// end of the structure.
98+
++number;
99+
}
100100
return;
101101
}
102102

@@ -190,19 +190,20 @@ SubElementOffset::computeForAddress(SILValue projectionDerivedFromRoot,
190190
continue;
191191
}
192192

193-
// In the case of enums, we note that our representation is:
194-
//
195-
// ---------|Enum| ---
196-
// / \
197-
// / \
198-
// v v
199-
// |Bits for Max Sized Payload| |Discrim Bit|
200-
//
201-
// So our payload is always going to start at the current field number since
202-
// we are the left most child of our parent enum. So we just need to look
203-
// through to our parent enum.
204193
if (auto *enumData = dyn_cast<UncheckedTakeEnumDataAddrInst>(
205194
projectionDerivedFromRoot)) {
195+
auto ty = enumData->getOperand()->getType();
196+
auto *enumDecl = enumData->getEnumDecl();
197+
for (auto *element : enumDecl->getAllElements()) {
198+
if (!element->hasAssociatedValues())
199+
continue;
200+
if (element == enumData->getElement())
201+
break;
202+
auto context = TypeExpansionContext(*rootAddress->getFunction());
203+
auto elementTy = ty.getEnumElementType(element, mod, context);
204+
finalSubElementOffset +=
205+
unsigned(TypeSubElementCount(elementTy, mod, context));
206+
}
206207
projectionDerivedFromRoot = enumData->getOperand();
207208
continue;
208209
}
@@ -376,6 +377,7 @@ SubElementOffset::computeForValue(SILValue projectionDerivedFromRoot,
376377

377378
void TypeTreeLeafTypeRange::constructFilteredProjections(
378379
SILValue value, SILInstruction *insertPt, SmallBitVector &filterBitVector,
380+
DominanceInfo *domTree,
379381
llvm::function_ref<bool(SILValue, TypeTreeLeafTypeRange)> callback) {
380382
auto *fn = insertPt->getFunction();
381383
SILType type = value->getType();
@@ -419,27 +421,56 @@ void TypeTreeLeafTypeRange::constructFilteredProjections(
419421
return;
420422
}
421423

422-
// We only allow for enums that can be completely destroyed. If there is code
423-
// where an enum should be partially destroyed, we need to treat the
424-
// unchecked_take_enum_data_addr as a separate value whose liveness we are
425-
// tracking.
426424
if (auto *enumDecl = type.getEnumOrBoundGenericEnum()) {
425+
struct ElementRecord {
426+
EnumElementDecl *element;
427+
unsigned start;
428+
unsigned next;
429+
};
430+
SmallVector<ElementRecord, 2> projectedElements;
427431
unsigned start = startEltOffset;
428-
429-
unsigned maxSubEltCount = 0;
430432
for (auto *eltDecl : enumDecl->getAllElements()) {
431433
if (!eltDecl->hasAssociatedValues())
432434
continue;
435+
433436
auto nextType = type.getEnumElementType(eltDecl, fn);
434-
maxSubEltCount =
435-
std::max(maxSubEltCount, unsigned(TypeSubElementCount(nextType, fn)));
437+
unsigned next = start + TypeSubElementCount(nextType, fn);
438+
if (noneSet(filterBitVector, start, next)) {
439+
start = next;
440+
continue;
441+
}
442+
443+
projectedElements.push_back({eltDecl, start, next});
444+
start = next;
436445
}
437446

438-
// Add a bit for the case bit.
439-
unsigned next = maxSubEltCount + 1;
447+
// Add a bit for the discriminator.
448+
unsigned next = start + 1;
449+
450+
if (!allSet(filterBitVector, start, next)) {
451+
for (auto record : projectedElements) {
452+
// Find a preexisting unchecked_take_enum_data_addr that dominates
453+
// insertPt.
454+
bool foundProjection = false;
455+
for (auto *user : value->getUsers()) {
456+
auto *utedai = dyn_cast<UncheckedTakeEnumDataAddrInst>(user);
457+
if (!utedai) {
458+
continue;
459+
}
460+
if (utedai->getElement() != record.element) {
461+
continue;
462+
}
463+
if (!domTree->dominates(utedai, insertPt)) {
464+
continue;
465+
}
440466

441-
// Make sure we are all set.
442-
assert(allSet(filterBitVector, start, next));
467+
callback(utedai, TypeTreeLeafTypeRange(record.start, record.next));
468+
foundProjection = true;
469+
}
470+
// assert(foundProjection);
471+
}
472+
return;
473+
}
443474

444475
// Then just pass back our enum base value as the pointer.
445476
callback(value, TypeTreeLeafTypeRange(start, next));
@@ -491,17 +522,34 @@ void TypeTreeLeafTypeRange::get(
491522
// An `inject_enum_addr` only initializes the enum tag.
492523
if (auto inject = dyn_cast<InjectEnumAddrInst>(op->getUser())) {
493524
auto upperBound = *startEltOffset + TypeSubElementCount(projectedValue);
494-
unsigned payloadUpperBound = 0;
495-
if (inject->getElement()->hasAssociatedValues()) {
496-
auto payloadTy = projectedValue->getType().getEnumElementType(
497-
inject->getElement(), op->getFunction());
525+
// TODO: account for deinit component if enum has deinit.
526+
assert(!projectedValue->getType().isValueTypeWithDeinit());
527+
ranges.push_back({upperBound - 1, upperBound});
528+
return;
529+
}
498530

499-
payloadUpperBound =
500-
*startEltOffset + TypeSubElementCount(payloadTy, op->getFunction());
531+
if (auto *utedai = dyn_cast<UncheckedTakeEnumDataAddrInst>(op->getUser())) {
532+
auto *selected = utedai->getElement();
533+
auto *enumDecl = utedai->getEnumDecl();
534+
unsigned numAtoms = 0;
535+
for (auto *element : enumDecl->getAllElements()) {
536+
if (!element->hasAssociatedValues()) {
537+
continue;
538+
}
539+
auto elementTy = projectedValue->getType().getEnumElementType(
540+
element, op->getFunction());
541+
auto elementAtoms =
542+
unsigned(TypeSubElementCount(elementTy, op->getFunction()));
543+
if (element != selected) {
544+
ranges.push_back({*startEltOffset + numAtoms,
545+
*startEltOffset + numAtoms + elementAtoms});
546+
}
547+
numAtoms += elementAtoms;
501548
}
502549
// TODO: account for deinit component if enum has deinit.
503550
assert(!projectedValue->getType().isValueTypeWithDeinit());
504-
ranges.push_back({payloadUpperBound, upperBound});
551+
ranges.push_back(
552+
{*startEltOffset + numAtoms, *startEltOffset + numAtoms + 1});
505553
return;
506554
}
507555

@@ -521,7 +569,7 @@ void TypeTreeLeafTypeRange::get(
521569
}
522570

523571
void TypeTreeLeafTypeRange::constructProjectionsForNeededElements(
524-
SILValue rootValue, SILInstruction *insertPt,
572+
SILValue rootValue, SILInstruction *insertPt, DominanceInfo *domTree,
525573
SmallBitVector &neededElements,
526574
SmallVectorImpl<std::pair<SILValue, TypeTreeLeafTypeRange>>
527575
&resultingProjections) {
@@ -569,7 +617,7 @@ void TypeTreeLeafTypeRange::constructProjectionsForNeededElements(
569617
// recursively process those ranges looking for subranges that have
570618
// completely set bits.
571619
range.constructFilteredProjections(
572-
value, insertPt, neededElements,
620+
value, insertPt, neededElements, domTree,
573621
[&](SILValue subType, TypeTreeLeafTypeRange range) -> bool {
574622
worklist.push_back({subType, range});
575623
return true;

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ namespace {
505505
struct UseState {
506506
MarkUnresolvedNonCopyableValueInst *address;
507507

508+
DominanceInfo *const domTree;
509+
508510
// FIXME: [partial_consume_of_deiniting_aggregate_with_drop_deinit] Keep track
509511
// of the projections out of which a use emerges and use that to tell
510512
// whether a particular partial consume is allowed.
@@ -605,6 +607,8 @@ struct UseState {
605607
/// scopes.
606608
DebugValueInst *debugValue = nullptr;
607609

610+
UseState(DominanceInfo *domTree) : domTree(domTree) {}
611+
608612
SILFunction *getFunction() const { return address->getFunction(); }
609613

610614
/// The number of fields in the exploded type.
@@ -1426,8 +1430,6 @@ struct MoveOnlyAddressCheckerPImpl {
14261430

14271431
SILFunction *fn;
14281432

1429-
DominanceInfo *domTree;
1430-
14311433
/// A set of mark_unresolved_non_copyable_value that we are actually going to
14321434
/// process.
14331435
llvm::SmallSetVector<MarkUnresolvedNonCopyableValueInst *, 32>
@@ -1459,9 +1461,9 @@ struct MoveOnlyAddressCheckerPImpl {
14591461
SILFunction *fn, DiagnosticEmitter &diagnosticEmitter,
14601462
DominanceInfo *domTree, PostOrderAnalysis *poa,
14611463
borrowtodestructure::IntervalMapAllocator &allocator)
1462-
: fn(fn), domTree(domTree), deleter(),
1463-
canonicalizer(fn, domTree, deleter),
1464-
diagnosticEmitter(diagnosticEmitter), poa(poa), allocator(allocator) {
1464+
: fn(fn), deleter(), canonicalizer(fn, domTree, deleter),
1465+
addressUseState(domTree), diagnosticEmitter(diagnosticEmitter),
1466+
poa(poa), allocator(allocator) {
14651467
deleter.setCallbacks(std::move(
14661468
InstModCallbacks().onDelete([&](SILInstruction *instToDelete) {
14671469
if (auto *mvi =
@@ -1597,6 +1599,11 @@ shouldVisitAsEndPointUse(Operand *op) {
15971599
if (isa<DropDeinitInst>(op->getUser())) {
15981600
return TransitiveAddressWalkerTransitiveUseVisitation::BothUserAndUses;
15991601
}
1602+
// An unchecked_take_enum_data_addr consumes all bits except the remaining
1603+
// element's.
1604+
if (isa<UncheckedTakeEnumDataAddrInst>(op->getUser())) {
1605+
return TransitiveAddressWalkerTransitiveUseVisitation::BothUserAndUses;
1606+
}
16001607
return TransitiveAddressWalkerTransitiveUseVisitation::OnlyUses;
16011608
}
16021609

@@ -1762,6 +1769,7 @@ static std::optional<PartialMutationError>
17621769
shouldEmitPartialMutationError(UseState &useState, PartialMutation::Kind kind,
17631770
SILInstruction *user, SILType useType,
17641771
TypeTreeLeafTypeRange usedBits) {
1772+
// FIXME: FIXME_NOW: This needs to be in terms of a use operand, right???
17651773
SILFunction *fn = useState.getFunction();
17661774

17671775
// We walk down from our ancestor to our projection, emitting an error if
@@ -2662,6 +2670,20 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
26622670
}
26632671
}
26642672

2673+
if (auto *seai = dyn_cast<SwitchEnumAddrInst>(user)) {
2674+
SmallVector<TypeTreeLeafTypeRange, 2> leafRanges;
2675+
TypeTreeLeafTypeRange::get(op, getRootAddress(), leafRanges);
2676+
if (!leafRanges.size()) {
2677+
LLVM_DEBUG(llvm::dbgs() << "Failed to form leaf type range!\n");
2678+
return false;
2679+
}
2680+
for (auto leafRange : leafRanges) {
2681+
useState.recordLivenessUse(user, leafRange);
2682+
}
2683+
2684+
return true;
2685+
}
2686+
26652687
// If we don't fit into any of those categories, just track as a liveness
26662688
// use. We assume all such uses must only be reads to the memory. So we assert
26672689
// to be careful.
@@ -3019,7 +3041,8 @@ static void insertDestroyBeforeInstruction(UseState &addressUseState,
30193041
RegularLocation::getAutoGeneratedLocation(nextInstruction->getLoc());
30203042
SmallVector<std::pair<SILValue, TypeTreeLeafTypeRange>> valuesToDestroy;
30213043
TypeTreeLeafTypeRange::constructProjectionsForNeededElements(
3022-
baseAddress, nextInstruction, bv, valuesToDestroy);
3044+
baseAddress, nextInstruction, addressUseState.domTree, bv,
3045+
valuesToDestroy);
30233046
while (!valuesToDestroy.empty()) {
30243047
auto pair = valuesToDestroy.pop_back_val();
30253048
if (pair.first->getType().isTrivial(*nextInstruction->getFunction()))

lib/SILOptimizer/Mandatory/MoveOnlyTypeUtils.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,25 @@ TypeOffsetSizePair::walkOneLevelTowardsChild(
105105
// The only possible child of Optional is the wrapped type.
106106
return {{ancestorOffsetSize, ancestorType.getOptionalObjectType()}};
107107
}
108-
llvm_unreachable("Cannot find child type of enum!\n");
108+
unsigned elementOffset = ancestorOffsetSize.startOffset;
109+
for (auto *element : enumDecl->getAllElements()) {
110+
if (!element->hasAssociatedValues()) {
111+
continue;
112+
}
113+
SILType elementTy = ancestorType.getEnumElementType(element, fn);
114+
unsigned elementSize = TypeSubElementCount(elementTy, fn);
115+
// iterOffset + size(tupleChild) is the offset of the next tuple
116+
// element. If our target offset is less than that, then we know that
117+
// the target type must be a child of this tuple element type.
118+
if (elementOffset + elementSize > startOffset) {
119+
return {{{elementOffset, elementSize}, elementTy}};
120+
}
121+
122+
// Otherwise, add the new size of this field to iterOffset so we visit
123+
// our sibling type next.
124+
elementOffset += elementSize;
125+
}
126+
llvm_unreachable("Not a child of this enum?!");
109127
}
110128

111129
llvm_unreachable("Hit a leaf type?! Should have handled it earlier");

lib/SILOptimizer/Mandatory/MoveOnlyUtils.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ bool noncopyable::memInstMustConsume(Operand *memOper) {
318318
auto convention = applySite.getArgumentConvention(*memOper);
319319
return !convention.isInoutConvention();
320320
}
321+
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
322+
return true;
323+
}
321324
}
322325
}
323326

test/SILOptimizer/moveonly_generics_complex.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// RUN: %target-swift-frontend \
2-
// RUN: -emit-sil -verify \
3-
// RUN: %s \
4-
// RUN: -enable-experimental-feature BuiltinModule \
5-
// RUN: -enable-experimental-feature NoncopyableGenerics \
1+
// RUN: %target-swift-frontend \
2+
// RUN: -emit-sil -verify \
3+
// RUN: %s \
4+
// RUN: -enable-experimental-feature BuiltinModule \
5+
// RUN: -enable-experimental-feature NoncopyableGenerics \
6+
// RUN: -enable-experimental-feature MoveOnlyPartialConsumption \
67
// RUN: -sil-verify-all
78

89
// REQUIRES: asserts
@@ -62,8 +63,7 @@ func _withUnprotectedUnsafeTemporaryAllocation<T: ~Copyable, R: ~Copyable>(
6263
Builtin.stackDealloc(stackAddress)
6364
}
6465
switch consume result {
65-
// FIXME: This shouldn't be diagnosed. But it's better than miscompiling.
66-
case .success(let success): return success // expected-error{{cannot partially consume 'unknown'}}
66+
case .success(let success): return success
6767
case .failure(let error): return try _rethrowsViaClosure { throw error }
6868
}
6969
}

0 commit comments

Comments
 (0)