Skip to content
Open
1 change: 1 addition & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ class CodeGen final : public CodeGenInterface

void genCodeForDivMod(GenTreeOp* treeNode);
void genCodeForMul(GenTreeOp* treeNode);
void genCodeForBitOp(GenTreeOp* treeNode);
void genCodeForIncSaturate(GenTree* treeNode);
void genCodeForMulHi(GenTreeOp* treeNode);
void genLeaInstruction(GenTreeAddrMode* lea);
Expand Down
51 changes: 51 additions & 0 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,51 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode)
genProduceReg(treeNode);
}

//------------------------------------------------------------------------
// genCodeForBitOp: Generate code for a GT_BIT_SET/GT_BIT_CLEAR/GT_BIT_INVERT operation, i.e. the
// value-producing `bts`/`btr`/`btc` instructions which set, reset, or complement a single bit of op1
// selected by op2.
//
// Arguments:
// treeNode - the node to generate the code for
//
void CodeGen::genCodeForBitOp(GenTreeOp* treeNode)
{
assert(treeNode->OperIs(GT_BIT_SET, GT_BIT_CLEAR, GT_BIT_INVERT));

GenTree* op1 = treeNode->gtGetOp1(); // value (read-modify-write destination)
GenTree* op2 = treeNode->gtGetOp2(); // bit index

genConsumeOperands(treeNode);

regNumber targetReg = treeNode->GetRegNum();
var_types targetType = genActualType(treeNode);
emitAttr size = emitTypeSize(targetType);
emitter* emit = GetEmitter();

assert((targetType == TYP_INT) || (targetType == TYP_LONG));
assert(op1->isUsedFromReg() && op2->isUsedFromReg());

instruction ins = treeNode->OperIs(GT_BIT_SET) ? INS_bts : treeNode->OperIs(GT_BIT_CLEAR) ? INS_btr : INS_btc;

// These are read-modify-write: the `mov` below loads op1 (the value) into the destination and
// then `bts`/`btr`/`btc` reads the bit index from op2. LSRA marks op2 as delayFree except when
// op2 shares op1's interval and it's their last use -- i.e. `x <op> (1 << x)`, where op1 and op2
// are the same value (see AddDelayFreeUses). So the destination can only alias op2 when op1 and
// op2 hold the same value, in which case the `mov` writes that same value back into op2's
// register and nothing is clobbered before the bit-test reads it. When the operands are distinct
// values, delayFree guarantees the destination and op2 use different registers.

// These are read-modify-write: the destination register also supplies the value operand.
inst_Mov(targetType, targetReg, op1->GetRegNum(), /* canSkip */ true);

// The BT-family reg,reg encoding places the destination in the r/m slot and the bit index in
// the reg slot, so the operands are passed reversed (see the note in instrsxarch.h).
emit->emitIns_R_R(ins, size, op2->GetRegNum(), targetReg);

Comment thread
tannergooding marked this conversation as resolved.
genProduceReg(treeNode);
}

//------------------------------------------------------------------------
// genCodeForMul: Generate code for a MUL operation.
//
Expand Down Expand Up @@ -1892,6 +1937,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genCodeForBinary(treeNode->AsOp());
break;

case GT_BIT_SET:
case GT_BIT_CLEAR:
case GT_BIT_INVERT:
genCodeForBitOp(treeNode->AsOp());
break;

case GT_MUL:
if (varTypeIsFloating(treeNode->TypeGet()))
{
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/emitxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13495,6 +13495,15 @@ void emitter::emitDispIns(
break;
}

if ((ins == INS_bts) || (ins == INS_btr) || (ins == INS_btc))
{
// The BT-family reg,reg encoding stores its operands reversed. Display them in
// the normal `dest, index` order.
printf("%s", emitRegName(id->idReg2(), tgtAttr));
printf(", %s", emitRegName(id->idReg1(), srcAttr));
break;
}

printf("%s", emitRegName(id->idReg1(), tgtAttr));
printf(", %s", emitRegName(id->idReg2(), srcAttr));
break;
Expand Down
10 changes: 6 additions & 4 deletions src/coreclr/jit/gtlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,16 @@ 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)
#endif // TARGET_RISCV64

// Maps to bset/bseti instruction. Computes result = op1 | (1 << op2)
#if defined(TARGET_RISCV64) || defined(TARGET_XARCH)
// Maps to riscv64 bset/bseti and xarch bts. 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)
// Maps to riscv64 bclr/bclri and xarch btr. 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)
// Maps to riscv64 binv/binvi and xarch btc. Computes result = op1 ^ (1 << op2)
GTNODE(BIT_INVERT , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR)
#endif
#endif // TARGET_RISCV64 || TARGET_XARCH

