Skip to content

[DI] Fixup alloc_box borrow scope ends. #41781

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

Conversation

nate-chandler
Copy link
Contributor

After #40793, alloc_boxes all have their lifetimes protected by a lexical borrow scope. In that PR, DI had been updated to see through begin_borrow instructions from a project_box to a mark_uninitialized. It did not, however, correct the end_borrow instructions when destroy_values of mark_uninitializeds were replaced with destroy_addrs of project_boxes. That is done here.

In a bit more detail, in the following context

%box = alloc_box
%mark_uninit = mark_uninitialized %box
%lifetime = begin_borrow [lexical] %mark_uninit
%proj_box = project_box %lifetime

When it is not statically known whether a field is initialized, we arereplacing the instruction

// before
destroy_value %mark_uninit
// after

with the following diamond

  // before
  %initialized = load
  cond_br %initialized, yes, no

yes:
  destroy_addr %proj_box
  br bottom

no:
  br bottom

bottom:
  dealloc_box %box
  br keep_going

keep_going:
  // after

Doing so is problematic, though, because by SILGen construction the destroy_value is always preceded by an end_borrow:

end_borrow %lifetime
destroy_value %mark_uninit

Previously, that end_borrow remained above the

%initialized = load

instruction in the above. That was invalid because the the newly introduced

destroy_addr %proj_box

was a use of the borrow scope (%proj_box is a projection of the begin_borrow) and consequently must be within the borrow scope.

Note also that it would not be sufficient to simply emit the diamond before the end_borrow. The end_borrow must come before the destruction of the value whose lifetime it is protecting (%box), and the diamond contains the instruction to destroy that value (dealloc_box) in its bottom block.

To resolve this issue, just move the end_borrow instruction from where it was to before the dealloc box. (This is actually done by moving it to the top of the diamond's "continue" block prior to the emission of that dealloc_box instruction.)

rdar://89984216

@nate-chandler nate-chandler requested a review from atrick March 11, 2022 04:30
@nate-chandler
Copy link
Contributor Author

@swift-ci please test

@nate-chandler
Copy link
Contributor Author

@swift-ci please test source compatibility

Copy link
Contributor

@atrick atrick left a comment

Choose a reason for hiding this comment

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

I think this fix can go in as is. But I have some follow-up questions

auto *Previous = Release->getPreviousInstruction();

Does this create an implicit coupling between SILGen and DI? What guarantees that the end_borrow/destroy_value are adjacent? How would someone working on SILGen know that, and what ensures that they stay adjacent? Normally any correctness assumption made across passes should be checked in SILVerifier. It may be reasonable to assume the end_borrow and destroy_value are in the same block, which is not much harder to handle.

              ebi->moveBefore(&*B.getInsertionPoint());
             }
           }
         }
       }

This small closure now has a couple pages of comments within 5 levels of nested control flow. So I can't see at a glance what the original closure as a whole or the surrounding code does. This could all be in an adjustEndBorrow helper with the comments outside the function and no control flow nesting.

@nate-chandler nate-chandler force-pushed the lexical_lifetimes/di-moves-end_borrow branch from 1ab460b to f5d4e54 Compare March 11, 2022 16:26
@nate-chandler
Copy link
Contributor Author

@atrick Added a function adjustAllocBoxEndBorrow which

  • scans backwards in the current block from the instruction prior to the release for a matching end_borrow
  • doesn't nest
  • has the highlevel explanatory comment outside

After swiftlang#40793, alloc_boxes all have
their lifetimes protected by a lexical borrow scope.  In that PR, DI had
been updated to see through begin_borrow instructions from a project_box
to a mark_uninitialized.  It did not, however, correct the end_borrow
instructions when destroy_values of mark_uninitializeds were replaced
with destroy_addrs of project_boxes.  That is done here.

In a bit more detail, in the following context

    %box = alloc_box
    %mark_uninit = mark_uninitialized %box
    %lifetime = begin_borrow [lexical] %mark_uninit
    %proj_box = project_box %lifetime

When it is not statically known whether a field is initialized, we are
replacing the instruction

      // before
      destroy_value %mark_uninit
      // after

with the following diamond

      // before
      %initialized = load
      cond_br %initialized, yes, no

    yes:
      destroy_addr %proj_box
      br bottom

    no:
      br bottom

    bottom:
      dealloc_box %box
      br keep_going

    keep_going:
      // after

Doing so is problematic, though, because by SILGen construction the
destroy_value is always preceded by an end_borrow:

      end_borrow %lifetime
      destroy_value %mark_uninit

Previously, that end_borrow remained above the

      %initialized = load

instruction in the above.  That was invalid because the the newly
introduced

      destroy_addr %proj_box

was a use of the borrow scope (%proj_box is a projection of the
begin_borrow) and consequently must be within the borrow scope.

Note also that it would not be sufficient to simply emit the diamond
before the end_borrow.  The end_borrow must come before the destruction
of the value whose lifetime it is protecting (%box), and the diamond
contains the instruction to destroy that value (dealloc_box) in its
bottom block.

To resolve this issue, just move the end_borrow instruction from where
it was to before the dealloc box.  (This is actually done by moving it to
the top of the diamond's "continue" block prior to the emission of that
dealloc_box instruction.)

rdar://89984216
@nate-chandler nate-chandler force-pushed the lexical_lifetimes/di-moves-end_borrow branch from f5d4e54 to 05e643b Compare March 11, 2022 16:29
@nate-chandler
Copy link
Contributor Author

@swift-ci please test

@nate-chandler
Copy link
Contributor Author

@swift-ci please clean test linux platform

@nate-chandler
Copy link
Contributor Author

@swift-ci please test linux platform

@nate-chandler
Copy link
Contributor Author

@swift-ci please clean test linux platform

@nate-chandler nate-chandler merged commit 58b414d into swiftlang:main Mar 11, 2022
@nate-chandler nate-chandler deleted the lexical_lifetimes/di-moves-end_borrow branch March 11, 2022 21:15
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