This commit is contained in:
Tomasz Sowiński 2025-07-30 15:54:01 +02:00 committed by GitHub
commit d520a5a189
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 402 additions and 67 deletions

View File

@ -706,6 +706,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Sve2, W("EnableArm64Sv
#elif defined(TARGET_RISCV64)
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")
#endif
// Runtime-async

View File

@ -48,6 +48,7 @@ enum CORINFO_InstructionSet
InstructionSet_RiscV64Base=1,
InstructionSet_Zba=2,
InstructionSet_Zbb=3,
InstructionSet_Zbs=4,
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
InstructionSet_X86Base=1,
@ -389,6 +390,8 @@ inline CORINFO_InstructionSetFlags EnsureInstructionSetFlagsAreValid(CORINFO_Ins
resultflags.RemoveInstructionSet(InstructionSet_Zbb);
if (resultflags.HasInstructionSet(InstructionSet_Zba) && !resultflags.HasInstructionSet(InstructionSet_RiscV64Base))
resultflags.RemoveInstructionSet(InstructionSet_Zba);
if (resultflags.HasInstructionSet(InstructionSet_Zbs) && !resultflags.HasInstructionSet(InstructionSet_RiscV64Base))
resultflags.RemoveInstructionSet(InstructionSet_Zbs);
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
if (resultflags.HasInstructionSet(InstructionSet_X86Base) && !resultflags.HasInstructionSet(InstructionSet_X86Base_X64))
@ -667,6 +670,8 @@ inline const char *InstructionSetToString(CORINFO_InstructionSet instructionSet)
return "Zba";
case InstructionSet_Zbb :
return "Zbb";
case InstructionSet_Zbs :
return "Zbs";
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
case InstructionSet_X86Base :
@ -864,6 +869,7 @@ inline CORINFO_InstructionSet InstructionSetFromR2RInstructionSet(ReadyToRunInst
case READYTORUN_INSTRUCTION_RiscV64Base: return InstructionSet_RiscV64Base;
case READYTORUN_INSTRUCTION_Zba: return InstructionSet_Zba;
case READYTORUN_INSTRUCTION_Zbb: return InstructionSet_Zbb;
case READYTORUN_INSTRUCTION_Zbs: return InstructionSet_Zbs;
#endif // TARGET_RISCV64
#ifdef TARGET_AMD64
case READYTORUN_INSTRUCTION_X86Base: return InstructionSet_X86Base;

View File

@ -37,11 +37,11 @@
#include <minipal/guid.h>
constexpr GUID JITEEVersionIdentifier = { /* 2d40ec46-2e41-4a8b-8349-3c1267b95821 */
0x2d40ec46,
0x2e41,
0x4a8b,
{0x83, 0x49, 0x3c, 0x12, 0x67, 0xb9, 0x58, 0x21}
constexpr GUID JITEEVersionIdentifier = { /* 6c4c75a1-33ba-48c5-a49d-59e8b92cef0d */
0x6c4c75a1,
0x33ba,
0x48c5,
{0xa4, 0x9d, 0x59, 0xe8, 0xb9, 0x2c, 0xef, 0x0d}
};
#endif // JIT_EE_VERSIONING_GUID_H

View File

@ -91,6 +91,7 @@ enum ReadyToRunInstructionSet
READYTORUN_INSTRUCTION_Avx512Vp2intersect_VL=81,
READYTORUN_INSTRUCTION_Avx512Vpopcntdq=82,
READYTORUN_INSTRUCTION_Avx512Vpopcntdq_VL=83,
READYTORUN_INSTRUCTION_Zbs=84,
};

View File

@ -1074,7 +1074,8 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode)
regNumber targetReg = treeNode->GetRegNum();
emitter* emit = GetEmitter();
assert(treeNode->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_AND, GT_AND_NOT, GT_OR, GT_OR_NOT, GT_XOR, GT_XOR_NOT));
assert(treeNode->OperIs(GT_ADD, GT_SUB, GT_MUL, GT_AND, GT_AND_NOT, GT_OR, GT_OR_NOT, GT_XOR, GT_XOR_NOT,
GT_BIT_SET, GT_BIT_CLEAR, GT_BIT_INVERT));
GenTree* op1 = treeNode->gtGetOp1();
GenTree* op2 = treeNode->gtGetOp2();
@ -2683,6 +2684,21 @@ instruction CodeGen::genGetInsForOper(GenTree* treeNode)
ins = INS_xnor;
break;
case GT_BIT_SET:
assert(compiler->compOpportunisticallyDependsOn(InstructionSet_Zbs));
ins = isImmed(treeNode) ? INS_bseti : INS_bset;
break;
case GT_BIT_CLEAR:
assert(compiler->compOpportunisticallyDependsOn(InstructionSet_Zbs));
ins = isImmed(treeNode) ? INS_bclri : INS_bclr;
break;
case GT_BIT_INVERT:
assert(compiler->compOpportunisticallyDependsOn(InstructionSet_Zbs));
ins = isImmed(treeNode) ? INS_binvi : INS_binv;
break;
default:
NO_WAY("Unhandled oper in genGetInsForOper() - integer");
break;
@ -4120,6 +4136,9 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
case GT_AND_NOT:
case GT_OR_NOT:
case GT_XOR_NOT:
case GT_BIT_SET:
case GT_BIT_CLEAR:
case GT_BIT_INVERT:
assert(varTypeIsIntegralOrI(treeNode));
FALLTHROUGH;

View File

@ -728,7 +728,8 @@ void emitter::emitIns_R_R_I(
if ((INS_addi <= ins && INS_srai >= ins) || (INS_addiw <= ins && INS_sraiw >= ins) ||
(INS_lb <= ins && INS_lhu >= ins) || INS_ld == ins || INS_lw == ins || INS_jalr == ins || INS_fld == ins ||
INS_flw == ins || INS_slli_uw == ins || INS_rori == ins || INS_roriw == ins)
INS_flw == ins || INS_slli_uw == ins || INS_rori == ins || INS_roriw == ins ||
(INS_bseti <= ins && ins <= INS_binvi))
{
assert(isGeneralRegister(reg2));
code |= (reg1 & 0x1f) << 7; // rd
@ -827,7 +828,8 @@ void emitter::emitIns_R_R_R(
(INS_addw <= ins && ins <= INS_sraw) || (INS_fadd_s <= ins && ins <= INS_fmax_s) ||
(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_sh1add <= ins && ins <= INS_sh3add_uw) || (INS_rol <= ins && ins <= INS_maxu) ||
(INS_bset <= ins && ins <= INS_binv))
{
#ifdef DEBUG
switch (ins)
@ -934,6 +936,11 @@ void emitter::emitIns_R_R_R(
case INS_minu:
case INS_max:
case INS_maxu:
case INS_bset:
case INS_bclr:
case INS_bext:
case INS_binv:
break;
default:
NYI_RISCV64("illegal ins within emitIns_R_R_R!");
@ -3675,7 +3682,7 @@ void emitter::emitDispInsName(
case 0x1:
{
unsigned funct6 = (imm12 >> 6) & 0x3f;
unsigned shamt = imm12 & 0x3f; // 6 BITS for SHAMT in RISCV6
unsigned shamt = imm12 & 0x3f; // 6 BITS for SHAMT in RISCV64
switch (funct6)
{
case 0b011000:
@ -3692,12 +3699,20 @@ void emitter::emitDispInsName(
}
case 0b000000:
printLength = printf("slli");
imm12 = shamt;
break;
case 0b001010:
printLength = printf("bseti");
break;
case 0b010010:
printLength = printf("bclri");
break;
case 0b011010:
printLength = printf("binvi");
break;
default:
return emitDispIllegalInstruction(code);
}
imm12 = shamt;
}
break;
case 0x2: // SLTI
@ -3717,7 +3732,7 @@ void emitter::emitDispInsName(
printLength = printf("xori");
}
break;
case 0x5: // SRLI & SRAI
case 0x5:
{
unsigned funct6 = (imm12 >> 6) & 0x3f;
imm12 &= 0x3f; // 6BITS for SHAMT in RISCV64
@ -3732,6 +3747,9 @@ void emitter::emitDispInsName(
case 0b011000:
printLength = printf("rori");
break;
case 0b010010:
printLength = printf("bexti");
break;
case 0b011010:
if (imm12 != 0b111000) // shift amount is treated as additional funct opcode
return emitDispIllegalInstruction(code);
@ -3982,6 +4000,28 @@ void emitter::emitDispInsName(
printf("%s %s, %s, %s\n", names[opcode3 & 0b11], rd, rs1, rs2);
return;
}
case 0b0010100:
if (opcode3 != 0b001)
return emitDispIllegalInstruction(code);
printf("bset %s, %s, %s\n", rd, rs1, rs2);
return;
case 0b0100100:
switch (opcode3)
{
case 0b001:
printf("bclr %s, %s, %s\n", rd, rs1, rs2);
return;
case 0b101:
printf("bext %s, %s, %s\n", rd, rs1, rs2);
return;
default:
return emitDispIllegalInstruction(code);
}
case 0b0110100:
if (opcode3 != 0b001)
return emitDispIllegalInstruction(code);
printf("binv %s, %s, %s\n", rd, rs1, rs2);
return;
default:
return emitDispIllegalInstruction(code);
}
@ -5142,8 +5182,22 @@ regNumber emitter::emitInsTernary(instruction ins, emitAttr attr, GenTree* dst,
ins = INS_addiw;
imm = -imm;
}
else if (ins == INS_bseti || ins == INS_bclri || ins == INS_bexti || ins == INS_binvi)
{
// Use base instructions where possible:
// "bexti rd, rs, 0" is equivalent to "andi rd, rs, 1"
// "bseti/bclri/binvi rd, rs, imm" are equivalent to "ori/andi/xori rd, rs, (~)(1 << imm)" for imm < 11
int minBitIndex = (ins == INS_bexti) ? 1 : 11;
int maxBitIndex = emitActualTypeSize(src1) * 8;
if (ins != INS_bexti && attr == EA_4BYTE)
maxBitIndex--; // can't touch the sign bit alone, it affects sign extension
assert(ins == INS_addi || ins == INS_addiw || ins == INS_andi || ins == INS_ori || ins == INS_xori);
assert(imm >= minBitIndex);
assert(imm < maxBitIndex);
}
assert(ins == INS_addi || ins == INS_addiw || ins == INS_andi || ins == INS_ori || ins == INS_xori ||
ins == INS_bseti || ins == INS_bclri || ins == INS_bexti || ins == INS_binvi);
regNumber tempReg = needCheckOv ? codeGen->internalRegisters.Extract(dst) : REG_NA;
@ -5291,6 +5345,9 @@ regNumber emitter::emitInsTernary(instruction ins, emitAttr attr, GenTree* dst,
case GT_OR_NOT:
case GT_XOR:
case GT_XOR_NOT:
case GT_BIT_SET:
case GT_BIT_CLEAR:
case GT_BIT_INVERT:
{
emitIns_R_R_R(ins, attr, dstReg, src1Reg, src2Reg);

View File

@ -294,6 +294,13 @@ GTNODE(SH3ADD_UW , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
GTNODE(ADD_UW , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
// Maps to riscv64 slli.uw instruction. Computes result = zext(op1[31..0]) << imm.
GTNODE(SLLI_UW , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
// Maps to bset/bseti instruction. Computes result = op1 | (1 << op2)
GTNODE(BIT_SET , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
// Maps to bclr/bclri instruction. Computes result = op1 & ~(1 << op2)
GTNODE(BIT_CLEAR , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
// Maps to binv/binvi instruction. Computes result = op1 ^ (1 << op2)
GTNODE(BIT_INVERT , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
#endif
//-----------------------------------------------------------------------------

View File

@ -307,6 +307,19 @@ INST(sh2add_uw, "sh2add_uw", 0, 0x2000403b)
INST(sh3add_uw, "sh3add_uw", 0, 0x2000603b)
INST(slli_uw, "slli_uw", 0, 0x0800101b)
// Zbs (RV32 + RV64)
//// R_R_R
INST(bset, "bset", 0, 0x28001033)
INST(bclr, "bclr", 0, 0x48001033)
INST(bext, "bext", 0, 0x48005033)
INST(binv, "binv", 0, 0x68001033)
//// R_R_I
INST(bseti, "bseti", 0, 0x28001013)
INST(bclri, "bclri", 0, 0x48001013)
INST(bexti, "bexti", 0, 0x48005013)
INST(binvi, "binvi", 0, 0x68001013)
// clang-format on
/*****************************************************************************/
#undef INST

View File

@ -429,6 +429,7 @@ RELEASE_CONFIG_INTEGER(EnableArm64Sve2, "EnableArm64Sve2",
#elif defined(TARGET_RISCV64)
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
#endif
RELEASE_CONFIG_INTEGER(EnableEmbeddedBroadcast, "EnableEmbeddedBroadcast", 1) // Allows embedded broadcasts to be disabled

View File

@ -197,74 +197,149 @@ GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue)
//
GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp)
{
if (comp->opts.OptimizationEnabled())
GenTree*& op1 = binOp->gtOp1;
GenTree*& op2 = binOp->gtOp2;
bool isOp1Negated = op1->OperIs(GT_NOT);
bool isOp2Negated = op2->OperIs(GT_NOT);
ContainCheckBinary(binOp);
if (!comp->opts.OptimizationEnabled())
return binOp->gtNext;
if (comp->compOpportunisticallyDependsOn(InstructionSet_Zbs) && binOp->OperIs(GT_OR, GT_XOR, GT_AND))
{
GenTree*& op1 = binOp->gtOp1;
GenTree*& op2 = binOp->gtOp2;
bool isOp1Negated = op1->OperIs(GT_NOT);
bool isOp2Negated = op2->OperIs(GT_NOT);
if (binOp->OperIs(GT_AND, GT_OR, GT_XOR) && (isOp1Negated || isOp2Negated))
if (op2->IsIntegralConst())
{
if ((isOp1Negated && isOp2Negated) || comp->compOpportunisticallyDependsOn(InstructionSet_Zbb))
GenTreeIntConCommon* constant = op2->AsIntConCommon();
UINT64 bit = (UINT64)constant->IntegralValue();
if (binOp->OperIs(GT_AND))
bit = ~bit;
if (!op2->isContained() && isPow2(bit) &&
(!op1->TypeIs(TYP_INT) || BitOperations::Log2(bit) != 31)) // don't change the sign bit alone
{
if (isOp1Negated)
{
BlockRange().Remove(op1);
op1 = op1->AsUnOp()->gtGetOp1();
}
if (isOp2Negated)
assert(binOp->OperIs(GT_OR, GT_XOR, GT_AND));
static_assert(AreContiguous(GT_OR, GT_XOR, GT_AND), "");
constexpr genTreeOps singleBitOpers[] = {GT_BIT_SET, GT_BIT_INVERT, GT_BIT_CLEAR};
binOp->ChangeOper(singleBitOpers[binOp->OperGet() - GT_OR]);
bit = BitOperations::Log2(bit);
assert(bit >= 11); // smaller single-bit masks fit into ori/xori/andi
constant->SetIntegralValue(bit);
constant->SetContained();
}
}
else // op2 is not constant
{
GenTree* opp1 = isOp1Negated ? op1->gtGetOp1() : op1;
GenTree* opp2 = isOp2Negated ? op2->gtGetOp1() : op2;
bool isOp1SingleBit =
(isOp1Negated == binOp->OperIs(GT_AND)) && opp1->OperIs(GT_LSH) && opp1->gtGetOp1()->IsIntegralConst(1);
bool isOp2SingleBit =
(isOp2Negated == binOp->OperIs(GT_AND)) && opp2->OperIs(GT_LSH) && opp2->gtGetOp1()->IsIntegralConst(1);
if (isOp1SingleBit || isOp2SingleBit)
{
// a | (1 << b), a ^ (1 << b), a & ~(1 << b) => BIT_{SET,INVERT,CLEAR}(a, b)
if (isOp1SingleBit)
std::swap(op1, op2);
if (binOp->OperIs(GT_AND))
{
assert(op2->OperIs(GT_NOT));
BlockRange().Remove(op2);
op2 = op2->AsUnOp()->gtGetOp1();
op2 = op2->gtGetOp1();
}
if (isOp1Negated != isOp2Negated)
{
assert(comp->compOpportunisticallyDependsOn(InstructionSet_Zbb));
if (isOp1Negated)
std::swap(op1, op2);
assert(binOp->OperIs(GT_OR, GT_XOR, GT_AND));
static_assert(AreContiguous(GT_OR, GT_XOR, GT_AND), "");
constexpr genTreeOps singleBitOpers[] = {GT_BIT_SET, GT_BIT_INVERT, GT_BIT_CLEAR};
binOp->ChangeOper(singleBitOpers[binOp->OperGet() - GT_OR]);
genTreeOps operNot = GT_NONE;
switch (binOp->OperGet())
{
case GT_AND:
operNot = GT_AND_NOT;
break;
case GT_OR:
operNot = GT_OR_NOT;
break;
default:
assert(binOp->OperIs(GT_XOR));
operNot = GT_XOR_NOT;
break;
}
binOp->ChangeOper(operNot);
}
else if (binOp->OperIs(GT_AND, GT_OR)) // XOR is good after negation removal, (~a ^ ~b) == (a ^ b)
{
assert(isOp1Negated && isOp2Negated);
LIR::Use use;
if (BlockRange().TryGetUse(binOp, &use))
{
// (~a | ~b) == ~(a & b), (~a & ~b) == ~(a | b)
genTreeOps reverseOper = binOp->OperIs(GT_AND) ? GT_OR : GT_AND;
binOp->ChangeOper(reverseOper);
assert(op2->OperIs(GT_LSH));
assert(op2->gtGetOp1()->IsIntegralConst(1));
assert(!op2->gtGetOp2()->IsIntegralConst());
BlockRange().Remove(op2->gtGetOp1());
BlockRange().Remove(op2);
op2 = op2->gtGetOp2(); // shift amount becomes bit index
GenTreeUnOp* negation = comp->gtNewOperNode(GT_NOT, binOp->gtType, binOp);
BlockRange().InsertAfter(binOp, negation);
use.ReplaceWith(negation);
}
else
{
binOp->SetUnusedValue();
}
assert(op1->TypeIs(TYP_INT, TYP_LONG));
if (!op2->IsIntegralConst() && op1->TypeIs(TYP_INT))
{
// Zbs instructions don't have *w variants so wrap the bit index / shift amount to 0-31 manually
GenTreeIntCon* mask = comp->gtNewIconNode(0x1F);
mask->SetContained();
BlockRange().InsertAfter(op2, mask);
op2 = comp->gtNewOperNode(GT_AND, op2->TypeGet(), op2, mask);
BlockRange().InsertAfter(mask, op2);
}
}
}
}
ContainCheckBinary(binOp);
if (binOp->OperIs(GT_AND, GT_OR, GT_XOR) && (isOp1Negated || isOp2Negated))
{
if ((isOp1Negated && isOp2Negated) || comp->compOpportunisticallyDependsOn(InstructionSet_Zbb))
{
if (isOp1Negated)
{
BlockRange().Remove(op1);
op1 = op1->AsUnOp()->gtGetOp1();
}
if (isOp2Negated)
{
BlockRange().Remove(op2);
op2 = op2->AsUnOp()->gtGetOp1();
}
if (isOp1Negated != isOp2Negated)
{
assert(comp->compOpportunisticallyDependsOn(InstructionSet_Zbb));
op2->ClearContained(); // negated binary instructions don't have immediate variants
if (isOp1Negated)
std::swap(op1, op2);
genTreeOps operNot = GT_NONE;
switch (binOp->OperGet())
{
case GT_AND:
operNot = GT_AND_NOT;
break;
case GT_OR:
operNot = GT_OR_NOT;
break;
default:
assert(binOp->OperIs(GT_XOR));
operNot = GT_XOR_NOT;
break;
}
binOp->ChangeOper(operNot);
}
else if (binOp->OperIs(GT_AND, GT_OR)) // XOR is good after negation removal, (~a ^ ~b) == (a ^ b)
{
assert(isOp1Negated && isOp2Negated);
LIR::Use use;
if (BlockRange().TryGetUse(binOp, &use))
{
// (~a | ~b) == ~(a & b), (~a & ~b) == ~(a | b)
genTreeOps reverseOper = binOp->OperIs(GT_AND) ? GT_OR : GT_AND;
binOp->ChangeOper(reverseOper);
GenTreeUnOp* negation = comp->gtNewOperNode(GT_NOT, binOp->gtType, binOp);
BlockRange().InsertAfter(binOp, negation);
use.ReplaceWith(negation);
}
else
{
binOp->SetUnusedValue();
}
}
}
}
return binOp->gtNext;
}

View File

@ -269,6 +269,9 @@ int LinearScan::BuildNode(GenTree* tree)
case GT_SH3ADD_UW:
case GT_ADD_UW:
case GT_SLLI_UW:
case GT_BIT_SET:
case GT_BIT_CLEAR:
case GT_BIT_INVERT:
if (tree->OperIs(GT_ROR, GT_ROL) && !compiler->compOpportunisticallyDependsOn(InstructionSet_Zbb))
buildInternalIntRegisterDefForNode(tree);
srcCount = BuildBinaryUses(tree->AsOp());

View File

@ -306,6 +306,7 @@ namespace ILCompiler
{
public const int Zba = (1 << 0);
public const int Zbb = (1 << 1);
public const int Zbs = (1 << 2);
public static void AddToBuilder(InstructionSetSupportBuilder builder, int flags)
{
@ -313,6 +314,8 @@ namespace ILCompiler
builder.AddSupportedInstructionSet("zba");
if ((flags & Zbb) != 0)
builder.AddSupportedInstructionSet("zbb");
if ((flags & Zbs) != 0)
builder.AddSupportedInstructionSet("zbs");
}
public static int FromInstructionSet(InstructionSet instructionSet)
@ -325,6 +328,7 @@ namespace ILCompiler
// Optional ISAs - only available via opt-in or opportunistic light-up
InstructionSet.RiscV64_Zba => Zba,
InstructionSet.RiscV64_Zbb => Zbb,
InstructionSet.RiscV64_Zbs => Zbs,
_ => throw new NotSupportedException(((InstructionSet_RiscV64)instructionSet).ToString())
};

View File

@ -94,6 +94,7 @@ namespace Internal.ReadyToRunConstants
Avx512Vp2intersect_VL=81,
Avx512Vpopcntdq=82,
Avx512Vpopcntdq_VL=83,
Zbs=84,
}
}

View File

@ -62,6 +62,7 @@ namespace Internal.ReadyToRunConstants
case InstructionSet.RiscV64_RiscV64Base: return ReadyToRunInstructionSet.RiscV64Base;
case InstructionSet.RiscV64_Zba: return ReadyToRunInstructionSet.Zba;
case InstructionSet.RiscV64_Zbb: return ReadyToRunInstructionSet.Zbb;
case InstructionSet.RiscV64_Zbs: return ReadyToRunInstructionSet.Zbs;
default: throw new Exception("Unknown instruction set");
}

View File

@ -46,6 +46,7 @@ namespace Internal.JitInterface
RiscV64_RiscV64Base = InstructionSet_RiscV64.RiscV64Base,
RiscV64_Zba = InstructionSet_RiscV64.Zba,
RiscV64_Zbb = InstructionSet_RiscV64.Zbb,
RiscV64_Zbs = InstructionSet_RiscV64.Zbs,
X64_X86Base = InstructionSet_X64.X86Base,
X64_SSE42 = InstructionSet_X64.SSE42,
X64_AVX = InstructionSet_X64.AVX,
@ -181,6 +182,7 @@ namespace Internal.JitInterface
RiscV64Base = 1,
Zba = 2,
Zbb = 3,
Zbs = 4,
}
public enum InstructionSet_X64
@ -518,6 +520,8 @@ namespace Internal.JitInterface
resultflags.AddInstructionSet(InstructionSet.RiscV64_RiscV64Base);
if (resultflags.HasInstructionSet(InstructionSet.RiscV64_Zba))
resultflags.AddInstructionSet(InstructionSet.RiscV64_RiscV64Base);
if (resultflags.HasInstructionSet(InstructionSet.RiscV64_Zbs))
resultflags.AddInstructionSet(InstructionSet.RiscV64_RiscV64Base);
break;
case TargetArchitecture.X64:
@ -794,6 +798,8 @@ namespace Internal.JitInterface
resultflags.AddInstructionSet(InstructionSet.RiscV64_Zbb);
if (resultflags.HasInstructionSet(InstructionSet.RiscV64_RiscV64Base))
resultflags.AddInstructionSet(InstructionSet.RiscV64_Zba);
if (resultflags.HasInstructionSet(InstructionSet.RiscV64_RiscV64Base))
resultflags.AddInstructionSet(InstructionSet.RiscV64_Zbs);
break;
case TargetArchitecture.X64:
@ -1040,6 +1046,7 @@ namespace Internal.JitInterface
yield return new InstructionSetInfo("base", "RiscV64Base", InstructionSet.RiscV64_RiscV64Base, true);
yield return new InstructionSetInfo("zba", "Zba", InstructionSet.RiscV64_Zba, true);
yield return new InstructionSetInfo("zbb", "Zbb", InstructionSet.RiscV64_Zbb, true);
yield return new InstructionSetInfo("zbs", "Zbs", InstructionSet.RiscV64_Zbs, true);
break;
case TargetArchitecture.X64:
@ -1452,6 +1459,9 @@ namespace Internal.JitInterface
case "Zbb":
{ return InstructionSet.RiscV64_Zbb; }
case "Zbs":
{ return InstructionSet.RiscV64_Zbs; }
}
break;

View File

@ -259,9 +259,11 @@ definearch ,RiscV64 ,64Bit ,RiscV64, RiscV64
instructionset ,RiscV64 ,RiscV64Base , ,56 ,RiscV64Base ,base
instructionset ,RiscV64 ,Zba , ,57 ,Zba ,zba
instructionset ,RiscV64 ,Zbb , ,58 ,Zbb ,zbb
instructionset ,RiscV64 ,Zbs , ,84 ,Zbs ,zbs
implication ,RiscV64 ,Zbb ,RiscV64Base
implication ,RiscV64 ,Zba ,RiscV64Base
implication ,RiscV64 ,Zbs ,RiscV64Base
; ,name and aliases ,archs ,lower baselines included by implication
instructionsetgroup ,x86-64 ,X64 X86 ,base

View File

@ -1416,6 +1416,11 @@ void EEJitManager::SetCpuInfo()
{
CPUCompileFlags.Set(InstructionSet_Zbb);
}
if (((cpuFeatures & RiscV64IntrinsicConstants_Zbs) != 0) && CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableRiscV64Zbs))
{
CPUCompileFlags.Set(InstructionSet_Zbs);
}
#endif
// These calls are very important as it ensures the flags are consistent with any

View File

@ -606,6 +606,11 @@ int minipal_getcpufeatures(void)
{
result |= RiscV64IntrinsicConstants_Zbb;
}
if (pairs[0].value & RISCV_HWPROBE_EXT_ZBS)
{
result |= RiscV64IntrinsicConstants_Zbs;
}
}
#endif // HAVE_HWPROBE_H

View File

@ -56,6 +56,7 @@ static_assert((1 << ARM64_ATOMICS_FEATURE_FLAG_BIT) == ARM64IntrinsicConstants_A
#if defined(HOST_RISCV64)
#define RiscV64IntrinsicConstants_Zba (1 << 0)
#define RiscV64IntrinsicConstants_Zbb (1 << 1)
#define RiscV64IntrinsicConstants_Zbs (1 << 2)
#endif // HOST_RISCV64
#ifdef __cplusplus

View File

@ -0,0 +1,108 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Runtime.CompilerServices;
using Xunit;
public static class SingleBit
{
[MethodImpl(MethodImplOptions.NoInlining)]
static int Set(int a, int b) => a | (1 << b);
[MethodImpl(MethodImplOptions.NoInlining)]
static int SetSwap(int a, int b) => (1 << b) | a ;
[MethodImpl(MethodImplOptions.NoInlining)]
static int Set10(int a) => a | (1 << 10);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Set11(int a) => a | (1 << 11);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Set31(int a) => a | (1 << 31);
[MethodImpl(MethodImplOptions.NoInlining)]
static int SetNegatedBit(int a, int b) => ~(1 << a) | (1 << b);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Clear(int a, int b) => a & ~(1 << b);
[MethodImpl(MethodImplOptions.NoInlining)]
static int ClearSwap(int a, int b) => ~(1 << b) & a;
[MethodImpl(MethodImplOptions.NoInlining)]
static int Clear10(int a) => a & ~(1 << 10);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Clear11(int a) => a & ~(1 << 11);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Clear31(int a) => a & ~(1 << 31);
[MethodImpl(MethodImplOptions.NoInlining)]
static int ClearNegatedBit(int a, int b) => ~(1 << a) & ~(1 << b);
[MethodImpl(MethodImplOptions.NoInlining)]
static int ClearPositiveBit(int a, int b) => (1 << a) & ~(1 << b);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Invert(int a, int b) => a ^ (1 << b);
[MethodImpl(MethodImplOptions.NoInlining)]
static int InvertSwap(int a, int b) => (1 << b) ^ a;
[MethodImpl(MethodImplOptions.NoInlining)]
static int Invert10(int a) => a ^ (1 << 10);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Invert11(int a) => a ^ (1 << 11);
[MethodImpl(MethodImplOptions.NoInlining)]
static int Invert31(int a) => a ^ (1 << 31);
[MethodImpl(MethodImplOptions.NoInlining)]
static int InvertNegatedBit(int a, int b) => ~(1 << a) ^ (1 << b);
[Fact]
public static void Test()
{
Assert.Equal(0x12345478, Set(0x12345078, 10 + 32));
Assert.Equal(0x12345878, Set(0x12345078, 11 + 32));
Assert.Equal(0x12345478, SetSwap(0x12345078, 10 + 32));
Assert.Equal(0x12345878, SetSwap(0x12345078, 11 + 32));
Assert.Equal(0x12345478, Set10(0x12345078));
Assert.Equal(0x12345878, Set11(0x12345078));
Assert.Equal(int.MinValue, Set31(0));
Assert.Equal(-1, SetNegatedBit(0, 0 + 32));
Assert.Equal(0x12345078, Clear(0x12345478, 10 + 32));
Assert.Equal(0x12345078, Clear(0x12345878, 11 + 32));
Assert.Equal(0x12345078, ClearSwap(0x12345478, 10 + 32));
Assert.Equal(0x12345078, ClearSwap(0x12345878, 11 + 32));
Assert.Equal(0x12345078, Clear10(0x12345478));
Assert.Equal(0x12345078, Clear11(0x12345878));
Assert.Equal(0, Clear31(int.MinValue));
Assert.Equal(-4, ClearNegatedBit(0, 1 + 32));
Assert.Equal(0, ClearPositiveBit(0, 0 + 32));
Assert.Equal(0x12345478, Invert(0x12345078, 10 + 32));
Assert.Equal(0x12345078, Invert(0x12345478, 10 + 32));
Assert.Equal(0x12345878, Invert(0x12345078, 11 + 32));
Assert.Equal(0x12345078, Invert(0x12345878, 11 + 32));
Assert.Equal(0x12345478, InvertSwap(0x12345078, 10 + 32));
Assert.Equal(0x12345078, InvertSwap(0x12345478, 10 + 32));
Assert.Equal(0x12345878, InvertSwap(0x12345078, 11 + 32));
Assert.Equal(0x12345078, InvertSwap(0x12345878, 11 + 32));
Assert.Equal(0x12345478, Invert10(0x12345078));
Assert.Equal(0x12345078, Invert10(0x12345478));
Assert.Equal(0x12345878, Invert11(0x12345078));
Assert.Equal(0x12345078, Invert11(0x12345878));
Assert.Equal(0, Invert31(int.MinValue));
Assert.Equal(int.MinValue, Invert31(0));
Assert.Equal(-1, InvertNegatedBit(0, 0 + 32));
Assert.Equal(-4, InvertNegatedBit(0, 1 + 32));
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CLRTestEnvironmentVariable -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
</PropertyGroup>
<PropertyGroup>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="SingleBit.cs"/>
<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
<CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="0" />
</ItemGroup>
</Project>