From be3c701daaf4ae3b2757e01a9a0cf16bd92646b2 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 14 May 2026 13:07:33 -0700 Subject: [PATCH 01/12] Checkpoint --- .../runtime/portable/WriteBarriers.cpp | 12 +- src/coreclr/vm/CMakeLists.txt | 12 +- src/coreclr/vm/fcall.h | 11 ++ src/coreclr/vm/jitinterface.h | 12 +- src/coreclr/vm/wasm/writebarriers.cpp | 113 ++++++++++++++++++ 5 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 src/coreclr/vm/wasm/writebarriers.cpp diff --git a/src/coreclr/runtime/portable/WriteBarriers.cpp b/src/coreclr/runtime/portable/WriteBarriers.cpp index 00301cd1f2f457..c863b1399f58dd 100644 --- a/src/coreclr/runtime/portable/WriteBarriers.cpp +++ b/src/coreclr/runtime/portable/WriteBarriers.cpp @@ -4,22 +4,22 @@ #include -EXTERN_C FCDECL2(VOID, RhpAssignRef, Object **dst, Object *ref); -FCIMPL2(VOID, RhpAssignRef, Object **dst, Object *ref) +EXTERN_C FCDECL2_RAW(VOID, RhpAssignRef, Object **dst, Object *ref); +FCIMPL2_RAW(VOID, RhpAssignRef, Object **dst, Object *ref) { PORTABILITY_ASSERT("RhpAssignRef is not yet implemented"); } FCIMPLEND -EXTERN_C FCDECL2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); -FCIMPL2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref) +EXTERN_C FCDECL2_RAW(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); +FCIMPL2_RAW(VOID, RhpCheckedAssignRef, Object **dst, Object *ref) { PORTABILITY_ASSERT("RhpCheckedAssignRef is not yet implemented"); } FCIMPLEND -EXTERN_C FCDECL2(VOID, RhpByRefAssignRef, Object **dst, Object *ref); -FCIMPL2(VOID, RhpByRefAssignRef, Object **dst, Object *ref) +EXTERN_C FCDECL2_RAW(VOID, RhpByRefAssignRef, Object **dst, Object **ref); +FCIMPL2_RAW(VOID, RhpByRefAssignRef, Object **dst, Object **ref) { PORTABILITY_ASSERT("RhpByRefAssignRef is not yet implemented"); } diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index fbbc909fa6758a..ef94d72e71bc60 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -652,8 +652,18 @@ if (FEATURE_PORTABLE_HELPERS) list(APPEND VM_SOURCES_WKS ${PORTABLE_SOURCES_DIR}/AllocFast.cpp ${PORTABLE_VM_SOURCES_DIR}/AllocSlow.cpp - ${PORTABLE_SOURCES_DIR}/WriteBarriers.cpp ) + + if(CLR_CMAKE_TARGET_ARCH_WASM) + list(APPEND VM_SOURCES_WKS + ${ARCH_SOURCES_DIR}/WriteBarriers.cpp + ) + else() + list(APPEND VM_SOURCES_WKS + ${PORTABLE_SOURCES_DIR}/WriteBarriers.cpp + ) + endif() + elseif(CLR_CMAKE_TARGET_WIN32) if(CLR_CMAKE_TARGET_ARCH_AMD64) set(VM_SOURCES_WKS_ARCH_ASM diff --git a/src/coreclr/vm/fcall.h b/src/coreclr/vm/fcall.h index 79b3cb9155992f..9b3b98c28ef8dd 100644 --- a/src/coreclr/vm/fcall.h +++ b/src/coreclr/vm/fcall.h @@ -148,6 +148,7 @@ #define FCDECL1(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) #define FCDECL1_V(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) #define FCDECL2(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) +#define FCDECL2_RAW(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(a1, a2); rettype F_CALL_CONV funcname(a1, a2) #define FCDECL2_VV(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) #define FCDECL2_VI(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) #define FCDECL2_IV(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) @@ -190,6 +191,10 @@ #endif // !SWIZZLE_STKARG_ORDER +#ifndef FCDECL2_RAW +#define FCDECL2_RAW(rettype, funcname, a1, a2) FCDECL2(rettype, funcname, a1, a2) +#endif + #if defined(ENABLE_CONTRACTS) #define FC_CAN_TRIGGER_GC() FCallGCCanTrigger::Enter() #define FC_CAN_TRIGGER_GC_END() FCallGCCanTrigger::Leave(__FUNCTION__, __FILE__, __LINE__) @@ -410,6 +415,8 @@ struct FCSigCheck { #define FCIMPL1(rettype, funcname, a1) WASM_CALLABLE_FUNC_2(rettype, funcname, a1, PCODE portableEntryPointContext) { FCIMPL_PROLOG(funcname) #define FCIMPL1_V(rettype, funcname, a1) WASM_CALLABLE_FUNC_2(rettype, funcname, a1, PCODE portableEntryPointContext) { FCIMPL_PROLOG(funcname) #define FCIMPL2(rettype, funcname, a1, a2) WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { FCIMPL_PROLOG(funcname) +#define FCIMPL2_RAW(rettype, funcname, a1, a2) FCSIGCHECK(funcname, #rettype "," #a1 "," #a2) \ + rettype F_CALL_CONV funcname(a1, a2) { FCIMPL_PROLOG(funcname) #define FCIMPL2VA(rettype, funcname, a1, a2) WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { FCIMPL_PROLOG(funcname) #define FCIMPL2_VV(rettype, funcname, a1, a2) WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { FCIMPL_PROLOG(funcname) #define FCIMPL2_VI(rettype, funcname, a1, a2) WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { FCIMPL_PROLOG(funcname) @@ -515,6 +522,10 @@ struct FCSigCheck { #define HCIMPLEND_RAW } #define HCIMPLEND } +#ifndef FCIMPL2_RAW +#define FCIMPL2_RAW(rettype, funcname, a1, a2) FCIMPL2(rettype, funcname, a1, a2) +#endif + // The managed calling convention expects returned small types (e.g. bool) to be // widened to 32-bit on return. The C/C++ calling convention does not guarantee returned // small types to be widened on most platforms. The small types have to be artificially diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 609e4aef7279e5..7edb58ba06162d 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -188,7 +188,7 @@ EXTERN_C void ReversePInvokeBadTransition(); extern "C" FCDECL3(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref, CheckedWriteBarrierKinds kind); #else // Regular checked write barrier. -extern "C" FCDECL2(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref); +extern "C" FCDECL2_RAW(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref); #ifdef TARGET_ARM64 #define RhpCheckedAssignRef RhpCheckedAssignRefArm64 @@ -202,11 +202,11 @@ extern "C" FCDECL2(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref); #endif // FEATURE_USE_ASM_GC_WRITE_BARRIERS && defined(FEATURE_COUNT_GC_WRITE_BARRIERS) -extern "C" FCDECL2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); -extern "C" FCDECL2(VOID, RhpByRefAssignRef, Object **dst, Object *ref); -extern "C" FCDECL2(VOID, RhpAssignRef, Object **dst, Object *ref); +extern "C" FCDECL2_RAW(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); +extern "C" FCDECL2_RAW(VOID, RhpByRefAssignRef, Object **dst, Object **ref); +extern "C" FCDECL2_RAW(VOID, RhpAssignRef, Object **dst, Object *ref); -extern "C" FCDECL2(VOID, JIT_WriteBarrier, Object **dst, Object *ref); +extern "C" FCDECL2_RAW(VOID, JIT_WriteBarrier, Object **dst, Object *ref); EXTERN_C FCDECL2_VV(INT64, JIT_LMul, INT64 val1, INT64 val2); @@ -967,7 +967,7 @@ class CInterpreterJitInfo final : public CEECodeGenInfo void SetDebugInfo(PTR_BYTE pDebugInfo) override; LPVOID GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) override; - + virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub(void** entryPoint) override final; }; #endif // FEATURE_INTERPRETER diff --git a/src/coreclr/vm/wasm/writebarriers.cpp b/src/coreclr/vm/wasm/writebarriers.cpp new file mode 100644 index 00000000000000..ac8f3ad2df1591 --- /dev/null +++ b/src/coreclr/vm/wasm/writebarriers.cpp @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#include +#include "..\gchelpers.inl" +#include "gcheaputilities.h" + +#define ASM_HELPER_2(rettype, funcname, a1, a2) \ + EXTERN_C rettype __attribute__((naked)) F_CALL_CONV funcname(a1, a2) +#define GC_ASM(text) \ + asm(text \ + :: [g_lowest_address] "i" (&g_lowest_address), \ + [g_highest_address] "i" (&g_highest_address), \ + [g_ephemeral_low] "i" (&g_ephemeral_low), \ + [g_ephemeral_high] "i" (&g_ephemeral_high), \ + [g_card_table] "i" (&g_card_table), \ + [card_byte_shift] "i" (card_byte_shift) \ + ) + +EXTERN_C FCDECL2_RAW(VOID, JIT_WriteBarrier, Object **dst, Object *ref); +ASM_HELPER_2(VOID, JIT_WriteBarrier, Object **dst, Object *ref) +{ + GC_ASM( + /* *dst = ref */ + "local.get 0\n" + "local.get 1\n" + "i32.store 0\n" + /* if ((ref < ephemeral_low) || (ref >= ephemeral_high)) return */ + "local.get 1\n" + "i32.const 0\n i32.load %[g_ephemeral_low]\n" + "i32.lt_u\n" + "local.get 1\n" + "i32.const 0\n i32.load %[g_ephemeral_high]\n" + "i32.ge_u\n" + "i32.or\n" + "if\n return\n end_if\n" + /* ref = ref >> card_byte_shift */ + "local.get 1\n" + "i32.const %[card_byte_shift]\n" + "i32.shr_u\n" + "local.tee 1\n" + /* if (g_card_table[ref] == 255) return */ + "i32.load8_u %[g_card_table]\n" + "i32.const 255\n" + "i32.eq\n" + "if\n return\n end_if\n" + /* g_card_table[ref] = 255 */ + "local.get 1\n" + "i32.const 255\n" + "i32.store8 %[g_card_table]\n" + "return\n" + ); +} + +EXTERN_C FCDECL2_RAW(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref); +ASM_HELPER_2(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref) +{ + GC_ASM( + /* *dst = ref */ + "local.get 0\n" + "local.get 1\n" + "i32.store 0\n" + /* if ((dst < lowest) || (dst >= highest)) return */ + "local.get 0\n" + "i32.const 0\n i32.load %[g_lowest_address]\n" + "i32.lt_u\n" + "local.get 0\n" + "i32.const 0\n i32.load %[g_highest_address]\n" + "i32.ge_u\n" + "i32.or\n" + "if\n return\n end_if\n" + /* if ((ref < ephemeral_low) || (ref >= ephemeral_high)) return */ + "local.get 1\n" + "i32.const 0\n i32.load %[g_ephemeral_low]\n" + "i32.lt_u\n" + "local.get 1\n" + "i32.const 0\n i32.load %[g_ephemeral_high]\n" + "i32.ge_u\n" + "i32.or\n" + "if\n return\n end_if\n" + /* ref = ref >> card_byte_shift */ + "local.get 1\n" + "i32.const %[card_byte_shift]\n" + "i32.shr_u\n" + "local.tee 1\n" + /* if (g_card_table[ref] == 255) return */ + "i32.load8_u %[g_card_table]\n" + "i32.const 255\n" + "i32.eq\n" + "if\n return\n end_if\n" + /* g_card_table[ref] = 255 */ + "local.get 1\n" + "i32.const 255\n" + "i32.store8 %[g_card_table]\n" + "return\n" + ); +} + +EXTERN_C FCDECL2_RAW(VOID, RhpAssignRef, Object **dst, Object *ref); +ASM_HELPER_2(VOID, RhpAssignRef, Object **dst, Object *ref) +__attribute__((alias("JIT_WriteBarrier"))); + +EXTERN_C FCDECL2_RAW(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); +ASM_HELPER_2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref) +__attribute__((alias("JIT_CheckedWriteBarrier"))); + +EXTERN_C FCDECL2_RAW(VOID, RhpByRefAssignRef, Object **dst, Object **ref); +ASM_HELPER_2(VOID, RhpByRefAssignRef, Object **dst, Object **ref) +{ + GC_ASM("unreachable" + ); +} From c65cbe784355122be3d0a4549d538d6295d3cf86 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 18 May 2026 14:50:51 -0700 Subject: [PATCH 02/12] Cleanup --- src/coreclr/vm/CMakeLists.txt | 2 +- src/coreclr/vm/fcall.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index ef94d72e71bc60..805c844d09dd68 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -656,7 +656,7 @@ if (FEATURE_PORTABLE_HELPERS) if(CLR_CMAKE_TARGET_ARCH_WASM) list(APPEND VM_SOURCES_WKS - ${ARCH_SOURCES_DIR}/WriteBarriers.cpp + ${ARCH_SOURCES_DIR}/writebarriers.cpp ) else() list(APPEND VM_SOURCES_WKS diff --git a/src/coreclr/vm/fcall.h b/src/coreclr/vm/fcall.h index 9b3b98c28ef8dd..61a3afd288d624 100644 --- a/src/coreclr/vm/fcall.h +++ b/src/coreclr/vm/fcall.h @@ -148,7 +148,7 @@ #define FCDECL1(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) #define FCDECL1_V(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) #define FCDECL2(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) -#define FCDECL2_RAW(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(a1, a2); rettype F_CALL_CONV funcname(a1, a2) +#define FCDECL2_RAW(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(a1, a2) #define FCDECL2_VV(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) #define FCDECL2_VI(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) #define FCDECL2_IV(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) From fadf0cadaa0ef9ca635d5e9efeef9a4ea11b9536 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 18 May 2026 15:01:40 -0700 Subject: [PATCH 03/12] Fix use of ref instead of dst --- src/coreclr/vm/wasm/writebarriers.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/coreclr/vm/wasm/writebarriers.cpp b/src/coreclr/vm/wasm/writebarriers.cpp index ac8f3ad2df1591..2dcbfaf3651709 100644 --- a/src/coreclr/vm/wasm/writebarriers.cpp +++ b/src/coreclr/vm/wasm/writebarriers.cpp @@ -35,18 +35,18 @@ ASM_HELPER_2(VOID, JIT_WriteBarrier, Object **dst, Object *ref) "i32.ge_u\n" "i32.or\n" "if\n return\n end_if\n" - /* ref = ref >> card_byte_shift */ - "local.get 1\n" + /* dst = dst >> card_byte_shift */ + "local.get 0\n" "i32.const %[card_byte_shift]\n" "i32.shr_u\n" - "local.tee 1\n" - /* if (g_card_table[ref] == 255) return */ + "local.tee 0\n" + /* if (g_card_table[dst] == 255) return */ "i32.load8_u %[g_card_table]\n" "i32.const 255\n" "i32.eq\n" "if\n return\n end_if\n" - /* g_card_table[ref] = 255 */ - "local.get 1\n" + /* g_card_table[dst] = 255 */ + "local.get 0\n" "i32.const 255\n" "i32.store8 %[g_card_table]\n" "return\n" @@ -79,18 +79,18 @@ ASM_HELPER_2(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref) "i32.ge_u\n" "i32.or\n" "if\n return\n end_if\n" - /* ref = ref >> card_byte_shift */ - "local.get 1\n" + /* dst = dst >> card_byte_shift */ + "local.get 0\n" "i32.const %[card_byte_shift]\n" "i32.shr_u\n" - "local.tee 1\n" - /* if (g_card_table[ref] == 255) return */ + "local.tee 0\n" + /* if (g_card_table[dst] == 255) return */ "i32.load8_u %[g_card_table]\n" "i32.const 255\n" "i32.eq\n" "if\n return\n end_if\n" - /* g_card_table[ref] = 255 */ - "local.get 1\n" + /* g_card_table[dst] = 255 */ + "local.get 0\n" "i32.const 255\n" "i32.store8 %[g_card_table]\n" "return\n" From e7b7942c477ed0888d973c476b2941043cb0ac53 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Mon, 18 May 2026 15:02:26 -0700 Subject: [PATCH 04/12] Cleanup --- src/coreclr/vm/wasm/writebarriers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/wasm/writebarriers.cpp b/src/coreclr/vm/wasm/writebarriers.cpp index 2dcbfaf3651709..2aab47462c5c72 100644 --- a/src/coreclr/vm/wasm/writebarriers.cpp +++ b/src/coreclr/vm/wasm/writebarriers.cpp @@ -3,7 +3,7 @@ // #include -#include "..\gchelpers.inl" +#include "../gchelpers.inl" #include "gcheaputilities.h" #define ASM_HELPER_2(rettype, funcname, a1, a2) \ From 003a92cddfe040b1a6e58ad8ed97190fb1645ae9 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 09:03:38 -0700 Subject: [PATCH 05/12] Address PR feedback --- src/coreclr/inc/jithelpers.h | 5 ++++ src/coreclr/jit/codegenwasm.cpp | 4 +-- src/coreclr/vm/wasm/writebarriers.cpp | 42 +++++++++++++++------------ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index a6e5b985c0ae3e..e74a814669dffd 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -170,7 +170,12 @@ DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF, RhpAssignRef, METHOD__NIL) DYNAMICJITHELPER(CORINFO_HELP_CHECKED_ASSIGN_REF, RhpCheckedAssignRef,METHOD__NIL) +#ifdef !TARGET_WASM DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_BYREF, RhpByRefAssignRef,METHOD__NIL) +#else + JITHELPER(CORINFO_HELP_ASSIGN_BYREF, NULL, METHOD__NIL) +#endif // !TARGET_WASM + DYNAMICJITHELPER(CORINFO_HELP_BULK_WRITEBARRIER, NULL, METHOD__BUFFER__MEMCOPYGC) // Accessing fields diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index db26846e35e0b7..4b7584ba593c4c 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2663,9 +2663,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, // RhpCheckedAssignRef HELPER_SIG(CORINFO_HELP_CHECKED_ASSIGN_REF, UNMANAGED, CORINFO_WASM_TYPE_VOID /* retval */, CORINFO_WASM_TYPE_I, CORINFO_WASM_TYPE_I); - // RhpByRefAssignRef - HELPER_SIG(CORINFO_HELP_ASSIGN_BYREF, UNMANAGED, CORINFO_WASM_TYPE_VOID /* retval */, CORINFO_WASM_TYPE_I, - CORINFO_WASM_TYPE_I); + // RhpByRefAssignRef: Not implemented on Wasm, omitted // RhBulkMoveWithWriteBarrier HELPER_SIG(CORINFO_HELP_BULK_WRITEBARRIER, UNMANAGED, CORINFO_WASM_TYPE_VOID /* retval */, CORINFO_WASM_TYPE_I, CORINFO_WASM_TYPE_I, CORINFO_WASM_TYPE_I); diff --git a/src/coreclr/vm/wasm/writebarriers.cpp b/src/coreclr/vm/wasm/writebarriers.cpp index 2aab47462c5c72..e4d6c128dac1a5 100644 --- a/src/coreclr/vm/wasm/writebarriers.cpp +++ b/src/coreclr/vm/wasm/writebarriers.cpp @@ -6,8 +6,27 @@ #include "../gchelpers.inl" #include "gcheaputilities.h" +#ifdef FEATURE_MULTITHREADING +#error The current assembly implementation of write barriers assumes single-threaded Wasm +#endif + +#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES +#error The current assembly implementation of write barriers does not implement card bundles +#endif + +// To simplify integration with the rest of the codebase and avoid complicating the build +// system, Wasm write barriers are implemented using inline assembly inside C functions, below. +// These write barriers use the Wasm-specific FCDECL2_RAW for a native calling convention instead +// of a managed calling convention (to omit the sp and pep parameters). This reduces code size +// considerably and simplifies the JIT. In order to make it safe to call these write barriers from +// inside of a managed function without manually updating the __stack_pointer global, the barriers +// *must* be implemented without use of the linear memory stack, and the best way to guarantee that +// is to implement the barriers by hand in assembly. + #define ASM_HELPER_2(rettype, funcname, a1, a2) \ EXTERN_C rettype __attribute__((naked)) F_CALL_CONV funcname(a1, a2) + +// Helper to make relevant GC globals and constants visible inside of inline assembly #define GC_ASM(text) \ asm(text \ :: [g_lowest_address] "i" (&g_lowest_address), \ @@ -18,8 +37,8 @@ [card_byte_shift] "i" (card_byte_shift) \ ) -EXTERN_C FCDECL2_RAW(VOID, JIT_WriteBarrier, Object **dst, Object *ref); -ASM_HELPER_2(VOID, JIT_WriteBarrier, Object **dst, Object *ref) +EXTERN_C FCDECL2_RAW(VOID, RhpAssignRef, Object **dst, Object *ref); +ASM_HELPER_2(VOID, RhpAssignRef, Object **dst, Object *ref) { GC_ASM( /* *dst = ref */ @@ -53,8 +72,8 @@ ASM_HELPER_2(VOID, JIT_WriteBarrier, Object **dst, Object *ref) ); } -EXTERN_C FCDECL2_RAW(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref); -ASM_HELPER_2(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref) +EXTERN_C FCDECL2_RAW(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); +ASM_HELPER_2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref) { GC_ASM( /* *dst = ref */ @@ -96,18 +115,3 @@ ASM_HELPER_2(VOID, JIT_CheckedWriteBarrier, Object **dst, Object *ref) "return\n" ); } - -EXTERN_C FCDECL2_RAW(VOID, RhpAssignRef, Object **dst, Object *ref); -ASM_HELPER_2(VOID, RhpAssignRef, Object **dst, Object *ref) -__attribute__((alias("JIT_WriteBarrier"))); - -EXTERN_C FCDECL2_RAW(VOID, RhpCheckedAssignRef, Object **dst, Object *ref); -ASM_HELPER_2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref) -__attribute__((alias("JIT_CheckedWriteBarrier"))); - -EXTERN_C FCDECL2_RAW(VOID, RhpByRefAssignRef, Object **dst, Object **ref); -ASM_HELPER_2(VOID, RhpByRefAssignRef, Object **dst, Object **ref) -{ - GC_ASM("unreachable" - ); -} From 5a233faea2efbe161b8ac2d3d5d74a135beffe62 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 09:08:46 -0700 Subject: [PATCH 06/12] Fix bad ifdef --- src/coreclr/inc/jithelpers.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index e74a814669dffd..9e449ab9f6dac0 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -170,11 +170,11 @@ DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_REF, RhpAssignRef, METHOD__NIL) DYNAMICJITHELPER(CORINFO_HELP_CHECKED_ASSIGN_REF, RhpCheckedAssignRef,METHOD__NIL) -#ifdef !TARGET_WASM - DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_BYREF, RhpByRefAssignRef,METHOD__NIL) -#else +#ifdef TARGET_WASM JITHELPER(CORINFO_HELP_ASSIGN_BYREF, NULL, METHOD__NIL) -#endif // !TARGET_WASM +#else + DYNAMICJITHELPER(CORINFO_HELP_ASSIGN_BYREF, RhpByRefAssignRef,METHOD__NIL) +#endif // TARGET_WASM DYNAMICJITHELPER(CORINFO_HELP_BULK_WRITEBARRIER, NULL, METHOD__BUFFER__MEMCOPYGC) From 87c493ebb02deac1b4701c99c2d10585a89e41c5 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 09:34:41 -0700 Subject: [PATCH 07/12] Fix lack of card table indirection --- src/coreclr/vm/wasm/writebarriers.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/coreclr/vm/wasm/writebarriers.cpp b/src/coreclr/vm/wasm/writebarriers.cpp index e4d6c128dac1a5..932f9c47198ed6 100644 --- a/src/coreclr/vm/wasm/writebarriers.cpp +++ b/src/coreclr/vm/wasm/writebarriers.cpp @@ -54,20 +54,23 @@ ASM_HELPER_2(VOID, RhpAssignRef, Object **dst, Object *ref) "i32.ge_u\n" "i32.or\n" "if\n return\n end_if\n" - /* dst = dst >> card_byte_shift */ + /* dst = &g_card_table[(dst >> card_byte_shift)] */ "local.get 0\n" "i32.const %[card_byte_shift]\n" "i32.shr_u\n" + "i32.const 0\n" + "i32.load %[g_card_table]\n" + "i32.add\n" "local.tee 0\n" - /* if (g_card_table[dst] == 255) return */ - "i32.load8_u %[g_card_table]\n" + /* if (*dst == 255) return */ + "i32.load8_u 0\n" "i32.const 255\n" "i32.eq\n" "if\n return\n end_if\n" - /* g_card_table[dst] = 255 */ + /* *dst = 255 */ "local.get 0\n" "i32.const 255\n" - "i32.store8 %[g_card_table]\n" + "i32.store8 0\n" "return\n" ); } @@ -98,20 +101,23 @@ ASM_HELPER_2(VOID, RhpCheckedAssignRef, Object **dst, Object *ref) "i32.ge_u\n" "i32.or\n" "if\n return\n end_if\n" - /* dst = dst >> card_byte_shift */ + /* dst = &g_card_table[(dst >> card_byte_shift)] */ "local.get 0\n" "i32.const %[card_byte_shift]\n" "i32.shr_u\n" + "i32.const 0\n" + "i32.load %[g_card_table]\n" + "i32.add\n" "local.tee 0\n" - /* if (g_card_table[dst] == 255) return */ - "i32.load8_u %[g_card_table]\n" + /* if (*dst == 255) return */ + "i32.load8_u 0\n" "i32.const 255\n" "i32.eq\n" "if\n return\n end_if\n" - /* g_card_table[dst] = 255 */ + /* *dst = 255 */ "local.get 0\n" "i32.const 255\n" - "i32.store8 %[g_card_table]\n" + "i32.store8 0\n" "return\n" ); } From 9d6aa882fb188e4fc9b55a3715752443258271ec Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 09:38:37 -0700 Subject: [PATCH 08/12] Move wasm writebarriers.cpp from src/coreclr/vm/wasm to src/coreclr/runtime/wasm --- src/coreclr/{vm => runtime}/wasm/writebarriers.cpp | 0 src/coreclr/vm/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/coreclr/{vm => runtime}/wasm/writebarriers.cpp (100%) diff --git a/src/coreclr/vm/wasm/writebarriers.cpp b/src/coreclr/runtime/wasm/writebarriers.cpp similarity index 100% rename from src/coreclr/vm/wasm/writebarriers.cpp rename to src/coreclr/runtime/wasm/writebarriers.cpp diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 805c844d09dd68..3c71f3ab2e7cd5 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -656,7 +656,7 @@ if (FEATURE_PORTABLE_HELPERS) if(CLR_CMAKE_TARGET_ARCH_WASM) list(APPEND VM_SOURCES_WKS - ${ARCH_SOURCES_DIR}/writebarriers.cpp + ${PORTABLE_SOURCES_DIR}/../wasm/writebarriers.cpp ) else() list(APPEND VM_SOURCES_WKS From a8d9caf2bc1e62cb72207a1a63d55b2fe1781131 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 09:46:34 -0700 Subject: [PATCH 09/12] Cleanup comments --- src/coreclr/runtime/arm64/WriteBarriers.S | 6 +++--- src/coreclr/runtime/arm64/WriteBarriers.asm | 6 +++--- src/coreclr/runtime/loongarch64/WriteBarriers.S | 6 +++--- src/coreclr/runtime/riscv64/WriteBarriers.S | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/coreclr/runtime/arm64/WriteBarriers.S b/src/coreclr/runtime/arm64/WriteBarriers.S index 8087b290f00272..74ab04fad7fa47 100644 --- a/src/coreclr/runtime/arm64/WriteBarriers.S +++ b/src/coreclr/runtime/arm64/WriteBarriers.S @@ -187,7 +187,7 @@ // Exit label .endm -// void JIT_ByRefWriteBarrier +// void RhpByRefAssignRef // On entry: // x13 : the source address (points to object reference to write) // x14 : the destination address (object reference written here) @@ -212,7 +212,7 @@ LEAF_ENTRY RhpByRefAssignRefArm64, _TEXT LEAF_END RhpByRefAssignRefArm64, _TEXT -// JIT_CheckedWriteBarrier(Object** dst, Object* src) +// RhpCheckedAssignRef(Object** dst, Object* src) // // Write barrier for writes to objects that may reside // on the managed heap. @@ -250,7 +250,7 @@ LOCAL_LABEL(NotInHeap): ret LEAF_END RhpCheckedAssignRefArm64, _TEXT -// JIT_WriteBarrier(Object** dst, Object* src) +// RhpAssignRef(Object** dst, Object* src) // // Write barrier for writes to objects that are known to // reside on the managed heap. diff --git a/src/coreclr/runtime/arm64/WriteBarriers.asm b/src/coreclr/runtime/arm64/WriteBarriers.asm index 10fb789fa37fc1..0d7d39db9da98f 100644 --- a/src/coreclr/runtime/arm64/WriteBarriers.asm +++ b/src/coreclr/runtime/arm64/WriteBarriers.asm @@ -189,7 +189,7 @@ INVALIDGCVALUE EQU 0xCCCCCCCD ;; Exit label MEND -;; void JIT_ByRefWriteBarrier +;; void RhpByRefAssignRef ;; On entry: ;; x13 : the source address (points to object reference to write) ;; x14 : the destination address (object reference written here) @@ -215,7 +215,7 @@ INVALIDGCVALUE EQU 0xCCCCCCCD LEAF_END RhpByRefAssignRefArm64 -;; JIT_CheckedWriteBarrier(Object** dst, Object* src) +;; RhpCheckedAssignRef(Object** dst, Object* src) ;; ;; Write barrier for writes to objects that may reside ;; on the managed heap. @@ -244,7 +244,7 @@ NotInHeap LEAF_END RhpCheckedAssignRefArm64 -;; JIT_WriteBarrier(Object** dst, Object* src) +;; RhpAssignRef(Object** dst, Object* src) ;; ;; Write barrier for writes to objects that are known to ;; reside on the managed heap. diff --git a/src/coreclr/runtime/loongarch64/WriteBarriers.S b/src/coreclr/runtime/loongarch64/WriteBarriers.S index 20888056b04ea8..b7ffa658921c8c 100644 --- a/src/coreclr/runtime/loongarch64/WriteBarriers.S +++ b/src/coreclr/runtime/loongarch64/WriteBarriers.S @@ -177,7 +177,7 @@ // Exit label .endm -// void JIT_ByRefWriteBarrier +// void RhpByRefAssignRef // On entry: // t8 : the source address (points to object reference to write) // t6 : the destination address (object reference written here) @@ -203,7 +203,7 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT LEAF_END RhpByRefAssignRef, _TEXT -// JIT_CheckedWriteBarrier(Object** dst, Object* src) +// RhpCheckedAssignRef(Object** dst, Object* src) // // Write barrier for writes to objects that may reside // on the managed heap. @@ -233,7 +233,7 @@ LOCAL_LABEL(NotInHeap): LEAF_END RhpCheckedAssignRef, _TEXT -// JIT_WriteBarrier(Object** dst, Object* src) +// RhpAssignRef(Object** dst, Object* src) // // Write barrier for writes to objects that are known to // reside on the managed heap. diff --git a/src/coreclr/runtime/riscv64/WriteBarriers.S b/src/coreclr/runtime/riscv64/WriteBarriers.S index 469908665b3533..8be86b6801333c 100644 --- a/src/coreclr/runtime/riscv64/WriteBarriers.S +++ b/src/coreclr/runtime/riscv64/WriteBarriers.S @@ -187,7 +187,7 @@ // Exit label .endm -// void JIT_ByRefWriteBarrier +// void RhpByRefAssignRef // On entry: // t5 : the source address (points to object reference to write) // t3 : the destination address (object reference written here) @@ -213,7 +213,7 @@ LEAF_ENTRY RhpByRefAssignRef, _TEXT LEAF_END RhpByRefAssignRef, _TEXT -// JIT_CheckedWriteBarrier(Object** dst, Object* src) +// RhpCheckedAssignRef(Object** dst, Object* src) // // Write barrier for writes to objects that may reside // on the managed heap. @@ -248,7 +248,7 @@ LOCAL_LABEL(NotInHeap): LEAF_END RhpCheckedAssignRef, _TEXT -// JIT_WriteBarrier(Object** dst, Object* src) +// RhpAssignRef(Object** dst, Object* src) // // Write barrier for writes to objects that are known to // reside on the managed heap. From e083d6e43fc07d688afaa0fdc3fd95b758f036b4 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 12:11:02 -0700 Subject: [PATCH 10/12] Fix storeblk codegen to not use byref write barrier since we're not implementing it on Wasm --- src/coreclr/jit/codegenwasm.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 4b7584ba593c4c..650e27db737fee 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -3219,13 +3219,12 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* blkOp) emit->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(destReg)); emit->emitIns_I(INS_I_const, EA_PTRSIZE, destOffset); emit->emitIns(INS_I_add); + // Do an I_load here instead of I_const + I_add because we're using the (Object **, Object *) write barrier, + // not the (Object **, Object **) BYREF write barrier used on other architectures. emit->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(srcReg)); - emit->emitIns_I(INS_I_const, EA_PTRSIZE, srcOffset); - emit->emitIns(INS_I_add); - // NOTE: This helper's signature omits SP/PEP so all we need on the stack is dst and src. - // TODO-WASM-CQ: add a version of CORINFO_HELP_ASSIGN_BYREF that returns the updated dest/src - // pointers as a multi-value tuple and use it here. - genEmitHelperCall(CORINFO_HELP_ASSIGN_BYREF, 0, EA_PTRSIZE); + emit->emitIns_I(INS_I_load, EA_PTRSIZE, srcOffset); + // NOTE: This helper's signature omits SP/PEP so all we need on the stack is dst and ref. + genEmitHelperCall(CORINFO_HELP_CHECKED_ASSIGN_REF, 0, EA_PTRSIZE); gcPtrCount--; } ++i; From 250e5577fcf196efcf280306dd098e9b499827db Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Wed, 20 May 2026 12:35:40 -0700 Subject: [PATCH 11/12] Fix include paths --- src/coreclr/runtime/wasm/writebarriers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/runtime/wasm/writebarriers.cpp b/src/coreclr/runtime/wasm/writebarriers.cpp index 932f9c47198ed6..2816ac2196c905 100644 --- a/src/coreclr/runtime/wasm/writebarriers.cpp +++ b/src/coreclr/runtime/wasm/writebarriers.cpp @@ -3,8 +3,8 @@ // #include -#include "../gchelpers.inl" -#include "gcheaputilities.h" +#include "../../vm/gchelpers.inl" +#include "../../vm/gcheaputilities.h" #ifdef FEATURE_MULTITHREADING #error The current assembly implementation of write barriers assumes single-threaded Wasm From dabdb0696e8505960b11f59909f911537fbab31c Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 21 May 2026 16:55:19 -0700 Subject: [PATCH 12/12] Address PR feedback --- src/coreclr/runtime/wasm/writebarriers.cpp | 7 +++++-- src/coreclr/vm/CMakeLists.txt | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/coreclr/runtime/wasm/writebarriers.cpp b/src/coreclr/runtime/wasm/writebarriers.cpp index 2816ac2196c905..3d4d2e0ab49eec 100644 --- a/src/coreclr/runtime/wasm/writebarriers.cpp +++ b/src/coreclr/runtime/wasm/writebarriers.cpp @@ -3,8 +3,11 @@ // #include -#include "../../vm/gchelpers.inl" -#include "../../vm/gcheaputilities.h" + +#ifndef FEATURE_NATIVEAOT +#include "gchelpers.inl" +#include "gcheaputilities.h" +#endif #ifdef FEATURE_MULTITHREADING #error The current assembly implementation of write barriers assumes single-threaded Wasm diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 3c71f3ab2e7cd5..0c101444bd7918 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -655,9 +655,6 @@ if (FEATURE_PORTABLE_HELPERS) ) if(CLR_CMAKE_TARGET_ARCH_WASM) - list(APPEND VM_SOURCES_WKS - ${PORTABLE_SOURCES_DIR}/../wasm/writebarriers.cpp - ) else() list(APPEND VM_SOURCES_WKS ${PORTABLE_SOURCES_DIR}/WriteBarriers.cpp @@ -950,6 +947,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_WASM) ${ARCH_SOURCES_DIR}/entrypoints.h ) set(VM_SOURCES_WKS_ARCH + ${RUNTIME_DIR}/${ARCH_SOURCES_DIR}/writebarriers.cpp ${ARCH_SOURCES_DIR}/calldescrworkerwasm.cpp ${ARCH_SOURCES_DIR}/profiler.cpp ${ARCH_SOURCES_DIR}/helpers.cpp