Skip to content

[RISC-V] Simple math intrinsics #113689

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 3, 2025
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
39 changes: 38 additions & 1 deletion src/coreclr/jit/codegenriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4722,7 +4722,44 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
//
void CodeGen::genIntrinsic(GenTreeIntrinsic* treeNode)
{
NYI_RISCV64("genIntrinsic-----unimplemented/unused on RISCV64 yet----");
GenTree* op1 = treeNode->gtGetOp1();
GenTree* op2 = treeNode->gtGetOp2IfPresent();

emitAttr size = emitActualTypeSize(treeNode);
bool is4 = (size == 4);

instruction instr = INS_invalid;
switch (treeNode->gtIntrinsicName)
{
case NI_System_Math_Abs:
instr = is4 ? INS_fsgnjx_s : INS_fsgnjx_d;
op2 = op1; // "fabs rd, rs" is a pseudo-instruction for "fsgnjx rd, rs, rs"
break;
case NI_System_Math_Sqrt:
instr = is4 ? INS_fsqrt_s : INS_fsqrt_d;
break;
case NI_System_Math_MinNumber:
instr = is4 ? INS_fmin_s : INS_fmin_d;
break;
case NI_System_Math_MaxNumber:
instr = is4 ? INS_fmax_s : INS_fmax_d;
break;
default:
NO_WAY("Unknown intrinsic");
}

genConsumeOperands(treeNode->AsOp());
regNumber dest = treeNode->GetRegNum();
regNumber src1 = op1->GetRegNum();
if (op2 == nullptr)
{
GetEmitter()->emitIns_R_R(instr, size, dest, src1);
}
else
{
GetEmitter()->emitIns_R_R_R(instr, size, dest, src1, op2->GetRegNum());
}
genProduceReg(treeNode);
}

//---------------------------------------------------------------------
Expand Down
51 changes: 19 additions & 32 deletions src/coreclr/jit/emitriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ bool emitter::emitInsIsLoadOrStore(instruction ins)
* Returns the specific encoding of the given CPU instruction.
*/

