Commit Graph

10 Commits

Author SHA1 Message Date
John McCall 8cda95bca4 Don't delay stack deallocation of non-nested allocations
Credit to @eeckstein for noticing this possibility

I considered some much more complicated solutions to this problem, like
tracking all the deallocations we add and remove and then undoing them
for the allocations we end up marking non_nested, but ultimately I
figured out that this would work and was much less invasive, even if it
potentially yields slightly worse results in some cases.
2026-04-02 19:30:57 -04:00
John McCall a6be0957b8 StackNesting: don't mark pending allocations [non_nested]
Instead, just emit them immediately when we encounter a non-reorderable
deallocation. Credit for this idea goes to Michael Gottesman.

This is a nice optimization; it avoids marking a lot of allocations as
[non_nested] unnecessarily. But by not marking allocations that are
well-nested w.r.t the non-reorderable deallocation, it also means we never
need to mark allocations that were emitted well-ordered and never fell
out of order. That should actually achieve the intuition I had with the
earlier work that it was okay to not support marking certain stack
allocations that are never introduced directly by the SIL optimizer.
2026-04-02 19:30:57 -04:00
John McCall bf851fa4dd Model the stack allocation effects of {add,remove}TaskLocalValue.
The biggest complexity here is dealing with the improper nesting of the
allocation for the argument, and I'm not very proud of the solution I found,
but for a one-off builtin, I think it'll work.
2026-03-20 00:21:40 -04:00
John McCall d45af1c021 Allow alloc_ref and alloc_ref_dynamic to be marked [non_nested]
This hopefully unblocks fixing the stack nesting of the
concellation and priority escalation handler builtins.
2026-03-13 19:40:21 -04:00
John McCall 9aa0cb4e4f Make the cancellation and priority-ecscalation builtins respect stack nesting.
You can see in the patch that I obviously wrote this to include the
task-local builtins, but unfortunately they don't work for this because
they don't actually have a use/def relationship. That will need to be
follow-up work.

Includes a test that the defer special case applies to the priority
escalation builtin.
2026-03-13 19:40:20 -04:00
John McCall e1e7e8886c Fix a bug involving two async lets on the stack at once. 2026-03-06 03:14:46 -05:00
John McCall baaefaff98 Model the async let builtins as participating in the stack discipline.
This interacts with my previous commits to ensure that we mark other
allocation operations as non-nested when we would otherwise need to move
the finishAsyncLet operation.
2026-03-06 03:14:46 -05: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
Michael Gottesman 682ef268d2 [optimizer] Teach SIL optimizer that stack nesting should ignore nested stack allocations. 2025-11-21 11:21:15 -08:00
John McCall 8d231d20c6 Rewrite StackNesting to be a non-iterative single-pass algorithm.
The previous algorithm was doing an iterative forward data flow analysis
followed by a reverse data flow analysis. I suspect the history here is that
it was a reverse analysis, and that didn't really work for infinite loops,
and so complexity accumulated.

The new algorithm is quite straightforward and relies on the allocations
being properly jointly post-dominated, just not nested. We simply walk
forward through the blocks in consistent-with-dominance order, maintaining
the stack of active allocations and deferring deallocations that are
improperly nested until we deallocate the allocations above it. The only
real subtlety is that we have to delay walking into dead-end regions until
we've seen all of the edges into them, so that we can know whether we have
a coherent stack state in them. If the state is incoherent, we need to
remove any deallocations of previous allocations because we cannot talk
correctly about what's on top of the stack.

The reason I'm doing this, besides it just being a simpler and hopefully
faster algorithm, is that modeling some of the uses of the async stack
allocator properly requires builtins that cannot just be semantically
reordered. That should be somewhat easier to handle with the new approach,
although really (1) we should not have runtime functions that need this and
(2) we're going to need a conservatively-correct solution that's different
from this anyway because hoisting allocations is *also* limited in its own
way.

I've attached a rather pedantic proof of the correctness of the algorithm.

The thing that concerns me most about the rewritten pass is that it isn't
actually validating joint post-dominance on input, so if you give it bad
input, it might be a little mystifying to debug the verifier failures.
2025-11-03 11:51:17 -08:00