Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a1cd1f8
Implement Wasm codegen for Vector128.Create with const operands
adamperlin Jun 17, 2026
2921181
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/wasm…
adamperlin Jun 18, 2026
f4fd2fe
Fix categories and flags for some Vector128 Wasm hardware intrinsics
adamperlin Jun 18, 2026
48e0731
Checkpoint: PackedSimd.Add now compiles as an intrinsic (with const v…
adamperlin Jun 19, 2026
7bf550b
Add a few more PackedSimd intrinsics
adamperlin Jun 19, 2026
50f2359
Add table-driven codegen entries for (simd), (simd, simd) and (simd, …
adamperlin Jun 20, 2026
250aa59
Add ifdefs and remove not fully implemented opcode
adamperlin Jun 22, 2026
3766e1b
Cleanup (for now) unused code
adamperlin Jun 22, 2026
46ae244
Add #if FEATURE_SIMD
adamperlin Jun 22, 2026
cb5a7e9
jit-format
adamperlin Jun 22, 2026
c0f347d
Fix ifdef condition syntax
adamperlin Jun 22, 2026
3d4abae
Apply suggestions from code review
adamperlin Jun 22, 2026
628c47a
Fix unconditional NYI_WASM_SIMD lower bailout, handle no disasm file …
adamperlin Jun 22, 2026
4c056e3
Remove TODO
adamperlin Jun 23, 2026
10ff35c
Potential fix for pull request finding
adamperlin Jun 23, 2026
6ecc662
Copilot feedback: remove unconditional import NYI on non-constant vec…
adamperlin Jun 23, 2026
292bcaf
Format hwintrinsiclistwasm.h
adamperlin Jun 23, 2026
e7cef58
More copilot feedback
adamperlin Jun 23, 2026
5a93f14
Potential fix for pull request finding
adamperlin Jun 23, 2026
3b52445
Merge branch 'main' of github.com:dotnet/runtime into adamperlin/wasm…
adamperlin Jun 24, 2026
01e61c6
Address more review feedback: Ensure Vector128.GetElement can fall ba…
adamperlin Jun 25, 2026
ef9b870
Merge branch 'adamperlin/wasm-basic-simd' of github.com:adamperlin/ru…
adamperlin Jun 25, 2026
aff1238
Update src/coreclr/jit/hwintrinsic.cpp
adamperlin Jun 25, 2026
79f9b4d
Additional review feedback: handle PackedSimd.LoadVector128, Store to…
adamperlin Jun 26, 2026
132760a
Add impSimdCreate helper shared across targets
adamperlin Jun 26, 2026
6eaf553
Merge branch 'adamperlin/wasm-basic-simd' of github.com:adamperlin/ru…
adamperlin Jun 26, 2026
b868b58
Fix op reversal assert
adamperlin Jun 26, 2026
3a54c5a
Potential fix for pull request finding
adamperlin Jun 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,9 @@ class CodeGen final : public CodeGenInterface

#if defined(TARGET_WASM)
void genCodeForConstant(GenTree* treeNode);
#if defined(FEATURE_SIMD)
void genCodeForVectorConstant(GenTree* treeNode);
#endif
void genCatchArg(GenTree* treeNode);
#endif

Expand Down
43 changes: 32 additions & 11 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,14 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
return;
}

#ifdef FEATURE_HW_INTRINSICS
if (treeNode->OperIsHWIntrinsic())
{
genHWIntrinsic(treeNode->AsHWIntrinsic());
return;
}
#endif // FEATURE_HW_INTRINSICS

switch (treeNode->OperGet())
{
case GT_ADD:
Expand Down Expand Up @@ -1004,6 +1012,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genCkfinite(treeNode);
break;

#if defined(FEATURE_SIMD)
case GT_CNS_VEC:
genCodeForVectorConstant(treeNode);
break;
#endif // FEATURE_SIMD

default:
#ifdef DEBUG
if (JitConfig.JitWasmNyiToR2RUnsupported())
Expand Down Expand Up @@ -1883,6 +1897,22 @@ void CodeGen::genCodeForConstant(GenTree* treeNode)
WasmProduceReg(treeNode);
}

#ifdef FEATURE_SIMD
void CodeGen::genCodeForVectorConstant(GenTree* treeNode)
{
assert(treeNode->IsCnsVec());
GenTreeVecCon* vecCon = treeNode->AsVecCon();

uint8_t bytes[16] = {};
memcpy(bytes, &vecCon->gtSimdVal, genTypeSize(vecCon->TypeGet()));

// There is only one type variant for v128.const, v128.const <byte[16]>
// and the bytes are reinterpreted according to whichever operation consumes the value.
GetEmitter()->emitIns_V128Imm(INS_v128_const, bytes);
WasmProduceReg(treeNode);
}
#endif

//------------------------------------------------------------------------
// genCodeForShift: Generate code for a shift or rotate operator
//
Expand Down Expand Up @@ -2553,10 +2583,6 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree)
if (!varDsc->lvIsRegCandidate())
{
var_types type = varDsc->GetRegisterType(tree);
if (type == TYP_SIMD16)
{
NYI_WASM_SIMD("SIMD16 local load");
}

GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, GetFramePointerRegIndex());
GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(type), tree->GetLclNum(), 0);
Expand Down Expand Up @@ -2642,13 +2668,8 @@ void CodeGen::genCodeForIndir(GenTreeIndir* tree)
{
assert(tree->OperIs(GT_IND));

var_types type = tree->TypeGet();
if (type == TYP_SIMD16)
{
NYI_WASM_SIMD("SIMD16 indirect load");
}

instruction ins = ins_Load(type);
var_types type = tree->TypeGet();
instruction ins = ins_Load(type);

genConsumeAddress(tree->Addr());

Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,13 @@ void Compiler::compSetProcessor()
// Add virtual vector ISAs. These are both supported as part of the required baseline.
instructionSetFlags.AddInstructionSet(InstructionSet_Vector64);
instructionSetFlags.AddInstructionSet(InstructionSet_Vector128);
#elif defined(TARGET_WASM)
// Ensure required baseline ISAs are supported in JIT code, even if not passed in by the VM.
instructionSetFlags.AddInstructionSet(InstructionSet_WasmBase);
instructionSetFlags.AddInstructionSet(InstructionSet_PackedSimd);

// Add virtual vector ISA. Vector128 is part of the required Wasm SIMD baseline.
instructionSetFlags.AddInstructionSet(InstructionSet_Vector128);
#endif // TARGET_ARM64

assert(instructionSetFlags.Equals(EnsureInstructionSetFlagsAreValid(instructionSetFlags)));
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5152,6 +5152,11 @@ class Compiler
GenTree* impImportLdvirtftn(GenTree* thisPtr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo);

#if defined(FEATURE_HW_INTRINSICS)
GenTree* impSimdCreate(NamedIntrinsic intrinsic,
CORINFO_SIG_INFO* sig,
var_types simdBaseType,
var_types retType,
unsigned simdSize);
GenTree* impSimdCreateScalarHalf(GenTree* op1);
GenTree* impSimdToScalarHalf(GenTree* op1, CORINFO_CLASS_HANDLE halfClsHnd);
#endif // FEATURE_HW_INTRINSICS
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4777,6 +4777,13 @@ unsigned Compiler::gtSetMultiOpOrder(GenTreeMultiOp* multiOp)
// We want the more complex tree to be evaluated first.
if ((level < lvl2) && !multiOp->AsHWIntrinsic()->IsUserCall() && gtCanSwapOrder(op1, op2))
{
#if defined(TARGET_WASM)
// TODO-WASM-CQ: Wasm's stack-machine codegen requires operands to be evaluated in
// source order, so we cannot honor GTF_REVERSE_OPS here. For commutative HW
// intrinsics we could instead physically swap multiOp->Op(1)/Op(2) to retain the
// CQ benefit (mirroring the binary commutative path in gtSetEvalOrder). For now,
// just skip the swap to keep evaluation order intact.
#else
if (multiOp->IsReverseOp())
{
multiOp->ClearReverseOp();
Expand All @@ -4787,6 +4794,7 @@ unsigned Compiler::gtSetMultiOpOrder(GenTreeMultiOp* multiOp)
}

std::swap(level, lvl2);
#endif // TARGET_WASM
}

if (level < 1)
Expand Down
172 changes: 170 additions & 2 deletions src/coreclr/jit/hwintrinsic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,9 +1048,8 @@ static const HWIntrinsicIsaRange hwintrinsicIsaRangeArray[] = {
{ NI_Illegal, NI_Illegal }, // SveSm4_Arm64
#elif defined(TARGET_WASM)
{ NI_Illegal, NI_Illegal }, // WasmBase
{ NI_Illegal, NI_Illegal }, // PackedSimd
{ FIRST_NI_PackedSimd, LAST_NI_PackedSimd }, // PackedSimd
{ FIRST_NI_Vector128, LAST_NI_Vector128 }, // Vector128
Comment thread
adamperlin marked this conversation as resolved.
// TODO-WASM: Add PackedSimd intrinsic ranges
#else
#error Unsupported platform
#endif
Expand Down Expand Up @@ -1605,6 +1604,173 @@ GenTree* Compiler::getArgForHWIntrinsic(var_types argType, CORINFO_CLASS_HANDLE
return arg;
}

//------------------------------------------------------------------------
// impSimdCreate: Common importer logic for Vector{64,128,256,512}.Create intrinsics.
//
// Handles the shared pattern used across xarch, arm64, and wasm:
// * sig->numArgs == 1 -> emit a broadcast/splat node
// * sig->numArgs == simdLength, all const-> fold into a single GenTreeVecCon
// * otherwise -> pack operands into a GT_HWINTRINSIC
// via IntrinsicNodeBuilder
//
// Arguments:
// intrinsic -- the NI_Vector*_Create NamedIntrinsic being imported
// sig -- the call signature (used for numArgs)
// simdBaseType -- the base element type of the SIMD vector
// retType -- the SIMD return type
// simdSize -- size in bytes of the SIMD vector
//
// Return Value:
// The imported GenTree* representing the Create call. Operands are popped
// off the importer stack as part of this call.
//
GenTree* Compiler::impSimdCreate(
NamedIntrinsic intrinsic, CORINFO_SIG_INFO* sig, var_types simdBaseType, var_types retType, unsigned simdSize)
{
if (sig->numArgs == 1)
{
GenTree* op1 = impStackTop().val;

#ifdef TARGET_WASM
if (!(op1->IsIntegralConst() || op1->IsCnsFltOrDbl()))
{
// Vector*.Create(T) with a non-constant operand is not yet supported on Wasm.
// Returning nullptr lets the importer fall back to the managed implementation.
return nullptr;
}
#endif

op1 = impPopStack().val;
return gtNewSimdCreateBroadcastNode(retType, op1, simdBaseType, simdSize);
}

uint32_t simdLength = getSIMDVectorLength(simdSize, simdBaseType);
assert(sig->numArgs == simdLength);

bool isConstant = true;

if (varTypeIsFloating(simdBaseType))
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
if (!impStackTop(index).val->IsCnsFltOrDbl())
{
isConstant = false;
break;
}
}
}
else
{
assert(varTypeIsIntegral(simdBaseType));

for (uint32_t index = 0; index < sig->numArgs; index++)
{
if (!impStackTop(index).val->IsIntegralConst())
{
isConstant = false;
break;
}
}
}

if (isConstant)
{
GenTreeVecCon* vecCon = gtNewVconNode(retType);

switch (simdBaseType)
{
case TYP_BYTE:
case TYP_UBYTE:
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
uint8_t cnsVal = static_cast<uint8_t>(impPopStack().val->AsIntConCommon()->IntegralValue());
vecCon->gtSimdVal.u8[simdLength - 1 - index] = cnsVal;
}
break;
}

case TYP_SHORT:
case TYP_USHORT:
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
uint16_t cnsVal = static_cast<uint16_t>(impPopStack().val->AsIntConCommon()->IntegralValue());
vecCon->gtSimdVal.u16[simdLength - 1 - index] = cnsVal;
}
break;
}

case TYP_INT:
case TYP_UINT:
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
uint32_t cnsVal = static_cast<uint32_t>(impPopStack().val->AsIntConCommon()->IntegralValue());
vecCon->gtSimdVal.u32[simdLength - 1 - index] = cnsVal;
}
break;
}

case TYP_LONG:
case TYP_ULONG:
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
uint64_t cnsVal = static_cast<uint64_t>(impPopStack().val->AsIntConCommon()->IntegralValue());
vecCon->gtSimdVal.u64[simdLength - 1 - index] = cnsVal;
}
break;
}

case TYP_FLOAT:
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
float cnsVal = static_cast<float>(impPopStack().val->AsDblCon()->DconValue());
vecCon->gtSimdVal.f32[simdLength - 1 - index] = cnsVal;
}
break;
}

case TYP_DOUBLE:
{
for (uint32_t index = 0; index < sig->numArgs; index++)
{
double cnsVal = static_cast<double>(impPopStack().val->AsDblCon()->DconValue());
vecCon->gtSimdVal.f64[simdLength - 1 - index] = cnsVal;
}
break;
}

default:
{
unreached();
}
}

return vecCon;
}
#ifdef TARGET_WASM
else
{
// non const Vector128.Create not yet implemented on Wasm
return nullptr;
}
#endif

IntrinsicNodeBuilder nodeBuilder(getAllocator(CMK_ASTNode), sig->numArgs);

for (int i = sig->numArgs - 1; i >= 0; i--)
{
GenTree* arg = impPopStack().val;
nodeBuilder.AddOperand(i, arg);
}

return gtNewSimdHWIntrinsicNode(retType, std::move(nodeBuilder), intrinsic, simdBaseType, simdSize);
}

//------------------------------------------------------------------------
// addRangeCheckIfNeeded: add a GT_BOUNDS_CHECK node for non-full-range imm-intrinsic
//
Expand Down Expand Up @@ -2275,6 +2441,8 @@ GenTree* Compiler::impHWIntrinsic(NamedIntrinsic intrinsic,
if ((simdSize != 8) && (simdSize != 16) && (simdSize != SIZE_UNKNOWN))
#elif defined(TARGET_XARCH)
if ((simdSize != 16) && (simdSize != 32) && (simdSize != 64))
#elif defined(TARGET_WASM)
if (simdSize != 16)
#endif // TARGET_*
{
assert(!"Unexpected SIMD size");
Expand Down
Loading
Loading