Commit Graph

303 Commits

Author SHA1 Message Date
Erik Eckstein 541664237c Optimizer: add the MergeBorrowScopes pass
The pass merges to adjacent borrow scopes in a basic block.

```
  %2 = begin_borrow %1
  use(%2)
  end_borrow %2
  ...
  %6 = begin_borrow %1
  use(%6)
  end_borrow %6

```
->
```
  %2 = begin_borrow %1
  use(%2)
  ...
  use(%2)
  end_borrow %2
```

This helps other optimizations, like common-subexpression-elimination, because the borrow liveranges are larger and not split.
2026-06-10 08:18:51 +02:00
Erik Eckstein 8949e1387e Optimizer: simplify index_addr and index_raw_pointer
Which replaces the old index_addr SIL combine optimization
2026-06-09 16:17:53 +02:00
Erik Eckstein 40c3598cc3 Optimizer: add simplification for unchecked_ownership_conversion
Replaces a sequence which is commonly found in generated class destructors:

```
  %1 = some_owned_value
  %2 = begin_borrow %1                // the only use of %1
  %3 = unchecked_ref_cast %2 to $C
  %4 = unchecked_ownership_conversion %3, @guaranteed to @owned
  end_borrow %2
  end_lifetime %1
```
->
```
  %1 = some_owned_value
  %2 = begin_borrow %1                // now dead
  end_borrow %2
  %4 = unchecked_ref_cast %1 to $C
```
2026-06-03 07:06:58 +02:00
elsa 83d4291709 CSE Optimizer Pass rewrite (#88248)
Resolves rdar://173862129
2026-05-15 19:11:10 +01:00
Meghana Gupta 784da0a090 Correctly invalidate analyses after keypath optimization
Keypath simplification can create new basic blocks when projecting
optional chain components (OptionalChainProjector splits the block and creates new conditional branches).
However, the Swift-side tryOptimizeKeypath was not notifying the pass manager about
CFG or instruction changes, leaving analyses like the dominator tree stale.
Fix this by calling notifyBranchesChanged() and notifyInstructionsChanged()
when tryOptimizeKeypath succeeds.
2026-04-24 09:43:42 -07:00
Doug Gregor aad51cab01 [Embedded] Remove the ability to disable existentials in Embedded Swift
Support for existentials in Embedded Swift has been available for a
little while now and appears to be solid. Remove the ability to disable
them (via `-disable-experimental-feature EmbeddedExistentials`), both
because it simplifies the code and because it's an ABI break to
disable the feature.
2026-04-17 17:38:01 -07:00
Erik Eckstein 080f9e6a81 Optimizer: add the ConformanceCheckOptimization optimization pass
Optimizes protocol conformance checking by pre-populating vtables with conformance information for "fast-cast" protocols that have superclass constraints.

This optimization works by:

1. Identifying classes that are eligible for optimization (have fixed metadata layout, are not open access, and belong to the current module)

2. Finding protocols, enabled for fast casting nad that have superclass constraints and belong to the current module

3. Pre-computing conformance checks for these protocols and storing the results directly in the vtable, eliminating the need for runtime conformance lookups
2026-04-03 07:49:35 +02:00
Erik Eckstein b97b3640f9 Optimizer: add some Context APIs
* `var isWholeModule`
* `var moduleDecl`
* `ClassDecl.hasFixedMetadataLayout`
2026-04-03 07:49:34 +02:00
Erik Eckstein cc3c701462 Optimizer: move the cond_fail true optimization out of SILCombine into its own optimization pass
This optimization handles unconditional `cond_fail` instructions, i.e. `cond_fail`s with a non-zero `integer_literal` operand.
It cuts off the control flow after such a `cond_fail` by inserting an `unreachable` instruction.
However, this optimization cannot be done as instruction simplification, because it can leave OSSA lifetimes uncompleted.
Other simplification may depend on complete lifetimes.
Similar for constant folding failing casts: we also cannot insert an `unreachable` there.

Instead, do this optimization a new function pass (which can do lifetime completion).

Fixes a SIL verification error
rdar://173728487
2026-04-02 07:56:25 +02:00
Daniil Kovalev 0266a0a0df [AutoDiff][sil-combine] Support differentiable_function with borrowed scopes (#87826)
Support folding `differentiable_function_extract` of
`differentiable_function` in presence of borrowed scopes. Such folding
is crucial for VJP inlining, which is required for AutoDiff closure
specialization (ADCS) pass working properly.

Such handling was not required previously, but now ADCS runs in presence
of OSSA, making handling of borrowed scopes essential.

The folding logic is based on similar logic for
`struct`/`struct_extract` simplification.

Note that the `AutoDiff/SILOptimizer/licm_context.swift` test needs to
be modified since it relies on specific inlining behavior. Particularly,
we need to force inlining of the implicitly generated VJP of `B.a()`
into the VJP of `q()`. Without `@inline(__always)`, this particular
inlining decision stops happening on MacOS because the SIL combiner
changes from this patch affect the inlining decisions.

The changes from this patch make some new inlining decisions possible to
be taken befor attempting to inline the VJP of `B.a()` into the VJP of
`q()`. As a result, the VJP of `B.a()` becomes bigger because of other
VJPs being inlined into that, and the inlining cost of `B.a()` VJP
becomes too high when trying to perform inlining inside the VJP of
`q()`.

Depends on #87859 to allow force-inlining in
`AutoDiff/SILOptimizer/licm_context.swift`.
2026-03-18 18:06:18 +00:00
Erik Eckstein 77f64398e5 Optimizer: run SimpilfyStructExtract also in SILCombine 2026-03-10 07:56:51 +01:00
Erik Eckstein b155f18741 simplify CheckedCastBranchInst: remove dead checked_cast_br instructions and run the simplification in SILCombine
For example:

```
  checked_cast_br B in %1 to X, bb1, bb2
bb1(%2 : @owned $X):
  destroy_value %2         // no other instructions in this block
  br bb3
bb2(%4 : @owned $B):
  destroy_value %4         // no other instructions in this block
  br bb3
```
2026-03-10 07:56:51 +01:00
Erik Eckstein 2d2b457be4 Optimizer: simplify move_value
The `move_value` instruction is only used to specify flags.
Remove a `move_value` which either doesn't specify any flags or which flags are not relevant outside the mandatory pipeline, like `[lexical]`.
2026-03-10 07:56:49 +01:00
Alejandro Alonso a4fdb7d9ec Simplify the identity for dereference borrows 2026-01-27 11:04:25 -08:00
Alejandro Alonso 9f95dc7695 Simplify init_borrow_addr when the borrow type is statically known
make sure we register as well as add test
2026-01-27 11:03:53 -08:00
Erik Eckstein 18063707b5 Optimizer: enable complete OSSA lifetimes throughout the pass pipeline
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
2026-01-22 17:41:48 +01:00
Erik Eckstein 7b6247332f Optimizer: add FunctionPassContext.updateAnalysis()
This is useful if a pass needs an updated analysis after invalidating the SIL of a function.
2026-01-22 17:41:24 +01:00
Erik Eckstein 0f0aa0c17b Optimizer: require that there are no unreachable blocks and infinite loops in OSSA
These two new invariants eliminate corner cases which caused bugs if optimization didn't handle them.
Also, it will significantly simplify lifetime completion.

The implementation basically consists of these changes:
* add a flag in SILFunction which tells optimization if they need to take care of infinite loops
* add a utility to break infinite loops
* let all optimizations remove unreachable blocks and break infinite loops if necessary
* add verification to check the new SIL invariants

The new `breakIfniniteLoops` utility breaks infinite loops in the control flow by inserting an "artificial" loop exit to a new dead-end block with an `unreachable`.
It inserts a `cond_br` with a `builtin "infinite_loop_true_condition"`:
```
bb0:
  br bb1
bb1:
  br bb1              // back-end branch
```
->
```
bb0:
  br bb1
bb1:
  %1 = builtin "infinite_loop_true_condition"() // always true, but the compiler doesn't know
  cond_br %1, bb2, bb3
bb2:                  // new back-end block
  br bb1
bb3:                  // new dead-end block
  unreachable
```
2026-01-22 17:41:23 +01:00
Erik Eckstein 34aa3c00d3 Optimizer: move var needFixStackNesting from MutatingContext to FunctionPassContext 2026-01-22 17:41:23 +01:00
Erik Eckstein 16cb80b60d Optimizer: implement all of cond_fail simplification as instruction simplification
Inserts an unreachable after an unconditional fail:
```
  %0 = integer_literal 1
  cond_fail %0, "message"
  // following instructions
```
->
```
  %0 = integer_literal 1
  cond_fail %0, "message"
  unreachable
deadblock:
  // following instructions
```

Remove the old SILCombine implementation because it's not working well with OSSA lifetime completion.
This also required to move the `shouldRemoveCondFail` utility function from SILCombine to InstOptUtils.
2026-01-22 17:41:22 +01:00
Erik Eckstein c822615dc2 Optimizer: convert the UpdateBorrowedFromPass pass to a test
And remove the now empty TestPasses directory.
2025-12-15 10:01:51 +01:00
Erik Eckstein bff873dea0 Optimizer: convert the SILPrinter and TestInstructionIteration passes to tests 2025-12-15 10:01:41 +01:00
Erik Eckstein c639c716b9 Optimizer: convert the RangeDumper pass to a test 2025-12-15 10:01:41 +01:00
Erik Eckstein d8a85693c8 Optimizer: convert the FunctionUsesDumper pass to a test 2025-12-15 10:01:41 +01:00
Erik Eckstein 6ef1064a92 Optimizer: add ModuleTest 2025-12-15 10:01:41 +01:00
Erik Eckstein 751e52cdc3 Optimizer: convert the DeadEndBlockDumper pass to a test 2025-12-15 10:01:40 +01:00
Erik Eckstein dc38a2226e Optimizer: convert the AliasInfoDumper and MemBehaviorDumper passes to tests 2025-12-15 09:09:41 +01:00
Erik Eckstein edacd4c4e3 Optimizer: convert the AccessDumper from a pass to a test 2025-12-15 09:09:41 +01:00
Erik Eckstein abc40b3b55 Optimizer: convert the EscapeInfoDumper from a pass to a test 2025-12-15 09:09:40 +01:00
Arnold Schwaighofer 4d879967a7 [embedded] Feature::EmbeddedExistentials requires Feature::Embedded 2025-12-09 10:21:51 -08:00
eeckstein 9304ce951c Merge pull request #85707 from eeckstein/embedded-witness-method-specialization
embedded: change the function representation of directly called witness methods
2025-12-01 09:36:45 +01:00
eeckstein 46c69e40c1 Merge pull request #85533 from eeckstein/fix-access-simplification
SILOptimizer: don't remove empty conflicting access scopes
2025-12-01 09:35:48 +01:00
Erik Eckstein d7b75b6819 Optimizer: register explicit-copy instruction simplifications for SILCombine
Usually `explicit_copy_addr` and `explicit_copy_value` don't survive until the first SILCombine pass run anyway.
But if they do, the simplifications need to be registered, otherwise SILCombine will complain.
2025-11-27 19:40:31 +01:00
Erik Eckstein 64dd574bea embedded: change the function representation of directly called witness methods
This is needed in Embedded Swift because the `witness_method` convention requires passing the witness table to the callee.
However, the witness table is not necessarily available.
A witness table is only generated if an existential value of a protocol is created.

This is a rare situation because only witness thunks have `witness_method` convention and those thunks are created as "transparent" functions, which means they are always inlined (after de-virtualization of a witness method call).
However, inlining - even of transparent functions - can fail for some reasons.

This change adds a new EmbeddedWitnessCallSpecialization pass:
If a function with `witness_method` convention is directly called, the function is specialized by changing the convention to `method` and the call is replaced by a call to the specialized function:

```
  %1 = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
  %2 = apply %1(%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
...
sil [ossa] @callee : $@convention(witness_method: P) (@guaranteed C) -> () {
  ...
}
```
->
```
  %1 = function_ref @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> ()
  %2 = apply %1(%0) : $@convention(method) (@guaranteed C) -> ()
...
// specialized callee
sil shared [ossa] @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> () {
  ...
}
```

Fixes a compiler crash
rdar://165184147
2025-11-26 16:23:47 +01:00
Erik Eckstein 17ca4d9787 Optimizer: add FunctionPassContext.mangle(withChangedRepresentation original: Function) 2025-11-26 16:23:47 +01:00
Erik Eckstein ab2345a2ed FunctionPassContext: support setting arbitrary function type representations when creating specialized functions 2025-11-26 16:23:47 +01:00
Erik Eckstein 9a124742b0 Optimizer: add the DeadAccessScopeElimination optimization pass
It eliminates dead access scopes if they are not conflicting with other scopes.

Removes:
```
  %2 = begin_access [modify] [dynamic] %1
  ...                                       // no uses of %2
  end_access %2
```

However, dead _conflicting_ access scopes are not removed.
If a conflicting scope becomes dead because an optimization e.g. removed a load, it is still important to get an access violation at runtime.
Even a propagated value of a redundant load from a conflicting scope is undefined.

```
  %2 = begin_access [modify] [dynamic] %1
  store %x to %2
  %3 = begin_access [read] [dynamic] %1    // conflicting with %2!
  %y = load %3
  end_access %3
  end_access %2
  use(%y)
```
After redundant-load-elimination:
```
  %2 = begin_access [modify] [dynamic] %1
  store %x to %2
  %3 = begin_access [read] [dynamic] %1    // now dead, but still conflicting with %2
  end_access %3
  end_access %2
  use(%x)                                  // propagated from the store, but undefined here!
```
In this case the scope `%3` is not removed because it's important to get an access violation error at runtime before the undefined value `%x` is used.

This pass considers potential conflicting access scopes in called functions.
But it does not consider potential conflicting access in callers (because it can't!).
However, optimizations, like redundant-load-elimination, can only do such transformations if the outer access scope is within the function, e.g.

```
bb0(%0 : $*T):     // an inout from a conflicting scope in the caller
  store %x to %0
  %3 = begin_access [read] [dynamic] %1
  %y = load %3     // cannot be propagated because it cannot be proved that %1 is the same address as %0
  end_access %3
```

All those checks are only done for dynamic access scopes, because they matter for runtime exclusivity checking.
Dead static scopes are removed unconditionally.
2025-11-24 14:49:45 +01:00
Erik Eckstein 62786b01e2 Optimizer: add the mandatory destroy hoisting pass
It hoists `destroy_value` instructions for non-lexical values.

```
  %1 = some_ownedValue
  ...
  last_use(%1)
  ... // other instructions
  destroy_value %1
```
->
```
  %1 = some_ownedValue
  ...
  last_use(%1)
  destroy_value %1    // <- moved after the last use
  ... // other instructions
```

In contrast to non-mandatory optimization passes, this is the only pass which hoists destroys over deinit-barriers.
This ensures consistent behavior in -Onone and optimized builds.
2025-11-06 21:00:44 +01:00
Aidan Hall 8632e58825 Create Pack Specialisation pass 2025-10-30 14:28:16 +00:00
Aidan Hall c7af4c584e Bridging: APIs for PackSpecialization pass 2025-10-26 13:44:34 +00:00
Arnold Schwaighofer a59f6c4a3c Fix formatting 2025-10-21 07:54:55 -07:00
Arnold Schwaighofer 22c8ea6af6 Add experimental feature flag EmbeddedExistentials
This flag can be used to gradually add the functionility that will allow use of
protocol values in embedded mode.
2025-10-20 10:18:45 -07:00
Erik Eckstein 181b2f1f6d Optimizer: eliminate struct_extracts of an owned struct where the struct_extracts are inside a borrow scope
This is done by splitting the `begin_borrow` of the whole struct into individual borrows of the fields (for trivial fields no borrow is needed).
And then sinking the `struct` to it's consuming use(s).

```
  %3 = struct $S(%nonTrivialField, %trivialField)  // owned
  ...
  %4 = begin_borrow %3
  %5 = struct_extract %4, #S.nonTrivialField
  %6 = struct_extract %4, #S.trivialField
  use %5, %6
  end_borrow %4
  ...
  end_of_lifetime %3
```
->
```
  ...
  %5 = begin_borrow %nonTrivialField
  use %5, %trivialField
  end_borrow %5
  ...
  %3 = struct $S(%nonTrivialField, %trivialField)
  end_of_lifetime %3
```

This optimization is important for Array code where the Array buffer is constantly wrapped into structs and then extracted again to access the buffer.
2025-10-08 17:48:37 +02:00
Erik Eckstein df20d36255 ClosureSpecialization: support for OSSA and a big overhaul
Beside supporting OSSA, this change significantly simplifies the pass.
The main change is that instead of starting at a closure (e.g. `partial_apply`) and finding all call sites, we now start at a call site and look for closures for all arguments. This makes a lot of things much simpler, e.g. not so many intermediate data structures are required to track all the states.

I needed to remove the 3 unit tests because the things those tests were testing are not there anymore. However, the pass is tested with a lot of sil tests (and I added quite a few), which should give good test coverage.

The old ClosureSpecializer pass is still kept in place, because at that point in the pipeline we don't have OSSA, yet. Once we have that, we can replace the old pass withe the new one.
However, the autodiff closure specializer already runs in the OSSA pipeline and there the new changes take effect.
2025-10-06 12:02:48 +02:00
Erik Eckstein 89bba668e2 Mangling: add a closure specialization mangling for arguments which specialize for the same closure as a previous argument
For example:
```
  %1 = partial_apply %closure
  apply %f(%1, %1)    // first argument: `.closure(%1)`
                      // second argument: `.previousArgumentIndex(0)`
```
2025-10-06 09:47:41 +02:00
nate-chandler 5945606067 Merge pull request #84577 from nate-chandler/rdar161433604
[Optimizer] Use valid inst range to broadcast inlining changes.
2025-10-01 14:08:52 -07:00
Nate Chandler 07d75186ba [Optimizer] Don't walk to deleted.
During inlining, some instructions in the caller may be deleted.  So
when publishing this best effort list of instructions which were
"changed" during inlining, don't start from a deleted instruction.
Instead just don't publish, as already happens in other cases.

Without actually addressing
```
// TODO: get a list of cloned instructions from the `inlineFunction`
```
this is somewhat better than checking for having reached the end of the
function during the walk because that will result in falsely
broadcasting that some unchanged instructions have changed whereas this
change only results in not broadcasting which is already being done in
some cases (e.g. when a `begin_apply` is immediately followed by an
`end_apply`).
2025-09-29 20:52:18 -07:00
Nate Chandler b62798381e [Optimizer] Don't walk from deleted.
During inlining, the apply is deleted.  So when publishing this best
effort list of which instructions were "changed" during inlining, start
from the instruction before the deleted apply.
2025-09-29 20:52:18 -07:00
Nate Chandler 36205d90d2 [Gardening] Detypo'd "inlining". 2025-09-29 13:56:29 -07:00
Erik Eckstein 39b9969049 SIL: use Test for the SmallProjectionPath's unit tests
Now that we have `Test` available in the SIL module, we can use it for the SmallProjectionPath's unit tests and get rid of the RunUnitTests pass.
2025-09-24 11:46:34 +02:00