Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

@EgorBo@jkotas@kg@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

@EgorBo@jkotas@kg@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

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

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

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

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

@EgorBo@jkotas@kg@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

@EgorBo@jkotas@kg@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

@EgorBo@jkotas@kg@jakobbotsch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Remove CORINFO_HELP_ASSIGN_BYREF - #128542

Merged
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref
May 28, 2026
Merged

Remove CORINFO_HELP_ASSIGN_BYREF#128542
EgorBo merged 13 commits into
dotnet:mainfrom
EgorBo:remove-corinfo-help-assign-byref

Conversation

@EgorBo

@EgorBoEgorBo commented May 25, 2026

Copy link
Copy Markdown
Member

This PR removes CORINFO_HELP_ASSIGN_BYREF write-barrier (which is basically CORINFO_HELP_CHECKED_ASSIGN_REF with a custom calling convention), it is replaced by either CORINFO_HELP_ASSIGN_REF or CORINFO_HELP_CHECKED_ASSIGN_REF. As a side effect we no longer use slow rep movsq on x64 for gaps between GC slots and may use SIMD. The motivation that CORINFO_HELP_ASSIGN_BYREF was very useful for large struct copies, but we now have a bulk write barrier for those.

NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT side as dead as well since CORINFO_HELP_ASSIGN_BYREF is unused now, but that's for another PR and might require R2R bump.

classMyClass{MyStruct_fld;voidSetA(MyStructvalue)=>_fld=value;}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);
- push rdi
push rsi
+ push rbx+ mov rbx, rdx- lea rdi, bword ptr [rcx+0x08]- mov rsi, rdx- call CORINFO_HELP_ASSIGN_BYREF- call CORINFO_HELP_ASSIGN_BYREF- mov ecx, 5- rep movsq+ lea rsi, bword ptr [rcx+0x08]+ mov rdx, gword ptr [rbx]+ mov rcx, rsi+ call CORINFO_HELP_ASSIGN_REF+ mov rdx, gword ptr [rbx+0x08]+ lea rcx, bword ptr [rsi+0x08]+ call CORINFO_HELP_ASSIGN_REF+ vmovdqu ymm0, ymmword ptr [rbx+0x10]+ vmovdqu ymmword ptr [rsi+0x10], ymm0+ mov rax, qword ptr [rbx+0x30]+ mov qword ptr [rsi+0x30], rax+ vzeroupper+ pop rbx
pop rsi
- pop rdi
ret

Benchmark

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;usingSystem.Runtime.CompilerServices;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{MyStructsrc,dst;[Benchmark]publicvoidHeap()=>dst=src;}publicstructMyStruct{publicstringA;publicInlineArray4<long>G;publicstringB;}

INTEL XEON PLATINUM 8573C

MethodToolchainMeanErrorRatio
HeapPR #1285422.724 ns0.0628 ns1.00
Heapmain31.152 ns0.2597 ns11.44

AMD EPYC 9V45 2.60GHz

MethodToolchainMeanErrorRatio
HeapPR #1285421.968 ns0.0603 ns1.00
Heapmain13.121 ns0.0067 ns6.68

Apple M4

MethodToolchainMeanErrorRatio
HeapPR #1285420.9574 ns0.0021 ns1.00
Heapmain1.4646 ns0.0053 ns1.53

Sligtly less difference on arm64 where we don't have slow movsq for nongc gaps.

CopilotAI review requested due to automatic review settings May 25, 2026 01:13
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 25, 2026
@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.

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 removes the CORINFO_HELP_ASSIGN_BYREF / JIT_ByRefWriteBarrier helper from the JIT/EE boundary and ReadyToRun format, and replaces the JIT’s “cpobj with interleaved GC pointers” lowering/codegen path with either (a) decomposition into per-slot ASSIGN_REF write barriers (for small GC pointer counts) or (b) the existing bulk write-barrier helper (CORINFO_HELP_BULK_WRITEBARRIER). It also bumps the ReadyToRun major version to reflect the serialized format change.

