Recognize bit-manipulation idioms and emit bzhi/bts/btc/btr/bt on xarch#33
Closed
tannergooding wants to merge 3 commits into
Closed
Recognize bit-manipulation idioms and emit bzhi/bts/btc/btr/bt on xarch#33tannergooding wants to merge 3 commits into
tannergooding wants to merge 3 commits into
Conversation
Lowers several single-bit and bit-field idioms to dedicated x86 instructions: x & ((1 << y) - 1) -> bzhi (reuses NI_AVX2_ZeroHighBits) x | (1 << y) -> bts (GT_BIT_SET) x & ~(1 << y) -> btr (GT_BIT_CLEAR) x ^ (1 << y) -> btc (GT_BIT_INVERT) bzhi is recognized in LowerBinaryArithmetic and folded into the existing AVX2 scalar intrinsic, so containment and memory operands work for free. It matches both the ADD(-1) and SUB(1) mask forms and only fires for a variable shift amount, since a constant amount folds to a plain and with an immediate. The bts/btr/btc patterns reuse the existing GT_BIT_SET/GT_BIT_CLEAR/GT_BIT_INVERT opers (shared with riscv64) with xarch codegen, LSRA, and emitter support. Only the reg,reg form is used: the value operand is kept in a register so the implicit masking of the bit index (mod the operand width) matches the shl semantics, and because the memory-destination forms are slower. They likewise only fire for a variable bit index, since a constant index folds to a plain or/and/xor with an immediate. bextr (constant-control field extract) is intentionally left out: it must materialize the control word and is multi-uop, so shr/sar + and in place is smaller and faster; the fully-variable (x >> y) & ((1 << z) - 1) case already lowers optimally to shrx + bzhi. This can be revisited with a cost/liveness-aware heuristic in a follow-up. Contributes to dotnet#81514 Fixes dotnet#81512 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extend tryReduceSingleBitTestOps to also match AND(RSH|RSZ(x, y), 1) so that a single-bit test written as ((x >> y) & 1) feeding a branch lowers to bt, the same as the already-handled x & (1 << y) shape. 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, matching the C# masked-shift semantics even for an out-of-range y. Restricted to a variable index since a constant index keeps the shift and bt has no immediate form here (the existing constant-mask test is already optimal). The value operand's containment is cleared because ContainCheckCompare is skipped on success and the reg,reg bt form requires it in a register. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Owner
Author
|
Superseded by dotnet#130481 — this was accidentally opened against the fork's own main instead of upstream. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lowers several single-bit and bit-field idioms to dedicated x86 instructions on xarch:
x & ((1 << y) - 1)bzhi(reusesNI_AVX2_ZeroHighBits)x | (1 << y)bts(GT_BIT_SET)x & ~(1 << y)btr(GT_BIT_CLEAR)x ^ (1 << y)btc(GT_BIT_INVERT)(x >> y) & 1in== 0/!= 0bt(GT_BITTEST_EQ/GT_BITTEST_NE)Details
bzhiis recognized inLowerBinaryArithmeticand folded into the existing AVX2 scalar intrinsic, so containment and memory operands come for free. It matches both theADD(-1)andSUB(1)mask forms and only fires for a variable shift amount, since a constant amount folds to a plainandwith an immediate.bts/btr/btcreuse the existingGT_BIT_SET/GT_BIT_CLEAR/GT_BIT_INVERTopers (shared with riscv64) with xarch codegen, LSRA, and emitter support. Only thereg,regform is used: the value operand is kept in a register so the implicit masking of the bit index (mod the operand width) matches theshlsemantics, and because the memory-destination forms are slower. They likewise only fire for a variable bit index.btrecognizes(x >> y) & 1feeding a== 0/!= 0and emits the flag-producing bit test.bextrdeferred#81514also coversbextr, but constant-controlbextr((x >> c) & mask) is intentionally left out: it must materialize the control word and is multi-uop, soshr/sar+andin place is smaller and faster. SPMI confirmed it was a net regression (+7,671bytes) and it only wins when the source is a live-after local (saving a preservation copy) — which needs last-use info not available during lowering. The fully-variable(x >> y) & ((1 << z) - 1)case already lowers optimally toshrx + bzhi, so nothing is lost. This can be revisited with a cost/liveness-aware heuristic in a follow-up.Diffs
SPMI asmdiffs (x64 windows): overall
-3,874bytes, every collection improves in both size and PerfScore (coreclr_tests PerfScore-5.09%), no asserts, no replay failures. The only regressions are 6 methods at+1/+2bytes each (memory-RMWbtswhere the memory destination can't be contained without breaking the bit-index masking) — negligible against the wins.Contributes to dotnet#81514
Fixes dotnet#81512
Note
This PR description was generated with the assistance of GitHub Copilot.