> 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;
> +
> +    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

Reply via email to