Changes:

  • Delete the ASSIGN_BYREF helper surface area (helper IDs, write-barrier stubs/AV markers, and per-arch asm implementations) across VM, runtime, NativeAOT EH helper marking, and AOT tooling.
  • Remove the CpObj* block op kinds and all backend genCodeForCpObj implementations; introduce TryDecomposeBlockStoreAsIndirs + LowerBlockStoreAsGcBulkCopyCall as the new lowering strategy for GC-containing struct copies.
  • Bump ReadyToRun major version to 19 and reserve helper ID 0x32 (formerly ByRefWriteBarrier) to avoid reuse.

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/wasm/helpers.cppRemoves wasm helper end marker and AV location globals for byref write barrier.
src/coreclr/vm/threads.cppStops registering CORINFO_HELP_ASSIGN_BYREF helper in InitThreadManager; updates AMD64 comment.
src/coreclr/vm/riscv64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/loongarch64/asmhelpers.SRemoves JIT_ByRefWriteBarrier implementation; adjusts comments about incremented regs.
src/coreclr/vm/jitinterface.hRemoves RhpByRefAssignRef* declarations/mapping and JIT_ByRefWriteBarrier decl.
src/coreclr/vm/i386/jithelp.SDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/i386/jithelp.asmDeletes x86 JIT_ByRefWriteBarrier helper macro/entry and its inclusion in the group.
src/coreclr/vm/excep.cppRemoves byref write barrier end marker, AV locations, and helper-range checks.
src/coreclr/vm/arm64/patchedcode.SRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm64/patchedcode.asmRemoves ARM64 JIT_ByRefWriteBarrier; updates comments for remaining helpers/return stubs.
src/coreclr/vm/arm/stubs.cppRemoves ARM byref barrier variants from stub declarations/mapping/validation.
src/coreclr/vm/arm/patchedcode.SRemoves ARM JIT_ByRefWriteBarrier region from patched code blob.
src/coreclr/vm/arm/asmhelpers.SRemoves ARM byref write barrier helper macro and descriptor entries.
src/coreclr/vm/amd64/jithelpers_fast.SRemoves AMD64 Unix JIT_ByRefWriteBarrier implementation.
src/coreclr/vm/amd64/JitHelpers_Fast.asmRemoves AMD64 Windows JIT_ByRefWriteBarrier implementation.
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.csRemoves CORINFO_HELP_ASSIGN_BYREF from managed mirror enum.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.csReserves 0x32 with a “do not reuse” comment in tool-side helper enum.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.csRemoves signature pretty-printing for ByRefWriteBarrier.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.csDrops mapping from ASSIGN_BYREF to ByRefWriteBarrier R2R helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.csRemoves ReadyToRunHelper.ByRefWriteBarrier entry point logic.
src/coreclr/runtime/riscv64/WriteBarriers.SRemoves RhpByRefAssignRef implementation/AV alternate entry.
src/coreclr/runtime/portable/WriteBarriers.cppRemoves portable stub for RhpByRefAssignRef.
src/coreclr/runtime/loongarch64/WriteBarriers.SRemoves RhpByRefAssignRef; updates comment about incremented regs in remaining helper.
src/coreclr/runtime/i386/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/i386/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/runtime/arm64/WriteBarriers.SRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm64/WriteBarriers.asmRemoves RhpByRefAssignRefArm64 implementation/AV alternate entry.
src/coreclr/runtime/arm/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV labels.
src/coreclr/runtime/amd64/WriteBarriers.SRemoves RhpByRefAssignRef implementation and AV alternate entries (incl. Apple unwind workaround).
src/coreclr/runtime/amd64/WriteBarriers.asmRemoves RhpByRefAssignRef implementation and AV alternate entries.
src/coreclr/nativeaot/Runtime/EHHelpers.cppRemoves byref write barrier AV locations from NativeAOT helper-range detection.
src/coreclr/jit/utils.cppRemoves CORINFO_HELP_ASSIGN_BYREF from helper call property initialization.
src/coreclr/jit/targetx86.hRemoves x86 ASSIGN_BYREF register/killset documentation and masks.
src/coreclr/jit/targetwasm.hRemoves wasm byref write barrier reg/killset defines.
src/coreclr/jit/targetriscv64.hRemoves RISCV64 byref write barrier reg/killset defines.
src/coreclr/jit/targetloongarch64.hRemoves LoongArch64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm64.hRemoves ARM64 byref write barrier reg/killset defines.
src/coreclr/jit/targetarm.hRemoves ARM byref write barrier reg/killset defines.
src/coreclr/jit/targetamd64.hRemoves AMD64 byref write barrier killset defines and related commentary.
src/coreclr/jit/lsraxarch.cppRemoves CpObj-specific LSRA register assignment logic for xarch.
src/coreclr/jit/lsrariscv64.cppRemoves CpObj-specific LSRA register assignment logic for riscv64.
src/coreclr/jit/lsraloongarch64.cppRemoves CpObj-specific LSRA register assignment logic for loongarch64.
src/coreclr/jit/lsrabuild.cppRemoves CpObj killset handling that depended on ASSIGN_BYREF.
src/coreclr/jit/lsraarmarch.cppRemoves CpObj-specific LSRA register assignment logic for arm/arm64.
src/coreclr/jit/lowerxarch.cppChanges GC-containing struct copy lowering to decompose-or-bulk-helper; adjusts stack/off-heap handling.
src/coreclr/jit/lowerwasm.cppUses decompose-or-bulk-helper for GC-containing struct copies on wasm.
src/coreclr/jit/lowerriscv64.cppUses decompose-or-bulk-helper for GC-containing struct copies on riscv64.
src/coreclr/jit/lowerloongarch64.cppUses decompose-or-bulk-helper for GC-containing struct copies on loongarch64.
src/coreclr/jit/lowerarmarch.cppUses decompose-or-bulk-helper for GC-containing struct copies on arm/arm64.
src/coreclr/jit/lower.hReplaces TryLowerBlockStoreAsGcBulkCopyCall with LowerBlockStoreAsGcBulkCopyCall and adds TryDecomposeBlockStoreAsIndirs.
src/coreclr/jit/lower.cppImplements decomposition into per-slot indirections and the bulk write-barrier fallback lowering.
src/coreclr/jit/gentree.hRemoves CpObj-specific BlkOpKind enum values.
src/coreclr/jit/gentree.cppRemoves debug printing for CpObj BlkOpKind values.
src/coreclr/jit/emit.cppRemoves special-case GC-reg-kill handling for ASSIGN_BYREF.
src/coreclr/jit/codegenxarch.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenwasm.cppRemoves byref write barrier signature comment.
src/coreclr/jit/codegenriscv64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegenloongarch64.cppRemoves CpObj codegen path and related block-kind dispatch.
src/coreclr/jit/codegencommon.cppRemoves helper killset case for ASSIGN_BYREF.
src/coreclr/jit/codegenarmarch.cppRemoves CpObj block-kind dispatch.
src/coreclr/jit/codegenarm64.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegenarm.cppRemoves CpObj codegen path implementation.
src/coreclr/jit/codegen.hRemoves genCodeForCpObj declaration.
src/coreclr/inc/readytorunhelpers.hRemoves READYTORUN_HELPER_ByRefWriteBarrier mapping macro entry.
src/coreclr/inc/readytorun.hBumps R2R major/minimum major to 19; reserves helper slot 0x32; documents the format break.
src/coreclr/inc/jithelpers.hRemoves CORINFO_HELP_ASSIGN_BYREF helper entry.
src/coreclr/inc/jiteeversionguid.hUpdates JITEEVersionIdentifier to reflect the interface change.
src/coreclr/inc/corinfo.hRemoves CORINFO_HELP_ASSIGN_BYREF from CorInfoHelpFunc.
docs/design/coreclr/botr/readytorun-format.mdUpdates R2R helper enum docs to reserve 0x32 and note removal.

Comment threadsrc/coreclr/jit/lowerxarch.cpp Outdated
@EgorBo

EgorBo commented May 25, 2026

Copy link
Copy Markdown
MemberAuthor

@EgorBot -amd -arm

usingBenchmarkDotNet.Attributes;usingBenchmarkDotNet.Running;BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);publicclassBenchmarks{privateMyStructa1;privateMyStructa2;[Benchmark]publicvoidCopy(){a1=a2;}}recordstructMyStruct(longN1,longN2,longN3,longN4,longN5,stringA,stringB);

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 32 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/jit/lower.cpp:10167

  • LowerBlockStoreAsGcBulkCopyCall now unconditionally drops a GT_IND source without preserving/handling volatile semantics (previously this path explicitly refused volatile sources/stores). If data is a volatile indirection (e.g. reading from a volatile struct field), removing the GT_IND loses the volatile flag, and the resulting CORINFO_HELP_BULK_WRITEBARRIER call is not wrapped with the usual memory barrier nodes (compare with LowerBlockStoreAsHelperCall). This risks incorrect volatile ordering/semantics on non-xarch targets. Consider either (1) keeping the earlier bail-out for volatile source/store and forcing decomposition in those cases, or (2) explicitly inserting the appropriate BARRIER_* nodes around the helper call and preserving volatile-source behavior.
