Skip to content

Commit f1ca590

Browse files
authored
[Wasm RyuJIT] Wire up GC info encoding/decoding for Wasm (#126932)
* Wire up basic gc info encoding in the wasm jit * Build gcinfo_universal_wasm and link it into the wasm version of the jit * Move gcdecode/gcencode into JIT_SOURCES * Remove the TARGET_WASM exclusion for the gc info encoder and decoder * Define a Wasm32 GcInfoEncoding * Allow building with EMIT_GENERATE_GCINFO on targets without a fixed register set like Wasm, and set EMIT_GENERATE_GCINFO on Wasm * Never generate tracked gc slots on Wasm * Update wasm regalloc to never enregister gc refs * Consume temporary regs for the operand of GT_NULLCHECK nodes to handle the case where a dead block store leaves behind an orphaned indirection that turns into a null check
1 parent 20fe189 commit f1ca590

18 files changed

Lines changed: 172 additions & 52 deletions

src/coreclr/gcinfo/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ endif()
6666
if (CLR_CMAKE_TARGET_ARCH_ARM64 OR CLR_CMAKE_TARGET_ARCH_AMD64)
6767
create_gcinfo_lib(TARGET gcinfo_universal_arm64 OS universal ARCH arm64)
6868
create_gcinfo_lib(TARGET gcinfo_unix_x64 OS unix ARCH x64)
69+
create_gcinfo_lib(TARGET gcinfo_universal_wasm OS universal ARCH wasm)
6970
if (CLR_CMAKE_BUILD_COMMUNITY_ALTJITS EQUAL 1)
7071
create_gcinfo_lib(TARGET gcinfo_unix_loongarch64 OS unix ARCH loongarch64)
7172
create_gcinfo_lib(TARGET gcinfo_unix_riscv64 OS unix ARCH riscv64)

src/coreclr/gcinfo/gcinfoencoder.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,10 +2634,8 @@ int BitStreamWriter::EncodeVarLengthSigned( SSIZE_T n, UINT32 base )
26342634
}
26352635
}
26362636

2637-
#ifndef TARGET_WASM
26382637
// Instantiate the encoder so other files can use it
26392638
template class TGcInfoEncoder<TargetGcInfoEncoding>;
2640-
#endif // !TARGET_WASM
26412639

26422640
#ifdef FEATURE_INTERPRETER
26432641
template class TGcInfoEncoder<InterpreterGcInfoEncoding>;

src/coreclr/inc/gcinfotypes.h

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,13 +909,62 @@ struct X86GcInfoEncoding {
909909
static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true;
910910
};
911911

912-
#elif defined(TARGET_WASM)
912+
#elif defined(TARGET_WASM) && !defined(TARGET_64BIT)
913913

914914
#ifndef TARGET_POINTER_SIZE
915915
#define TARGET_POINTER_SIZE 4 // equal to sizeof(void*) and the managed pointer size in bytes for this target
916916
#endif
917917

