Skip to content

Commit e854689

Browse files
committed
WIP: Dead interpolation elimination improvements
1 parent d8a119f commit e854689

File tree

3 files changed

+64
-4
lines changed

3 files changed

+64
-4
lines changed

lib/SILOptimizer/Transforms/DeadObjectElimination.cpp

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,37 @@ removeInstructions(ArrayRef<SILInstruction*> UsersToRemove) {
191191
// Use Graph Analysis
192192
//===----------------------------------------------------------------------===//
193193

194+
static bool mutatesOnlyExpectedSelf(ApplyInst *Apply, SILInstruction *ExpectedSelf) {
195+
if (!Apply->hasSemantics("interpolation.selfEffectsOnly"))
196+
return false;
197+
assert(Apply->hasSelfArgument() && "interpolation.selfEffectsOnly on non-method");
198+
199+
SILInstruction *ActualSelf = Apply->getSelfArgument()->getDefiningInstruction();
200+
return ActualSelf == ExpectedSelf;
201+
}
202+
203+
// FIXME: This is blatantly awful and needs to be better.
204+
static bool isStringObjectReleaseLoad(LoadInst *Load) {
205+
// Are we dereferencing a pointer stored in _StringGuts._object?
206+
if (auto *StructLookup = dyn_cast<StructElementAddrInst>(Load->getOperand()->getDefiningInstruction())) {
207+
if (StructLookup->getField()->getName().is("_object") && StructLookup->getStructDecl()->getName().is("_StringObject")) {
208+
209+
// Are the only uses strong_release?
210+
for (auto Use : Load->getUses()) {
211+
if (!isa<StrongReleaseInst>(Use->getUser()))
212+
return false;
213+
}
214+
return true;
215+
}
216+
}
217+
218+
return false;
219+
}
220+
194221
/// Returns false if Inst is an instruction that would require us to keep the
195222
/// alloc_ref alive.
196-
static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts) {
223+
static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts,
224+
SILInstruction *AllocInst) {
197225
if (isa<SetDeallocatingInst>(Inst) || isa<FixLifetimeInst>(Inst))
198226
return true;
199227

@@ -224,6 +252,16 @@ static bool canZapInstruction(SILInstruction *Inst, bool acceptRefCountInsts) {
224252
if (isa<DestroyAddrInst>(Inst))
225253
return true;
226254

255+
// We can remove applies of certain interpolation-related methods.
256+
if (auto *Apply = dyn_cast<ApplyInst>(Inst))
257+
if (mutatesOnlyExpectedSelf(Apply, AllocInst))
258+
return true;
259+
260+
// FIXME: Necessary for string interpolation, but is it correct?
261+
if (auto *Load = dyn_cast<LoadInst>(Inst))
262+
if (isStringObjectReleaseLoad(Load))
263+
return true;
264+
227265
// Otherwise we do not know how to handle this instruction. Be conservative
228266
// and don't zap it.
229267
return false;
@@ -252,7 +290,7 @@ hasUnremovableUsers(SILInstruction *AllocRef, UserList &Users,
252290
}
253291

254292
// If we can't zap this instruction... bail...
255-
if (!canZapInstruction(I, acceptRefCountInsts)) {
293+
if (!canZapInstruction(I, acceptRefCountInsts, AllocRef)) {
256294
LLVM_DEBUG(llvm::dbgs() << " Found instruction we can't zap...\n");
257295
return true;
258296
}
@@ -715,10 +753,24 @@ bool DeadObjectElimination::processAllocRef(AllocRefInst *ARI) {
715753
return true;
716754
}
717755

756+
static bool isDefaultStringInterpolation(SILType silTy) {
757+
auto astTy = silTy.getASTType();
758+
if (!astTy) return false;
759+
760+
auto nomTy = astTy.getNominalOrBoundGenericNominal();
761+
if (!nomTy) return false;
762+
763+
// FIXME: Do this in a non-horrible way.
764+
return nomTy->getName().is("DefaultStringInterpolation");
765+
}
766+
718767
bool DeadObjectElimination::processAllocStack(AllocStackInst *ASI) {
719768
// Trivial types don't have destructors. Let's try to zap this AllocStackInst.
720-
if (!ASI->getElementType().isTrivial(ASI->getModule()))
769+
if (!ASI->getElementType().isTrivial(ASI->getModule()) &&
770+
!isDefaultStringInterpolation(ASI->getElementType())) {
771+
LLVM_DEBUG(llvm::dbgs() << " Skipping due to non-trivial type:" << *ASI);
721772
return false;
773+
}
722774

723775
UserList UsersToRemove;
724776
if (hasUnremovableUsers(ASI, UsersToRemove, /*acceptRefCountInsts=*/ true)) {

lib/SILOptimizer/Utils/PerformanceInlinerUtils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,8 @@ static bool shouldSkipApplyDuringEarlyInlining(FullApplySite AI) {
611611
return false;
612612

613613
if (Callee->hasSemanticsAttr("self_no_escaping_closure") ||
614-
Callee->hasSemanticsAttr("pair_no_escaping_closure"))
614+
Callee->hasSemanticsAttr("pair_no_escaping_closure") ||
615+
Callee->hasSemanticsAttr("interpolation.selfEffectsOnly"))
615616
return true;
616617

617618
// Add here the checks for any specific @_effects attributes that need

stdlib/public/core/StringInterpolation.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol {
8383
/// Do not call this method directly. It is used by the compiler when
8484
/// interpreting string interpolations.
8585
@inlinable
86+
@_semantics("interpolation.selfEffectsOnly")
8687
public mutating func appendLiteral(_ literal: String) {
8788
literal.write(to: &self)
8889
}
@@ -105,6 +106,7 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol {
105106
/// print(message)
106107
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
107108
@inlinable
109+
@_semantics("interpolation.selfEffectsOnly")
108110
public mutating func appendInterpolation<T>(_ value: T)
109111
where T: TextOutputStreamable, T: CustomStringConvertible
110112
{
@@ -127,6 +129,7 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol {
127129
/// print(message)
128130
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
129131
@inlinable
132+
@_semantics("interpolation.selfEffectsOnly")
130133
public mutating func appendInterpolation<T>(_ value: T)
131134
where T: TextOutputStreamable
132135
{
@@ -151,6 +154,7 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol {
151154
/// print(message)
152155
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
153156
@inlinable
157+
@_semantics("interpolation.selfEffectsOnly")
154158
public mutating func appendInterpolation<T>(_ value: T)
155159
where T: CustomStringConvertible
156160
{
@@ -175,6 +179,7 @@ public struct DefaultStringInterpolation: StringInterpolationProtocol {
175179
/// print(message)
176180
/// // Prints "If one cookie costs 2 dollars, 3 cookies cost 6 dollars."
177181
@inlinable
182+
@_semantics("interpolation.selfEffectsOnly")
178183
public mutating func appendInterpolation<T>(_ value: T) {
179184
_print_unlocked(value, &self)
180185
}
@@ -196,13 +201,15 @@ extension DefaultStringInterpolation: CustomStringConvertible {
196201

197202
extension DefaultStringInterpolation: TextOutputStream {
198203
@inlinable
204+
@_semantics("interpolation.selfEffectsOnly")
199205
public mutating func write(_ string: String) {
200206
// Most interpolations will not append to an empty string, so we bypass the
201207
// empty-singleton check.
202208
_storage._guts._appendSlow(string._guts)
203209
}
204210

205211
@inlinable
212+
@_semantics("interpolation.selfEffectsOnly")
206213
public mutating func _writeASCII(_ buffer: UnsafeBufferPointer<UInt8>) {
207214
_storage._guts.append(_UnmanagedString(buffer))
208215
}

0 commit comments

Comments
 (0)