When analyzing uses of `self`, the flow-isolation pass was recently changed
to conservatively treat unrecognized instructions as property uses. This
caused errors in real-world projects that use instructions the pass does not
yet handle, since they were being incorrectly flagged as property accesses.
Restore the previous behavior of ignoring unrecognized instructions for now.
This lets affected projects compile again while we tighten this up in a more
measured way.
rdar://175530379
SIL functions should always have actor isolation set, otherwise
it could lead to wrong deductions in optimization passes like
`SendNonSendable` or `OptimizeHopToExecutor`.
This is a first step to move isolation assignment into
`SILFunction::create`.
Previously, flow-sensitive isolation checking only applied to actor instance
initializers. This extends it to cover initializers of global-actor-isolated
nominal types (structs, classes).
rdar://131136194
Restructure the isolation info lattice from a linear progression to a
diamond-shaped lattice that properly models merge failures:
Old: Unknown -> Disconnected -> Task -> Actor
New: Disconnected ---> Task ---> Invalid
\--> Actor -/
This cleanup prepares the isolation lattice for better error handling when
incompatible regions are merged.
Previously, it was incorrect and not a true lattice and prevented us from
modeling merge errors in between Task and Actor isolation. We need to model this
so that we can properly emit errors when we suppress the AST from emitting these
errors during flow isolation. We are also going to move these sorts of errors
completely from the AST to Region Analysis to simplify things.
NOTE: This causes us to emit some more "unknown pattern" errors. I updated the
tests with that so in the next series of commits, we can validate they all go away.
The method name `verifyIsolation` implied a precondition-style check,
but the function actually emits user-facing diagnostics for isolation
violations found by the dataflow solver. Rename it to `emitDiagnostics`
to better reflect its purpose.
FlowIsolation was digging into ASTContext::Diags manually and extracting
SILLocations from SIL constructs. Rather than do this, refactor the diagnostic
helpers I used in SendNonSendable into DiagnosticHelpers.h and use them in both
passes.
Replace lightweight `// MARK: foo` comments with LLVM-style banner
separators (`//===---...---===// MARK: Foo //===---...---===//`) and
convert `//`-style comments on `LatticeState` and `BlockInfo` to `///`
doc-comments. Also split the single anonymous namespace into per-section
namespaces so that utility functions (e.g. `getCallee`, `isWithinDeinit`)
can be hoisted out of the anonymous namespace without disturbing the
type definitions.
The names `Info` and `AnalysisInfo` were generic and didn't clearly convey
the scope of each type. Rename them to `BlockInfo` and `FunctionInfo` to
make explicit that `BlockInfo` holds per-basic-block data and `FunctionInfo`
holds the analysis state for an entire SILFunction.
The struct `State` in FlowIsolation.cpp represents the possible lattice
states in the dataflow analysis (Isolated and Nonisolated), not a general
"state" object. Rename it to `LatticeState` to make this role explicit and
avoid confusion with other kinds of state (e.g. AnalysisInfo, Info).
Change propertyUses from SmallPtrSet<SILInstruction *, 8> to
SmallPtrSet<Operand *, 8> so that each recorded use captures both the
violating instruction and the specific operand value. This enables use
of the SIL naming infrastructure to produce better diagnostics.
Callers of markPropertyUse() are updated to pass the operand rather than
the instruction, and sites that previously called ->getParent() on a
SILInstruction now call ->getParentBlock() on the Operand.
Convert the dump() methods on Info and the FlowIsolation analysis class to
follow the standard Swift C++ compiler pattern: a print(raw_ostream &) method
for the real implementation, with SWIFT_DEBUG_DUMP delegating to it via
llvm::dbgs(). This makes the types consistent with the rest of the codebase and
allows callers to redirect output to arbitrary streams.
This is an NFC change. I believe this code was written before OperandWorklist
existed. Doesn't make sense to roll out own when we have standard
infrastructure.
If an instruction references the DynamicSelfType by calling a
static member with `Self.foo()`, we consider this a type-dependent
use of `self`. This means that at runtime we may need to load the
isa pointer, but we don't need to touch any other protected state
from the instance.
Therefore, we can skip type-dependent uses in the analysis to
avoid false positives in this case.
An existing test case already exercised the overly-conservative
behavior, so I just updated it to not expect an error.
Fixes rdar://129676769.
Although I don't plan to bring over new assertions wholesale
into the current qualification branch, it's entirely possible
that various minor changes in main will use the new assertions;
having this basic support in the release branch will simplify that.
(This is why I'm adding the includes as a separate pass from
rewriting the individual assertions)
Instance properties of non-sendable types cannot safely be
accessed within deinitializers. Make sure we respect `@preconcurrency`
when diagnosing these.
Init accessor properties should be handled specifically because
they do not reference underlying storage directly via `ref_element_addr`
instructions when 'self' is fully initialized.
It lowers let property accesses of classes.
Lowering consists of two tasks:
* In class initializers, insert `end_init_let_ref` instructions at places where all let-fields are initialized.
This strictly separates the life-range of the class into a region where let fields are still written during
initialization and a region where let fields are truly immutable.
* Add the `[immutable]` flag to all `ref_element_addr` instructions (for let-fields) which are in the "immutable"
region. This includes the region after an inserted `end_init_let_ref` in an class initializer, but also all
let-field accesses in other functions than the initializer and the destructor.
This pass should run after DefiniteInitialization but before RawSILInstLowering (because it relies on `mark_uninitialized` still present in the class initializer).
Note that it's not mandatory to run this pass. If it doesn't run, SIL is still correct.
Simplified example (after lowering):
bb0(%0 : @owned C): // = self of the class initializer
%1 = mark_uninitialized %0
%2 = ref_element_addr %1, #C.l // a let-field
store %init_value to %2
%3 = end_init_let_ref %1 // inserted by lowering
%4 = ref_element_addr [immutable] %3, #C.l // set to immutable by lowering
%5 = load %4
This is a futile attempt to discourage future use of getType() by
giving it a "scary" name.
We want people to use getInterfaceType() like with the other decl kinds.
Reformatting everything now that we have `llvm` namespaces. I've
separated this from the main commit to help manage merge-conflicts and
for making it a bit easier to read the mega-patch.
This is phase-1 of switching from llvm::Optional to std::optional in the
next rebranch. llvm::Optional was removed from upstream LLVM, so we need
to migrate off rather soon. On Darwin, std::optional, and llvm::Optional
have the same layout, so we don't need to be as concerned about ABI
beyond the name mangling. `llvm::Optional` is only returned from one
function in
```
getStandardTypeSubst(StringRef TypeName,
bool allowConcurrencyManglings);
```
It's the return value, so it should not impact the mangling of the
function, and the layout is the same as `std::optional`, so it should be
mostly okay. This function doesn't appear to have users, and the ABI was
already broken 2 years ago for concurrency and no one seemed to notice
so this should be "okay".
I'm doing the migration incrementally so that folks working on main can
cherry-pick back to the release/5.9 branch. Once 5.9 is done and locked
away, then we can go through and finish the replacement. Since `None`
and `Optional` show up in contexts where they are not `llvm::None` and
`llvm::Optional`, I'm preparing the work now by going through and
removing the namespace unwrapping and making the `llvm` namespace
explicit. This should make it fairly mechanical to go through and
replace llvm::Optional with std::optional, and llvm::None with
std::nullopt. It's also a change that can be brought onto the
release/5.9 with minimal impact. This should be an NFC change.
When we disabled flow-isolation warnings for non-complete concurrency
checking, we disabled one of two places where we emit diagnostics.
Disable the other one. Fixes rdar://94707894.
The flow-isolation pass was not respecting the new strict-concurrency checking mode.
Since the Sendable diagnostics in these deinits are very noisy, I'm moving them to only
be emitted in 'complete' mode. The reason why they're so noisy is that any class that
inherits from a `@MainActor`-constrained class will have these diagnostics emitted when
trying to access its own `@MainActor`-isolated members.
This is needed, even during the `deinit`, because multiple instances of a `@MainActor`-isolated
class might have stored properties that refer to the same state.
This change specifically avoids emitting these diagnostics even in 'targeted' mode because
I'd like to take more time to reconsider the ergonomics of these deinits.
resolves rdar://94699928
When emitting the note to point out what introduced
nonisolation, the code building the message assumed that
`DeclContext::getDecl` will not return null, when it can
if it's a closure expression.
fixes rdar://88776902
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.