Skip to content
Open
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
1 change: 1 addition & 0 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSm4, W("EnableArm64Sv
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zba, W("EnableRiscV64Zba"), 1, "Allows RiscV64 Zba hardware intrinsics to be disabled")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64Zbb"), 1, "Allows RiscV64 Zbb hardware intrinsics to be disabled")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbs, W("EnableRiscV64Zbs"), 1, "Allows RiscV64 Zbs hardware intrinsics to be disabled")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zicond, W("EnableRiscV64Zicond"), 1, "Allows RiscV64 Zicond hardware intrinsics to be disabled")
#endif

///
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/inc/corinfoinstructionset.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ enum CORINFO_InstructionSet
InstructionSet_Zba=2,
InstructionSet_Zbb=3,
InstructionSet_Zbs=4,
InstructionSet_Zicond=5,
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
InstructionSet_X86Base=1,
Expand Down Expand Up @@ -447,6 +448,8 @@ inline CORINFO_InstructionSetFlags EnsureInstructionSetFlagsAreValid(CORINFO_Ins
resultflags.RemoveInstructionSet(InstructionSet_Zba);
if (resultflags.HasInstructionSet(InstructionSet_Zbs) && !resultflags.HasInstructionSet(InstructionSet_RiscV64Base))
resultflags.RemoveInstructionSet(InstructionSet_Zbs);
if (resultflags.HasInstructionSet(InstructionSet_Zicond) && !resultflags.HasInstructionSet(InstructionSet_RiscV64Base))
resultflags.RemoveInstructionSet(InstructionSet_Zicond);
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
if (resultflags.HasInstructionSet(InstructionSet_X86Base) && !resultflags.HasInstructionSet(InstructionSet_X86Base_X64))
Expand Down Expand Up @@ -741,6 +744,8 @@ inline const char *InstructionSetToString(CORINFO_InstructionSet instructionSet)
return "Zbb";
case InstructionSet_Zbs :
return "Zbs";
case InstructionSet_Zicond :
return "Zicond";
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
case InstructionSet_X86Base :
Expand Down Expand Up @@ -942,6 +947,7 @@ inline CORINFO_InstructionSet InstructionSetFromR2RInstructionSet(ReadyToRunInst
case READYTORUN_INSTRUCTION_Zba: return InstructionSet_Zba;
case READYTORUN_INSTRUCTION_Zbb: return InstructionSet_Zbb;
case READYTORUN_INSTRUCTION_Zbs: return InstructionSet_Zbs;
case READYTORUN_INSTRUCTION_Zicond: return InstructionSet_Zicond;
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
case READYTORUN_INSTRUCTION_X86Base: return InstructionSet_X86Base;
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

constexpr GUID JITEEVersionIdentifier = { /* 31a04b06-915e-42a0-bbd2-c9c397677ae5 */
0x31a04b06,
0x915e,
0x42a0,
{0xbb, 0xd2, 0xc9, 0xc3, 0x97, 0x67, 0x7a, 0xe5}
constexpr GUID JITEEVersionIdentifier = { /* c7a85d30-48b8-4d2f-8767-89f520440ded */
0xc7a85d30,
0x48b8,
0x4d2f,
{0x87, 0x67, 0x89, 0xf5, 0x20, 0x44, 0x0d, 0xed}
};

#endif // JIT_EE_VERSIONING_GUID_H
1 change: 1 addition & 0 deletions src/coreclr/inc/readytoruninstructionset.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ enum ReadyToRunInstructionSet
READYTORUN_INSTRUCTION_SveAes=88,
READYTORUN_INSTRUCTION_SveSha3=89,
READYTORUN_INSTRUCTION_SveSm4=90,
READYTORUN_INSTRUCTION_Zicond=91,

};

Expand Down
59 changes: 59 additions & 0 deletions src/coreclr/jit/codegenriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3150,6 +3150,61 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree)
genProduceReg(tree);
}

