[os_log][SIL Optimization] Make new os log APIs @_transparent and

change OSLogOptimization pass so that inlining is not performed.
This commit is contained in:
Ravi Kandhadai
2019-05-21 19:05:44 -07:00
parent 81156b2fc1
commit 27eefd7311
5 changed files with 188 additions and 164 deletions

View File

@@ -28,13 +28,14 @@
/// Algorithm Overview:
///
/// This pass implements a function-level transformation that collects calls
/// to the os log APIs, which are annotated with an @_semantics attribute,
/// and performs the following steps on each such call.
/// to the initializer of the custom string interpolation type: OSLogMessage,
/// which are annotated with an @_semantics attribute, and performs the
/// following steps on each such call.
///
/// 1. Determines the range of instructions to constant evaluate.
/// The range starts from the first instruction that corresponds to the
/// construction of the custom string interpolation to the last instruction
/// of the body of the log call. The log call is also inlined into the
/// The range starts from the first SIL instruction that corresponds to the
/// construction of the custom string interpolation type: OSLogMessage to
/// its destruction. The log call is also inlined into the
/// caller.
///
/// 2. Constant evaluates the range of instruction identified in Step 1 and
@@ -56,13 +57,13 @@
/// Code Overview:
///
/// The function 'OSLogOptimization::run' implements the overall driver for
/// step 1 to 4. The functions 'OSLogOptimization::inlineLogCallAndGetLast' and
/// 'beginOfInterpolation' implement step 1 i.e., they compute the range of
/// instructions to evaluate. The functions 'constantFold' is a driver for the
/// steps 2 to 4. Step 2 is implemented by the function 'collectConstants',
/// step 3 by 'detectAndDiagnoseErrors', and step 4 by 'substituteConstants' and
/// 'emitCodeForSymbolicValue'. The remaining functions in the file implement
/// the subtasks and utilities needed by the above functions.
/// steps 1 to 4. The function 'beginOfInterpolation' implements step 1. It
/// computes the instruction from which start the evaluation. The function
/// 'constantFold' is a driver for the steps 2 to 4. Step 2 is implemented by
/// the function 'collectConstants', step 3 by 'detectAndDiagnoseErrors', and
/// step 4 by 'substituteConstants' and 'emitCodeForSymbolicValue'.
/// The remaining functions in the file implement the subtasks and utilities
/// needed by the above functions.
#include "swift/AST/ASTContext.h"
#include "swift/AST/DiagnosticEngine.h"
@@ -70,6 +71,9 @@
#include "swift/AST/Expr.h"
#include "swift/AST/Module.h"
#include "swift/AST/SubstitutionMap.h"
#include "swift/Basic/OptimizationMode.h"
#include "swift/Demangling/Demangle.h"
#include "swift/Demangling/Demangler.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILBuilder.h"
@@ -264,7 +268,6 @@ evaluateOrSkip(ConstExprStepEvaluator &stepEval,
/// instruction itself.
static bool isInstructionFoldable(SingleValueInstruction *inst) {
ASTContext &astContext = inst->getFunction()->getASTContext();
;
SILType silType = inst->getType();
return (!silType.isAddress() && !isa<LiteralInst>(inst) &&
@@ -272,20 +275,31 @@ static bool isInstructionFoldable(SingleValueInstruction *inst) {
isIntegerOrStringType(silType, astContext));
}
/// Constant evaluate the instructions in the range 'first' to 'last'.
/// Add foldable, constant-valued instructions discovered during the evaluation
/// to the 'foldState' passed in.
/// Constant evaluate the instructions from 'start' to the end of the function
/// or until oslogMessage is released. Add foldable, constant-valued
/// instructions discovered during the evaluation to the 'foldState' passed in.
/// \returns error information for emitting diagnostics if the evaluation
/// failed.
static Optional<SymbolicValue> collectConstants(SILBasicBlock::iterator first,
SILBasicBlock::iterator last,
FoldState &foldState) {
static Optional<SymbolicValue>
collectConstants(SILInstruction *start, SingleValueInstruction *oslogMessage,
FoldState &foldState) {
ConstExprStepEvaluator &constantEvaluator = foldState.constantEvaluator;
SILBasicBlock::iterator currI = start->getIterator();
for (auto currI = first; currI != last;) {
// The loop will return when it sees a return instruction or a release value
// of oslogMessage.
while (true) {
SILInstruction *currInst = &(*currI);
// Break if currInst is a release_value of 'oslogMessage'.
if (auto *releaseValueInst = dyn_cast<ReleaseValueInst>(currInst)) {
if (releaseValueInst->getOperand()->getDefiningInstruction() ==
oslogMessage) {
break;
}
}
// Initialize string info from this instruction if possible.
foldState.stringInfo.extractStringInfoFromInstruction(currInst);
@@ -428,12 +442,12 @@ static void substituteConstants(FoldState &foldState) {
/// TODO: some of the checks here would be made redundant by a dedicated
/// diagnostics check that will happen before the optimization starts.
static bool detectAndDiagnoseErrors(Optional<SymbolicValue> errorInfo,
SingleValueInstruction *osLogMessageAddr,
SingleValueInstruction *osLogMessage,
FoldState &foldState) {
ConstExprStepEvaluator &constantEvaluator = foldState.constantEvaluator;
SILLocation loc = osLogMessageAddr->getLoc();
SILLocation loc = osLogMessage->getLoc();
SourceLoc sourceLoc = loc.getSourceLoc();
SILFunction *fn = osLogMessageAddr->getFunction();
SILFunction *fn = osLogMessage->getFunction();
SILModule &module = fn->getModule();
ASTContext &astContext = fn->getASTContext();
bool errorDetected = false;
@@ -450,21 +464,16 @@ static bool detectAndDiagnoseErrors(Optional<SymbolicValue> errorInfo,
// inferred as constants. If not, it implies incorrect implementation
// of the os log API in the overlay. Diagnostics here are for os log
// library authors.
Optional<SymbolicValue> osLogMessageAddrValueOpt =
constantEvaluator.lookupConstValue(osLogMessageAddr);
assert(osLogMessageAddrValueOpt.hasValue() &&
osLogMessageAddrValueOpt->getKind() == SymbolicValue::Address);
SmallVector<unsigned, 2> accessPath;
SymbolicValue osLogMessageValue =
osLogMessageAddrValueOpt->getAddressValue(accessPath)->getValue();
if (!osLogMessageValue.isConstant()) {
Optional<SymbolicValue> osLogMessageValueOpt =
constantEvaluator.lookupConstValue(osLogMessage);
if (!osLogMessageValueOpt ||
osLogMessageValueOpt->getKind() != SymbolicValue::Aggregate) {
diagnose(astContext, sourceLoc, diag::oslog_non_constant_message);
return true;
}
SymbolicValue osLogInterpolationValue =
osLogMessageValue.lookThroughSingleElementAggregates();
osLogMessageValueOpt->lookThroughSingleElementAggregates();
if (!osLogInterpolationValue.isConstant()) {
diagnose(astContext, sourceLoc, diag::oslog_non_constant_interpolation);
return true;
@@ -474,7 +483,7 @@ static bool detectAndDiagnoseErrors(Optional<SymbolicValue> errorInfo,
// string or integer has a constant value. If this is violated this could
// be an indication of an error in the usage of the API. Diagnostics emitted
// here are for the users of the os log APIs.
SILType osLogMessageType = osLogMessageAddr->getType();
SILType osLogMessageType = osLogMessage->getType();
StructDecl *structDecl = osLogMessageType.getStructOrBoundGenericStruct();
assert(structDecl);
@@ -510,17 +519,15 @@ static bool detectAndDiagnoseErrors(Optional<SymbolicValue> errorInfo,
return errorDetected;
}
/// Given a range of instructions from `first` to `last`, fold the constants
/// that could be discovered by constant evaluating the instructions.
static void constantFold(SILBasicBlock::iterator first,
SILBasicBlock::iterator last) {
SILInstruction *firstInst = &(*first);
/// Constant evaluate instructions starting from 'start' and fold the uses
/// of the value 'oslogMessage'. Stop when oslogMessageValue is released.
static void constantFold(SILInstruction *start,
SingleValueInstruction *oslogMessage) {
// Initialize fold state.
FoldState state(firstInst->getFunction());
FoldState state(start->getFunction());
auto errorInfo = collectConstants(first, last, state);
auto errorInfo = collectConstants(start, oslogMessage, state);
// At this point, the `OSLogMessage` instance should be mapped to a symbolic
// value in the interpreter state. Furthermore, its format string and
@@ -528,74 +535,82 @@ static void constantFold(SILBasicBlock::iterator first,
// If this is not the case, it means the formatting options or privacy
// qualifiers provided by the user were not inferred as compile-time
// constants. Detect and diagnose this scenario.
assert(isa<SingleValueInstruction>(firstInst));
bool errorDetected = detectAndDiagnoseErrors(
errorInfo, dyn_cast<SingleValueInstruction>(firstInst), state);
bool errorDetected = detectAndDiagnoseErrors(errorInfo, oslogMessage, state);
if (errorDetected)
return;
substituteConstants(state);
}
/// Find an argument of type OSLogMessage from the given os log call.
static SILValue findOSLogMessageArgument(ApplyInst *oslogCall) {
ASTContext &astContext = oslogCall->getFunction()->getASTContext();
for (auto argument : oslogCall->getArguments()) {
SILType argumentType = argument->getType();
auto *structDecl = argumentType.getStructOrBoundGenericStruct();
if (!structDecl)
continue;
if (structDecl->getName() == astContext.Id_OSLogMessage) {
return argument;
}
}
return SILValue();
}
/// Given an os log call that is passed a string interpolation,
/// find the first instruction that marks the begining of the string
/// interpolation. Normally, this instruction is the alloc_stack of the
/// string interpolation type, which in this case if 'OSLogMessage'.
/// Given a call to the initializer of OSLogMessage, which conforms to
/// 'ExpressibleByStringInterpolation', find the first instruction, if any,
/// that marks the begining of the string interpolation that is used to
/// create an OSLogMessage instance. Normally, this instruction is the
/// alloc_stack of the string interpolation type: 'OSLogInterpolation'.
/// Constant evaluation and folding must begin from this instruction.
static Optional<SILBasicBlock::iterator>
beginOfInterpolation(ApplyInst *oslogCall) {
SILFunction *caller = oslogCall->getFunction();
static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
auto oslogInitCallSite = FullApplySite(oslogInit);
SILFunction *callee = oslogInitCallSite.getCalleeFunction();
auto &astContext = oslogInit->getFunction()->getASTContext();
// Find an argument to the call with name 'OSLogMessage'. Since the log
// function definition may change/shuffle its arguments and also since mock
// test helpers for log functions do exist, do not rely on the position of
// the argument and instead look for an argument with a name.
auto &astContext = caller->getASTContext();
auto oslogMessageArgument = findOSLogMessageArgument(oslogCall);
assert(oslogMessageArgument && "No argument of type 'OSLogMessage' in the "
"os log call");
// The initializer must return the OSLogMessage instance directly.
assert(oslogInitCallSite.getNumArguments() >= 1 &&
oslogInitCallSite.getNumIndirectSILResults() == 0);
auto *stringInterpolAllocInst =
oslogMessageArgument->getDefiningInstruction();
if (!stringInterpolAllocInst ||
!isa<AllocStackInst>(stringInterpolAllocInst)) {
diagnose(astContext, oslogCall->getLoc().getSourceLoc(),
diag::oslog_dynamic_message);
return None;
SILInstruction *firstArgumentInst =
oslogInitCallSite.getArgument(0)->getDefiningInstruction();
if (!firstArgumentInst) {
// oslogInit call does not correspond to an auto-generated initialization
// done by the compiler on seeing a string interpolation. Ignore this.
return nullptr;
}
// Assumption: the alloc_stack of the string interpolation type 'OSLogMessage'
// must precede every other instruction relevant to the string interpolation.
return stringInterpolAllocInst->getIterator();
SILInstruction *startInst = nullptr;
// If this is an initialization from string interpolation, the first argument
// to the initializer is a load of an auto-generated alloc-stack of
// OSLogInterpolation:
// 'alloc_stack $OSLogInterpolation, var, name $interpolation'
// If no such instruction exists, ignore this call as this is not a
// OSLogMessage instantiation through string interpolation.
if (callee->hasSemanticsAttr("oslog.message.init_interpolation")) {
auto *loadInst = dyn_cast<LoadInst>(firstArgumentInst);
if (!loadInst)
return nullptr;
auto *allocStackInst = dyn_cast<AllocStackInst>(loadInst->getOperand());
if (!allocStackInst)
return nullptr;
Optional<SILDebugVariable> varInfo = allocStackInst->getVarInfo();
if (!varInfo && varInfo->Name != astContext.Id_dollarInterpolation.str())
return nullptr;
startInst = allocStackInst;
}
// If this is an initialization from a string literal, the first argument
// should be the creation of the string literal.
if (callee->hasSemanticsAttr("oslog.message.init_stringliteral")) {
if (!getStringMakeUTF8Init(firstArgumentInst))
return nullptr;
startInst = firstArgumentInst;
}
return startInst;
}
/// If the SILInstruction is a call to an os log function, return the call
/// as an ApplyInst. Otherwise, return nullptr.
static ApplyInst *getAsOSLogCall(SILInstruction *inst) {
/// If the SILInstruction is an initialization of OSLogMessage, return the
/// initialization call as an ApplyInst. Otherwise, return nullptr.
static ApplyInst *getAsOSLogMessageInit(SILInstruction *inst) {
auto *applyInst = dyn_cast<ApplyInst>(inst);
if (!applyInst) {
return nullptr;
}
SILFunction *callee = applyInst->getCalleeFunction();
if (!callee || !callee->hasSemanticsAttrThatStartsWith("oslog.log")) {
if (!callee ||
!callee->hasSemanticsAttrThatStartsWith("oslog.message.init")) {
return nullptr;
}
@@ -612,69 +627,50 @@ class OSLogOptimization : public SILFunctionTransform {
~OSLogOptimization() override {}
/// Inline the given os log Call, by loading and linking the callee if it has
/// not been loaded yet, and return the last inlined instruction.
/// \param oslogCall log call instruction to inline.
/// \returns last instruction of the callee after inlining.
SILBasicBlock::iterator inlineLogCallAndGetLast(ApplyInst *oslogCall) {
SILFunction *callee = oslogCall->getReferencedFunctionOrNull();
assert(callee);
// Load and link the called os log function before inlining. This is needed
// to link shared functions that are used in the callee body.
if (callee->isExternalDeclaration()) {
callee->getModule().loadFunction(callee);
assert(!callee->isExternalDeclaration());
oslogCall->getFunction()->getModule().linkFunction(
callee, SILModule::LinkingMode::LinkNormal);
}
// Inline the log call into the caller and find the last instruction of the
// log call.
SILOptFunctionBuilder funcBuilder(*this);
SILBasicBlock *lastBB =
SILInliner::inlineFullApply(
oslogCall, SILInliner::InlineKind::PerformanceInline, funcBuilder)
.second;
return lastBB->end();
}
/// The entry point to the transformation.
void run() override {
auto &fun = *getFunction();
// Don't rerun optimization on deserialized functions or stdlib functions.
if (getFunction()->wasDeserializedCanonical()) {
if (fun.wasDeserializedCanonical()) {
return;
}
auto &fun = *getFunction();
// Collect all os log calls in the function.
SmallVector<ApplyInst *, 4> oslogCalls;
// Collect all 'OSLogMessage.init' in the function. 'OSLogMessage' is a
// custom string interpolation type used by the new OS log APIs.
SmallVector<ApplyInst *, 4> oslogMessageInits;
for (auto &bb : fun) {
for (auto &inst : bb) {
auto oslogCall = getAsOSLogCall(&inst);
if (!oslogCall)
auto init = getAsOSLogMessageInit(&inst);
if (!init)
continue;
oslogCalls.push_back(oslogCall);
oslogMessageInits.push_back(init);
}
}
// Optimize each os log call found. Optimizing a call will change the
// function body by inlining functions and folding constants.
for (auto *oslogCall : oslogCalls) {
// Constant fold the uses of properties of OSLogMessage instance. Note that
// the function body will change due to constant folding, after each
// iteration.
for (auto *oslogInit : oslogMessageInits) {
// Find the range of instructions that have to be constant evaluated
// and folded. The relevant instructions start at the begining of the
// string interpolation passed to the log call and end at the last
// instruction of the body of the called os log function (which will be
// inlined).
Optional<SILBasicBlock::iterator> firstI =
beginOfInterpolation(oslogCall);
if (!firstI)
// Find the first instruction from where constant evaluation and folding
// must begin. The first instruction should precede (in the control-flow
// order) the instructions that are generated by the compiler for
// the string-interpolation literal that is used to instantiate
// OSLogMessage instance.
SILInstruction *interpolationStart = beginOfInterpolation(oslogInit);
if (!interpolationStart) {
// This scenario indicates an explicit initialization of OSLogMessage
// that doesn't use a string inteprolation literal.
// However, this is not always an error as explicit initialization is
// used by thunk initializers auto-generated for protocol conformances.
// TODO: the log APIs uses must be diagnosed by a separate pass
// (possibly before mandatory inlining). The current pass should not
// emit diagnostics but only perform optimization.
continue;
}
SILBasicBlock::iterator lastI = inlineLogCallAndGetLast(oslogCall);
constantFold(firstI.getValue(), lastI);
constantFold(interpolationStart, oslogInit);
}
}
};