Skip to content

Commit b2c1540

Browse files
committed
[SIL Optimization][OSLogOptimization] Improve the logic for
discovering the beginning of the string interpolation passed to os log APIs. The implementation follows the chain of dependencies starting from an initializer call to OSLogMessage until the first instruction of interpolation is discovered. This is more robust towards changes to the SIL generation of string interpolation literals.
1 parent ad4d623 commit b2c1540

File tree

1 file changed

+114
-35
lines changed

1 file changed

+114
-35
lines changed

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 114 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
#include "swift/SILOptimizer/Utils/SILInliner.h"
9292
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
9393
#include "llvm/ADT/MapVector.h"
94+
#include "swift/SIL/BasicBlockUtils.h"
95+
#include "swift/SIL/CFG.h"
96+
#include "llvm/ADT/BreadthFirstIterator.h"
9497

9598
using namespace swift;
9699

@@ -661,52 +664,99 @@ static void constantFold(SILInstruction *start,
661664
static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
662665
auto oslogInitCallSite = FullApplySite(oslogInit);
663666
SILFunction *callee = oslogInitCallSite.getCalleeFunction();
664-
auto &astContext = oslogInit->getFunction()->getASTContext();
665667

668+
assert (callee->hasSemanticsAttrThatStartsWith("oslog.message.init"));
666669
// The initializer must return the OSLogMessage instance directly.
667670
assert(oslogInitCallSite.getNumArguments() >= 1 &&
668671
oslogInitCallSite.getNumIndirectSILResults() == 0);
669672

670-
SILInstruction *firstArgumentInst =
671-
oslogInitCallSite.getArgument(0)->getDefiningInstruction();
672-
if (!firstArgumentInst) {
673-
// oslogInit call does not correspond to an auto-generated initialization
674-
// done by the compiler on seeing a string interpolation. Ignore this.
675-
return nullptr;
676-
}
673+
// List of backward dependencies that needs to be analyzed.
674+
SmallVector<SILInstruction *, 4> worklist = { oslogInit };
675+
SmallPtrSet<SILInstruction *, 4> seenInstructions = { oslogInit };
676+
// List of instructions that could potentially mark the beginning of the
677+
// interpolation.
678+
SmallPtrSet<SILInstruction *, 4> candidateStartInstructions;
679+
680+
unsigned i = 0;
681+
while (i < worklist.size()) {
682+
SILInstruction *inst = worklist[i++];
683+
684+
if (isa<PartialApplyInst>(inst)) {
685+
// Partial applies are used to capture the dynamic arguments passed to
686+
// the string interpolation. Their arguments are not required to be
687+
// known at compile time and they need not be constant evaluated.
688+
// Therefore, do not follow this dependency chain.
689+
continue;
690+
}
677691

678-
SILInstruction *startInst = nullptr;
692+
for (Operand &operand : inst->getAllOperands()) {
693+
if (SILInstruction *definingInstruction =
694+
operand.get()->getDefiningInstruction()) {
695+
if (seenInstructions.count(definingInstruction))
696+
continue;
697+
worklist.push_back(definingInstruction);
698+
seenInstructions.insert(definingInstruction);
699+
candidateStartInstructions.insert(definingInstruction);
700+
}
701+
// If there is no definining instruction for this operand, it could be a
702+
// basic block or function parameter. Such operands are not considered
703+
// in the backward slice. Dependencies through them are safe to ignore
704+
// in this context.
705+
}
706+
707+
// If the instruction: `inst` has an operand, its definition should precede
708+
// `inst` in the control-flow order. Therefore, remove `inst` from the
709+
// candidate start instructions.
710+
if (inst->getNumOperands() > 0) {
711+
candidateStartInstructions.erase(inst);
712+
}
713+
714+
if (!isa<AllocStackInst>(inst)) {
715+
continue;
716+
}
717+
718+
// If we have an alloc_stack instruction, include stores into it into the
719+
// backward dependency list. However, whether alloc_stack precedes its in
720+
// control-flow order can only be determined by traversing the instrutions
721+
// in the control-flow order.
722+
AllocStackInst *allocStackInst = cast<AllocStackInst>(inst);
723+
for (StoreInst *storeInst : allocStackInst->getUsersOfType<StoreInst>()) {
724+
worklist.push_back(storeInst);
725+
candidateStartInstructions.insert(storeInst);
726+
}
727+
}
679728

680-
// If this is an initialization from string interpolation, the first argument
681-
// to the initializer is a load of an auto-generated alloc-stack of
682-
// OSLogInterpolation:
683-
// 'alloc_stack $OSLogInterpolation, var, name $interpolation'
684-
// If no such instruction exists, ignore this call as this is not a
685-
// OSLogMessage instantiation through string interpolation.
686-
if (callee->hasSemanticsAttr("oslog.message.init_interpolation")) {
687-
auto *loadInst = dyn_cast<LoadInst>(firstArgumentInst);
688-
if (!loadInst)
689-
return nullptr;
690-
691-
auto *allocStackInst = dyn_cast<AllocStackInst>(loadInst->getOperand());
692-
if (!allocStackInst)
693-
return nullptr;
694-
695-
Optional<SILDebugVariable> varInfo = allocStackInst->getVarInfo();
696-
if (!varInfo && varInfo->Name != astContext.Id_dollarInterpolation.str())
697-
return nullptr;
698-
699-
startInst = allocStackInst;
729+
// Find the first basic block in the control-flow order. TODO: if we do not
730+
// madatorily inline appendLiteral/Interpolation functions of
731+
// OSLogInterpolation, we can expect all candidate instructions to be in the
732+
// same basic block. Once @_transparent is removed from those functions,
733+
// simplify this code.
734+
SmallPtrSet<SILBasicBlock *, 4> candidateBBs;
735+
for (auto *candidate: candidateStartInstructions) {
736+
SILBasicBlock *candidateBB = candidate->getParent();
737+
candidateBBs.insert(candidateBB);
700738
}
701739

702-
// If this is an initialization from a string literal, the first argument
703-
// should be the creation of the string literal.
704-
if (callee->hasSemanticsAttr("oslog.message.init_stringliteral")) {
705-
if (!getStringMakeUTF8Init(firstArgumentInst))
706-
return nullptr;
707-
startInst = firstArgumentInst;
740+
SILBasicBlock *firstBB = nullptr;
741+
SILBasicBlock *entryBB = oslogInit->getFunction()->getEntryBlock();
742+
for (SILBasicBlock *bb: llvm::breadth_first<SILBasicBlock *>(entryBB)) {
743+
if (candidateBBs.count(bb)) {
744+
firstBB = bb;
745+
break;
746+
}
708747
}
748+
assert(firstBB);
709749

750+
// Iterate over the instructions in the firstBB and find the instruction that
751+
// starts the interpolation.
752+
SILInstruction *startInst = nullptr;
753+
for (SILInstruction &inst : *firstBB) {
754+
if (candidateStartInstructions.count(&inst)) {
755+
startInst = &inst;
756+
break;
757+
}
758+
}
759+
assert(startInst);
710760
return startInst;
711761
}
712762

@@ -733,6 +783,27 @@ static ApplyInst *getAsOSLogMessageInit(SILInstruction *inst) {
733783
return nullptr;
734784
}
735785

786+
/// Return true iff this function is a protocol witness for
787+
/// ExpressibleByStringInterpolation.init(stringInterpolation:) in OSLogMessage.
788+
bool isAutoGeneratedInitOfOSLogMessage(SILFunction &fun) {
789+
DeclContext *declContext = fun.getDeclContext();
790+
if (!declContext)
791+
return false;
792+
Decl *decl = declContext->getAsDecl();
793+
if (!decl)
794+
return false;
795+
ConstructorDecl *cdecl = dyn_cast<ConstructorDecl>(decl);
796+
if (!cdecl)
797+
return false;
798+
DeclContext *parentContext = cdecl->getParent();
799+
if (!parentContext)
800+
return false;
801+
NominalTypeDecl *typeDecl = parentContext->getSelfNominalTypeDecl();
802+
if (!typeDecl)
803+
return false;
804+
return typeDecl->getName() == fun.getASTContext().Id_OSLogMessage;
805+
}
806+
736807
class OSLogOptimization : public SILFunctionTransform {
737808

738809
~OSLogOptimization() override {}
@@ -747,6 +818,14 @@ class OSLogOptimization : public SILFunctionTransform {
747818
return;
748819
}
749820

821+
// Skip the auto-generated (transparent) witness method of OSLogMessage,
822+
// which ends up invoking the OSLogMessage initializer:
823+
// "oslog.message.init_interpolation" but without an interpolated
824+
// string literal.
825+
if (isAutoGeneratedInitOfOSLogMessage(fun)) {
826+
return;
827+
}
828+
750829
// Collect all 'OSLogMessage.init' in the function. 'OSLogMessage' is a
751830
// custom string interpolation type used by the new OS log APIs.
752831
SmallVector<ApplyInst *, 4> oslogMessageInits;

0 commit comments

Comments
 (0)