Lazily allocate object writer section state - #132923
Open
awakecoding wants to merge 1 commit into
Open
awakecoding wants to merge 1 commit into
awakecoding wants to merge 1 commit into
Conversation
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib |
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
Pull request overview
This PR reduces fixed per-section allocation overhead in the shared object-writer layer used by NativeAOT tooling by deferring relocation-list and section-data backing allocations until they’re actually needed, while preserving emitted object layout and relocation semantics.
Changes:
- Lazily allocate symbolic relocation lists in
ObjectWriter(usenullto represent “no relocations”, allocateList<SymbolicRelocation>on first add). - Make
SectionDataallocate append buffers / overflow fragment storage only when needed, and share immutable padding buffers for common padding bytes. - Lazily allocate COFF relocation lists by converting
SectionDefinitionto a mutable class with aRelocationsproperty and adding focused regression tests.
File summaries
| File | Description |
|---|---|
| src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs | Lazily allocates append buffer and fragment storage; shares common padding buffers; keeps stream semantics by flushing before read/seek. |
| src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs | Defers per-section symbolic relocation list allocation; skips undefined-symbol scan and relocation emission for null lists. |
| src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs | Defers COFF relocation list allocation and updates emission to handle null reloc lists safely. |
| src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj | Adds InternalsVisibleTo for the test project to access internals needed for object-writer tests. |
| src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ObjectWriterTests.cs | Adds targeted tests covering SectionData buffering/padding behavior and COFF relocation/ordering/determinism invariants. |
| src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj | Includes the new ObjectWriterTests.cs in the test project build. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
awakecoding
force-pushed
the
copilot/nativeaot-lazy-section-state
branch
from
September 1, 2026 19:21
7dc0ad7 to
75525c6
Compare
Defer symbolic and COFF relocation lists until first use. Store the first section data buffer inline and share common padding buffers to avoid per-section collection allocations.
awakecoding
force-pushed
the
copilot/nativeaot-lazy-section-state
branch
from
September 12, 2026 02:16
75525c6 to
7365913
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed body## Summary- Allocate symbolic and COFF relocation lists only when a section emits its first relocation.- Store the first section-data fragment directly, allocate overflow storage only for subsequent fragments, and create append buffers only when needed.- Share immutable zero and x86/x64 NOP padding buffers instead of allocating one padding array per section.These changes form one cohesive optimization on current main: all three allocations are per-section state created by the shared object-writer layer, and all can be deferred for the common section shape without changing object layout.## Motivation
ObjectWritercurrently creates empty symbolic and format-specific relocation lists for every section.SectionDataalso eagerly creates anArrayBufferWriter<byte>, a buffer list, and a padding array even when a section has no buffered writes and only one data fragment.That fixed cost is material for NativeAOT workloads with very large section counts, especially when most sections have no relocations and only one fragment.## ImplementationObjectWriternow usesnullto represent a section with no symbolic relocations and creates the existingList<SymbolicRelocation>on the first add. Undefined-symbol discovery and format-specific relocation conversion skip this zero-relocation state. Non-empty lists remain ordinary mutable lists, preserving insertion order and platform-specific behavior such as Mach-O's existing in-place reversal.The COFF writer similarly createsList<CoffRelocation>only when symbolic relocations are converted.SectionDefinitionbecomes a class so this lazily initialized property can be updated without replacing the section record. COFF relocation counts, overflow records, ordering, and emitted bytes are unchanged.SectionDatanow:- creates itsArrayBufferWriter<byte>on first buffered write;- stores its firstReadOnlyMemory<byte>directly;- moves to the existing list representation on the second fragment;- shares immutable 16-byte zero and NOP padding buffers;- retains existing no-copyReadOnlyMemory<byte>ownership and live stream behavior.Object emission remains single-threaded; this does not change its thread-safety contract. No collection capacity planning, compact relocation representation, experiment gate, or profiling infrastructure is included.## Validation- Clean current-main baseline before the first rebase atc210d82dbc1ab432b9369604a1caef9a0ab763d2- The initial long-path.\build.cmd clr+libs+hostattempt stopped before native compilation because the inherited Windows environment exceededcmd.exe's command-line limit (The input line is too long). - The same unchanged commit built successfully from a shortN:mapping with a sanitized PATH: 0 warnings, 0 errors.-.\build.cmd clr.aot+libs -rc Release -lc Release- Final uninstrumented toolchain build succeeded: 0 warnings, 0 errors.-.\build.cmd clr.aot+libs -rc Checked -lc Release- Succeeded: 0 warnings, 0 errors.-.\src\tests\build.cmd nativeaot Release tree nativeaot- Succeeded with the suite's 9 expected trim/AOT-analysis warnings and 0 errors.-.\src\tests\run.cmd runnativeaottests Release- 28 passed, 0 failed or skipped. - The NativeAOT determinism test produced matching 11,021,593-byte outputs.- Current-main rebase at0fd08c887c1317055025965cbe829b732fbe942d- From a shortU:mapping with a sanitized PATH,.\dotnet.cmd build src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj -c Release -p:Platform=x64succeeded: 0 warnings, 0 errors. -.\dotnet.cmd build src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj -c Release -p:Platform=x64succeeded: 0 warnings, 0 errors. - Current main intentionally deletedILCompiler.Compiler.Tests; this rebase drops the test-only friend assembly and the former focused unit tests in accordance with that upstream test-architecture change.- Targeteddotnet format --verify-no-changeschecks andgit diff --checkpassed.- Two independent model-family reviews and a post-rebase source review checked every object-writer relocation override and the stream/storage lifetime rules.## Current-main benchmarkThe authoritative current-main benchmark uses the repository's net11 toolchain rather than forcing the retained net10 RDM response through an incompatible compiler/framework contract.An ignored local runner generated 10,000 worker/marker type pairs, compiled them with current-main ILC in multifile mode, and produced 220,076 COFF sections. Baseline and changed compilers were built from the same current-main commit with identical temporary measurement probes. One warmup per variant preceded five measured interleaved A/B pairs.| Metric (median of 5) | Baseline | Changed | Change || --- | ---: | ---: | ---: || Wall time | 4.554 s | 4.350 s | -4.48% || CPU time | 12.750 s | 12.516 s | -1.84% || Object emission | 1.726 s | 1.580 s | -8.44% || Node materialization | 0.739 s | 0.700 s | -5.30% || Object-phase allocation | 833.61 MiB | 795.89 MiB | -37.72 MiB (-4.52%) || Total managed allocation | 1,344.63 MiB | 1,306.27 MiB | -38.36 MiB (-2.85%) || Peak private memory | 776.33 MiB | 719.42 MiB | -56.91 MiB (-7.33%) || Peak working set | 700.89 MiB | 637.96 MiB | -62.93 MiB (-8.98%) |Object-phase allocation fell in every pair by 37.65-37.83 MiB. Timing and peak process metrics remain sensitive to shared-machine scheduling and GC timing: one object-emission pair regressed 1.6%, while the other four improved. The repeated allocation reduction is the primary current-main signal; timing is directional.Every measured baseline and changed run emitted the same 49,921,241-byte object with SHA-2562BDC559EBE496FC9A7237FA938EFB3278429BA0D23D97B4B2146A5E970BA3A32.## Retained .NET 10 RDM evidenceThe retained production profile used the matching v10.0.11 compiler/framework contract, not current main. That workload emitted a 3,744,336,196-byte COFF BigObj with 3,526,007 sections, 18,258,125 symbols, and tens of millions of relocation records.For the acceptedlazy-relocation-lists;compact-section-datamechanism:- repeated full-RDM runs reduced managed allocation by 1.13-1.21 GiB;- the confirming pair reduced object emission by about 5.6%, while the first pair was near-neutral;- a smaller EntryModel screen attributed 15.9 MB of allocation and about 4.4% object-phase improvement to lazy relocation lists, followed by another 4.9 MB allocation reduction from compact section data with timing near noise;- generated objects were byte-identical.Those net10 results establish the large-scale motivation. The net11 stress workload above is the authoritative validation that the mechanism still applies to current main.## Limitations and risk- Local end-to-end measurements are Windows x64. The symbolic relocation and section-data changes are shared by COFF, ELF, Mach-O, PE, Wasm, and ReadyToRun; all overrides were reviewed and the ReadyToRun consumer was built, but non-Windows end-to-end coverage is left to CI.- Allocation savings scale with section count, so ordinary applications should see a smaller absolute effect than the stress and RDM workloads.- No whole-compiler wall-time guarantee is claimed because short-run controls and the retained production runs both showed substantial environmental variance.- This is an internal representation change with no public API or intended output change.> [!NOTE]> This PR description was drafted with GitHub Copilot.