Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4c8682d
PR feedback from SingleAccretion
kg Feb 23, 2026
76e30d0
Address copilot feedback
kg Feb 25, 2026
90f2757
Repair merge damage
kg Feb 25, 2026
8335ca1
jit-format
kg Feb 25, 2026
7029698
loadStructWithRefs compiles now
kg Feb 25, 2026
3c5e30b
When doing a cpobj to the stack just generate a memcpy instead
kg Feb 25, 2026
98c92df
jit-format
kg Feb 25, 2026
18e6520
Address copilot feedback
kg Feb 25, 2026
d7dc84d
Block attempts to mark a struct as multiply used since we can't alloc…
kg Feb 26, 2026
d1a2a40
Checkpoint cpobj rewrite
kg Feb 26, 2026
82211b9
Codegen fixes
kg Feb 26, 2026
46e99ee
Implement isContainableMemoryOp
kg Feb 26, 2026
2cf1f15
Fix emitattr
kg Feb 26, 2026
81b74b7
Fix local stores having their address contained
kg Feb 26, 2026
9b164d7
Assert that storeind's address isn't contained
kg Feb 26, 2026
960e033
Put comment back
kg Feb 26, 2026
38b85f3
jit-format
kg Feb 26, 2026
5a93cce
Centralize isContainableMemoryOp
kg Feb 27, 2026
bd8f88b
Use IsInvariantInRange
kg Feb 27, 2026
036958d
Remove assert
kg Feb 27, 2026
03ae6bb
Apply suggestions from code review
kg Feb 27, 2026
08c5ce6
Address PR feedback
kg Feb 27, 2026
1a46a5d
Mark store_blk as generating null checks
kg Feb 27, 2026
427e33d
Do IsInvariantInRange as an assert instead of the check
kg Feb 27, 2026
5f8983a
Address PR feedback by adding a GetLowering accessor
kg Feb 27, 2026
49ea954
Remove unnecessary ifdef
kg Feb 27, 2026
01b86f0
Remove commented dead code
kg Feb 27, 2026
1eeae1c
Address PR feedback
kg Feb 27, 2026
6d74664
jit-format
kg Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 98 additions & 2 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
static const int LINEAR_MEMORY_INDEX = 0;

#ifdef TARGET_64BIT
static const instruction INS_I_load = INS_i64_load;
static const instruction INS_I_store = INS_i64_store;
static const instruction INS_I_const = INS_i64_const;
static const instruction INS_I_add = INS_i64_add;
static const instruction INS_I_mul = INS_i64_mul;
Expand All @@ -21,6 +23,8 @@ static const instruction INS_I_le_u = INS_i64_le_u;
static const instruction INS_I_ge_u = INS_i64_ge_u;
static const instruction INS_I_gt_u = INS_i64_gt_u;
#else // !TARGET_64BIT
static const instruction INS_I_load = INS_i32_load;
static const instruction INS_I_store = INS_i32_store;
static const instruction INS_I_const = INS_i32_const;
static const instruction INS_I_add = INS_i32_add;
static const instruction INS_I_mul = INS_i32_mul;
Expand Down Expand Up @@ -427,7 +431,9 @@ void CodeGen::WasmProduceReg(GenTree* node)
//
// If the operand is a candidate, we use that candidate's current register.
// Otherwise it must have been allocated into a temporary register initialized
// in 'WasmProduceReg'.
// in 'WasmProduceReg'. To do this, set the LIR::Flags::MultiplyUsed flag during
// lowering or other pre-regalloc phases, and ensure that regalloc is updated to
// call CollectReferences on the node(s) that need to be used multiple times.
//
// Arguments:
// operand - The operand node
Expand Down Expand Up @@ -2420,9 +2426,99 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* blkOp)
}
}

//------------------------------------------------------------------------
// genCodeForCpObj: Produce code for a GT_STORE_BLK node that represents a cpobj operation.
//
// Arguments:
// cpObjNode - the node
//
void CodeGen::genCodeForCpObj(GenTreeBlk* cpObjNode)
Comment thread
kg marked this conversation as resolved.
{
NYI_WASM("genCodeForCpObj");
GenTree* dstAddr = cpObjNode->Addr();
GenTree* source = cpObjNode->Data();
var_types srcAddrType = TYP_BYREF;

assert(source->isContained());
if (source->OperIs(GT_IND))
{
Comment thread
kg marked this conversation as resolved.
source = source->gtGetOp1();
assert(!source->isContained());
srcAddrType = source->TypeGet();
}

noway_assert(source->IsLocal());
noway_assert(dstAddr->IsLocal());
Comment thread
kg marked this conversation as resolved.
Outdated

// If the destination is on the stack we don't need the write barrier.
bool dstOnStack = cpObjNode->IsAddressNotOnHeap(m_compiler);

#ifdef DEBUG
assert(!dstAddr->isContained());

// This GenTree node has data about GC pointers, this means we're dealing
// with CpObj.
assert(cpObjNode->GetLayout()->HasGCPtr());
#endif // DEBUG

genConsumeOperands(cpObjNode);

ClassLayout* layout = cpObjNode->GetLayout();
unsigned slots = layout->GetSlotCount();

regNumber srcReg = GetMultiUseOperandReg(source);
regNumber dstReg = GetMultiUseOperandReg(dstAddr);

if (cpObjNode->IsVolatile())
{
// TODO-WASM: Memory barrier
}

emitter* emit = GetEmitter();

emitAttr attrSrcAddr = emitActualTypeSize(srcAddrType);
emitAttr attrDstAddr = emitActualTypeSize(dstAddr->TypeGet());

unsigned gcPtrCount = cpObjNode->GetLayout()->GetGCPtrCount();

unsigned i = 0, offset = 0;
while (i < slots)
{
// Copy the pointer-sized non-gc-pointer slots one at a time (and GC pointer slots if the destination is stack)
// using regular I-sized load/store pairs.
Comment thread
kg marked this conversation as resolved.
Outdated
if (dstOnStack || !layout->IsGCPtr(i))
Comment thread
kg marked this conversation as resolved.
Outdated
{
// Do a pointer-sized load+store pair at the appropriate offset relative to dest and source
emit->emitIns_I(INS_local_get, attrDstAddr, WasmRegToIndex(dstReg));
emit->emitIns_I(INS_local_get, attrSrcAddr, WasmRegToIndex(srcReg));
emit->emitIns_I(INS_I_load, EA_PTRSIZE, offset);
emit->emitIns_I(INS_I_store, EA_PTRSIZE, offset);
}
else
{
// Compute the actual dest/src of the slot being copied to pass to the helper.
emit->emitIns_I(INS_local_get, attrDstAddr, WasmRegToIndex(dstReg));
emit->emitIns_I(INS_I_const, attrDstAddr, offset);
emit->emitIns(INS_I_add);
emit->emitIns_I(INS_local_get, attrSrcAddr, WasmRegToIndex(srcReg));
emit->emitIns_I(INS_I_const, attrSrcAddr, offset);
emit->emitIns(INS_I_add);
// Call the byref assign helper. On other targets this updates the dst/src regs but here it won't,
// so we have to do the local.get+i32.const+i32.add dance every time.
genEmitHelperCall(CORINFO_HELP_ASSIGN_BYREF, 0, EA_PTRSIZE);
gcPtrCount--;
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Outdated
}
++i;
offset += TARGET_POINTER_SIZE;
}

assert(dstOnStack || (gcPtrCount == 0));
Comment thread
kg marked this conversation as resolved.
Outdated

if (cpObjNode->IsVolatile())
{
// TODO-WASM: Memory barrier
}

WasmProduceReg(cpObjNode);
Comment thread
kg marked this conversation as resolved.
Outdated
}

//------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2566,6 +2566,10 @@ class Compiler
friend class ReplaceVisitor;
friend class FlowGraphNaturalLoop;

#ifdef TARGET_WASM
friend class WasmRegAlloc; // For m_pLowering
Comment thread
kg marked this conversation as resolved.
Outdated
#endif

#ifdef FEATURE_HW_INTRINSICS
friend struct GenTreeHWIntrinsic;
friend struct HWIntrinsicInfo;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12844,7 +12844,7 @@ void Compiler::gtDispTree(GenTree* tree,

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
printf(" (memory.%s)", tree->OperIsCopyBlkOp() ? "copy" : "fill");
break;
#endif

Expand Down
14 changes: 14 additions & 0 deletions src/coreclr/jit/lowerwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ void Lowering::LowerBlockStore(GenTreeBlk* blkNode)
}
Comment thread
kg marked this conversation as resolved.

blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindCpObjUnroll;
dstAddr->gtLIRFlags |= LIR::Flags::MultiplyUsed;
if (src->OperIs(GT_IND))
src->gtGetOp1()->gtLIRFlags |= LIR::Flags::MultiplyUsed;
else
src->gtLIRFlags |= LIR::Flags::MultiplyUsed;
}
else
{
Expand Down Expand Up @@ -512,6 +517,15 @@ void Lowering::AfterLowerBlock()
// instead be ifdef-ed out for WASM.
m_anyChanges = true;

if (node->IsInvariant())
{
JITDUMP("Stackifier moving invariant node [%06u] after [%06u]\n", Compiler::dspTreeID(node),
Compiler::dspTreeID(prev));
m_lower->BlockRange().Remove(node);
m_lower->BlockRange().InsertAfter(prev, node);
break;
}

JITDUMP("node==[%06u] prev==[%06u]\n", Compiler::dspTreeID(node), Compiler::dspTreeID(prev));
NYI_WASM("IR not in a stackified form");
}
Expand Down
29 changes: 29 additions & 0 deletions src/coreclr/jit/regallocwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "regallocwasm.h"

#include "lower.h" // for LowerRange()

RegAllocInterface* GetRegisterAllocator(Compiler* compiler)
{
return new (compiler->getAllocator(CMK_LSRA)) WasmRegAlloc(compiler);
Expand Down Expand Up @@ -330,6 +332,10 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node)
CollectReferencesForBinop(node->AsOp());
break;

case GT_STORE_BLK:
CollectReferencesForBlockStore(node->AsBlk());
break;

default:
assert(!node->OperIsLocalStore());
break;
Expand Down Expand Up @@ -417,6 +423,21 @@ void WasmRegAlloc::CollectReferencesForBinop(GenTreeOp* binopNode)
ConsumeTemporaryRegForOperand(binopNode->gtGetOp1() DEBUGARG("binop overflow check"));
}

// CollectReferencesForBlockStore: Collect virtual register references for a block store.
//
// Arguments:
// node - The GT_STORE_BLK node
//
void WasmRegAlloc::CollectReferencesForBlockStore(GenTreeBlk* node)
{
GenTree* src = node->Data();
if (src->OperIs(GT_IND))
Comment thread
kg marked this conversation as resolved.
src = src->gtGetOp1();

ConsumeTemporaryRegForOperand(src DEBUGARG("block store source"));
ConsumeTemporaryRegForOperand(node->Addr() DEBUGARG("block store destination"));
}

//------------------------------------------------------------------------
// CollectReferencesForLclVar: Collect virtual register references for a LCL_VAR.
//
Expand Down Expand Up @@ -476,6 +497,9 @@ void WasmRegAlloc::RewriteLocalStackStore(GenTreeLclVarCommon* lclNode)
CurrentRange().InsertAfter(lclNode, store);
CurrentRange().Remove(lclNode);
CurrentRange().InsertBefore(insertionPoint, lclNode);

auto tempRange = LIR::ReadOnlyRange(store, store);
m_compiler->m_pLowering->LowerRange(m_currentBlock, tempRange);
Comment thread
kg marked this conversation as resolved.
Outdated
}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -539,6 +563,8 @@ void WasmRegAlloc::RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node)
// Note how due to the fact we're processing nodes in stack order,
// we don't need to maintain free/busy sets, only a simple stack.
regNumber reg = AllocateTemporaryRegister(genActualType(node));
// If the node already has a regnum, trying to assign it a second one is no good.
assert(node->GetRegNum() == REG_NA);
Comment thread
kg marked this conversation as resolved.
Outdated
node->SetRegNum(reg);
}

Expand All @@ -561,6 +587,7 @@ void WasmRegAlloc::ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const
}

regNumber reg = ReleaseTemporaryRegister(genActualType(operand));
// If this assert fails you likely called ConsumeTemporaryRegForOperand on your operands in the wrong order.
assert(reg == operand->GetRegNum());
Comment thread
kg marked this conversation as resolved.
Outdated
CollectReference(operand);

Expand Down Expand Up @@ -605,6 +632,8 @@ void WasmRegAlloc::ResolveReferences()
{
TemporaryRegStack& temporaryRegs = m_temporaryRegs[static_cast<unsigned>(type)];
TemporaryRegBank& allocatedTemporaryRegs = temporaryRegMap[static_cast<unsigned>(type)];
// If temporaryRegs.Count != 0 that means CollectReferences failed to CollectReference one or more multiply-used
// nodes.
assert(temporaryRegs.Count == 0);
Comment thread
kg marked this conversation as resolved.
Outdated

allocatedTemporaryRegs.Count = temporaryRegs.MaxCount;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/regallocwasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class WasmRegAlloc : public RegAllocInterface
void CollectReferencesForCast(GenTreeOp* castNode);
void CollectReferencesForBinop(GenTreeOp* binOpNode);
void CollectReferencesForLclVar(GenTreeLclVar* lclVar);
void CollectReferencesForBlockStore(GenTreeBlk* node);
Comment thread
kg marked this conversation as resolved.
Outdated
void RewriteLocalStackStore(GenTreeLclVarCommon* node);
void CollectReference(GenTree* node);
void RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node);
Expand Down
Loading