Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Separate GC WKS and SVR compilation units by janvorli · Pull Request #126720 · dotnet/runtime · GitHub
Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Separate GC WKS and SVR compilation units by janvorli · Pull Request #126720 · dotnet/runtime · GitHub
Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Separate GC WKS and SVR compilation units by janvorli · Pull Request #126720 · dotnet/runtime · GitHub
Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Separate GC WKS and SVR compilation units by janvorli · Pull Request #126720 · dotnet/runtime · GitHub
Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Separate GC WKS and SVR compilation units by janvorli · Pull Request #126720 · dotnet/runtime · GitHub
Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Separate GC WKS and SVR compilation units by janvorli · Pull Request #126720 · dotnet/runtime · GitHub
Skip to content

Separate GC WKS and SVR compilation units - #126720

Merged
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation
Jun 18, 2026
Merged

Separate GC WKS and SVR compilation units#126720
janvorli merged 10 commits into
dotnet:mainfrom
janvorli:gc-separate-compilation

Conversation

@janvorli

Copy link
Copy Markdown
Member

Move the GC sources away from the wrapper-file model that text-included gc.cpp and gcee.cpp under SERVER_GC and instead compile the shared sources directly as separate WKS and SVR objects.

This change introduces gcinternal.h as the shared compilation context for the gc.cpp split, converts the former tail-included GC implementation fragments into separately compiled translation units, and updates the GC, VM, NativeAOT, and GC sample build surfaces to consume the new object layout.

It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the relevant CMake files, and renames the shared GC header from gc_common.h to gcinternal.h to avoid confusion with gccommon.cpp.

During the split, cross-translation-unit declarations and inline helpers needed by multiple GC source files were moved into the shared header, while local-only inline helpers were moved back into their owning .cpp files to avoid keeping unnecessary bodies in the shared header.

I've made size comparison between the new clrgc.dll, clrgcexp.dll and coreclr.dll and the changes in the gc dlls were very minor, around ~1.5kB growth due to little different decisions of the linker / compiler w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.

@janvorlijanvorli added this to the 11.0.0 milestone Apr 9, 2026
@janvorlijanvorli self-assigned this Apr 9, 2026
CopilotAI review requested due to automatic review settings April 9, 2026 16:53
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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

Pull request overview

This PR refactors CoreCLR GC build plumbing to stop using wrapper translation units (gcwks.cpp/gcsvr.cpp and gceewks.cpp/gceesvr.cpp) and instead compile the shared GC implementation sources directly into separate WKS/SVR object sets, using a new shared compilation-context header gcinternal.h.

Changes:

  • Introduces gcinternal.h and updates many GC .cpp files to include it and wrap code in WKS/SVR namespaces based on SERVER_GC.
  • Updates CoreCLR VM and standalone GC CMake build graphs to build GC sources as object libraries for WKS/SVR and consume them from coreclr/clrgc targets.
  • Updates NativeAOT runtime and the GC sample project build surfaces to consume the new GC source layout.

Reviewed changes

