Skip to content

[LVA] Record store as read of source for reference types. #31131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

zoecarver
Copy link
Contributor

After processing a store, if the store is alive, stops tracking the source if it's a reference type.

In the following case:

    xx = alloc_ref
    e = ref_element_addr xx
    store undef to e
    yy = alloc_ref
    h = ref_element_addr yy
    store xx to h
    use h

The first store will be eliminated because there are no reads of any of the projected aliases (xx). However, that's not true, the (second) store counts as a read. If this were a struct, the current assumption would be correct because we couldn't GEP a non-address struct (so there is no way that the source of a store could have a GEP on it). But, reference types do allow GEPs so, this is not a safe assumption.

Copy link
Contributor

@eeckstein eeckstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the intention here. Is this a bug fix?
Are you trying to handle the case where a class reference escapes through a store?
If yes, I would be surprised if this is not handled by escape/alias analysis already.

Maybe you clarify the description/comment.

%2 = alloc_ref [stack] $Foo

%4 = integer_literal $Builtin.Int64, 0
%5 = struct $Int (%4 : $Builtin.Int64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will not compile on 32-bit architectures, where Int is 32 bit. To fix this, use Int64 instead of plain Int.

@zoecarver
Copy link
Contributor Author

@eeckstein yes, precisely. That is the issue this solves. The reproducer/test that I've added fails because of the issue brought up in #31136 but, I suspect given the correct input we could get this to fail on master as well.

It is handled by analysis but (AFAICT) analysis never runs on stores/store operands.

@zoecarver
Copy link
Contributor Author

zoecarver commented May 14, 2020

@eeckstein here's another example of the same issue (not with a reference type so, this patch doesn't fix it):

class F { }

struct S {
   var f : F
}
struct U {
  var s : S
}

sil hidden [noinline] @test : $@convention(thin) (S, @inout F) -> () {
bb0(%0 : $S, %f : $*F):
  %2 = alloc_stack $S, var, name "vs"
  store %0 to %2 : $*S
  %4 = address_to_pointer %2 : $*S to $Builtin.RawPointer
  %9 = alloc_stack $U
  %10 = unchecked_addr_cast %9 : $*U to $*Builtin.RawPointer
  store %4 to %10 : $*Builtin.RawPointer
  %12 = load %9 : $*U
  %15 = struct_extract %12 : $U, #U.s
  %16 = struct_extract %15 : $S, #S.f
  dealloc_stack %9 : $*U
  store %16 to %f : $*F
  dealloc_stack %2 : $*S
  %19 = tuple ()
  return %19 : $()
}

The second store isn't counted as a read of %2 (or %4) so the first store is removed incorrectly. This fails on master.

if (!L.isMustAliasLSLocation(R, AA)) {
// If this is a reference type, then the *live* store counts as an unkown
// read.
if (L.getBase()->getType().isAnyClassReferenceType())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be unconditional.

@zoecarver zoecarver force-pushed the lva/dse-store-is-read branch from 9c732d1 to d919461 Compare May 15, 2020 03:30
After processing a store, if the store is alive, stops tracking the source if it's a reference type.

In the following case:
    xx = alloc_ref
    e = ref_element_addr xx
    store undef to e
    yy = alloc_ref
    h = ref_element_addr yy
    store xx to h
    use h
The first store will be eliminated because there are no reads of any of the projected aliases (xx). However, that's not true, the (second) store counts as a read. If this were a struct, the current assumption would be correct because we couldn't GEP a non-address struct (so there is no way that the source of a store could have a GEP on it). But, reference types do allow GEPs so, this is not a safe assumption.
@zoecarver zoecarver force-pushed the lva/dse-store-is-read branch from d919461 to 5630e54 Compare May 15, 2020 03:32
bb0(%0 : $B):
%1 = alloc_box $<τ_0_0> { var τ_0_0 } <B>
%1a = project_box %1 : $<τ_0_0> { var τ_0_0 } <B>, 0
store %0 to %1a : $*B
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We lose this optimization when the source is a reference type. I think that's a rare case but, I would like to add this back as a special-case optimization in another patch.

@zoecarver zoecarver requested a review from eeckstein May 15, 2020 03:34
@eeckstein
Copy link
Contributor

This is the wrong approach. A store is never reading memory. So we should not model it as such. The problem here is about that an address is escaping.

I'm not sure if there is really a bug currently. The SIL in your example is invalid, because it lets the address of a stack location escape after the dealloc_stack

@zoecarver
Copy link
Contributor Author

@eeckstein you're right. This is not the correct fix; it's not a read. I'll close this PR but, I think the issue is actually valid.

What's escaping (in the most recent example) is a pointer to type F, that's not ever (necessarily) deallocated. %0 holds a pointer to F which escapes out of the function in the store to %f. AFAIK the pointer in %0 could be live before and after the function. When it's no longer stored out of the function, the program may have a different behavior.

This is a very contrived example but, it shows a technically incorrect behavior. If something like #31136 lands, the problem will become much more common.

sil @store_is_read_of_src : $@convention(thin) (S, @inout F) -> () {
 bb0(%0 : $S, %f : $*F):
   %2 = alloc_stack $S, var, name "vs"
   store %0 to %2 : $*S
   %4 = address_to_pointer %2 : $*S to $Builtin.RawPointer
   %9 = alloc_stack $U
   %10 = unchecked_addr_cast %9 : $*U to $*Builtin.RawPointer
   store %4 to %10 : $*Builtin.RawPointer
   %12 = load %9 : $*U
   %15 = struct_extract %12 : $U, #U.s
   %16 = struct_extract %15 : $S, #S.f
   dealloc_stack %9 : $*U
   store %16 to %f : $*F
   dealloc_stack %2 : $*S
   %19 = tuple ()
   return %19 : $()
 }

@zoecarver zoecarver closed this May 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants