Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
636c1c7a
by Ian Duncan at 2026-06-16T20:26:50-04:00
14 changed files:
- + changelog.d/T26978
- + changelog.d/T27047
- compiler/GHC/CmmToAsm/AArch64/CodeGen.hs
- compiler/GHC/CmmToAsm/AArch64/Instr.hs
- compiler/GHC/CmmToAsm/AArch64/Ppr.hs
- + testsuite/tests/codeGen/should_gen_asm/aarch64-sxth-mul2.asm
- + testsuite/tests/codeGen/should_gen_asm/aarch64-sxth-mul2.cmm
- + testsuite/tests/codeGen/should_gen_asm/aarch64-sxtw.asm
- + testsuite/tests/codeGen/should_gen_asm/aarch64-sxtw.cmm
- testsuite/tests/codeGen/should_gen_asm/all.T
- + testsuite/tests/codeGen/should_run/aarch64-sxtw-cmm.cmm
- + testsuite/tests/codeGen/should_run/aarch64-sxtw-run.hs
- + testsuite/tests/codeGen/should_run/aarch64-sxtw-run.stdout
- testsuite/tests/codeGen/should_run/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +issues: #26978
|
|
| 3 | +mrs: !15619
|
|
| 4 | +synopsis:
|
|
| 5 | + AArch64 code generation: use ``SXTH`` instead of ``SXTW`` for ``W32`` sign extension
|
|
| 6 | +description:
|
|
| 7 | + Fix the AArch64 code generator incorrectly using using ``SXTH``
|
|
| 8 | + (sign-extend halfword, 16-bit) for ``W32``-to-``W64`` sign extension, and
|
|
| 9 | + always use the source width for the source operand in sign extension
|
|
| 10 | + instructions.
|
|
| 11 | + |
|
| 12 | + Also ensures that sub-W64 code generation for ``MO_S_Mul2`` uses ``W32``
|
|
| 13 | + registers as mandated by the ARM specification.
|
|
| 14 | + |
| 1 | +section: compiler
|
|
| 2 | +issues: #27047
|
|
| 3 | +mrs: !15619
|
|
| 4 | +synopsis:
|
|
| 5 | + Fix incorrect overflow bit for ``MUL2`` on AArch64 for sub-``W64`` operands.
|
|
| 6 | +description:
|
|
| 7 | + This fixes the computation of the overflow bit for ``MO_S_Mul2`` on
|
|
| 8 | + sub-``W64`` operands; the old condition was exactly inverted.
|
|
| 9 | + |
| ... | ... | @@ -1894,15 +1894,15 @@ signExtendReg w w' r = |
| 1894 | 1894 | W64 -> noop
|
| 1895 | 1895 | W32
|
| 1896 | 1896 | | w' == W32 -> noop
|
| 1897 | - | otherwise -> extend SXTH
|
|
| 1897 | + | otherwise -> extend SXTW
|
|
| 1898 | 1898 | W16 -> extend SXTH
|
| 1899 | 1899 | W8 -> extend SXTB
|
| 1900 | 1900 | _ -> panic "intOp"
|
| 1901 | 1901 | where
|
| 1902 | 1902 | noop = return (r, nilOL)
|
| 1903 | 1903 | extend instr = do
|
| 1904 | - r' <- getNewRegNat II64
|
|
| 1905 | - return (r', unitOL $ instr (OpReg w' r') (OpReg w' r))
|
|
| 1904 | + r' <- getNewRegNat (intFormat w')
|
|
| 1905 | + return (r', unitOL $ instr (OpReg w' r') (OpReg w r))
|
|
| 1906 | 1906 | |
| 1907 | 1907 | -- | Instructions to truncate the value in the given register from width @w@
|
| 1908 | 1908 | -- down to width @w'@.
|
| ... | ... | @@ -2333,41 +2333,35 @@ genCCall target dest_regs arg_regs = do |
| 2333 | 2333 | let lo = getRegisterReg platform (CmmLocal dst_lo)
|
| 2334 | 2334 | hi = getRegisterReg platform (CmmLocal dst_hi)
|
| 2335 | 2335 | nd = getRegisterReg platform (CmmLocal dst_needed)
|
| 2336 | - -- Do everything in a full 64 bit registers
|
|
| 2337 | 2336 | w' = platformWordWidth platform
|
| 2338 | 2337 | |
| 2339 | - (reg_a, code_a') <- signExtendReg w w' reg_a'
|
|
| 2340 | - (reg_b, code_b') <- signExtendReg w w' reg_b'
|
|
| 2338 | + -- Sign-extend inputs to W32 for SMULL (Xd = Wn * Wm).
|
|
| 2339 | + -- signExtendReg always allocates a fresh temp for w < W32,
|
|
| 2340 | + -- and is a noop for W32 (safe: SMULL reads both sources
|
|
| 2341 | + -- atomically before writing the destination).
|
|
| 2342 | + (reg_a, code_a') <- signExtendReg w W32 reg_a'
|
|
| 2343 | + (reg_b, code_b') <- signExtendReg w W32 reg_b'
|
|
| 2341 | 2344 | |
| 2342 | 2345 | return $
|
| 2343 | 2346 | code_a `appOL`
|
| 2344 | 2347 | code_b `appOL`
|
| 2345 | 2348 | code_a' `appOL`
|
| 2346 | 2349 | code_b' `snocOL`
|
| 2347 | - -- the low 2w' of lo contains the full multiplication;
|
|
| 2348 | - -- eg: int8 * int8 -> int16 result
|
|
| 2349 | - -- so lo is in the last w of the register, and hi is in the second w.
|
|
| 2350 | - SMULL (OpReg w' lo) (OpReg w' reg_a) (OpReg w' reg_b) `snocOL`
|
|
| 2351 | - -- Make sure we hold onto the sign bits for dst_needed
|
|
| 2352 | - ASR (OpReg w' hi) (OpReg w' lo) (OpImm (ImmInt $ widthInBits w)) `appOL`
|
|
| 2353 | - -- lo can now be truncated so we can get at it's top bit easily.
|
|
| 2350 | + -- SMULL Xd, Wn, Wm: multiply two W32 values producing a
|
|
| 2351 | + -- 64-bit result. The low w bits of lo contain the truncated
|
|
| 2352 | + -- product, and hi gets the overflow (sign extension bits).
|
|
| 2353 | + SMULL (OpReg w' lo) (OpReg W32 reg_a) (OpReg W32 reg_b) `snocOL`
|
|
| 2354 | + ASR (OpReg w' hi) (OpReg w' lo) (OpImm (ImmInt $ widthInBits w)) `appOL`
|
|
| 2354 | 2355 | truncateReg w' w lo `snocOL`
|
| 2355 | - -- Note the use of CMN (compare negative), not CMP: we want to
|
|
| 2356 | - -- test if the top half is negative one and the top
|
|
| 2357 | - -- bit of the bottom half is positive one. eg:
|
|
| 2358 | - -- hi = 0b1111_1111 (actually 64 bits)
|
|
| 2359 | - -- lo = 0b1010_1111 (-81, so the result didn't need the top half)
|
|
| 2360 | - -- lo' = ASR(lo,7) (second reg of SMN)
|
|
| 2361 | - -- = 0b0000_0001 (theeshift gives us 1 for negative,
|
|
| 2362 | - -- and 0 for positive)
|
|
| 2363 | - -- hi == -lo'?
|
|
| 2364 | - -- 0b1111_1111 == 0b1111_1111 (yes, top half is just overflow)
|
|
| 2365 | - -- Another way to think of this is if hi + lo' == 0, which is what
|
|
| 2366 | - -- CMN really is under the hood.
|
|
| 2356 | + -- CMN (compare negative) tests hi + lo' == 0, i.e. hi == -lo'.
|
|
| 2357 | + -- lo' = LSR(lo, w-1) gives 1 if lo is negative, 0 if positive.
|
|
| 2358 | + -- No overflow iff hi is the sign extension of lo:
|
|
| 2359 | + -- lo positive (bit w-1 = 0) => lo' = 0, need hi == 0
|
|
| 2360 | + -- lo negative (bit w-1 = 1) => lo' = 1, need hi == -1
|
|
| 2361 | + -- CMN sets Z when hi + lo' == 0 (no overflow), so we use
|
|
| 2362 | + -- NE to set nd = 1 when overflow occurred.
|
|
| 2367 | 2363 | CMN (OpReg w' hi) (OpRegShift w' lo SLSR (widthInBits w - 1)) `snocOL`
|
| 2368 | - -- Set dst_needed to 1 if hi and lo' were (negatively) equal
|
|
| 2369 | - CSET (OpReg w' nd) EQ `appOL`
|
|
| 2370 | - -- Finally truncate hi to drop any extraneous sign bits.
|
|
| 2364 | + CSET (OpReg w' nd) NE `appOL`
|
|
| 2371 | 2365 | truncateReg w' w hi
|
| 2372 | 2366 | -- Can't handle > 64 bit operands
|
| 2373 | 2367 | | otherwise -> unsupported (MO_S_Mul2 w)
|
| ... | ... | @@ -105,6 +105,7 @@ regUsageOfInstr platform instr = case instr of |
| 105 | 105 | SXTB dst src -> usage (regOp src, regOp dst)
|
| 106 | 106 | UXTB dst src -> usage (regOp src, regOp dst)
|
| 107 | 107 | SXTH dst src -> usage (regOp src, regOp dst)
|
| 108 | + SXTW dst src -> usage (regOp src, regOp dst)
|
|
| 108 | 109 | UXTH dst src -> usage (regOp src, regOp dst)
|
| 109 | 110 | CLZ dst src -> usage (regOp src, regOp dst)
|
| 110 | 111 | RBIT dst src -> usage (regOp src, regOp dst)
|
| ... | ... | @@ -296,6 +297,7 @@ patchRegsOfInstr instr env = case instr of |
| 296 | 297 | SXTB o1 o2 -> SXTB (patchOp o1) (patchOp o2)
|
| 297 | 298 | UXTB o1 o2 -> UXTB (patchOp o1) (patchOp o2)
|
| 298 | 299 | SXTH o1 o2 -> SXTH (patchOp o1) (patchOp o2)
|
| 300 | + SXTW o1 o2 -> SXTW (patchOp o1) (patchOp o2)
|
|
| 299 | 301 | UXTH o1 o2 -> UXTH (patchOp o1) (patchOp o2)
|
| 300 | 302 | CLZ o1 o2 -> CLZ (patchOp o1) (patchOp o2)
|
| 301 | 303 | RBIT o1 o2 -> RBIT (patchOp o1) (patchOp o2)
|
| ... | ... | @@ -699,8 +701,7 @@ data Instr |
| 699 | 701 | | UXTB Operand Operand
|
| 700 | 702 | | SXTH Operand Operand
|
| 701 | 703 | | UXTH Operand Operand
|
| 702 | - -- | SXTW Operand Operand
|
|
| 703 | - -- | SXTX Operand Operand
|
|
| 704 | + | SXTW Operand Operand
|
|
| 704 | 705 | | PUSH_STACK_FRAME
|
| 705 | 706 | | POP_STACK_FRAME
|
| 706 | 707 | -- 1. Arithmetic Instructions ----------------------------------------------
|
| ... | ... | @@ -857,6 +858,7 @@ instrCon i = |
| 857 | 858 | SXTB{} -> "SXTB"
|
| 858 | 859 | UXTB{} -> "UXTB"
|
| 859 | 860 | SXTH{} -> "SXTH"
|
| 861 | + SXTW{} -> "SXTW"
|
|
| 860 | 862 | UXTH{} -> "UXTH"
|
| 861 | 863 | PUSH_STACK_FRAME{} -> "PUSH_STACK_FRAME"
|
| 862 | 864 | POP_STACK_FRAME{} -> "POP_STACK_FRAME"
|
| ... | ... | @@ -516,6 +516,7 @@ pprInstr platform instr = case instr of |
| 516 | 516 | SXTB o1 o2 -> op2 (text "\tsxtb") o1 o2
|
| 517 | 517 | UXTB o1 o2 -> op2 (text "\tuxtb") o1 o2
|
| 518 | 518 | SXTH o1 o2 -> op2 (text "\tsxth") o1 o2
|
| 519 | + SXTW o1 o2 -> op2 (text "\tsxtw") o1 o2
|
|
| 519 | 520 | UXTH o1 o2 -> op2 (text "\tuxth") o1 o2
|
| 520 | 521 | |
| 521 | 522 | -- 3. Logical and Move Instructions ------------------------------------------
|
| 1 | +sxth |
| 1 | +#include "Cmm.h"
|
|
| 2 | + |
|
| 3 | +// Exercises MO_S_Mul2 W16: signExtendReg W16 W32 must emit SXTH to
|
|
| 4 | +// sign-extend the 16-bit inputs to 32-bit before SMULL.
|
|
| 5 | +testMul2W16 (W_ buffer) {
|
|
| 6 | + I16 a, b, needed, hi, lo;
|
|
| 7 | + a = I16[buffer];
|
|
| 8 | + b = I16[buffer + 2];
|
|
| 9 | + (needed, hi, lo) = prim %mul2_16(a, b);
|
|
| 10 | + I16[buffer + 4] = lo;
|
|
| 11 | + return();
|
|
| 12 | +} |
| 1 | +smull |
| 1 | +#include "Cmm.h"
|
|
| 2 | + |
|
| 3 | +// Exercises MO_S_Mul2 W32, which uses SMULL (Xd = Wn * Wm).
|
|
| 4 | +// After the fix, signExtendReg W32 W32 is a noop (no SXTW needed),
|
|
| 5 | +// but the SMULL instruction itself must still appear.
|
|
| 6 | +testMul2W32 (W_ buffer) {
|
|
| 7 | + I32 a, b, needed, hi, lo;
|
|
| 8 | + a = I32[buffer];
|
|
| 9 | + b = I32[buffer + 4];
|
|
| 10 | + (needed, hi, lo) = prim %mul2_32(a, b);
|
|
| 11 | + I32[buffer + 8] = lo;
|
|
| 12 | + return();
|
|
| 13 | +} |
| ... | ... | @@ -31,3 +31,5 @@ is_aarch64_codegen = [ |
| 31 | 31 | # AArch64-specific tests
|
| 32 | 32 | test('aarch64-ushr-subword', is_aarch64_codegen, compile_grep_asm, ['hs', True, '-O'])
|
| 33 | 33 | test('aarch64-shl-subword', is_aarch64_codegen, compile_grep_asm, ['hs', True, '-O'])
|
| 34 | +test('aarch64-sxtw', is_aarch64_codegen, compile_grep_asm, ['cmm', True, ''])
|
|
| 35 | +test('aarch64-sxth-mul2', is_aarch64_codegen, compile_grep_asm, ['cmm', True, '']) |
| 1 | +#include "Cmm.h"
|
|
| 2 | + |
|
| 3 | +// MO_S_Mul2 W32: signExtendReg W32 W32 is a noop (inputs already W32).
|
|
| 4 | +// Tests the SMULL path with values whose bit 15 != bit 31 (the old SXTH
|
|
| 5 | +// bug would corrupt these).
|
|
| 6 | +runMul2W32zh (W_ x) {
|
|
| 7 | + I32 a, b, needed, hi, lo;
|
|
| 8 | + a = %lobits32(x);
|
|
| 9 | + b = 2::I32;
|
|
| 10 | + (needed, hi, lo) = prim %mul2_32(a, b);
|
|
| 11 | + return(TO_W_(lo));
|
|
| 12 | +}
|
|
| 13 | + |
|
| 14 | +// MO_S_Mul2 W16: signExtendReg W16 W32 emits SXTH before SMULL.
|
|
| 15 | +runMul2W16zh (W_ x) {
|
|
| 16 | + I16 a, b, needed, hi, lo;
|
|
| 17 | + a = %lobits16(x);
|
|
| 18 | + b = 2::I16;
|
|
| 19 | + (needed, hi, lo) = prim %mul2_16(a, b);
|
|
| 20 | + return(TO_W_(lo));
|
|
| 21 | +}
|
|
| 22 | + |
|
| 23 | +// MO_S_Mul2 W8: signExtendReg W8 W32 emits SXTB before SMULL.
|
|
| 24 | +runMul2W8zh (W_ x) {
|
|
| 25 | + I8 a, b, needed, hi, lo;
|
|
| 26 | + a = %lobits8(x);
|
|
| 27 | + b = 2::I8;
|
|
| 28 | + (needed, hi, lo) = prim %mul2_8(a, b);
|
|
| 29 | + return(TO_W_(lo));
|
|
| 30 | +}
|
|
| 31 | + |
|
| 32 | +// MO_S_Mul2 W16 returning (needed, hi, lo) as a packed triple.
|
|
| 33 | +// Tests the CSET NE overflow detection: needed=1 when overflow occurs.
|
|
| 34 | +// Returns (needed << 32) | (lo & 0xFFFF) so the Haskell side can inspect both.
|
|
| 35 | +runMul2W16Overflowzh (W_ x, W_ y) {
|
|
| 36 | + I16 a, b, needed, hi, lo;
|
|
| 37 | + a = %lobits16(x);
|
|
| 38 | + b = %lobits16(y);
|
|
| 39 | + (needed, hi, lo) = prim %mul2_16(a, b);
|
|
| 40 | + return(TO_W_(needed) * 0x100000000 + (TO_W_(lo) `and` 0xFFFF));
|
|
| 41 | +} |
| 1 | +{-# LANGUAGE ForeignFunctionInterface #-}
|
|
| 2 | +{-# LANGUAGE GHCForeignImportPrim #-}
|
|
| 3 | +{-# LANGUAGE MagicHash #-}
|
|
| 4 | +{-# LANGUAGE UnliftedFFITypes #-}
|
|
| 5 | +module Main where
|
|
| 6 | + |
|
| 7 | +import GHC.Exts
|
|
| 8 | +import Data.Bits (shiftR, (.&.))
|
|
| 9 | + |
|
| 10 | +foreign import prim "runMul2W32zh" runMul2W32# :: Word# -> Word#
|
|
| 11 | +foreign import prim "runMul2W16zh" runMul2W16# :: Word# -> Word#
|
|
| 12 | +foreign import prim "runMul2W8zh" runMul2W8# :: Word# -> Word#
|
|
| 13 | +foreign import prim "runMul2W16Overflowzh"
|
|
| 14 | + runMul2W16Overflow# :: Word# -> Word# -> Word#
|
|
| 15 | + |
|
| 16 | +mul2W32 :: Word -> Word
|
|
| 17 | +mul2W32 (W# x) = W# (runMul2W32# x)
|
|
| 18 | + |
|
| 19 | +mul2W16 :: Word -> Word
|
|
| 20 | +mul2W16 (W# x) = W# (runMul2W16# x)
|
|
| 21 | + |
|
| 22 | +mul2W8 :: Word -> Word
|
|
| 23 | +mul2W8 (W# x) = W# (runMul2W8# x)
|
|
| 24 | + |
|
| 25 | +mul2W16Overflow :: Word -> Word -> (Word, Word)
|
|
| 26 | +mul2W16Overflow (W# x) (W# y) =
|
|
| 27 | + let r = W# (runMul2W16Overflow# x y)
|
|
| 28 | + needed = r `shiftR` 32
|
|
| 29 | + lo = r .&. 0xFFFF
|
|
| 30 | + in (needed, lo)
|
|
| 31 | + |
|
| 32 | +main :: IO ()
|
|
| 33 | +main = do
|
|
| 34 | + -- W32: 50000 has bit 15 set, bit 31 clear. The old SXTH bug would
|
|
| 35 | + -- sign-extend from bit 15, corrupting the value before SMULL.
|
|
| 36 | + putStrLn $ "W32 50000*2 = " ++ show (mul2W32 50000)
|
|
| 37 | + putStrLn $ "W32 1*2 = " ++ show (mul2W32 1)
|
|
| 38 | + putStrLn $ "W32 70000*2 = " ++ show (mul2W32 70000)
|
|
| 39 | + |
|
| 40 | + -- W16: exercises signExtendReg W16 W32 (SXTH before SMULL).
|
|
| 41 | + putStrLn $ "W16 200*2 = " ++ show (mul2W16 200)
|
|
| 42 | + putStrLn $ "W16 100*2 = " ++ show (mul2W16 100)
|
|
| 43 | + |
|
| 44 | + -- W8: exercises signExtendReg W8 W32 (SXTB before SMULL).
|
|
| 45 | + putStrLn $ "W8 50*2 = " ++ show (mul2W8 50)
|
|
| 46 | + putStrLn $ "W8 3*2 = " ++ show (mul2W8 3)
|
|
| 47 | + |
|
| 48 | + -- Overflow detection (CSET NE fix):
|
|
| 49 | + -- 200 * 200 = 40000, overflows I16 [-32768, 32767]. needed=1.
|
|
| 50 | + let (n1, lo1) = mul2W16Overflow 200 200
|
|
| 51 | + putStrLn $ "W16 200*200: needed=" ++ show n1 ++ " lo=" ++ show lo1
|
|
| 52 | + -- 100 * 2 = 200, fits in I16. needed=0.
|
|
| 53 | + let (n2, lo2) = mul2W16Overflow 100 2
|
|
| 54 | + putStrLn $ "W16 100*2: needed=" ++ show n2 ++ " lo=" ++ show lo2
|
|
| 55 | + -- 181 * 181 = 32761, just under 32767. needed=0.
|
|
| 56 | + let (n3, lo3) = mul2W16Overflow 181 181
|
|
| 57 | + putStrLn $ "W16 181*181: needed=" ++ show n3 ++ " lo=" ++ show lo3
|
|
| 58 | + -- 182 * 182 = 33124, just over 32767. needed=1.
|
|
| 59 | + let (n4, lo4) = mul2W16Overflow 182 182
|
|
| 60 | + putStrLn $ "W16 182*182: needed=" ++ show n4 ++ " lo=" ++ show lo4 |
| 1 | +W32 50000*2 = 100000
|
|
| 2 | +W32 1*2 = 2
|
|
| 3 | +W32 70000*2 = 140000
|
|
| 4 | +W16 200*2 = 400
|
|
| 5 | +W16 100*2 = 200
|
|
| 6 | +W8 50*2 = 100
|
|
| 7 | +W8 3*2 = 6
|
|
| 8 | +W16 200*200: needed=1 lo=40000
|
|
| 9 | +W16 100*2: needed=0 lo=200
|
|
| 10 | +W16 181*181: needed=0 lo=32761
|
|
| 11 | +W16 182*182: needed=1 lo=33124 |
| ... | ... | @@ -289,3 +289,9 @@ test('T27072w', [req_c, js_skip, when(opsys('darwin'), skip)], |
| 289 | 289 | # AArch64-specific runtime tests
|
| 290 | 290 | test('aarch64-ushr-subword-run', [unless(arch('aarch64'), skip)], compile_and_run, ['-O'])
|
| 291 | 291 | test('aarch64-subword-ops', [unless(arch('aarch64'), skip)], compile_and_run, ['-O'])
|
| 292 | +test('aarch64-sxtw-run',
|
|
| 293 | + [ extra_files(['aarch64-sxtw-cmm.cmm']),
|
|
| 294 | + unless(arch('aarch64'), skip),
|
|
| 295 | + when(unregisterised(), skip)],
|
|
| 296 | + multi_compile_and_run,
|
|
| 297 | + ['aarch64-sxtw-run', [('aarch64-sxtw-cmm.cmm', '')], '-O']) |