https://forums.swift.org/t/improving-the-representation-of-polymorphic-interfaces-in-sil-with-substituted-function-types/29711
This prepares SIL to be able to more accurately preserve the calling convention of
polymorphic generic interfaces by letting the type system represent "substituted function types".
We add a couple of fields to SILFunctionType to support this:
- A substitution map, accessed by `getSubstitutions()`, which maps the generic signature
of the function to its concrete implementation. This will allow, for instance, a protocol
witness for a requirement of type `<Self: P> (Self, ...) -> ...` for a concrete conforming
type `Foo` to express its type as `<Self: P> (Self, ...) -> ... for <Foo>`, preserving the relation
to the protocol interface without relying on the pile of hacks that is the `witness_method`
protocol.
- A bool for whether the generic signature of the function is "implied" by the substitutions.
If true, the generic signature isn't really part of the calling convention of the function.
This will allow closure types to distinguish a closure being passed to a generic function, like
`<T, U> in (*T, *U) -> T for <Int, String>`, from the concrete type `(*Int, *String) -> Int`,
which will make it easier for us to differentiate the representation of those as types, for
instance by giving them different pointer authentication discriminators to harden arm64e
code.
This patch is currently NFC, it just introduces the new APIs and takes a first pass at updating
code to use them. Much more work will need to be done once we start exercising these new
fields.
This does bifurcate some existing APIs:
- SILFunctionType now has two accessors to get its generic signature.
`getSubstGenericSignature` gets the generic signature that is used to apply its
substitution map, if any. `getInvocationGenericSignature` gets the generic signature
used to invoke the function at apply sites. These differ if the generic signature is
implied.
- SILParameterInfo and SILResultInfo values carry the unsubstituted types of the parameters
and results of the function. They now have two APIs to get that type. `getInterfaceType`
returns the unsubstituted type of the generic interface, and
`getArgumentType`/`getReturnValueType` produce the substituted type that is used at
apply sites.
Use the newly-introduced property wrapper backing initializer function
in definite initialization (DI) to form the assign_by_wrapper
instruction, rather than forming a reference to the (only) property
wrapper's `init(wrappedValue:)`. This allows DI to work on properties
that have multiple, composed property wrappers applied to them.
Fixes a crash when a function, which assigns to a wrapped property has additional generic parameters.
https://bugs.swift.org/browse/SR-11484
rdar://problem/55442328
This mostly requires changing various entry points to pass around a
TypeConverter instead of a SILModule. I've left behind entry points
that take a SILModule for a few methods like SILType::subst() to
avoid creating even more churn.
This will ensure that the optimizer never eliminates the strong_retain. This
operation is meant to be unmanaged, we should respect the user's choice here
even in optimized builds.
We've fixed a number of bugs recently where callers did not expect
to get a null Type out of subst(). This occurs particularly often
in SourceKit, where the input AST is often invalid and the types
resulting from substitution are mostly used for display.
Let's fix all these potential problems in one fell swoop by changing
subst() to always return a Type, possibly one containing ErrorTypes.
Only a couple of places depended on the old behavior, and they were
easy enough to change from checking for a null Type to checking if
the result responds with true to hasError().
Also while we're at it, simplify a few call sites of subst().
Since the return value of getAccessor() depends on mutable state, it
does not make sense in the request evaluator world. Let's begin by
removing some utility methods derived from getAccessor(), replacing
calls to them with calls to getAccessor().
When multiple property wrapper attributes are provided on a declaration,
compose them outside-in to form a composite property wrapper type. For
example,
@A @B @C var foo = 17
will produce
var $foo = A(initialValue: B(initialValue: C(initialValue: 17)))
and foo's getter/setter will access "foo.value.value.value".
ManagedValue::{forward,assign}Into both have the signature SILGenFunction &,
SILLocation, SILValue. For some reason copyInto has SILLocation and SILValue
swapped. This commit standardizes copyInto to match the others.
OpaqueValueState used to store a SILValue, so back then the IsConsumable flag
was meaningful. But now we can just check if the ManagedValue has a cleanup
or not.
Also, we were passing around an opened ArchetypeType for no good reason.
Instead of passing in a DeclContext, which we don't have when emitting a keypath
accessor, pass in a ModuleDecl and ResilienceExpansion.
Keypaths now work well enough in inlinable contexts that we can check in an
end-to-end resilience test.
Previously, the stdlib provided:
- getters for AnyKeyPath and PartialKeyPath, which have remained;
- a getter for KeyPath, which still exists alongside a new read
coroutine; and
- a pair of owned mutable addressors that provided modify-like behavior
for WritableKeyPath and ReferenceWritableKeyPath, which have been
replaced with modify coroutines and augmented with dedicated setters.
SILGen then uses the most efficient accessor available for the access
it's been asked to do: for example, if it's been asked to produce a
borrowed r-value, it uses the read accessor.
Providing a broad spectrum of accessor functions here seems acceptable
because the code-size hit is fixed-size: we don't need to generate
extra code per storage declaration to support more alternatives for
key paths.
Note that this is just the compiler ABI; the implementation is still
basically what it was. That means the implementation of the setters
and the read accessor is pretty far from optimal. But we can improve
the implementation later; we can't improve the ABI.
The coroutine accessors have to be implemented in C++ and used via
hand-rolled declarations in SILGen because it's not currently possible
to declare independent coroutine accessors in Swift.
Not NFC because it also fixes an evaluation order bug (and reorders
some less-important stuff): the key-path expression needs to be
evaluated immediately during formal evaluation and cannot be delayed
until start-of-access.
Dynamic replacements are currently written in extensions as
extension ExtendedType {
@_dynamicReplacement(for: replacedFun())
func replacement() { }
}
The runtime implementation allows an implementation in the future where
dynamic replacements are gather in a scope and can be dynamically
enabled and disabled.
For example:
dynamic_extension_scope CollectionOfReplacements {
extension ExtentedType {
func replacedFun() {}
}
extension ExtentedType2 {
func replacedFun() {}
}
}
CollectionOfReplacements.enable()
CollectionOfReplacements.disable()
Beyond just being better code, this also fixes problems where
the duplicate code didn't handle e.g. _read accessors.
I believe this finishes unblocking _read in the stdlib (rdar://45230566).
To make that work, enter appropriate scopes (ArgumentScopes and
FormalEvaluationScopes) at a bunch of places. But note that l-value
emission generally can't enter such a scope, so in generic routines
like emitOpenExistentialExpr we have to just assert that we're
already in a scope.