Skip to content

Preserve readonly struct fields when calling mutating members - #592

Merged
dadhi merged 1 commit into
dadhi:masterfrom
jogibear9988:fix/readonly-struct-receivers
Nov 8, 2026
Merged

dadhi merged 1 commit into
dadhi:masterfrom
jogibear9988:fix/readonly-struct-receivers

Conversation

@jogibear9988

Copy link
Copy Markdown
Contributor

Calling a mutating struct method on a readonly instance field currently changes the original field. Load its value into the existing temporary-address path before calling the member, matching System.Linq.Expressions. Writable fields and explicit assignment/ref paths retain their address handling.

The regression fixture checks repeated calls, writable controls, static readonly fields, nested readonly receivers, and receiver evaluation exactly once, using strict CompileFast and System comparison.

Allocation tradeoff: compiling the measured readonly struct receiver costs 8 additional bytes per compilation (944 to 952 B/op) in both Standard and LightExpression, on .NET 8 and 10. The defensive copy requires a local. This is an accepted correctness cost; it is not allocation-neutral. Runtime allocation remains zero. Other measured working paths retain their allocation counts and emitted IL.

Validation

Baseline: fetched dadhi/master at e2c78de038244228f978f93ed89f40f30daec680. The regression fixture fails on master and passes with this change, for both input APIs.

Release solution build: zero warnings/errors. Full runner results:

Runtime TestX passed Legacy passed
.NET 10 308 1756
.NET 8 308 1755
.NET Framework 4.7.2 289 1639

Performance measurements

Windows ARM64, .NET 8.0.28 and 10.0.9, Release, tiering disabled. Prebuilt trees; Light conversion occurs outside timing. Separate assembly contexts, one CPU, nine alternating samples per phase, two fresh process rounds with reversed load order, roughly 100 ms pair calibration, and GC allocation counters. Tables show the median of the 18 samples for compilation and delegate invocation; ns/op and allocated B/op are master → patch. All measured expressions already produce correct results on master.

Timing limitation: this machine shows substantial variation even comparing master with itself (individual process medians ranged from -43.5% to +16.8% for compilation and -46.9% to +55.5% for invocation). These results do not establish a speedup or prove CPU-time neutrality. Slower observations are included below. Allocation and IL comparisons are more stable.

All measured working cases, including controls
Runtime / API / workload Compile ns Compile B Invoke ns Invoke B Same IL
net10.0 / Standard / add_control 5078.7 → 4953.5 872 → 872 2.9 → 2.9 0 → 0 yes
net10.0 / Standard / nested_field_read 3488.9 → 3570.9 952 → 952 2.1 → 1.9 0 → 0 yes
net10.0 / Standard / readonly_read 5324.5 → 5744.2 944 → 952 2.0 → 1.9 0 → 0 no
net10.0 / Standard / writable_read 5125.8 → 4139.7 944 → 944 1.9 → 2.0 0 → 0 yes
net10.0 / Light / add_control 2505.5 → 2508.9 872 → 872 2.1 → 2.0 0 → 0 yes
net10.0 / Light / nested_field_read 3295.9 → 3753.0 952 → 952 2.1 → 1.9 0 → 0 yes
net10.0 / Light / readonly_read 2626.4 → 2768.1 944 → 952 2.0 → 2.2 0 → 0 no
net10.0 / Light / writable_read 3507.3 → 3554.5 944 → 944 2.6 → 2.1 0 → 0 yes
net8.0 / Standard / add_control 1145.8 → 1120.4 872 → 872 1.9 → 1.9 0 → 0 yes
net8.0 / Standard / nested_field_read 1096.7 → 1108.3 952 → 952 1.8 → 1.9 0 → 0 yes
net8.0 / Standard / readonly_read 1234.0 → 1257.5 944 → 952 1.8 → 1.8 0 → 0 no
net8.0 / Standard / writable_read 1278.9 → 1227.8 944 → 944 1.9 → 1.9 0 → 0 yes
net8.0 / Light / add_control 966.1 → 1056.9 872 → 872 2.0 → 1.9 0 → 0 yes
net8.0 / Light / nested_field_read 1227.9 → 1184.6 952 → 952 2.1 → 2.1 0 → 0 yes
net8.0 / Light / readonly_read 1192.2 → 1236.8 944 → 952 2.0 → 2.0 0 → 0 no
net8.0 / Light / writable_read 1388.5 → 1411.1 944 → 944 2.3 → 2.2 0 → 0 yes

@dadhi
dadhi merged commit bc7980f into dadhi:master Nov 8, 2026
4 checks passed
@dadhi

dadhi commented Nov 8, 2026

Copy link
Copy Markdown
Owner

@jogibear9988 thank you

@jogibear9988
jogibear9988 deleted the fix/readonly-struct-receivers branch November 8, 2026 21:40
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