inline emitter::code_t emitter::emitInsCode(instruction ins /*, insFormat fmt*/) const
inline emitter::code_t emitter::emitInsCode(instruction ins /*, insFormat fmt*/)
{
code_t code = BAD_CODE;

Expand Down Expand Up @@ -688,7 +688,7 @@ void emitter::emitIns_R_R(
if (INS_fcvt_d_w != ins && INS_fcvt_d_wu != ins) // fcvt.d.w[u] always produces an exact result
code |= 0x7 << 12; // round according to frm status register
}
else if (INS_fcvt_s_d == ins || INS_fcvt_d_s == ins)
else if (INS_fcvt_s_d == ins || INS_fcvt_d_s == ins || INS_fsqrt_s == ins || INS_fsqrt_d == ins)
{
assert(isFloatReg(reg1));
assert(isFloatReg(reg2));
Expand Down Expand Up @@ -865,7 +865,6 @@ void emitter::emitIns_R_R_R(
case INS_fsub_s:
case INS_fmul_s:
case INS_fdiv_s:
case INS_fsqrt_s:
case INS_fsgnj_s:
case INS_fsgnjn_s:
case INS_fsgnjx_s:
Expand All @@ -880,7 +879,6 @@ void emitter::emitIns_R_R_R(
case INS_fsub_d:
case INS_fmul_d:
case INS_fdiv_d:
case INS_fsqrt_d:
case INS_fsgnj_d:
case INS_fsgnjn_d:
case INS_fsgnjx_d:
Expand Down Expand Up @@ -925,7 +923,7 @@ void emitter::emitIns_R_R_R(
code |= ((reg1 & 0x1f) << 7);
code |= ((reg2 & 0x1f) << 15);
code |= ((reg3 & 0x1f) << 20);
if ((INS_fadd_s <= ins && INS_fsqrt_s >= ins) || (INS_fadd_d <= ins && INS_fsqrt_d >= ins))
if ((INS_fadd_s <= ins && INS_fdiv_s >= ins) || (INS_fadd_d <= ins && INS_fdiv_d >= ins))
{
code |= 0x7 << 12;
}
Expand Down Expand Up @@ -4083,22 +4081,17 @@ void emitter::emitDispInsName(
case 0x2C: // FSQRT.S
printf("fsqrt.s %s, %s\n", fd, fs1);
return;
case 0x10: // FSGNJ.S & FSGNJN.S & FSGNJX.S
if (opcode4 == 0) // FSGNJ.S
case 0x10: // FSGNJ.S & FSGNJN.S & FSGNJX.S
NYI_IF(opcode4 >= 3, "RISC-V illegal fsgnj.s variant");
if (fs1 != fs2)
{
printf("fsgnj.s %s, %s, %s\n", fd, fs1, fs2);
const char* variants[3] = {".s ", "n.s", "x.s"};
printf("fsgnj%s %s, %s, %s\n", variants[opcode4], fd, fs1, fs2);
}
else if (opcode4 == 1) // FSGNJN.S
else // pseudos
{
printf("fsgnjn.s %s, %s, %s\n", fd, fs1, fs2);
}
else if (opcode4 == 2) // FSGNJX.S
{
printf("fsgnjx.s %s, %s, %s\n", fd, fs1, fs2);
}
else
{
NYI_RISCV64("illegal ins within emitDisInsName!");
const char* names[3] = {"fmv.s ", "fneg.s", "fabs.s"};
printf("%s %s, %s\n", names[opcode4], fd, fs1);
}
return;
case 0x14: // FMIN.S & FMAX.S
Expand Down Expand Up @@ -4186,7 +4179,6 @@ void emitter::emitDispInsName(
{
printf("fcvt.s.lu %s, %s\n", fd, xs1);
}

else
{
NYI_RISCV64("illegal ins within emitDisInsName!");
Expand All @@ -4210,22 +4202,17 @@ void emitter::emitDispInsName(
case 0x2d: // FSQRT.D
printf("fsqrt.d %s, %s\n", fd, fs1);
return;
case 0x11: // FSGNJ.D & FSGNJN.D & FSGNJX.D
if (opcode4 == 0) // FSGNJ.D
case 0x11: // FSGNJ.D & FSGNJN.D & FSGNJX.D
NYI_IF(opcode4 >= 3, "RISC-V illegal fsgnj.d variant");
if (fs1 != fs2)
{
printf("fsgnj.d %s, %s, %s\n", fd, fs1, fs2);
const char* variants[3] = {".d ", "n.d", "x.d"};
printf("fsgnj%s %s, %s, %s\n", variants[opcode4], fd, fs1, fs2);
}
else if (opcode4 == 1) // FSGNJN.D
else // pseudos
{
printf("fsgnjn.d %s, %s, %s\n", fd, fs1, fs2);
}
else if (opcode4 == 2) // FSGNJX.D
{
printf("fsgnjx.d %s, %s, %s\n", fd, fs1, fs2);
}
else
{
NYI_RISCV64("illegal ins within emitDisInsName!");
const char* names[3] = {"fmv.d ", "fneg.d", "fabs.d"};
printf("%s %s, %s\n", names[opcode4], fd, fs1);
}
return;
case 0x15: // FMIN.D & FMAX.D
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/emitriscv64.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ bool emitDispBranchInstrType(unsigned opcode2, bool is_zero_reg, bool& print_sec
void emitDispIllegalInstruction(code_t instructionCode);
void emitDispImmediate(ssize_t imm, bool newLine = true, unsigned regBase = REG_ZERO);

emitter::code_t emitInsCode(instruction ins /*, insFormat fmt*/) const;
static emitter::code_t emitInsCode(instruction ins /*, insFormat fmt*/);

// Generate code for a load or store operation and handle the case of contained GT_LEA op1 with [base + offset]
void emitInsLoadStoreOp(instruction ins, emitAttr attr, regNumber dataReg, GenTreeIndir* indir);
Expand Down
67 changes: 64 additions & 3 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7907,16 +7907,35 @@ bool Compiler::IsTargetIntrinsic(NamedIntrinsic intrinsicName)
default:
return false;
}
#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
#elif defined(TARGET_RISCV64)
switch (intrinsicName)
{
case NI_System_Math_Abs:
case NI_System_Math_Sqrt:
case NI_System_Math_MinNumber:
case NI_System_Math_MinMagnitudeNumber:
case NI_System_Math_MaxNumber:
case NI_System_Math_MaxMagnitudeNumber:
case NI_System_Math_Min:
case NI_System_Math_MinMagnitude:
case NI_System_Math_Max:
case NI_System_Math_MaxMagnitude:
case NI_System_Math_MultiplyAddEstimate:
case NI_System_Math_ReciprocalEstimate:
case NI_System_Math_ReciprocalSqrtEstimate:
return true;

default:
return false;
}
#elif defined(TARGET_LOONGARCH64)
switch (intrinsicName)
{
case NI_System_Math_Abs:
case NI_System_Math_Sqrt:
case NI_System_Math_ReciprocalSqrtEstimate:
{
// TODO-LoongArch64: support these standard intrinsics
// TODO-RISCV64: support these standard intrinsics

return false;
}

Expand Down Expand Up @@ -10177,6 +10196,48 @@ GenTree* Compiler::impMinMaxIntrinsic(CORINFO_METHOD_HANDLE method,
}
#endif // FEATURE_HW_INTRINSICS && TARGET_XARCH

#ifdef TARGET_RISCV64
GenTree *op1Clone = nullptr, *op2Clone = nullptr;

op2 = impPopStack().val;
if (!isNumber)
{
op2 = impCloneExpr(op2, &op2Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Clone op2 for Math.Min/Max non-Number"));
}

op1 = impPopStack().val;
if (!isNumber)
{
op1 = impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("Clone op1 for Math.Min/Max non-Number"));
}

static const CORINFO_CONST_LOOKUP nullEntry = {IAT_VALUE};
if (isMagnitude)
{
op1 = new (this, GT_INTRINSIC) GenTreeIntrinsic(callType, op1, NI_System_Math_Abs, nullptr R2RARG(nullEntry));
op2 = new (this, GT_INTRINSIC) GenTreeIntrinsic(callType, op2, NI_System_Math_Abs, nullptr R2RARG(nullEntry));
}
NamedIntrinsic name = isMax ? NI_System_Math_MaxNumber : NI_System_Math_MinNumber;
GenTree* minMax = new (this, GT_INTRINSIC) GenTreeIntrinsic(callType, op1, op2, name, nullptr R2RARG(nullEntry));

if (!isNumber)
{
GenTreeOp* isOp1Number = gtNewOperNode(GT_EQ, TYP_INT, op1Clone, gtCloneExpr(op1Clone));
GenTreeOp* isOp2Number = gtNewOperNode(GT_EQ, TYP_INT, op2Clone, gtCloneExpr(op2Clone));
GenTreeOp* isOkForMinMax = gtNewOperNode(GT_EQ, TYP_INT, isOp1Number, isOp2Number);

GenTreeOp* nanPropagator = gtNewOperNode(GT_ADD, callType, gtCloneExpr(op1Clone), gtCloneExpr(op2Clone));

GenTreeQmark* qmark = gtNewQmarkNode(callType, isOkForMinMax, gtNewColonNode(callType, minMax, nanPropagator));
// QMARK has to be a root node
unsigned tmp = lvaGrabTemp(true DEBUGARG("Temp for Qmark in Math.Min/Max non-Number"));
impStoreToTemp(tmp, qmark, CHECK_SPILL_NONE);
minMax = gtNewLclvNode(tmp, callType);
}

return minMax;
#endif // TARGET_RISCV64

// TODO-CQ: Returning this as an intrinsic blocks inlining and is undesirable
// return impMathIntrinsic(method, sig, callType, intrinsicName, tailCall, isSpecial);

Expand Down
18 changes: 11 additions & 7 deletions src/coreclr/jit/lsrariscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,19 +346,23 @@ int LinearScan::BuildNode(GenTree* tree)

case GT_INTRINSIC:
{
noway_assert((tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Abs) ||
(tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Ceiling) ||
(tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Floor) ||
(tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Round) ||
(tree->AsIntrinsic()->gtIntrinsicName == NI_System_Math_Sqrt));
NamedIntrinsic name = tree->AsIntrinsic()->gtIntrinsicName;
noway_assert((name == NI_System_Math_Abs) || (name == NI_System_Math_Sqrt) ||
(name == NI_System_Math_MinNumber) || (name == NI_System_Math_MaxNumber));

// Both operand and its result must be of the same floating point type.
GenTree* op1 = tree->gtGetOp1();
GenTree* op2 = tree->gtGetOp2IfPresent();
assert(varTypeIsFloating(op1));
assert(op1->TypeGet() == tree->TypeGet());

assert(op1->TypeIs(tree->TypeGet()));
BuildUse(op1);
srcCount = 1;
if (op2 != nullptr)
{
assert(op2->TypeIs(tree->TypeGet()));
BuildUse(op2);
srcCount++;
}
assert(dstCount == 1);
BuildDef(tree);
}
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.CoreLib/src/System/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ public static double ReciprocalEstimate(double d)
[Intrinsic]
public static double ReciprocalSqrtEstimate(double d)
{
#if MONO || TARGET_RISCV64 || TARGET_LOONGARCH64
#if MONO || TARGET_LOONGARCH64
return 1.0 / Sqrt(d);
#else
return ReciprocalSqrtEstimate(d);
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.CoreLib/src/System/MathF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ public static float ReciprocalEstimate(float x)
[Intrinsic]
public static float ReciprocalSqrtEstimate(float x)
{
#if MONO || TARGET_RISCV64 || TARGET_LOONGARCH64
#if MONO || TARGET_LOONGARCH64
return 1.0f / Sqrt(x);
#else
return ReciprocalSqrtEstimate(x);
Expand Down
Loading