Skip to content

Inline GC handle write barrier - #132245

Open
jkotas with Copilot wants to merge 13 commits into
mainfrom
copilot/improve-gchandle-target-performance
Open

Inline GC handle write barrier#132245
jkotas with Copilot wants to merge 13 commits into
mainfrom
copilot/improve-gchandle-target-performance

Conversation

Copilot AI commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Validation

  • Built the CoreCLR GC native target successfully.
  • Built libraries successfully.
  • Full CoreCLR Release build reached the changed GC objects successfully but failed later on pre-existing JIT identifier errors in instrsxarch.h.
  • The focused GCHandle.Target test could not be built because test dependency assets have not been generated.

Performance

  • EgorBot measurement requested for Linux x64 and macOS ARM64.

Note

This pull request description was generated by AI/Copilot.

Copilot AI and others added 2 commits August 12, 2026 23:10
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 23:22
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the CoreCLR GC handle write-barrier by moving the HndWriteBarrierWorker implementation into handletable.inl (so it can be inlined into its callers) and introduces a fast-path that avoids the handle-type lookup when the assigned target is in generation 0. This targets reduced overhead in hot handle assignment paths while keeping the GC/EE interface shape unchanged.

Changes:

  • Inline HndWriteBarrierWorker into src/coreclr/gc/handletable.inl and remove the out-of-line implementation from handletable.cpp.
  • Add an early-return fast-path in the worker to skip HandleFetchType(handle) when GetConvertedGeneration(value) is 0.
  • Move/adjust declarations (GetConvertedGeneration, HandleFetchType) to be available to the inlined implementation and update includes accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/coreclr/gc/handletablepriv.h Removes now-unneeded forward declarations previously used by the out-of-line worker.
src/coreclr/gc/handletable.inl Adds the inlined HndWriteBarrierWorker implementation and updates HndAssignHandle to call it directly.
src/coreclr/gc/handletable.h Adds handletableconstants.h include and exposes needed non-DAC prototypes for the inlined worker.
src/coreclr/gc/handletable.cpp Removes the out-of-line HndWriteBarrierWorker implementation (now provided inline).

