Skip to content

Implicit byref arguments are never on the GC heap - #133304

Closed
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:implicit-byref-not-on-heap
Closed

Implicit byref arguments are never on the GC heap#133304
EgorBo wants to merge 4 commits into
dotnet:mainfrom
EgorBo:implicit-byref-not-on-heap

Conversation

@EgorBo

@EgorBo EgorBo commented Sep 5, 2026

Copy link
Copy Markdown
Member

Implicit byref arguments (structs passed by reference under the hood: not 1/2/4/8 bytes on win-x64, larger than 16 bytes on arm64) are now guaranteed to never live on the GC heap, so callees can store into them without write barriers.

struct S16 { public object A; public object B; }

[MethodImpl(MethodImplOptions.NoInlining)]
static void Store(S16 s, object o) { s.A = o; Sink(ref s); }
 ; P:Store(S16,System.Object)   win-x64
-       push     rbx
-       sub      rsp, 32
-       mov      rbx, rcx
-       mov      rcx, rbx
-       call     CORINFO_HELP_CHECKED_ASSIGN_REF
-       mov      rcx, rbx
+       sub      rsp, 40
+       mov      gword ptr [rcx], rdx
        call     [P:Sink(byref)]
        nop
-       add      rsp, 32
-       pop      rbx
+       add      rsp, 40
        ret
-; Total bytes of code 32
+; Total bytes of code 19

Bumps READYTORUN_MAJOR_VERSION to 29: crossgen2 now emits barrier-free stores, so older runtimes must reject these images. MINIMUM_READYTORUN_MAJOR_VERSION stays at 26 - existing images can only forward an incoming implicit byref, never originate heap-backed storage for one.

Implicit byref argument storage must live outside the GC heap on all
architectures using this convention, so callees can store into these
arguments without write barriers.

Bumps READYTORUN_MAJOR_VERSION to 29: crossgen2 now emits barrier-free
stores, so older runtimes must reject these images.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2871fa5f-63ee-4a17-9721-6c4895a014b3
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 5, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 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: @JulieLeeMSFT, @jakobbotsch
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.

🟡 Changes recommended

The behavioral code-generation change lacks targeted regression coverage for write-barrier omission and GC reporting.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates the implicit-byref ABI contract so argument storage is guaranteed outside the GC heap, allowing the JIT to omit write barriers safely.

Changes:

  • Marks implicit-byref stores as non-heap and updates promotion costing.
  • Copies debugger func-eval arguments into GC-protected native memory.
  • Documents the contract and advances ReadyToRun to version 29.
File summaries
File Description
src/coreclr/vm/callhelpers.h Documents caller ownership and GC-reporting requirements.
src/coreclr/vm/callhelpers.cpp Asserts implicit-byref arguments are not heap-backed.
src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs Advances managed R2R version.
src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h Advances NativeAOT R2R version.
src/coreclr/jit/promotion.cpp Removes obsolete write-barrier costing.
src/coreclr/jit/morph.cpp Marks implicit-byref stores as non-heap.
src/coreclr/jit/lclvars.cpp Documents the new storage invariant.
src/coreclr/inc/readytorun.h Advances and documents R2R version 29.
src/coreclr/debug/ee/funceval.cpp Creates protected native copies for func-eval arguments.
docs/design/coreclr/botr/clr-abi.md Documents the revised ABI contract.
Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/coreclr/jit/morph.cpp
Comment thread docs/design/coreclr/botr/clr-abi.md Outdated
## By-value value types passed by reference

Just like native, AMD64 has implicit-byrefs. Any structure (value type in IL parlance) that is not 1, 2, 4, or 8 bytes in size (i.e., 3, 5, 6, 7, or >= 9 bytes in size) that is declared to be passed by value, is instead passed by reference. For JIT generated code, it follows the native ABI where the passed-in reference is a pointer to a compiler generated temp local on the stack. However, there are some cases within remoting or reflection where apparently stackalloc is too hard, and so they pass in pointers within the GC heap, thus the JITed code must report these implicit byref parameters as interior pointers (BYREFs in JIT parlance), in case the callee is one of these reflection paths. Similarly, all writes must use checked write barriers.
Just like native, Windows AMD64 has implicit-byrefs. Any structure (value type in IL parlance) that is not 1, 2, 4, or 8 bytes in size (i.e., 3, 5, 6, 7, or >= 9 bytes in size) that is declared to be passed by value, is instead passed by reference. For JIT generated code, it follows the native ABI where the passed-in reference is a pointer to a compiler generated temp local on the stack.

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.

This applies to all architectures now. This section should be reworded so that it does not start with explaining Windows Amd64 calling convention.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed, although, it looks like the diffs are quite minimal, let's see on CI SPMI once it finishes 🤔

Comment thread docs/design/coreclr/botr/clr-abi.md Outdated

Since .NET 11, implicit-byref argument storage must be outside the GC heap on all architectures that use this convention. Runtime callers may use explicitly GC-protected native memory instead of the stack. The caller is responsible for making a writable copy as required by by-value semantics and for reporting any GC references in that copy. Callees may omit write barriers when modifying the argument. This does not apply to explicit byref parameters or the `this` pointer of a value type, and does not remove aliasing caused by taking the argument's address within the callee. Implicit-byref argument pointers are still represented and reported as GC byrefs.

The AMD64 native calling conventions (Windows 64 and System V) require return buffer address to be returned by callee in RAX. JIT also follows this rule.

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.

This should be moved to AMD64-only note in "Return buffers" section

EgorBo and others added 3 commits September 5, 2026 17:41
Reword the implicit-byref section so it does not lead with the Windows
AMD64 calling convention, and move the AMD64 return-buffer-in-RAX note
into the "Return buffers" section.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2871fa5f-63ee-4a17-9721-6c4895a014b3
The Blob Service "List Blobs" API pages its results, and the first page is
not guaranteed to cover the whole prefix. Since the listing is ordered by
blob name, a truncated first page drops entire target directories, which
produced zero partitions for those targets and failed the Helix send with
"SendHelixJob given no WorkItems to send".

Follow the NextMarker until the listing is exhausted, and log the blob
count so a truncated listing is visible in the setup log.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2871fa5f-63ee-4a17-9721-6c4895a014b3
@EgorBo

EgorBo commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

Diffs aren't too motivating. Perhaps, a bit more can be improved with proper alias analysis (implicit byref never aliases with gc heap), but closing for now.

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

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants