Skip to content

Commit a12fe9a

Browse files
committed
[SIL Optimization] Fix a bug in the identification of dead constant
evaluable calls. The fix makes the check more conservative and assumes that calls with generic arguments are not dead, as generic functions with arbitrary side-effects can be invoked through them. Also, add a few helper functions to the InstructionDeleter utility that will enable deleting an instruction along with its users.
1 parent 4bc7837 commit a12fe9a

File tree

2 files changed

+101
-21
lines changed

2 files changed

+101
-21
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/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)