//------------------------------------------------------------------------
// genCodeForSelect: Produce branch-free code for a GT_SELECT node using Zicond.
//
// SELECT(cond, trueVal, falseVal) =>
// czero.nez tmp, falseVal, cond ; tmp = (cond != 0) ? 0 : falseVal
// czero.eqz dst, trueVal, cond ; dst = (cond == 0) ? 0 : trueVal
// or dst, dst, tmp
//
// Degenerate cases (one operand is the zero register) collapse to a single czero.
//
// Arguments:
// tree - the GT_SELECT node
//
void CodeGen::genCodeForSelect(GenTreeOp* tree)
{
assert(tree->OperIs(GT_SELECT));
assert(m_compiler->compOpportunisticallyDependsOn(InstructionSet_Zicond));
assert(varTypeIsIntegralOrI(tree));

GenTreeConditional* sel = tree->AsConditional();
GenTree* cond = sel->gtCond;
GenTree* trueVal = sel->gtOp1;
GenTree* falseVal = sel->gtOp2;

genConsumeRegs(cond);
genConsumeRegs(trueVal);
genConsumeRegs(falseVal);

regNumber targetReg = tree->GetRegNum();
regNumber condReg = cond->GetRegNum();
regNumber trueReg = trueVal->isContained() ? REG_ZERO : trueVal->GetRegNum();
regNumber falseReg = falseVal->isContained() ? REG_ZERO : falseVal->GetRegNum();

emitter* emit = GetEmitter();
emitAttr attr = emitActualTypeSize(tree);

if (falseReg == REG_ZERO)
{
emit->emitIns_R_R_R(INS_czero_eqz, attr, targetReg, trueReg, condReg);
}
else if (trueReg == REG_ZERO)
{
emit->emitIns_R_R_R(INS_czero_nez, attr, targetReg, falseReg, condReg);
}
else
{
regNumber tmpReg = internalRegisters.Extract(tree);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
regNumber tmpReg = internalRegisters.Extract(tree);
regNumber tmpReg = internalRegisters.GetSingle(tree);

emit->emitIns_R_R_R(INS_czero_nez, attr, tmpReg, falseReg, condReg);
emit->emitIns_R_R_R(INS_czero_eqz, attr, targetReg, trueReg, condReg);
emit->emitIns_R_R_R(INS_or, attr, targetReg, targetReg, tmpReg);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
emit->emitIns_R_R_R(INS_or, attr, targetReg, targetReg, tmpReg);
emit->emitIns_R_R_R(INS_add, TYP_I_IMPL, targetReg, targetReg, tmpReg);

riscv/riscv-isa-manual#2335

}

genProduceReg(tree);
}

//------------------------------------------------------------------------
// genCodeForJumpCompare: Generates code for jmpCompare statement.
//
Expand Down Expand Up @@ -3940,6 +3995,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genCodeForCompare(treeNode->AsOp());
break;

case GT_SELECT:
genCodeForSelect(treeNode->AsOp());
break;

case GT_JCMP:
genCodeForJumpCompare(treeNode->AsOpCC());
break;
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6215,6 +6215,11 @@ int Compiler::compCompileAfterInit(CORINFO_MODULE_HANDLE classPtr,
{
instructionSetFlags.AddInstructionSet(InstructionSet_Zbb);
}

if (JitConfig.EnableRiscV64Zicond() != 0)
{
instructionSetFlags.AddInstructionSet(InstructionSet_Zicond);
}
#endif

// These calls are important and explicitly ordered to ensure that the flags are correct in
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -10698,6 +10698,7 @@ class Compiler
}
#endif // DEBUG

public:
bool notifyInstructionSetUsage(CORINFO_InstructionSet isa, bool supported) const;

// Answer the question: Is a particular ISA allowed to be used implicitly by optimizations?
Expand Down Expand Up @@ -10741,6 +10742,7 @@ class Compiler
return opts.compSupportsISA.HasInstructionSet(isa);
}

