Implicit byref arguments are never on the GC heap - #133304
Conversation
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
|
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. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
🟡 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
| ## 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. |
There was a problem hiding this comment.
This applies to all architectures now. This section should be reworded so that it does not start with explaining Windows Amd64 calling convention.
There was a problem hiding this comment.
Addressed, although, it looks like the diffs are quite minimal, let's see on CI SPMI once it finishes 🤔
|
|
||
| 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. |
There was a problem hiding this comment.
This should be moved to AMD64-only note in "Return buffers" section
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
|
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. |
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.
Bumps
READYTORUN_MAJOR_VERSIONto 29: crossgen2 now emits barrier-free stores, so older runtimes must reject these images.MINIMUM_READYTORUN_MAJOR_VERSIONstays at 26 - existing images can only forward an incoming implicit byref, never originate heap-backed storage for one.