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