Comment thread src/coreclr/gc/handletable.h Outdated
Comment thread src/coreclr/gc/handletable.h
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 13, 2026 05:51
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/gc/handletable.inl:68

  • The comment describing async pinned handle behavior has a few grammar/wording issues (e.g., missing space after //, “need”/“consider”). Updating it will make the intent clearer without changing behavior.
        //OverlappedData need special treatment: because all user data pointed by it needs to be reported by this handle,
        //its age is consider to be min age of the user data, to be simple, we just make it 0

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 13, 2026 06:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Suppressed comments (2)

src/coreclr/gc/handletablepriv.h:171

  • HandleFetchType in handletable.inl reads the block type via raw uint8_t* indexing (segment[HANDLE_SEGMENT_BLOCK_TYPE_OFFSET + uBlock]). The existing offsetof static_assert guards the offset, but the implementation also assumes rgBlockType elements are 1 byte. Adding a size check here makes that assumption explicit and prevents silent breakage if the field type ever changes.
static_assert(offsetof(_TableSegmentHeader, rgBlockType) == HANDLE_SEGMENT_BLOCK_TYPE_OFFSET);

src/coreclr/gc/handletable.inl:43

  • New code in handletable.inl uses C-style casts when deriving segment pointers (e.g., (uint8_t*)((uintptr_t)handle & HANDLE_SEGMENT_ALIGN_MASK)), while nearby code already uses reinterpret_cast. This is easy to fix and aligns with the repo's native guidance to prefer C++ casts.
    uint8_t* barrier = (uint8_t*)((uintptr_t)handle & HANDLE_SEGMENT_ALIGN_MASK);
    _ASSERTE(barrier);

    uintptr_t offset = (uintptr_t)handle & HANDLE_SEGMENT_CONTENT_MASK;
    _ASSERTE(offset >= HANDLE_HEADER_SIZE);

Comment thread src/coreclr/gc/gcinternal.h
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 15, 2026 04:52
Copilot AI requested a review from jkotas August 15, 2026 04:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/gc/handletable.inl:70

  • Fix grammar/spacing in this comment (missing space after //, subject/verb agreement, and "consider" → "considered") to keep comments clear and consistent.
        //OverlappedData need special treatment: because all user data pointed by it needs to be reported by this handle,
        //its age is consider to be min age of the user data, to be simple, we just make it 0

@jkotas

jkotas commented Aug 15, 2026

Copy link
Copy Markdown
Member

@EgorBot -windows_x64 -linux_x64 -linux_arm64

using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;

public class Bench
{
    [Benchmark]
    public void GCHandle()
    {
        using (var hnd = new PinnedGCHandle<string>("Hello"))
        {
        }
    }
}

@jkotas

jkotas commented Aug 16, 2026

Copy link
Copy Markdown
Member

@copilot Delete TableFreeHandlesToCache method - it is dead code

@jkotas

jkotas commented Aug 16, 2026

Copy link
Copy Markdown
Member

@copilot Make sure that TableFreeSingleHandleToCache is inlined in the one place it is called

@jkotas

jkotas commented Aug 16, 2026

Copy link
Copy Markdown
Member

@copilot GetGlobalHandleStore never fails. Delete unnecessary error handling in appdomain.cpp

@jkotas

jkotas commented Aug 16, 2026

Copy link
Copy Markdown
Member

@copilot Make sure that GetGlobalHandleStore iis cached in a static in NativeAOT - similar to how it is done in CoreCLR

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 16, 2026 04:13

Copilot AI commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Delete TableFreeHandlesToCache method - it is dead code

Deleted the dead implementation and declaration in 796149b. The CoreCLR runtime build succeeds with 0 warnings and 0 errors.

Note

This comment was generated by AI/Copilot.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/coreclr/gc/handletable.inl:71

  • The comment for async pinned handles has grammar issues and is a bit hard to read (e.g., "need"/"is consider"), which makes the rationale unclear in a subtle write-barrier path. Consider rewording for clarity while keeping the intent the same.
        //OverlappedData need special treatment: because all user data pointed by it needs to be reported by this handle,
        //its age is consider to be min age of the user data, to be simple, we just make it 0
        if (uType == HNDTYPE_ASYNCPINNED)

…e GetGlobalHandleStore in NativeAOT

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 16, 2026 04:28

Copilot AI commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Make sure that GetGlobalHandleStore iis cached in a static in NativeAOT - similar to how it is done in CoreCLR

Added g_pGlobalHandleStore in 55c837b, set alongside g_pGCHandleManager in both GCHeapUtilities::InitializeDefaultGC and InitializeStandaloneGC, and exposed via GCHandleUtilities::GetGlobalHandleStore(). Updated HandleTableHelpers.cpp to use it. Verified clr.nativeaotruntime builds with 0 errors.

@copilot GetGlobalHandleStore never fails. Delete unnecessary error handling in appdomain.cpp

Removed the null check and COMPlusThrowOM in 55c837b.

@copilot Make sure that TableFreeSingleHandleToCache is inlined in the one place it is called

Addressed in 55c837b — moved TableFreeSingleHandleToCache into handletablepriv.h as an __inline function (guarded by #ifndef DACCESS_COMPILE since it uses Interlocked::ExchangePointer in a way that doesn't compile for DAC's OBJECTHANDLE type), so it's inlined at its only call site in HndDestroyHandle. Verified clr.runtime builds with 0 errors, including cee_dac.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Comment on lines +24 to +31
// Retrieves the global GC handle store.
static IGCHandleStore* GetGlobalHandleStore()
{
LIMITED_METHOD_CONTRACT;

assert(g_pGlobalHandleStore != nullptr);
return g_pGlobalHandleStore;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants