-
Notifications
You must be signed in to change notification settings - Fork 10.5k
StringOptimization: handle static let variables for String constant folding. #33232
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
Conversation
@swift-ci test |
@swift-ci benchmark |
Performance: -O
Code size: -OPerformance: -Osize
Code size: -OsizePerformance: -Onone
Code size: -swiftlibsHow to read the dataThe tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.If you see any unexpected regressions, you should consider fixing the Noise: Sometimes the performance results (not code size!) contain false Hardware Overview
|
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.
I'm not really familiar with StringOptimizer, but this basically looks fine.
These pass pipeline changes conflict with the changes I've been trying to make for a long time
#32659
Essentially, we should not destroy semantics in any way during the mid-level pipeline stage. So semantics calls, global initializers, and semantics types should only be inlined or expanded by the low-level pipeline stage as part of lowering. The "high level" pipeline stage should only be for things that need to run before serialization. I'm not sure if StringOptimizer should run before serialization.
"Unexpected promotion of address-only type!"); | ||
|
||
assert(NewValue && "Expected a value to release!"); | ||
assert(NewValue || (Ty.is<TupleType>() && Ty.getAs<TupleType>()->getNumElements() == 0)); |
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 looks like this assert is supposed to protect the call to emitLoweredDestroyValue
. I don't think a new destroy should be created for an invalid SILValue.
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.
This assert makes sure that a value is actually written to the location before it is destroyed.
But this is not required for empty tuples.
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 Disabling this assert means that an invalid SILValue is passed to emitLoweredDestroyValue
. That is incorrect.
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.
emitLoweredDestroyValue is a no-op for an empty tuple.
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 get that it might happen to work, or might not. I'm just being picky about something trivial to make a point about interfaces. emitLoweredDestroyValue does not specify that it can take an invalid SILValue, and there's no single header-level implementation to check, so to anyone trying to understand this code it is incorrect at face value. It doesn't matter whether emitLoweredDestroyValue is actually going to emit an instruction or not. In general, if an interface can accept an invalid SILValue that needs to be specified in the interface. The same is true for accepting null pointers.
i.e. clearly written code would look like
if (!newValue) {
assert(Ty.is<Tuple...)
return
}
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, that's a good point
@@ -290,7 +290,11 @@ void addFunctionPasses(SILPassPipelinePlan &P, | |||
P.addLowerAggregateInstrs(); | |||
|
|||
// Split up operations on stack-allocated aggregates (struct, tuple). | |||
P.addSROA(); | |||
if (OpLevel == OptimizationLevelKind::HighLevel) { |
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 think semantics should be preserved throughout the mid-level pipeline, and the low-level SIL pipeline should just be a series of lowering passes... although this is fine for now
@@ -493,6 +493,8 @@ static void addHighLevelFunctionPipeline(SILPassPipelinePlan &P) { | |||
addFunctionPasses(P, OptimizationLevelKind::HighLevel); | |||
|
|||
addHighLevelLoopOptPasses(P); | |||
|
|||
P.addStringOptimization(); |
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 string optimization need to be done before serialization for some reason? If not, I think it should stay in the mid-level pipeline an SROA can avoid splitting them up into low-level "lowering". Does splitting Strings up enable other mid-level 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.
Global initializers should not be optimized in mid-level SIL at all
I fixed this here, which is now in @meg-gupta 's PR, but it still hasn't been merged
7c9e8b8
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 doesn't need to be before Serialization, it just has to be before the mid-level pipeline: the reason is that, even if StringOptimization is at the begin of the mid-level function pipeline, it can be that the whole mid-level function pipeline already has run for the global initializer (which means that the string init is inlined). And then the optimization cannot analyze the initializer anymore.
As we don't have a function-pipeline between serialization and the midlevel function pipeline, I put it into the high-level function pipeline.
@meg-gupta Feel free to change that in 7c9e8b8. The pipeline just needs to make sure that all string operations are not inlined (including in the initializers) when StringOptimization runs.
StringOptimization::StringInfo | ||
StringOptimization::getStringFromStaticLet(SILValue value) { | ||
// Match the pattern | ||
// %ptr_to_global = apple %addressor() |
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.
s/apple/apply
An empty tuple doesn't need to be assigned before it is passed to destroy_addr. I found this by experimenting with the pass pipeline. Not sure if this crash can happen with the current pipeline.
…sn't split String types. The StringOptimization relies on seeing String values a a whole and not being split.
Needed to make sure that global initializers are not optimized in mid-level SIL while other functions are still in high-level SIL. Having the StringOptimization not in high-level SIL was just a mistake in my earlier PR.
…olding. For example, constant fold: struct Str { static let s = "hello" } ... let x = "<\(Str.s)>"
e137173
to
e3f3b75
Compare
@swift-ci smoke test |
1 similar comment
@swift-ci smoke test |
For example, constant fold:
This PR also contains some other minor changes which are required for this optimization. For details see the commit list.