diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 0dc138339952f0..f7d4f12612d970 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1256,7 +1256,9 @@ class CodeGen final : public CodeGenInterface void genCodeForJumpCompare(GenTreeOpCC* tree); void genCompareImmAndJump( GenCondition::Code cond, regNumber reg, ssize_t compareImm, emitAttr size, BasicBlock* target); + void genCodeForBfi(GenTreeOp* tree); void genCodeForBfiz(GenTreeOp* tree); + void genCodeForBfx(GenTreeOp* tree); #endif // TARGET_ARM64 void genEHCatchRet(BasicBlock* block); diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 17c796ae792b28..7f418967e56a8d 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -5831,6 +5831,38 @@ void CodeGen::instGen_MemoryBarrier(BarrierKind barrierKind) } } +//------------------------------------------------------------------------ +// genCodeForBfi: Generates the code sequence for a GenTree node that +// represents a bitfield insert. +// +// Arguments: +// tree - the bitfield insert. +// +void CodeGen::genCodeForBfi(GenTreeOp* tree) +{ + assert(tree->OperIs(GT_BFI)); + + GenTreeBfm* bfm = tree->AsBfm(); + + emitAttr size = emitActualTypeSize(tree); + unsigned regBits = emitter::getBitWidth(size); + + GenTree* base = tree->gtGetOp1(); + GenTree* src = tree->gtGetOp2(); + + genConsumeOperands(bfm); + + unsigned offset = bfm->GetOffset(); + unsigned width = bfm->GetWidth(); + + assert(width >= 1 && width <= regBits); + assert(offset < regBits && (offset + width) <= regBits); + + GetEmitter()->emitIns_R_R_I_I(INS_bfi, size, base->GetRegNum(), src->GetRegNum(), (int)offset, (int)width); + + genProduceReg(tree); +} + //------------------------------------------------------------------------ // genCodeForBfiz: Generates the code sequence for a GenTree node that // represents a bitfield insert in zero with sign/zero extension. @@ -5858,6 +5890,38 @@ void CodeGen::genCodeForBfiz(GenTreeOp* tree) genProduceReg(tree); } +//------------------------------------------------------------------------ +// genCodeForBfx: Generates the code sequence for a GenTree node that +// represents a bitfield extract. +// +// Arguments: +// tree - the bitfield extract. +// +void CodeGen::genCodeForBfx(GenTreeOp* tree) +{ + assert(tree->OperIs(GT_BFX)); + + GenTreeBfm* bfm = tree->AsBfm(); + emitAttr size = emitActualTypeSize(tree); + + GenTree* src = tree->gtGetOp1(); + + const unsigned bitWidth = emitter::getBitWidth(size); + const unsigned lsb = bfm->GetOffset(); + const unsigned width = bfm->GetWidth(); + + assert((bitWidth == 32) || (bitWidth == 64)); + assert(lsb < bitWidth); + assert(width > 0); + assert((lsb + width) <= bitWidth); + + genConsumeRegs(src); + + GetEmitter()->emitIns_R_R_I_I(INS_ubfx, size, tree->GetRegNum(), src->GetRegNum(), (int)lsb, (int)width); + + genProduceReg(tree); +} + //------------------------------------------------------------------------ // JumpKindToInsCond: Convert a Jump Kind to a condition. // diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 1b32c4affd322b..69d9154773c0de 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -329,9 +329,17 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForSwap(treeNode->AsOp()); break; + case GT_BFI: + genCodeForBfi(treeNode->AsOp()); + break; + case GT_BFIZ: genCodeForBfiz(treeNode->AsOp()); break; + + case GT_BFX: + genCodeForBfx(treeNode->AsOp()); + break; #endif // TARGET_ARM64 case GT_JMP: diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 14fcc315d48c8e..de0b31e0782e28 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2983,6 +2983,11 @@ class Compiler GenTreeColon* gtNewColonNode(var_types type, GenTree* thenNode, GenTree* elseNode); GenTreeQmark* gtNewQmarkNode(var_types type, GenTree* cond, GenTreeColon* colon); +#if defined(TARGET_ARM64) + GenTreeBfm* gtNewBfiNode(var_types type, GenTree* base, GenTree* src, unsigned offset, unsigned width); + GenTreeBfm* gtNewBfxNode(var_types type, GenTree* base, unsigned offset, unsigned width); +#endif + GenTree* gtNewLargeOperNode(genTreeOps oper, var_types type = TYP_I_IMPL, GenTree* op1 = nullptr, diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 3465a46b8dd223..baa2dc15bb3301 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4629,6 +4629,18 @@ GenTree::VisitResult GenTree::VisitOperands(TVisitor visitor) return visitor(cond->gtOp2); } +#ifdef TARGET_ARM64 + case GT_BFX: + { + GenTree* op1 = gtGetOp1(); + if (op1 != nullptr) + { + return visitor(op1); + } + return VisitResult::Continue; + } +#endif + // Binary nodes default: assert(this->OperIsBinary()); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index e280b6c999a7e6..2a2b174ce03e66 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -7770,6 +7770,24 @@ GenTreeQmark* Compiler::gtNewQmarkNode(var_types type, GenTree* cond, GenTreeCol return result; } +#if defined(TARGET_ARM64) +GenTreeBfm* Compiler::gtNewBfiNode(var_types type, GenTree* base, GenTree* src, unsigned offset, unsigned width) +{ + GenTreeBfm* result = new (this, GT_BFI) GenTreeBfm(GT_BFI, type, base, src, offset, width); + result->gtFlags |= (base->gtFlags | src->gtFlags) & (GTF_ALL_EFFECT); + result->gtFlags &= ~GTF_SET_FLAGS; + return result; +} + +GenTreeBfm* Compiler::gtNewBfxNode(var_types type, GenTree* base, unsigned offset, unsigned width) +{ + GenTreeBfm* result = new (this, GT_BFX) GenTreeBfm(GT_BFX, type, base, nullptr, offset, width); + result->gtFlags |= (base->gtFlags & GTF_ALL_EFFECT); + result->gtFlags &= ~GTF_SET_FLAGS; + return result; +} +#endif + GenTreeIntCon* Compiler::gtNewIconNode(ssize_t value, var_types type) { assert(genActualType(type) == type); @@ -10472,6 +10490,15 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) m_advance = &GenTreeUseEdgeIterator::Terminate; return; +#ifdef TARGET_ARM64 + case GT_BFX: + assert(m_node->AsOp()->gtOp2 == nullptr); + m_edge = &m_node->AsOp()->gtOp1; + assert(*m_edge != nullptr); + m_advance = &GenTreeUseEdgeIterator::Terminate; + return; +#endif + // Unary operators with an optional operand case GT_FIELD_ADDR: case GT_RETURN: diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 5c60753cc7d0dd..836b04bc126f90 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -5957,6 +5957,44 @@ struct GenTreeQmark : public GenTreeOp #endif }; +#ifdef TARGET_ARM64 +struct GenTreeBfm : public GenTreeOp +{ + unsigned gtOffset; + unsigned gtWidth; + + GenTreeBfm(genTreeOps oper, var_types type, GenTree* base, GenTree* src, unsigned offset, unsigned width) + : GenTreeOp(oper, type, base, src) + , gtOffset(offset) + , gtWidth(width) + { + assert((oper == GT_BFI) || (oper == GT_BFX)); + assert((oper != GT_BFX) || (src == nullptr)); + assert((oper != GT_BFI) || (src != nullptr)); + } + + unsigned GetOffset() const + { + return gtOffset; + } + unsigned GetWidth() const + { + return gtWidth; + } + unsigned GetMask() const + { + return ((~0ULL >> (64 - gtWidth)) << gtOffset); + } + +#if DEBUGGABLE_GENTREE + GenTreeBfm() + : GenTreeOp() + { + } +#endif +}; +#endif + /* gtIntrinsic -- intrinsic (possibly-binary op [NULL op2 is allowed] with an additional field) */ struct GenTreeIntrinsic : public GenTreeOp diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index d30b6f10c5d612..ca603bb4e0963d 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -221,7 +221,9 @@ GTNODE(OR_NOT , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR) GTNODE(XOR_NOT , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR) #ifdef TARGET_ARM64 +GTNODE(BFI , GenTreeBfm ,0,0,GTK_BINOP|GTK_EXOP|DBK_NOTHIR) // Bitfield Insert. GTNODE(BFIZ , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTHIR) // Bitfield Insert in Zero. +GTNODE(BFX , GenTreeBfm ,0,0,GTK_UNOP|GTK_EXOP|DBK_NOTHIR) // Bitfield Extract. #endif //----------------------------------------------------------------------------- diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index 6e7d62e496f038..f04ae1a588a418 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -95,6 +95,7 @@ GTSTRUCT_1(StoreInd , GT_STOREIND) GTSTRUCT_1(CmpXchg , GT_CMPXCHG) #ifdef TARGET_ARM64 GTSTRUCT_N(Conditional , GT_SELECT, GT_SELECT_INC, GT_SELECT_INV, GT_SELECT_NEG) +GTSTRUCT_2(Bfm , GT_BFI, GT_BFX) #else GTSTRUCT_N(Conditional , GT_SELECT) #endif //TARGET_ARM64 diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index aabc4fe17b258c..f45514e66d4718 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -11909,6 +11909,378 @@ bool Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next) return true; } +#ifdef TARGET_ARM64 +//------------------------------------------------------------------------ +// TryLowerOrToBFI : Lower OR of 2 masking operations into a BFI node +// OR op1 can be a const var, AND, BFI or OR node +// OR op2 can be a LSH & AND or a BFIZ node +// +// Arguments: +// tree - pointer to the node +// next - [out] Next node to lower if this function returns true +// +// Return Value: +// false if no changes were made +// +bool Lowering::TryLowerOrToBFI(GenTreeOp* tree, GenTree** next) +{ + assert(tree->OperIs(GT_OR)); + + if (!comp->opts.OptimizationEnabled()) + { + return false; + } + + BfiPattern bfiPattern; + if (!TryMatchOrToBfiPattern(tree, &bfiPattern)) + { + return false; + } + + unsigned regBits = genTypeSize(tree) * BITS_PER_BYTE; + uint64_t regMask = (regBits == 64) ? UINT64_MAX : ((1ull << regBits) - 1); + + uint64_t newMask = (bfiPattern.lowMask << bfiPattern.offset) & regMask; + uint64_t baseMask = 0; + bool baseMaskKnown = false; + + GenTree* node = bfiPattern.base; + for (int depth = 0; depth < 64 && (node != nullptr); depth++) + { + if (node->OperIs(GT_AND)) + { + GenTree* andOp1 = node->gtGetOp1(); + GenTree* andOp2 = node->gtGetOp2(); + + GenTree* valNode = andOp1; + GenTree* constNode = andOp2; + if (!constNode->IsIntegralConst()) + { + std::swap(valNode, constNode); + if (!constNode->IsIntegralConst()) + { + baseMaskKnown = false; + break; + } + } + + uint64_t c = (uint64_t)constNode->AsIntConCommon()->IntegralValue(); + c &= regMask; + baseMask |= c; + baseMaskKnown = true; + break; + } + else if (node->IsIntegralConst()) + { + uint64_t c = (uint64_t)node->AsIntConCommon()->IntegralValue(); + c &= regMask; + baseMask |= c; + baseMaskKnown = true; + break; + } + else if (node->OperIs(GT_BFI)) + { + GenTreeBfm* bfm = node->AsBfm(); + uint64_t m = bfm->GetMask() & regMask; + baseMask |= m; + baseMaskKnown = true; + + node = bfm->gtGetOp1(); + continue; + } + else if (node->OperIs(GT_OR)) + { + BfiPattern nested; + if (!TryMatchOrToBfiPattern(node->AsOp(), &nested)) + { + baseMaskKnown = false; + break; + } + + uint64_t subMask = (nested.lowMask << nested.offset) & regMask; + baseMask |= subMask; + baseMaskKnown = true; + + node = nested.base; + continue; + } + else + { + baseMaskKnown = false; + break; + } + } + + if (!baseMaskKnown || ((baseMask & newMask) != 0)) + { + return false; + } + + var_types ty = genActualType(tree->TypeGet()); + GenTreeBfm* bfm = comp->gtNewBfiNode(ty, bfiPattern.base, bfiPattern.value, bfiPattern.offset, bfiPattern.width); + bfm->CopyCosts(tree); + + ContainCheckNode(bfm); + + BlockRange().InsertBefore(tree, bfm); + + LIR::Use use; + if (BlockRange().TryGetUse(tree, &use)) + { + use.ReplaceWith(bfm); + } + + // Remove old nodes depending on pattern kind + switch (bfiPattern.kind) + { + case BfiPatternKind::FromLshAndMask: + BlockRange().Remove(bfiPattern.shiftAnd); + BlockRange().Remove(bfiPattern.shiftAndConst); + BlockRange().Remove(bfiPattern.shiftConst); + BlockRange().Remove(bfiPattern.shiftNode); + break; + + case BfiPatternKind::FromBfiz: + // Remove CAST first (it is op1 of BFIZ) + if (bfiPattern.castNode != nullptr) + { + BlockRange().Remove(bfiPattern.castNode); + } + if (bfiPattern.shiftConst != nullptr) + { + BlockRange().Remove(bfiPattern.shiftConst); + } + BlockRange().Remove(bfiPattern.shiftNode); // BFIZ node + break; + + default: + return false; + } + + BlockRange().Remove(tree); + + *next = bfm->gtNext; + return true; +} + +//------------------------------------------------------------------------ +// TryMatchOrToBfiPattern : Check if the tree op2 matches the 2 valid +// BFI patterns. +// Case A: The op2 is a LSH node with a AND performing a constant mask +// Case B: The op2 is a BFIZ node with a CAST node +// +// Arguments: +// tree - pointer to the or node +// result - [out] BfiPattern struct containing pointers to nodes +// of the found pattern. +// +// Return Value: +// false if the or node doesn't match the required pattern +// +bool Lowering::TryMatchOrToBfiPattern(GenTreeOp* tree, BfiPattern* result) +{ + assert(tree->OperIs(GT_OR)); + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + if (!op1->OperIs(GT_LSH, GT_BFIZ) && !op2->OperIs(GT_LSH, GT_BFIZ)) + { + return false; + } + + GenTree* orOp1 = op1->OperIs(GT_LSH, GT_BFIZ) ? op1 : op2; + GenTree* base = (orOp1 == op1) ? op2 : op1; + + unsigned regBits = genTypeSize(tree) * BITS_PER_BYTE; + uint64_t regMask = (regBits == 64) ? UINT64_MAX : ((1ull << regBits) - 1); + + // Case A: OR(base, LSH(AND(value, lowMask), offset)) + if (orOp1->OperIs(GT_LSH)) + { + GenTree* shiftAnd = orOp1->gtGetOp1(); + GenTree* shiftConst = orOp1->gtGetOp2(); + + if (!shiftAnd->OperIs(GT_AND) || !shiftConst->IsIntegralConst()) + { + return false; + } + + // Allow const on either side of AND + GenTree* valueNode = shiftAnd->gtGetOp1(); + GenTree* constNode = shiftAnd->gtGetOp2(); + + if (!constNode->IsIntegralConst()) + { + std::swap(valueNode, constNode); + if (!constNode->IsIntegralConst()) + { + return false; + } + } + + ssize_t shiftVal = shiftConst->AsIntConCommon()->IntegralValue(); + if (shiftVal < 0) + { + return false; + } + + uint64_t lowMask = (uint64_t)constNode->AsIntConCommon()->IntegralValue(); + lowMask &= regMask; + + if (lowMask == 0) + { + return false; + } + + // lowMask must be contiguous from LSB + if ((lowMask & (lowMask + 1)) != 0) + { + return false; + } + + uint64_t width = (uint64_t)BitOperations::PopCount(lowMask); + uint64_t offset = (uint64_t)shiftVal; + + if (offset >= regBits || (offset + width) > regBits) + { + return false; + } + + result->kind = BfiPatternKind::FromLshAndMask; + result->base = base; + result->value = valueNode; + result->shiftNode = orOp1; + result->shiftAnd = shiftAnd; + result->shiftAndConst = constNode; + result->shiftConst = shiftConst; + result->castNode = nullptr; + result->lowMask = lowMask; + result->offset = offset; + result->width = width; + return true; + } + + // Case B: OR(base, BFIZ(CAST(...), shiftByConst)) + assert(orOp1->OperIs(GT_BFIZ)); + + GenTree* shiftConst = orOp1->gtGetOp2(); + if ((shiftConst == nullptr) || !shiftConst->IsIntegralConst()) + { + return false; + } + + ssize_t shiftVal = shiftConst->AsIntConCommon()->IntegralValue(); + if (shiftVal < 0) + { + return false; + } + + GenTree* bfizOp1 = orOp1->gtGetOp1(); + if ((bfizOp1 == nullptr) || !bfizOp1->OperIs(GT_CAST)) + { + return false; + } + + GenTreeCast* cast = bfizOp1->AsCast(); + GenTree* castOp = cast->CastOp(); + + uint64_t width = (uint64_t)varTypeIsSmall(cast->CastToType()) ? genTypeSize(cast->CastToType()) * BITS_PER_BYTE + : genTypeSize(castOp) * BITS_PER_BYTE; + if ((width == 0) || (width > regBits)) + { + return false; + } + + uint64_t offset = ((uint64_t)shiftVal) & (regBits - 1); + if (offset >= regBits || (offset + width) > regBits) + { + return false; + } + + uint64_t lowMask = (width == 64) ? UINT64_MAX : ((1ull << width) - 1); + lowMask &= regMask; + + result->kind = BfiPatternKind::FromBfiz; + result->base = base; + result->value = cast->CastOp(); + result->shiftNode = orOp1; + result->shiftConst = shiftConst; + result->castNode = bfizOp1; + result->shiftAnd = nullptr; + result->shiftAndConst = nullptr; + result->lowMask = lowMask; + result->offset = offset; + result->width = width; + return true; +} + +//------------------------------------------------------------------------ +// TryLowerOrToBFX : Lower AND of left shift and constant +// +// Arguments: +// tree - pointer to the node +// next - [out] Next node to lower if this function returns true +// +// Return Value: +// false if no changes were made +// +bool Lowering::TryLowerOrToBFX(GenTreeOp* tree, GenTree** next) +{ + assert(tree->OperIs(GT_AND)); + + if (!comp->opts.OptimizationEnabled()) + { + return false; + } + + GenTree* shift = tree->gtGetOp1(); + GenTree* andConst = tree->gtGetOp2(); + if (!shift->OperIs(GT_RSH, GT_RSZ) || !andConst->IsIntegralConst()) + { + return false; + } + + GenTree* shiftVar = shift->gtGetOp1(); + GenTree* shiftConst = shift->gtGetOp2(); + if (!shiftConst->IsIntegralConst()) + { + return false; + } + + uint64_t mask = (uint64_t)andConst->AsIntConCommon()->IntegralValue(); + uint64_t shiftVal = (uint64_t)shiftConst->AsIntConCommon()->IntegralValue(); + if ((mask & (mask + 1)) != 0) + { + return false; + } + + uint64_t width = (uint64_t)BitOperations::PopCount(mask); + uint64_t offset = (uint64_t)shiftVal; + var_types ty = genActualType(tree->TypeGet()); + GenTreeBfm* bfm = comp->gtNewBfxNode(ty, shiftVar, offset, width); + bfm->CopyCosts(tree); + + ContainCheckNode(bfm); + + BlockRange().InsertBefore(tree, bfm); + + LIR::Use use; + if (BlockRange().TryGetUse(tree, &use)) + { + use.ReplaceWith(bfm); + } + + BlockRange().Remove(shiftConst); + BlockRange().Remove(shift); + BlockRange().Remove(andConst); + BlockRange().Remove(tree); + + *next = bfm->gtNext; + return true; +} +#endif + //------------------------------------------------------------------------ // ContainCheckConditionalCompare: determine whether the source of a compare within a compare chain should be contained. // diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index c8468dd114a70f..6771107d518cd9 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -96,6 +96,32 @@ class Lowering final : public Phase bool TryLowerNegToMulLongOp(GenTreeOp* op, GenTree** next); bool TryContainingCselOp(GenTreeHWIntrinsic* parentNode, GenTreeHWIntrinsic* childNode); #endif +#if defined(TARGET_ARM64) + bool TryLowerOrToBFI(GenTreeOp* tree, GenTree** next); + + enum class BfiPatternKind + { + FromLshAndMask, + FromBfiz + }; + + struct BfiPattern + { + BfiPatternKind kind; + GenTree* base; + GenTree* value; + GenTree* shiftAnd; + GenTree* shiftAndConst; + GenTree* shiftConst; + GenTree* shiftNode; + GenTree* castNode; + uint64_t lowMask; + uint64_t offset; + uint64_t width; + }; + bool TryMatchOrToBfiPattern(GenTreeOp* orTree, BfiPattern* result); + bool TryLowerOrToBFX(GenTreeOp* tree, GenTree** next); +#endif #ifdef TARGET_RISCV64 bool TryLowerShiftAddToShxadd(GenTreeOp* tree, GenTree** next); bool TryLowerZextAddToAddUw(GenTreeOp* tree, GenTree** next); diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index c8f677347601cc..1c1ce2bc02ebd6 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -659,6 +659,22 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) { return next; } + + if (binOp->OperIs(GT_AND)) + { + if (TryLowerOrToBFX(binOp, &next)) + { + return next; + } + } + else + { + assert(binOp->OperIs(GT_OR)); + if (TryLowerOrToBFI(binOp, &next)) + { + return next; + } + } } if (binOp->OperIs(GT_SUB)) diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index 2637f08b81796b..e750af561636f8 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -871,12 +871,30 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree); break; + case GT_BFI: + { + tgtPrefUse = BuildUse(tree->gtGetOp1()); + srcCount = 1; + RefPosition* srcUse = nullptr; + srcCount += BuildDelayFreeUses(tree->gtGetOp2(), tree, RBM_NONE, &srcUse); + BuildDef(tree); + break; + } + case GT_BFIZ: assert(tree->gtGetOp1()->OperIs(GT_CAST)); srcCount = BuildOperandUses(tree->gtGetOp1()->gtGetOp1()); BuildDef(tree); break; + case GT_BFX: + { + srcCount = BuildOperandUses(tree->gtGetOp1()); + assert(dstCount == 1); + BuildDef(tree); + break; + } + case GT_RETURNTRAP: // this just turns into a compare of its child with an int // + a conditional call diff --git a/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj b/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj index 6bbebee74d7177..915e6b1ff1c632 100644 --- a/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj +++ b/src/tests/Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj @@ -7,6 +7,7 @@ + diff --git a/src/tests/Common/CoreCLRTestLibrary/Expect.cs b/src/tests/Common/CoreCLRTestLibrary/Expect.cs new file mode 100644 index 00000000000000..b9d62483d82fff --- /dev/null +++ b/src/tests/Common/CoreCLRTestLibrary/Expect.cs @@ -0,0 +1,26 @@ +// 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.Linq.Expressions; +using Xunit; + +namespace TestLibrary; + +public static class Expect +{ + public static void ExpectEqual(Expression> expr, T expected, ref bool fail) + { + var compiled = expr.Compile(); + T actual = compiled(); + + // Get just the expression body text + string exprText = expr.Body.ToString(); + + if (!Equals(actual, expected)) + { + Console.WriteLine($"{exprText} = {actual}, expected {expected}"); + fail = true; + } + } +} diff --git a/src/tests/JIT/opt/InstructionCombining/Bfi.cs b/src/tests/JIT/opt/InstructionCombining/Bfi.cs new file mode 100644 index 00000000000000..06369c8f5819ef --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/Bfi.cs @@ -0,0 +1,310 @@ +// 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 static TestLibrary.Expect; +using Xunit; + +namespace TestBfi +{ + public class Program + { + [MethodImpl(MethodImplOptions.NoInlining)] + [Fact] + public static int CheckBfi() + { + bool fail = false; + + ExpectEqual(() => ComposeBits_BaseAnd_Mask0(0xB, 0x1), 0xB, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Mask1(0xB, 0x1), 0x1B, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Mask2(0xB, 0x2), 0x2B, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Mask3(0xB, 0x2), 0x2B, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_MaskFF(0xB, 0xFA), 0xFAB, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Shift0(0xE, 0x1), 0x7, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Shift31(0xE, 0x1), 0x8000000E, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Shift31_Mask3(0xA, 0x3), 0x8000000A, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Shift32(0xE, 0x1), 0xF, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Overlap(0x1, 0x3), 0xD, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Variable(0x1000, 0xC), 0xD000, ref fail); + ExpectEqual(() => ComposeBits_BaseAnd_Variables(0xF, 0xA, 0x2), 0x2A, ref fail); + ExpectEqual(() => ComposeBits_BaseConstant_Mask3(0x3), 0x3A, ref fail); + ExpectEqual(() => ComposeBits_BaseConstant_Mask4(0x7), 0x4A, ref fail); + ExpectEqual(() => ComposeBits_BaseConstant_Overlap(0x2), 0x3F, ref fail); + ExpectEqual(() => ComposeBits_BaseBfi(0xB, 0x2, 0x4C), 0x132B, ref fail); + ExpectEqual(() => ComposeBits_BaseBfi_SwapOrder(0xB, 0x2, 0x4C), 0x132B, ref fail); + ExpectEqual(() => ComposeBits_BaseBfi_BfiOverlap(0xB, 0x2, 0x4C), 0x9AB, ref fail); + ExpectEqual(() => ComposeBits_BaseBfi_OverlapBfi(0xB, 0x2, 0x4C), 0x99B, ref fail); + ExpectEqual(() => ComposeBits_BaseBfi_BfiOverlapBfi(0xB, 0x2, 0x4C, 0x5), 0x59AB, ref fail); + ExpectEqual(() => ComposeBits_BaseBfi_NoBfiAfterInvalidPattern(0xB, 0x2, 0xFC, 0xFF, 0x7), 0xFFAB, ref fail); + ExpectEqual(() => ComposeBits_Pack32Values(1,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1, + 1,0,1,1,0,0,1,1,1,1,0,1,0,1,0,1), 0xABCDEF01, ref fail); + + if (fail) + { + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Mask0(int a, int b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + return (a & 0xF) | ((b & 0x0) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Mask1(int a, int b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #1 + return (a & 0xf) | ((b & 0x1) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Mask2(int a, int b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #2 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #4 + return (a & 0xf) | ((b & 0x2) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Mask3(int a, int b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + return (a & 0xf) | ((b & 0x3) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_MaskFF(int a, int b) + { + //AR M64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //AR M64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + return (a & 0xf) | ((b & 0xFF) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseAnd_Shift0(uint a, uint b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #7 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #1 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}} + return (a & 0x7) | (b & 0x1); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseAnd_Shift31(uint a, uint b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #31, #1 + return (a & 0xf) | ((b & 0x1) << 31); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseAnd_Shift31_Mask3(uint a, uint b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #3 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #31 + return (a & 0xf) | ((b & 0x3) << 31); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseAnd_Shift32(uint a, uint b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #1 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}} + return (a & 0xf) | ((b & 0x1) << 32); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Overlap(int a, int b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #7 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #3 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #2 + return (a & 0x7) | ((b & 0x3) << 2); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Variable(int a, int b) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #12 + return a | ((b & 0xF) << 12); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseAnd_Variables(int a, int b, int c) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #3 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #4 + return (a & b) | ((c & 0x3) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseConstant_Mask3(int a) + { + //ARM64-FULL-LINE: mov {{w[0-9]+}}, #10 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + return 0xA | ((a & 0x3) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseConstant_Mask4(int a) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #4 + //ARM64-FULL-LINE: mov {{w[0-9]+}}, #10 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #4 + return 0xA | ((a & 0x4) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ComposeBits_BaseConstant_Overlap(int a) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #3 + //ARM64-FULL-LINE: lsl {{w[0-9]+}}, {{w[0-9]+}}, #4 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, #31 + return 0x1F | ((a & 0x3) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseBfi(uint a, uint b, uint c) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #6, #7 + return (a & 0xf) | ((b & 0x3) << 4) | ((c & 0x7F) << 6); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseBfi_SwapOrder(uint a, uint b, uint c) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #6, #7 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + return (a & 0xf) | ((c & 0x7F) << 6) | ((b & 0x3) << 4); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseBfi_BfiOverlap(uint a, uint b, uint c) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #127 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #5 + return (a & 0xf) | ((b & 0x3) << 4) | ((c & 0x7F) << 5); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseBfi_OverlapBfi(uint a, uint b, uint c) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #3 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #3 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #5, #7 + return (a & 0xf) | ((b & 0x3) << 3) | ((c & 0x7F) << 5); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseBfi_BfiOverlapBfi(uint a, uint b, uint c, uint d) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #127 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #5 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #12, #3 + return (a & 0xf) | ((b & 0x3) << 4) | ((c & 0x7F) << 5) | ((d & 0x7) << 12); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_BaseBfi_NoBfiAfterInvalidPattern(uint a, uint b, uint c, uint d, uint e) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #15 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #2 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}} + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #5 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #7 + //ARM64-FULL-LINE: orr {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSL #13 + return (a & 0xf) | ((b & 0x3) << 4) | ((c & d) << 5) | ((e & 0x7) << 13); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ComposeBits_Pack32Values( + uint b0, uint b1, uint b2, uint b3, + uint b4, uint b5, uint b6, uint b7, + uint b8, uint b9, uint b10, uint b11, + uint b12, uint b13, uint b14, uint b15, + uint b16, uint b17, uint b18, uint b19, + uint b20, uint b21, uint b22, uint b23, + uint b24, uint b25, uint b26, uint b27, + uint b28, uint b29, uint b30, uint b31) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #1, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #2, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #3, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #4, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #5, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #6, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #7, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #8, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #9, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #10, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #11, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #12, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #13, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #14, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #15, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #16, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #17, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #18, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #19, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #20, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #21, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #22, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #23, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #24, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #25, #1 + //ARM64-FULL-LINE: bfi {{w[0-9]+}}, {{w[0-9]+}}, #26, #1 + return ((b0 & 1u) << 0) | + ((b1 & 1u) << 1) | + ((b2 & 1u) << 2) | + ((b3 & 1u) << 3) | + ((b4 & 1u) << 4) | + ((b5 & 1u) << 5) | + ((b6 & 1u) << 6) | + ((b7 & 1u) << 7) | + ((b8 & 1u) << 8) | + ((b9 & 1u) << 9) | + ((b10 & 1u) << 10) | + ((b11 & 1u) << 11) | + ((b12 & 1u) << 12) | + ((b13 & 1u) << 13) | + ((b14 & 1u) << 14) | + ((b15 & 1u) << 15) | + ((b16 & 1u) << 16) | + ((b17 & 1u) << 17) | + ((b18 & 1u) << 18) | + ((b19 & 1u) << 19) | + ((b20 & 1u) << 20) | + ((b21 & 1u) << 21) | + ((b22 & 1u) << 22) | + ((b23 & 1u) << 23) | + ((b24 & 1u) << 24) | + ((b25 & 1u) << 25) | + ((b26 & 1u) << 26) | + ((b27 & 1u) << 27) | + ((b28 & 1u) << 28) | + ((b29 & 1u) << 29) | + ((b30 & 1u) << 30) | + ((b31 & 1u) << 31); + } + } +} diff --git a/src/tests/JIT/opt/InstructionCombining/Bfi.csproj b/src/tests/JIT/opt/InstructionCombining/Bfi.csproj new file mode 100644 index 00000000000000..21cac1ee0cb80d --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/Bfi.csproj @@ -0,0 +1,18 @@ + + + + true + + + None + True + + + + true + + + + + + diff --git a/src/tests/JIT/opt/InstructionCombining/Bfx.cs b/src/tests/JIT/opt/InstructionCombining/Bfx.cs new file mode 100644 index 00000000000000..fe22a3753af2c6 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/Bfx.cs @@ -0,0 +1,267 @@ +// 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 static TestLibrary.Expect; +using Xunit; + +namespace TestBfx +{ + public class Program + { + [MethodImpl(MethodImplOptions.NoInlining)] + [Fact] + public static int CheckBfx() + { + bool fail = false; + + ExpectEqual(() => ExtractBits_Int_NoShift(0x7F654321), 0x1, ref fail); + ExpectEqual(() => ExtractBits_Int_Shift(0x7F654321), 0xC, ref fail); + ExpectEqual(() => ExtractBits_Int_Shift_Multiple(0x7F654321), 0x15C, ref fail); + ExpectEqual(() => ExtractBits_Int_Shift_Non_Continous_Mask(0x7F654321), 0x43, ref fail); + ExpectEqual(() => ExtractBits_Int_Shift_Mask0xFF(0x7F654321), 0xC, ref fail); + ExpectEqual(() => ExtractBits_Int_Shift_Mask0xFFFF(0x7F654321), 0x950C, ref fail); + ExpectEqual(() => ExtractBits_UInt_NoShift(0xFEDCBA98u), 0x98u, ref fail); + ExpectEqual(() => ExtractBits_UInt_Shift(0xFEDCBA98u), 0x3FBu, ref fail); + ExpectEqual(() => ExtractBits_UInt_Shift_Multiple(0xFEDCBA98u), 0x1A18u, ref fail); + ExpectEqual(() => ExtractBits_UInt_Shift_Non_Continous_Mask(0xFEDCBA98u), 0x4, ref fail); + ExpectEqual(() => ExtractBits_UInt_Shift_Mask0xFF(0xFEDCBA98u), 0xEAu, ref fail); + ExpectEqual(() => ExtractBits_UInt_Shift_Mask0xFFFF(0xFEDCBA98u), 0x72EAu, ref fail); + ExpectEqual(() => ExtractBits_Long_NoShift(0x7FFFEDCBA9876543L), 0x6543L, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift(0x7FFFEDCBA9876543L), 0x1D95L, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift_Multiple(0x7FFFEDCBA9876543L), 0x47F6EL, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift_Non_Continous_Mask(0x7FFFEDCBA9876543L), 0x14L, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift_Mask0xFF(0x7FFFEDCBA9876543L), 0x95L, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift_Mask0xFFFF(0x7FFFEDCBA9876543L), 0x1D95L, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift_Mask0xFFFFFFFF(0x7FFFEDCBA9876543L), 0x2EA61D95L, ref fail); + ExpectEqual(() => ExtractBits_Long_Shift_Mask0xFFFFFFFFFFFFFFFF(0x7FFFEDCBA9876543L), 0x1FFFFB72EA61D95L, ref fail); + ExpectEqual(() => ExtractBits_ULong_NoShift(0xFFFEEDCBA9876543UL), 0x3, ref fail); + ExpectEqual(() => ExtractBits_ULong_Shift(0xFFFEEDCBA9876543UL), 0x261D95UL, ref fail); + ExpectEqual(() => ExtractBits_ULong_Shift_Multiple(0xFFFEEDCBA9876543UL), 0x1107F6EUL, ref fail); + ExpectEqual(() => ExtractBits_ULong_Shift_Non_Continous_Mask(0xFFFEEDCBA9876543UL), 0x8UL, ref fail); + ExpectEqual(() => ExtractBits_ULong_Shift_Mask0xFF(0xFFFEEDCBA9876543UL), 0x95UL, ref fail); + ExpectEqual(() => ExtractBits_ULong_Shift_Mask0xFFFF(0xFFFEEDCBA9876543UL), 0x1D95UL, ref fail); + ExpectEqual(() => ExtractBits_ULong_Shift_Mask0xFFFFFFFF(0xFFFEEDCBA9876543UL), 0xCBA98765, ref fail); + + if (fail) + { + return 101; + } + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ExtractBits_Int_NoShift(int x) + { + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, #31 + return x & 0x1F; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ExtractBits_Int_Shift(int x) + { + //ARM64-FULL-LINE: ubfx {{w[0-9]+}}, {{w[0-9]+}}, #6, #6 + return (x >> 6) & 0x3F; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ExtractBits_Int_Shift_Multiple(int x) + { + //ARM64-FULL-LINE: ubfx {{w[0-9]+}}, {{w[0-9]+}}, #6, #7 + //ARM64-FULL-LINE: ubfx {{w[0-9]+}}, {{w[0-9]+}}, #10, #9 + int a = (x >> 6) & 0x7F; + int b = (x >> 10) & 0x1FF; + return a + b; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ExtractBits_Int_Shift_Non_Continous_Mask(int x) + { + //ARM64-FULL-LINE: mov {{w[0-9]+}}, #243 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, ASR #8 + return (x >> 8) & 0xF3; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ExtractBits_Int_Shift_Mask0xFF(int x) + { + //ARM64-FULL-LINE: asr {{w[0-9]+}}, {{w[0-9]+}}, #6 + //ARM64-FULL-LINE: uxtb {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int ExtractBits_Int_Shift_Mask0xFFFF(int x) + { + //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #6 + //ARM64-FULL-LINE: uxth {{w[0-9]+}}, {{w[0-9]+}} + return (int)(((uint)x >> 6) & 0xFFFF); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ExtractBits_UInt_NoShift(uint x) + { + //ARM64-FULL-LINE and {{w[0-9]+}}, {{w[0-9]+}}, #511 + return x & 0x1FF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ExtractBits_UInt_Shift(uint x) + { + //ARM64-FULL-LINE: ubfx {{w[0-9]+}}, {{w[0-9]+}}, #22, #10 + return (x >> 22) & 0x3FF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ExtractBits_UInt_Shift_Multiple(uint x) + { + //ARM64-FULL-LINE: ubfx {{w[0-9]+}}, {{w[0-9]+}}, #6, #12 + //ARM64-FULL-LINE: ubfx {{w[0-9]+}}, {{w[0-9]+}}, #10, #13 + uint a = (x >> 6) & 0xFFF; + uint b = (x >> 10) & 0x1FFF; + return a + b; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ExtractBits_UInt_Shift_Non_Continous_Mask(uint x) + { + //ARM64-FULL-LINE: mov {{w[0-9]+}}, #5 + //ARM64-FULL-LINE: and {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, LSR #24 + return (x >> 24) & 0x5; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ExtractBits_UInt_Shift_Mask0xFF(uint x) + { + //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #6 + //ARM64-FULL-LINE: uxtb {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static uint ExtractBits_UInt_Shift_Mask0xFFFF(uint x) + { + //ARM64-FULL-LINE: lsr {{w[0-9]+}}, {{w[0-9]+}}, #6 + //ARM64-FULL-LINE: uxth {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFFFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_NoShift(long x) + { + //ARM64-FULL-LINE: uxth {{w[0-9]+}}, {{w[0-9]+}} + return x & 0xFFFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift(long x) + { + //ARM64-FULL-LINE: ubfx {{x[0-9]+}}, {{x[0-9]+}}, #6, #17 + return (x >> 6) & 0x1FFFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift_Multiple(long x) + { + //ARM64-FULL-LINE: ubfx {{x[0-9]+}}, {{x[0-9]+}}, #6, #18 + //ARM64-FULL-LINE: ubfx {{x[0-9]+}}, {{x[0-9]+}}, #10, #19 + long a = (x >> 6) & 0x3FFFF; + long b = (x >> 10) & 0x7FFFF; + return a + b; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift_Non_Continous_Mask(long x) + { + //ARM64-FULL-LINE: mov {{x[0-9]+}}, #20 + //ARM64-FULL-LINE: and {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, ASR #12 + return (x >> 12) & 0x14; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift_Mask0xFF(long x) + { + //ARM64-FULL-LINE: asr {{x[0-9]+}}, {{x[0-9]+}}, #6 + //ARM64-FULL-LINE: uxtb {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift_Mask0xFFFF(long x) + { + //ARM64-FULL-LINE: asr {{x[0-9]+}}, {{x[0-9]+}}, #6 + //ARM64-FULL-LINE: uxth {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFFFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift_Mask0xFFFFFFFF(long x) + { + //ARM64-FULL-LINE: asr {{x[0-9]+}}, {{x[0-9]+}}, #6 + return (x >> 6) & 0xFFFFFFFFL; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static long ExtractBits_Long_Shift_Mask0xFFFFFFFFFFFFFFFF(long x) + { + //ARM64-FULL-LINE: lsr {{x[0-9]+}}, {{x[0-9]+}}, #6 + return (long)(((ulong)x >> 6) & 0xFFFFFFFFFFFFFFFFUL); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_NoShift(ulong x) + { + //ARM64-FULL-LINE: and {{x[0-9]+}}, {{x[0-9]+}}, #15 + return x & 0xF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_Shift(ulong x) + { + //ARM64-FULL-LINE: ubfx {{x[0-9]+}}, {{x[0-9]+}}, #6, #22 + return (x >> 6) & 0x3FFFFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_Shift_Multiple(ulong x) + { + //ARM64-FULL-LINE: ubfx {{x[0-9]+}}, {{x[0-9]+}}, #6, #23 + //ARM64-FULL-LINE: ubfx {{x[0-9]+}}, {{x[0-9]+}}, #10, #24 + ulong a = (x >> 6) & 0x7FFFFF; + ulong b = (x >> 10) & 0xFFFFFF; + return a + b; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_Shift_Non_Continous_Mask(ulong x) + { + //ARM64-FULL-LINE: mov {{x[0-9]+}}, #204 + //ARM64-FULL-LINE: and {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, LSR #5 + return (x >> 5) & 0xCC; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_Shift_Mask0xFF(ulong x) + { + //ARM64-FULL-LINE: lsr {{x[0-9]+}}, {{x[0-9]+}}, #6 + //ARM64-FULL-LINE: uxtb {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_Shift_Mask0xFFFF(ulong x) + { + //ARM64-FULL-LINE: lsr {{x[0-9]+}}, {{x[0-9]+}}, #6 + //ARM64-FULL-LINE: uxth {{w[0-9]+}}, {{w[0-9]+}} + return (x >> 6) & 0xFFFF; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ulong ExtractBits_ULong_Shift_Mask0xFFFFFFFF(ulong x) + { + //ARM64-FULL-LINE: lsr {{x[0-9]+}}, {{x[0-9]+}}, #8 + return (x >> 8) & 0xFFFFFFFFUL; + } + } +} diff --git a/src/tests/JIT/opt/InstructionCombining/Bfx.csproj b/src/tests/JIT/opt/InstructionCombining/Bfx.csproj new file mode 100644 index 00000000000000..48ceb6f3570323 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/Bfx.csproj @@ -0,0 +1,18 @@ + + + + true + + + None + True + + + + true + + + + + +