Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 68 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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);
Expand DownExpand Up@@ -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);
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
Comment thread
kg marked this conversation as resolved.
// 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;
Comment thread
kg marked this conversation as resolved.

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));
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,7 @@ IF_DEF(F64, IS_NONE, NONE) // <opcode> <f64 immediate (stored as 64-bi
IF_DEF(MEMARG, IS_NONE, NONE) // <opcode> <memarg> (<align> <offset>)
IF_DEF(LOCAL_DECL, IS_NONE, NONE) // <ULEB128 immediate> <byte>
IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 immediate>
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>

#undef IF_DEF
#endif // !DEFINE_ID_OPS
Expand Down
21 changes: 20 additions & 1 deletion src/coreclr/jit/emitwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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));
Comment thread
kg marked this conversation as resolved.
break;
}
default:
unreached();
}
Expand DownExpand Up@@ -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;
Expand DownExpand Up@@ -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);
}
Comment thread
kg marked this conversation as resolved.
break;
case IF_LOCAL_DECL:
{
unsigned int count = emitGetLclVarDeclCount(id);
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12846,6 +12846,12 @@ void Compiler::gtDispTree(GenTree* tree,
printf(" (Loop)");
break;

#ifdef TARGET_WASM
case GenTreeBlk::BlkOpKindNativeOpcode:
printf(" (memory.copy|fill)");
Comment thread
kg marked this conversation as resolved.
break;
#endif

default:
unreached();
}
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -8098,6 +8098,9 @@ struct GenTreeBlk : public GenTreeIndir
BlkOpKindLoop,
BlkOpKindUnroll,
BlkOpKindUnrollMemmove,
#ifdef TARGET_WASM
BlkOpKindNativeOpcode,
#endif
} gtBlkOpKind;

bool gtBlkOpGcUnsafe;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/instrswasm.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
58 changes: 57 additions & 1 deletion src/coreclr/jit/lowerwasm.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -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();

Comment thread
kg marked this conversation as resolved.
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();
Comment thread
kg marked this conversation as resolved.

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;
Comment thread
kg marked this conversation as resolved.
}
}
}

//------------------------------------------------------------------------
Expand DownExpand Up@@ -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");
}

Expand Down
43 changes: 14 additions & 29 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Comment thread
kg marked this conversation as resolved.
using Internal.TypeSystem;
using ILCompiler.ObjectWriter;
Expand DownExpand Up@@ -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<WasmValueType> result = new(signature.Length + 4);

if (!signature.IsStatic)
{
Expand All@@ -168,48 +160,41 @@ public static WasmFuncType GetSignature(MethodDesc method)
{
explicitThis = true;
}
else
{
parameterCount += 1; // implicit this
}
}

Span<WasmValueType> 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]));
Comment thread
kg marked this conversation as resolved.
}

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<WasmValueType>())
: new([LowerType(loweredReturnType)]);

Expand Down
Loading