Repository navigation
Preserve readonly instance fields passed to ref parameters - #594
Conversation
|
is it duplicate of #592? |
4e75f46 to
4ea2f50
Compare
|
@dadhi They cover complementary uses of the same copy path:
I verified this on current master ( I have rebased this PR and extended the existing copy path by removing the The full .NET 8, .NET 10 and .NET Framework runners pass. Fresh compilation/invocation measurements against current master are in the description: allocations are unchanged for all measured working cases, including readonly receiver controls. |
Passing a readonly instance field to a ref parameter currently exposes its storage, allowing the callee to change the field. For example, incrementing a readonly int through ref changes its stored value from 3 to 4, while System.Linq.Expressions passes a temporary and preserves 3.
Extend the existing readonly-field defensive-copy path to ref arguments by removing its
byRefIndex == -1restriction. Reuse the same local-address helper for both receivers and ref arguments. Writable field addresses and explicit assignment handling retain their existing branches.The strict Standard/LightExpression regression compares the return value and retained owner storage against System.Linq.Expressions for readonly int and string fields passed to mutating methods.
Validation
Baseline: current master at
76f2b28f9363d3364be75dc9830c7e303aea8b3a. The new fixture fails on that baseline for both input APIs and passes with this change. Release solution build: zero warnings/errors.Performance
Fresh comparison of the baseline above with this change: Windows ARM64, .NET 8.0.28 and 10.0.9, Release, tiering disabled. Prebuilt trees; Light conversion occurs outside timing. Compilation and delegate invocation are measured separately, using isolated load contexts, one CPU, nine alternating samples per phase, and two process rounds in opposite load order with approximately 100 ms pair calibration. Timings combine the 18 samples; allocations use thread allocation counters.
All measured expressions already work on the baseline. The changed path uses a ref-reading method on a readonly int field, avoiding an incorrect mutation as a performance benchmark. The readonly struct receiver, writable receiver and arithmetic cases are controls.
Measured compilation allocations are unchanged in every case; invocation allocates zero. The readonly ref argument changes IL to use a temporary. The readonly receiver and other controls retain their emitted IL. Timing varies between rounds; slower observations are included, and these measurements do not prove CPU-time neutrality.
All measured cases: current master → change
add_controlreadonly_readwritable_readreadonly_refadd_controlreadonly_readwritable_readreadonly_refadd_controlreadonly_readwritable_readreadonly_refadd_controlreadonly_readwritable_readreadonly_ref