More groundwork for protocols with superclass constraints.
In several places we need to distinguish between existential
types that have a superclass term (MyClass & Proto) and
existential types containing a protocol with a superclass
constraint.
This is similar to how I can write 'AnyObject & Proto', or
write 'Proto1 & Proto2' where Proto1 has an ': AnyObject'
in its inheritance clause.
Note that some of the usages will be revisited later as
I do more refactoring and testing. This is just a first pass.
The storage kind has been replaced with three separate "impl kinds",
one for each of the basic access kinds (read, write, and read/write).
This makes it far easier to mix-and-match implementations of different
accessors, as well as subtleties like implementing both a setter
and an independent read/write operation.
AccessStrategy has become a bit more explicit about how exactly the
access should be implemented. For example, the accessor-based kinds
now carry the exact accessor intended to be used. Also, I've shifted
responsibilities slightly between AccessStrategy and AccessSemantics
so that AccessSemantics::Ordinary can be used except in the sorts of
semantic-bypasses that accessor synthesis wants. This requires
knowing the correct DC of the access when computing the access strategy;
the upshot is that SILGenFunction now needs a DC.
Accessor synthesis has been reworked so that only the declarations are
built immediately; body synthesis can be safely delayed out of the main
decl-checking path. This caused a large number of ramifications,
especially for lazy properties, and greatly inflated the size of this
patch. That is... really regrettable. The impetus for changing this
was necessity: I needed to rework accessor synthesis to end its reliance
on distinctions like Stored vs. StoredWithTrivialAccessors, and those
fixes were exposing serious re-entrancy problems, and fixing that... well.
Breaking the fixes apart at this point would be a serious endeavor.
* Teach findAccessedStorage about global addressors.
AccessedStorage now properly represents access to global variables, even if they
haven't been fully optimized down to global_addr instructions.
This is essential for optimizing dynamic exclusivity checks. As a
verified SIL property, all access to globals and class properties
needs to be identifiable.
* Add stronger SILVerifier support for formal access.
Ensure that all formal access follows recognizable patterns
at all points in the SIL pipeline.
This is important to run acccess enforcement optimization late in the pipeline.
The major important thing here is that by using copy_unowned_value we can
guarantee that the non-ownership SIL ARC optimizer will treat the release
associated with the strong_retain_unowned as on a distinc rc-identity from its
argument. As an example of this problem consider the following SILGen like
output:
----
%1 = copy_value %0 : $Builtin.NativeObject
%2 = ref_to_unowned %1
%3 = copy_unowned_value %2
destroy_value %1
...
destroy_value %3
----
In this case, we are converting a strong reference to an unowned value and then
lifetime extending the value past the original value. After eliminating
ownership this lowers to:
----
strong_retain %0 : $Builtin.NativeObject
%1 = ref_to_unowned %0
strong_retain_unowned %1
strong_release %0
...
strong_release %0
----
From an RC identity perspective, we have now blurred the lines in between %3 and
%1 in the previous example. This can then result in the following miscompile:
----
%1 = ref_to_unowned %0
strong_retain_unowned %1
...
strong_release %0
----
In this case, it is possible that we created a lifetime gap that will then cause
strong_retain_unowned to assert. By not lowering copy_unowned_value throughout
the SIL pipeline, we instead get this after lowering:
----
strong_retain %0 : $Builtin.NativeObject
%1 = ref_to_unowned %0
%2 = copy_unowned_value %1
strong_release %0
...
strong_release %2
----
And we do not miscompile since we preserved the high level rc identity
pairing.
There shouldn't be any performance impact since we do not really optimize
strong_retain_unowned at the SIL level. I went through all of the places that
strong_retain_unowned was referenced and added appropriate handling for
copy_unowned_value.
rdar://41328987
**NOTE** I am going to remove strong_retain_unowned in a forthcoming commit. I
just want something more minimal for cherry-picking purposes.
Client code can make a best effort at emitting a key path referencing a property with its publicly exposed API, which in the common case will match what the defining module would produce as the canonical key path component representation of the declaration. We can reduce the code size impact of these descriptors by not emitting them when there's no hidden or possibly-resiliently-changed-in-the-past information about a storage declaration, having the property descriptor symbol reference a sentinel value telling client key paths to use their definition of the key path component.
This ensures that DI creates dealloc_box in cases where the box is uninitialized
conditionally.
In the process, I also discovered that we were missing a test case for DI being
used by LLDB. Long term we shouldn't support that code pattern in the general
case, but for now we at least need a test case for it.
rdar://40332620
Upstream has renamed the DEBUG() macro to LLVM_DEBUG. This updates swift
accordingly:
$ find . -name \*.cpp -print -exec sed -i "" -E "s/ DEBUG\(/ LLVM_DEBUG(/g" {} \;
This is necessary for the correctness of optimizations that remove markers. We
must know that an unidentified access can never see class or global properties.
Fixed-ABI means that we can do value operations on the type without
any metadata: value-allocations, copies, and destroys. It's currently
equivalent to being fixed-size, but (1) being fixed-size isn't useful
by itself at the SIL level and (2) you can imagine resilience or generics
micro-optimizations where there's like an attribute that tells us the
size of a type without actually telling us how to copy it. All types
are fixed-ABI except:
- layout-unconstrained generic types,
- resilient value types, and
- value types which contain a subobject of such a type (except within
indirect enum cases).
ABI-accessible means that we can perform value operations at all.
We might not be able to if the type is not fixed-ABI and it is private
to a different file (in non-WMO builds) or internal to a different
module, because in such cases we will not be able to access its metadata.
In general, we can't use such types `T` directly, but we may be able to
use types `C` that contain such types as subobjects. Furthermore, we
may be reasonably exposed to SIL that performs operations that treat `C`
as non-opaque, e.g. if `C` is frozen (as it will be by default for
modules in Swift 5). We can still achieve correctness in these cases
as long as we don't either:
- inline code that contains value operations on `T` or
- attempt to recursively expand a value operation on `T` into value
operations on its subobjects.
The SIL optimizer currently never tries to expand value operations on
objects in memory. However, IRGen always recursively expands value
operations on frozen types; that will be fixed in a follow-up patch.
The SIL verification that I've added here is definitely incomplete.
For large basic blocks the dominance check between two instructions in the same block was very expensive.
Although the verifier does not run in no-assert compiler builds, we don't want it to be extra slow for assert builds.
https://bugs.swift.org/browse/SR-7632