-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SILOptimizer] Generalize optimization of static keypaths #28799
Conversation
We have an optimization in SILCombiner that "inlines" the use of compile-time constant key paths by performing the property access directly instead of calling a runtime function (leading to huge performance gains e.g. for heavy use of @dynamicMemberLookup). However, this optimization previously only supported key paths which solely access stored properties, so computed properties, optional chaining, etc. still had to call a runtime function. This commit generalizes the optimization to support all types of key paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Tow general comments:
- You are using a class hierarchy to implement the different kind of projections. That's nice. In addition you are using closures for the callback. Given that you already have the class hierarchy, you don't need the closures, which would make the code a bit easier to understand: If you store child-links in the projectors, you could do something like:
void project(AccessType accessType, SILValue parentValue) {
// Stuff needed before the child projection, e.g. compute myAddress
child->project(accessType, myAddress)
// Stuff needed after the child projection
}
- About the tests: For each projector type, can you please add tests which use the non-trivial types
GenClass
orSimpleClass
as payloads. This will test if the ownership is correct in the generated SIL, i.e. no over-release and no memory leaks.
Also: can you please add tests which test the combination of several projectors, i.e. key paths with multiple different components?
@jckarter Can you please answer the questions about the subscript context and the generic environment? |
@eeckstein Thanks for the feedback! Regarding your first point: I used the closures in addition to the class hierarchy because the access type needs to flow backwards from leaf to root. (For example, when projecting The simplest alternative I can think of is to instead store the access types as member variables, determining them ahead-of-time when creating the projector in |
@NobodyNada Ah, I see. Makes sense (no need to change it). |
Rather than try to reconstruct the memory layout of the generic argument buffer, I think it'd be better to implement a To keep this PR tractable, maybe you make it so that this patch only supports nongeneric computed components for now, and try implementing a Thanks for implementing this! |
It might make sense to make this its own pass. One less thing in SILCombine's scope and it probably doesn't benefit from being run multiple times. |
@jckarter Thanks! I’ll go with what you suggested and leave indices/generic environments for a followup PR. I don’t think I’ll have time to work on that followup PR anytime within the next few months, but I may have a chance around summertime. I do have one question though: Under what circumstances does a generic environment get passed to a subscript accessor? I experimented a bit but didn’t come up with a test case that triggered that. @zoecarver Sounds good, I can make that change. At what point in the pipeline should the pass run? My best guess is the beginning of |
@NobodyNada Sounds good, no rush! A generic environment ought to get passed down when a key path literal appears in an unspecialized generic context, and its index types are dependent on that generic context. For example:
|
@eeckstein @jckarter Thanks for the feedback! I finally got around to making those changes; sorry for the delay. |
@swift-ci Please test |
Build failed |
Build failed |
Hmm, both of the failing tests pass on my machine. It looks like the tests only failed on Linux (it says the Mac tests were aborted), so I'll try to investigate on a Linux machine sometime this week. It seems strange that those crashes would be platform-specific though. |
@swift-ci Please test OS X |
It seems like
Let's see if those tests crash on macOS as well. |
Hmm, I could not reproduce the failing tests on Ubuntu 18.04. I can't access the CI build logs anymore to compare; have they been archived somewhere or would we have to re-run the tests to get new logs? |
@swift-ci test linux |
Build failed |
When a computed property returns a generic, the accessor's function type may involve a type parameter that needs to be resolved using the key path instruction's substitution map.
@swift-ci Please test |
Build failed |
Build failed |
emitKeyPathComponentForDecl was only checking if the setter was accessible from the current module, not the current function. This failed when accessing an internal setter from a module imported for testing.
I was inconsistently providing initialized or uninitialized memory to the callback when projecting a settable address, depending on component type. We should always provide an uninitialized address.
I believe I fixed all test failures. There were three issues:
The failures were not platform-specific after all, but they only showed up on Linux CI because the Mac CI bot ran the tests with optimizations disabled. |
@swift-ci please test |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Thanks @NobodyNada! |
Unfortunately, it looks like this still leads to a test failure in stdlib/KeyPath.swift on 32-bit slices (which aren't covered by our normal PR testing):
You should be able to use |
@jckarter I haven’t run the tests locally, but line 434 of KeyPathProjector.swift should be removed since optAddr is uninitialized here (I must have missed that in |
@NobodyNada Sure, thanks for the quick response! |
@NobodyNada I went ahead and reverted this in #29946. Let me know when you have an updated patch so we can get this back in. Thanks again! |
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
KeyPath's getter/setter/hash/equals functions have their own calling convention, which receives generic arguments and embedded indices from a given KeyPath argument buffer. The convention was previously implemented by: 1. Accepting an argument buffer as an UnsafeRawPointer and casting it to indices tuple pointer in SIL. 2. Bind generic arguments info from the given argument buffer while emitting prologue in IRGen by creating a new forwarding thunk. This 2-phase lowering approach was not ideal, as it blocked KeyPath projection optimization [^1], and also required having a target arch specific signature lowering logic in SIL-level [^2]. This patch centralizes the KeyPath accessor calling convention logic to IRGen, by introducing `@convention(keypath_accessor_XXX)` convention in SIL and lowering it in IRGen. This change unblocks the KeyPath projection optimization while capturing subscript indices, and also makes it easier to support WebAssembly target. [^1]: swiftlang#28799 [^2]: https://forums.swift.org/t/wasm-support/16087/21
We have an optimization in SILCombiner (introduced in #24929) that inlines compile-time constant key paths by performing the property access directly instead of calling a runtime function (leading to huge performance gains e.g. for heavy use of
@dynamicMemberLookup
). However, this optimization previously only supported key paths which solely access stored properties, so computed properties, optional chaining, etc. still had to call a runtime function. This commit generalizes the optimization to support all types of key paths.This is my first time contributing to any sort of compiler development, so it's likely I've overlooked something. The previous iteration of this optimzation essentially computed the stored property offsets and returned the resulting address. I still used with a design that projects the key path to an address since that's what's returned by the runtime functions that we're replacing, but my implementation is more complex because projections for most types of key path components need both setup and teardown logic to e.g. deallocate temporaries, call getters and setters, or re-wrap optionals.
I created an abstract class called
KeyPathProjector
. It has a method calledproject
, which accepts an access type (get, set, or modify) and a callback lambda. This method inserts SIL instructions to project the key path to an address, invokes the callback, and then inserts instructions to tear down the projection. A complete key path is handled (internally toKeyPathProjector.cpp
) by creating a "chained" projector: each projector in the chain is responsible for projecting a single component on top of the previously projected result. For each type of key path component, there is one subclass ofKeyPathProjector
(again, all of these are implementation details ofKeyPathProjector.cpp
).There are a few special cases (such as access enforcement and optional chaining), but I think my logic works, and it passes all the tests I've tried. I do have a couple concerns though: