Skip to content

Commit 23e2b70

Browse files
Merge pull request swiftlang#29447 from ravikandhadai/oslog-dead-code-elim-alloc-stack
[SIL Optimization] Improve dead-code in OSLogOptimization pass, fix a bug and add helper functions to the new InstructionDeleter utility
2 parents 4bc7837 + 52f274a commit 23e2b70

File tree

5 files changed

+316
-39
lines changed

5 files changed

+316
-39
lines changed

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ class InstructionDeleter {
7979
/// up.
8080
void trackIfDead(SILInstruction *inst);
8181

82+
/// If the instruction \p inst is dead, delete it immediately and record
83+
/// its operands so that they can be cleaned up later.
84+
void deleteIfDead(
85+
SILInstruction *inst,
86+
llvm::function_ref<void(SILInstruction *)> callback =
87+
[](SILInstruction *) {});
88+
8289
/// Delete the instruction \p inst and record instructions that may become
8390
/// dead because of the removal of \c inst. This function will add necessary
8491
/// ownership instructions to fix the lifetimes of the operands of \c inst to
@@ -130,6 +137,14 @@ class InstructionDeleter {
130137
void
131138
cleanUpDeadInstructions(llvm::function_ref<void(SILInstruction *)> callback =
132139
[](SILInstruction *) {});
140+
141+
/// Recursively visit users of \c inst (including \c inst)and delete
142+
/// instructions that are dead (including \c inst). Invoke the \c callback on
143+
/// instructions that are deleted.
144+
void recursivelyDeleteUsersIfDead(
145+
SILInstruction *inst,
146+
llvm::function_ref<void(SILInstruction *)> callback =
147+
[](SILInstruction *) {});
133148
};
134149

135150
/// If \c inst is dead, delete it and recursively eliminate all code that
@@ -147,6 +162,9 @@ void eliminateDeadInstruction(
147162
SILInstruction *inst, llvm::function_ref<void(SILInstruction *)> callback =
148163
[](SILInstruction *) {});
149164

165+
/// Return the number of @inout arguments passed to the given apply site.
166+
unsigned getNumInOutArguments(FullApplySite applySite);
167+
150168
/// For each of the given instructions, if they are dead delete them
151169
/// along with their dead operands. Note this utility must be phased out and
152170
/// replaced by \c eliminateDeadInstruction and

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 139 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,31 +1059,152 @@ static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
10591059
return errorDetected;
10601060
}
10611061