Copilot reviewed 31 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/coreclr/vm/wks/CMakeLists.txtAdds GC object files into cee_wks_core build inputs.
src/coreclr/vm/CMakeLists.txtDefines shared GC source list and builds vm_gc_wks/vm_gc_svr object libraries.
src/coreclr/dlls/mscoree/coreclr/CMakeLists.txtLinks vm_gc_wks/vm_gc_svr into coreclr and coreclr_static.
src/coreclr/nativeaot/Runtime/CMakeLists.txtReplaces wrapper sources with direct GC .cpp compilation for NativeAOT.
src/coreclr/gc/gcinternal.hNew shared GC compilation-context header; centralizes includes and inlines.
src/coreclr/gc/*.cppSwitches individual GC implementation files to include gcinternal.h and wrap in WKS/SVR namespaces.
src/coreclr/gc/CMakeLists.txtBuilds standalone GC (clrgc/clrgcexp) with WKS/SVR object libraries.
src/coreclr/gc/sample/*Updates GC sample to compile split GC .cpp files directly.

Comment threadsrc/coreclr/gc/gcinternal.h
Comment threadsrc/coreclr/vm/wks/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated
@mangod9

Copy link
Copy Markdown
Member

what is the motivation for this change? Does it improve build times?

@janvorli

Copy link
Copy Markdown
MemberAuthor

what is the motivation for this change? Does it improve build times?

It doesn't affect build time in any way. The main reason is code editing experience. The individual files into which the gc.cpp was split in my recent change didn't include the headers for symbols they use, so when editing one of those e.g. in VS code, it was showing a lot of red squiggles and code navigation didn't work well.

@jkotas

Copy link
Copy Markdown
Member

Build breaks....

CopilotAI review requested due to automatic review settings April 10, 2026 12:31

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/CMakeLists.txt Outdated

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/plan_phase.cpp
@github-actionsgithub-actionsBot mentioned this pull request Apr 10, 2026

@VSadovVSadov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@MichalStrehovsky

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Size statistics

Pull request #126720

ProjectSize beforeSize afterDifference
TodosApi-linux 24545416 24488072 -57344
TodosApi-windows 25923584 25909760 -13824
avalonia.app-linux 18912136 18887560 -24576
avalonia.app-windows 19439616 19433472 -6144
hello-linux 1246088 1221512 -24576
hello-minimal-linux 1098528 1073952 -24576
hello-minimal-windows 773632 766464 -7168
hello-windows 933376 927232 -6144
kestrel-minimal-linux 5354976 5293536 -61440
kestrel-minimal-windows 4854784 4840960 -13824
reflection-linux 1832784 1808208 -24576
reflection-windows 1696256 1689600 -6656
webapiaot-linux 9721456 9660016 -61440
webapiaot-windows 10246144 10232320 -13824
winrt-component-minimal-windows 721408 714240 -7168

@jkotas

Copy link
Copy Markdown
Member

2% size savings on Hello World on Linux, nice!

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

This type of refactoring tends to depend on good PGO data and whole program optimizations for good perf:

  • On Windows, we do not use LGCG and PGO for NAOT to avoid compiler version fragility
  • On Linux, the infrastructure for collecting PGO data is broken for libcoreclr.so. It is one of the check boxes in Startup time of small workloads #120407 and there was a chat on Teams about that. We may want to frontload fixing it to avoid regressing GC perf on Linux.

@janvorli

janvorli commented Apr 13, 2026

Copy link
Copy Markdown
MemberAuthor

Do we understand why this makes the code smaller? It is likely making it both smaller and slower (less inlining) ... not something we necessarily want for the GC. It may be a good idea to measure the impact on GC throughput, on both Windows and Linux.

I definitely want to run the GC perf runs and understand where the size improvement comes from before we merge this.
There were two large functions that were inlined before and that I stopped marking as inlined because I felt like they were too large to make sense inlining them and wanted to verify a perf impact of that. Those were gc_heap::mark_through_cards_helper and gc_heap::set_region_gen_num. Edit: And gc_heap::get_promoted_bytes

@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas performance and functionality tests didn't show any regressions.

@jkotas

Copy link
Copy Markdown
Member

Have you measured it on a binary that showed the large code size reduction?

(Also, there is a merge conflict that needs to be resolved.)

@janvorli

Copy link
Copy Markdown
MemberAuthor

So on my machine, the diff when really running the NativeAOT version was 0,9%.

@jkotas

Copy link
Copy Markdown
Member

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

Alternatively, use the low-tech equivalent (e.g. https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html) that merges all .cpp files together during the build so that the C++ compiler can still see all code together.

Otherwise, we will likely end up with thousands paper cut perf regression over time.

@janvorli

Copy link
Copy Markdown
MemberAuthor

It would be best to figure out how to enable lto for the code in static libraries that we ship by applying the lto when we produce the static library.

I don't think that's possible. Static libraries are just bags of object files. Since they are not combined in any way, it seems LTO would not be able to do anything. It seems that the unity build would be the only option for this case.

@am11

am11 commented Jun 12, 2026

Copy link
Copy Markdown
Member

I don't think that's possible. Static libraries are just bags of object files.

Technically, it is possible. By using -flto -ffat-lto-objects during compilation, we can generate a static library archive that bundles LTO bytecode alongside standard object code (in .a file). I tested this on the main branch; as expected, it increased the size of each static library and the resulting NuGet package. However, the final linked binary size remained the same (a "Hello World" sample actually increased by a few bytes). The real dealbreaker is the toolchain dependency: shipping bytecode requires consumers to use the exact same compiler version to link successfully. For all practical purposes, that makes it a no-go for our distribution.

The CMake Unity Build approach sounds interesting.

@jkotas

jkotas commented Jun 13, 2026

Copy link
Copy Markdown
Member

Since they are not combined in any way, it seems LTO would not be able to do anything.

You can combine the object files in a custom way to produce the library. We have started doing a few days ago to deal with libunwind symbol conflicts. The same technique may be used for LTO too #128927 (review) . This technique is compiler-toolchain specific. I do not think there is a publicly documented way to do this with MSVC. Unity build is more portable.

CopilotAI review requested due to automatic review settings June 15, 2026 23:28
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas I've enabled unity build for the GC stuff and also changed the two functions I've mentioned above back to inline. That got rid of all the difference in the test you've shared.

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/gc/mark_phase.cpp Outdated
CopilotAI review requested due to automatic review settings June 16, 2026 12:20

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 16, 2026 17:27

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Comment threadsrc/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@janvorli

Copy link
Copy Markdown
MemberAuthor

@jkotas, @VSadov can you please review this again?

@janvorli

Copy link
Copy Markdown
MemberAuthor

/ba-g timeouts on win-64 arm are occurring on many PRs and are unrelated to this change.

@janvorli
janvorli merged commit 06caa69 into dotnet:mainJun 18, 2026
106 of 112 checks passed
@janvorli
janvorli deleted the gc-separate-compilation branch June 18, 2026 00:07
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Move the GC sources away from the wrapper-file model that text-included
gc.cpp and gcee.cpp under `SERVER_GC` and instead compile the shared
sources directly as separate WKS and SVR objects.
This change introduces gcinternal.h as the shared compilation context
for the gc.cpp split, converts the former tail-included GC
implementation fragments into separately compiled translation units, and
updates the GC, VM, NativeAOT, and GC sample build surfaces to consume
the new object layout.
It also removes the gcsvr.cpp/gcwks.cpp and gceesvr.cpp/gceewks.cpp
wrappers, compiles gcee.cpp through the same dual-build WKS/SVR source
lists as gc.cpp, deduplicates the repeated WKS/SVR source lists in the
relevant CMake files, and renames the shared GC header from gc_common.h
to gcinternal.h to avoid confusion with gccommon.cpp.
During the split, cross-translation-unit declarations and inline helpers
needed by multiple GC source files were moved into the shared header,
while local-only inline helpers were moved back into their owning .cpp
files to avoid keeping unnecessary bodies in the shared header.
I've made size comparison between the new clrgc.dll, clrgcexp.dll and
coreclr.dll and the changes in the gc dlls were very minor, around
~1.5kB growth due to little different decisions of the linker / compiler
w.r.t. cold / hot code. The coreclr even became ~1.5kB smaller.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@janvorli@mangod9@jkotas@MichalStrehovsky@VSadov@am11