Skip to content

Commit 633bc79

Browse files
committed
[OSLogOptimization] Improve the OSLogOptimization pass so that it can
fold a symbolic closure, which is the representation of a closure literal in the constant evaluator. This commit improves the function emitCodeForSymbolicValue so that given a symbolic closure it can emit SIL code for constructing the closure. This improvement enables folding the arguments array, which is an array of closures, by its constant value inferred by constant evaluating the new OSLog calls.
1 parent 6f086af commit 633bc79

File tree

3 files changed

+526
-25
lines changed

3 files changed

+526
-25
lines changed

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 131 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -282,42 +282,52 @@ evaluateOrSkip(ConstExprStepEvaluator &stepEval,
282282

283283
/// Return true iff the given value is a stdlib Int or Bool and it not a direct
284284
/// construction of Int or Bool.
285-
static bool isFoldableIntOrBool(SILValue value, SILInstruction *definingInst,
286-
ASTContext &astContext) {
287-
assert(definingInst);
288-
return !isa<StructInst>(definingInst) &&
289-
isIntegerOrBoolType(value->getType(), astContext);
285+
static bool isFoldableIntOrBool(SILValue value, ASTContext &astContext) {
286+
return isIntegerOrBoolType(value->getType(), astContext) &&
287+
!isa<StructInst>(value);
290288
}
291289

292290
/// Return true iff the given value is a string and is not an initialization
293291
/// of an string from a string literal.
294-
static bool isFoldableString(SILValue value, SILInstruction *definingInst,
295-
ASTContext &astContext) {
296-
assert(definingInst);
292+
static bool isFoldableString(SILValue value, ASTContext &astContext) {
297293
return isStringType(value->getType(), astContext) &&
298-
!getStringMakeUTF8Init(definingInst);
294+
(!isa<ApplyInst>(value) ||
295+
!getStringMakeUTF8Init(cast<ApplyInst>(value)));
299296
}
300297

301298
/// Return true iff the given value is an array and is not an initialization
302299
/// of an array from an array literal.
303-
static bool isFoldableArray(SILValue value, SILInstruction *definingInst,
304-
ASTContext &astContext) {
305-
assert(definingInst);
300+
static bool isFoldableArray(SILValue value, ASTContext &astContext) {
306301
if (!isArrayType(value->getType(), astContext))
307302
return false;
308-
309-
// Check if this is not an initialization of an array from a literal.
303+
// If value is an initialization of an array from a literal or an empty array
304+
// initializer, it need not be folded. Arrays constructed from literals use a
305+
// function with semantics: "array.uninitialized_intrinsic" that returns
306+
// a pair, where the first element of the pair is the array.
307+
SILInstruction *definingInst = value->getDefiningInstruction();
308+
if (!definingInst)
309+
return true;
310310
SILInstruction *constructorInst = definingInst;
311311
if (isa<DestructureTupleInst>(definingInst) ||
312312
isa<TupleExtractInst>(definingInst)) {
313313
constructorInst = definingInst->getOperand(0)->getDefiningInstruction();
314314
}
315-
ApplyInst *apply = dyn_cast<ApplyInst>(definingInst);
316-
if (!apply)
315+
if (!constructorInst || !isa<ApplyInst>(constructorInst))
317316
return true;
318-
SILFunction *callee = apply->getCalleeFunction();
319-
return !callee || !callee->hasSemanticsAttr("array.init.empty") ||
320-
!callee->hasSemanticsAttr("array.uninitialized_intrinsic");
317+
SILFunction *callee = cast<ApplyInst>(constructorInst)->getCalleeFunction();
318+
return !callee ||
319+
(!callee->hasSemanticsAttr("array.init.empty") &&
320+
!callee->hasSemanticsAttr("array.uninitialized_intrinsic"));
321+
}
322+
323+
/// Return true iff the given value is a closure but is not a creation of a
324+
/// closure e.g., through partial_apply or thin_to_thick_function or
325+
/// convert_function.
326+
static bool isFoldableClosure(SILValue value) {
327+
return value->getType().is<SILFunctionType>() &&
328+
(!isa<FunctionRefInst>(value) && !isa<PartialApplyInst>(value) &&
329+
!isa<ThinToThickFunctionInst>(value) &&
330+
!isa<ConvertFunctionInst>(value));
321331
}
322332

323333
/// Check whether a SILValue is foldable. String, integer, array and
@@ -337,9 +347,9 @@ static bool isSILValueFoldable(SILValue value) {
337347
!isa<LoadBorrowInst>(definingInst) &&
338348
!isa<BeginBorrowInst>(definingInst) &&
339349
!isa<CopyValueInst>(definingInst) &&
340-
(isFoldableIntOrBool(value, definingInst, astContext) ||
341-
isFoldableString(value, definingInst, astContext) ||
342-
isFoldableArray(value, definingInst, astContext)));
350+
(isFoldableIntOrBool(value, astContext) ||
351+
isFoldableString(value, astContext) ||
352+
isFoldableArray(value, astContext) || isFoldableClosure(value)));
343353
}
344354

345355
/// Diagnose failure during evaluation of a call to a constant-evaluable
@@ -585,6 +595,39 @@ static SILValue emitCodeForConstantArray(ArrayRef<SILValue> elements,
585595
return arraySIL;
586596
}
587597

598+
/// Given a SILValue \p value, return the instruction immediately following the
599+
/// definition of the value. That is, if the value is defined by an
600+
/// instruction, return the instruction following the definition. Otherwise, if
601+
/// the value is a basic block parameter, return the first instruction of the
602+
/// basic block.
603+
SILInstruction *getInstructionFollowingValueDefinition(SILValue value) {
604+
SILInstruction *definingInst = value->getDefiningInstruction();
605+
if (definingInst) {
606+
return &*std::next(definingInst->getIterator());
607+
}
608+
// Here value must be a basic block argument.
609+
SILBasicBlock *bb = value->getParentBlock();
610+
return &*bb->begin();
611+
}
612+
613+
/// Given a SILValue \p value, create a copy of the value using copy_value in
614+
/// OSSA or retain in non-OSSA, if \p value is a non-trivial type. Otherwise, if
615+
/// \p value is a trivial type, return the value itself.
616+
SILValue makeOwnedCopyOfSILValue(SILValue value, SILFunction &fun) {
617+
SILType type = value->getType();
618+
if (type.isTrivial(fun))
619+
return value;
620+
assert(!type.isAddress() && "cannot make owned copy of addresses");
621+
622+
SILInstruction *instAfterValueDefinition =
623+
getInstructionFollowingValueDefinition(value);
624+
SILLocation copyLoc = instAfterValueDefinition->getLoc();
625+
SILBuilderWithScope builder(instAfterValueDefinition);
626+
const TypeLowering &typeLowering = builder.getTypeLowering(type);
627+
SILValue copy = typeLowering.emitCopyValue(builder, copyLoc, value);
628+
return copy;
629+
}
630+
588631
/// Generate SIL code that computes the constant given by the symbolic value
589632
/// `symVal`. Note that strings and struct-typed constant values will require
590633
/// multiple instructions to be emitted.
@@ -684,6 +727,61 @@ static SILValue emitCodeForSymbolicValue(SymbolicValue symVal,
684727
elementSILValues, expectedType->getCanonicalType(), builder, loc);
685728
return arraySIL;
686729
}
730+
case SymbolicValue::Closure: {
731+
assert(expectedType->is<AnyFunctionType>() ||
732+
expectedType->is<SILFunctionType>());
733+
734+
SymbolicClosure *closure = symVal.getClosure();
735+
SubstitutionMap callSubstMap = closure->getCallSubstitutionMap();
736+
SILModule &module = builder.getModule();
737+
ArrayRef<SymbolicClosureArgument> captures = closure->getCaptures();
738+
739+
// Recursively emit code for all captured values that are mapped to a
740+
// symbolic value. If there is a captured value that is not mapped
741+
// to a symbolic value, use the captured value as such (after possibly
742+
// copying non-trivial captures).
743+
SmallVector<SILValue, 4> capturedSILVals;
744+
for (SymbolicClosureArgument capture : captures) {
745+
SILValue captureOperand = capture.first;
746+
Optional<SymbolicValue> captureSymVal = capture.second;
747+
if (!captureSymVal) {
748+
SILFunction &fun = builder.getFunction();
749+
assert(captureOperand->getFunction() == &fun &&
750+
"non-constant captured arugment not defined in this function");
751+
// If the captureOperand is a non-trivial value, it should be copied
752+
// as it now used in a new folded closure.
753+
SILValue captureCopy = makeOwnedCopyOfSILValue(captureOperand, fun);
754+
capturedSILVals.push_back(captureCopy);
755+
continue;
756+
}
757+
// Here, we have a symbolic value for the capture. Therefore, use it to
758+
// create a new constant at this point. Note that the captured operand
759+
// type may have generic parameters which has to be substituted with the
760+
// substitution map that was inferred by the constant evaluator at the
761+
// partial-apply site.
762+
SILType operandType = captureOperand->getType();
763+
SILType captureType = operandType.subst(module, callSubstMap);
764+
SILValue captureSILVal = emitCodeForSymbolicValue(
765+
captureSymVal.getValue(), captureType.getASTType(), builder, loc,
766+
stringInfo);
767+
capturedSILVals.push_back(captureSILVal);
768+
}
769+
770+
FunctionRefInst *functionRef =
771+
builder.createFunctionRef(loc, closure->getTarget());
772+
SILType closureType = closure->getClosureType();
773+
ParameterConvention convention =
774+
closureType.getAs<SILFunctionType>()->getCalleeConvention();
775+
PartialApplyInst *papply = builder.createPartialApply(
776+
loc, functionRef, callSubstMap, capturedSILVals, convention);
777+
// The type of the created closure must be a lowering of the expected type.
778+
SILType resultType = papply->getType();
779+
CanType expectedCanType = expectedType->getCanonicalType();
780+
assert(expectedType->is<SILFunctionType>()
781+
? resultType.getASTType() == expectedCanType
782+
: resultType.is<SILFunctionType>());
783+
return papply;
784+
}
687785
default: {
688786
llvm_unreachable("Symbolic value kind is not supported");
689787
}
@@ -997,8 +1095,9 @@ static void constantFold(SILInstruction *start,
9971095
/// marks the begining of the string interpolation that is used to create an
9981096
/// OSLogMessage instance. This function traverses the backward data-dependence
9991097
/// chain of the given OSLogMessage initializer: \p oslogInit. As a special case
1000-
/// it avoid chasing the data-dependenceies through a partial-apply as they are
1001-
/// considered as constants.
1098+
/// it avoids chasing the data-dependencies from the captured values of
1099+
/// partial-apply instructions, as a partial apply instruction is considered as
1100+
/// a constant regardless of the constantness of its captures.
10021101
static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
10031102
auto oslogInitCallSite = FullApplySite(oslogInit);
10041103
SILFunction *callee = oslogInitCallSite.getCalleeFunction();
@@ -1023,7 +1122,14 @@ static SILInstruction *beginOfInterpolation(ApplyInst *oslogInit) {
10231122
// Partial applies are used to capture the dynamic arguments passed to
10241123
// the string interpolation. Their arguments are not required to be
10251124
// known at compile time and they need not be constant evaluated.
1026-
// Therefore, do not follow this dependency chain.
1125+
// Therefore, follow only the dependency chain along function ref operand.
1126+
SILInstruction *definingInstruction =
1127+
inst->getOperand(0)->getDefiningInstruction();
1128+
assert(definingInstruction && "no function-ref operand in partial-apply");
1129+
if (seenInstructions.insert(definingInstruction).second) {
1130+
worklist.push_back(definingInstruction);
1131+
candidateStartInstructions.insert(definingInstruction);
1132+
}
10271133
continue;
10281134
}
10291135

0 commit comments

Comments
 (0)