Simon Jakobi pushed to branch wip/sjakobi/T25233 at Glasgow Haskell Compiler / GHC
Commits:
-
8ff2e0e0
by Simon Jakobi at 2026-07-07T18:42:24+02:00
6 changed files:
- compiler/GHC/CmmToAsm/X86/CodeGen.hs
- compiler/GHC/CmmToAsm/X86/Instr.hs
- compiler/GHC/CmmToAsm/X86/Ppr.hs
- + testsuite/tests/codeGen/should_gen_asm/T25233.asm
- + testsuite/tests/codeGen/should_gen_asm/T25233.hs
- testsuite/tests/codeGen/should_gen_asm/all.T
Changes:
| ... | ... | @@ -1442,6 +1442,28 @@ getRegister' platform is32Bit (CmmMachOp mop [x]) = do -- unary MachOps |
| 1442 | 1442 | (PUNPCKLQDQ fmt (OpReg dst) dst)
|
| 1443 | 1443 | )
|
| 1444 | 1444 | |
| 1445 | +-- Use the bit-test instructions btr/bts/btc for clearing, setting and
|
|
| 1446 | +-- complementing a single, variable bit: e.g. x .&. complement (1 `shiftL` i)
|
|
| 1447 | +-- is btr. See Note [Bit-test instructions].
|
|
| 1448 | +getRegister' _ is32Bit (CmmMachOp (MO_And w) [x, CmmMachOp (MO_Not w') [y]])
|
|
| 1449 | + | w == w', Just i <- isSingleBit w y, bitTestOpWidthOK is32Bit w
|
|
| 1450 | + = genBitTestCode (intFormat w) BTR x i
|
|
| 1451 | +getRegister' _ is32Bit (CmmMachOp (MO_And w) [CmmMachOp (MO_Not w') [y], x])
|
|
| 1452 | + | w == w', Just i <- isSingleBit w y, bitTestOpWidthOK is32Bit w
|
|
| 1453 | + = genBitTestCode (intFormat w) BTR x i
|
|
| 1454 | +getRegister' _ is32Bit (CmmMachOp (MO_Or w) [x, y])
|
|
| 1455 | + | Just i <- isSingleBit w y, bitTestOpWidthOK is32Bit w
|
|
| 1456 | + = genBitTestCode (intFormat w) BTS x i
|
|
| 1457 | +getRegister' _ is32Bit (CmmMachOp (MO_Or w) [y, x])
|
|
| 1458 | + | Just i <- isSingleBit w y, bitTestOpWidthOK is32Bit w
|
|
| 1459 | + = genBitTestCode (intFormat w) BTS x i
|
|
| 1460 | +getRegister' _ is32Bit (CmmMachOp (MO_Xor w) [x, y])
|
|
| 1461 | + | Just i <- isSingleBit w y, bitTestOpWidthOK is32Bit w
|
|
| 1462 | + = genBitTestCode (intFormat w) BTC x i
|
|
| 1463 | +getRegister' _ is32Bit (CmmMachOp (MO_Xor w) [y, x])
|
|
| 1464 | + | Just i <- isSingleBit w y, bitTestOpWidthOK is32Bit w
|
|
| 1465 | + = genBitTestCode (intFormat w) BTC x i
|
|
| 1466 | + |
|
| 1445 | 1467 | getRegister' platform is32Bit (CmmMachOp mop [x, y]) = do -- dyadic MachOps
|
| 1446 | 1468 | sse4_1 <- sse4_1Enabled
|
| 1447 | 1469 | sse4_2 <- sse4_2Enabled
|
| ... | ... | @@ -5883,6 +5905,68 @@ genTrivialCode rep instr a b = do |
| 5883 | 5905 | instr b_op dst
|
| 5884 | 5906 | return (Any rep code)
|
| 5885 | 5907 | |
| 5908 | +{- Note [Bit-test instructions]
|
|
| 5909 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 5910 | +x86 has dedicated instructions for clearing (btr), setting (bts) and
|
|
| 5911 | +complementing (btc) a single bit whose index is given in a register. We use
|
|
| 5912 | +them for Cmm patterns such as
|
|
| 5913 | + |
|
| 5914 | + x & ~(1 << i) ==> btr i, x (#25233)
|
|
| 5915 | + |
|
| 5916 | +replacing a mov/shl/not/and sequence with a single instruction. The
|
|
| 5917 | +shift-count register operand of shl is masked modulo the operand width, and
|
|
| 5918 | +the bit-offset register operand of btr/bts/btc is masked the same way, so
|
|
| 5919 | +the replacement is faithful even for out-of-range i (where the Cmm shift is
|
|
| 5920 | +in any case undefined).
|
|
| 5921 | + |
|
| 5922 | +The bit-offset operand of these instructions must be an immediate or a
|
|
| 5923 | +register. We only use them when the bit index is a non-literal expression:
|
|
| 5924 | +when i is a literal, constant folding has already turned the mask into a
|
|
| 5925 | +literal, and an ordinary and/or/xor with an immediate is at least as good.
|
|
| 5926 | + |
|
| 5927 | +We restrict the pattern to W32 and native-width W64: the instructions do not
|
|
| 5928 | +exist at width 8, and sub-word Cmm operations at W8/W16 are rare enough that
|
|
| 5929 | +they are not worth the extra care.
|
|
| 5930 | +-}
|
|
| 5931 | + |
|
| 5932 | +-- | Match @1 << i@ with a non-literal @i@, returning @i@.
|
|
| 5933 | +-- See Note [Bit-test instructions].
|
|
| 5934 | +isSingleBit :: Width -> CmmExpr -> Maybe CmmExpr
|
|
| 5935 | +isSingleBit w (CmmMachOp (MO_Shl w') [CmmLit (CmmInt 1 _), i])
|
|
| 5936 | + | w == w', not (isLit i) = Just i
|
|
| 5937 | + where
|
|
| 5938 | + isLit (CmmLit {}) = True
|
|
| 5939 | + isLit _ = False
|
|
| 5940 | +isSingleBit _ _ = Nothing
|
|
| 5941 | + |
|
| 5942 | +bitTestOpWidthOK :: Bool -> Width -> Bool
|
|
| 5943 | +bitTestOpWidthOK is32Bit w = w == W32 || (w == W64 && not is32Bit)
|
|
| 5944 | + |
|
| 5945 | +-- | Generate code for @dst := x@ followed by a bit-test instruction
|
|
| 5946 | +-- (btr/bts/btc) with bit offset @i@. Analogous to 'genTrivialCode', but the
|
|
| 5947 | +-- offset operand must be a register, not memory.
|
|
| 5948 | +-- See Note [Bit-test instructions].
|
|
| 5949 | +genBitTestCode :: Format -> (Format -> Operand -> Operand -> Instr)
|
|
| 5950 | + -> CmmExpr -> CmmExpr -> NatM Register
|
|
| 5951 | +genBitTestCode rep instr x i = do
|
|
| 5952 | + (i_reg, i_code) <- getNonClobberedReg i
|
|
| 5953 | + x_code <- getAnyReg x
|
|
| 5954 | + tmp <- getNewRegNat rep
|
|
| 5955 | + let
|
|
| 5956 | + -- As in genTrivialCode, 'i' must stay alive across the computation of
|
|
| 5957 | + -- 'x' into dst, so save it in a temporary if dst holds 'i'.
|
|
| 5958 | + code dst
|
|
| 5959 | + | dst == i_reg =
|
|
| 5960 | + i_code `appOL`
|
|
| 5961 | + unitOL (MOV rep (OpReg i_reg) (OpReg tmp)) `appOL`
|
|
| 5962 | + x_code dst `snocOL`
|
|
| 5963 | + instr rep (OpReg tmp) (OpReg dst)
|
|
| 5964 | + | otherwise =
|
|
| 5965 | + i_code `appOL`
|
|
| 5966 | + x_code dst `snocOL`
|
|
| 5967 | + instr rep (OpReg i_reg) (OpReg dst)
|
|
| 5968 | + return (Any rep code)
|
|
| 5969 | + |
|
| 5886 | 5970 | regClashesWithOp :: Reg -> Operand -> Bool
|
| 5887 | 5971 | reg `regClashesWithOp` OpReg reg2 = reg == reg2
|
| 5888 | 5972 | reg `regClashesWithOp` OpAddr amode = any (==reg) (addrModeRegs amode)
|
| ... | ... | @@ -193,6 +193,11 @@ data Instr |
| 193 | 193 | | SHLD Format Operand{-amount-} Operand Operand
|
| 194 | 194 | |
| 195 | 195 | | BT Format Imm Operand
|
| 196 | + -- Bit test-and-reset/set/complement; the bit offset must be an
|
|
| 197 | + -- immediate or a register (not memory).
|
|
| 198 | + | BTR Format Operand{-bit offset-} Operand
|
|
| 199 | + | BTS Format Operand{-bit offset-} Operand
|
|
| 200 | + | BTC Format Operand{-bit offset-} Operand
|
|
| 196 | 201 | | NOP
|
| 197 | 202 | |
| 198 | 203 | |
| ... | ... | @@ -496,6 +501,9 @@ regUsageOfInstr platform instr |
| 496 | 501 | SHLD fmt imm dst1 dst2 -> usageRMM fmt imm dst1 dst2
|
| 497 | 502 | SHRD fmt imm dst1 dst2 -> usageRMM fmt imm dst1 dst2
|
| 498 | 503 | BT fmt _ src -> mkRUR (use_R fmt src [])
|
| 504 | + BTR fmt off dst -> usageRM fmt off dst
|
|
| 505 | + BTS fmt off dst -> usageRM fmt off dst
|
|
| 506 | + BTC fmt off dst -> usageRM fmt off dst
|
|
| 499 | 507 | |
| 500 | 508 | PUSH fmt op -> mkRUR (use_R fmt op [])
|
| 501 | 509 | POP fmt op -> mkRU [] (def_W fmt op)
|
| ... | ... | @@ -830,6 +838,9 @@ patchRegsOfInstr platform instr env |
| 830 | 838 | SHLD fmt imm dst1 dst2 -> patch2 (SHLD fmt imm) dst1 dst2
|
| 831 | 839 | SHRD fmt imm dst1 dst2 -> patch2 (SHRD fmt imm) dst1 dst2
|
| 832 | 840 | BT fmt imm src -> patch1 (BT fmt imm) src
|
| 841 | + BTR fmt off dst -> patch2 (BTR fmt) off dst
|
|
| 842 | + BTS fmt off dst -> patch2 (BTS fmt) off dst
|
|
| 843 | + BTC fmt off dst -> patch2 (BTC fmt) off dst
|
|
| 833 | 844 | TEST fmt src dst -> patch2 (TEST fmt) src dst
|
| 834 | 845 | CMP fmt src dst -> patch2 (CMP fmt) src dst
|
| 835 | 846 | PUSH fmt op -> patch1 (PUSH fmt) op
|
| ... | ... | @@ -862,6 +862,15 @@ pprInstr platform i = case i of |
| 862 | 862 | BT format imm src
|
| 863 | 863 | -> pprFormatImmOp (text "bt") format imm src
|
| 864 | 864 | |
| 865 | + BTR format off dst
|
|
| 866 | + -> pprFormatOpOp (text "btr") format off dst
|
|
| 867 | + |
|
| 868 | + BTS format off dst
|
|
| 869 | + -> pprFormatOpOp (text "bts") format off dst
|
|
| 870 | + |
|
| 871 | + BTC format off dst
|
|
| 872 | + -> pprFormatOpOp (text "btc") format off dst
|
|
| 873 | + |
|
| 865 | 874 | CMP format src dst
|
| 866 | 875 | | isFloatFormat format -> pprFormatOpOp (text "ucomi") format src dst -- SSE2
|
| 867 | 876 | | otherwise -> pprFormatOpOp (text "cmp") format src dst
|
| 1 | +btrq
|
|
| 2 | +btsq
|
|
| 3 | +btcq |
| 1 | +{-# LANGUAGE MagicHash #-}
|
|
| 2 | + |
|
| 3 | +-- Check that clearing/setting/complementing a single, variable bit
|
|
| 4 | +-- uses the btr/bts/btc instructions (#25233).
|
|
| 5 | +module T25233 where
|
|
| 6 | + |
|
| 7 | +import GHC.Exts
|
|
| 8 | + |
|
| 9 | +myClearBit :: Word# -> Int# -> Word#
|
|
| 10 | +myClearBit x i = x `and#` not# (1## `uncheckedShiftL#` i)
|
|
| 11 | + |
|
| 12 | +mySetBit :: Word# -> Int# -> Word#
|
|
| 13 | +mySetBit x i = x `or#` (1## `uncheckedShiftL#` i)
|
|
| 14 | + |
|
| 15 | +myComplementBit :: Word# -> Int# -> Word#
|
|
| 16 | +myComplementBit x i = x `xor#` (1## `uncheckedShiftL#` i) |
| ... | ... | @@ -23,6 +23,8 @@ test('avx512-int64-minmax', [unless(arch('x86_64'), skip), |
| 23 | 23 | when(unregisterised(), skip)], compile_grep_asm, ['hs', True, '-mavx512vl'])
|
| 24 | 24 | test('avx512-word64-minmax', [unless(arch('x86_64'), skip),
|
| 25 | 25 | when(unregisterised(), skip)], compile_grep_asm, ['hs', True, '-mavx512vl'])
|
| 26 | +test('T25233', [unless(arch('x86_64'), skip),
|
|
| 27 | + when(unregisterised(), skip)], compile_grep_asm, ['hs', True, '-O'])
|
|
| 26 | 28 | is_aarch64_codegen = [
|
| 27 | 29 | unless(arch('aarch64'), skip),
|
| 28 | 30 | when(unregisterised(), skip),
|