Skip to content
Closed
4 changes: 4 additions & 0 deletions include/swift/SIL/Projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ enum class ProjectionKind : unsigned {
Class = PointerIntEnumIndexKindValue<3, ProjectionKind>::value,
Enum = PointerIntEnumIndexKindValue<4, ProjectionKind>::value,
Box = PointerIntEnumIndexKindValue<5, ProjectionKind>::value,
Access = PointerIntEnumIndexKindValue<6, ProjectionKind>::value,
LastIndexKind = Enum,
};

Expand All @@ -129,6 +130,7 @@ static inline bool isCastProjectionKind(ProjectionKind Kind) {
case ProjectionKind::Enum:
case ProjectionKind::Box:
case ProjectionKind::TailElems:
case ProjectionKind::Access:
return false;
}
}
Expand Down Expand Up @@ -428,6 +430,7 @@ class Projection {
case ProjectionKind::TailElems:
case ProjectionKind::Enum:
case ProjectionKind::Box:
case ProjectionKind::Access:
return false;
}

Expand All @@ -439,6 +442,7 @@ class Projection {
case ProjectionKind::Class:
case ProjectionKind::Enum:
case ProjectionKind::Struct:
case ProjectionKind::Access:
return true;
case ProjectionKind::BitwiseCast:
case ProjectionKind::Index:
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/Utils/InstructionUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ SILValue swift::stripOwnershipInsts(SILValue v) {
return v;
case ValueKind::CopyValueInst:
case ValueKind::BeginBorrowInst:
case ValueKind::BeginAccessInst:
v = cast<SingleValueInstruction>(v)->getOperand(0);
}
}
Expand Down
15 changes: 15 additions & 0 deletions lib/SIL/Utils/Projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ Projection::Projection(SingleValueInstruction *I) : Value() {
assert(getKind() == ProjectionKind::BitwiseCast);
break;
}
case SILInstructionKind::BeginAccessInst: {
Value = ValueTy(ProjectionKind::Access, uintptr_t(0));
assert(getKind() == ProjectionKind::Access);
break;
}
}
}

