This new OSSA invariant simplifies many optimizations because they don't have to take care of the corner case of incomplete lifetimes in dead-end blocks.
The implementation basically consists of these changes:
* add the lifetime completion utility
* add a flag in SILFunction which tells optimization that they need to run the lifetime completion utility
* let all optimizations complete lifetimes if necessary
* enable the ownership verifier to check complete lifetimes
These two new invariants eliminate corner cases which caused bugs if optimization didn't handle them.
Also, it will significantly simplify lifetime completion.
The implementation basically consists of these changes:
* add a flag in SILFunction which tells optimization if they need to take care of infinite loops
* add a utility to break infinite loops
* let all optimizations remove unreachable blocks and break infinite loops if necessary
* add verification to check the new SIL invariants
The new `breakIfniniteLoops` utility breaks infinite loops in the control flow by inserting an "artificial" loop exit to a new dead-end block with an `unreachable`.
It inserts a `cond_br` with a `builtin "infinite_loop_true_condition"`:
```
bb0:
br bb1
bb1:
br bb1 // back-end branch
```
->
```
bb0:
br bb1
bb1:
%1 = builtin "infinite_loop_true_condition"() // always true, but the compiler doesn't know
cond_br %1, bb2, bb3
bb2: // new back-end block
br bb1
bb3: // new dead-end block
unreachable
```
add `Test`, which is the SIL-equivalent of `FunctionTest`.
It's invocation closure gets a `TestContext` instead of a `FunctionContext`.
^ The commit message #2 will be skipped:
^ - test
This allows to move many SIL APIs and utilities, which require a context, to the SIL module.
The SIL-part of SwiftPassInvocation is extracted into a base class SILContext which now lives in SIL.
Also: simplify the begin/end-pass functions of the SwiftPassInvocation.
Previously, AutoDiff closure specialization pass was triggered only on
VJPs containing single basic block. However, the pass logic allows
running on arbitrary VJPs. This PR enables the pass for all VJPs
unconditionally. So, if the pullback corresponding to multiple-BB VJP
accepts some closures directly as arguments, these closures might become
specialized by the pass. Closures passed via payload of branch tracing
enum are not specialized - this is subject for future changes.
The PR contains several commits.
1. The thing named "call site" in the code is partial_apply of pullback
corresponding to the VJP. This might appear only once, so we drop
support for multiple "call sites".
2. Enhance existing SILOptimizer tests for the pass.
3. Add validation-tests for single basic block case.
4. The change itself - delete check against single basic block.
5. Add validation-tests for multiple basic block case.
6. Add SILOptimizer tests for multiple basic block case.
Functional changes:
Improved modeling of dependence on local variable scopes.
For nested modify->read accesses, only extend the read accesses.
Avoid making a read access dependent on an inout argument.
The following needs to be an error to prevent span storage from being modified:
@lifetime(owner)
foo(owner: inout Owner) -> Span {
owner.span
}
Improve usability of borrowing trivial values (UnsafePointer). Allow:
let span = Span(buffer.baseAddress)
Ignore access scopes for trivial values.
Structural changes:
Delete the LifetimeDependenceUseDefWalker.
Encapsulate all logic for variable introducers within the LifetimeDependenceInsertion pass. Once mark_dependence instructions are inserted, no subsequent pass needs to think about the "root" of a dependence.
Fixes: rdar://142451725 (Escape analysis fails with mutations)
Add a unit test harness to SwiftCompilerSources to match the one in C++ since
both source bases have different implementations of the same utilities, and they
must be consistent for correctness.
Changes in this CR add part of the, Swift based, Autodiff specific
closure specialization optimization pass. The pass does not modify any
code nor does it even exist in any of the optimization pipelines. The
rationale for pushing this partially complete optimization pass upstream
is to keep up with the breaking changes in the underlying Swift based
compiler infrastructure.
Provide APIs needed by lifetime dependence diagnostics, namely LifetimeDependenceConvention.
Reorganize the APIs so it's easy to find related functionality which
API is responsible for which functionality.
Remove the originalFunctionConvention complexity. It is no longer
needed for lifetime dependence inference, and generally should be
avoided in SIL.
Add some placeholder FIXMEs because this not a good PR in which to
change existing functionality.
In the C++ sources it is slightly more convenient to dump to stderr than
to print to stdout, but it is rather more unsightly to print to stderr
from the Swift sources. Switch to stdout. Also allows the dump
functions to be marked debug only.
Introduce two modes of bridging:
* inline mode: this is basically how it worked so far. Using full C++ interop which allows bridging functions to be inlined.
* pure mode: bridging functions are not inlined but compiled in a cpp file. This allows to reduce the C++ interop requirements to a minimum. No std/llvm/swift headers are imported.
This change requires a major refactoring of bridging sources. The implementation of bridging functions go to two separate files: SILBridgingImpl.h and OptimizerBridgingImpl.h.
Depending on the mode, those files are either included in the corresponding header files (inline mode), or included in the c++ file (pure mode).
The mode can be selected with the BRIDGING_MODE cmake variable. By default it is set to the inline mode (= existing behavior). The pure mode is only selected in certain configurations to work around C++ interop issues:
* In debug builds, to workaround a problem with LLDB's `po` command (rdar://115770255).
* On windows to workaround a build problem.