-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[5.6] move function upstreaming #40538
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
[5.6] move function upstreaming #40538
Conversation
… mark_unresolved_move_addr. To give a bit more information, currently the way the move function is implemented is that: 1. SILGen emits a builtin "move" that is called within the function _move in the stdlib. 2. Mandatory Inlining today if the final inlined type is address only, inlines builtin "move" as mark_unresolved_move_addr. Otherwise, if the inlined type is loadable, it performs a load [take] + move [diagnostic] + store [init]. 3. In the diagnostic pipeline before any mem optimizations have run, we run the move checker for addresses. This eliminates /all/ mark_unresolved_move_addr as part of emitting diagnostics. In order to make this work, we perform a small optimization before the checker runs that moves the mark_unresolved_move_addr from being on temporary alloc_stacks to the true base underlying address we are trying to move. This optimization is necessary since _move is generic and often times SILGen will emit this temporary that we do not want. 4. Then after we have run the guaranteed mem optimizations, we run the object based move checker emitting diagnostics. This PR changes the scheme above to the following: 1. SILGen emits a builtin "move" that is called within the function _move in the stdlib. 2. Mandatory Inlining inlines builtin "move" as mark_unresolved_move_addr. 3. In the diagnostic pipeline before we have run any mem optimizations and before we have run the actual move address checker, we massage the IR as we do above but in a separate pass where in addition we try to match this pattern: ``` %temporary = alloc_stack $LoadableType store %1 to [init] %temporary : $*LoadableType mark_unresolved_move_addr %temporary to %otherAddr : $*LoadableType destroy_addr %temporary : $*LoadableType ``` and transform it to: ``` %temporary = alloc_stack $LoadableType %2 = move_value [allows_diagnostics] %1 : $*LoadableType store %2 to [init] %temporary : $*LoadableType destroy_addr %temporary : $*LoadableType ``` ensuring that the object move checker will handle this. 4. Then after we have run the guaranteed mem optimizations, we run the object based move checker emitting diagnostics. (cherry picked from commit 4a9c26f)
This is done by extending the support in the previous PR for a. If we see the following pattern: ``` %0 = alloc_stack [lexical] $LoadableType, var ... *init of %0* ... %1 = load [copy] %0 : $*LoadableType %temporary = alloc_stack $LoadableType store %1 to [init] %temporary : $*LoadableType mark_unresolved_move_addr %temporary to %otherAddr : $*LoadableType destroy_addr %temporary : $*LoadableType ``` we transform it to: ``` %0 = alloc_stack [lexical] $LoadableType, var ... *init of %0* ... %1 = load [copy] %0 : $*LoadableType %temporary = alloc_stack $LoadableType store %2 to [init] %temporary : $*LoadableType mark_unresolved_move_addr %0 to %otherAddr : $*LoadableType destroy_addr %temporary : $*LoadableType ``` For safety reasons, we always make sure that %0 isn't destroyed in between the load [copy] and the mark_unresolved_move_addr. After this runs, we can use the address verifier on these types of vars with some additional work that I am doing in a subsequent commit. (cherry picked from commit 544b338)
…ical borrows. A lexical borrow is creating a new entity that should be checked separately from the operand of the lexical borrow. (cherry picked from commit 258986a)
This is safe to do since: 1. _move is already an always emit into client transparent function, so no ABI impact comes from it. 2. _move is underscored so it is a non-stable stdlib API meaning we aren't providing any guarantees here. 3. Code that doesn't use _move will not be effected by this so there isn't an impact on other code. (cherry picked from commit deb4541)
This assert was making sure we never saw a reborrow since they shouldn't occur in Raw SIL. Some people were reporting that they /are/ hitting this assert, so I am converting it into an early exit + skip analyzing an address. This will prevent the assertion and also will fulfill the same purpose as the original, not performing the move checking. This will still result in correctness since if we skip as a move marker instruction as a result of us skipping processing an address, the pass will emit a compile time diagnostic saying the checker wasn't able to understand the given code. So not the best, but at least if this hits a move itself we will be ok. (cherry picked from commit eb03fb6)
…an initial value. We process these as loadable vars. This is really useful since it ensures that uniqueness is preserved in this case: ``` let x: K2 do { x = self.k2 } switch _move(x)[userHandle] { case .foo: assert(_isUnique(&self.k2)) } ``` I added a test that proves this. (cherry picked from commit 396f510)
(cherry picked from commit 5aa34f4)
(cherry picked from commit dbd0b20)
… to match move checking of address only parameters. (cherry picked from commit 7b0b78d)
…nts. These are verified like a var except that the parameter will have an implicit read use on all function exit terminators. This ensures that if a programmer moves a value out of the inout parameter, a new value must be assigned before function exit. This is important since otherwise the convention would be invalidated. Example: ``` func f(_ x: inout T) { // Move a value out of x. x does not have a value within it anymore. let value = _move(x) } // So we emit an error saying there is a use here since an inout must have // a valid object within it upon function exit due to convention guarantees. ``` As an added side-effect of this, one can now use move as on self in mutating contexts. Thus one can move the self value out of the self inout binding using _move and if one does not replace self with a new value by end of function, you will get a compile time error, e.x.: ``` struct S { var buffer: Klass mutating func doSomething() { let b = move(self).buffer // ... do some stuff, maybe get a different buffer ... let maybeDifferentBuffer = maybeNewBuffer(b) // If we do not re-initialize S with a new value by uncommenting // the following line, we will get a compile time error. // self = S(differentBuffer) } } ``` (cherry picked from commit e6faa30)
…], remove allows_diagnostic flag. Just a small thing I noticed.
@swift-ci test |
Windows failure is a SourceKit test that is unrelated. |
Build failed |
macOS failed due to libswift @eeckstein |
@swift-ci test macOS platform |
Build failed |
@swift-ci test macOS platform |
@swift-ci test windows platform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I scanned through these commits. They are all 5.6 worthy.
Just upstreaming move function changes that I made on main.