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?

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

Reply via email to