1062+
using CallbackTy = llvm::function_ref<void(SILInstruction *)>;
1063+
1064+
/// Return true iff the given address-valued instruction has only stores into
1065+
/// it. This function tests for the conditions under which a call, that was
1066+
/// constant evaluated, that writes into the address-valued instruction can be
1067+
/// considered as a point store and exploits it to remove such uses.
1068+
/// TODO: eventually some of this logic can be moved to
1069+
/// PredictableDeadAllocElimination pass, but the assumption about constant
1070+
/// evaluable functions taking inout parameters is not easily generalizable to
1071+
/// arbitrary non-constant contexts where the function could be used. The logic
1072+
/// here is relying on the fact that the constant_evaluable function has been
1073+
/// evaluated and therefore doesn't have any side-effects.
1074+
static bool hasOnlyStoreUses(SingleValueInstruction *addressInst) {
1075+
for (Operand *use : addressInst->getUses()) {
1076+
SILInstruction *user = use->getUser();
1077+
switch (user->getKind()) {
1078+
default:
1079+
return false;
1080+
case SILInstructionKind::BeginAccessInst: {
1081+
if (!hasOnlyStoreUses(cast<BeginAccessInst>(user)))
1082+
return false;
1083+
continue;
1084+
}
1085+
case SILInstructionKind::StoreInst: {
1086+
// For now, ignore assigns as we need to destroy_addr its dest if it
1087+
// is deleted.
1088+
if (cast<StoreInst>(user)->getOwnershipQualifier() ==
1089+
StoreOwnershipQualifier::Assign)
1090+
return false;
1091+
continue;
1092+
}
1093+
case SILInstructionKind::EndAccessInst:
1094+
case SILInstructionKind::DestroyAddrInst:
1095+
case SILInstructionKind::InjectEnumAddrInst:
1096+
case SILInstructionKind::DeallocStackInst:
1097+
continue;
1098+
case SILInstructionKind::ApplyInst: {
1099+
ApplyInst *apply = cast<ApplyInst>(user);
1100+
SILFunction *callee = apply->getCalleeFunction();
1101+
if (!callee || !isConstantEvaluable(callee) || !apply->use_empty())
1102+
return false;
1103+
// Note that since we are looking at an alloc_stack used to produce the
1104+
// OSLogMessage instance, this constant_evaluable call should have been
1105+
// evaluated successfully by the evaluator. Otherwise, we would have
1106+
// reported an error earlier. Therefore, all values manipulated by such
1107+
// a call are symbolic constants and the call would not have any global
1108+
// side effects. The following logic relies on this property.
1109+
// If there are other indirect writable results for the call other than
1110+
// the alloc_stack we are checking, it may not be dead. Therefore, bail
1111+
// out.
1112+
FullApplySite applySite(apply);
1113+
unsigned numWritableArguments =
1114+
getNumInOutArguments(applySite) + applySite.getNumIndirectSILResults();
1115+
if (numWritableArguments > 1)
1116+
return false;
1117+
SILArgumentConvention convention = applySite.getArgumentConvention(*use);
1118+
if (convention == SILArgumentConvention::Indirect_In_Guaranteed ||
1119+
convention == SILArgumentConvention::Indirect_In_Constant ||
1120+
convention == SILArgumentConvention::Indirect_In_Guaranteed) {
1121+
if (numWritableArguments > 0)
1122+
return false;
1123+
}
1124+
// Here, either there are no writable parameters or the alloc_stack
1125+
// is the only writable parameter.
1126+
continue;
1127+
}
1128+
}
1129+
}
1130+
return true;
1131+
}
1132+
1133+
/// Delete the given alloc_stack instruction by deleting the users of the
1134+
/// instruction. In case the user is a begin_apply, recursively delete the users
1135+
/// of begin_apply. This will also fix the lifetimes of the deleted instructions
1136+
/// whenever possible.
1137+
static void forceDeleteAllocStack(SingleValueInstruction *inst,
1138+
InstructionDeleter &deleter,
1139+
CallbackTy callback) {
1140+
SmallVector<SILInstruction *, 8> users;
1141+
for (Operand *use : inst->getUses())
1142+
users.push_back(use->getUser());
1143+
1144+
for (SILInstruction *user : users) {
1145+
if (isIncidentalUse(user))
1146+
continue;
1147+
if (isa<DestroyAddrInst>(user)) {
1148+
deleter.forceDelete(user, callback);
1149+
continue;
1150+
}
1151+
if (isa<BeginAccessInst>(user)) {
1152+
forceDeleteAllocStack(cast<BeginAccessInst>(user), deleter, callback);
1153+
continue;
1154+
}
1155+
deleter.forceDeleteAndFixLifetimes(user, callback);
1156+
}
1157+
deleter.forceDelete(inst, callback);
1158+
}
1159+
1160+
/// Delete \c inst , if it is dead, along with its dead users and invoke the
1161+
/// callback whever an instruction is deleted.
1162+
static void deleteInstructionWithUsersAndFixLifetimes(
1163+
SILInstruction *inst, InstructionDeleter &deleter, CallbackTy callback) {
1164+
// If this is an alloc_stack, it can be eliminated as long as it is only
1165+
// stored into or destroyed.
1166+
if (AllocStackInst *allocStack = dyn_cast<AllocStackInst>(inst)) {
1167+
if (hasOnlyStoreUses(allocStack))
1168+
forceDeleteAllocStack(allocStack, deleter, callback);
1169+
return;
1170+
}
1171+
deleter.recursivelyDeleteUsersIfDead(inst, callback);
1172+
}
1173+
10621174
/// Try to dead-code eliminate the OSLogMessage instance \c oslogMessage passed
10631175
/// to the os log call and clean up its dependencies. If the instance cannot be
10641176
/// eliminated, it implies that either the instance is not auto-generated or the
10651177
/// implementation of the os log overlay is incorrect. Therefore emit
10661178
/// diagnostics in such cases.
10671179
static void tryEliminateOSLogMessage(SingleValueInstruction *oslogMessage) {
1068-
// Collect the set of root instructions that could be dead due to constant
1069-
// folding. These include the oslogMessage initialzer call and its transitive
1070-
// users.
1071-
SmallVector<SILInstruction *, 8> oslogMessageUsers;
1072-
getTransitiveUsers(oslogMessage, oslogMessageUsers);
1073-
10741180
InstructionDeleter deleter;
1075-
for (SILInstruction *user : oslogMessageUsers)
1076-
deleter.trackIfDead(user);
1077-
deleter.trackIfDead(oslogMessage);
1078-
1079-
bool isOSLogMessageDead = false;
1080-
deleter.cleanUpDeadInstructions([&](SILInstruction *deadInst) {
1081-
if (deadInst == oslogMessage)
1082-
isOSLogMessageDead = true;
1083-
});
1084-
// At this point, the OSLogMessage instance must be deleted if
1085-
// the overlay implementation (or its extensions by users) is correct.
1086-
if (!isOSLogMessageDead) {
1181+
// List of instructions that are possibly dead.
1182+
SmallVector<SILInstruction *, 4> worklist = {oslogMessage};
1183+
// Set of all deleted instructions.
1184+
SmallPtrSet<SILInstruction *, 4> deletedInstructions;
1185+
unsigned startIndex = 0;
1186+
while (startIndex < worklist.size()) {
1187+
SILInstruction *inst = worklist[startIndex++];
1188+
if (deletedInstructions.count(inst))
1189+
continue;
1190+
deleteInstructionWithUsersAndFixLifetimes(
1191+
inst, deleter, [&](SILInstruction *deadInst) {
1192+
// Add operands of all deleted instructions to the worklist so that
1193+
// they can be recursively deleted if possible.
1194+
for (Operand &operand : deadInst->getAllOperands()) {
1195+
if (SILInstruction *definingInstruction =
1196+
operand.get()->getDefiningInstruction()) {
1197+
if (!deletedInstructions.count(definingInstruction))
1198+
worklist.push_back(definingInstruction);
1199+
}
1200+
}
1201+
(void)deletedInstructions.insert(deadInst);
1202+
});
1203+
}
1204+
deleter.cleanUpDeadInstructions();
1205+
// If the OSLogMessage instance is not deleted, the overlay implementation
1206+
// (or its extensions by users) is incorrect.
1207+
if (!deletedInstructions.count(oslogMessage)) {
10871208
SILFunction *fun = oslogMessage->getFunction();
10881209
diagnose(fun->getASTContext(), oslogMessage->getLoc().getSourceLoc(),
10891210
diag::oslog_message_alive_after_opts);

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -189,21 +189,53 @@ static bool hasOnlyEndOfScopeOrDestroyUses(SILInstruction *inst) {
189189
return true;
190190
}
191191

192-
/// Return true iff the \p applySite calls a constant evaluable function and if
193-
/// it is read-only which implies the following:
194-
/// (1) The call does not write into any memory location.
192+
unsigned swift::getNumInOutArguments(FullApplySite applySite) {
193+
assert(applySite);
194+
auto substConv = applySite.getSubstCalleeConv();
195+
unsigned numIndirectResults = substConv.getNumIndirectSILResults();
196+
unsigned numInOutArguments = 0;
197+
for (unsigned argIndex = 0; argIndex < applySite.getNumArguments();
198+
argIndex++) {
199+
// Skip indirect results.
200+
if (argIndex < numIndirectResults) {
201+
continue;
202+
}
203+
auto paramNumber = argIndex - numIndirectResults;
204+
auto ParamConvention =
205+
substConv.getParameters()[paramNumber].getConvention();
206+
switch (ParamConvention) {
207+
case ParameterConvention::Indirect_Inout:
208+
case ParameterConvention::Indirect_InoutAliasable: {
209+
numInOutArguments++;
210+
break;
211+
default:
212+
break;
213+
}
214+
}
215+
}
216+
return numInOutArguments;
217+
}
218+
219+
/// Return true iff the \p applySite calls a constant-evaluable function and
220+
/// it is non-generic and read/destroy only, which means that the call can do
221+
/// only the following and nothing else:
222+
/// (1) The call may read any memory location.
195223
/// (2) The call may destroy owned parameters i.e., consume them.
196-
/// (3) The call does not throw or exit the program.
197-
static bool isReadOnlyConstantEvaluableCall(FullApplySite applySite) {
224+
/// (3) The call may write into memory locations newly created by the call.
225+
/// (4) The call may use assertions, which traps at runtime on failure.
226+
/// (5) The call may return a non-generic value.
227+
/// Essentially, these are calls whose "effect" is visible only in their return
228+
/// value or through the parameters that are destroyed. The return value
229+
/// is also guaranteed to have value semantics as it is non-generic and
230+
/// reference semantics is not constant evaluable.
231+
static bool isNonGenericReadOnlyConstantEvaluableCall(FullApplySite applySite) {
198232
assert(applySite);
199233
SILFunction *callee = applySite.getCalleeFunction();
200234
if (!callee || !isConstantEvaluable(callee)) {
201235
return false;
202236
}
203-
// Here all effects of the call is restricted to its indirect results, which
204-
// must have value semantics. If there are no indirect results, the call must
205-
// be read-only, except for consuming its operands.
206-
return applySite.getNumIndirectSILResults() == 0;
237+
return !applySite.hasSubstitutions() && !getNumInOutArguments(applySite) &&
238+
!applySite.getNumIndirectSILResults();
207239
}
208240

209241
/// A scope-affecting instruction is an instruction which may end the scope of
@@ -270,23 +302,25 @@ static bool isScopeAffectingInstructionDead(SILInstruction *inst) {
270302
return true;
271303
}
272304
case SILInstructionKind::ApplyInst: {
273-
// The following property holds for constant evaluable functions:
305+
// The following property holds for constant-evaluable functions that do
306+
// not take arguments of generic type:
274307
// 1. they do not create objects having deinitializers with global
308+
// side effects, as they can only create objects consisting of trivial
309+
// values, (non-generic) arrays and strings.
310+
// 2. they do not use global variables or call arbitrary functions with
275311
// side effects.
276-
// 2. they do not use global variables and will only use objects reachable
277-
// from parameters.
278312
// The above two properties imply that a value returned by a constant
279-
// evaluable function either does not have a deinitializer with global side
280-
// effects, or if it does, the deinitializer that has the global side effect
281-
// must be that of a parameter.
313+
// evaluable function does not have a deinitializer with global side
314+
// effects. Therefore, the deinitializer can be sinked.
282315
//
283-
// A read-only constant evaluable call only reads and/or destroys its
284-
// parameters. Therefore, if its return value is used only in destroys, the
285-
// constant evaluable call can be removed provided the parameters it
286-
// consumes are explicitly destroyed at the call site, which is taken care
287-
// of by the function: \c deleteInstruction
316+
// A generic, read-only constant evaluable call only reads and/or
317+
// destroys its (non-generic) parameters. It therefore cannot have any
318+
// side effects (note that parameters being non-generic have value
319+
// semantics). Therefore, the constant evaluable call can be removed
320+
// provided the parameter lifetimes are handled correctly, which is taken
321+
// care of by the function: \c deleteInstruction.
288322
FullApplySite applySite(cast<ApplyInst>(inst));
289-
return isReadOnlyConstantEvaluableCall(applySite);
323+
return isNonGenericReadOnlyConstantEvaluableCall(applySite);
290324
}
291325
default: {
292326
return false;
@@ -318,9 +352,14 @@ static void destroyConsumedOperandOfDeadInst(Operand &operand) {
318352
SILValue operandValue = operand.get();
319353
if (operandValue->getType().isTrivial(*fun))
320354
return;
355+
// Ignore type-dependent operands which are not real operands but are just
356+
// there to create use-def dependencies.
357+
if (deadInst->isTypeDependentOperand(operand))
358+
return;
321359
// A scope ending instruction cannot be deleted in isolation without removing
322360
// the instruction defining its operand as well.
323361
assert(!isEndOfScopeMarker(deadInst) && !isa<DestroyValueInst>(deadInst) &&
362+
!isa<DestroyAddrInst>(deadInst) &&
324363
"lifetime ending instruction is deleted without its operand");
325364
ValueOwnershipKind operandOwnershipKind = operandValue.getOwnershipKind();
326365
UseLifetimeConstraint lifetimeConstraint =
@@ -379,7 +418,10 @@ void InstructionDeleter::deleteInstruction(SILInstruction *inst,
379418
// First drop all references from all instructions to be deleted and then
380419
// erase the instruction. Note that this is done in this order so that when an
381420
// instruction is deleted, its uses would have dropped their references.
421+
// Note that the toDeleteInsts must also be removed from the tracked
422+
// deadInstructions.
382423
for (SILInstruction *inst : toDeleteInsts) {
424+
deadInstructions.remove(inst);
383425
inst->dropAllReferences();
384426
}
385427
for (SILInstruction *inst : toDeleteInsts) {
@@ -428,6 +470,14 @@ static bool hasOnlyIncidentalUses(SILInstruction *inst,
428470
return true;
429471
}
430472

473+
void InstructionDeleter::deleteIfDead(SILInstruction *inst,
474+
CallbackTy callback) {
475+
if (isInstructionTriviallyDead(inst) ||
476+
isScopeAffectingInstructionDead(inst)) {
477+
deleteInstruction(inst, callback, /*Fix lifetime of operands*/ true);
478+
}
479+
}
480+
431481
void InstructionDeleter::forceDeleteAndFixLifetimes(SILInstruction *inst,
432482
CallbackTy callback) {
433483
SILFunction *fun = inst->getFunction();
@@ -447,6 +497,18 @@ void InstructionDeleter::forceDelete(SILInstruction *inst,
447497
deleteInstruction(inst, callback, /*Fix lifetime of operands*/ false);
448498
}
449499

500+
void InstructionDeleter::recursivelyDeleteUsersIfDead(SILInstruction *inst,
501+
CallbackTy callback) {
502+
SmallVector<SILInstruction *, 8> users;
503+
for (SILValue result : inst->getResults())
504+
for (Operand *use : result->getUses())
505+
users.push_back(use->getUser());
506+
507+
for (SILInstruction *user : users)
508+
recursivelyDeleteUsersIfDead(user, callback);
509+
deleteIfDead(inst, callback);
510+
}
511+
450512
void swift::eliminateDeadInstruction(SILInstruction *inst,
451513
CallbackTy callback) {
452514
InstructionDeleter deleter;

0 commit comments

Comments
 (0)