Skip to content
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

Optimizer: Rework peephole optimizations to replace existential (archetypes) with concrete types #79674

Merged
merged 22 commits into from
Mar 10, 2025

Conversation

eeckstein
Copy link
Contributor

Those optimizations were done in SILCombine, but the implementation is very complicated.
This PR starts to de-compose the complex logic into small instruction simplifications.

It also adds the ability to figure out the concrete type of metatype existentials - which was missing in the current implementation in SILCombine.

This lets us optimize code like

func takesA<T: ProtoA>(_ type: T.Type) -> T? {
  // Constant fold this cast when takesA is specialized for a concrete type.
  if let bType = T.self as? ProtoB.Type {
    return createB(bType) as? T
  }
  return nil
}

This PR also contains a lot of Swift SIL infrastructure improvements.
See the commit messages for details.

Fixes an optimization deficiency
rdar://139199849

@eeckstein eeckstein requested review from atrick, meg-gupta and nate-chandler and removed request for xedin and hborla February 27, 2025 21:12
@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein
Copy link
Contributor Author

@swift-ci benchmark

@eeckstein
Copy link
Contributor Author

@swift-ci apple silicon benchmark

@eeckstein
Copy link
Contributor Author

@swift-ci test source compatibility

@@ -28,6 +28,15 @@ public struct SubstitutionMap: CustomStringConvertible {
public init() {
self.bridged = BridgedSubstitutionMap()
}

public init(forGenericSignatureOf functionType: CanonicalType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just take a generic signature and not a generic function type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have GenericSignature bridged to Swift yet. I can do this in a follow up PR

Copy link
Contributor Author

@eeckstein eeckstein Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, I included it in this PR

@atrick
Copy link
Contributor

atrick commented Feb 28, 2025

I haven't looked at the new implementation yet. I'll just repeat the same advice I gave when the original version was checked in. Attempting to pattern match backward starting at the call site is a misguided approach. Normal compilers would do this kind of optimization using forward sparse constant propagation (SCCP), which propagates type information as well as constants.

@eeckstein eeckstein force-pushed the simplify-open-existential-metatype branch from acb2617 to 64f6fe2 Compare February 28, 2025 15:05
@eeckstein
Copy link
Contributor Author

eeckstein commented Feb 28, 2025

Attempting to pattern match backward starting at the call site is a misguided approach.

This is not what's happening here. The peephole optimizations run in SILCombine's worklist loop so information is (also) propagated in forward direction.

And SCCP - well we don't do it in the whole compiler. And especially for this purpose it would be over-engineered. An existential which is conditionally initialized with two different concrete types (and one case is dead, depending on the other type) is really a very esoteric corner case. We need to make sure to be able to optimize the common cases - and we are not there, yet.

@atrick
Copy link
Contributor

atrick commented Mar 2, 2025

This is not what's happening here. The peephole optimizations run in SILCombine's worklist loop so information is (also) propagated in forward direction.

Right, from your description it sounded like was your approach. I'm surprised it was possible without new instructions. That's a good approach as long as each individual transform is clearly moving in one direction toward a simpler or canonical form. A big problem with peepholes is that two different optimizations might not agree on the canonical form.

@atrick
Copy link
Contributor

atrick commented Mar 2, 2025

And SCCP - well we don't do it in the whole compiler.

Yes, I'm not suggesting doing that instead of this PR. I'm just repeating my advice from a decade ago so it isn't forgotten. It's a simpler way to do specialization vs backward pattern matching and not that difficult, expensive, or complicated. It's similar to how much simpler it is to propagate "borrowed-from" information using forward data flow vs. backward recursion (find-adjacent-phi)

@eeckstein eeckstein force-pushed the simplify-open-existential-metatype branch from 64f6fe2 to 1f60301 Compare March 6, 2025 07:40
@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein
Copy link
Contributor Author

@swift-ci apple silicon benchmark

@eeckstein
Copy link
Contributor Author

@swift-ci test source compatibility

Check if an operand's instruction has been deleted in `UseList.next()`.
This allows to delete an instruction, which has two uses of a value, during use-list iteration.
There is no `isNominal` property in SIL.Type.
* getting the formal source and target types of casts
* `isFromVarDecl` and `usesMoveableValueDebugInfo` for AllocStackInst
* WitnessMethod APIs
* `TryApply.isNonAsync`
The optional C++ type was bridged to a non-optional Swift type.
The correct way is to bridged the non-optional C++ type to the non-optional Swift type.
* `createCheckedCastAddrBranch`
* `createUnconditionalCheckedCastAddr`
* `createDebugValue`
* `createWitnessMethod`
* `createInitEnumDataAddr`
…ing the conformances in an Array

Instead of a ConformanceArray
eeckstein added 15 commits March 7, 2025 15:59
…ting SILCombine visit function.

So far a `SILCombineSimplifiable` could only replace a SILCombine visit implementation.
With the `SWIFT_SILCOMBINE_PASS_WITH_LEGACY` (to be used in Passes.def) it's possible to keep an existing C++ implementation and on top of that add a Swift Simplification pass.
…ypes.

Replace `unconditional_checked_cast` to an existential metatype with an `init_existential_metatype`, it the source is a conforming type.
Note that init_existential_metatype is better than unconditional_checked_cast because it does not need to do any runtime casting.
* factor out common methods of AST Type/CanonicalType into a `TypeProperties` protocol.
* add more APIs to AST Type/CanoncialType.
* move `MetatypeRepresentation` from SIL.Type to AST.Type and implement it with a swift enum.
* let `Builder.createMetatype` get a CanonicalType as instance type, because the instance type must not be a lowered type.
And simplify the implementation of `var Instruction.definedOperands`
…rns a sequence of the specific instruction class and not a sequence of `Instruction`
* `func Instruction.dominatesInSameBlock(_ otherInst: Instruction) -> Bool`
* `var Instruction.concreteTypeOfDependentExistentialArchetype`
and add `var Type.invocationGenericSignatureOfFunctionType`
And let it conform to `NoReflectionChildren` to make it's debug dump in lldb nicer
…y instructions

If an apply uses an existential archetype (`@opened("...")`) and the concrete type is known, replace the existential archetype with the concrete type
  1. in the apply's substitution map
  2. in the arguments, e.g. by inserting address casts
For example:
```
  %5 = apply %1<@opend("...")>(%2) : <τ_0_0> (τ_0_0) -> ()
```
->
```
  %4 = unchecked_addr_cast %2 to $*ConcreteType
  %5 = apply %1<ConcreteType>(%4) : <τ_0_0> (τ_0_0) -> ()
```
* Reimplement most of the logic in Swift as an Instruction simplification and remove the old code from SILCombine
* support more cases of existential archetype replacements:

For example:
```
  %0 = alloc_stack $any P
  %1 = init_existential_addr %0, $T
  use %1
```
is transformed to
```
  %0 = alloc_stack $T
  use %0
```

Also, if the alloc_stack is already an opened existential and the concrete type is known,
replace it as well:
```
  %0 = metatype $@thick T.Type
  %1 = init_existential_metatype %0, $@thick any P.Type
  %2 = open_existential_metatype %1 : $@thick any P.Type to $@thick (@opened("X", P) Self).Type
  ...
  %3 = alloc_stack $@opened("X", any P) Self
  use %3
```
is transformed to
```
  ...
  %3 = alloc_stack $T
  use %3
```
Don't get the swift source version string, but the debug dump string
@eeckstein eeckstein force-pushed the simplify-open-existential-metatype branch from 1f60301 to 26c7310 Compare March 7, 2025 14:59
@eeckstein eeckstein requested a review from ktoso as a code owner March 7, 2025 14:59
@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein eeckstein merged commit 737b5ec into swiftlang:main Mar 10, 2025
5 checks passed
@eeckstein eeckstein deleted the simplify-open-existential-metatype branch March 10, 2025 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants