llvm::cl::opt flags are compiled out in non-asserts builds, making these
debug flags unavailable in an important category of use cases — debugging
a release compiler in lldb. By promoting them to Swift frontend flags stored
in DiagnosticOptions/SILOptions, they are available in all build
configurations.
The three existing flags are migrated:
-diagnostics-assert-on-error
-diagnostics-assert-on-warning
-sil-region-isolation-assert-on-unknown-pattern
(backing field renamed to AbortOnUnknownRegionIsolationPatternError)
A new flag is added:
-diagnostics-assert-on-group <group>
Traps when any diagnostic belonging to the named group is emitted,
allowing targeted breakpoints on a single diagnostic group rather than
all errors or all warnings.
The assert-on-{error,warning,group} flags are intentionally kept separate
from the normal diagnostic suppression/escalation machinery so that they
remain useful while other diagnostics are also being emitted.
Tests are added for all four flags.
A closure that captures a `@dynamic_self` metatype produces a
`partial_apply` with `self` appearing as a type-defs operand. The region
analysis was iterating `pai->getAllOperands()` and feeding every operand
to `translateSILMultiAssign`, which then merged `self`'s region with the
PA's region even though no runtime value of `self` is captured.
For `sending`-parameter closures this manifested as a bogus "pattern
that the region-based isolation checker does not understand how to
check" diagnostic, because the spurious merge violated the
region-disjoint invariant required by `sending`.
Switch to `ApplySite(pai).getArgumentOperands()`, matching
`translateSILPartialApplyAsyncLetBegin` and
`translateIsolatedPartialApply`, which already use that idiom and so
were never affected. The apply-site code in
`translateNonIsolationCrossingSILApply` likewise filters
`isTypeDependent()` operands.
When init_existential_addr has a formal concrete type involving an opened
archetype (e.g., the iterator type in a nonisolated actor init that iterates
over any Sequence<Int>), the AssignDirect path calls getConformanceIsolation()
and introduces a task-isolated isolated-conformance region. In a nonisolated
actor init, parameters are 'self'-isolated, and merging 'self'-isolated with
task-isolated-conformance triggers a RegionIsolationUnknownPattern false
positive ("pattern that the region-based isolation checker does not understand
how to check").
The reproducer is:
actor Foo {
init(sequence: any Sequence<Int>) {
for element in sequence { // error: pattern that the region-based
_ = element // isolation checker does not understand
}
}
}
Treating the conformance as an independent task-isolated conformance is
incorrect. According to SE-0470, the iterator's conformance to IteratorProtocol
is a dependent conformance on the conformance to Sequence which implies that the
former conformance must be isolated to the same isolation domain as the latter
conformance.
The fix detects this situation by calling
getFormalConcreteType()->hasOpenedExistential() and calls
translateSILMultiAssign over all operands (primary + type-dependent) without a
conformance isolation override. This places the init_existential_addr in the
same region as its type-dependent operand producing value (the
open_existential_addr), reflecting the dependent conformance relationship.
Found the same issue in init_existential_ref (class-bound existentials) and
init_existential_value (opaque-values mode) and applied the same fix.
Tests cover all three instruction variants at the Swift level and as partition
op translation SIL tests.
rdar://176882987
We cannot use spare bits or other overlapping storage layout tricks with fundamentally
address-only enums, and we can take advantage of this to do borrowing switches or other
in-place projections without copying the value. However, for resilient enums, the
implementation may use spare bit packing, but the type must be handled address-only
outside of its defining module, and we didn't have a way to express that with
borrowing switch. Optimization passes have also been running into problems with the
complexity that we were using `unchecked_take_enum_data_addr` sometimes as a pure
operation. This patch splits the instruction into three:
- `unchecked_inplace_enum_data_addr` represents a nondestructive in-place enum
projection. It is only allowed for enums whose projection operation is
nondestructive.
- `unchecked_take_enum_data_addr` represents a destructive enum projection,
invalidating the enum and leaving the payload to be further consumed.
This matches the current instruction's semantics.
- `unchecked_borrow_enum_data_addr` represents a borrowing enum projection.
The instruction takes a second operand for "scratch" space, which the
enum representation may be copied into in order to avoid invalidating the
enum value, so the result is dependent on the lifetime of both the
original enum and the scratch buffer. This allows for borrowing switches
over resilient enums.
`unchecked_borrow_enum_data_addr` is implemented by taking advantage of the
"address-only enums can't do spare bit optimization" property at runtime.
We inspect the operand type's bitwise-borrowability from its metadata. If
the type is bitwise-borrowable, then we are allowed to bitwise-copy the
enum to the scratch space and apply the projection to the scratch space,
preserving the original value. If the type is not bitwise-borrowable, then
we cannot use spare bit optimization in its layout, so we apply the
projection in-place.
Fixes rdar://174952822.
For example:
```
struct InoutPair: ~Escapable, ~Copyable {
@_lifetime(self: &x)
mutating func insertX(_ x: inout Int) {...}
@_lifetime(self: &y)
mutating func insertY(_ y: inout Int) {...}
}
struct Pair {
var x: Int
var y: Int
mutating func run() {
var inoutPair = InoutPair()
inoutPair.insertX(&x)
inoutPair.insertY(&y)
take(inoutPair) // The access of both &x and &y are extended up to here
}
}
```
In the `Pair.run` method, the accesses to &x and &y are overlapping because the
final value of `inoutPair` depends on both. Fix DiagnoseStaticExclusivity to
recognize that these accesses are to independent subobjects even though that
have a lifetime dependency.
Fixes rdar://175271283 ([exclusivity] handle simultaneous sub-object exclusivity
with lifetime dependencies)
Specifically, we previously used translateSILMultiAssign with checked_cast_br
and checked_cast_addr_br and relied upon its behavior around injecting an
overriding isolation to work.
This caused a bunch of problems when dealing with isolated conformances
introduced by checked_cast_br and checked_cast_addr_br.
In this commit, I fix these issues by:
1. Making it so that we actually represent in SILIsolationInfo that a phi
argument from a checked_cast_br can have disconnected or an isolated conformance
isolation. We previously just returned an invalid isolation. This worked since
we took advantage of a quirk in translateSILMultiAssign that overrode the
isolation of certain results. This quirk breaks the abstraction boundary that
SILIsolationInfo decides the isolation of elements and RegionAnalysis just
propagates those around. I want to remove this behavior but that needs to occur
in a future commit since I am trying to be more surgical with this change.
2. I changed checked_cast_br and checked_cast_addr_br to not use
translateSILMultiAssign so that I have more control and can avoid the behavior
quirk. This had a nice benefit that we now properly represent for
checked_cast_br that the merge in between the parent value and the cast value
only happens on the success path.
3. I added tests for isolated conformances and casting to give us more
confidence that the behavior is correct. We previously had pretty minimal tests.
4. I added partition op translation and silisolation info tests for these two
casting instructions to increase code coverage. I want to increase it more, but
this is a good first step.
rdar://172941821
Specifically, previously if we had a non-Sendable field in a non-Sendable parent
class, we would perform an assign direct. This is not safe anymore since we are
being much more stringent about mismerges.
Instead, the correct thing to do is if the field has a differing explicit
isolation (not its region isolation, but the isolation of the element) from its
parent nominal type, we require the parent operand type and assign fresh the
ref_element_addr. This ensures that they are in different regions and we do not
have the same region with differing isolations within it.
The reason why this is safe is that The field will be isolated to whatever
actor isolation it has at the AST level. So any time we escape it or send
it, we will properly get an error. Any other ref_element_addr to the same
field will be a different region, but that doesn't matter since they all
will still be isolated ot the same actor isolation meaning merges will not
cause problems.
I am going to be fixing a similar issue with structs but it is more complicated,
so I thought I would fix this now.
ColdBlockInfo previously had to be constructed and run manually for each function.
Turning it into a SILAnalysis makes it lazily computed and cached per-function,
with automatic invalidation whenever the control-flow graph changes.
This is otherwise a non-functional change.
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
In the optimizer (= non mandatory) pipeline we don't need to maintain the lexical flags of `begin_borrow`, etc. anymore.
This enables more optimizations to be done.
Introduce the IncompatibleRegionMerge partition op error type to detect merges
between regions with incompatible isolation.
Previously, these merge failures produced generic "unknown pattern" errors. Now
we detect them during partition evaluation and emit context-specific diagnostics
like "cannot assign X to Y" or "cannot pass X to function Y", explaining the
specific operation that caused the isolation conflict.
This is part of a larger effort to move isolation-related diagnostics from the
AST level to RegionIsolation at the SIL level. This is important because it
enables flow isolation to support global actor isolated nominal types.
NOTE: We are going to emit errors that we are not going to emit eventually. I am
just updating so I can track that they are all resolved.
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.
Complete the transition to the full translateSILMultiAssign signature by
updating all remaining call sites to pass explicit indirect results. We just
pass through mechanically an empty ArrayRef<Operand *>, so nothing semantic is
changing.
Arguably some of these entrypoints should use actual indirect parameters
(looking at use store_borrow), but this at least makes progress.
Previously, region-based isolation was not diagnosing cases where a non-Sendable
value projected from a Sendable base that was MainActor isolated. For example:
```swift
@MainActor
struct MainActorBox {
var value: NonSendableKlass?
mutating func take() -> sending NonSendableKlass {
if let value {
self.value = nil
return value // Should warn: main actor-isolated value cannot be 'sending'
} else {
preconditionFailure("Consumed twice")
}
}
}
```
This was caused by two issues:
1. A logic bug in `AddressBaseComputingVisitor::visitAll` where we overwrote an
already-found Sendable value when recursing. The visitor should only record
the first Sendable value found, not subsequent ones. This caused us to
incorrectly return the `@MainActor` `MainActorBox` as the base instead of
emitting a require for the projected value.
2. struct_element_addr being transparent unconditionally causing us to not even
emit a require at all.
This commit has two parts:
1. I fixed the first two issues by fixing the logic bug and by making
struct_element_addr only transparent if its operand and result are
non-Sendable. I added logic that we have used in other such cases to handle
non-Sendable operand/Sendable result as well as Sendable operand and
non-Sendable result. I then added the same support for tuple_element_addr and
unchecked_take_enum_data_addr since they suffered from a similar problem.
2. Adding the logic to handle variants where the operand was non-Sendable and
the result was Sendable, caused a bunch of tests to break so I had to fix
that.
To fix the broken test cases, I had to make the compiler smarter about how it
was inserting this require. To do this:
1. I checked if the access base of the projection was actually immutable or if
we are an alloc_stack that there was a prefix path in the projection path of
immutable projections that end in the alloc_stack. In such a case, we know
that we do not need to require that the operand is available in the current
isolation domain since we will never write to that piece of memory and once
we have loaded the result from memory, we know that the value is Sendable so
nothing bad can happen.
2. If the access base of the projection was mutable, I used the same logic that
we used for alloc_box that were non-Sendable that stored a Sendable thing by
changing operand to be a `require [mutable_base_of_sending]` on the result of
the projection instead of requiring the projection's operand. This ensures
that we handled important flow sensitive cases.
rdar://169626088
For stores to unaliased, non-aggregate-projected destinations, switch from
translateSILAssignDirect(destValue, src) to translateSILAssignIndirect(dest, src).
This passes the Operand* rather than just the SILValue, enabling proper tracking
of the destination address in region analysis.
First in a series migrating instruction handlers to indirect assignment.
NOTE: I originally thought that pack_element_set would also have this property,
but alloc_pack today is always assumed to be to be MayAlias and thus we emit a
merge. I think this is fine since one can never actually assign over a pack
variable like one can other things (that is I think they are generally just used
for marshalling and the like). If I am wrong, I can just fix it later.
rdar://156024613
https://github.com/swiftlang/swift/issues/83121
Explicitly pass an empty indirect results array to translateSILMultiAssign when
translating partial_apply instructions, since partial_apply does not produce
indirect results. This transitions the call site from the backward-compatible
overload to the full signature introduced in bc8eadad12f.
Add two template overloads of translateSILAssignIndirect that wrap
translateSILMultiAssign for the common case of assigning to a single
indirect parameter destination:
1. A generic template accepting any collection of source operands
2. A specialization for a single source operand
These simplify call sites that only need to handle one destination.
Add a new IndirectResultsRangeTy template parameter and indirectResultAddresses
parameter to translateSILMultiAssign, allowing it to process both direct result
values and indirect result operands.
I also added overload that takes only direct results for backward
compatibility. I am going to get rid of this eventually once we have finished
the transition.
It previously just accepted ArrayRef<TrackableValue>. I am going to be using
this with a transform + concat range in a subsequent change to pass a combined
ArrayRef<TrackableValue> + ArrayRef<std::pair<Operand, TrackableValue>>.second.
Since after address lowering, `Borrow` can remain loadable with a known-
layout address-only referent, we need instructions that handle three
forms:
- borrow and referent are both loadable values
- borrow is a value, but referent is address-only
- borrow and referent are both address-only
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
Pass-invocations can be nested if a module pass creates an inner context for a specific function to modify.
This change
* removes the SwiftPassInvocation instance from SILCombine and let SILCombine use the current SwiftPassInvocation
* correctly use the innermost SwiftPassInvocation in various bridged utilities
begin_dealloc_ref is only lookthrough its first parameter. We want to treat the
other parameter as just a require. This hit an assert in the SIL test cases in
the next commit.
Split the `Assign` partition operation into `AssignDirect` and `AssignIndirect`
to properly handle the case where a value in memory is overwritten.
Previously, when a store instruction overwrote a value in memory (e.g.,
`store %new to [assign] %addr`), we would simply update %addr's region to
match %new's region. This caused us to lose information about the original
value that was in %addr, which could include actor or task isolation info.
For example:
%addr = alloc_stack
store %task_local to [init] %addr // %addr's region is task-isolated
store %new_value to [assign] %addr // Previously: lost task isolation!
Now, `AssignIndirect` creates a new representative value (identified by the
store's destination operand) for the overwritten value and merges it into
the destination's region before reassigning. This preserves the region's
isolation properties.
`AssignDirect` continues to be used for direct results where the SSA value
itself is new (e.g., struct_extract results, pack_element_get addresses).
The actual implementation change for AssignIndirect will come in a follow-up
commit. This commit only refactors code in preparation for that.
This commit systematically replaces all calls to `SILIsolationInfo::isNonSendableType(type, fn)`
and `SILIsolationInfo::isSendableType(type, fn)` with their value-based equivalents
`SILIsolationInfo::isNonSendable(value)` and `SILIsolationInfo::isSendable(value)`.
This refactoring enables more precise Sendability analysis for captured values
in closures, which is a prerequisite for treating inferred-immutable weak
captures as Sendable, a modification I will be making a subsequent commit.
I made the type-based `isSendableType(type, fn)` methods private to prevent
future misuse. The only place where isSendableType was needed to be used outside
of SILIsolationInfo itself was when checking the fields of a box. Rather than
exposing the API for that one purpose, I added two APIs specifically for that
use case.
Before this change the ability to print FunctionInfo on CallerAnalysis was
defined on CallerAnalysis itself and by mistake FunctionInfo had a declaration
for a print method that was not actually defined.
This made it so that one could only print out FunctionInfo for all
CallerAnalysis functions making it impossible to print out function info for a
specific SILFunction (and also to by mistake when working locally run into a
linkage error).
With this change, I refactored the printing functionality for individual
functions from CallerAnalysis onto the FunctionInfo and had CallerAnalysis just
call it for all Functions.
Empty access scopes can be a result of e.g. redundant-load-elimination.
It's still important to keep those access scopes to detect access violations.
Even if the load is physically not done anymore, in case of a conflicting access a propagated load is still wrong and must be detected.
rdar://164571252
Teach SIL type lowering to recursively track custom vs. default deinit status.
Determine whether each type recursively only has default deinitialization. This
includes any recursive deinitializers that may be invoked by releasing a
reference held by this type.
If a type only has default deinitialization, then the deinitializer cannot
have any semantically-visible side effects. It cannot write to any memory