[Git][ghc/ghc][wip/fix-26109] 6 commits: Handle non-fractional CmmFloats in Cmm's CBE (#26229)

recursion-ninja pushed to branch wip/fix-26109 at Glasgow Haskell Compiler / GHC Commits: 03555ed8 by Sylvain Henry at 2025-08-10T22:20:57-04:00 Handle non-fractional CmmFloats in Cmm's CBE (#26229) Since f8d9d016305be355f518c141f6c6d4826f2de9a2, toRational for Float and Double converts float's infinity and NaN into Rational's infinity and NaN (respectively 1%0 and 0%0). Cmm CommonBlockEliminator hashing function needs to take these values into account as they can appear as literals now. See added testcase. - - - - - 6c956af3 by J. Ryan Stinnett at 2025-08-10T22:21:42-04:00 Fix extensions list in `DoAndIfThenElse` docs - - - - - 6dc420b1 by J. Ryan Stinnett at 2025-08-10T22:21:42-04:00 Document status of `RelaxedPolyRec` extension This adds a brief extension page explaining the status of the `RelaxedPolyRec` extension. The behaviour of this mode is already explained elsewhere, so this page is mainly for completeness so that various lists of extensions have somewhere to point to for this flag. Fixes #18630 - - - - - 18036d52 by Simon Peyton Jones at 2025-08-11T11:31:20-04:00 Take more care in zonkEqTypes on AppTy/AppTy This patch fixes #26256. See Note [zonkEqTypes and the PKTI] in GHC.Tc.Solver.Equality - - - - - c8d76a29 by Zubin Duggal at 2025-08-11T11:32:02-04:00 ci: upgrade bootstrap compiler on windows to 9.10.1 - - - - - eb410f1b by Recursion Ninja at 2025-08-11T13:08:58-04:00 Resolving issues #20645 and #26109 Correctly sign extending and casting smaller bit width types for LLVM operations: - bitReverse8# - bitReverse16# - bitReverse32# - byteSwap16# - byteSwap32# - pdep8# - pdep16# - pext8# - pext16# - - - - - 29 changed files: - .gitlab/generate-ci/gen_ci.hs - .gitlab/jobs.yaml - compiler/GHC/Builtin/primops.txt.pp - compiler/GHC/Cmm/CommonBlockElim.hs - compiler/GHC/CmmToLlvm/CodeGen.hs - compiler/GHC/Tc/Solver/Equality.hs - docs/users_guide/conf.py - docs/users_guide/expected-undocumented-flags.txt - docs/users_guide/exts/doandifthenelse.rst - + docs/users_guide/exts/relaxed_poly_rec.rst - docs/users_guide/exts/types.rst - libraries/ghc-internal/cbits/pdep.c - libraries/ghc-internal/cbits/pext.c - + testsuite/tests/llvm/should_run/T20645.hs - + testsuite/tests/llvm/should_run/T20645.stdout - testsuite/tests/llvm/should_run/all.T - + testsuite/tests/numeric/should_compile/T26229.hs - testsuite/tests/numeric/should_compile/all.T - testsuite/tests/numeric/should_run/foundation.hs - + testsuite/tests/partial-sigs/should_compile/T26256.hs - + testsuite/tests/partial-sigs/should_compile/T26256.stderr - testsuite/tests/partial-sigs/should_compile/all.T - + testsuite/tests/typecheck/should_compile/T26256a.hs - testsuite/tests/typecheck/should_compile/all.T - utils/genprimopcode/Lexer.x - utils/genprimopcode/Main.hs - utils/genprimopcode/Parser.y - utils/genprimopcode/ParserM.hs - utils/genprimopcode/Syntax.hs Changes: ===================================== .gitlab/generate-ci/gen_ci.hs ===================================== @@ -478,7 +478,7 @@ opsysVariables _ (Windows {}) = mconcat , "LANG" =: "en_US.UTF-8" , "CABAL_INSTALL_VERSION" =: "3.10.2.0" , "HADRIAN_ARGS" =: "--docs=no-sphinx-pdfs" - , "GHC_VERSION" =: "9.6.4" + , "GHC_VERSION" =: "9.10.1" ] opsysVariables _ _ = mempty ===================================== .gitlab/jobs.yaml ===================================== @@ -3698,7 +3698,7 @@ "BUILD_FLAVOUR": "validate", "CABAL_INSTALL_VERSION": "3.10.2.0", "CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", - "GHC_VERSION": "9.6.4", + "GHC_VERSION": "9.10.1", "HADRIAN_ARGS": "--docs=no-sphinx-pdfs", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "LANG": "en_US.UTF-8", @@ -3761,7 +3761,7 @@ "BUILD_FLAVOUR": "validate", "CABAL_INSTALL_VERSION": "3.10.2.0", "CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", - "GHC_VERSION": "9.6.4", + "GHC_VERSION": "9.10.1", "HADRIAN_ARGS": "--docs=no-sphinx-pdfs", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "LANG": "en_US.UTF-8", @@ -5579,7 +5579,7 @@ "BUILD_FLAVOUR": "release", "CABAL_INSTALL_VERSION": "3.10.2.0", "CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", - "GHC_VERSION": "9.6.4", + "GHC_VERSION": "9.10.1", "HADRIAN_ARGS": "--docs=no-sphinx-pdfs", "IGNORE_PERF_FAILURES": "all", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", @@ -5643,7 +5643,7 @@ "BUILD_FLAVOUR": "release", "CABAL_INSTALL_VERSION": "3.10.2.0", "CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", - "GHC_VERSION": "9.6.4", + "GHC_VERSION": "9.10.1", "HADRIAN_ARGS": "--docs=no-sphinx-pdfs", "IGNORE_PERF_FAILURES": "all", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", @@ -7982,7 +7982,7 @@ "BUILD_FLAVOUR": "validate", "CABAL_INSTALL_VERSION": "3.10.2.0", "CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", - "GHC_VERSION": "9.6.4", + "GHC_VERSION": "9.10.1", "HADRIAN_ARGS": "--docs=no-sphinx-pdfs", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "LANG": "en_US.UTF-8", @@ -8044,7 +8044,7 @@ "BUILD_FLAVOUR": "validate", "CABAL_INSTALL_VERSION": "3.10.2.0", "CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", - "GHC_VERSION": "9.6.4", + "GHC_VERSION": "9.10.1", "HADRIAN_ARGS": "--docs=no-sphinx-pdfs", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "LANG": "en_US.UTF-8", ===================================== compiler/GHC/Builtin/primops.txt.pp ===================================== @@ -148,6 +148,7 @@ defaults vector = [] deprecated_msg = {} -- A non-empty message indicates deprecation div_like = False -- Second argument expected to be non zero - used for tests + defined_bits = Nothing -- The number of bits the operation is defined for (if not all bits) -- Note [When do out-of-line primops go in primops.txt.pp] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1065,8 +1066,10 @@ primop CtzOp "ctz#" GenPrimOp Word# -> Word# primop BSwap16Op "byteSwap16#" GenPrimOp Word# -> Word# {Swap bytes in the lower 16 bits of a word. The higher bytes are undefined. } + with defined_bits = 16 primop BSwap32Op "byteSwap32#" GenPrimOp Word# -> Word# {Swap bytes in the lower 32 bits of a word. The higher bytes are undefined. } + with defined_bits = 32 primop BSwap64Op "byteSwap64#" GenPrimOp Word64# -> Word64# {Swap bytes in a 64 bits of a word.} primop BSwapOp "byteSwap#" GenPrimOp Word# -> Word# @@ -1074,10 +1077,13 @@ primop BSwapOp "byteSwap#" GenPrimOp Word# -> Word# primop BRev8Op "bitReverse8#" GenPrimOp Word# -> Word# {Reverse the order of the bits in a 8-bit word.} + with defined_bits = 8 primop BRev16Op "bitReverse16#" GenPrimOp Word# -> Word# {Reverse the order of the bits in a 16-bit word.} + with defined_bits = 16 primop BRev32Op "bitReverse32#" GenPrimOp Word# -> Word# {Reverse the order of the bits in a 32-bit word.} + with defined_bits = 32 primop BRev64Op "bitReverse64#" GenPrimOp Word64# -> Word64# {Reverse the order of the bits in a 64-bit word.} primop BRevOp "bitReverse#" GenPrimOp Word# -> Word# ===================================== compiler/GHC/Cmm/CommonBlockElim.hs ===================================== @@ -29,6 +29,7 @@ import GHC.Utils.Word64 (truncateWord64ToWord32) import Control.Arrow (first, second) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NE +import GHC.Real (infinity,notANumber) -- ----------------------------------------------------------------------------- -- Eliminate common blocks @@ -167,7 +168,12 @@ hash_block block = hash_lit :: CmmLit -> Word32 hash_lit (CmmInt i _) = fromInteger i - hash_lit (CmmFloat r _) = truncate r + hash_lit (CmmFloat r _) + -- handle these special cases as `truncate` fails on non-fractional numbers (#26229) + | r == infinity = 9999999 + | r == -infinity = 9999998 + | r == notANumber = 6666666 + | otherwise = truncate r hash_lit (CmmVec ls) = hash_list hash_lit ls hash_lit (CmmLabel _) = 119 -- ugh hash_lit (CmmLabelOff _ i) = cvt $ 199 + i ===================================== compiler/GHC/CmmToLlvm/CodeGen.hs ===================================== @@ -230,23 +230,22 @@ genCall t@(PrimTarget (MO_Prefetch_Data localityInt)) [] args statement $ Expr $ Call StdCall fptr (argVars' ++ argSuffix) [] | otherwise = panic $ "prefetch locality level integer must be between 0 and 3, given: " ++ (show localityInt) --- Handle PopCnt, Clz, Ctz, and BSwap that need to only convert arg --- and return types -genCall t@(PrimTarget (MO_PopCnt w)) dsts args = - genCallSimpleCast w t dsts args - -genCall t@(PrimTarget (MO_Pdep w)) dsts args = - genCallSimpleCast2 w t dsts args -genCall t@(PrimTarget (MO_Pext w)) dsts args = - genCallSimpleCast2 w t dsts args -genCall t@(PrimTarget (MO_Clz w)) dsts args = - genCallSimpleCast w t dsts args -genCall t@(PrimTarget (MO_Ctz w)) dsts args = - genCallSimpleCast w t dsts args -genCall t@(PrimTarget (MO_BSwap w)) dsts args = - genCallSimpleCast w t dsts args -genCall t@(PrimTarget (MO_BRev w)) dsts args = - genCallSimpleCast w t dsts args +-- Handle Clz, Ctz, BRev, BSwap, Pdep, Pext, and PopCnt that need to only +-- convert arg and return types +genCall (PrimTarget op@(MO_Clz w)) [dst] args = + genCallSimpleCast w op dst args +genCall (PrimTarget op@(MO_Ctz w)) [dst] args = + genCallSimpleCast w op dst args +genCall (PrimTarget op@(MO_BRev w)) [dst] args = + genCallSimpleCast w op dst args +genCall (PrimTarget op@(MO_BSwap w)) [dst] args = + genCallSimpleCast w op dst args +genCall (PrimTarget op@(MO_Pdep w)) [dst] args = + genCallSimpleCast w op dst args +genCall (PrimTarget op@(MO_Pext w)) [dst] args = + genCallSimpleCast w op dst args +genCall (PrimTarget op@(MO_PopCnt w)) [dst] args = + genCallSimpleCast w op dst args genCall (PrimTarget (MO_AtomicRMW width amop)) [dst] [addr, n] = runStmtsDecls $ do addrVar <- exprToVarW addr @@ -640,63 +639,28 @@ genCallExtract _ _ _ _ = -- since GHC only really has i32 and i64 types and things like Word8 are backed -- by an i32 and just present a logical i8 range. So we must handle conversions -- from i32 to i8 explicitly as LLVM is strict about types. -genCallSimpleCast :: Width -> ForeignTarget -> [CmmFormal] -> [CmmActual] - -> LlvmM StmtData -genCallSimpleCast w t@(PrimTarget op) [dst] args = do - let width = widthToLlvmInt w - dstTy = cmmToLlvmType $ localRegType dst - - fname <- cmmPrimOpFunctions op - (fptr, _, top3) <- getInstrinct fname width [width] - - (dstV, _dst_ty) <- getCmmReg (CmmLocal dst) - - let (_, arg_hints) = foreignTargetHints t - let args_hints = zip args arg_hints - (argsV, stmts2, top2) <- arg_vars args_hints ([], nilOL, []) - (argsV', stmts4) <- castVars Signed $ zip argsV [width] - (retV, s1) <- doExpr width $ Call StdCall fptr argsV' [] - (retVs', stmts5) <- castVars (cmmPrimOpRetValSignage op) [(retV,dstTy)] - let retV' = singletonPanic "genCallSimpleCast" retVs' - let s2 = Store retV' dstV Nothing [] - - let stmts = stmts2 `appOL` stmts4 `snocOL` - s1 `appOL` stmts5 `snocOL` s2 - return (stmts, top2 ++ top3) -genCallSimpleCast _ _ dsts _ = - panic ("genCallSimpleCast: " ++ show (length dsts) ++ " dsts") - --- Handle simple function call that only need simple type casting, of the form: --- truncate arg >>= \a -> call(a) >>= zext --- --- since GHC only really has i32 and i64 types and things like Word8 are backed --- by an i32 and just present a logical i8 range. So we must handle conversions --- from i32 to i8 explicitly as LLVM is strict about types. -genCallSimpleCast2 :: Width -> ForeignTarget -> [CmmFormal] -> [CmmActual] - -> LlvmM StmtData -genCallSimpleCast2 w t@(PrimTarget op) [dst] args = do - let width = widthToLlvmInt w - dstTy = cmmToLlvmType $ localRegType dst - - fname <- cmmPrimOpFunctions op - (fptr, _, top3) <- getInstrinct fname width (const width <$> args) - - (dstV, _dst_ty) <- getCmmReg (CmmLocal dst) - - let (_, arg_hints) = foreignTargetHints t - let args_hints = zip args arg_hints - (argsV, stmts2, top2) <- arg_vars args_hints ([], nilOL, []) - (argsV', stmts4) <- castVars Signed $ zip argsV (const width <$> argsV) - (retV, s1) <- doExpr width $ Call StdCall fptr argsV' [] - (retVs', stmts5) <- castVars (cmmPrimOpRetValSignage op) [(retV,dstTy)] - let retV' = singletonPanic "genCallSimpleCast2" retVs' - let s2 = Store retV' dstV Nothing [] +genCallSimpleCast :: Width -> CallishMachOp -> CmmFormal -> [CmmActual] + -> LlvmM StmtData +genCallSimpleCast specW op dst args = do + let width = widthToLlvmInt specW + argsW = const width <$> args + dstType = cmmToLlvmType $ localRegType dst + signage = cmmPrimOpRetValSignage op + + fname <- cmmPrimOpFunctions op + (fptr, _, top3) <- getInstrinct fname width argsW + (dstV, _dst_ty) <- getCmmReg (CmmLocal dst) + let (_, arg_hints) = foreignTargetHints $ PrimTarget op + let args_hints = zip args arg_hints + (argsV, stmts2, top2) <- arg_vars args_hints ([], nilOL, []) + (argsV', stmts4) <- castVars signage $ zip argsV argsW + (retV, s1) <- doExpr width $ Call StdCall fptr argsV' [] + (retV', stmts5) <- castVar signage retV dstType + let s2 = Store retV' dstV Nothing [] let stmts = stmts2 `appOL` stmts4 `snocOL` - s1 `appOL` stmts5 `snocOL` s2 + s1 `snocOL` stmts5 `snocOL` s2 return (stmts, top2 ++ top3) -genCallSimpleCast2 _ _ dsts _ = - panic ("genCallSimpleCast2: " ++ show (length dsts) ++ " dsts") -- | Create a function pointer from a target. getFunPtrW :: (LMString -> LlvmType) -> ForeignTarget @@ -811,11 +775,47 @@ castVar signage v t | getVarType v == t Signed -> LM_Sext Unsigned -> LM_Zext - cmmPrimOpRetValSignage :: CallishMachOp -> Signage cmmPrimOpRetValSignage mop = case mop of - MO_Pdep _ -> Unsigned - MO_Pext _ -> Unsigned + -- Some bit-wise operations /must/ always treat the input and output values + -- as 'Unsigned' in order to return the expected result values when pre/post- + -- operation bit-width truncation and/or extension occur. For example, + -- consider the Bit-Reverse operation: + -- + -- If the result of a Bit-Reverse is treated as signed, + -- an positive input can result in an negative output, i.e.: + -- + -- identity(0x03) = 0x03 = 00000011 + -- breverse(0x03) = 0xC0 = 11000000 + -- + -- Now if an extension is performed after the operation to + -- promote a smaller bit-width value into a larger bit-width + -- type, it is expected that the /bit-wise/ operations will + -- not be treated /numerically/ as signed. + -- + -- To illustrate the difference, consider how a signed extension + -- for the type i16 to i32 differs for out values above: + -- ext_zeroed(i32, breverse(0x03)) = 0x00C0 = 0000000011000000 + -- ext_signed(i32, breverse(0x03)) = 0xFFC0 = 1111111111000000 + -- + -- Here we can see that the former output is the expected result + -- of a bit-wise operation which needs to be promoted to a larger + -- bit-width type. The latter output is not desirable when we must + -- constraining a value into a range of i16 within an i32 type. + -- + -- Hence we always treat the "signage" as unsigned for Bit-Reverse! + -- + -- The same reasoning applied to Bit-Reverse above applies to the other + -- bit-wise operations; do not sign extend a possibly negated number! + MO_BRev _ -> Unsigned + MO_BSwap _ -> Unsigned + MO_Clz _ -> Unsigned + MO_Ctz _ -> Unsigned + MO_Pdep _ -> Unsigned + MO_Pext _ -> Unsigned + MO_PopCnt _ -> Unsigned + + -- All other cases, default to preserving the numeric sign when extending. _ -> Signed -- | Decide what C function to use to implement a CallishMachOp ===================================== compiler/GHC/Tc/Solver/Equality.hs ===================================== @@ -197,12 +197,8 @@ zonkEqTypes ev eq_rel ty1 ty2 then tycon tc1 tys1 tys2 else bale_out ty1 ty2 - go ty1 ty2 - | Just (ty1a, ty1b) <- tcSplitAppTyNoView_maybe ty1 - , Just (ty2a, ty2b) <- tcSplitAppTyNoView_maybe ty2 - = do { res_a <- go ty1a ty2a - ; res_b <- go ty1b ty2b - ; return $ combine_rev mkAppTy res_b res_a } + -- If you are temppted to add a case for AppTy/AppTy, be careful + -- See Note [zonkEqTypes and the PKTI] go ty1@(LitTy lit1) (LitTy lit2) | lit1 == lit2 @@ -278,6 +274,32 @@ zonkEqTypes ev eq_rel ty1 ty2 combine_rev f (Right tys) (Right ty) = Right (f ty tys) +{- Note [zonkEqTypes and the PKTI] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Because `zonkEqTypes` does /partial/ zonking, we need to be very careful +to maintain the Purely Kinded Type Invariant: see GHC.Tc.Gen/HsType +HsNote [The Purely Kinded Type Invariant (PKTI)]. + +In #26256 we try to solve this equality constraint: + Int :-> Maybe Char ~# k0 Int (m0 Char) +where m0 and k0 are unification variables, and + m0 :: Type -> Type +It happens that m0 was already unified + m0 := (w0 :: kappa) +where kappa is another unification variable that is also already unified: + kappa := Type->Type. +So the original type satisifed the PKTI, but a partially-zonked form + k0 Int (w0 Char) +does not!! (This a bit reminiscent of Note [mkAppTyM].) + +The solution I have adopted is simply to make `zonkEqTypes` bale out on `AppTy`. +After all, it's only supposed to be a quick hack to see if two types are already +equal; if we bale out we'll just get into the "proper" canonicaliser. + +The only tricky thing about this approach is that it relies on /omitting/ +code -- for the AppTy/AppTy case! Hence this Note +-} + {- ********************************************************************* * * * canonicaliseEquality ===================================== docs/users_guide/conf.py ===================================== @@ -35,8 +35,6 @@ nitpick_ignore = [ ("envvar", "TMPDIR"), ("c:type", "bool"), - - ("extension", "RelaxedPolyRec"), ] rst_prolog = """ ===================================== docs/users_guide/expected-undocumented-flags.txt ===================================== @@ -14,7 +14,6 @@ -XPolymorphicComponents -XRecordPuns -XRelaxedLayout --XRelaxedPolyRec -copy-libs-when-linking -dannot-lint -dppr-ticks ===================================== docs/users_guide/exts/doandifthenelse.rst ===================================== @@ -8,7 +8,7 @@ Do And If Then Else :since: 7.0.1 - :status: Included in :extension:`Haskell2010` + :status: Included in :extension:`GHC2024`, :extension:`GHC2021`, :extension:`Haskell2010` Allow semicolons in ``if`` expressions. ===================================== docs/users_guide/exts/relaxed_poly_rec.rst ===================================== @@ -0,0 +1,17 @@ +.. _relaxed-poly-rec: + +Generalised typing of mutually recursive bindings +------------------------------------------------- + +.. extension:: RelaxedPolyRec + :shortdesc: Generalised typing of mutually recursive bindings. + + :since: 6.8.1 + + :status: Included in :extension:`GHC2024`, :extension:`GHC2021`, :extension:`Haskell2010` + +See :ref:`infelicities-recursive-groups` for a description of this extension. +This is a long-standing GHC extension. Around the time of GHC 7.6.3, this +extension became required as part of a typechecker refactoring. +The ``-XRelaxedPolyRec`` flag is now deprecated (since the feature is always +enabled) and may be removed at some future time. ===================================== docs/users_guide/exts/types.rst ===================================== @@ -30,3 +30,4 @@ Types type_errors defer_type_errors roles + relaxed_poly_rec ===================================== libraries/ghc-internal/cbits/pdep.c ===================================== @@ -24,20 +24,23 @@ hs_pdep64(StgWord64 src, StgWord64 mask) return result; } +// When dealing with values of bit-width shorter than uint64_t, ensure to +// cast the return value to correctly truncate the undefined upper bits. +// This is *VERY* important when GHC is using the LLVM backend! StgWord hs_pdep32(StgWord src, StgWord mask) { - return hs_pdep64(src, mask); + return (StgWord) ((StgWord32) hs_pdep64(src, mask)); } StgWord hs_pdep16(StgWord src, StgWord mask) { - return hs_pdep64(src, mask); + return (StgWord) ((StgWord16) hs_pdep64(src, mask)); } StgWord hs_pdep8(StgWord src, StgWord mask) { - return hs_pdep64(src, mask); + return (StgWord) ((StgWord8) hs_pdep64(src, mask)); } ===================================== libraries/ghc-internal/cbits/pext.c ===================================== @@ -1,13 +1,13 @@ #include "Rts.h" #include "MachDeps.h" -StgWord64 -hs_pext64(StgWord64 src, StgWord64 mask) +static StgWord64 +hs_pext(const unsigned char bit_width, const StgWord64 src, const StgWord64 mask) { uint64_t result = 0; int offset = 0; - for (int bit = 0; bit != sizeof(uint64_t) * 8; ++bit) { + for (int bit = 0; bit != bit_width; ++bit) { const uint64_t src_bit = (src >> bit) & 1; const uint64_t mask_bit = (mask >> bit) & 1; @@ -20,20 +20,29 @@ hs_pext64(StgWord64 src, StgWord64 mask) return result; } +StgWord64 +hs_pext64(const StgWord64 src, const StgWord64 mask) +{ + return hs_pext(64, src, mask); +} + +// When dealing with values of bit-width shorter than uint64_t, ensure to +// cast the return value to correctly truncate the undefined upper bits. +// This is *VERY* important when GHC is using the LLVM backend! StgWord -hs_pext32(StgWord src, StgWord mask) +hs_pext32(const StgWord src, const StgWord mask) { - return hs_pext64(src, mask); + return (StgWord) ((StgWord32) hs_pext(32, src, mask)); } StgWord -hs_pext16(StgWord src, StgWord mask) +hs_pext16(const StgWord src, const StgWord mask) { - return hs_pext64(src, mask); + return (StgWord) ((StgWord16) hs_pext(16, src, mask)); } StgWord -hs_pext8(StgWord src, StgWord mask) +hs_pext8(const StgWord src, const StgWord mask) { - return hs_pext64(src, mask); + return (StgWord) ((StgWord8) hs_pext(8, src, mask)); } ===================================== testsuite/tests/llvm/should_run/T20645.hs ===================================== @@ -0,0 +1,18 @@ +-- Minimal reproducer for https://gitlab.haskell.org/ghc/ghc/-/issues/20645 +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE ExtendedLiterals #-} +import GHC.Exts +import GHC.Word +import Numeric (showHex) + +opaqueInt8# :: Int8# -> Int8# +opaqueInt8# x = x +{-# OPAQUE opaqueInt8# #-} + +main :: IO () +main = let !x = opaqueInt8# 109#Int8 + !y = opaqueInt8# 1#Int8 + in putStrLn $ flip showHex "" (W# ( pext8# + (word8ToWord# (int8ToWord8# (0#Int8 `subInt8#` x ))) + (word8ToWord# (int8ToWord8# (y `subInt8#` 4#Int8))) + )) ===================================== testsuite/tests/llvm/should_run/T20645.stdout ===================================== @@ -0,0 +1 @@ +49 ===================================== testsuite/tests/llvm/should_run/all.T ===================================== @@ -17,3 +17,4 @@ test('T22487', [normal, normalise_errmsg_fun(ignore_llvm_and_vortex)], compile_a test('T22033', [normal, normalise_errmsg_fun(ignore_llvm_and_vortex)], compile_and_run, ['']) test('T25730', [req_c, unless(arch('x86_64'), skip), normalise_errmsg_fun(ignore_llvm_and_vortex)], compile_and_run, ['T25730C.c']) # T25730C.c contains Intel instrinsics, so only run this test on x86 +test('T20645', [normal, normalise_errmsg_fun(ignore_llvm_and_vortex), when(have_llvm(), extra_ways(["optllvm"]))], compile_and_run, ['']) ===================================== testsuite/tests/numeric/should_compile/T26229.hs ===================================== @@ -0,0 +1,12 @@ +{-# LANGUAGE NegativeLiterals #-} + +module T26229 where + +sqrte2pqiq :: (Floating a, Ord a) => a -> a -> a +sqrte2pqiq e qiq -- = sqrt (e*e + qiq) + | e < - 1.5097698010472593e153 = -(qiq/e) - e + | e < 5.582399551122541e57 = sqrt (e*e + qiq) -- test Infinity# + | e < -5.582399551122541e57 = -sqrt (e*e + qiq) -- test -Infinity# + | otherwise = (qiq/e) + e +{-# SPECIALIZE sqrte2pqiq :: Double -> Double -> Double #-} +{-# SPECIALIZE sqrte2pqiq :: Float -> Float -> Float #-} ===================================== testsuite/tests/numeric/should_compile/all.T ===================================== @@ -22,3 +22,4 @@ test('T15547', normal, compile, ['-ddump-simpl -O -dsuppress-all -dno-typeable-b test('T23019', normal, compile, ['-O']) test('T23907', [ when(wordsize(32), expect_broken(23908))], compile, ['-ddump-simpl -O2 -dsuppress-all -dno-typeable-binds -dsuppress-uniques']) test('T24331', normal, compile, ['-O -ddump-simpl -dsuppress-all -dsuppress-uniques -dno-typeable-binds']) +test('T26229', normal, compile, ['-O2']) ===================================== testsuite/tests/numeric/should_run/foundation.hs ===================================== @@ -24,6 +24,7 @@ module Main ( main ) where +import Data.Bits (Bits((.&.), bit)) import Data.Word import Data.Int import GHC.Natural @@ -408,6 +409,33 @@ instance TestPrimop (Word# -> Int# -> Word#) where testPrimop s l r = Property s $ \(uWord -> a1) (uInt -> a2) -> (wWord (l a1 a2)) === wWord (r a1 a2) -} +-- | A special data-type for representing functions where, +-- since only some number of the lower bits are defined, +-- testing for strict equality in the undefined upper bits is not appropriate! +-- Without using this data-type, false-positive failures will be reported +-- when the undefined bit regions do not match, even though the equality of bits +-- in this undefined region has no bearing on correctness. +data LowerBitsAreDefined = + LowerBitsAreDefined + { definedLowerWidth :: Word + -- ^ The (strictly-non-negative) number of least-significant bits + -- for which the attached function is defined. + , undefinedBehavior :: (Word# -> Word#) + -- ^ Function with undefined behavior for some of its most significant bits. + } + +instance TestPrimop LowerBitsAreDefined where + testPrimop s l r = Property s $ \ (uWord#-> x0) -> + let -- Create a mask to unset all bits in the undefined area, + -- leaving set bits only in the area of defined behavior. + -- Since the upper bits are undefined, + -- if the function defines behavior for the lower N bits, + -- then /only/ the lower N bits are preserved, + -- and the upper WORDSIZE - N bits are discarded. + mask = bit (fromEnum (definedLowerWidth r)) - 1 + valL = wWord# (undefinedBehavior l x0) .&. mask + valR = wWord# (undefinedBehavior r x0) .&. mask + in valL === valR twoNonZero :: (a -> a -> b) -> a -> NonZero a -> b twoNonZero f x (NonZero y) = f x y @@ -655,13 +683,13 @@ testPrimops = Group "primop" , testPrimop "ctz32#" Primop.ctz32# Wrapper.ctz32# , testPrimop "ctz64#" Primop.ctz64# Wrapper.ctz64# , testPrimop "ctz#" Primop.ctz# Wrapper.ctz# - , testPrimop "byteSwap16#" Primop.byteSwap16# Wrapper.byteSwap16# - , testPrimop "byteSwap32#" Primop.byteSwap32# Wrapper.byteSwap32# + , testPrimop "byteSwap16#" (16 `LowerBitsAreDefined` Primop.byteSwap16#) (16 `LowerBitsAreDefined` Wrapper.byteSwap16#) + , testPrimop "byteSwap32#" (32 `LowerBitsAreDefined` Primop.byteSwap32#) (32 `LowerBitsAreDefined` Wrapper.byteSwap32#) , testPrimop "byteSwap64#" Primop.byteSwap64# Wrapper.byteSwap64# , testPrimop "byteSwap#" Primop.byteSwap# Wrapper.byteSwap# - , testPrimop "bitReverse8#" Primop.bitReverse8# Wrapper.bitReverse8# - , testPrimop "bitReverse16#" Primop.bitReverse16# Wrapper.bitReverse16# - , testPrimop "bitReverse32#" Primop.bitReverse32# Wrapper.bitReverse32# + , testPrimop "bitReverse8#" (8 `LowerBitsAreDefined` Primop.bitReverse8#) (8 `LowerBitsAreDefined` Wrapper.bitReverse8#) + , testPrimop "bitReverse16#" (16 `LowerBitsAreDefined` Primop.bitReverse16#) (16 `LowerBitsAreDefined` Wrapper.bitReverse16#) + , testPrimop "bitReverse32#" (32 `LowerBitsAreDefined` Primop.bitReverse32#) (32 `LowerBitsAreDefined` Wrapper.bitReverse32#) , testPrimop "bitReverse64#" Primop.bitReverse64# Wrapper.bitReverse64# , testPrimop "bitReverse#" Primop.bitReverse# Wrapper.bitReverse# , testPrimop "narrow8Int#" Primop.narrow8Int# Wrapper.narrow8Int# ===================================== testsuite/tests/partial-sigs/should_compile/T26256.hs ===================================== @@ -0,0 +1,23 @@ +{-# LANGUAGE GHC2021 #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE PartialTypeSignatures #-} + +module M (go) where + +import Data.Kind + +type Apply :: (Type -> Type) -> Type +data Apply m + +type (:->) :: Type -> Type -> Type +type family (:->) where (:->) = (->) + +f :: forall (k :: Type -> Type -> Type) (m :: Type -> Type). + k Int (m Char) -> k Bool (Apply m) +f = f + +x :: Int :-> Maybe Char +x = x + +go :: Bool -> _ _ +go = f x ===================================== testsuite/tests/partial-sigs/should_compile/T26256.stderr ===================================== @@ -0,0 +1,8 @@ +T26256.hs:22:15: warning: [GHC-88464] [-Wpartial-type-signatures (in -Wdefault)] + • Found type wildcard ‘_’ standing for ‘Apply :: (* -> *) -> *’ + • In the type signature: go :: Bool -> _ _ + +T26256.hs:22:17: warning: [GHC-88464] [-Wpartial-type-signatures (in -Wdefault)] + • Found type wildcard ‘_’ standing for ‘Maybe :: * -> *’ + • In the first argument of ‘_’, namely ‘_’ + In the type signature: go :: Bool -> _ _ ===================================== testsuite/tests/partial-sigs/should_compile/all.T ===================================== @@ -108,3 +108,4 @@ test('T21667', normal, compile, ['']) test('T22065', normal, compile, ['']) test('T16152', normal, compile, ['']) test('T20076', expect_broken(20076), compile, ['']) +test('T26256', normal, compile, ['']) ===================================== testsuite/tests/typecheck/should_compile/T26256a.hs ===================================== @@ -0,0 +1,19 @@ +{-# LANGUAGE GHC2021 #-} +{-# LANGUAGE TypeFamilies #-} + +module T26256 (go) where + +import Data.Kind + +class Cat k where (<<<) :: k a b -> k x a -> k x b +instance Cat (->) where (<<<) = (.) +class Pro k p where pro :: k a b s t -> p a b -> p s t +data Hiding o a b s t = forall e. Hiding (s -> o e a) +newtype Apply e a = Apply (e a) + +type (:->) :: Type -> Type -> Type +type family (:->) where + (:->) = (->) + +go :: (Pro (Hiding Apply) p) => (s :-> e a) -> p a b -> p s t +go sea = pro (Hiding (Apply <<< sea)) ===================================== testsuite/tests/typecheck/should_compile/all.T ===================================== @@ -940,3 +940,4 @@ test('T26020', normal, compile, ['']) test('T26020a', [extra_files(['T26020a_help.hs'])], multimod_compile, ['T26020a', '-v0']) test('T25992', normal, compile, ['']) test('T14010', normal, compile, ['']) +test('T26256a', normal, compile, ['']) ===================================== utils/genprimopcode/Lexer.x ===================================== @@ -56,6 +56,7 @@ words :- <0> "CanFail" { mkT TCanFail } <0> "ThrowsException" { mkT TThrowsException } <0> "ReadWriteEffect" { mkT TReadWriteEffect } + <0> "defined_bits" { mkT TDefinedBits } <0> "can_fail_warning" { mkT TCanFailWarnFlag } <0> "DoNotWarnCanFail" { mkT TDoNotWarnCanFail } <0> "WarnIfEffectIsCanFail" { mkT TWarnIfEffectIsCanFail } ===================================== utils/genprimopcode/Main.hs ===================================== @@ -10,6 +10,7 @@ module Main where import Parser import Syntax +import Control.Applicative (asum) import Data.Char import Data.List (union, intersperse, intercalate, nub, sort) import Data.Maybe ( catMaybes, mapMaybe ) @@ -116,9 +117,15 @@ desugarVectorSpec i = case vecOptions i of main :: IO () main = getArgs >>= \args -> if length args /= 1 || head args `notElem` known_args - then error ("usage: genprimopcode command < primops.txt > ...\n" + then error ("Usage: genprimopcode command < primops.txt > ...\n" ++ " where command is one of\n" ++ unlines (map (" "++) known_args) + ++ unlines + [ "" + , "Nota Bene: Be sure to manually run primops.txt through the C Pre-Processor" + , " before sending the input stream to STDIN, i.e:" + , "" + , " cpp -P -w primops.txt | genprimopcode command" ] ) else do hSetEncoding stdin utf8 -- The input file is in UTF-8. Set the encoding explicitly. @@ -312,6 +319,7 @@ gen_hs_source (Info defaults entries) = opt (OptionVector _) = "" opt (OptionFixity mf) = "fixity = " ++ show mf opt (OptionEffect eff) = "effect = " ++ show eff + opt (OptionDefinedBits bc) = "defined_bits = " ++ show bc opt (OptionCanFailWarnFlag wf) = "can_fail_warning = " ++ show wf hdr s@(Section {}) = sec s @@ -638,6 +646,7 @@ gen_switch_from_attribs attrib_name fn_name (Info defaults entries) getAltRhs (OptionVector _) = "True" getAltRhs (OptionFixity mf) = show mf getAltRhs (OptionEffect eff) = show eff + getAltRhs (OptionDefinedBits bc) = show bc getAltRhs (OptionCanFailWarnFlag wf) = show wf mkAlt po @@ -753,7 +762,12 @@ gen_foundation_tests (Info _ entries) = let testPrimOpHow = if is_divLikeOp po then "testPrimopDivLike" else "testPrimop" - in Just $ intercalate " " [testPrimOpHow, "\"" ++ poName ++ "\"", wrap "Primop" poName, wrap "Wrapper" poName] + qualOp qualification = + let qName = wrap qualification poName + in case mb_defined_bits po of + Nothing -> qName + Just bs -> concat ["(", show bs, " `LowerBitsAreDefined` ", qName, ")"] + in Just $ intercalate " " [testPrimOpHow, "\"" ++ poName ++ "\"", qualOp "Primop", qualOp "Wrapper"] | otherwise = Nothing @@ -771,6 +785,16 @@ gen_foundation_tests (Info _ entries) divableTyCons = ["Int#", "Word#", "Word8#", "Word16#", "Word32#", "Word64#" ,"Int8#", "Int16#", "Int32#", "Int64#"] + mb_defined_bits :: Entry -> Maybe Word + mb_defined_bits op@(PrimOpSpec{}) = + let opOpts = opts op + getDefBits :: Option -> Maybe Word + getDefBits (OptionDefinedBits x) = x + getDefBits _ = Nothing + in asum $ getDefBits <$> opOpts + mb_defined_bits _ = Nothing + + ------------------------------------------------------------------ -- Create PrimOpInfo text from PrimOpSpecs ----------------------- ------------------------------------------------------------------ ===================================== utils/genprimopcode/Parser.y ===================================== @@ -50,6 +50,7 @@ import AccessOps CanFail { TCanFail } ThrowsException { TThrowsException } ReadWriteEffect { TReadWriteEffect } + defined_bits { TDefinedBits } can_fail_warning { TCanFailWarnFlag } DoNotWarnCanFail { TDoNotWarnCanFail } WarnIfEffectIsCanFail { TWarnIfEffectIsCanFail } @@ -81,13 +82,14 @@ pOptions : pOption pOptions { $1 : $2 } | {- empty -} { [] } pOption :: { Option } -pOption : lowerName '=' false { OptionFalse $1 } - | lowerName '=' true { OptionTrue $1 } - | lowerName '=' pStuffBetweenBraces { OptionString $1 $3 } - | lowerName '=' integer { OptionInteger $1 $3 } - | vector '=' pVectorTemplate { OptionVector $3 } - | fixity '=' pInfix { OptionFixity $3 } - | effect '=' pEffect { OptionEffect $3 } +pOption : lowerName '=' false { OptionFalse $1 } + | lowerName '=' true { OptionTrue $1 } + | lowerName '=' pStuffBetweenBraces { OptionString $1 $3 } + | lowerName '=' integer { OptionInteger $1 $3 } + | vector '=' pVectorTemplate { OptionVector $3 } + | fixity '=' pInfix { OptionFixity $3 } + | effect '=' pEffect { OptionEffect $3 } + | defined_bits '=' pGoodBits { OptionDefinedBits $3 } | can_fail_warning '=' pPrimOpCanFailWarnFlag { OptionCanFailWarnFlag $3 } pInfix :: { Maybe Fixity } @@ -102,6 +104,10 @@ pEffect : NoEffect { NoEffect } | ThrowsException { ThrowsException } | ReadWriteEffect { ReadWriteEffect } +pGoodBits :: { Maybe Word } +pGoodBits : integer { Just $ toEnum $1 } + | nothing { Nothing } + pPrimOpCanFailWarnFlag :: { PrimOpCanFailWarnFlag } pPrimOpCanFailWarnFlag : DoNotWarnCanFail { DoNotWarnCanFail } | WarnIfEffectIsCanFail { WarnIfEffectIsCanFail } ===================================== utils/genprimopcode/ParserM.hs ===================================== @@ -116,6 +116,7 @@ data Token = TEOF | TCanFail | TThrowsException | TReadWriteEffect + | TDefinedBits | TCanFailWarnFlag | TDoNotWarnCanFail | TWarnIfEffectIsCanFail ===================================== utils/genprimopcode/Syntax.hs ===================================== @@ -76,6 +76,7 @@ data Option | OptionFixity (Maybe Fixity) -- fixity = infix{,l,r} <int> | Nothing | OptionEffect PrimOpEffect -- effect = NoEffect | DoNotSpeculate | CanFail | ThrowsException | ReadWriteEffect | FallibleReadWriteEffect | OptionCanFailWarnFlag PrimOpCanFailWarnFlag -- can_fail_warning = DoNotWarnCanFail | WarnIfEffectIsCanFail | YesWarnCanFail + | OptionDefinedBits (Maybe Word) -- defined_bits = Just 16 | Nothing deriving Show -- categorises primops @@ -196,6 +197,7 @@ get_attrib_name (OptionVector _) = "vector" get_attrib_name (OptionFixity _) = "fixity" get_attrib_name (OptionEffect _) = "effect" get_attrib_name (OptionCanFailWarnFlag _) = "can_fail_warning" +get_attrib_name (OptionDefinedBits _) = "defined_bits" lookup_attrib :: String -> [Option] -> Maybe Option lookup_attrib _ [] = Nothing View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/01be20d3762af6fe8df718cb86788c2... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/01be20d3762af6fe8df718cb86788c2... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
recursion-ninja (@recursion-ninja)