// or layouts with too many GC pointers to decompose efficiently). Volatile
// semantics are preserved by the helper call itself — the call acts as a
// compiler memory barrier and the helper uses atomic per-slot stores.
//
// Arguments:
// blk - The block store node to lower
//
void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk)
{
assert(blk->OperIs(GT_STORE_BLK));
assert(!blk->OperIsInitBlkOp());
assert(blk->GetLayout()->HasGCPtr());
assert(!blk->IsAddressNotOnHeap(m_compiler));
GenTree* dest = blk->Addr();
GenTree* data = blk->Data();
if (data->OperIs(GT_IND))
{
// Drop GT_IND nodes
BlockRange().Remove(data);
data = data->AsIndir()->Addr();
}

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from c845f6c to ee79d4dCompareMay 25, 2026 12:02
CopilotAI review requested due to automatic review settings May 25, 2026 12:42
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from ee79d4d to d805a92CompareMay 25, 2026 12:42

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 32 changed files in this pull request and generated 1 comment.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from d805a92 to 28e1813CompareMay 25, 2026 13:03
@EgorBo

Copy link
Copy Markdown
MemberAuthor

@MihuBot

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 32 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBoforce-pushed the remove-corinfo-help-assign-byref branch from 1b597b2 to 0177e27CompareMay 25, 2026 14:04
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp Outdated

@jakobbotschjakobbotsch 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.

A few nits, otherwise LGTM.

This fixes #7469 and #90196 I think.

Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
CopilotAI review requested due to automatic review settings May 27, 2026 15:43

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 32 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/jit/lower.cpp
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment threadsrc/coreclr/jit/lower.cpp
@EgorBo
EgorBo merged commit 3cb9cf7 into dotnet:mainMay 28, 2026
144 of 147 checks passed
@EgorBo
EgorBo deleted the remove-corinfo-help-assign-byref branch May 28, 2026 07:12
EgorBo added a commit that referenced this pull request May 29, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@EgorBo

Copy link
Copy Markdown
MemberAuthor

Improvements:

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 17, 2026
EgorBo added a commit to EgorBo/runtime-1 that referenced this pull request Jul 8, 2026
LowerCopyBlockStore/LowerInitBlockStore dropped write barriers for a
stack destination containing GC pointers (correct) but then, for sizes
above the unroll threshold, fell back to the managed CORINFO_HELP_MEMCPY
(SpanHelpers.Memmove) / CORINFO_HELP_MEMZERO (SpanHelpers.ClearWithoutReferences)
helpers. Those calls are GC-safe points, so a GC triggered mid-operation
could observe torn GC pointers in the partially-written, GC-reported
stack destination and corrupt the heap.
Only take the "no write barriers" copy shortcut when the copy is actually
unrolled into a single non-interruptible region; otherwise stay on the
GC-aware CpObj path. Route zeroing of any GC-pointer struct (stack or
heap) to the atomic pointer-sized zeroing loop instead of the helper.
Copy regression introduced by dotnet#128542.
Fixesdotnet#130286
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
EgorBo added a commit that referenced this pull request Jul 8, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
This PR removes `CORINFO_HELP_ASSIGN_BYREF` write-barrier (which is
basically `CORINFO_HELP_CHECKED_ASSIGN_REF` with a custom calling
convention), it is replaced by either `CORINFO_HELP_ASSIGN_REF` or
`CORINFO_HELP_CHECKED_ASSIGN_REF`. As a side effect we no longer use
slow `rep movsq` on x64 for gaps between GC slots and may use SIMD. The
motivation that `CORINFO_HELP_ASSIGN_BYREF` was very useful for large
struct copies, but we now have a bulk write barrier for those.
NOTE: A lot of code (>1000-2000 LOC) can be removed from CoreCLR/NAOT
side as dead as well since `CORINFO_HELP_ASSIGN_BYREF` is unused now,
but that's for another PR and might require R2R bump.
```cs
class MyClass
{
MyStruct _fld;
void SetA(MyStruct value) => _fld = value;
}
record struct MyStruct(long N1, long N2, long N3, long N4, long N5, string A, string B);
```
```diff
- push rdi
push rsi
+ push rbx
+ mov rbx, rdx
- lea rdi, bword ptr [rcx+0x08]
- mov rsi, rdx
- call CORINFO_HELP_ASSIGN_BYREF
- call CORINFO_HELP_ASSIGN_BYREF
- mov ecx, 5
- rep movsq
+ lea rsi, bword ptr [rcx+0x08]
+ mov rdx, gword ptr [rbx]
+ mov rcx, rsi
+ call CORINFO_HELP_ASSIGN_REF
+ mov rdx, gword ptr [rbx+0x08]
+ lea rcx, bword ptr [rsi+0x08]
+ call CORINFO_HELP_ASSIGN_REF
+ vmovdqu ymm0, ymmword ptr [rbx+0x10]
+ vmovdqu ymmword ptr [rsi+0x10], ymm0
+ mov rax, qword ptr [rbx+0x30]
+ mov qword ptr [rsi+0x30], rax
+ vzeroupper
+ pop rbx
pop rsi
- pop rdi
ret
```
## Benchmark
```cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
public class Benchmarks
{
MyStruct src, dst;
[Benchmark]
public void Heap() => dst = src;
}
public struct MyStruct
{
public string A; public InlineArray4<long> G; public string B;
}
```
#### INTEL XEON PLATINUM 8573C | Method | Toolchain | Mean | Error | Ratio | |------- |----------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 2.724 ns | 0.0628 ns | 1.00 | | Heap | main | 31.152 ns | 0.2597 ns | 11.44 |
#### AMD EPYC 9V45 2.60GHz
| Method | Toolchain | Mean | Error | Ratio | |------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 1.968 ns | 0.0603 ns | 1.00 | | Heap | main | 13.121 ns | 0.0067 ns | 6.68 |
#### Apple M4
| Method | Toolchain | Mean | Error | Ratio |
|------- |------------------- |----------:|----------:|------:|
| Heap | PR #128542 | 0.9574 ns | 0.0021 ns | 1.00 |
| Heap | main | 1.4646 ns | 0.0053 ns | 1.53 |
Sligtly less difference on arm64 where we don't have slow `movsq` for
nongc gaps.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Follow up #128542
Not sure if it's worth bumping the R2R format for this if nothing else
needs it, but let's see if it compiles
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Fixes#130286.
For a stack-target struct copy/zero containing GC pointers,
`LowerCopyBlockStore`/`LowerInitBlockStore` correctly dropped write
barriers but, above the unroll threshold, fell back to the managed
`CORINFO_HELP_MEMCPY` (`Memmove`) / `CORINFO_HELP_MEMZERO`
(`ClearWithoutReferences`) helpers. Those are GC-safe points, so a GC
mid-operation could observe torn GC pointers in the partially-written,
GC-reported stack destination and corrupt the heap.
Fix:
- Copy: only take the "no write barriers" shortcut when the copy is
actually unrolled (single non-interruptible region); otherwise stay on
the GC-aware CpObj path.
- Zero: route zeroing of any GC-pointer struct (stack or heap) to the
atomic pointer-sized loop instead of the helper.
The copy regression was introduced by #128542.
Verified: SPMI `replay -f benchmarks.run` clean; asmdiffs limited to
large GC structs now using the GC-safe path; before/after codegen
confirms `MEMCPY`→`BULK_WRITEBARRIER` and `MEMZERO`→zeroing loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 18, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR 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.

5 participants

@EgorBo@jkotas@kg@jakobbotsch