As John said, this is out of scope for swift 4. I think it’s find to just have it in master.
Thanks again for your work! > On Jul 25, 2017, at 10:00 AM, John McCall via swift-dev <swift-dev@swift.org> > wrote: > > >> 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 _______________________________________________ swift-dev mailing list swift-dev@swift.org https://lists.swift.org/mailman/listinfo/swift-dev