Skip to content

Commit 52f274a

Browse files
committed
[OSLogOptimization] Add support for eliminating dead alloc stacks
using the new InstructionDeleter utility. The elimination logic exploits the fact that constant_evaluable function calls have been evaluated and folded, in order to remove such calls that may possibly take @inout parameters.
1 parent a12fe9a commit 52f274a

File tree

3 files changed

+215
-18
lines changed

3 files changed

+215
-18
lines changed

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);

test/SILOptimizer/OSLogPrototypeCompileTest.sil

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,3 +965,78 @@ bb0:
965965
// CHECK-NOT: OSLogMessageDCEStub
966966
// CHECK: return
967967
}
968+
969+
// Check dead-code elimination of alloc stack used only as inout parameter of a
970+
// constant evaluable call (that only writes into that inout parameter).
971+
sil [ossa] [_semantics "constant_evaluable"] @appendInterpolationStub : $@convention(thin) (@inout OSLogInterpolationDCEStub) -> () {
972+
bb0(%0 : $*OSLogInterpolationDCEStub):
973+
%9999 = tuple()
974+
return %9999 : $()
975+
}
976+
977+
// CHECK-LABEL: @testDCEOfAllocStack
978+
sil [ossa] @testDCEOfAllocStack : $@convention(thin) () -> () {
979+
bb0:
980+
%0 = string_literal utf8 "some message"
981+
%1 = integer_literal $Builtin.Word, 12
982+
%2 = integer_literal $Builtin.Int1, -1
983+
%3 = metatype $@thin String.Type
984+
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
985+
%4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
986+
%5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
987+
%6 = struct $OSLogInterpolationDCEStub(%5 : $String)
988+
%7 = alloc_stack $OSLogInterpolationDCEStub
989+
store %6 to [init] %7 : $*OSLogInterpolationDCEStub
990+
%8 = function_ref @appendInterpolationStub : $@convention(thin) (@inout OSLogInterpolationDCEStub) -> ()
991+
%9 = apply %8(%7) : $@convention(thin) (@inout OSLogInterpolationDCEStub) -> ()
992+
%10 = load [copy] %7 : $*OSLogInterpolationDCEStub
993+
%11 = function_ref @oslogMessageDCEInit : $@convention(thin) (@owned OSLogInterpolationDCEStub) -> @owned OSLogMessageDCEStub
994+
%12 = apply %11(%10) : $@convention(thin) (@owned OSLogInterpolationDCEStub) -> @owned OSLogMessageDCEStub
995+
destroy_value %12 : $OSLogMessageDCEStub
996+
destroy_addr %7 : $*OSLogInterpolationDCEStub
997+
dealloc_stack %7 : $*OSLogInterpolationDCEStub
998+
%13 = tuple ()
999+
return %13 : $()
1000+
// CHECK: bb0
1001+
// CHECK-NEXT: [[EMPTYTUP:%[0-9]+]] = tuple ()
1002+
// CHECK-NEXT: return [[EMPTYTUP]]
1003+
}
1004+
1005+
// Check that dead-code elimination of alloc stack does not happen when there
1006+
// are multiple writable parameters
1007+
sil [ossa] [_semantics "constant_evaluable"] @appendInterpolationStubError : $@convention(thin) (@inout OSLogInterpolationDCEStub, @inout Builtin.Int32) -> () {
1008+
bb0(%0 : $*OSLogInterpolationDCEStub, %1 : $*Builtin.Int32):
1009+
%9999 = tuple()
1010+
return %9999 : $()
1011+
}
1012+
1013+
// CHECK-LABEL: @testNoDCEOfAllocStack
1014+
sil [ossa] @testNoDCEOfAllocStack : $@convention(thin) () -> () {
1015+
bb0:
1016+
%0 = string_literal utf8 "some message"
1017+
%1 = integer_literal $Builtin.Word, 12
1018+
%2 = integer_literal $Builtin.Int1, -1
1019+
%3 = metatype $@thin String.Type
1020+
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
1021+
%4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
1022+
%5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String
1023+
%6 = struct $OSLogInterpolationDCEStub(%5 : $String)
1024+
%7 = alloc_stack $OSLogInterpolationDCEStub
1025+
store %6 to [init] %7 : $*OSLogInterpolationDCEStub
1026+
%8 = alloc_stack $Builtin.Int32
1027+
%lit = integer_literal $Builtin.Int32, -1
1028+
store %lit to [trivial] %8 : $*Builtin.Int32
1029+
%9 = function_ref @appendInterpolationStubError : $@convention(thin) (@inout OSLogInterpolationDCEStub, @inout Builtin.Int32) -> ()
1030+
%10 = apply %9(%7, %8) : $@convention(thin) (@inout OSLogInterpolationDCEStub, @inout Builtin.Int32) -> ()
1031+
%11 = load [copy] %7 : $*OSLogInterpolationDCEStub
1032+
%12 = function_ref @oslogMessageDCEInit : $@convention(thin) (@owned OSLogInterpolationDCEStub) -> @owned OSLogMessageDCEStub
1033+
%13 = apply %12(%11) : $@convention(thin) (@owned OSLogInterpolationDCEStub) -> @owned OSLogMessageDCEStub
1034+
destroy_value %13 : $OSLogMessageDCEStub
1035+
destroy_addr %7 : $*OSLogInterpolationDCEStub
1036+
dealloc_stack %8 : $*Builtin.Int32
1037+
dealloc_stack %7 : $*OSLogInterpolationDCEStub
1038+
%14 = tuple ()
1039+
return %14 : $()
1040+
// CHECK: bb0
1041+
// CHECK: alloc_stack $OSLogInterpolationDCEStub
1042+
}

test/SILOptimizer/OSLogPrototypeCompileTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
487487
let concatString = string + ":" + String(number)
488488
h.log("\(concatString)")
489489
// CHECK-NOT: OSLogMessage
490+
// CHECK-NOT: OSLogInterpolation
490491
// CHECK-LABEL: end sil function '$s25OSLogPrototypeCompileTest23testDeadCodeEliminationL_1h6number8num32bit6stringy0aB06LoggerV_Sis5Int32VSStF'
491492
}
492493
}

0 commit comments

Comments
 (0)