//-----------------------------------------------------------------------------
// Other nodes that look like unary/binary operators:
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/instrsxarch.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ INST4(lea, "lea", IUM_WR, BAD_CODE, BAD_CODE,
// and the registers need to be reversed to get the correct encoding.
INST3(bt, "bt", IUM_RD, 0x0F00A3, BAD_CODE, 0x0F00A3, 1C, 2X, INS_TT_NONE, Undefined_OF | Undefined_SF | Undefined_ZF | Undefined_AF | Undefined_PF | Writes_CF | Encoding_REX2)

// BTS/BTR/BTC are only emitted in their reg,reg form (like BT the registers are reversed to get
// the correct encoding). They read+write the first operand and write the old bit value to CF.
INST3(bts, "bts", IUM_RW, 0x0F00AB, BAD_CODE, 0x0F00AB, 1C, 2X, INS_TT_NONE, Undefined_OF | Undefined_SF | Undefined_ZF | Undefined_AF | Undefined_PF | Writes_CF | Encoding_REX2)
INST3(btr, "btr", IUM_RW, 0x0F00B3, BAD_CODE, 0x0F00B3, 1C, 2X, INS_TT_NONE, Undefined_OF | Undefined_SF | Undefined_ZF | Undefined_AF | Undefined_PF | Writes_CF | Encoding_REX2)
INST3(btc, "btc", IUM_RW, 0x0F00BB, BAD_CODE, 0x0F00BB, 1C, 2X, INS_TT_NONE, Undefined_OF | Undefined_SF | Undefined_ZF | Undefined_AF | Undefined_PF | Writes_CF | Encoding_REX2)

INST3(bsr, "bsr", IUM_WR, BAD_CODE, BAD_CODE, 0x0F00BD, 3C, 1C, INS_TT_NONE, Undefined_OF | Undefined_SF | Writes_ZF | Undefined_AF | Undefined_PF | Undefined_CF | Encoding_REX2)
INST3(bsf, "bsf", IUM_WR, BAD_CODE, BAD_CODE, 0x0F00BC, 3C, 1C, INS_TT_NONE, Undefined_OF | Undefined_SF | Writes_ZF | Undefined_AF | Undefined_PF | Undefined_CF | Encoding_REX2)

Expand Down
28 changes: 28 additions & 0 deletions src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4235,6 +4235,34 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp)
test->gtOp2 = bitOp->gtGetOp2();
return true;
}

#ifdef TARGET_XARCH
// Also recognize the arithmetic form `(x >> y) & 1`, i.e. AND(RSH|RSZ(x, y), 1), which
// tests bit `y` of `x` just like `x & (1 << y)`. Only bit 0 of the shifted value is kept so
// the shift kind is irrelevant, and `bt` masks the bit index modulo the operand size, which
// matches the C# masked-shift semantics even for an out-of-range `y`. Restricted to a
// variable index because a constant index keeps the shift, and `bt` has no immediate form
// here (a constant mask `test` is already optimal).
GenTree* shiftOp = test->gtOp1;
GenTree* oneOp = test->gtOp2;
if (!oneOp->IsIntegralConst(1))
std::swap(shiftOp, oneOp);

if (oneOp->IsIntegralConst(1) && shiftOp->OperIs(GT_RSH, GT_RSZ) && varTypeIsIntOrI(shiftOp) &&
!shiftOp->gtGetOp2()->IsIntegralConst())
{
BlockRange().Remove(oneOp);
BlockRange().Remove(shiftOp);
test->gtOp1 = shiftOp->gtGetOp1();
test->gtOp2 = shiftOp->gtGetOp2();

// ContainCheckCompare is skipped when this transform succeeds, so clear any containment
// the value operand picked up from the removed shift (e.g. a `shrx` memory source) --
// the reg,reg `bt` form requires it in a register.
test->gtOp1->ClearContained();
return true;
}
#endif // TARGET_XARCH
return false;
};

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/lower.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ class Lowering final : public Phase

#ifdef TARGET_XARCH
GenTree* TryLowerMulWithConstant(GenTreeOp* node);
GenTree* TryLowerBitwiseOpToBitOp(GenTreeOp* binOp);
#endif // TARGET_XARCH

#ifdef TARGET_WASM
Expand Down Expand Up @@ -509,6 +510,7 @@ class Lowering final : public Phase
GenTree* TryLowerAndOpToResetLowestSetBit(GenTreeOp* andNode);
GenTree* TryLowerAndOpToExtractLowestSetBit(GenTreeOp* andNode);
GenTree* TryLowerAndOpToAndNot(GenTreeOp* andNode);
GenTree* TryLowerAndOpToZeroHighBits(GenTreeOp* andNode);
GenTree* TryLowerXorOpToGetMaskUpToLowestSetBit(GenTreeOp* xorNode);
void LowerBswapOp(GenTreeOp* node);
GenTree* LowerHWIntrinsicDotInnerMulSum(GenTreeHWIntrinsic* node);
Expand Down
Loading
Loading