Repository navigation
feat(codegen): generate AOT-safe C# private field accessors - #11372
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
F# record and union serializers still use dynamic FieldAccessor delegates despite the documented AOT-safe behavior.
Review effort: Balanced
Findings: 1
What changed in this PR
Adds AOT-safe generated access to inaccessible serialized fields using ref-returning UnsafeAccessor methods.
Changes:
- Generates runtime-appropriate field accessors for serializers and copiers.
- Adds JIT, snapshot, and NativeAOT coverage.
- Documents target-framework and hot-reload behavior.
| File | Description |
|---|---|
.github/workflows/analyzer-audit.yml |
Adds Windows/Linux NativeAOT validation. |
src/Orleans.CodeGenerator/CopierGenerator.cs |
Shares unsafe-accessor declaration generation. |
src/Orleans.CodeGenerator/LibraryTypes.cs |
Detects runtime accessor support. |
src/Orleans.CodeGenerator/SerializerGenerator.cs |
Generates and uses ref-returning field accessors. |
src/Orleans.Serialization/README.md |
Documents generated field-access behavior. |
test/Orleans.CodeGenerator.Tests/FieldAccessCodegenTests.cs |
Tests generated accessor strategies. |
test/Orleans.CodeGenerator.Tests/FieldAccessRuntimeTests.cs |
Tests runtime serialization and copying. |
test/Orleans.CodeGenerator.Tests/HotReloadCodegenTests.cs |
Verifies hot-reload fallback behavior. |
test/Orleans.CodeGenerator.Tests/Orleans.CodeGenerator.Tests.csproj |
Links shared field-access fixtures. |
test/Orleans.CodeGenerator.Tests/OrleansSourceGeneratorTests.cs |
Selects framework-specific snapshots. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestBasicClassWithAnnotatedFields.verified.cs |
Updates private-field output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassReferenceProperties.verified.cs |
Updates backing-field output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithInterfaceConstructorParameter.verified.cs |
Updates interface-field output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestClassWithOptionalConstructorParameters.verified.cs |
Updates constructor-field output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.FieldAccessor.verified.cs |
Captures generic fallback output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestGenericClassWithConstructorParameters.UnsafeAccessor.verified.cs |
Captures generic unsafe-accessor output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecords.verified.cs |
Updates record backing-field output. |
test/Orleans.CodeGenerator.Tests/snapshots/OrleansSourceGeneratorTests.TestRecordsWithParameterIdAttributes.verified.cs |
Updates attributed-record output. |
test/Orleans.NativeAotSmoke/Fields.cs |
Adds the field-access smoke entry point. |
test/Orleans.NativeAotSmoke/FieldsChecks.cs |
Provides shared round-trip assertions. |
test/Orleans.NativeAotSmoke/FieldsNativeChecks.cs |
Exercises generated components under NativeAOT. |
test/Orleans.NativeAotSmoke/FieldsPayloads.cs |
Defines private and generic payload fixtures. |
test/Orleans.NativeAotSmoke/NativeAotEnvironment.cs |
Ensures dynamic code is unavailable. |
test/Orleans.NativeAotSmoke/Orleans.NativeAotSmoke.csproj |
Configures reusable NativeAOT scenarios. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Code coverage
Report-only conclusion: improved. The current-main baseline is commit Coverage combines every CI test matrix job, including providers, CodeGen, .NET 8/10, Linux, Windows, and macOS, using canonical physical source and branch identities. The comparison remains report-only while normal line and branch variance is calibrated. Coverage details |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The runtime-gated implementation, documented fallbacks, snapshots, cross-target runtime tests, and NativeAOT workflow provide comprehensive coverage.
Review effort: Balanced
Findings: None
Resolved since last review (1)
9cb3d51 to
7985c70
Compare

Generated serializers and copiers for C# payloads currently initialize inaccessible-field delegates through
FieldAccessorandReflection.Emit.DynamicMethod. Private fields, readonly fields, and compiler-generated auto-property backing fields therefore require runtime code generation, even when the generator knows the exact field at compile time.Generate ref-returning
UnsafeAccessorKind.Fieldmethods for C# fields on supported target runtimes. Each generated codec or copier emits oneaccessField_<id>method per field and uses its returned reference for both reads and writes, reducing method and attribute metadata compared with separate getter/setter accessors. Non-generic C# payloads use this path on .NET 8+, and generic C# payloads use it on .NET 9+, preserving the declaring type's generic parameter positions and constraints. Volatile fields use this path on .NET 10+; earlier targets restore them through existing delegates so their libraries remain compatible with runtimes affected by dotnet/runtime#109665. Struct receivers are passed by reference, and deserializers/copiers assign through the returned field reference to restore readonly, get-only, init-only, and private-setter state. Serializer and copier emission share accessor declaration and deduplication helpers.Select runtime capabilities from the SDK's
TargetFrameworkIdentifierandTargetFrameworkVersioncompiler-visible properties, and separately requireUnsafeAccessorAttributein the compilation references. Parsed capability flags participate in incremental option equality and hashing: changing targets across an accessor-support boundary updates generated output, while targets with equivalent capabilities reuse cached output. Compilation-only library type caching remains independent of the target options. Synthetic compilation tests supply target metadata explicitly, including platform-specific TFMs, supported version boundaries, and missing or invalid metadata.For C# payloads, legacy targets, .NET 8 generic payloads, pre-.NET 10 volatile fields, and hot-reload field access retain the existing separate getter/setter delegate behavior. Normal accessible member access and wire IDs remain unchanged. Focused generator/runtime tests and reviewed snapshots cover both strategies, including actual .NET 8 and .NET 10 execution, exact accessor declaration counts, and shared read/write calls. Volatile-field runtime cases also reproduce and verify compatibility with unpatched CoreCLR 9.0.0. F# record and union field restoration retains its existing generated-delegate strategy on JIT-enabled runtimes.
The reusable
Orleans.NativeAotSmokeharness contains aFieldsscenario with project-localPublishAot. Its default component path statically constructs generated codecs/copiers for C# payloads and verifies native private/backing-field round trips, readonly and volatile fields, constrained and nested generics, generic structs, and array deep-copy isolation.Fields.smoke.jsonregisters this case for the centralized Windows/Linux native-smoke matrix runner introduced by #11367. Its diagnostic policy keeps the existing broader serializer backlog visible while rejecting generated-code,FieldAccessor, andUnsafeAccessordiagnostics. This PR contributes the case and manifest and uses the shared runner and central CI pipeline already on main.Ordinary
AddSerializer/Serializer<T>/DeepCopierbehavior is covered under JIT. Full NativeAOT serializer startup remains gated by the independently tracked manifest metadata failure in #11368; the optional--full-pipelinescenario preserves that reproduction. No runtime activation changes or public API changes are included.Related to #8254 and the parent NativeAOT series.
Microsoft Reviewers: Open in CodeFlow