Commit Graph

511 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
Anthony Latsis 755e1787f6 SILOptimizer: Adjust to "[Support][NFCI] Store DomTree children as linked list (#176409)"
See https://github.com/llvm/llvm-project/pull/176409.
2026-06-04 08:52:56 +01:00
Erik Eckstein c588393f00 Optimizer: add hasOnlyUsers(ofType:) for Operand sequences 2026-06-03 07:06:35 +02:00
elsa be6322ed43 [CSE] Add missing call to completeLifetimes when needed (#89570)
Resolves rdar://177962122.

The new CSE pass rewrite failed to call completeLifetimes when needed,
which resulted in assertion crashes.
2026-06-01 12:11:33 -07:00
Andrew Trick a133bab977 Fix LifetimeDependenceDiagnostics - disable noescape function args
In LifetimeDependenceDiagnostics, disable diagnostics of function arguments that
have a noescape function type. Diagosing noescape function-type arguments is
meant to be handled by a separate pass, DiagnoseInvalidEscapingCaptures. In some
distant future, we could consider merging these diagnostics. For now, they
should be independent.

This fixes a bug introduced with:

commit e3d55f64c2
Date:   Wed Apr 29 10:37:32 2026 +0100

    Lifetimes: Treat noescape function types as ~Escapable

After that change, lifetime diagnostics inadvertantly kicked on for noescape
function-type arg. The diagnostic would then fail when importing ObjC API with
an optional noescape function such as:

+ (instancetype)takeNoEscapeBlock:(void(NS_NOESCAPE ^)(void))block;
// error: lifetime-dependent variable 'block' escapes its scope)

Here the compiler generates a thunk to unwrap the optional and generates a
mark_dependence on the Optional's inner value.

Fixes rdar://177381648 - error: lifetime-dependent variable escapes its scope
2026-05-28 16:21:43 -07:00
Meghana Gupta adf9b5c94b Fix LICM producing illegal load [trivial] on non-trivial address types
The LICM pass's hoistAndSinkLoadAndStore incorrectly determined load/store
ownership based on firstStore.storeOwnership rather than the address type.

This PR fixes it by determining ownership based on the address type's triviality:
2026-05-22 16:55:46 -07:00
Erik Eckstein 096ede496f LoopInvariantCodeMotion: fix an ownership error caused by hoisting trivial sub-projection loads
If `load [trivial]` only loads a "part" of a stored value, which itself is non-trivial, the pass crashes with an ownership error.
The fix is to wrap the projection instructions (e.g. `struct_extract`) inside a borrow scope. This is correctly done in `createProjectionAndCopy`.

https://github.com/swiftlang/swift/issues/89255
rdar://177430359
2026-05-21 08:18:02 +02:00
Erik Eckstein 83739a0462 LoopInvariantCodeMotion: fix a crash when trying to hoist load [take]
So far we supported hoisting a `load [take]` which "takes" just a part of a stored value (i.e. a projected value).
This works as long as no other struct/tuple field is also loaded in the loop.
If this is the case it causes an ownership error.
The fix is to disallow hoisting projected `load [take]` instructions.
2026-05-21 07:54:09 +02:00
Aidan Hall 070f7923d9 Bridging: Rename FunctionConvention.results to resultsWithError
This matches the name of the C++ method it bridges, and avoids the ambiguity of
the previous name.
2026-05-18 14:47:05 +01:00
Ben Cohen ad8a8f7cc8 SILOptimizer: add FunctionConvention.formalResults; use it in PackSpecialization
The C++ `SILFunctionType` exposes both `getResults()` (formal results only)
and `getResultsWithError()` (formal + error). The Swift mirror previously
only had `results`, bridging to the with-error variant. Add `formalResults`
for the formal-only view, matching the C++ split.

Switch PackSpecialization's three result-iteration sites to `formalResults`.
The bridged `createSpecializedFunctionDeclaration` preserves the error
result on its own, so iterating with-error included it twice in the new
function's signature.

Also forward the original apply's `nothrow`/`noasync` flags to the
specialized apply, required for SIL verification of a plain apply calling
a function with an error result.
2026-05-18 14:45:26 +01:00
elsa 83d4291709 CSE Optimizer Pass rewrite (#88248)
Resolves rdar://173862129
2026-05-15 19:11:10 +01:00
Andrew Trick 2dca2e48c4 Merge pull request #88980 from atrick/fix-immortal-init
LifetimeDependenceDiagnostics: calls with no deps are immortal
2026-05-09 14:22:17 -07:00
Andrew Trick 4845b0bf4d LifetimeDependenceDiagnostics: calls with no deps are immortal
Fix lifetime diagnostics to consider an implicit initializer of a ~Escapable
type to be implicitly immortal. Required to handle Optional<~Escapable> stored
properties, such as:

struct Foo<Element: ~Escapable>: ~Escapable {
  var element: Element?

  @_lifetime(borrow c)
  init<C>(c: borrowing C) {
    // error: Lifetime-dependent variable 'self' escapes its scope
  }
}

The fix is simply to remove a temporary safeguard that I put in place to
compensate for our incomplete closure lifetimes. We now have the complete
representation of lifetimes on closures, so don't need the safeguard.

Representationally, a function that returns a ~Escapable value but has no
dependendencies is immortal. This was always the intended design, but the
temporary safeguard treated these cases as implicitly bound to some local scope.

Removing this safeguard has the effect of:

- Variable intialization is immortal (it cannot depend on anything by
  definition). The safety of the initializer is checked inside the implementation
  of those expressions rather than the caller.

- Empty ~Escapable types have an implicit immortal initializer (why not?)

- Calls to a function with @_unsafeNonescapableResult but no @_lifetime
  annotation produce an immortal value. This is reasonable, and we want to
  deprecate this attribute as soon as possible anyway. It is not for general use.

This is currently blocking usage of BorrowingSequence, such as a hypothetical BorrowingSequenceMapSequenceIterator.

Fixes rdar://176561897 ([nonescapable] initialization of Optional fields reports
a lifetime escape)
2026-05-08 16:47:56 -07:00
Daniil Kovalev a9a74aa8fe [AutoDiff] Find partial_apply of pullback w/o loop in closure spec pass (#88911)
Currently, AutoDiff Closure Specialization pass iterates over all VJP
instructions and checks each of them against a set of conditions which
`partial_apply` of pullback must satisfy.

This logic could be re-implemented w/o loop, checking conditions in
opposite direction, starting from `return` instruction and transitively
going to defining instructions of operands (`tuple` and `partial_apply`
for the desired pullback case).
2026-05-07 22:52:48 +00:00
Erik Eckstein ba0b04559e ComputeEscapeEffects: add a complexity limit for escape analysis of function arguments
Similar to what we do in AliasAnalysis

related to rdar://175999887
2026-05-07 09:52:14 +02:00
Gábor Horváth cfeda37650 Merge pull request #88622 from Xazax-hun/tsan-effects-modeled
[SILOptimizer] Correctly compute the effects of TSan instrumentation
2026-05-06 12:20:31 +01:00
Erik Eckstein d28549af34 Optimizer: workaround for fast type casting to the FullApplySite and ReturnInstruction protocols
Rather than doing a standard swift runtime cast to an existential, explicitly check for the conforming instruction classes, which is much faster.
The new `isFullApplySite` and `isReturnInstruction` casting utilities are used in the (very few) time critical places in the optimizer.

After toolchain builders are upgraded to a compiler version which includes the fix for this problem (https://github.com/swiftlang/swift/pull/88270), we don't need this workaround anymore and the regular `as`/`is` casts can be used again.

Now the runtime casts doesn't show up prominently in compile-time profiling data anymore - even with a host compiler which doesn't implement fast type checks, yet.

rdar://173916206
2026-04-30 17:53:39 +02:00
Gabor Horvath 12b6809813 [SILOptimizer] Correctly compute the effects of TSan instrumentation
Passing a C++ object to the TSanInOutAccess builtin resulted in an extra
temporary copy. This copy was not optimized out because the semantics of this
builtin was not understood by the optimizer. Teaching the utils that this
intrinsic does not actually modify the object, does not escape it,
and does not read it lets the optimizer eliminate this copy.

Strictly speaking, the test code that uses interop is not safe/correct,
this is why it had a lifetime issue.

rdar://173921363
2026-04-30 11:21:17 +01:00
Joe Groff 097b0d3400 SIL: Split unchecked_*_enum_data_addr according to ownership and effects.
We cannot use spare bits or other overlapping storage layout tricks with fundamentally
address-only enums, and we can take advantage of this to do borrowing switches or other
in-place projections without copying the value. However, for resilient enums, the
implementation may use spare bit packing, but the type must be handled address-only
outside of its defining module, and we didn't have a way to express that with
borrowing switch. Optimization passes have also been running into problems with the
complexity that we were using `unchecked_take_enum_data_addr` sometimes as a pure
operation. This patch splits the instruction into three:

- `unchecked_inplace_enum_data_addr` represents a nondestructive in-place enum
  projection. It is only allowed for enums whose projection operation is
  nondestructive.
- `unchecked_take_enum_data_addr` represents a destructive enum projection,
  invalidating the enum and leaving the payload to be further consumed.
  This matches the current instruction's semantics.
- `unchecked_borrow_enum_data_addr` represents a borrowing enum projection.
  The instruction takes a second operand for "scratch" space, which the
  enum representation may be copied into in order to avoid invalidating the
  enum value, so the result is dependent on the lifetime of both the
  original enum and the scratch buffer. This allows for borrowing switches
  over resilient enums.

`unchecked_borrow_enum_data_addr` is implemented by taking advantage of the
"address-only enums can't do spare bit optimization" property at runtime.
We inspect the operand type's bitwise-borrowability from its metadata. If
the type is bitwise-borrowable, then we are allowed to bitwise-copy the
enum to the scratch space and apply the projection to the scratch space,
preserving the original value. If the type is not bitwise-borrowable, then
we cannot use spare bit optimization in its layout, so we apply the
projection in-place.

Fixes rdar://174952822.
2026-04-27 15:40:37 -07:00
Meghana Gupta c431f654c8 Don't optimize alloc_stack with dynamic_lifetime in TempLValueElimination
The [dynamic_lifetime] attribute represents that the stack location's initialization state is tracked dynamically via a boolean flag — it may be uninitialized at certain program points. TempLValueElimination pass can replace an alloc_stack [dynamic_lifetime] with a destination alloc_stack that does not have dynamic_lifetime. This results in invalid SIL which triggers verifier errors due to lifetime mismatches in some program paths in ossa.

This PR fixes this issue by bailing out of TempLValueElimination for alloc_stack [dynamic_lifetime] in ossa.

Resolves rdar://175097584
2026-04-21 12:22:18 -07:00
Erik Eckstein b18ba61e89 Optimizer: natively implement Instruction.isDeinitBarrier in swift
No need to bridge to the C++ implementation, because the swift implementation is very simple
2026-04-08 21:34:43 +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
Erik Eckstein f7ff9c72c5 ObjectOutliner: don't outline objects in external functions because this would "duplicate" singletons
Fixes a miscompile
rdar://172766879
2026-03-29 13:01:45 +02:00
Peter Rong 2cc9a2f376 [SILOptimizer] Test ConstantCapturePropagation ODR violation with generic closures (#87916)
## Summary
This PR fixes the ODR violation detailed in
https://github.com/swiftlang/swift/issues/87917

## Impl

When specializeClosure determines the specialized closure remains
generic (`isGeneric == true`), clone the body without applying type
substitutions, and use `partial_apply` with the substitution map at the
call site instead of `thin_to_thick_function`. The body stays generic
with type parameters like `$*Optional<Self>`, which is correct since all
callers share the same shared symbol and apply their own concrete types
through the substitution map on the `partial_apply`.

## Tests
Add SIL and executable tests demonstrating that
ConstantCapturePropagation produces an ODR violation when specializing
generic closures with different concrete type substitutions but the same
constant captures.

The SIL test provides a minimal reproducer: two callers instantiate the
same generic closure with UInt8 and Int respectively, both capturing
constant `radix=10`. With `-enable-sil-verify-all`, the verifier catches
the type mismatch in the specialized function body.

The executable test exercises the real-world scenario through
`FixedWidthInteger.init?(_:radix:)`, where the miscompile causes Int and
`Int32` parsing to return garbage values because the closure body was
baked with `UInt8` type metadata.

Fixes: https://github.com/swiftlang/swift/issues/87917

[Assisted-by](https://t.ly/Dkjjk): [Claude Opus
4.6](https://www.anthropic.com/news/claude-opus-4-6)

cc @drodriguez @kyulee-com
2026-03-23 13:40:38 -07:00
eeckstein ef3d132575 Merge pull request #87977 from eeckstein/fix-constant-capture-propagation
ConstantCapturePropagation: fix two problems with function type representation
2026-03-20 15:30:18 +01:00
Erik Eckstein 6a02551361 ConstantCapturePropagation: fix two problems with function type representation
* convert "method"s to "thin" functions: We are removing arguments from the original function. If the removed argument is the "self" argument, the specialized function cannot be a "method" anymore.

* don't create `thin_to_thick_function` instructions for non-thin functions. Instead keep it a `partial_apply`

Fixes a SIL verifier crash.
rdar://172774069
2026-03-20 09:39:58 +01:00
Erik Eckstein 8110746b2e TempRValueElimination: delete debug_value instructions which are outside of the stack location's liverange
We already move `debug_value` instructions into the liverange in such cases.
But we didn't do this if the `debug_value`'s operand is a projection of the `alloc_stack`.
The fix is to delete such `debug_value` instructions. It's not ideal, however this is a very rare case.
Also, we need to delete dead projection instructions.
Dead projection instructions can appear outside of the liverange in case they were only used by an (now deleted) `debug_value` or `destroy_addr` instruction.

Fixes a SIL ownership verification error
https://github.com/swiftlang/swift/issues/87980
rdar://172950559
2026-03-20 09:36:42 +01:00
Erik Eckstein 4954cffb2d ConstantCapturePropagation: drop the support of non-OSSA
ConstantCapturePropagation now runs purely in the OSSA pipeline. So no need to support non-OSSA anymore.
2026-03-16 16:12:29 +01:00
Erik Eckstein dd4f3073ca DestroyHoisting: add a complexity limit for very large functions
For very large functions this optimization can run into noticeable quadratic behavior.
Therefore, ignore functions with more than 100000 SIL instructions.
This limit is large enough to not affect most of real-world SIL functions.
2026-03-16 16:12:28 +01:00
Erik Eckstein 11d3025200 TempRValueElimination: bail in non-OSSA if the stack liverange has exit edges
If there is no use on a path leaving the liverange, we don't know how the value is destroyed there.

Fixes a miscompile
rdar://171676249
2026-03-12 16:51:13 +01:00
eeckstein 2047aff5ce Merge pull request #87807 from eeckstein/fix-temp-lvalue-elimination
TempLValueElimination: don't insert an early destroy in non-OSSA
2026-03-12 13:54:09 +01:00
Meghana Gupta 86792e46bc Merge pull request #87787 from meg-gupta/fixmergecf
Fix MergeCondFails pass to handle different failure messages correctly
2026-03-12 03:21:19 -07:00
Erik Eckstein 920029185c TempLValueElimination: dont' insert an early destroy in non-OSSA
In non-OSSA we cannot insert an early `destroy_addr`, because a loaded value could be retained later, e.g.
```
  %1 = load %destination
  ...                     // we cannot insert a `destroy_addr %destination` here!
  stores to %temp
  strong_retain %1
```

fixes a miscompile
rdar://172223667
2026-03-12 07:24:18 +01:00
Meghana Gupta b61e14a5fe Fix MergeCondFails pass to handle different failure messages correctly
On encountering a cond_fail with differing message, merge the accumulated cond_fail instructions and begin new merge set.

Partially resolves rdar://164947648
2026-03-11 11:50:20 -07:00
Erik Eckstein b449b8c7a1 SIL: fix PartialApplyInst.isNested
* All SIL modifications must go through a `MutatingContext`. Therefore replace the simple setter for `isNested` with `set(isNested:, context)`
* It's better to add a `isNested` parameter for `Builder.createPartialApply` than to set it after each construction of a `partial_apply`, which can easily be missed.
2026-03-09 18:04:37 +01:00
John McCall 374e3d37f0 Allow partial_apply [on_stack] to be flagged [non_nested]. 2026-03-06 03:15:28 -05:00
Erik Eckstein 3774c3b6fd MandatoryDestroyHoisting: ignore type-dependent operands when computing the liverange of a value
Type-dependent operands can appear outside the liverange of a value and therefore must be ignored.
This bug caused MandatoryDestroyHoisting to insert wrong destroys.

Fixes a SIL verification error and/or a mis-compile
rdar://170510052
2026-02-27 12:05:57 +01:00
Erik Eckstein c17ecdffc7 ConstantCapturePropagation: insert borrow scopes for specialized guaranteed arguments
This is needed if the guaranteed argument is replaced by a copied owned value. If the argument has any use which is not compatible with "owned" ownership, we need a borrow scope.
2026-02-26 08:13:58 +01:00
Erik Eckstein 5e2851d36e ClosureSpecialization: fix two problems
* insert borrow scopes for specialized guaranteed arguments: This is needed if the guaranteed argument is replaced by a copied owned value. If the argument has any use which is not compatible with "owned" ownership, we need a borrow scope.

* prevent creating a specialized function with more than one "isolated" parameters
2026-02-20 18:28:14 +01:00
Erik Eckstein c8711d132a TempRValueElimination: ignore fix_lifetime instruction in alias analysis
`fix_lifetime` has memory-write effects defined.
However, in TempRValueElimination we don't shrink lifetimes. Therefore we can safely ignore this instruction.

rdar://168840965
2026-02-11 19:10:36 +01:00
Erik Eckstein ddb0141141 TempRValueElimination: support alloc_stacks with multi-block lifetime
So far the optimization just handled the case where all uses of the alloc_stack are in the same basic block.
Now we can handle arbitrary liveranges of the alloc_stack.
Also, remove the destroy-hoisting part of the algorithm because this is already handle by the dedicated DestroyAddrHoisting pass
2026-02-11 06:43:46 +01:00
Erik Eckstein 6dde4c942e MandatoryPerformanceOptimizations: force de-virtualizing value-type deinits
Do this even if the function then contains references to other functions with wrong linkage.
MandatoryPerformanceOptimization fixes the linkage afterwards.
This is similar to what we already do with de-virtualizing class and witness methods: https://github.com/swiftlang/swift/pull/76032

rdar://168171608
2026-02-02 19:36:48 +01:00
Adrian Prantl 8b7d18ae00 Merge pull request #86902 from adrian-prantl/168622625
[TempRValueElimination] Make AllocStackInst.hasUses analysis global
2026-02-01 18:55:34 -08:00
Adrian Prantl d7ca6483a0 [TempRValueElimination] Make AllocStackInst.hasUses analysis global
Wihtout this change an alloc_stack instruction that is defined in a different
basic block than its use could result in instructions that are not dominated by
its operands. In an asserts build this is caught by the SIL verifier, but in a
non-asserts build it can crash the compiler.

Thanks to Erik Eckstein for the actual implementation of the fix!

rdar://168622625
2026-01-30 14:01:19 -08:00
Erik Eckstein 9d0b6c8417 RedundantLoadElimination: fix a complexity problem when replacing a huge amount of loads with a common value
As RedundantLoadElimination processing the loads in reverse control flow order, the replaced loads might accumulate quite a lot of users.
This happens if the are many loads from the same location in a row.
To void quadratic complexity in `uses.replaceAll`, we swap both load instructions and move the uses from the `existingLoad` (which usually has a small number of uses) to this load - and delete the `existingLoad`.

This came up in the Sema/large_int_array.swift.gyb test, which tests a 64k large Int array.
With this fix, the compile time gets down from 3 minutes to 5 seconds.
I also changed the test:
* run the compiler with the timeout script to detect build time regressions
* re-enabled the SIL verifier because the problem there is already fixed (https://github.com/swiftlang/swift/pull/86781)
* run the compiler with -O to also test the whole optimizer pipeline and not only the mandatory pipeline
* assigned the array to a real variable (instead of `_`) to not let the optimizer remove the whole array too early
* run the compiler with and without `-parse-as-library` because this makes a huge difference how the global array is being generated
2026-01-28 17:54:37 +01:00
eeckstein ee398d1b18 Merge pull request #86644 from eeckstein/lifetime-completion
Optimizer: enable complete OSSA lifetimes throughout the pass pipeline
2026-01-23 06:14:10 +01:00
Erik Eckstein 7af1e1ffd1 TempRValueElimination: fix a problem with load-store copy elimination
If a value is "copied" to the stack location via a `load` and `store` instruction pair and the source location is written or de-allocated between both instructions,
the optimization generated wrong SIL.
The fix is to make sure that writes to the source locations are always checked between the `load` and `store`.

rdar://168595700
2026-01-22 18:03:02 +01:00
Erik Eckstein 5ea44a4a70 CopyToBorrowOptimization: remove special handling of dead-end blocks
Which is now possible with complete OSSA lifetimes
2026-01-22 17:41:48 +01: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 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