private:
#ifdef DEBUG
//------------------------------------------------------------------------
// canUseEvexEncodingDebugOnly - Answer the question: Is Evex encoding supported on this target.
Expand Down
17 changes: 16 additions & 1 deletion src/coreclr/jit/emitriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -850,7 +850,7 @@ void emitter::emitIns_R_R_R(
(INS_fadd_d <= ins && ins <= INS_fmax_d) || (INS_feq_s <= ins && ins <= INS_fle_s) ||
(INS_feq_d <= ins && ins <= INS_fle_d) || (INS_lr_w <= ins && ins <= INS_amomaxu_d) ||
(INS_sh1add <= ins && ins <= INS_sh3add_uw) || (INS_rol <= ins && ins <= INS_maxu) ||
(INS_bset <= ins && ins <= INS_binv))
(INS_bset <= ins && ins <= INS_binv) || (INS_czero_eqz <= ins && ins <= INS_czero_nez))
{
#ifdef DEBUG
switch (ins)
Expand Down Expand Up @@ -962,6 +962,9 @@ void emitter::emitIns_R_R_R(
case INS_bclr:
case INS_bext:
case INS_binv:

case INS_czero_eqz:
case INS_czero_nez:
break;
default:
NYI_RISCV64("illegal ins within emitIns_R_R_R!");
Expand Down Expand Up @@ -4041,6 +4044,18 @@ void emitter::emitDispInsName(
return emitDispIllegalInstruction(code);
printf("binv %s, %s, %s\n", rd, rs1, rs2);
return;
case 0b0000111:
switch (opcode3)
{
case 0b101:
printf("czero.eqz %s, %s, %s\n", rd, rs1, rs2);
return;
case 0b111:
printf("czero.nez %s, %s, %s\n", rd, rs1, rs2);
return;
default:
return emitDispIllegalInstruction(code);
}
default:
return emitDispIllegalInstruction(code);
}
Expand Down
34 changes: 32 additions & 2 deletions src/coreclr/jit/ifconversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,8 +639,38 @@ bool OptIfConversionDsc::optIfConvert(int* pReachabilityBudget)
#ifdef TARGET_RISCV64
if (select->OperIs(GT_SELECT))
{
JITDUMP("Skipping if-conversion that could not be optimized to ordinary operations\n");
return true;
// Without Zicond, riscv64 has no native lowering for GT_SELECT - the
// branchy form is kept. With Zicond the SELECT is lowered to a
// czero.{eqz,nez}/or sequence, but only for integer-typed selects.
bool canCodegenSelect = m_compiler->compOpportunisticallyDependsOn(InstructionSet_Zicond) &&
varTypeIsIntegralOrI(select->TypeGet());
if (!canCodegenSelect)
{
JITDUMP("Skipping if-conversion that could not be optimized to ordinary operations\n");
return true;
}

// RISC-V has no flag register, so any branchless SELECT must first materialize
// the condition into a 0/1 register before the czero/or sequence (>=5 insns).
// When one arm is a no-op (read of an existing local) and the other is a small
// immediate, the branchy form fits in ~2 insns (branch + addi), so keep it.
GenTreeConditional* sel = select->AsConditional();
GenTree* selTrue = sel->gtOp1;
GenTree* selFalse = sel->gtOp2;

auto isLocalNoOp = [](GenTree* n) {
return n->OperIs(GT_LCL_VAR);
};
auto isSmallImm = [](GenTree* n) {
return n->IsIntegralConst() && (n->AsIntConCommon()->IntegralValue() >= -2048) &&
(n->AsIntConCommon()->IntegralValue() <= 2047);
};
Comment on lines +664 to +667
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto isSmallImm = [](GenTree* n) {
return n->IsIntegralConst() && (n->AsIntConCommon()->IntegralValue() >= -2048) &&
(n->AsIntConCommon()->IntegralValue() <= 2047);
};
auto isSmallImm = [](GenTree* n) {
return n->IsIntegralConst() && emitter::isValidSimm12(n->AsIntConCommon()->IntegralValue());
};


if ((isLocalNoOp(selTrue) && isSmallImm(selFalse)) || (isSmallImm(selTrue) && isLocalNoOp(selFalse)))
{
JITDUMP("Skipping if-conversion: branchy form fits in ~2 insns; Zicond would need 5+\n");
return true;
}
}
#endif

Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/instrsriscv64.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ INST(bclri, "bclri", 0, 0x48001013)
INST(bexti, "bexti", 0, 0x48005013)
INST(binvi, "binvi", 0, 0x68001013)

// Zicond (RV32 + RV64)
//// R_R_R
INST(czero_eqz, "czero.eqz", 0, 0x0e005033)
INST(czero_nez, "czero.nez", 0, 0x0e007033)

// RVC
INST(c_mv, "c.mv", 0, 0x00008002)
INST(c_add, "c.add", 0, 0x00009002)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ RELEASE_CONFIG_INTEGER(EnableArm64SveSm4, "EnableArm64SveSm4",
RELEASE_CONFIG_INTEGER(EnableRiscV64Zba, "EnableRiscV64Zba", 1) // Allows RiscV64 Zba hardware intrinsics to be disabled
RELEASE_CONFIG_INTEGER(EnableRiscV64Zbb, "EnableRiscV64Zbb", 1) // Allows RiscV64 Zbb hardware intrinsics to be disabled
RELEASE_CONFIG_INTEGER(EnableRiscV64Zbs, "EnableRiscV64Zbs", 1) // Allows RiscV64 Zbs hardware intrinsics to be disabled
RELEASE_CONFIG_INTEGER(EnableRiscV64Zicond, "EnableRiscV64Zicond", 1) // Allows RiscV64 Zicond hardware intrinsics to be disabled
#endif

RELEASE_CONFIG_INTEGER(EnableEmbeddedBroadcast, "EnableEmbeddedBroadcast", 1) // Allows embedded broadcasts to be disabled
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4805,8 +4805,9 @@ GenTree* Lowering::LowerSelect(GenTreeConditional* select)
// TODO-CQ: If we allowed multiple nodes to consume the same CPU flags then
// we could do this on x86. We currently disable if-conversion for TYP_LONG
// on 32-bit architectures because of this.
GenCondition selectCond;
GenTreeOpCC* newSelect = nullptr;
#if !defined(TARGET_LOONGARCH64) && !defined(TARGET_RISCV64)
GenCondition selectCond;
if (((select->gtFlags & GTF_SET_FLAGS) == 0) && TryLowerConditionToFlagsNode(select, cond, &selectCond))
{
select->SetOper(GT_SELECTCC);
Expand All @@ -4818,6 +4819,7 @@ GenTree* Lowering::LowerSelect(GenTreeConditional* select)
JITDUMP("\n");
}
else
#endif
{
ContainCheckSelect(select);
}
Expand Down
56 changes: 55 additions & 1 deletion src/coreclr/jit/lowerriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,61 @@ void Lowering::ContainCheckCompare(GenTreeOp* cmp)
//
void Lowering::ContainCheckSelect(GenTreeOp* node)
{
noway_assert(!"GT_SELECT nodes are not supported on riscv64");
assert(node->OperIs(GT_SELECT));
// GT_SELECT is only produced/codegen'd on riscv64 when Zicond is available; integer-only.
assert(m_compiler->compOpportunisticallyDependsOn(InstructionSet_Zicond));
assert(varTypeIsIntegralOrI(node));

GenTreeConditional* sel = node->AsConditional();

// czero.{eqz,nez} encode both polarities, so reversed relops (GE/LE and unsigned
// EQ/NE-with-zero) that would otherwise emit slt+xori can have the trailing xori
// dropped by swapping the SELECT arms and using the un-reversed compare directly.
GenTree* cond = sel->gtCond;
if (cond->OperIsCompare())
{
GenTreeOp* relop = cond->AsOp();
genTreeOps oper = relop->OperGet();
// genCodeForCompare reverses GE/LE into LT/GT and emits a trailing xori; it
// also rewrites unsigned EQ(x,0)/NE(x,0) into LE/GT and reverses again. Both
// patterns end up materializing the negation as xori reg, reg, 1.
Copy link
Copy Markdown
Member

@tomeksowi tomeksowi May 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xori 1 is also added for for floating comparisons, you could do away with it similar to JTRUE:

if (cmp->OperIsCompare() && varTypeIsFloating(cmp->gtGetOp1()) && (cmp->gtFlags & GTF_RELOP_NAN_UN) != 0)
{
// Unordered floating-point comparisons are achieved by neg'ing the ordered counterparts. Avoid that by
// reversing both the FP comparison and the zero-comparison fused with the branch.
cmp->ChangeOper(GenTree::ReverseRelop(cmp->OperGet()));
cmp->gtFlags &= ~GTF_RELOP_NAN_UN;
code = GenCondition::EQ;
}

bool relopIsReversed = oper == GT_GE || oper == GT_LE;
if (!relopIsReversed && relop->OperIs(GT_EQ, GT_NE))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!relopIsReversed && relop->OperIs(GT_EQ, GT_NE))
if (relop->OperIs(GT_EQ, GT_NE))

nit, if it's EQ or NE, then it's not LE or GE

{
GenTree* relopOp2 = relop->gtGetOp2();
relopIsReversed = oper == GT_EQ && relopOp2->IsIntegralConst(0) && relopOp2->isContained();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could remove EQ|NE 0 and leave just op1, czero already compares the condition register with zero.

}
LIR::Use condUse;
if (relopIsReversed && BlockRange().TryGetUse(cond, &condUse) && (condUse.User() == sel))
{
relop->SetOper(GenTree::ReverseRelop(oper));
std::swap(sel->gtOp1, sel->gtOp2);
}
}
// Also fold an explicit XOR-with-1 negation produced by other lowerings.
else if (cond->OperIs(GT_XOR))
{
GenTree* xorLhs = cond->AsOp()->gtGetOp1();
GenTree* xorRhs = cond->AsOp()->gtGetOp2();
LIR::Use condUse;
if (xorRhs->IsIntegralConst(1) && xorLhs->OperIsCompare() && BlockRange().TryGetUse(cond, &condUse) &&
(condUse.User() == sel))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check the user? Isn't sel always the user of cond given that's its operand? (same question for the check on line 1311)

{
sel->gtCond = xorLhs;
std::swap(sel->gtOp1, sel->gtOp2);
BlockRange().Remove(xorRhs);
BlockRange().Remove(cond);
}
}

// czero.{eqz,nez} take a register source and a register condition, so the only
// contained form is an integral-zero operand that can be expressed via REG_ZERO.
GenTree* op1 = sel->gtOp1;
GenTree* op2 = sel->gtOp2;
if (op1->IsIntegralConst(0) && !op1->AsIntCon()->ImmedValNeedsReloc(m_compiler))
MakeSrcContained(node, op1);
Comment on lines +1337 to +1338
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this happen? Isn't SELECT normalized to have a constant always on RHS?

if (op2->IsIntegralConst(0) && !op2->AsIntCon()->ImmedValNeedsReloc(m_compiler))
MakeSrcContained(node, op2);
}

//------------------------------------------------------------------------
Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/jit/lsrariscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,19 @@ int LinearScan::BuildNode(GenTree* tree)
srcCount = BuildCmp(tree);
break;

case GT_SELECT:
{
GenTreeConditional* sel = tree->AsConditional();
srcCount = BuildOperandUses(sel->gtCond);
srcCount += BuildOperandUses(sel->gtOp1);
srcCount += BuildOperandUses(sel->gtOp2);
buildInternalIntRegisterDefForNode(tree);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a temp if the constant op is zero

assert(dstCount == 1);
BuildDef(tree);
buildInternalRegisterUses();
}
break;

case GT_CKFINITE:
srcCount = 1;
assert(dstCount == 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ private static class RiscV64IntrinsicConstants
public const int Zba = (1 << 0);
public const int Zbb = (1 << 1);
public const int Zbs = (1 << 2);
public const int Zicond = (1 << 3);

public static void AddToBuilder(InstructionSetSupportBuilder builder, int flags)
{
Expand All @@ -333,6 +334,8 @@ public static void AddToBuilder(InstructionSetSupportBuilder builder, int flags)
builder.AddSupportedInstructionSet("zbb");
if ((flags & Zbs) != 0)
builder.AddSupportedInstructionSet("zbs");
if ((flags & Zicond) != 0)
builder.AddSupportedInstructionSet("zicond");
}

public static int FromInstructionSet(InstructionSet instructionSet)
Expand All @@ -346,6 +349,7 @@ public static int FromInstructionSet(InstructionSet instructionSet)
InstructionSet.RiscV64_Zba => Zba,
InstructionSet.RiscV64_Zbb => Zbb,
InstructionSet.RiscV64_Zbs => Zbs,
InstructionSet.RiscV64_Zicond => Zicond,

_ => throw new NotSupportedException(((InstructionSet_RiscV64)instructionSet).ToString())
};
Expand Down
Loading
Loading