> On Jul 25, 2017, at 12:55 PM, Johannes Weiß via swift-dev > <swift-dev@swift.org> wrote: > > Thanks very much Eric & Michael for your help! And thanks for merging it. > Will this automatically be part of Swift 4 or do I need to make another PR > against a swift 4 branch?
It's very hard to imagine we would take any optimizer work this late for Swift 4. John. > > Thanks, > Johannes > >> On 18 Jul 2017, at 9:21 pm, Johannes Weiß via swift-dev >> <swift-dev@swift.org> wrote: >> >> great, thank you. I think I have worked all the suggestions in, let's do the >> rest in this PR: https://github.com/apple/swift/pull/11040 >> >> It's getting late here so I'll probably to the test tomorrow (but marked the >> patch as 'do not merge' & added that a test is still missing). >> >>> On 18 Jul 2017, at 7:29 pm, Erik Eckstein <eeckst...@apple.com> wrote: >>> >>>> >>>> On Jul 18, 2017, at 10:40 AM, Johannes Weiß <johanneswe...@apple.com> >>>> wrote: >>>> >>>> Thanks, both suggestions look great. Will work them in tomorrow and will >>>> also try to add a test for the whole thing. >>>> >>>>> On 18 Jul 2017, at 5:53 pm, Michael Gottesman <mgottes...@apple.com> >>>>> wrote: >>>>> >>>>>> >>>>>> On Jul 18, 2017, at 8:39 AM, Johannes Weiß <johanneswe...@apple.com> >>>>>> wrote: >>>>>> >>>>>> Hi Erik, >>>>>> >>>>>>> On 17 Jul 2017, at 10:26 pm, Erik Eckstein <eeckst...@apple.com> wrote: >>>>>>> >>>>>>> Hi Johannes, >>>>>>> >>>>>>> great that you want to work on this! >>>>>> >>>>>> Thanks for your help, without Michael's and your help I wouldn't have >>>>>> been able to do anything here really! >>>>>> >>>>>> >>>>>>> Some ideas: >>>>>>> SideEffectAnalysis currently does not have a notion of “this argument >>>>>>> is not modified by the callee” if the callee is unknown or does >>>>>>> anything non-trivial. >>>>>>> Therefore I think it’s best to put the in_guarantee check directly into >>>>>>> MemoryBehaviorVisitor::visitApplyInst(): >>>>>>> If the parameter convention is in_guaranteed and the parameter is V, >>>>>>> then the behavior can be MemBehavior::MayRead >>>>>> >>>>>> >>>>>> Thanks, that actually makes a lot of sense. Here's my diff right now >>>>>> >>>>>> diff --git a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp >>>>>> b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp >>>>>> index b1fe7fa665..c44cc64f94 100644 >>>>>> --- a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp >>>>>> +++ b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp >>>>>> @@ -245,6 +245,23 @@ MemBehavior >>>>>> MemoryBehaviorVisitor::visitApplyInst(ApplyInst *AI) { >>>>>> (InspectionMode == RetainObserveKind::ObserveRetains && >>>>>> ApplyEffects.mayAllocObjects())) { >>>>>> Behavior = MemBehavior::MayHaveSideEffects; >>> >>> You should move your new code out of the if (ApplyEffects.mayReadRC() ... >>> Otherwise it will not be executed if the called function does not read a >>> reference count. >>> >>> Beside that it looks good to me. >>> >>>>>> + >>>>>> + unsigned Idx = 0; >>>>>> + bool AllReadOnly = false; >>>>>> + for (Operand &operand : AI->getArgumentOperands()) { >>>>>> + if (operand.get() == V && >>>>>> AI->getOrigCalleeType()->getParameters()[Idx].isIndirectInGuaranteed()) { >>>>>> + AllReadOnly = true; >>>>>> + } else { >>>>>> + AllReadOnly = false; >>>>>> + break; >>>>>> + } >>>>>> + >>>>>> + Idx++; >>>>>> + } >>>>>> + >>>>>> + if (AllReadOnly) { >>>>>> + Behavior = MemBehavior::MayRead; >>>>>> + } >>>>> >>>>> Suggestion: >>>>> >>>>> if (all_of(enumerate(AI->getArgumentOperands()), >>>>> [&](std::pair<unsigned, SILValue> pair) -> bool { >>>>> return pair.second.get() == V && >>>>> AI->getOrigCalleeType()->getParameters()[Idx].isIndirectInGuaranteed() >>>>> })) { >>>>> Behavior = MemBehavior::MayRead; >>>>> } >>>>> >>>>> I may have gotten the order of the pair templates wrong. But something >>>>> like this is a little cleaner. >>>>> >>>>> Michael >>>>> >>>>>> } else { >>>>>> auto &GlobalEffects = ApplyEffects.getGlobalEffects(); >>>>>> Behavior = GlobalEffects.getMemBehavior(InspectionMode); >>>>>> >>>>>> which indeed turns >>>>>> >>>>>> --- SNIP --- >>>>>> sil @bar : $@convention(thin) (@in Int) -> () { >>>>>> bb0(%0 : $*Int): >>>>>> %value_raw = integer_literal $Builtin.Int64, 42 >>>>>> %value = struct $Int (%value_raw : $Builtin.Int64) >>>>>> store %value to %0 : $*Int >>>>>> >>>>>> %f_buz = function_ref @buz : $@convention(thin) (@in_guaranteed Int) -> >>>>>> () >>>>>> %r1 = apply %f_buz(%0) : $@convention(thin) (@in_guaranteed Int) -> () >>>>>> >>>>>> %value_again = load %0 : $*Int >>>>>> %f_test = function_ref @test : $@convention(thin) (Int) -> () >>>>>> %r2 = apply %f_test(%value_again) : $@convention(thin) (Int) -> () >>>>>> >>>>>> /* >>>>>> %f_bad = function_ref @bad : $@convention(thin) (@in_guaranteed Int) -> >>>>>> () >>>>>> %r3 = apply %f_bad(%0) : $@convention(thin) (@in_guaranteed Int) -> () >>>>>> >>>>>> %value_again2 = load %0 : $*Int >>>>>> %r4 = apply %f_test(%value_again2) : $@convention(thin) (Int) -> () >>>>>> */ >>>>>> >>>>>> %9999 = tuple() >>>>>> return %9999 : $() >>>>>> } >>>>>> --- SNAP --- >>>>>> >>>>>> into >>>>>> >>>>>> --- SNIP --- >>>>>> bb0(%0 : $*Int): >>>>>> %1 = integer_literal $Builtin.Int64, 42 // user: %2 >>>>>> %2 = struct $Int (%1 : $Builtin.Int64) // users: %7, %3 >>>>>> store %2 to %0 : $*Int // id: %3 >>>>>> // function_ref buz >>>>>> %4 = function_ref @buz : $@convention(thin) (@in_guaranteed Int) -> () >>>>>> // user: %5 >>>>>> %5 = apply %4(%0) : $@convention(thin) (@in_guaranteed Int) -> () >>>>>> // function_ref test >>>>>> %6 = function_ref @test : $@convention(thin) (Int) -> () // user: %7 >>>>>> %7 = apply %6(%2) : $@convention(thin) (Int) -> () >>>>>> %8 = tuple () // user: %9 >>>>>> return %8 : $() // id: %9 >>>>>> } // end sil function 'bar' >>>>>> --- SNAP --- >>>>>> >>>>>> so the load has successfully been eliminated. Also taking my initial >>>>>> repro [1], the retain, the load, and the release are now gone 😃. >>>>>> >>>>>> Is that roughly what you were thinking of? >>>>>> >>>>>> Many thanks, >>>>>> Johannes >>>>>> >>>>>> [1]: https://bugs.swift.org/secure/attachment/12378/test.swift >>>>>> >>>>>>> >>>>>>> Erik >>>>>>> >>>>>>>> On Jul 17, 2017, at 9:23 AM, Johannes Weiß <johanneswe...@apple.com> >>>>>>>> wrote: >>>>>>>> >>>>>>>> Thanks Michael! >>>>>>>> >>>>>>>> Erik, I hope what I wrote makes some sense. Any questions or >>>>>>>> suggestions, please let me know. >>>>>>>> >>>>>>>>> On 14 Jul 2017, at 7:30 pm, Michael Gottesman <mgottes...@apple.com> >>>>>>>>> wrote: >>>>>>>>> >>>>>>>>>> >>>>>>>>>> On Jul 12, 2017, at 9:53 AM, Johannes Weiß <johanneswe...@apple.com> >>>>>>>>>> wrote: >>>>>>>>>> >>>>>>>>>> Hi Michael, >>>>>>>>>> >>>>>>>>>> >>>>>>>>>>> [...] >>>>>>>>>>>> Long story short, I think the RLE actually works for the test case >>>>>>>>>>>> I created. It's even clever enough to see through my invalid >>>>>>>>>>>> function bad() which modified the storage despite its claim that >>>>>>>>>>>> it doesn't. I might also be misunderstanding something. >>>>>>>>>>> >>>>>>>>>>> When something is marked as in_guaranteed, it should be immutable. >>>>>>>>>>> If the callee violates that, then the SIL is malformed. Perhaps, we >>>>>>>>>>> can add some sort of verification check. >>>>>>>>>> >>>>>>>>>> Right, yes I was aware that that'd be illegal but I added it as a >>>>>>>>>> way to check whether this optimisation is 'looking into' the called >>>>>>>>>> function. >>>>>>>>>> >>>>>>>>>> >>>>>>>>>>> That being said, I have a good feeling that there is some sort of >>>>>>>>>>> analysis occuring here since you provided enough information to the >>>>>>>>>>> optimizer. The optimization here is regardless of whether or not we >>>>>>>>>>> can see the body of a function, we know that it is safe to optimize >>>>>>>>>>> this just based off the @in_guaranteed. This implies using a >>>>>>>>>>> declaration, not a definition of the bad function. >>>>>>>>>> >>>>>>>>>> makes sense, didn't think about just only declaring it... >>>>>>>>>> >>>>>>>>>> >>>>>>>>>>> When I said write the SIL by hand, what I meant was writing the >>>>>>>>>>> whole program by hand. In general, we prefer SIL programs that do >>>>>>>>>>> not have extraneous stuff that is added by the compiler (for >>>>>>>>>>> instance debug_value). Additionally, it is important for SIL files >>>>>>>>>>> to not have dependencies on the stdlib unless absolutely necessary >>>>>>>>>>> (i.e. your usage of Int). This prevents the stdlib maintainers from >>>>>>>>>>> having to update these tests given chances to the stdlib. Below is >>>>>>>>>>> a cleaned up version that shows the problem. Look at how small it >>>>>>>>>>> is and how it tests /exactly/ what we are trying to test and via >>>>>>>>>>> the use of declarations and the like we are able to exclude other >>>>>>>>>>> optimizations: >>>>>>>>>> >>>>>>>>>> That makes a lot of sense, thank you. I wasn't yet that familiar >>>>>>>>>> with SIL so I thought I start from a program generated by the >>>>>>>>>> compiler and then replace the guts with handwritten SIL. But your >>>>>>>>>> version is clearly a lot easier to understand and shows what >>>>>>>>>> precisely we want to see! >>>>>>>>>> >>>>>>>>>> Today, I looked into why this is happening more precisely. >>>>>>>>>> >>>>>>>>>> So we don't get the RLE because in this code >>>>>>>>>> >>>>>>>>>> if (isComputeAvailValue(Kind) || isPerformingRLE(Kind)) { >>>>>>>>>> for (unsigned i = 0; i < Locs.size(); ++i) { >>>>>>>>>> if (isTrackingLocation(ForwardSetIn, Ctx.getLocationBit(Locs[i]))) >>>>>>>>>> continue; >>>>>>>>>> updateForwardSetAndValForRead(Ctx, Ctx.getLocationBit(Locs[i]), >>>>>>>>>> Ctx.getValueBit(Vals[i])); >>>>>>>>>> // We can not perform the forwarding as we are at least missing >>>>>>>>>> // some pieces of the read location. >>>>>>>>>> CanForward = false; >>>>>>>>>> >>>>>>>>>> (https://github.com/apple/swift/blob/86620aaa7ebd32d33f4cdf61add5c63a72d3f02a/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp#L917) >>>>>>>>>> >>>>>>>>>> we're not taking the `continue` for the call to `buz()`. The reason >>>>>>>>>> why is that here >>>>>>>>>> >>>>>>>>>> if (!AA->mayWriteToMemory(I, R.getBase())) >>>>>>>>>> continue; >>>>>>>>>> // MayAlias. >>>>>>>>>> stopTrackingLocation(ForwardSetIn, i); >>>>>>>>>> stopTrackingValue(ForwardValIn, i); >>>>>>>>>> >>>>>>>>>> (https://github.com/apple/swift/blob/86620aaa7ebd32d33f4cdf61add5c63a72d3f02a/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp#L972) >>>>>>>>>> >>>>>>>>>> we're not taking the `continue`, ie. `AA->mayWriteToMemory(I, >>>>>>>>>> R.getBase())` is true. The reason for that is that the `SILFunction` >>>>>>>>>> for `buz` has >>>>>>>>>> >>>>>>>>>> EffectsKindAttr = Unspecified >>>>>>>>>> >>>>>>>>>> which equates to `MayHaveSideEffects`, that's also what >>>>>>>>>> `-debug-only=sil-redundant-load-elim,sil-membehavior` outputs: >>>>>>>>>> >>>>>>>>>> GET MEMORY BEHAVIOR FOR: >>>>>>>>>> %5 = apply %4(%0) : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>> %0 = argument of bb0 : $*Int // users: %6, %5, %3 >>>>>>>>>> Found apply, returning MayHaveSideEffects >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> So where I'm stuck today is that I'm not sure how `EffectsKindAttr` >>>>>>>>>> is actually defined. Sure, `$@convention(thin) (@in_guaranteed Int) >>>>>>>>>> -> ()` doesn't actually write to the `@in_guaranteed Int` (as that'd >>>>>>>>>> be illegal) but it may have other side effects. So I'm not sure if >>>>>>>>>> we can just create the function differently if we find only >>>>>>>>>> "read-only" kind of parameters. That'd be I think in >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> auto *fn = SILMod.createFunction(SILLinkage::Private, Name.str(), Ty, >>>>>>>>>> nullptr, loc, IsNotBare, >>>>>>>>>> IsNotTransparent, IsNotSerialized); >>>>>>>>>> >>>>>>>>>> (https://github.com/apple/swift/blob/ec6fc4d54db95f78ae72dab29734533f709ea2d7/lib/Parse/ParseSIL.cpp#L508 >>>>>>>>>> -> >>>>>>>>>> https://github.com/apple/swift/blob/157db57506b813837481b574a9d38e806bf954b6/lib/SIL/SILModule.cpp#L249) >>>>>>>>>> >>>>>>>>>> which doesn't specify any EffectsAttrKind and therefore it defaults >>>>>>>>>> to `Unspecified`. >>>>>>>>>> >>>>>>>>>> >>>>>>>>>> Just as a test, I did put a `[readonly]` in `sil @buz : >>>>>>>>>> $@convention(thin) (@in_guaranteed Int) -> ()` and as expected >>>>>>>>>> everything propagates through correctly and we get a successful RLE. >>>>>>>>>> >>>>>>>>>> So yes, maybe you have some pointers on where to best educate the >>>>>>>>>> compiler that the `buz` function won't write to that bit of memory. >>>>>>>>> >>>>>>>>> I have a few ideas of where to put it, but really the person to bring >>>>>>>>> in here is Erik. He is the one who wrote the side-effect part of the >>>>>>>>> optimizer. Keep in mind he is on vacation right now until next week. >>>>>>>>> So I wouldn't expect a response until then. >>>>>>>>> >>>>>>>>> Michael >>>>>>>>> >>>>>>>>>> >>>>>>>>>> Many thanks, >>>>>>>>>> Johannes >>>>>>>>>> >>>>>>>>>>> >>>>>>>>>>> ---- >>>>>>>>>>> sil_stage canonical >>>>>>>>>>> >>>>>>>>>>> import Builtin >>>>>>>>>>> >>>>>>>>>>> struct Int { >>>>>>>>>>> var _value : Builtin.Int64 >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> sil @test : $@convention(thin) (Int) -> () >>>>>>>>>>> sil @buz : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>>> sil @bad : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>>> >>>>>>>>>>> sil @bar : $@convention(thin) (@in Int) -> () { >>>>>>>>>>> bb0(%0 : $*Int): >>>>>>>>>>> %value_raw = integer_literal $Builtin.Int64, 42 >>>>>>>>>>> %value = struct $Int (%value_raw : $Builtin.Int64) >>>>>>>>>>> store %value to %0 : $*Int >>>>>>>>>>> >>>>>>>>>>> %f_buz = function_ref @buz : $@convention(thin) (@in_guaranteed >>>>>>>>>>> Int) -> () >>>>>>>>>>> %r1 = apply %f_buz(%0) : $@convention(thin) (@in_guaranteed Int) -> >>>>>>>>>>> () >>>>>>>>>>> >>>>>>>>>>> %value_again = load %0 : $*Int >>>>>>>>>>> %f_test = function_ref @test : $@convention(thin) (Int) -> () >>>>>>>>>>> %r2 = apply %f_test(%value_again) : $@convention(thin) (Int) -> () >>>>>>>>>>> >>>>>>>>>>> %f_bad = function_ref @bad : $@convention(thin) (@in_guaranteed >>>>>>>>>>> Int) -> () >>>>>>>>>>> %r3 = apply %f_bad(%0) : $@convention(thin) (@in_guaranteed Int) -> >>>>>>>>>>> () >>>>>>>>>>> >>>>>>>>>>> %value_again2 = load %0 : $*Int >>>>>>>>>>> %r4 = apply %f_test(%value_again2) : $@convention(thin) (Int) -> () >>>>>>>>>>> >>>>>>>>>>> %9999 = tuple() >>>>>>>>>>> return %9999 : $() >>>>>>>>>>> } >>>>>>>>>>> ---- >>>>>>>>>>> >>>>>>>>>>> When I run this test file through rle, I get: >>>>>>>>>>> >>>>>>>>>>> ---- >>>>>>>>>>> sil_stage canonical >>>>>>>>>>> >>>>>>>>>>> import Builtin >>>>>>>>>>> import Swift >>>>>>>>>>> import SwiftShims >>>>>>>>>>> >>>>>>>>>>> struct Int { >>>>>>>>>>> @sil_stored var _value: Builtin.Int64 >>>>>>>>>>> init(_value: Builtin.Int64) >>>>>>>>>>> } >>>>>>>>>>> >>>>>>>>>>> // test >>>>>>>>>>> sil @test : $@convention(thin) (Int) -> () >>>>>>>>>>> >>>>>>>>>>> // buz >>>>>>>>>>> sil @buz : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>>> >>>>>>>>>>> // bad >>>>>>>>>>> sil @bad : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>>> >>>>>>>>>>> // bar >>>>>>>>>>> sil @bar : $@convention(thin) (@in Int) -> () { >>>>>>>>>>> // %0 // users: %11, >>>>>>>>>>> %10, %6, %5, %3 >>>>>>>>>>> bb0(%0 : $*Int): >>>>>>>>>>> %1 = integer_literal $Builtin.Int64, 42 // user: %2 >>>>>>>>>>> %2 = struct $Int (%1 : $Builtin.Int64) // user: %3 >>>>>>>>>>> store %2 to %0 : $*Int // id: %3 >>>>>>>>>>> // function_ref buz >>>>>>>>>>> %4 = function_ref @buz : $@convention(thin) (@in_guaranteed Int) -> >>>>>>>>>>> () // user: %5 >>>>>>>>>>> %5 = apply %4(%0) : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>>> %6 = load %0 : $*Int // user: %8 >>>>>>>>>>> // function_ref test >>>>>>>>>>> %7 = function_ref @test : $@convention(thin) (Int) -> () // users: >>>>>>>>>>> %12, %8 >>>>>>>>>>> %8 = apply %7(%6) : $@convention(thin) (Int) -> () >>>>>>>>>>> // function_ref bad >>>>>>>>>>> %9 = function_ref @bad : $@convention(thin) (@in_guaranteed Int) -> >>>>>>>>>>> () // user: %10 >>>>>>>>>>> %10 = apply %9(%0) : $@convention(thin) (@in_guaranteed Int) -> () >>>>>>>>>>> %11 = load %0 : $*Int // user: %12 >>>>>>>>>>> %12 = apply %7(%11) : $@convention(thin) (Int) -> () >>>>>>>>>>> %13 = tuple () // user: %14 >>>>>>>>>>> return %13 : $() // id: %14 >>>>>>>>>>> } // end sil function 'bar' >>>>>>>>>>> ---- >>>>>>>>>>> >>>>>>>>>>> Michael >>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>> Does that all make sense? >>>>>>>>>>>> >>>>>>>>>>>> Thanks, >>>>>>>>>>>> Johannes >>>>>>>>>>>> <test-load-forwarding.sil><test-load-forwarding.sil-opt> >>>>>>>>>>>> >>>>>>>>>>>> >>>>>>>>>>>>> >>>>>>>>>>>>>> >>>>>>>>>>>>>> Thanks, >>>>>>>>>>>>>> Johannes >>>>>>>>>>>>>> >>>>>>>>>>>>>> [1]: https://bugs.swift.org/browse/SR-5403 >> >> _______________________________________________ >> swift-dev mailing list >> swift-dev@swift.org >> https://lists.swift.org/mailman/listinfo/swift-dev > > _______________________________________________ > swift-dev mailing list > swift-dev@swift.org > https://lists.swift.org/mailman/listinfo/swift-dev _______________________________________________ swift-dev mailing list swift-dev@swift.org https://lists.swift.org/mailman/listinfo/swift-dev