NOTE: If one does not define a deinit on a move only type, if a destroy_value
lasts until IRGen time we will assert since I haven't implemented support in
IRGen for the destroy value witness for move only types. That being said, just
creating a deinit by adding to such a type:
```
deinit {}
```
is pretty low overhead for the experiments we want to use this for.
Some notes:
1. I added support for both loadable/address only types.
2. These tests are based off of porting the move only object tests for inout,
vars, mutating self, etc.
3. I did not include already written tests for address only types in this
specific merge since I need to change us to borrow move only var like types.
Without that, we get a lot of spurious error msgs and the burden of writing that
is not worth it. So instead in a forthcoming commit where I fix that issue in
SILGen, I will commit the corresponding address only tests for this work.
4. I did not include support for trivial types in this. I am going to do
object/address for that at the same time.
It decides which functions need stack protection.
It sets the `needStackProtection` flags on all function which contain stack-allocated values for which an buffer overflow could occur.
Within safe swift code there shouldn't be any buffer overflows.
But if the address of a stack variable is converted to an unsafe pointer, it's not in the control of the compiler anymore.
This means, if there is any `address_to_pointer` instruction for an `alloc_stack`, such a function is marked for stack protection.
Another case is `index_addr` for non-tail allocated memory.
This pattern appears if pointer arithmetic is done with unsafe pointers in swift code.
If the origin of an unsafe pointer can only be tracked to a function argument, the pass tries to find the root stack allocation for such an argument by doing an inter-procedural analysis.
If this is not possible, the fallback is to move the argument into a temporary `alloc_stack` and do the unsafe pointer operations on the temporary.
rdar://93677524
By using the keyword instead of the function, we actually get a much simpler
implementation since we avoid all of the machinery of SILGenApply. Given that we
are going down that path, I am removing the old builtin implementation since it
is dead code.
The reason why I am removing this now is that in a subsequent commit, I want to
move all of the ownership checking passes to run /before/ mandatory inlining. I
originally placed the passes after mandatory inlining since the function version
of the move keyword was transparent and needing to be inlined before we could
process it. Since we use the keyword now, that is no longer an issue.
Otherwise in certain cases due to load promotion, we emit incorrect errors. As
an example:
let x = ...
var y = x
print(y)
would show an error that x is consumed twice... which is incorrect.
This is important for performance diagnostics: it’s assumed that (non-generic) MemoryLayout constants do not need to create metadata at runtime. At Onone this is only guaranteed if the TargetConstantFolding pass runs.
rdar://94836837
TargetConstantFolding performs constant folding for target-specific values:
```
MemoryLayout<S>.size
MemoryLayout<S>.alignment
MemoryLayout<S>.stride
```
Constant folding those expressions in the middle of the SIL pipeline enables other optimizations to e.g. allow such expressions in statically allocated global variables (done by the GlobalOpt pass).
The implementation requires to create a temporary IRGenModule, which is used to get actual constant sizes/alignments from IRGen's type lowering.
rdar://94831524
This pass lowers moveonly-ness from the IR after we have finished move only
checking. The transform can be configured in two ways: such that it only handles
trivial types and such that it does trivial and non-trivial types. For ease of
use, I created two top level transforms (TrivialMoveOnlyTypeEliminator and
MoveOnlyTypeElimintor) that invoke the two behaviors by configuring the
underlying transform slightly differently.
For now, I am running first the trivial-only and then the all of the above
lowering. The trivial only pass will remain at this part of the pipeline
forever, but with time we are going to move the lower everything pass later into
the pipeline once I have audited the optimizer pipeline to just not perform any
work on move only types. That being said, currently we do not have this
guarantee and this patch at least improves the world and lets us codegen no
implicit copy code again.
Removes redundant ObjectiveC <-> Swift bridging calls.
Basically, if a value is bridged from ObjectiveC to Swift an then back to ObjectiveC again, then just re-use the original ObjectiveC value.
Also in this commit: add an additional DCE pass before ownership elimination. It can cleanup dead code which is left behind by the ObjCBridgingOptimization.
rdar://89987440
The overall flow of the pass is:
1. We walk over the blocks summarizing the debug info instruction the blocks gen
as well as whether or not the block had an async funclet edge with in it.
2. We then perform a simple forward iterative optimistic dataflow using
intersection at merge points. At points where we find after merging that we have
a conflict and thus need to stop propagation, we insert a debug_value undef.
3. We then walk the CFG again visiting only blocks that we know had async
funclet edges. We then walk each said block from top to bottom starting with the
propagating gen information and updating as we go, dumping the current set of
debug_info we are tracking after each coroutine funclet boundary.
rdar://85020571
We must no pre-specialize imported code (except if this was explicitly
called for by the importing module).
Therefore, don't pre-specialize `shared` definitions based on their
pre-specialization attributes.
Rather, only pre-specialize if the pre-specialization is called for
using a `target: "theFunctionToSpecialize"` parameter.
Run OnonePrespecializations before serialization so that module native functions
are not yet marked `shared` and can be identified as native.
rdar://92337361
This will turn `partial_apply` instructions into explicit box construction and
extraction code sequences. To begin with, recognize when a private function
is only used in partial applications and directly modify the function to be
usable as a closure invocation function. This simplifies the lowering in IRGen
and avoids generating a "partial application forwarder" thunk.
The ComputeEffects pass derives escape information for function arguments and adds those effects in the function.
This needs a lot of changes in check-lines in the tests, because the effects are printed in SIL
NOTE: debug_value [moved] appearing in the source code implies a _move was
used. So this will not effect current stable swift code.
This is just a first version of this that I am using to commit/bring up tests
for IRGen supporting a full dataflow version of this patch.
Big picture is that there is a bunch of work that is done in the LLVM level in
the coroutine splitter to work around communicating live variables in the
various coroutine func-lets. This logic is all done with debug.declare and we
would need to update that logic in the coroutine splitter to handle
debug.addr. Rather than do this, after some conversation, AdrianP and I realized
that we could get the same effect of a debug.declare by just redeclaring the
current live set of debug_value after each possible coroutine funclet start. To
do this in full generality, we need a full dataflow but just to bring this up we
initially perform a dominance propagation algorithm of the following sort:
1. We walk the CFG along successors. By doing this we guarantee that we visit
blocks after their dominators.
2. When we visit a block, we walk the block from start->end. During this walk:
a. We grab a new block state from the centralized block->blockState map. This
state is a [SILDebugVariable : DebugValueInst].
b. If we see a debug_value, we map blockState[debug_value.getDbgVar()] =
debug_value. This ensures that when we get to the bottom of the block, we
have pairs of SILDebugVariable + last debug_value on it.
c. If we see any coroutine funclet boundaries, we clone the current tracked
set of our block state and then walk up the dom tree dumping in each block
any debug_value with a SILDebugVariable that we have not already
dumped. This is maintained by using a visited set of SILDebugVariable for
each funclet boundary.
The end result is that at the beginning of each funclet we will basically
declare the debug info for an addr.
This is insufficient of course for moves that are in conditional control flow,
e.x.:
```
let x = Klass()
if boolValue {
await asyncCall()
let _ = _move(x)
}
```
but this at least lets me begin to write tests for this in lldb using straight
line code and work out the rest of the issues in CodeGen using those tests.
Mandatory copy propagation was primarily a stop-gap until lexcial
lifetimes were implemented. It supposedly made variables lifetimes
more consistent between -O and -Onone builds. Now that lexical
lifetimes are enabled, it is no longer needed for that purpose (and
will never satisfactorily meet that goal anyway).
Mandatory copy propagation may be enabled again later as a -Onone "
optimization. But that requires a more careful audit of the effect on
debug information.
For now, it should be disabled.
Flow-isolation is a diagnostic SIL pass that finds
unsafe accesses to properties in initializers and
deinitializers that cannot gain isolation to otherwise
protect those accesses from concurrent modifications.
See SE-327 for more details about how and why it exists.
This commit includes changes and features like:
- The removal of the escaping-use restriction
- Flow-isolation that works properly with `defer` statements
- Flow-isolation with an emphasis on helpful diagnostics.
It also includes known issues like:
- Local / nonescaping functions are not analyzed by
flow-isolation, despite it being technically possible.
The main challenge in supporting it efficiently is that
such functions do not have a single exit-point, like
a `defer`. In particular, arbitrary functions can throw
so there are points where nonisolation should _not_ flow
out of the function at a call-site in the initializer, etc.
- The implementation of the flow-isolation pass is not
particularly memory efficient; it relies on BitDataflow
even though the particular flow problem is simple.
So, a more efficient implementation would be specialized for
this particular problem, etc.
There are also some changes to the Swift language itself: defer
will respect its context when deciding its property access kind.
Previously, a defer in an initializer would always access a stored
property through its accessor methods, instead of doing so directly
like its enclosing function might. This inconsistency is unfortunate,
so for Swift 6+ we make this consistent. For Swift 5, only a defer
in a function that is a member of the following kinds of types
will gain this consistency:
- an actor type
- any nominal type that is actor-isolated, excluding UnsafeGlobalActor.
These types are still rather new, so there is much less of a chance of
breaking expected behaviors around defer. In particular, the danger is
that users are relying on the behavior of defer triggering a property
observer within an init or deinit, when it would not be triggering it
without the defer.
This is part of a larger piece of work that is going to introduce the ability in
SIL for us to wrap trivial values in a move only type wrapper and thus perform
ownership based diagnostics upon those trivial values. This is being done now so
that we can perform SSA based ownership diagnostics on trivial types. This will
allow us to eventually be able to do things like no escape analysis on
UnsafePointer.
That all sounds great, but also we must consider how this effects the rest of
the optimizer. For instance, what if we want to have a no escape integer and
have overflow checks used upon it! To ensure that we can do this, the authors
realized that we did not need to persist the ownership information so late in
that part of the pipeline and we can just do the ownership checking earlier than
constant propagation and then lower. This is safe to do since the rest of the
optimizer will not introduce escapes of a pointer or extra copies unlike if the
underlying non move only type variant was also non-trivial.
With that in hand, this PR moves these two move only passes earlier than
constant propagation for this purpose. The reason they were put in the current
position vs earlier is that I wanted them to run after predictable dead
allocation elimination since it cleaned up the SIL I was reading as I designed
it. There isn't any reason that they can't run earlier. Once I bring in the new
SIL move only type wrapper, after these run, the trivial type lowering will then
run.
* rename the CrossModuleSerializationSetup pass to simply CrossModuleOptimization
* remove the CMO specific serializer pass. Instead run the CrossModuleSerializationSetup pass directly before the standard serializer pass.
* correctly handle shared functions (e.g. specializations)
* refactoring
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.
The reason why I am doing this is that we are going to be enabling lexical
lifetimes early in the pipeline so that I can use it for the move operator's
diagnostics.
To make it easy for passes to know whether or not they should support lexical
lifetimes, I included a query on SILOptions called
supportsLexicalLifetimes. This will return true if the pass (given the passed in
option) should insert the lexical lifetime flag. This ensures that passes that
run in both pipelines (e.x.: AllocBoxToStack) know whether or not to set the
lexical lifetime flag without having to locally reason about it.
This is just chopping off layers of a larger patch I am upstreaming.
NOTE: This is technically NFC since it leaves the default alone of not inserting
lexical lifetimes at all.
NOTE: This pass is disabled when -enable-experimental-lexical-lifetimes is
enabled.
When that flag is disabled, this removes the lexical flag from begin_borrow and
alloc_stack. This ensures that we can begin using begin_borrow [lexical] and
friends to emit diagnostics without impacting performance. I am going to be
preparing a subsequent patch that causes us to emit lexical lifetimes by
default. Due to this pass, I am not expecting any issues around perf.
This is just an initial prototype for people to play with. It is as always
behind the -enable-experimental-move-only flag.
NOTE: In this PR I implemented this only for 'local let' like things (local
lets/params). I did not implement in this PR support for local var and haven't
done anything with class ivars or globals.
rdar://83957028