diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index a23105ca5b1a1b..a9531e3920a12a 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -605,6 +605,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) JITDUMP("Ignoring GT_MEMORYBARRIER; single-threaded codegen\n"); break; + case GT_INTRINSIC: + genIntrinsic(treeNode->AsIntrinsic()); + break; + default: #ifdef DEBUG NYIRAW(GenTree::OpName(treeNode->OperGet())); @@ -1471,6 +1475,135 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) WasmProduceReg(lea); } +//------------------------------------------------------------------------ +// PackIntrinsicAndType: Pack a intrinsic and var_types into a uint32_t +// +// Arguments: +// ni - a NamedIntrinsic to pack +// type - a var_types to pack +// +// Return Value: +// intrinsic and type packed into an integer that can be used as a switch value/case +// +static constexpr uint32_t PackIntrinsicAndType(NamedIntrinsic ni, var_types type) +{ + if ((type == TYP_BYREF) || (type == TYP_REF)) + { + type = TYP_I_IMPL; + } + const int shift1 = ConstLog2::value + 1; + return ((uint32_t)ni << shift1) | ((uint32_t)type); +} + +//--------------------------------------------------------------------- +// genIntrinsic - generate code for a given intrinsic +// +// Arguments +// treeNode - the GT_INTRINSIC node +// +// Return value: +// None +// +void CodeGen::genIntrinsic(GenTreeIntrinsic* treeNode) +{ + genConsumeOperands(treeNode); + + // Handle intrinsics that can be implemented by target-specific instructions + instruction ins = INS_invalid; + + switch (PackIntrinsicAndType(treeNode->gtIntrinsicName, treeNode->TypeGet())) + { + case PackIntrinsicAndType(NI_System_Math_Abs, TYP_FLOAT): + ins = INS_f32_abs; + break; + case PackIntrinsicAndType(NI_System_Math_Abs, TYP_DOUBLE): + ins = INS_f64_abs; + break; + + case PackIntrinsicAndType(NI_System_Math_Ceiling, TYP_FLOAT): + ins = INS_f32_ceil; + break; + case PackIntrinsicAndType(NI_System_Math_Ceiling, TYP_DOUBLE): + ins = INS_f64_ceil; + break; + + case PackIntrinsicAndType(NI_System_Math_Floor, TYP_FLOAT): + ins = INS_f32_floor; + break; + case PackIntrinsicAndType(NI_System_Math_Floor, TYP_DOUBLE): + ins = INS_f64_floor; + break; + + case PackIntrinsicAndType(NI_System_Math_Max, TYP_FLOAT): + case PackIntrinsicAndType(NI_System_Math_MaxNative, TYP_FLOAT): + ins = INS_f32_max; + break; + case PackIntrinsicAndType(NI_System_Math_Max, TYP_DOUBLE): + case PackIntrinsicAndType(NI_System_Math_MaxNative, TYP_DOUBLE): + ins = INS_f64_max; + break; + + case PackIntrinsicAndType(NI_System_Math_Min, TYP_FLOAT): + case PackIntrinsicAndType(NI_System_Math_MinNative, TYP_FLOAT): + ins = INS_f32_min; + break; + case PackIntrinsicAndType(NI_System_Math_Min, TYP_DOUBLE): + case PackIntrinsicAndType(NI_System_Math_MinNative, TYP_DOUBLE): + ins = INS_f64_min; + break; + + case PackIntrinsicAndType(NI_System_Math_Round, TYP_FLOAT): + ins = INS_f32_nearest; + break; + case PackIntrinsicAndType(NI_System_Math_Round, TYP_DOUBLE): + ins = INS_f64_nearest; + break; + + case PackIntrinsicAndType(NI_System_Math_Sqrt, TYP_FLOAT): + ins = INS_f32_sqrt; + break; + case PackIntrinsicAndType(NI_System_Math_Sqrt, TYP_DOUBLE): + ins = INS_f64_sqrt; + break; + + case PackIntrinsicAndType(NI_System_Math_Truncate, TYP_FLOAT): + ins = INS_f32_trunc; + break; + case PackIntrinsicAndType(NI_System_Math_Truncate, TYP_DOUBLE): + ins = INS_f64_trunc; + break; + + case PackIntrinsicAndType(NI_PRIMITIVE_LeadingZeroCount, TYP_INT): + ins = INS_i32_clz; + break; + case PackIntrinsicAndType(NI_PRIMITIVE_LeadingZeroCount, TYP_LONG): + ins = INS_i64_clz; + break; + + case PackIntrinsicAndType(NI_PRIMITIVE_TrailingZeroCount, TYP_INT): + ins = INS_i32_ctz; + break; + case PackIntrinsicAndType(NI_PRIMITIVE_TrailingZeroCount, TYP_LONG): + ins = INS_i64_ctz; + break; + + case PackIntrinsicAndType(NI_PRIMITIVE_PopCount, TYP_INT): + ins = INS_i32_popcnt; + break; + case PackIntrinsicAndType(NI_PRIMITIVE_PopCount, TYP_LONG): + ins = INS_i64_popcnt; + break; + + default: + assert(!"genIntrinsic: Unsupported intrinsic"); + unreached(); + } + + GetEmitter()->emitIns(ins); + + WasmProduceReg(treeNode); +} + //------------------------------------------------------------------------ // genCodeForLclAddr: Generates the code for GT_LCL_ADDR. // diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 62b79e697788ff..1294aee45f8477 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -5988,7 +5988,7 @@ GenTree* Compiler::impPrimitiveNamedIntrinsic(NamedIntrinsic intrinsic, break; } -#if !defined(TARGET_64BIT) +#if !defined(TARGET_64BIT) && !defined(TARGET_WASM) if (varTypeIsLong(baseType)) { // TODO-CQ: Adding long decomposition support is more complex @@ -5997,15 +5997,19 @@ GenTree* Compiler::impPrimitiveNamedIntrinsic(NamedIntrinsic intrinsic, break; } -#endif // !TARGET_64BIT +#endif // !defined(TARGET_64BIT) && !defined(TARGET_WASM) -#ifdef TARGET_RISCV64 +#if defined(TARGET_RISCV64) if (compOpportunisticallyDependsOn(InstructionSet_Zbb)) { impPopStack(); result = new (this, GT_INTRINSIC) GenTreeIntrinsic(retType, op1, NI_PRIMITIVE_LeadingZeroCount, nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); } +#elif defined(TARGET_WASM) + impPopStack(); + result = new (this, GT_INTRINSIC) GenTreeIntrinsic(retType, op1, NI_PRIMITIVE_LeadingZeroCount, + nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); #elif defined(FEATURE_HW_INTRINSICS) #if defined(TARGET_XARCH) if (compOpportunisticallyDependsOn(InstructionSet_AVX2)) @@ -6168,7 +6172,7 @@ GenTree* Compiler::impPrimitiveNamedIntrinsic(NamedIntrinsic intrinsic, break; } -#if !defined(TARGET_64BIT) +#if !defined(TARGET_64BIT) && !defined(TARGET_WASM) if (varTypeIsLong(baseType)) { // TODO-CQ: Adding long decomposition support is more complex @@ -6179,13 +6183,17 @@ GenTree* Compiler::impPrimitiveNamedIntrinsic(NamedIntrinsic intrinsic, } #endif // !TARGET_64BIT -#ifdef TARGET_RISCV64 +#if defined(TARGET_RISCV64) if (compOpportunisticallyDependsOn(InstructionSet_Zbb)) { impPopStack(); result = new (this, GT_INTRINSIC) GenTreeIntrinsic(retType, op1, NI_PRIMITIVE_PopCount, nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); } +#elif defined(TARGET_WASM) + impPopStack(); + result = new (this, GT_INTRINSIC) + GenTreeIntrinsic(retType, op1, NI_PRIMITIVE_PopCount, nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); #elif defined(FEATURE_HW_INTRINSICS) #if defined(TARGET_XARCH) // Pop the value from the stack @@ -6326,7 +6334,7 @@ GenTree* Compiler::impPrimitiveNamedIntrinsic(NamedIntrinsic intrinsic, break; } -#if !defined(TARGET_64BIT) +#if !defined(TARGET_64BIT) && !defined(TARGET_WASM) if (varTypeIsLong(baseType)) { // TODO-CQ: Adding long decomposition support is more complex @@ -6335,15 +6343,19 @@ GenTree* Compiler::impPrimitiveNamedIntrinsic(NamedIntrinsic intrinsic, break; } -#endif // !TARGET_64BIT +#endif // !defined(TARGET_64BIT) && !defined(TARGET_WASM) -#ifdef TARGET_RISCV64 +#if defined(TARGET_RISCV64) if (compOpportunisticallyDependsOn(InstructionSet_Zbb)) { impPopStack(); result = new (this, GT_INTRINSIC) GenTreeIntrinsic(retType, op1, NI_PRIMITIVE_TrailingZeroCount, nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); } +#elif defined(TARGET_WASM) + impPopStack(); + result = new (this, GT_INTRINSIC) GenTreeIntrinsic(retType, op1, NI_PRIMITIVE_TrailingZeroCount, + nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); #elif defined(FEATURE_HW_INTRINSICS) #if defined(TARGET_XARCH) if (compOpportunisticallyDependsOn(InstructionSet_AVX2)) @@ -8480,6 +8492,32 @@ bool Compiler::IsTargetIntrinsic(NamedIntrinsic intrinsicName) default: return false; } + +#elif defined(TARGET_WASM) + + // TODO-WASM-CQ: we can likely support more intrinsics here + switch (intrinsicName) + { + case NI_System_Math_Abs: + case NI_System_Math_Ceiling: + case NI_System_Math_Floor: + case NI_System_Math_Max: + case NI_System_Math_MaxNative: + case NI_System_Math_Min: + case NI_System_Math_MinNative: + case NI_System_Math_Round: + case NI_System_Math_Sqrt: + case NI_System_Math_Truncate: + return true; + + case NI_PRIMITIVE_LeadingZeroCount: + case NI_PRIMITIVE_TrailingZeroCount: + case NI_PRIMITIVE_PopCount: + return true; + + default: + return false; + } #else // TODO: This portion of logic is not implemented for other arch. // The reason for returning true is that on all other arch the only intrinsic diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 2be00e7e1057c3..61a731709dcf4d 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -9253,7 +9253,12 @@ ValueNum ValueNumStore::EvalHWIntrinsicFunTernary( ValueNum ValueNumStore::EvalMathFuncUnary(var_types typ, NamedIntrinsic gtMathFN, ValueNum arg0VN) { assert(arg0VN == VNNormalValue(arg0VN)); - assert(m_compiler->IsMathIntrinsic(gtMathFN) RISCV64_ONLY(|| m_compiler->IsBitCountingIntrinsic(gtMathFN))); + +#if defined(TARGET_RISCV64) || defined(TARGET_WASM) + assert(m_compiler->IsMathIntrinsic(gtMathFN) || m_compiler->IsBitCountingIntrinsic(gtMathFN)); +#else + assert(m_compiler->IsMathIntrinsic(gtMathFN)); +#endif // If the math intrinsic is not implemented by target-specific instructions, such as implemented // by user calls, then don't do constant folding on it during ReadyToRun. This minimizes precision loss.