Required to fix SILCombine.
Divide the logic into smaller pieces. This allows passes to check for
replaceability before generating the replacement value.
Preparation for simplifying OSSA utilities into smaller logical
components making them flexibile and allowing improvements to be
staged in.
Preparation for rewriting non-trivial terminators and generalizing
support for guaranteed phis.
Add guaranteedUsePoints to the RAUW context. This will replace ALL
existing context book-keeping once the old code is deleted.
Introduce a borrowCopyOverScope entry point to handle extending
lifetime over a BorrowedValue. This simply uses the
BorrowedLifetimeExtender.
Introduce higher-level APIs:
- borrowOverValue to extened over a guaranteedValue
- borrowOverSingleUse to extened over a single guaranteed use
These replace both createPlusZeroBorrow and createPlusOneBorrow.
Update RAUW-ctor, RAUW::handleUnowned, and replaceAddressUses to use
the new API.
Restructure RAUW::canFixUpOwnershipForRAUW. Simply use
findInnerTransitiveGuaranteedUses.
Replace RAUW::handleGuaranteed and rewriteReborrows with
OLE::borrowOverValue.
Use the BorrowedLifetimeExtender utility to handle all situations
correctly.
TODO: createPlusOneBorrow can be completely removed, and a massive
amount of confusing/incomplete code can be deleted in a follow-up
commit.
Without introducing any new borrow scopes or owned lifetimes.
Top-level APIs:
- extendOwnedLifetime()
- extendLocalBorrow()
New utilitiy: GuaranteedOwnershipExtension.
This is a simple utility to determine whether new uses can be added to
an existing guaranteed value. It reports the kind of transformation
needed and performs the transformation if requested. If transformation
is needed, it simply calls one of the two top-level APIs.
Allow quickly checking for valid OSSA value substitution independent
from information about the value's lifetime or scope. Make it a static
member to allow this to check to be done outside of the RAUW
utility. e.g. from SimplifyCFG.
This mainly simplifies the utility, but also improves optimization as
a side effect.
Update OSSA RAUW after replacing BorrowedAddress with AddressOwnership.
InteriorPointer is no longer needed. This simplifies the fixup
context. Eventually the fixup context will be very lightweight. This
is just the first step.
Handle SSA update (phi creation) when extending an owned lifetime over
a borrowed lifetime.
This is a layer of logic above BorrowedValue but below
OwnershipLifetimeExtender and other higher-level utilities.
Ownership rauw uses a shared ownership fixup context to maintain state.
When ownership rauw fails, due to some invalid condition, we leave
behind stale data in this shared ownership fixup context.
This stale context can indvertantly affect the next rauw on addresses.
In addition to setting the ownership fixup context to nullptr, we
should also clear it so that it's internal data structures are
cleared.
This API is useful when writing compiler code that needs to handle ossa/non-ossa
as well as load_borrow/load while in OSSA. I am going to use this in
SILMem2Reg.
This directly adds support to BasicBlockCloner for updating OSSA.
It also adds a much more general-purpose GuaranteedPhiBorrowFixup
utility which can be used for more complicated SSA updates, in which
multiple phis need to be created. More generally, it handles adding
nested borrow scopes for guaranteed phis even when that phi is used by
other guaranteed phis.
Instead make `findJointPostDominatingSet` a stand-alone function.
There is no need to keep the temporary SmallVector alive across multiple calls of findJointPostDominatingSet for the purpose of re-using malloc'ed memory. The worklist usually contains way less elements than its small size.
This is necessitated by the SILArgument representation. It has the
tragic property that adding unrelated phis invalidates existing
phis. Therefore, the optimizer can't do book-keeping of phi values by
refering directly to SILValues or Operands. Instead, it must only
refer to SILBasicBlocks and argument indices.
Some notes:
1. I moved the identity round-trip case to InstSimplify since that is where
optimizations like that are.
2. I did not update in this commit the code that eliminates convert_function
when it is only destroyed. In a subsequent commit I am going to implement
that in a general way and apply it to all forwarding instructions.
3. I implemented eliminating convert_function with ownership only uses in a
utility so that I can reuse it for other similar optimizations in SILCombine.
Instead of saving BorrowingOperand on the context save the SILBasicBlock
and index of the terminator operand.
This avoids the use-after-free in eliminateReborrowsOfRecursiveBorrows.
Previously, eliminateReborrowsOfRecursiveBorrows called helper
insertOwnedBaseValueAlongBranchEdge, which can delete a branch
instruction (reborrow) that could have been cached in
recursiveReborrows.
In OSSA, we enforce that addresses from interior pointer instructions are scoped
within a borrow scope. This means that it is invalid to use such an address
outside of its parent borrow scope and as a result one can not just RAUW an
address value by a dominating address value since the latter may be invalid at
the former. I foresee that I am going to have to solve this problem and so I
decided to write this API to handle the vast majority of cases.
The way this API works is that it:
1. Computes an access path with base for the new value. If we do not have a base
value and a valid access path with root, we bail.
2. Then we check if our base value is the result of an interior pointer
instruction. If it isn't, we are immediately done and can RAUW without further
delay.
3. If we do have an interior pointer instruction, we see if the immediate
guaranteed value we projected from has a single borrow introducer value. If not,
we bail. I think this is reasonable since with time, all guaranteed values will
always only have a single borrow introducing value (once struct, tuple,
destructure_struct, destructure_tuple become reborrows).
4. Then we gather up all inner uses of our access path. If for some reason that
fails, we bail.
5. Then we see if all of those uses are within our borrow scope. If so, we can
RAUW without any further worry.
6. Otherwise, we perform a copy+borrow of our interior pointer's operand value
at the interior pointer, create a copy of the interior pointer instruction upon
this new borrow and then RAUW oldValue with that instead. By construction all
uses of oldValue will be within this new interior pointer scope.
The reason why I am doing this is that I am building up more utilities based on
passing around this struct of context that do not want it for RAUWing
purposes. So it makes sense on a helper (OwnershipRAUWHelper) that composes with
its state.
Just a refactor, should be NFC.
The only operational change here is that I needed to be able to grab the module
from the SILValue so I could see if we were in Raw SIL or not. I realized the
only case where we could not get the module is from SILUndef and at this point
in the code we know we are going to bail already for SILUndef. This is because
we already know our new value doesn't have OwnershipKind::None and we don't
replace OwnershipKind::None things with non-OwnershipKind::None things since I
haven't implemented support for that corner case yet (but will with time).
Once I realized the previous paragraph, I was able to add support without issue.
Specifically before this PR, if a caller did not customize a specific callback
of InstModCallbacks, we would store a static default std::function into
InstModCallbacks. This means that we always would have an indirect jump. That is
unfortunate since this code is often called in loops.
In this PR, I eliminate this problem by:
1. I made all of the actual callback std::function in InstModCallback private
and gave them a "Func" postfix (e.x.: deleteInst -> deleteInstFunc).
2. I created public methods with the old callback names to actually call the
callbacks. This ensured that as long as we are not escaping callbacks from
InstModCallback, this PR would not result in the need for any source changes
since we are changing a call of a std::function field to a call to a method.
3. I changed all of the places that were escaping inst mod's callbacks to take
an InstModCallback. We shouldn't be doing that anyway.
4. I changed the default value of each callback in InstModCallbacks to be a
nullptr and changed the public helper methods to check if a callback is
null. If the callback is not null, it is called, otherwise the getter falls
back to an inline default implementation of the operation.
All together this means that the cost of a plain InstModCallback is reduced and
one pays an indirect function cost price as one customizes it further which is
better scalability.
P.S. as a little extra thing, I added a madeChange field onto the
InstModCallback. Now that we have the helpers calling the callbacks, I can
easily insert instrumentation like this, allowing for users to pass in
InstModCallback and see if anything was RAUWed without needing to specify a
callback.
This is a generic API that when ownership is enabled allows one to replace all
uses of a value with a value with a differing ownership by transforming/lifetime
extending as appropriate.
This API supports all pairings of ownership /except/ replacing a value with
OwnershipKind::None with a value without OwnershipKind::None. This is a more
complex optimization that we do not support today. As a result, we include on
our state struct a helper routine that callers can use to know if the two values
that they want to process can be handled by the algorithm.
My moticiation is to use this to to update InstSimplify and SILCombiner in a
less bug prone way rather than just turn stuff off.
Noting that this transformation inserts ownership instructions, I have made sure
to test this API in two ways:
1. With Mandatory Combiner alone (to make sure it works period).
2. With Mandatory Combiner + Semantic ARC Opts to make sure that we can
eliminate the extra ownership instructions it inserts.
As one can see from the tests, the optimizer today is able to handle all of
these transforms except one conditional case where I need to eliminate a dead
phi arg. I have a separate branch that hits that today but I have exposed unsafe
behavior in ClosureLifetimeFixup that I need to fix first before I can land
that. I don't want that to stop this PR since I think the current low level ARC
optimizer may be able to help me here since this is a simple transform it does
all of the time.