Skip to content

Preserve array initializer values across exception regions - #614

Merged
dadhi merged 1 commit into
dadhi:masterfrom
jogibear9988:fix/array-initializer-exceptions-standalone
Nov 10, 2026
Merged

dadhi merged 1 commit into
dadhi:masterfrom
jogibear9988:fix/array-initializer-exceptions-standalone

Conversation

@jogibear9988

@jogibear9988 jogibear9988 commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Array initializers keep the new array and element position on the stack while evaluating an element. A try/finally in that element can invalidate the stack. Mark affected initializers during collection and use existing local-variable emission to store the array and element values. Array bounds and initializers share one collection loop: initializers require an empty stack for every element containing an exception region, while bounds require spilling only when the exception region follows an earlier bound.

Value, reference, nullable and decimal elements; cleanup, exceptions, side effects and discarded results. Regressions use strict FEC compilation for both Standard and LightExpression.

Validation

Release solution build: zero warnings/errors.

Runtime TestX groups Legacy tests
.NET 10 467 passed 1,756 passed
.NET 8 467 passed 1,755 passed
.NET Framework 4.7.2 446 passed 1,639 passed

Measurements

Current master ce28e2b0 → this change, Release, Windows ARM64, .NET 8.0.28 / 10.0.9. Compilation uses prebuilt trees; runtime measures invocation through the same wrapper. Two fresh process rounds with reversed load order, seven samples per round, about 30 ms calibration, one logical CPU and tiered compilation disabled. Tables use the median of the two per-round medians.

The controls below produce correct results on master for their measured inputs. Newly supported trees have no valid before/after execution comparison because master rejects them, produces incorrect results or emits invalid IL. Timings include machine and JIT/code-placement variation; the unchanged add control helps show that variation. These observations do not prove universal performance neutrality.

net8.0 results (master → change)
API / workload Compile ns Invoke ns Compile B/op Invoke B/op
Standard / add_control 1159.2 → 1158.2 2.0 → 2.0 872.0 → 872.0 0.0 → 0.0
Standard / array_bounds 2270.1 → 2139.6 34.6 → 33.1 960.0 → 960.0 64.0 → 64.0
Standard / array_bounds_first_try 2129.3 → 2104.6 38.2 → 32.8 1560.0 → 1560.0 64.0 → 64.0
Standard / array_bounds_unrelated_try 2011.4 → 2015.7 31.7 → 33.7 1560.0 → 1560.0 64.0 → 64.0
Standard / array_initializer 1381.4 → 1495.4 3.9 → 4.3 1112.0 → 1112.0 32.0 → 32.0
Standard / array_initializer_unrelated_try 2128.5 → 2126.8 4.6 → 4.0 1704.0 → 1704.0 32.0 → 32.0
Light / add_control 1113.5 → 997.1 2.1 → 2.1 872.0 → 872.0 0.0 → 0.0
Light / array_bounds 1721.0 → 1619.1 33.0 → 34.8 960.0 → 960.0 64.0 → 64.0
Light / array_bounds_first_try 1816.7 → 1686.2 33.6 → 34.8 1560.0 → 1560.0 64.0 → 64.0
Light / array_bounds_unrelated_try 2233.5 → 1943.2 38.0 → 38.1 1560.0 → 1560.0 64.0 → 64.0
Light / array_initializer 1135.8 → 1207.9 4.1 → 4.1 1112.0 → 1112.0 32.0 → 32.0
Light / array_initializer_unrelated_try 1540.5 → 1583.7 4.7 → 4.2 1704.0 → 1704.0 32.0 → 32.0
net10.0 results (master → change)
API / workload Compile ns Invoke ns Compile B/op Invoke B/op
Standard / add_control 3595.6 → 3595.9 1.9 → 1.9 872.0 → 872.0 0.0 → 0.0
Standard / array_bounds 5300.0 → 5159.1 26.7 → 32.5 960.0 → 960.0 64.0 → 64.0
Standard / array_bounds_first_try 8154.1 → 8059.6 41.6 → 45.7 1560.0 → 1560.0 64.0 → 64.0
Standard / array_bounds_unrelated_try 12408.2 → 12282.3 47.4 → 38.9 1560.0 → 1560.0 64.0 → 64.0
Standard / array_initializer 9816.9 → 8129.3 5.6 → 6.6 1112.1 → 1112.1 32.0 → 32.0
Standard / array_initializer_unrelated_try 4535.5 → 4658.9 4.5 → 4.8 1704.0 → 1704.0 32.0 → 32.0
Light / add_control 2475.9 → 2461.5 2.0 → 2.0 872.0 → 872.0 0.0 → 0.0
Light / array_bounds 3305.5 → 3392.4 27.7 → 28.2 960.0 → 960.0 64.0 → 64.0
Light / array_bounds_first_try 3973.9 → 3988.5 33.3 → 28.8 1560.0 → 1560.0 64.0 → 64.0
Light / array_bounds_unrelated_try 10010.6 → 8638.6 40.2 → 34.4 1560.0 → 1560.0 64.0 → 64.0
Light / array_initializer 3809.5 → 3828.2 4.3 → 4.6 1112.0 → 1112.0 32.0 → 32.0
Light / array_initializer_unrelated_try 4052.8 → 3960.0 4.4 → 4.0 1704.0 → 1704.0 32.0 → 32.0

Emitted IL differs in these measured workloads: none.
No measured allocation increase above 0.1 B/op in these controls.

@dadhi

dadhi commented Nov 10, 2026

Copy link
Copy Markdown
Owner

@jogibear9988 It seems the 2 prs where overlapping and require resolution by hand

Array initializers keep the new array and element position on the stack while evaluating an element. A try/finally in that element can invalidate the stack. Mark affected initializers during collection and use existing local-variable emission to store the array and element values.
@jogibear9988
jogibear9988 force-pushed the fix/array-initializer-exceptions-standalone branch from 7dddfde to ed50aff Compare November 10, 2026 13:48
@jogibear9988

Copy link
Copy Markdown
Contributor Author

You're right: #613 and this PR handled different array forms, but both changed the same collection block. I should have called out that overlap.

Rebased onto master ce28e2b0 and pushed ed50affc. Bounds and initializers now share one collection loop instead of having separate copies. Initializers must preserve the array across an exception region in any element; bounds need to preserve preceding values only when an exception region occurs after the first bound. The source comment explains that distinction.

The full Release solution build and .NET 8, .NET 10 and Framework test runners pass, including both bounds and initializer regressions, for Standard and LightExpression. Modern runners pass 467 TestX groups; Framework passes 446, alongside the legacy suites.

I also refreshed compilation/runtime measurements against this master, for both APIs and six working controls. Allocations and emitted IL are unchanged in every measured control. Timings move in both directions, including unchanged controls, so I am not claiming a general speedup or proving universal performance neutrality. The description contains the current measurements and validation results.

@dadhi
dadhi merged commit c7f3c54 into dadhi:master Nov 10, 2026
4 checks passed
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.

2 participants