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