Skip to content

Commit f2c544a

Browse files
authored
[RISC-V] Single bit checks (#117461)
* Lower const single bit test, variable WiP * Reuse xarch code for extracting bit index * Disable removeCast * Don't check JTRUE context for constant bit index lowering
1 parent 290d04d commit f2c544a

File tree

3 files changed

+164
-19
lines changed

3 files changed

+164
-19
lines changed

src/coreclr/jit/codegenriscv64.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5040,6 +5040,7 @@ void CodeGen::genCodeForShift(GenTree* tree)
50405040
}
50415041
else
50425042
{
5043+
assert(isImmed(tree));
50435044
instruction ins = genGetInsForOper(tree);
50445045
unsigned shiftByImm = (unsigned)shiftBy->AsIntCon()->gtIconVal;
50455046

src/coreclr/jit/lower.cpp

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4127,7 +4127,37 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
41274127
GenTree* op1 = cmp->gtGetOp1();
41284128
GenTreeIntCon* op2 = cmp->gtGetOp2()->AsIntCon();
41294129

4130-
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
4130+
#if defined(TARGET_XARCH) || defined(TARGET_ARM64) || defined(TARGET_RISCV64)
4131+
4132+
// If 'test' is a single bit test, leaves the tested expr in the left op, the bit index in the right op, and returns
4133+
// true. Otherwise, returns false.
4134+
auto tryReduceSingleBitTestOps = [this](GenTreeOp* test) -> bool {
4135+
assert(test->OperIs(GT_AND, GT_TEST_EQ, GT_TEST_NE));
4136+
GenTree* testedOp = test->gtOp1;
4137+
GenTree* bitOp = test->gtOp2;
4138+
#ifdef TARGET_RISCV64
4139+
if (bitOp->IsIntegralConstUnsignedPow2())
4140+
{
4141+
INT64 bit = bitOp->AsIntConCommon()->IntegralValue();
4142+
int log2 = BitOperations::Log2((UINT64)bit);
4143+
bitOp->AsIntConCommon()->SetIntegralValue(log2);
4144+
return true;
4145+
}
4146+
#endif
4147+
if (!bitOp->OperIs(GT_LSH))
4148+
std::swap(bitOp, testedOp);
4149+
4150+
if (bitOp->OperIs(GT_LSH) && varTypeIsIntOrI(bitOp) && bitOp->gtGetOp1()->IsIntegralConst(1))
4151+
{
4152+
BlockRange().Remove(bitOp->gtGetOp1());
4153+
BlockRange().Remove(bitOp);
4154+
test->gtOp1 = testedOp;
4155+
test->gtOp2 = bitOp->gtGetOp2();
4156+
return true;
4157+
}
4158+
return false;
4159+
};
4160+
41314161
ssize_t op2Value = op2->IconValue();
41324162

41334163
#ifdef TARGET_XARCH
@@ -4165,6 +4195,8 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
41654195
bool removeCast =
41664196
#ifdef TARGET_ARM64
41674197
(op2Value == 0) && cmp->OperIs(GT_EQ, GT_NE, GT_GT) && !castOp->isContained() &&
4198+
#elif defined(TARGET_RISCV64)
4199+
false && // disable, comparisons and bit operations are full-register only
41684200
#endif
41694201
(castOp->OperIs(GT_LCL_VAR, GT_CALL, GT_OR, GT_XOR, GT_AND)
41704202
#ifdef TARGET_XARCH
@@ -4222,6 +4254,52 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
42224254
cmp->SetOperRaw(GenTree::ReverseRelop(cmp->OperGet()));
42234255
}
42244256

4257+
#ifdef TARGET_RISCV64
4258+
if (op2Value == 0 && !andOp2->isContained() && tryReduceSingleBitTestOps(op1->AsOp()))
4259+
{
4260+
GenTree* testedOp = op1->gtGetOp1();
4261+
GenTree* bitIndexOp = op1->gtGetOp2();
4262+
4263+
if (bitIndexOp->IsIntegralConst())
4264+
{
4265+
// Shift the tested bit into the sign bit, then check if negative/positive.
4266+
// Work on whole registers because comparisons and compressed shifts are full-register only.
4267+
INT64 bitIndex = bitIndexOp->AsIntConCommon()->IntegralValue();
4268+
INT64 signBitIndex = genTypeSize(TYP_I_IMPL) * 8 - 1;
4269+
if (bitIndex < signBitIndex)
4270+
{
4271+
bitIndexOp->AsIntConCommon()->SetIntegralValue(signBitIndex - bitIndex);
4272+
bitIndexOp->SetContained();
4273+
op1->SetOperRaw(GT_LSH);
4274+
op1->gtType = TYP_I_IMPL;
4275+
}
4276+
else
4277+
{
4278+
// The tested bit is the sign bit, remove "AND bitIndex" and only check if negative/positive
4279+
assert(bitIndex == signBitIndex);
4280+
assert(genActualType(testedOp) == TYP_I_IMPL);
4281+
BlockRange().Remove(bitIndexOp);
4282+
BlockRange().Remove(op1);
4283+
cmp->AsOp()->gtOp1 = testedOp;
4284+
}
4285+
4286+
op2->gtType = TYP_I_IMPL;
4287+
cmp->SetOperRaw(cmp->OperIs(GT_NE) ? GT_LT : GT_GE);
4288+
cmp->ClearUnsigned();
4289+
4290+
return cmp;
4291+
}
4292+
4293+
// Shift the tested bit into the lowest bit, then AND with 1.
4294+
// The "EQ|NE 0" comparison is folded below as necessary.
4295+
var_types type = genActualType(testedOp);
4296+
op1->AsOp()->gtOp1 = andOp1 = comp->gtNewOperNode(GT_RSH, type, testedOp, bitIndexOp);
4297+
op1->AsOp()->gtOp2 = andOp2 = comp->gtNewIconNode(1, type);
4298+
BlockRange().InsertBefore(op1, andOp1, andOp2);
4299+
andOp2->SetContained();
4300+
}
4301+
#endif // TARGET_RISCV64
4302+
42254303
// Optimizes (X & 1) != 0 to (X & 1)
42264304
// Optimizes (X & 1) == 0 to ((NOT X) & 1)
42274305
// (== 1 or != 1) cases are transformed to (!= 0 or == 0) above
@@ -4257,6 +4335,7 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
42574335

42584336
if (op2Value == 0)
42594337
{
4338+
#ifndef TARGET_RISCV64
42604339
BlockRange().Remove(op1);
42614340
BlockRange().Remove(op2);
42624341

@@ -4300,6 +4379,7 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
43004379
}
43014380
}
43024381
#endif
4382+
#endif // !TARGET_RISCV64
43034383
}
43044384
else if (andOp2->IsIntegralConst() && GenTree::Compare(andOp2, op2))
43054385
{
@@ -4328,31 +4408,15 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
43284408
// Note that BT has the same behavior as LSH when the bit index exceeds the
43294409
// operand bit size - it uses (bit_index MOD bit_size).
43304410
//
4331-
4332-
GenTree* lsh = cmp->AsOp()->gtOp1;
4333-
GenTree* op = cmp->AsOp()->gtOp2;
4334-
4335-
if (!lsh->OperIs(GT_LSH))
4336-
{
4337-
std::swap(lsh, op);
4338-
}
4339-
4340-
if (lsh->OperIs(GT_LSH) && varTypeIsIntOrI(lsh) && lsh->gtGetOp1()->IsIntegralConst(1))
4411+
if (tryReduceSingleBitTestOps(cmp->AsOp()))
43414412
{
43424413
cmp->SetOper(cmp->OperIs(GT_TEST_EQ) ? GT_BITTEST_EQ : GT_BITTEST_NE);
4343-
4344-
BlockRange().Remove(lsh->gtGetOp1());
4345-
BlockRange().Remove(lsh);
4346-
4347-
cmp->AsOp()->gtOp1 = op;
4348-
cmp->AsOp()->gtOp2 = lsh->gtGetOp2();
43494414
cmp->gtGetOp2()->ClearContained();
4350-
43514415
return cmp->gtNext;
43524416
}
43534417
}
43544418
#endif // TARGET_XARCH
4355-
#endif // defined(TARGET_XARCH) || defined(TARGET_ARM64)
4419+
#endif // defined(TARGET_XARCH) || defined(TARGET_ARM64) || defined(TARGET_RISCV64)
43564420

