Skip to content

Avoid unnecessary arithmetic spills after completed exception regions - #661

Open
jogibear9988 wants to merge 1 commit into
dadhi:masterfrom
jogibear9988:fix/arithmetic-after-exception-spills-20261113
Open

jogibear9988 wants to merge 1 commit into
dadhi:masterfrom
jogibear9988:fix/arithmetic-after-exception-spills-20261113

Conversation

@jogibear9988

Copy link
Copy Markdown
Contributor

The global try-expression count forces extra left-value spills even when the right operand can only load an atomic value and cannot enter a protected region.

Keep that value on the stack for parameter/constant/default RHS operands and retain spills for complex or opaque intrinsic operands.

Right operands observing mutations made by a completed finalizer, parameter/constant/default variants, and an intrinsic LightExpression constant that emits an exception region.

Validation: Release solution build; full Standard and LightExpression runners on .NET 8, .NET 10 and .NET Framework. net8.0: 562 TestX cases and 1744 legacy cases; net10.0: 562 TestX cases and 1745 legacy cases; net472: 543 TestX cases and 1610 legacy cases.

Shared-workload measurements

Base: bac81d0734c0 (master); patch: 019767854711. Windows ARM64, SDK 10.0.301; .NET 8.0.28 and 10.0.9. Expression trees and invocation adapters are prepared outside the timed loops. Strict FEC compilation, tiered compilation disabled, one pinned logical processor, two opposite-load-order processes per runtime, seven alternating paired samples per process, 30 ms calibration target; medians of 14 samples.

Both columns below are FEC. “System trees” names the expression API, not the System compiler. Compilation includes delegate creation; invocation measures the returned delegate through the same adapter on each side. Managed allocations use GC.GetAllocatedBytesForCurrentThread.

Runtime / FEC API Workload Compile μs, master → patch Compile B/op, master → patch Invoke ns, master → patch Invoke B/op, master → patch
net10.0 / LightExpression arithmetic_after_try 6.580 → 6.454 1472.07 3.294 → 3.298 0.00
net10.0 / LightExpression ref_compound 4.802 → 5.418 880.06 3.354 → 4.005 0.00
net10.0 / LightExpression scalar_add 5.487 → 5.283 872.06 3.252 → 3.208 0.00
net10.0 / LightExpression try_finally 5.605 → 6.586 1472.00 3.691 → 3.244 0.00
net10.0 / System trees arithmetic_after_try 7.756 → 6.772 1472.08 3.256 → 3.194 0.00
net10.0 / System trees ref_compound 6.257 → 6.695 880.29 3.401 → 3.488 0.00
net10.0 / System trees scalar_add 5.036 → 4.945 872.07 3.003 → 2.927 0.00
net10.0 / System trees try_finally 5.248 → 5.824 1472.00 3.220 → 2.928 0.00
net8.0 / LightExpression arithmetic_after_try 2.048 → 1.883 1472.02 2.671 → 2.592 0.00
net8.0 / LightExpression ref_compound 1.757 → 1.796 880.02 2.322 → 2.355 0.00
net8.0 / LightExpression scalar_add 1.529 → 1.537 872.02 2.307 → 2.303 0.00
net8.0 / LightExpression try_finally 1.708 → 1.625 1472.00 2.397 → 2.377 0.00
net8.0 / System trees arithmetic_after_try 2.819 → 2.577 1472.03 2.505 → 2.507 0.00
net8.0 / System trees ref_compound 2.282 → 2.286 880.12 2.366 → 2.395 0.00
net8.0 / System trees scalar_add 2.106 → 2.186 872.04 2.455 → 2.375 0.00
net8.0 / System trees try_finally 2.310 → 2.209 1472.00 2.371 → 2.303 0.00

Emitted IL matched byte-for-byte for 12/16 shared control delegates. Observed compilation changes range from -12.7% to +17.5%; invocation changes from -12.1% to +19.4%. Slower results are retained. Even identical-IL controls vary, so these short machine timings do not establish a blanket runtime speedup or absence of regressions.
Managed allocations were unchanged on every shared workload in this run.

Longer confirmation run

Same base, patch and paired protocol, with a 150 ms calibration target instead of 30 ms. These results are additional measurements of the same code; they do not replace slower observations above.

Runtime / FEC API Workload Compile μs, master → patch Compile B/op Invoke ns, master → patch Invoke B/op
net10.0 / LightExpression arithmetic_after_try 5.392 → 4.768 1472.01 2.837 → 2.878 0.00
net10.0 / LightExpression ref_compound 4.433 → 4.260 880.01 3.338 → 3.481 0.00
net10.0 / LightExpression scalar_add 3.926 → 3.860 872.01 3.214 → 3.170 0.00
net10.0 / LightExpression try_finally 4.855 → 4.818 1472.00 2.837 → 2.872 0.00
net10.0 / System trees arithmetic_after_try 6.351 → 6.050 1472.01 2.794 → 2.797 0.00
net10.0 / System trees ref_compound 5.520 → 5.494 880.06 2.907 → 2.922 0.00
net10.0 / System trees scalar_add 4.672 → 4.721 872.01 2.817 → 2.864 0.00
net10.0 / System trees try_finally 5.581 → 5.715 1472.00 3.285 → 3.098 0.00
net8.0 / LightExpression arithmetic_after_try 5.781 → 5.960 1472.01 3.221 → 3.150 0.00
net8.0 / LightExpression ref_compound 3.943 → 3.938 880.01 2.826 → 3.021 0.00
net8.0 / LightExpression scalar_add 4.115 → 4.527 872.01 3.613 → 3.675 0.00
net8.0 / LightExpression try_finally 5.298 → 5.748 1472.00 3.275 → 3.309 0.00
net8.0 / System trees arithmetic_after_try 9.214 → 8.968 1472.02 3.650 → 3.234 0.00
net8.0 / System trees ref_compound 6.078 → 5.555 880.08 3.041 → 2.979 0.00
net8.0 / System trees scalar_add 4.549 → 4.462 872.02 2.974 → 3.138 0.00
net8.0 / System trees try_finally 5.339 → 5.409 1472.00 3.270 → 3.107 0.00

The global try-expression count forces extra left-value spills even when the right operand can only load an atomic value and cannot enter a protected region.

Keep that value on the stack for parameter/constant/default RHS operands and retain spills for complex or opaque intrinsic operands.

Validation: Release solution build and full Standard/LightExpression runners on .NET 8, .NET 10 and .NET Framework.
Right operands observing mutations made by a completed finalizer, parameter/constant/default variants, and an intrinsic LightExpression constant that emits an exception region.
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.

1 participant