When merging fixed-storage bounds checks with the same self value,
cloneFixedStorageIndex clones the index computation of a later check
to place it adjacent to an earlier one. The previous implementation could lead to
illegal SIL due to dominance violation in some cases by cloning an instruction
before it's operands were cloned.
Fix this with a recursive DFS that appends instructions in postorder.
Since a node is only added to the worklist after all its operands are recursively
processed, the resulting order is a valid topological sort that guarantees every
cloned instruction's operands are already available.
In cloneFixedStorageIndex, bail out when an instruction to be cloned has
a lifetime-ending operand. Cloning such an instruction (e.g.
destructure_struct/destructure_tuple with an @owned operand, end_borrow of a begin_borrow)
may create a second consume, violating OSSA invariants.
This change introduces an optimization that hoists bounds checks with fixed storage semantics when they operate on the same self value,
grouping them together within a basic block to enable downstream optimizations like partial vectorization.
Since the language guarantees the validity of underlying storage for types annotated with fixed storage semantics like Span, InlineArray etc,
we don't need to consult alias analysis to perform this optimization.
We have always hoisted cond_fail instructions over side effects in loops. This change enables hoisting over side-effects in blocks.
rdar://164947648 and rdar://166409418
Mainly:
* look through ownership instructions in more places
* support `load_borrow` and `store_borrow` where `load` and `store`s are expected
* support `destructure_struct` where `struct_extract` is expected
* enable inout-keypath optimization for OSSA
* OSSA support in StringOptimization