43574421
// Optimize EQ/NE(relop/SETCC, 0) into (maybe reversed) cond.
43584422
if (cmp->OperIs(GT_EQ, GT_NE) && op2->IsIntegralConst(0) && (op1->OperIsCompare() || op1->OperIs(GT_SETCC)))

src/tests/JIT/Directed/BitTest/BitTest.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,51 @@ public class Program
3838
[MethodImpl(MethodImplOptions.NoInlining)]
3939
static bool I8_BT_mem_reg(ref long x, int y) => (x & (1L << y)) != 0;
4040

41+
42+
[MethodImpl(MethodImplOptions.NoInlining)]
43+
static bool I1_BT_reg_min(sbyte x) => (x & (1 << 7)) != 0;
44+
45+
[MethodImpl(MethodImplOptions.NoInlining)]
46+
static sbyte I1_BT_reg_min_JCC(sbyte x) => (sbyte)((x & (1 << 7)) == 0 ? (x + 1) : (x - 1));
47+
48+
[MethodImpl(MethodImplOptions.NoInlining)]
49+
static bool I2_BT_reg_min(short x) => (x & (1 << 15)) != 0;
50+
51+
[MethodImpl(MethodImplOptions.NoInlining)]
52+
static bool I4_BT_reg_min(int x) => (x & (1 << 31)) != 0;
53+
54+
[MethodImpl(MethodImplOptions.NoInlining)]
55+
static bool I4_BT_reg_min_EQ(int x) => (x & (1 << 31)) == 0;
56+
57+
[MethodImpl(MethodImplOptions.NoInlining)]
58+
static int I4_BT_reg_min_JCC(int x) => (x & (1 << 31)) == 0 ? (x + 1) : (x - 1);
59+
60+
[MethodImpl(MethodImplOptions.NoInlining)]
61+
static bool I8_BT_reg_min(long x) => (x & (1L << 63)) != 0;
62+
63+
64+
[MethodImpl(MethodImplOptions.NoInlining)]
65+
static bool I1_BT_reg_min_1(sbyte x) => (x & (1 << 6)) != 0;
66+
67+
[MethodImpl(MethodImplOptions.NoInlining)]
68+
static sbyte I1_BT_reg_min_1_JCC(sbyte x) => (sbyte)((x & (1 << 6)) == 0 ? (x + 1) : (x - 1));
69+
70+
[MethodImpl(MethodImplOptions.NoInlining)]
71+
static bool I2_BT_reg_min_1(short x) => (x & (1 << 14)) != 0;
72+
73+
[MethodImpl(MethodImplOptions.NoInlining)]
74+
static bool I4_BT_reg_min_1(int x) => (x & (1 << 30)) != 0;
75+
76+
[MethodImpl(MethodImplOptions.NoInlining)]
77+
static bool I4_BT_reg_min_1_EQ(int x) => (x & (1 << 30)) == 0;
78+
79+
[MethodImpl(MethodImplOptions.NoInlining)]
80+
static int I4_BT_reg_min_1_JCC(int x) => (x & (1 << 30)) == 0 ? (x + 1) : (x - 1);
81+
82+
[MethodImpl(MethodImplOptions.NoInlining)]
83+
static bool I8_BT_reg_min_1(long x) => (x & (1L << 62)) != 0;
84+
85+
4186
[Fact]
4287
public static int TestEntryPoint()
4388
{
@@ -107,6 +152,41 @@ public static int TestEntryPoint()
107152
pass &= I8_BT_mem_reg(ref i8one, 64);
108153
pass &= !I8_BT_mem_reg(ref i8two, 0);
109154

155+
pass &= I1_BT_reg_min(sbyte.MinValue);
156+
pass &= !I1_BT_reg_min(sbyte.MaxValue);
157+
pass &= !I1_BT_reg_min_1(sbyte.MinValue);
158+
pass &= I1_BT_reg_min_1(sbyte.MaxValue);
159+
160+
pass &= I1_BT_reg_min_JCC(sbyte.MinValue) == sbyte.MaxValue;
161+
pass &= I1_BT_reg_min_JCC(sbyte.MaxValue) == sbyte.MinValue;
162+
pass &= I1_BT_reg_min_1_JCC(sbyte.MinValue) == (sbyte.MinValue + 1);
163+
pass &= I1_BT_reg_min_1_JCC(sbyte.MaxValue) == (sbyte.MaxValue - 1);
164+
165+
pass &= I2_BT_reg_min(short.MinValue);
166+
pass &= !I2_BT_reg_min(short.MaxValue);
167+
pass &= !I2_BT_reg_min_1(short.MinValue);
168+
pass &= I2_BT_reg_min_1(short.MaxValue);
169+
170+
pass &= I4_BT_reg_min(int.MinValue);
171+
pass &= !I4_BT_reg_min(int.MaxValue);
172+
pass &= !I4_BT_reg_min_1(int.MinValue);
173+
pass &= I4_BT_reg_min_1(int.MaxValue);
174+
175+
pass &= !I4_BT_reg_min_EQ(int.MinValue);
176+
pass &= I4_BT_reg_min_EQ(int.MaxValue);
177+
pass &= I4_BT_reg_min_1_EQ(int.MinValue);
178+
pass &= !I4_BT_reg_min_1_EQ(int.MaxValue);
179+
180+
pass &= I4_BT_reg_min_JCC(int.MinValue) == int.MaxValue;
181+
pass &= I4_BT_reg_min_JCC(int.MaxValue) == int.MinValue;
182+
pass &= I4_BT_reg_min_1_JCC(int.MinValue) == (int.MinValue + 1);
183+
pass &= I4_BT_reg_min_1_JCC(int.MaxValue) == (int.MaxValue - 1);
184+
185+
pass &= I8_BT_reg_min(long.MinValue);
186+
pass &= !I8_BT_reg_min(long.MaxValue);
187+
pass &= !I8_BT_reg_min_1(long.MinValue);
188+
pass &= I8_BT_reg_min_1(long.MaxValue);
189+
110190
if (pass)
111191
{
112192
Console.WriteLine("PASSED");

0 commit comments

Comments
 (0)