918-
#define TargetGcInfoEncoding InterpreterGcInfoEncoding
918+
#define TargetGcInfoEncoding Wasm32GcInfoEncoding
919+
920+
// TODO-WASM: Investigate normalizing stack slots to save space based on wasm stack alignment
921+
922+
struct Wasm32GcInfoEncoding {
923+
static const uint32_t NUM_NORM_CODE_OFFSETS_PER_CHUNK = (64);
924+
static const uint32_t NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2 = (6);
925+
static inline constexpr int32_t NORMALIZE_STACK_SLOT (int32_t x) { return (x); }
926+
static inline constexpr int32_t DENORMALIZE_STACK_SLOT (int32_t x) { return (x); }
927+
static inline constexpr uint32_t NORMALIZE_CODE_LENGTH (uint32_t x) { return (x); }
928+
static inline constexpr uint32_t DENORMALIZE_CODE_LENGTH (uint32_t x) { return (x); }
929+
static inline constexpr uint32_t NORMALIZE_STACK_BASE_REGISTER (uint32_t x) { return (x); }
930+
static inline constexpr uint32_t DENORMALIZE_STACK_BASE_REGISTER (uint32_t x) { return (x); }
931+
static inline constexpr uint32_t NORMALIZE_SIZE_OF_STACK_AREA (uint32_t x) { return (x); }
932+
static inline constexpr uint32_t DENORMALIZE_SIZE_OF_STACK_AREA (uint32_t x) { return (x); }
933+
static const bool CODE_OFFSETS_NEED_NORMALIZATION = false;
934+
static inline constexpr uint32_t NORMALIZE_CODE_OFFSET (uint32_t x) { return (x); }
935+
static inline constexpr uint32_t DENORMALIZE_CODE_OFFSET (uint32_t x) { return (x); }
936+
937+
static const int PSP_SYM_STACK_SLOT_ENCBASE = 6;
938+
static const int GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE = 6;
939+
static const int SECURITY_OBJECT_STACK_SLOT_ENCBASE = 6;
940+
static const int GS_COOKIE_STACK_SLOT_ENCBASE = 6;
941+
static const int CODE_LENGTH_ENCBASE = 6;
942+
static const int SIZE_OF_RETURN_KIND_IN_SLIM_HEADER = 2;
943+
static const int SIZE_OF_RETURN_KIND_IN_FAT_HEADER = 2;
944+
static const int STACK_BASE_REGISTER_ENCBASE = 3;
945+
static const int SIZE_OF_STACK_AREA_ENCBASE = 6;
946+
static const int SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE = 3;
947+
static const int REVERSE_PINVOKE_FRAME_ENCBASE = 6;
948+
static const int NUM_REGISTERS_ENCBASE = 3;
949+
static const int NUM_STACK_SLOTS_ENCBASE = 5;
950+
static const int NUM_UNTRACKED_SLOTS_ENCBASE = 5;
951+
static const int NORM_PROLOG_SIZE_ENCBASE = 4;
952+
static const int NORM_EPILOG_SIZE_ENCBASE = 3;
953+
static const int NORM_CODE_OFFSET_DELTA_ENCBASE = 3;
954+
static const int INTERRUPTIBLE_RANGE_DELTA1_ENCBASE = 5;
955+
static const int INTERRUPTIBLE_RANGE_DELTA2_ENCBASE = 5;
956+
static const int REGISTER_ENCBASE = 3;
957+
static const int REGISTER_DELTA_ENCBASE = REGISTER_ENCBASE;
958+
static const int STACK_SLOT_ENCBASE = 6;
959+
static const int STACK_SLOT_DELTA_ENCBASE = 4;
960+
static const int NUM_SAFE_POINTS_ENCBASE = 4;
961+
static const int NUM_INTERRUPTIBLE_RANGES_ENCBASE = 1;
962+
static const int NUM_EH_CLAUSES_ENCBASE = 2;
963+
static const int POINTER_SIZE_ENCBASE = 3;
964+
static const int LIVESTATE_RLE_RUN_ENCBASE = 2;
965+
static const int LIVESTATE_RLE_SKIP_ENCBASE = 4;
966+
static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = false;
967+
};
919968

920969
#else // No target defined
921970

src/coreclr/jit/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function(create_standalone_jit)
2323

2424
if(TARGETDETAILS_OS STREQUAL "unix_osx" OR TARGETDETAILS_OS STREQUAL "unix_anyos")
2525
set(JIT_ARCH_LINK_LIBRARIES gcinfo_unix_${TARGETDETAILS_ARCH})
26-
elseif(NOT ${TARGETDETAILS_ARCH} MATCHES "wasm")
26+
else()
2727
set(JIT_ARCH_LINK_LIBRARIES gcinfo_${TARGETDETAILS_OS}_${TARGETDETAILS_ARCH})
2828
endif()
2929

@@ -120,6 +120,8 @@ set( JIT_SOURCES
120120
fgstmt.cpp
121121
flowgraph.cpp
122122
forwardsub.cpp
123+
gcdecode.cpp
124+
gcencode.cpp
123125
gcinfo.cpp
124126
gentree.cpp
125127
gschecks.cpp
@@ -186,8 +188,6 @@ set ( JIT_NATIVE_TARGET_SOURCES
186188
lsra.cpp
187189
lsrabuild.cpp
188190
regMaskTPOps.cpp
189-
gcdecode.cpp
190-
gcencode.cpp
191191
unwind.cpp
192192
)
193193

src/coreclr/jit/codegencommon.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,9 @@ bool CodeGen::genIsSameLocalVar(GenTree* op1, GenTree* op2)
854854
// inline
855855
void CodeGenInterface::genUpdateRegLife(const LclVarDsc* varDsc, bool isBorn, bool isDying DEBUGARG(GenTree* tree))
856856
{
857-
#if EMIT_GENERATE_GCINFO // The regset being updated here is only needed for codegen-level GCness tracking
857+
// Targets like Wasm do not have a fixed set of registers so the regset logic in this method is unnecessary.
858+
#if EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
859+
// The regset being updated here is only needed for codegen-level GCness tracking.
858860
regMaskTP regMask = genGetRegMask(varDsc);
859861

860862
#ifdef DEBUG
@@ -884,7 +886,7 @@ void CodeGenInterface::genUpdateRegLife(const LclVarDsc* varDsc, bool isBorn, bo
884886
assert(varDsc->IsAlwaysAliveInMemory() || ((regSet.GetMaskVars() & regMask) == 0));
885887
regSet.AddMaskVars(regMask);
886888
}
887-
#endif // EMIT_GENERATE_GCINFO
889+
#endif // EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
888890
}
889891

890892
#ifndef TARGET_WASM
@@ -1032,6 +1034,7 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
10321034
bool isByRef = varDsc->TypeIs(TYP_BYREF);
10331035
bool isInReg = varDsc->lvIsInReg();
10341036
bool isInMemory = !isInReg || varDsc->IsAlwaysAliveInMemory();
1037+
#ifndef TARGET_WASM
10351038
if (isInReg)
10361039
{
10371040
// TODO-Cleanup: Move the code from compUpdateLifeVar to genUpdateRegLife that updates the
@@ -1047,7 +1050,8 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
10471050
}
10481051
codeGen->genUpdateRegLife(varDsc, false /*isBorn*/, true /*isDying*/ DEBUGARG(nullptr));
10491052
}
1050-
// Update the gcVarPtrSetCur if it is in memory.
1053+
#endif // !TARGET_WASM
1054+
// Update the gcVarPtrSetCur if it is in memory.
10511055
if (isInMemory && (isGCRef || isByRef))
10521056
{
10531057
VarSetOps::RemoveElemD(this, codeGen->gcInfo.gcVarPtrSetCur, deadVarIndex);
@@ -1070,6 +1074,7 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
10701074
bool isByRef = varDsc->TypeIs(TYP_BYREF);
10711075
if (varDsc->lvIsInReg())
10721076
{
1077+
#ifndef TARGET_WASM
10731078
// If this variable is going live in a register, it is no longer live on the stack,
10741079
// unless it is an EH/"spill at single-def" var, which always remains live on the stack.
10751080
if (!varDsc->IsAlwaysAliveInMemory())
@@ -1092,6 +1097,7 @@ void Compiler::compChangeLife(VARSET_VALARG_TP newLife)
10921097
{
10931098
codeGen->gcInfo.gcRegByrefSetCur |= regMask;
10941099
}
1100+
#endif // !TARGET_WASM
10951101
}
10961102
else if (lvaIsGCTracked(varDsc))
10971103
{
@@ -1769,7 +1775,7 @@ void CodeGen::genExitCode(BasicBlock* block)
17691775

17701776
genIPmappingAdd(IPmappingDscKind::Epilog, DebugInfo(), true);
17711777

1772-
#if EMIT_GENERATE_GCINFO && defined(DEBUG)
1778+
#if EMIT_GENERATE_GCINFO && defined(DEBUG) && !defined(TARGET_WASM)
17731779
// For returnining epilogs do some validation that the GC info looks right.
17741780
if (!block->HasFlag(BBF_HAS_JMP))
17751781
{
@@ -1791,7 +1797,7 @@ void CodeGen::genExitCode(BasicBlock* block)
17911797
}
17921798
}
17931799
}
1794-
#endif // EMIT_GENERATE_GCINFO && defined(DEBUG)
1800+
#endif // EMIT_GENERATE_GCINFO && defined(DEBUG) && !defined(TARGET_WASM)
17951801

17961802
if (m_compiler->getNeedsGSSecurityCookie())
17971803
{
@@ -7202,12 +7208,12 @@ void CodeGen::genReturn(GenTree* treeNode)
72027208
}
72037209
}
72047210

7205-
#if EMIT_GENERATE_GCINFO
7211+
#if EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
72067212
if (treeNode->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET))
72077213
{
72087214
genMarkReturnGCInfo();
72097215
}
7210-
#endif // EMIT_GENERATE_GCINFO
7216+
#endif // EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
72117217

72127218
#ifdef PROFILING_SUPPORTED
72137219

src/coreclr/jit/codegenlinear.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ void CodeGen::genCodeForBlock(BasicBlock* block)
259259
// and before first of the current block is emitted
260260
genUpdateLife(block->bbLiveIn);
261261

262-
#if EMIT_GENERATE_GCINFO
262+
#if EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
263263
// Even if liveness didn't change, we need to update the registers containing GC references.
264264
// genUpdateLife will update the registers live due to liveness changes. But what about registers that didn't
265265
// change? We cleared them out above. Maybe we should just not clear them out, but update the ones that change
@@ -353,7 +353,7 @@ void CodeGen::genCodeForBlock(BasicBlock* block)
353353
}
354354
}
355355
}
356-
#endif // EMIT_GENERATE_GCINFO
356+
#endif // EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
357357

358358
/* Start a new code output block */
359359

@@ -569,7 +569,7 @@ void CodeGen::genCodeForBlock(BasicBlock* block)
569569

570570
regSet.rsSpillChk();
571571

572-
#if EMIT_GENERATE_GCINFO
572+
#if EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
573573
// Make sure we didn't bungle pointer register tracking
574574
regMaskTP ptrRegs = gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur;
575575
regMaskTP nonVarPtrRegs = ptrRegs & ~regSet.GetMaskVars();
@@ -618,7 +618,7 @@ void CodeGen::genCodeForBlock(BasicBlock* block)
618618
}
619619

620620
noway_assert(nonVarPtrRegs == RBM_NONE);
621-
#endif // EMIT_GENERATE_GCINFO
621+
#endif // EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
622622
#endif // DEBUG
623623

624624
#if defined(DEBUG)
@@ -1601,7 +1601,7 @@ regNumber CodeGen::genConsumeReg(GenTree* tree)
16011601
// genUpdateLife() will also spill local var if marked as GTF_SPILL by calling CodeGen::genSpillVar
16021602
genUpdateLife(tree);
16031603

1604-
#if EMIT_GENERATE_GCINFO
1604+
#if EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
16051605
// there are three cases where consuming a reg means clearing the bit in the live mask
16061606
// 1. it was not produced by a local
16071607
// 2. it was produced by a local that is going dead
@@ -1659,7 +1659,7 @@ regNumber CodeGen::genConsumeReg(GenTree* tree)
16591659
{
16601660
gcInfo.gcMarkRegSetNpt(tree->gtGetRegMask());
16611661
}
1662-
#endif // EMIT_GENERATE_GCINFO
1662+
#endif // EMIT_GENERATE_GCINFO && HAS_FIXED_REGISTER_SET
16631663

16641664
genCheckConsumeNode(tree);
16651665
return tree->GetRegNum();

src/coreclr/jit/codegenwasm.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "codegen.h"
1010
#include "regallocwasm.h"
1111
#include "fgwasm.h"
12+
#include "gcinfo.h"
13+
#include "gcinfoencoder.h"
1214

1315
static const int LINEAR_MEMORY_INDEX = 0;
1416

@@ -3351,7 +3353,40 @@ void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock)
33513353

33523354
void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, unsigned prologSize, unsigned epilogSize DEBUGARG(void* code))
33533355
{
3354-
// GCInfo not captured/created by codegen.
3356+
IAllocator* allowZeroAlloc = new (m_compiler, CMK_GC) CompIAllocator(m_compiler->getAllocatorGC());
3357+
GcInfoEncoder* gcInfoEncoder = new (m_compiler, CMK_GC)
3358+
GcInfoEncoder(m_compiler->info.compCompHnd, m_compiler->info.compMethodInfo, allowZeroAlloc, NOMEM);
3359+
assert(gcInfoEncoder != nullptr);
3360+
3361+
// Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32).
3362+
gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize);
3363+
3364+
// We keep the call count for the second call to gcMakeRegPtrTable() below.
3365+
unsigned callCnt = 0;
3366+
3367+
// First we figure out the encoder ID's for the stack slots and registers.
3368+
gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS, &callCnt);
3369+
3370+
// Now we've requested all the slots we'll need; "finalize" these (make more compact data structures for them).
3371+
gcInfoEncoder->FinalizeSlotIds();
3372+
3373+
// Now we can actually use those slot ID's to declare live ranges.
3374+
gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt);
3375+
3376+
if (m_compiler->opts.IsReversePInvoke())
3377+
{
3378+
unsigned reversePInvokeFrameVarNumber = m_compiler->lvaReversePInvokeFrameVar;
3379+
assert(reversePInvokeFrameVarNumber != BAD_VAR_NUM);
3380+
const LclVarDsc* reversePInvokeFrameVar = m_compiler->lvaGetDesc(reversePInvokeFrameVarNumber);
3381+
gcInfoEncoder->SetReversePInvokeFrameSlot(reversePInvokeFrameVar->GetStackOffset());
3382+
}
3383+
3384+
gcInfoEncoder->Build();
3385+
3386+
// GC Encoder automatically puts the GC info in the right spot using ICorJitInfo::allocGCInfo(size_t)
3387+
// let's save the values anyway for debugging purposes
3388+
m_compiler->compInfoBlkAddr = gcInfoEncoder->Emit();
3389+
m_compiler->compInfoBlkSize = gcInfoEncoder->GetEncodedGCInfoSize();
33553390
}
33563391

33573392
//---------------------------------------------------------------------

src/coreclr/jit/compiler.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10738,6 +10738,10 @@ void Compiler::EnregisterStats::RecordLocal(const LclVarDsc* varDsc)
1073810738
m_simdUserForcesDep++;
1073910739
break;
1074010740

10741+
case DoNotEnregisterReason::WasmGCVisibility:
10742+
m_wasmGcVisibility++;
10743+
break;
10744+
1074110745
default:
1074210746
unreached();
1074310747
break;
@@ -10866,6 +10870,7 @@ void Compiler::EnregisterStats::Dump(FILE* fout) const
1086610870
PRINT_STATS(m_swizzleArg, notEnreg);
1086710871
PRINT_STATS(m_blockOpRet, notEnreg);
1086810872
PRINT_STATS(m_returnSpCheck, notEnreg);
10873+
PRINT_STATS(m_wasmGcVisibility, notEnreg);
1086910874
PRINT_STATS(m_callSpCheck, notEnreg);
1087010875
PRINT_STATS(m_simdUserForcesDep, notEnreg);
1087110876

src/coreclr/jit/compiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ enum class DoNotEnregisterReason
488488
CallSpCheck, // the local is used to do SP check on every call
489489
SimdUserForcesDep, // a promoted struct was used by a SIMD/HWI node; it must be dependently promoted
490490
HiddenBufferStructArg, // the argument is a hidden return buffer passed to a method.
491+
WasmGCVisibility,
491492
};
492493

493494
enum class AddressExposedReason
@@ -11761,6 +11762,7 @@ class Compiler
1176111762
unsigned m_liveInOutHndlr;
1176211763
unsigned m_depField;
1176311764
unsigned m_noRegVars;
11765+
unsigned m_wasmGcVisibility;
1176411766
#ifdef JIT32_GCENCODER
1176511767
unsigned m_PinningRef;
1176611768
#endif // JIT32_GCENCODER

0 commit comments

Comments
 (0)