-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SIL Optimization] Add a mandatory pass for optimizing the new os log APIs based on string interpolation. #24336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SIL Optimization] Add a mandatory pass for optimizing the new os log APIs based on string interpolation. #24336
Conversation
@swift-ci Please test |
Build failed |
@swift-ci Please test macOS Platform |
Build failed |
@swift-ci Please smoke test macOS Platform |
@swift-ci Please test macOS Platform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically LGTM!
See my comments inline.
/// failed. | ||
Optional<SymbolicValue> collectConstants( | ||
SILBasicBlock::iterator first, SILBasicBlock::iterator last, | ||
llvm::SmallMapVector<SingleValueInstruction *, SymbolicValue, 4> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would make sense to define a typedef for llvm::SmallMapVector<SingleValueInstruction *, SymbolicValue, 4>.
// the function body. This information is needed for constant folding string | ||
// literals and recording it will avoid reconstructing it. | ||
SILFunction *stringInitIntrinsic = nullptr; | ||
Optional<SILType> stringMetatype = None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for an Optional here. SILType has a null-value:
SILType stringMetatype = SILType();
|
||
/// Find the SILBasicBlock::iterator for a given instruction. | ||
static SILBasicBlock::iterator | ||
findIteratorForInstruction(SILInstruction *keyInst) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use keyInst->getIterator()?
static SILValue findOSLogMessageArgument(ApplyInst *oslogCall) { | ||
ASTContext &astContext = oslogCall->getFunction()->getASTContext(); | ||
|
||
for (auto argument : oslogCall->getArguments()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be any argument?
If it's defined to be e.g. the first argument, I think you should just check the first argument and not try to find an argument with the right type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two functions that use this type, one is the log function and the other is a mock test helper that does some runtime checks instead of logging and is used only in tests. The mock function has this as the first argument, whereas the log function has this as the second argument. I am also expecting that the log function may have to change/shuffle its arguments as it has to be API compatible with the server-side logging APIs. Just to be robust against these changes, I am searching for the argument by identifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. I suggest that you add a comment describing this
return argument; | ||
} | ||
} | ||
llvm_unreachable("No argument of type OSLogMessage in os log call"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
llvm_unreachable?
Shouldn't this be a regular error message?
} | ||
|
||
/// A utility function for inlining the apply instruction into the caller. | ||
/// TODO: should this function be moved to the SILInliner utility? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would make sense. AFAIK, the code is the same in the MandatoryInliner and in the PerformanceInliner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eeckstein I created a new PR to move this to the SILInliner and modified PerformanceInliner to use this utility.
if (errorDetected) { | ||
return; | ||
} | ||
// The necessary string information must have been initialized here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it always guaranteed that during collectConstants we see a call to makeUTF8?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's right. We have an empty string literal used in the initializer of the this struct and will be seen during traversal.
if (shouldAttemptEvaluation(inst, stepEval)) { | ||
return stepEval.tryEvaluateOrElseMakeEffectsNonConstant(instI); | ||
} | ||
return stepEval.skipByMakingEffectsNonConstant(instI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this will mark the alloc_stack of the string as "unknown" if this is an unknown function which (potentially) modifies the string in the alloc_stack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. It will mark the content of the address returned by alloc_stack as unknown. The address itself is a valid symbolic value, but it contents will be unknown.
73ba475
to
b7a8dce
Compare
b7a8dce
to
a4822fd
Compare
@eeckstein Thanks for the comments. I have pushed a commit that addresses them. |
@swift-ci Please test |
Build failed |
Build failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall the implementation is very well encapsulated into functions and commented.
Some of my feedback is about style and code organization, which never needs to not hold up a PR.
(I suspect that feedback would apply to plenty of existing SIL passes).
If you make one round of answering these review questions (via code comments wherever possible) and do whatever code reorganization you think is appropriate, then you can follow up with other changes later.
@@ -307,6 +307,8 @@ PASS(SerializeSILPass, "serialize-sil", | |||
"Utility pass. Serializes the current SILModule") | |||
PASS(YieldOnceCheck, "yield-once-check", | |||
"Check correct usage of yields in yield-once coroutines") | |||
PASS(OSLogOptimization, "os-log-mandatory-optimization", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there potentially going to be an "os-log-optimization" later that is not mandatory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No there won't be any non-mandatory optimization. I will drop "mandatory" from the name.
// See https://swift.org/LICENSE.txt for license information | ||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
// | ||
//===----------------------------------------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see a file-level doc comment summarizing the purpose of the pass. It also good to put any pass-dependencies or SIL stage prerequisites here.
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The past two years happened.
|
||
// FIXME: the following two tests should produce diagnostics and are not | ||
// valid uses of the log APIs. Only string interpolation literal should be | ||
// passed to log methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this FIXME mean that these let formatString
examples won't be optimized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but actually, this case should even produce a compile-time error. The error should be on "h.log(level: .debug, formatString)", which expects the second parameter to be a string interpolation. let formatString = ...
is not by itself an error, it will not be optimized as the optimization is driven by the calls to the log
function.
// Tests for the OSLogOptimization pass that performs compile-time analysis | ||
// and optimization of the new os log prototype APIs. The tests here check | ||
// whether specific compile-time constants such as the format string, | ||
// the size of the byte buffer etc. are literals after the mandatory pipeline. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice test cases!
// Inline the log call into the caller and find the last instruction of the | ||
// log call. | ||
SILOptFunctionBuilder funcBuilder(*this); | ||
FullApplySite oslogApplySite(oslogCall); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to explicitly construct FullApplySite
. Just pass osLogCall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay
|
||
// An allocator to store symbolic values constructed during interpretation. | ||
SymbolicValueBumpAllocator allocator; | ||
ConstExprStepEvaluator constantEvaluator(allocator, firstInst->getFunction()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, every time I've tried to cheat and avoid defining a new class for the per-invocation state of the pass, I've regretted it. I always end keeping pass state in a separate class. Usually, the only thing the SILFunctionTransform does is grab any analyses that are needed and construct the pass object. e.g. SILGenCleanup.cpp and CopyPropagation.cpp.
In your case, the only state is for the constant evaluator, so I would have defined a class for the "argument evaluator" that explicitly ties the lifetime of the constantMap and constantEvaluator to the bump allocator. The fact that lifetimes are dependent is not obvious to the reader when they just happen to be defined in a certain order on the stack.
[EDIT] it turns out that there's hidden state here that lives for the duration of the compilation unit. I would have put the string intrinsic metadata cache in a separate object that other pass objects can refer back to, rather than letting that detail determine the rest of the code organization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great suggestion. As you mention in the "edit", there is a "string intrinsic metadata cache", but its lifetime can also be linked to the constant evaluator and constantMap as it is something that will be available in every invocation. So I can also move that state to a new "argument evaluator" class.
// the function body. This information is needed for constant folding string | ||
// literals and recording it will avoid reconstructing it. | ||
SILFunction *stringInitIntrinsic = nullptr; | ||
SILType stringMetatype = SILType(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was nearly done with this review before I realized that OSLogOptimization
had any state. Please, please define all data members before any instance methods!
If you want to "colocate" the data members and extractStringInfoFromInstruction
, then move them to a separate class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
|
||
// Here we have either a String decl or a Stdlib integer decl or a | ||
// builtin integer type. | ||
constantMap.insert({singleValInst, constantVal.getValue()}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to insert every symbolic constant value into another map? The lifetime of the constantEvaluator is the same as this map, so you can just reuse the map it already has. For example, to determine which instructions are String/Int, you can just keep a SmallDenseSet<SingleValueInstruction *> and reiterate over the instruction range in substituteConstants
. Or what would be really great is if you just apply the above filter inside substituteConstants
and completely eliminate constantMap
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats a good suggestion. You are right that constantEvaluator has the map. The filter can also be moved to substituteConstants
and we can eliminate the constantMap state.
assert(interpolationStruct); | ||
|
||
auto propertyDecls = interpolationStruct->getStoredProperties(); | ||
auto propertyValues = osLogInterpolationValue.getAggregateValue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overuse of auto
makes the code harder for me to follow. I only use auto in three cases:
(1) when the type name is already spelled (or mostly spelled) on the same line e.g. cast, getLoc, getFunction, getModule...
(2) when defining iterators that have long dependent type names
(3) when it is otherwise brutally obvious
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will fix this.
the new os log APIs based on string interpolation.
a4822fd
to
b7b4662
Compare
Fixing @atrick comments |
@swift-ci Please test |
This PR adds a mandatory pass for optimizing the new os_log APIs based on string interpolation. The new os_log APIs are not publicly visible and are currently in stdlib/private. The goal of this optimization pass is to extract the format string and other compile-time constants from the string interpolation and make them literals. For this purpose, this pass uses the constant evaluator and folds string- and integer-valued constants inferred by the evaluator. This PR also include compile-time tests for the optimization pass, which are an addition to the existing runtime tests for the new os log APIs.
The PR consists of three commits, the latest commit has the relevant changes. The earlier two commits corresponds to other PRs that have not yet landed: refactoring of os log library implementation and skip functionality of constant evaluation.