Skip to content
Closed
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/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
43 changes: 43 additions & 0 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,43 @@ 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 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);

genProduceReg(treeNode);
}

//------------------------------------------------------------------------
// genCodeForMul: Generate code for a MUL operation.
//
Expand Down Expand Up @@ -1892,6 +1929,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 @@ -509,7 +509,9 @@ 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);
GenTree* TryLowerBitwiseOpToBitOp(GenTreeOp* binOp);
void LowerBswapOp(GenTreeOp* node);
GenTree* LowerHWIntrinsicDotInnerMulSum(GenTreeHWIntrinsic* node);
#elif defined(TARGET_ARM64)
Expand Down
Loading
Loading