Skip to content

Lazily allocate object writer section state - #132923

Open
awakecoding wants to merge 1 commit into
dotnet:mainfrom
awakecoding:copilot/nativeaot-lazy-section-state
Open

Lazily allocate object writer section state#132923
awakecoding wants to merge 1 commit into
dotnet:mainfrom
awakecoding:copilot/nativeaot-lazy-section-state

Conversation

@awakecoding

Copy link
Copy Markdown

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

ObjectWriter currently creates empty symbolic and format-specific relocation lists for every section. SectionData also eagerly creates an ArrayBufferWriter<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.

Implementation

ObjectWriter now uses null to represent a section with no symbolic relocations and creates the existing List<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 creates List<CoffRelocation> only when symbolic relocations are converted. SectionDefinition becomes 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.

SectionData now:

  • creates its ArrayBufferWriter<byte> on first buffered write;
  • stores its first ReadOnlyMemory<byte> directly;
  • moves to the existing list representation on the second fragment;
  • shares immutable 16-byte zero and NOP padding buffers;
  • retains existing no-copy ReadOnlyMemory<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 at c210d82dbc1ab432b9369604a1caef9a0ab763d2
    • The initial long-path .\build.cmd clr+libs+host attempt stopped before native compilation because the inherited Windows environment exceeded cmd.exe's command-line limit (The input line is too long).
    • The same unchanged commit built successfully from a short N: 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.
  • .\dotnet.cmd build src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj -c Release -p:Platform=x64 --no-restore
    • Succeeded: 0 warnings, 0 errors.
  • .\dotnet.cmd test src\coreclr\tools\aot\ILCompiler.Compiler.Tests\ILCompiler.Compiler.Tests.csproj -c <Release|Debug|Checked> -p:Platform=x64
    • 29 passed in each configuration, 0 failed or skipped.
    • Seven new cases cover empty section data, a non-empty inline-to-overflow transition, retained buffer ownership and mutation, multiple appends, small and large padding for zero/custom/NOP bytes, data and code alignment, empty/one/multiple relocation sections, relocation and section ordering, COFF addend mutation, lazy COFF list allocation, and deterministic object bytes.
  • .\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.
  • Targeted dotnet format --verify-no-changes checks and git diff --check passed.
  • Two independent model-family reviews checked every object-writer relocation override and the stream/storage lifetime rules. Their two test findings were addressed by marking the test factory complete for Checked/Debug and retaining a non-empty inline buffer through the overflow transition.

Current-main benchmark

The 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-256 2BDC559EBE496FC9A7237FA938EFB3278429BA0D23D97B4B2146A5E970BA3A32.

Retained .NET 10 RDM evidence

The 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 accepted lazy-relocation-lists;compact-section-data mechanism:

  • 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.

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.
Copilot AI lite review requested due to automatic review settings August 29, 2026 17:15
@azure-pipelines

Copy link
Copy Markdown
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.

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 29, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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 (use null to represent “no relocations”, allocate List<SymbolicRelocation> on first add).
  • Make SectionData allocate append buffers / overflow fragment storage only when needed, and share immutable padding buffers for common padding bytes.
  • Lazily allocate COFF relocation lists by converting SectionDefinition to a mutable class with a Relocations property 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-NativeAOT-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants