diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index a23105ca5b1a1b..f37a6d866a910a 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -10,6 +10,8 @@ #include "regallocwasm.h" #include "fgwasm.h" +static const int LINEAR_MEMORY_INDEX = 0; + #ifdef TARGET_64BIT static const instruction INS_I_const = INS_i64_const; static const instruction INS_I_add = INS_i64_add; @@ -599,6 +601,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genLeaInstruction(treeNode->AsAddrMode()); break; + case GT_STORE_BLK: + genCodeForStoreBlk(treeNode->AsBlk()); + break; + case GT_MEMORYBARRIER: // No-op for single-threaded wasm. assert(!WASM_THREAD_SUPPORT); @@ -1964,6 +1970,68 @@ void CodeGen::genCompareFloat(GenTreeOp* treeNode) WasmProduceReg(treeNode); } +//------------------------------------------------------------------------ +// genCodeForStoreBlk: Produce code for a GT_STORE_BLK node. +// +// Arguments: +// blkOp - the node +// +void CodeGen::genCodeForStoreBlk(GenTreeBlk* blkOp) +{ + assert(blkOp->OperIs(GT_STORE_BLK)); + + bool isCopyBlk = blkOp->OperIsCopyBlkOp(); + + switch (blkOp->gtBlkOpKind) + { + case GenTreeBlk::BlkOpKindCpObjUnroll: + genCodeForCpObj(blkOp->AsBlk()); + break; + + case GenTreeBlk::BlkOpKindLoop: + assert(!isCopyBlk); + genCodeForInitBlkLoop(blkOp); + break; + + case GenTreeBlk::BlkOpKindNativeOpcode: + genConsumeOperands(blkOp); + // Emit the size constant expected by the memory.copy and memory.fill opcodes + GetEmitter()->emitIns_I(INS_i32_const, EA_4BYTE, blkOp->Size()); + GetEmitter()->emitIns_I(isCopyBlk ? INS_memory_copy : INS_memory_fill, EA_8BYTE, LINEAR_MEMORY_INDEX); + break; + + default: + unreached(); + } +} + +void CodeGen::genCodeForCpObj(GenTreeBlk* cpObjNode) +{ + NYI_WASM("genCodeForCpObj"); +} + +//------------------------------------------------------------------------ +// genCodeForInitBlkLoop - Generate code for an InitBlk using an inlined for-loop. +// It's needed for cases when size is too big to unroll and we're not allowed +// to use memset call due to atomicity requirements. +// +// Arguments: +// blkOp - the GT_STORE_BLK node +// +void CodeGen::genCodeForInitBlkLoop(GenTreeBlk* blkOp) +{ + // TODO-WASM: In multi-threaded wasm we will need to generate a for loop that atomically zeroes one GC ref + // at a time. Right now we're single-threaded, so we can just use memory.fill. + assert(!WASM_THREAD_SUPPORT); + + genConsumeOperands(blkOp); + // Emit the value constant expected by the memory.fill opcode (zero) + GetEmitter()->emitIns_I(INS_i32_const, EA_4BYTE, 0); + // Emit the size constant expected by the memory.copy and memory.fill opcodes + GetEmitter()->emitIns_I(INS_i32_const, EA_4BYTE, blkOp->Size()); + GetEmitter()->emitIns_I(INS_memory_fill, EA_8BYTE, LINEAR_MEMORY_INDEX); +} + BasicBlock* CodeGen::genCallFinally(BasicBlock* block) { assert(block->KindIs(BBJ_CALLFINALLY)); diff --git a/src/coreclr/jit/emitfmtswasm.h b/src/coreclr/jit/emitfmtswasm.h index e10b45828ba996..70bc05f6cda22f 100644 --- a/src/coreclr/jit/emitfmtswasm.h +++ b/src/coreclr/jit/emitfmtswasm.h @@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // ( ) IF_DEF(LOCAL_DECL, IS_NONE, NONE) // IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // +IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // #undef IF_DEF #endif // !DEFINE_ID_OPS diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 5f3f5df1fdf842..16e78849bdd3ba 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -430,6 +430,12 @@ unsigned emitter::instrDesc::idCodeSize() const size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this)); break; } + case IF_MEMIDX_MEMIDX: + { + size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this)); + size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(emitGetInsSC(this)); + break; + } default: unreached(); } @@ -651,6 +657,14 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) dst += emitOutputByte(dst, valType); break; } + case IF_MEMIDX_MEMIDX: + { + dst += emitOutputOpcode(dst, ins); + cnsval_ssize_t constant = emitGetInsSC(id); + dst += emitOutputULEB128(dst, (uint64_t)constant); + dst += emitOutputULEB128(dst, (uint64_t)constant); + break; + } default: NYI_WASM("emitOutputInstr"); break; @@ -792,7 +806,12 @@ void emitter::emitDispIns( dispHandleIfAny(); } break; - + case IF_MEMIDX_MEMIDX: + { + cnsval_ssize_t imm = emitGetInsSC(id); + printf(" %llu %llu", (uint64_t)imm, (uint64_t)imm); + } + break; case IF_LOCAL_DECL: { unsigned int count = emitGetLclVarDeclCount(id); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 036014e4baaaa0..f708de8ebf4178 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree, printf(" (Loop)"); break; +#ifdef TARGET_WASM + case GenTreeBlk::BlkOpKindNativeOpcode: + printf(" (memory.copy|fill)"); + break; +#endif + default: unreached(); } diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index b1e84c28e55aee..8f5503a909d49f 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir BlkOpKindLoop, BlkOpKindUnroll, BlkOpKindUnrollMemmove, +#ifdef TARGET_WASM + BlkOpKindNativeOpcode, +#endif } gtBlkOpKind; bool gtBlkOpGcUnsafe; diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 4b179c8566ef24..d3fc9c0392809d 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -228,6 +228,9 @@ INST(i64_trunc_sat_f32_u, "i64.trunc_sat_f32_u", 0, IF_OPCODE, 0x05FC) INST(i64_trunc_sat_f64_s, "i64.trunc_sat_f64_s", 0, IF_OPCODE, 0x06FC) INST(i64_trunc_sat_f64_u, "i64.trunc_sat_f64_u", 0, IF_OPCODE, 0x07FC) +INST(memory_copy, "memory.copy", 0, IF_MEMIDX_MEMIDX, 0x0AFC) +INST(memory_fill, "memory.fill", 0, IF_ULEB128, 0x0BFC) + // clang-format on #undef INST diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 9f68ac04f3843b..53a6926bd6ccf8 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -195,7 +195,61 @@ void Lowering::LowerDivOrMod(GenTreeOp* divMod) // void Lowering::LowerBlockStore(GenTreeBlk* blkNode) { - NYI_WASM("LowerBlockStore"); + GenTree* dstAddr = blkNode->Addr(); + GenTree* src = blkNode->Data(); + + if (blkNode->OperIsInitBlkOp()) + { + if (src->OperIs(GT_INIT_VAL)) + { + src->SetContained(); + src = src->AsUnOp()->gtGetOp1(); + } + + if (blkNode->IsZeroingGcPointersOnHeap()) + { + blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindLoop; + src->SetContained(); + } + else + { + // memory.fill + blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindNativeOpcode; + } + } + else + { + assert(src->OperIs(GT_IND, GT_LCL_VAR, GT_LCL_FLD)); + src->SetContained(); + + if (src->OperIs(GT_LCL_VAR)) + { + // TODO-1stClassStructs: for now we can't work with STORE_BLOCK source in register. + const unsigned srcLclNum = src->AsLclVar()->GetLclNum(); + m_compiler->lvaSetVarDoNotEnregister(srcLclNum DEBUGARG(DoNotEnregisterReason::StoreBlkSrc)); + } + + ClassLayout* layout = blkNode->GetLayout(); + bool doCpObj = layout->HasGCPtr(); + + // CopyObj or CopyBlk + if (doCpObj) + { + // Try to use bulk copy helper + if (TryLowerBlockStoreAsGcBulkCopyCall(blkNode)) + { + return; + } + + blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindCpObjUnroll; + } + else + { + assert(blkNode->OperIs(GT_STORE_BLK)); + // memory.copy + blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindNativeOpcode; + } + } } //------------------------------------------------------------------------ @@ -440,6 +494,8 @@ void Lowering::AfterLowerBlock() // rare, introduced in lowering only. All HIR-induced cases (such as from "gtSetEvalOrder") should // instead be ifdef-ed out for WASM. m_anyChanges = true; + + JITDUMP("node==[%06u] prev==[%06u]\n", Compiler::dspTreeID(node), Compiler::dspTreeID(prev)); NYI_WASM("IR not in a stackified form"); } diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index 606c305a8a616e..961686f5d27c64 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using Internal.TypeSystem; using ILCompiler.ObjectWriter; @@ -148,17 +149,8 @@ public static WasmFuncType GetSignature(MethodDesc method) returnIsVoid = true; } - int parameterCount = signature.Length; - - if (hasReturnBuffer) - { - parameterCount++; // return buffer - } - - if (!method.IsUnmanagedCallersOnly) - { - parameterCount += 2; // sp and pe - } + // Reserve space for potential implicit this, stack pointer parameter, portable entrypoint parameter, and return buffer + ArrayBuilder result = new(signature.Length + 4); if (!signature.IsStatic) { @@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method) { explicitThis = true; } - else - { - parameterCount += 1; // implicit this - } } - Span wasmParameters = new WasmValueType[parameterCount]; - - int index = 0; - if (method.IsUnmanagedCallersOnly) // reverse P/Invoke { if (hasReturnBuffer) { - wasmParameters[index++] = pointerType; + result.Add(pointerType); } } else // managed call { - wasmParameters[0] = pointerType; // Stack pointer parameter - - // Return buffer is first after this. + result.Add(pointerType); // Stack pointer parameter if (hasThis) { - wasmParameters[index++] = pointerType; + result.Add(pointerType); } if (hasReturnBuffer) { - wasmParameters[index++] = pointerType; + result.Add(pointerType); } - - wasmParameters[wasmParameters.Length - 1] = pointerType; // PE entrypoint parameter } for (int i = explicitThis ? 1 : 0; i < signature.Length; i++) { - wasmParameters[index++] = LowerType(signature[i]); + result.Add(LowerType(signature[i])); + } + + if (!method.IsUnmanagedCallersOnly) + { + result.Add(pointerType); // PE entrypoint parameter } - WasmResultType ps = new(wasmParameters.ToArray()); + WasmResultType ps = new(result.ToArray()); WasmResultType ret = returnIsVoid ? new(Array.Empty()) : new([LowerType(loweredReturnType)]);