Expand Down Expand Up @@ -196,6 +201,7 @@ SILType Projection::getType(SILType BaseType, SILModule &M,
case ProjectionKind::BitwiseCast:
case ProjectionKind::TailElems:
return getCastType(BaseType);
case ProjectionKind::Access:
case ProjectionKind::Index:
// Index types do not change the underlying type.
return BaseType;
Expand Down Expand Up @@ -237,6 +243,8 @@ Projection::createObjectProjection(SILBuilder &B, SILLocation Loc,
return B.createUncheckedRefCast(Loc, Base, getCastType(BaseTy));
case ProjectionKind::BitwiseCast:
return B.createUncheckedBitwiseCast(Loc, Base, getCastType(BaseTy));
case ProjectionKind::Access:
return nullptr;
}

llvm_unreachable("Unhandled ProjectionKind in switch.");
Expand Down Expand Up @@ -281,6 +289,8 @@ Projection::createAddressProjection(SILBuilder &B, SILLocation Loc,
case ProjectionKind::RefCast:
case ProjectionKind::BitwiseCast:
return B.createUncheckedAddrCast(Loc, Base, getCastType(BaseTy));
case ProjectionKind::Access:
return nullptr;
}

llvm_unreachable("Unhandled ProjectionKind in switch.");
Expand Down Expand Up @@ -835,6 +845,10 @@ SILValue Projection::getOperandForAggregate(SILInstruction *I) const {
}
}
break;
case ProjectionKind::Access:
if (auto access = dyn_cast<BeginAccessInst>(I))
return access->getOperand();
break;
case ProjectionKind::Class:
case ProjectionKind::TailElems:
case ProjectionKind::Box:
Expand Down Expand Up @@ -892,6 +906,7 @@ static bool isSupportedProjection(const Projection &p) {
switch (p.getKind()) {
case ProjectionKind::Struct:
case ProjectionKind::Tuple:
case ProjectionKind::Access:
return true;
case ProjectionKind::Class:
case ProjectionKind::Enum:
Expand Down
2 changes: 2 additions & 0 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ static void addLowLevelPassPipeline(SILPassPipelinePlan &P) {
P.addDeadObjectElimination();
P.addObjectOutliner();
P.addDeadStoreElimination();
P.addDCE();
P.addDeadObjectElimination();

// We've done a lot of optimizations on this function, attempt to FSO.
P.addFunctionSignatureOpts();
Expand Down
6 changes: 6 additions & 0 deletions lib/SILOptimizer/Transforms/DeadCodeElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ namespace {
// FIXME: Reconcile the similarities between this and
// isInstructionTriviallyDead.
static bool seemsUseful(SILInstruction *I) {
if (auto access = dyn_cast<BeginAccessInst>(I))
return access->getSingleUse() == nullptr && !access->use_empty();

if (isa<EndAccessInst>(I))
return I->getOperand(0)->getSingleUse() == nullptr;

if (I->mayHaveSideEffects())
return true;

Expand Down
24 changes: 24 additions & 0 deletions lib/SILOptimizer/Transforms/DeadStoreElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ static bool isDeadStoreInertInstruction(SILInstruction *Inst) {
case SILInstructionKind::DeallocRefInst:
case SILInstructionKind::CondFailInst:
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::EndAccessInst:
case SILInstructionKind::SetDeallocatingInst:
return true;
default:
return false;
Expand Down Expand Up @@ -1140,6 +1142,28 @@ void DSEContext::processInstruction(SILInstruction *I, DSEKind Kind) {
processStoreInst(I, Kind);
} else if (isa<DebugValueAddrInst>(I)) {
processDebugValueAddrInst(I, Kind);
} else if (isa<BeginAccessInst>(I)) {
// Do the same thing here as in RLE. Begin access writes/reads memory only
// if one of its users writes/reads memory. We will look at all of its users
// and we can project to the source memory location so, if there are any
// actual writes/reads of memory we will catch them there so, we can ignore
// begin_access here.
return;
} else if (auto *release = dyn_cast<StrongReleaseInst>(I)) {
// If the strong release operand type can't have a custom destructor it's
// OK.
if (release->getOperand()->getType().getClassOrBoundGenericClass() ==
nullptr)
return;
// For strong releases, we have to prove that the strong release won't
// envoke the destructor. For now, we just try to find a set_deallocating
// instruction that indicates the life-ending strong_release has been
// devirtualized. TODO: there should be a better way to do this.
for (auto *use : release->getOperand()->getUses()) {
if (isa<SetDeallocatingInst>(use->getUser()))
return;
}
processUnknownReadInst(I, Kind);
} else if (I->mayReadFromMemory()) {
processUnknownReadInst(I, Kind);
}
Expand Down
15 changes: 15 additions & 0 deletions lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ static bool isRLEInertInstruction(SILInstruction *Inst) {
case SILInstructionKind::IsEscapingClosureInst:
case SILInstructionKind::IsUniqueInst:
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::EndAccessInst:
case SILInstructionKind::SetDeallocatingInst:
case SILInstructionKind::DeallocRefInst:
return true;
default:
return false;
Expand Down Expand Up @@ -1089,6 +1092,18 @@ void BlockState::processInstructionWithKind(RLEContext &Ctx,
return;
}

// Begin access writes to memory only if one of its users writes to memory.
// We will look at all of its users and we can project to the source memory
// location so, if there are any actual writes to memory we will catch them
// there so, we can ignore begin_access here.
if (isa<BeginAccessInst>(Inst))
return;

// If the load is valid, then this strong release won't invoke the destructor.
// So we can ignore it.
if (isa<StrongReleaseInst>(Inst))
return;

// If this instruction has side effects, but is inert from a load store
// perspective, skip it.
if (isRLEInertInstruction(Inst))
Expand Down
18 changes: 18 additions & 0 deletions test/SILOptimizer/dead_code_elimination.sil
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,21 @@ bb2:
bb3:
br bb1
}

// Check that DCE eliminates dead access instructions.
// CHECK-LABEL: sil @dead_access
// CHECK: bb0
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK-LABEL: end sil function 'dead_access'
sil @dead_access : $@convention(thin) (@in Container) -> () {
bb0(%0 : $*Container):
%1 = begin_access [modify] [dynamic] %0 : $*Container
end_access %1 : $*Container

%3 = begin_access [read] [static] %0 : $*Container
end_access %3 : $*Container

%999 = tuple ()
return %999 : $()
}
22 changes: 22 additions & 0 deletions test/SILOptimizer/dead_store_elim.sil
Original file line number Diff line number Diff line change
Expand Up @@ -1501,3 +1501,25 @@ bb0(%0 : $*foo):
%20 = tuple()
return %20 : $()
}

// Check that begin_access, end_access, strong_release, set_deallocating, and dealloc_ref don't prevent optimization.
// CHECK-LABEL: ignore_read_write
// CHECK: bb0
// CHECK-NOT: store
// CHECK-LABEL: end sil function 'ignore_read_write'
sil @ignore_read_write : $@convention(thin) () -> Int {
bb0:
%1 = alloc_ref [stack] $foo
strong_retain %1 : $foo
%2 = integer_literal $Builtin.Int64, 0
%3 = struct $Int (%2 : $Builtin.Int64)
%4 = ref_element_addr %1 : $foo, #foo.a
%5 = begin_access [modify] [dynamic] [no_nested_conflict] %4 : $*Int
store %3 to %5 : $*Int
end_access %5 : $*Int
strong_release %1 : $foo
set_deallocating %1 : $foo
dealloc_ref %1 : $foo
dealloc_ref [stack] %1 : $foo
return %3 : $Int
}
21 changes: 21 additions & 0 deletions test/SILOptimizer/devirt_release.sil
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ bb0:
return %r : $()
}

// CHECK-LABEL: sil @devirtualize_release_multiblock
// CHECK-NOT: strong_release
// CHECK: [[A:%[0-9]+]] = alloc_ref
// CHECK-NEXT: set_deallocating [[A]]
// CHECK: [[D:%[0-9]+]] = function_ref @$s4test1BCfD
// CHECK-NEXT: apply [[D]]([[A]])
// CHECK-NEXT: br
// CHECK: bb1:
// CHECK-NEXT: dealloc_ref [stack] [[A]]
// CHECK: return
sil @devirtualize_release_multiblock : $@convention(thin) () -> () {
bb0:
%1 = alloc_ref [stack] $B
strong_release %1 : $B
br bb1

bb1:
dealloc_ref [stack] %1 : $B
%r = tuple ()
return %r : $()
}

sil @unknown_func : $@convention(thin) () -> ()

Expand Down