Skip to content

Preserve binary array receivers across exception regions - #616

Merged
dadhi merged 2 commits into
dadhi:masterfrom
jogibear9988:fix/binary-index-exceptions-standalone
Nov 10, 2026
Merged

dadhi merged 2 commits into
dadhi:masterfrom
jogibear9988:fix/binary-index-exceptions-standalone

Conversation

@jogibear9988

@jogibear9988 jogibear9988 commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Binary ArrayIndex emission leaves the array reference on the stack while evaluating its index. Save and reload the receiver when an exception region can occur during emission, so a try/finally index retains the array and evaluation order. Share the aggressively inlined EmitLoadLeftAndRight helper with arithmetic emission: restore a saved left operand beneath a right operand, saving the right first only when it is still on the stack. Existing local types, allocation order and operand order are preserved.

Vector and non-vector arrays with lower bounds -2, 0 and 1; receiver/index/finally side effects. Arithmetic regressions also check subtraction results and left/right/finally effects for int, long, decimal and their nullable forms, including null left operands, against System compilation. 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 1186.4 → 1187.4 2.0 → 2.0 872.0 → 872.0 0.0 → 0.0
Standard / binary_index_unrelated_try 1683.7 → 1925.1 2.0 → 2.0 1472.0 → 1472.0 0.0 → 0.0
Standard / subtract_try 1474.2 → 1574.5 1.9 → 1.9 1472.0 → 1472.0 0.0 → 0.0
Standard / nullable_subtract_try 3397.7 → 3413.5 2.2 → 2.2 2656.0 → 2656.0 0.0 → 0.0
Standard / nullable_subtract_try_null 4512.0 → 3996.7 2.2 → 2.2 2656.0 → 2656.0 0.0 → 0.0
Standard / nullable_add 3204.1 → 3270.5 2.5 → 2.5 1928.1 → 1928.1 0.0 → 0.0
Standard / nullable_add_null 3194.9 → 3130.9 2.4 → 2.4 1928.1 → 1928.1 0.0 → 0.0
Standard / vector_array_read 1244.8 → 1295.6 1.8 → 1.8 872.0 → 872.0 0.0 → 0.0
Light / add_control 1053.7 → 1103.3 1.9 → 2.0 872.0 → 872.0 0.0 → 0.0
Light / binary_index_unrelated_try 1401.6 → 1533.0 2.0 → 2.1 1472.0 → 1472.0 0.0 → 0.0
Light / subtract_try 1396.2 → 1561.1 2.0 → 2.0 1472.0 → 1472.0 0.0 → 0.0
Light / nullable_subtract_try 3129.2 → 2850.4 2.2 → 2.2 2656.0 → 2656.0 0.0 → 0.0
Light / nullable_subtract_try_null 2979.7 → 3038.2 2.3 → 2.3 2656.0 → 2656.0 0.0 → 0.0
Light / nullable_add 3135.4 → 3256.1 2.7 → 2.8 1928.0 → 1928.0 0.0 → 0.0
Light / nullable_add_null 2817.3 → 2881.6 2.5 → 2.4 1928.0 → 1928.0 0.0 → 0.0
Light / vector_array_read 1109.6 → 990.8 1.8 → 1.8 872.0 → 872.0 0.0 → 0.0
net10.0 results (master → change)
API / workload Compile ns Invoke ns Compile B/op Invoke B/op
Standard / add_control 4181.2 → 4208.7 2.3 → 2.3 872.1 → 872.1 0.0 → 0.0
Standard / binary_index_unrelated_try 4252.3 → 4872.7 2.3 → 2.3 1472.1 → 1472.1 0.0 → 0.0
Standard / subtract_try 9554.6 → 8988.9 2.3 → 2.3 1472.1 → 1472.1 0.0 → 0.0
Standard / nullable_subtract_try 9779.6 → 9953.6 2.2 → 2.4 2656.1 → 2656.1 0.0 → 0.0
Standard / nullable_subtract_try_null 10685.2 → 10190.9 2.0 → 2.0 2656.1 → 2656.1 0.0 → 0.0
Standard / nullable_add 9611.3 → 9574.6 2.3 → 2.3 1928.0 → 1928.0 0.0 → 0.0
Standard / nullable_add_null 9480.3 → 9161.8 2.3 → 2.2 1928.0 → 1928.0 0.0 → 0.0
Standard / vector_array_read 3151.1 → 3396.0 2.0 → 1.8 872.0 → 872.0 0.0 → 0.0
Light / add_control 3629.0 → 3082.5 2.0 → 2.0 872.0 → 872.0 0.0 → 0.0
Light / binary_index_unrelated_try 3494.4 → 3616.5 2.0 → 2.0 1472.0 → 1472.0 0.0 → 0.0
Light / subtract_try 3591.1 → 3922.6 2.0 → 2.0 1472.0 → 1472.0 0.0 → 0.0
Light / nullable_subtract_try 8789.2 → 8837.9 2.2 → 2.1 2656.1 → 2656.1 0.0 → 0.0
Light / nullable_subtract_try_null 9406.8 → 9029.0 2.0 → 2.0 2656.1 → 2656.1 0.0 → 0.0
Light / nullable_add 8932.2 → 9130.1 2.3 → 2.4 1928.0 → 1928.0 0.0 → 0.0
Light / nullable_add_null 11487.0 → 9502.8 2.3 → 2.3 1928.0 → 1928.0 0.0 → 0.0
Light / vector_array_read 3115.6 → 3208.4 2.1 → 1.9 872.0 → 872.0 0.0 → 0.0

Array-index spilling remains conservative: a try elsewhere in the lambda can also activate the save/reload path. This accounts for the IL difference in binary_index_unrelated_try.

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

Binary ArrayIndex emission leaves the array reference on the stack while evaluating its index. Save and reload the receiver when an exception region can occur during emission, so a try/finally index retains the array and evaluation order.
Restore a saved left operand below either a stack-held or cached right operand through one aggressively inlined helper. Preserve existing local types, local order and emitted instruction order.

Check noncommutative arithmetic and evaluation/finally effects for integer, long, decimal and nullable operands against System compilation.
@jogibear9988
jogibear9988 force-pushed the fix/binary-index-exceptions-standalone branch from 343180b to d1bb492 Compare November 10, 2026 14:11
@dadhi
dadhi merged commit b5afdb2 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