Magnus pushed to branch wip/mangoiv/27817 at Glasgow Haskell Compiler / GHC

Commits:

18 changed files:

Changes:

  • .gitlab-ci.yml
    ... ... @@ -137,15 +137,37 @@ workflow:
    137 137
     .lint:
    
    138 138
       stage: tool-lint
    
    139 139
       tags:
    
    140
    -    - lint
    
    141
    -  before_script:
    
    142
    -    # Allow Git to read from the runner-owned checkout. This is needed
    
    143
    -    # for example by the lint-author job.
    
    144
    -    - git config --global --add safe.directory "$CI_PROJECT_DIR"
    
    140
    +    - lint-anyarch
    
    141
    +  image: debian:13
    
    145 142
       rules:
    
    146 143
         - *drafts-can-fail-lint
    
    147 144
         - when: always
    
    148 145
     
    
    146
    +.install-ghc-toolchain: &install-ghc-toolchain
    
    147
    +  - apt update
    
    148
    +  - apt install -qy build-essential curl libffi-dev libffi8 libgmp-dev libgmp10 libncurses-dev libncurses6 libtinfo6 pkg-config python3 cmake git happy alex autoconf automake python3 libgmp-dev libnuma-dev libncurses-dev
    
    149
    +  - curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_MINIMAL=1 sh
    
    150
    +  - source /root/.ghcup/env
    
    151
    +  - ghcup --no-verbose install ghc --set --install-targets 'install_bin install_lib update_package_db' '9.10'
    
    152
    +  - ghcup --no-verbose install cabal --set
    
    153
    +  - ghc --info
    
    154
    +  - cabal --version
    
    155
    +
    
    156
    +# linting but we need a boot toolchain
    
    157
    +.lint-ghc-toolchain:
    
    158
    +  extends: .lint
    
    159
    +  before_script:
    
    160
    +    - *install-ghc-toolchain
    
    161
    +    - git config --global --add safe.directory "$CI_PROJECT_DIR"
    
    162
    +
    
    163
    +# linting but we need python
    
    164
    +.lint-python:
    
    165
    +  extends: .lint
    
    166
    +  before_script:
    
    167
    +    - apt update
    
    168
    +    - apt install -qy python3 mypy shellcheck git
    
    169
    +    - git config --global --add safe.directory "$CI_PROJECT_DIR"
    
    170
    +
    
    149 171
     .nightly: &nightly
    
    150 172
       variables:
    
    151 173
         XZ_OPT: "-9"
    
    ... ... @@ -217,11 +239,11 @@ not-interruptible:
    217 239
       stage: not-interruptible
    
    218 240
       script: "true"
    
    219 241
       interruptible: false
    
    220
    -  image: "debian:13"
    
    242
    +  image: debian:13
    
    221 243
       variables:
    
    222 244
         GIT_STRATEGY: none
    
    223 245
       tags:
    
    224
    -    - lint
    
    246
    +    - lint-anyarch
    
    225 247
       rules:
    
    226 248
     #   - if: '$CI_COMMIT_BRANCH == "master"'
    
    227 249
     #      when: always
    
    ... ... @@ -242,7 +264,6 @@ include: '.gitlab/jobs.yaml'
    242 264
     
    
    243 265
     ghc-linters:
    
    244 266
       stage: tool-lint
    
    245
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    246 267
       extends: .lint-params
    
    247 268
       variables:
    
    248 269
         BUILD_FLAVOUR: default
    
    ... ... @@ -258,16 +279,11 @@ ghc-linters:
    258 279
     # Skipped if the MR has the ~"no-changelog" label.
    
    259 280
     lint-changelog:
    
    260 281
       stage: tool-lint
    
    261
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    262
    -  extends: .lint
    
    282
    +  extends: .lint-ghc-toolchain
    
    263 283
       variables:
    
    264 284
         CHANGELOG_EXPECT_MR: "$CI_MERGE_REQUEST_IID"
    
    265 285
         GIT_DEPTH: 1
    
    266 286
         GIT_SUBMODULE_STRATEGY: none
    
    267
    -  before_script:
    
    268
    -    - export PATH="/opt/toolchain/bin:$PATH"
    
    269
    -    # workaround for docker permissions
    
    270
    -    - sudo chown ghc:ghc -R .
    
    271 287
       script:
    
    272 288
         - .gitlab/ci.sh lint_changelog
    
    273 289
       dependencies: []
    
    ... ... @@ -279,42 +295,30 @@ lint-changelog:
    279 295
     
    
    280 296
     # Run mypy Python typechecker on linter scripts.
    
    281 297
     lint-linters:
    
    282
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/linters:$DOCKER_REV"
    
    283
    -  extends: .lint
    
    298
    +  extends: .lint-python
    
    284 299
       variables:
    
    285 300
         GIT_DEPTH: 1
    
    286 301
         GIT_SUBMODULE_STRATEGY: none
    
    287
    -  before_script:
    
    288
    -    # workaround for docker permissions
    
    289
    -    - sudo chown ghc:ghc -R .
    
    290 302
       script:
    
    291 303
         - mypy testsuite/tests/linters/regex-linters/*.py
    
    292 304
       dependencies: []
    
    293 305
     
    
    294 306
     # Check that .T files all parse by listing broken tests.
    
    295 307
     lint-testsuite:
    
    296
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    297
    -  extends: .lint
    
    308
    +  extends: .lint-ghc-toolchain
    
    298 309
       variables:
    
    299 310
         GIT_DEPTH: 1
    
    300 311
         GIT_SUBMODULE_STRATEGY: none
    
    301
    -  before_script:
    
    302
    -    # workaround for docker permissions
    
    303
    -    - sudo chown ghc:ghc -R .
    
    304 312
       script:
    
    305
    -    - make -Ctestsuite list_broken TEST_HC=$GHC
    
    313
    +    - make -Ctestsuite list_broken TEST_HC=$(which ghc)
    
    306 314
       dependencies: []
    
    307 315
     
    
    308 316
     # Run mypy Python typechecker on testsuite driver
    
    309 317
     typecheck-testsuite:
    
    310
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/linters:$DOCKER_REV"
    
    311
    -  extends: .lint
    
    318
    +  extends: .lint-python
    
    312 319
       variables:
    
    313 320
         GIT_DEPTH: 1
    
    314 321
         GIT_SUBMODULE_STRATEGY: none
    
    315
    -  before_script:
    
    316
    -    # workaround for docker permissions
    
    317
    -    - sudo chown ghc:ghc -R .
    
    318 322
       script:
    
    319 323
         - mypy testsuite/driver/runtests.py
    
    320 324
       dependencies: []
    
    ... ... @@ -323,7 +327,6 @@ typecheck-testsuite:
    323 327
     # accommodate, e.g., haddock changes not yet upstream) but not on `master` or
    
    324 328
     # Marge jobs.
    
    325 329
     .lint-submods:
    
    326
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    327 330
       extends: .lint-params
    
    328 331
       variables:
    
    329 332
         BUILD_FLAVOUR: default
    
    ... ... @@ -341,13 +344,9 @@ typecheck-testsuite:
    341 344
     # Check that commits in an MR were not authored with the identity that CI uses
    
    342 345
     # when it needs to create commits itself. This only needs Git history.
    
    343 346
     lint-author:
    
    344
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    345
    -  extends: .lint
    
    347
    +  extends: .lint-python
    
    346 348
       variables:
    
    347 349
         GIT_SUBMODULE_STRATEGY: none
    
    348
    -  before_script:
    
    349
    -    # workaround for docker permissions
    
    350
    -    - sudo chown ghc:ghc -R .
    
    351 350
       script:
    
    352 351
         - git fetch "$CI_MERGE_REQUEST_PROJECT_URL" $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
    
    353 352
         - base="$(git merge-base FETCH_HEAD $CI_COMMIT_SHA)"
    
    ... ... @@ -425,12 +424,9 @@ lint-submods-branch:
    425 424
     
    
    426 425
     .lint-params:
    
    427 426
       needs: []
    
    428
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    429 427
       extends: .lint
    
    430 428
       before_script:
    
    431
    -    - export PATH="/opt/toolchain/bin:$PATH"
    
    432
    -    # workaround for docker permissions
    
    433
    -    - sudo chown ghc:ghc -R .
    
    429
    +    - *install-ghc-toolchain
    
    434 430
         - .gitlab/ci.sh setup
    
    435 431
       after_script:
    
    436 432
         - .gitlab/ci.sh save_cache
    
    ... ... @@ -448,18 +444,13 @@ lint-submods-branch:
    448 444
     
    
    449 445
     hadrian-ghc-in-ghci:
    
    450 446
       stage: quick-build
    
    447
    +  extends: .lint-ghc-toolchain
    
    451 448
       needs:
    
    452 449
         - job: lint-linters
    
    453 450
         - job: lint-submods
    
    454 451
           optional: true
    
    455
    -  image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    456
    -  before_script:
    
    457
    -    # workaround for docker permissions
    
    458
    -    - sudo chown ghc:ghc -R .
    
    459 452
       variables:
    
    460 453
         GHC_FLAGS: -Werror -Wwarn=unused-imports
    
    461
    -  tags:
    
    462
    -    - x86_64-linux
    
    463 454
       script:
    
    464 455
         - git clean -xdf && git submodule foreach git clean -xdf
    
    465 456
         - . .gitlab/ci.sh setup
    
    ... ... @@ -493,9 +484,6 @@ hadrian-multi:
    493 484
           optional: true
    
    494 485
       dependencies: null
    
    495 486
       image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    496
    -  before_script:
    
    497
    -    # workaround for docker permissions
    
    498
    -    - sudo chown ghc:ghc -R .
    
    499 487
       variables:
    
    500 488
         GHC_FLAGS: "-Werror=-Wno-error=incomplete-record-selectors -Wwarn=deprecations -Wwarn=unused-imports"
    
    501 489
            # -Wno-error=incomplete-record-selectors is present because -Wall now
    
    ... ... @@ -1055,9 +1043,6 @@ perf:
    1055 1043
       image: "registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb13:$DOCKER_REV"
    
    1056 1044
       tags:
    
    1057 1045
         - x86_64-linux-perf
    
    1058
    -  before_script:
    
    1059
    -    # workaround for docker permissions
    
    1060
    -    - sudo chown ghc:ghc -R .
    
    1061 1046
       script:
    
    1062 1047
         - root=$(pwd)/ghc
    
    1063 1048
         - |
    

  • changelog.d/25763-cmdline-opt
    1
    +section: compiler
    
    2
    +synopsis: Speed up command line parser with a trie map
    
    3
    +description:
    
    4
    +  Parsing flags is now faster when there are many flags to parse.
    
    5
    +
    
    6
    +  There is a new trie implementation with String keys in ``GHC.Data.StringTrie``.
    
    7
    +
    
    8
    +  Added a new abstract type ``FlagSpecTrie`` to represent tries of flags.
    
    9
    +
    
    10
    +  ``GHC.Driver.Session`` gets ``flagsDynamic`` removed and ``flagsDynamicTrie``
    
    11
    +  added with type ``FlagSpecTrie``. ``processCmdLineP`` and ``parseDynamicFlagsFull``
    
    12
    +  now have a ``FlagSpecTrie`` parameter instead of a list.
    
    13
    +
    
    14
    +  ``GHC.Driver.Session.Mode`` gets a value ``mode_flags_trie`` of type ``FlagSpecTrie``.
    
    15
    +
    
    16
    +  ``GHC.Driver.CmdLine.processArgs`` now gets a ``FlagSpecTrie`` argument instead
    
    17
    +  of a list.
    
    18
    +mrs: !16392
    
    19
    +issues: #25763

  • compiler/GHC/Data/StringTrie.hs
    1
    +module GHC.Data.StringTrie
    
    2
    +  ( StringTrie(..)
    
    3
    +  , empty
    
    4
    +  , insert
    
    5
    +  , fromList
    
    6
    +  , fromListWithSparseness
    
    7
    +  , lookup
    
    8
    +  ) where
    
    9
    +
    
    10
    +import GHC.Prelude hiding (lookup)
    
    11
    +
    
    12
    +import Data.Array (Array, accumArray, assocs)
    
    13
    +import Data.Maybe (fromMaybe)
    
    14
    +import qualified Data.IntMap.Lazy as M
    
    15
    +
    
    16
    +-- | A trie mapping strings to values of type @a@.
    
    17
    +--
    
    18
    +-- See #25763 and Note [Optimising the processing of command-line arguments]
    
    19
    +-- in "GHC.Driver.Session".
    
    20
    +data StringTrie a
    
    21
    +    = -- | A value and other children under the same path.
    
    22
    +      --
    
    23
    +      -- The 'IntMap' can be empty.
    
    24
    +      STNode a (M.IntMap (StringTrie a))
    
    25
    +      -- | There is no value at this path, but there are children under it
    
    26
    +      --
    
    27
    +      -- The 'IntMap' must be non-empty, so 'STEmpty' remains the only way to
    
    28
    +      -- represent a trie with no values.
    
    29
    +    | STFork (M.IntMap (StringTrie a))
    
    30
    +      -- | A trie with no values
    
    31
    +    | STEmpty
    
    32
    +  deriving (Eq, Show)
    
    33
    +
    
    34
    +-- | There are no key-value pairs in this trie.
    
    35
    +empty :: StringTrie a
    
    36
    +empty = STEmpty
    
    37
    +
    
    38
    +-- | Inserts a key-value pair into the trie. When a value for the key is already
    
    39
    +-- present, the new value replaces the old one.
    
    40
    +insert :: forall a. String -> a -> StringTrie a -> StringTrie a
    
    41
    +insert k0 v = go k0
    
    42
    +  where
    
    43
    +    go :: String -> StringTrie a -> StringTrie a
    
    44
    +    go (x:xs) STEmpty = STFork $ M.singleton (fromEnum x) (go xs STEmpty)
    
    45
    +    go "" STEmpty = STNode v M.empty
    
    46
    +    go "" (STFork children) = STNode v children
    
    47
    +    go (x:xs) (STFork children) =
    
    48
    +        STFork (M.insert (fromEnum x) child children)
    
    49
    +      where
    
    50
    +        child = go xs (fromMaybe empty $ M.lookup (fromEnum x) children)
    
    51
    +
    
    52
    +    go "" (STNode _ children) = STNode v children
    
    53
    +    go (x:xs) (STNode vs children) =
    
    54
    +        STNode vs (M.insert (fromEnum x) child children)
    
    55
    +      where
    
    56
    +        child = go xs (fromMaybe empty $ M.lookup (fromEnum x) children)
    
    57
    +
    
    58
    +-- | Returns the values associated with prefixes of a query string as well as
    
    59
    +-- the non-matching suffix. The matches are returned ordered by suffix length.
    
    60
    +--
    
    61
    +-- Examples:
    
    62
    +--
    
    63
    +-- > lookup "abc" (fromList [("ab", 2), ("abc", 3), ("b", 4)]) == [(3, ""), (2, "c")]
    
    64
    +-- > lookup "abc" (fromList [("ab", 2), ("ab", 3)]) == [(3, "c")]
    
    65
    +-- > lookup "ba" (fromList [("ab", 2), ("abc", 3)]) == []
    
    66
    +--
    
    67
    +-- More formally:
    
    68
    +--
    
    69
    +-- @(v, suffix)@ is in @lookup queryString t@ iff there is a string @k@ such that
    
    70
    +--     * @(k, v)@ is in @t@, and
    
    71
    +--     * @k ++ suffix == queryString@.
    
    72
    +lookup
    
    73
    +    :: String
    
    74
    +    -> StringTrie a
    
    75
    +    -> [(a, String)]
    
    76
    +lookup = go []
    
    77
    +  where
    
    78
    +    go :: [(a, String)] -> String -> StringTrie a -> [(a, String)]
    
    79
    +    --
    
    80
    +    -- In @lookup queryString t@ and @go acc unmatchedSuffix t'@,
    
    81
    +    --
    
    82
    +    -- unmatchedSuffix is a suffix of queryString, and t' is the subtrie
    
    83
    +    -- of t at path @queryString - unmatchedSuffix@.
    
    84
    +    --
    
    85
    +    -- (v, suffix) is in acc iff there is a k such that
    
    86
    +    --    * (k, v) is in t, and
    
    87
    +    --    * @k ++ suffix == queryString@, and
    
    88
    +    --    * @unmatchedSuffix /= suffix@.
    
    89
    +    go acc [] (STNode v  _) = (v, "") : acc
    
    90
    +    go acc [] (STFork _) = acc
    
    91
    +    go acc _ STEmpty = acc
    
    92
    +    go acc unmatchedSuffix@(c:rest) (STNode v children) =
    
    93
    +      let acc' = (v, unmatchedSuffix) : acc
    
    94
    +       in maybe acc' (go acc' rest) (M.lookup (fromEnum c) children)
    
    95
    +    go acc (c:rest) (STFork children) =
    
    96
    +      maybe acc (go acc rest) (M.lookup (fromEnum c) children)
    
    97
    +
    
    98
    +-- | Constructs a trie from a list of key-value pairs.
    
    99
    +--
    
    100
    +-- Later entries in the list will overwrite earlier ones with the same key.
    
    101
    +--
    
    102
    +-- Equivalent to @foldl (\t (k, v) -> insert k v t) empty@, but lazier: the
    
    103
    +-- subtrie for each first character is only built when demanded.
    
    104
    +fromList :: [(String, a)] -> StringTrie a
    
    105
    +fromList = fromListWithSparseness 32
    
    106
    +
    
    107
    +-- | Like 'fromList', but allows the caller to specify a threshold for when to use
    
    108
    +-- an array to sort the keys.
    
    109
    +fromListWithSparseness :: Int -> [(String, a)] -> StringTrie a
    
    110
    +fromListWithSparseness sparseness = go
    
    111
    +  where
    
    112
    +    go [] = STEmpty
    
    113
    +    go xs =
    
    114
    +      let revxs = reverse xs
    
    115
    +          valuesWithEmptyKeys = filter (null . fst) revxs
    
    116
    +          children = M.map go (headBuckets sparseness revxs)
    
    117
    +       in case valuesWithEmptyKeys of
    
    118
    +            (_, v) : _ -> STNode v children
    
    119
    +            _          -> STFork children
    
    120
    +
    
    121
    +    -- Group the entries with a non-empty key by their first character (in reverse order).
    
    122
    +    --
    
    123
    +    -- Discards entries with an empty key. Uses an array to sort the entries, unless the
    
    124
    +    -- entries are too sparse.
    
    125
    +    headBuckets :: Int -> [(String, a)] -> M.IntMap [(String, a)]
    
    126
    +    headBuckets sparseness kvs
    
    127
    +        -- Entries are too sparse if they are too few or if they span too large a range
    
    128
    +        -- of characters.
    
    129
    +      | null (drop sparseness kvs) || any non_ascii kvs
    
    130
    +      = M.fromListWith (++) [ (fromEnum c, [(cs, v)]) | (c:cs, v) <- kvs ]
    
    131
    +      | otherwise
    
    132
    +      = M.fromDistinctAscList
    
    133
    +           [ (i, group) | (i, group) <- assocs (bucketsArr kvs), not (null group) ]
    
    134
    +
    
    135
    +    -- @(k, v)@ is in @bucketsArr kvs ! i@ iff @(tail k, v)@ is in @kvs@ and
    
    136
    +    -- @fromEnum (head k) == i@.
    
    137
    +    --
    
    138
    +    -- Note: Discard entries with an empty key.
    
    139
    +    bucketsArr :: [(String, a)] -> Array Int [(String, a)]
    
    140
    +    bucketsArr kvs =
    
    141
    +      accumArray
    
    142
    +        (flip (:))
    
    143
    +        []
    
    144
    +        (0, 0x7f)
    
    145
    +        [ (fromEnum c, (cs, v)) | (c:cs, v) <- kvs ]
    
    146
    +
    
    147
    +    non_ascii (c:_, _) = fromEnum c > 0x7f
    
    148
    +    non_ascii ([],  _) = False

  • compiler/GHC/Data/Word64Map/Internal.hs
    ... ... @@ -3,6 +3,8 @@
    3 3
     
    
    4 4
     {-# OPTIONS_HADDOCK not-home #-}
    
    5 5
     {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
    
    6
    +-- Speeds up otherwise unoptimized ghcs by 5-25% (#27814)
    
    7
    +{-# OPTIONS_GHC -O2 #-}
    
    6 8
     
    
    7 9
     
    
    8 10
     -----------------------------------------------------------------------------
    

  • compiler/GHC/Data/Word64Map/Strict/Internal.hs
    1 1
     
    
    2 2
     {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
    
    3
    +-- Speeds up otherwise unoptimized ghcs by 5-25% (#27814)
    
    4
    +{-# OPTIONS_GHC -O2 #-}
    
    3 5
     
    
    4 6
     -----------------------------------------------------------------------------
    
    5 7
     -- |
    

  • compiler/GHC/Data/Word64Set/Internal.hs
    ... ... @@ -2,6 +2,8 @@
    2 2
     {-# LANGUAGE TypeFamilies #-}
    
    3 3
     
    
    4 4
     {-# OPTIONS_HADDOCK not-home #-}
    
    5
    +-- Speeds up otherwise unoptimized ghcs by 5-25% (#27814)
    
    6
    +{-# OPTIONS_GHC -O2 #-}
    
    5 7
     
    
    6 8
     -----------------------------------------------------------------------------
    
    7 9
     -- |
    

  • compiler/GHC/Driver/CmdLine.hs
    1
    +{-# LANGUAGE BangPatterns #-}
    
    1 2
     -------------------------------------------------------------------------------
    
    2 3
     --
    
    3 4
     -- | Command-line parser
    
    4 5
     --
    
    5
    --- This is an abstract command-line parser used by DynFlags.
    
    6
    +-- This is a command-line parser used by DynFlags.
    
    6 7
     --
    
    7 8
     -- (c) The University of Glasgow 2005
    
    8 9
     --
    
    ... ... @@ -10,13 +11,26 @@
    10 11
     
    
    11 12
     module GHC.Driver.CmdLine
    
    12 13
         (
    
    13
    -      processArgs, parseResponseFile, OptKind(..), GhcFlagMode(..),
    
    14
    -      Flag(..), defFlag, defGhcFlag, defGhciFlag, defHiddenFlag, hoistFlag,
    
    15
    -      errorsToGhcException,
    
    14
    +      processArgs,
    
    15
    +
    
    16
    +      -- ** Flags
    
    17
    +      Flag(..), defFlag, defGhcFlag, defGhciFlag, defHiddenFlag,
    
    18
    +      OptKind(..), GhcFlagMode(..),
    
    19
    +
    
    20
    +      -- ** EwM monad
    
    21
    +      EwM, runEwM, addErr, addWarn, addFlagWarn, getArg, getCurLoc, liftEwM,
    
    16 22
     
    
    17 23
           Err(..), Warn, warnsToMessages,
    
    18 24
     
    
    19
    -      EwM, runEwM, addErr, addWarn, addFlagWarn, getArg, getCurLoc, liftEwM
    
    25
    +      -- ** FlagSpecTrie
    
    26
    +      FlagSpecTrie, mkFlagSpecTrie, extendFlagSpecTrie,
    
    27
    +
    
    28
    +      -- ** State
    
    29
    +      CmdLineP(..), runCmdLineP,
    
    30
    +      getCmdLineState, putCmdLineState,
    
    31
    +
    
    32
    +      -- ** Utils
    
    33
    +      parseResponseFile, errorsToGhcException
    
    20 34
         ) where
    
    21 35
     
    
    22 36
     import GHC.Prelude
    
    ... ... @@ -24,6 +38,7 @@ import GHC.Prelude
    24 38
     import GHC.Utils.Misc
    
    25 39
     import GHC.Utils.Panic
    
    26 40
     import GHC.Data.Bag
    
    41
    +import qualified GHC.Data.StringTrie as StringTrie
    
    27 42
     import GHC.Types.SrcLoc
    
    28 43
     import GHC.Types.Error
    
    29 44
     import GHC.Utils.Error
    
    ... ... @@ -31,14 +46,16 @@ import GHC.Driver.Errors.Types
    31 46
     import GHC.Driver.Errors.Ppr () -- instance Diagnostic DriverMessage
    
    32 47
     import GHC.Utils.Outputable (text)
    
    33 48
     
    
    34
    -import Data.Function
    
    35
    -import Data.List (sortBy, intercalate, stripPrefix)
    
    49
    +import Data.List (intercalate)
    
    36 50
     import Data.Word
    
    37 51
     
    
    38 52
     import GHC.ResponseFile
    
    39 53
     import Control.Exception (IOException, catch)
    
    40 54
     import Control.Monad (ap)
    
    41 55
     import Control.Monad.IO.Class
    
    56
    +import Control.Monad.Trans.State (StateT)
    
    57
    +import qualified Control.Monad.Trans.State as State
    
    58
    +import Data.Functor.Identity (runIdentity)
    
    42 59
     
    
    43 60
     --------------------------------------------------------
    
    44 61
     --         The Flag and OptKind types
    
    ... ... @@ -62,24 +79,6 @@ defGhciFlag name optKind = Flag name optKind OnlyGhci
    62 79
     defHiddenFlag :: String -> OptKind m -> Flag m
    
    63 80
     defHiddenFlag name optKind = Flag name optKind HiddenFlag
    
    64 81
     
    
    65
    -hoistFlag :: forall m n. (forall a. m a -> n a) -> Flag m -> Flag n
    
    66
    -hoistFlag f (Flag a b c) = Flag a (go b) c
    
    67
    -  where
    
    68
    -      go (NoArg k)  = NoArg (go2 k)
    
    69
    -      go (HasArg k) = HasArg (\s -> go2 (k s))
    
    70
    -      go (SepArg k) = SepArg (\s -> go2 (k s))
    
    71
    -      go (Prefix k) = Prefix (\s -> go2 (k s))
    
    72
    -      go (OptPrefix k) = OptPrefix (\s -> go2 (k s))
    
    73
    -      go (OptIntSuffix k) = OptIntSuffix (\n -> go2 (k n))
    
    74
    -      go (IntSuffix k) = IntSuffix (\n -> go2 (k n))
    
    75
    -      go (Word64Suffix k) = Word64Suffix (\s -> go2 (k s))
    
    76
    -      go (FloatSuffix k) = FloatSuffix (\s -> go2 (k s))
    
    77
    -      go (PassFlag k) = PassFlag (\s -> go2 (k s))
    
    78
    -      go (AnySuffix k) = AnySuffix (\s -> go2 (k s))
    
    79
    -
    
    80
    -      go2 :: EwM m a -> EwM n a
    
    81
    -      go2 (EwM g) = EwM $ \loc es ws -> f (g loc es ws)
    
    82
    -
    
    83 82
     -- | GHC flag modes describing when a flag has an effect.
    
    84 83
     data GhcFlagMode
    
    85 84
         = OnlyGhc  -- ^ The flag only affects the non-interactive GHC
    
    ... ... @@ -159,6 +158,9 @@ getCurLoc = EwM (\(L loc _) es ws -> return (es, ws, loc))
    159 158
     liftEwM :: Monad m => m a -> EwM m a
    
    160 159
     liftEwM action = EwM (\_ es ws -> do { r <- action; return (es, ws, r) })
    
    161 160
     
    
    161
    +hoistEwM :: (forall a. m a -> n a) -> EwM m b -> EwM n b
    
    162
    +hoistEwM f (EwM g) = EwM $ \loc es ws -> f (g loc es ws)
    
    163
    +
    
    162 164
     warnsToMessages :: DiagOpts -> [Warn] -> Messages DriverMessage
    
    163 165
     warnsToMessages diag_opts = foldr
    
    164 166
       (\(L loc w) ws -> addMessage (mkPlainMsgEnvelope diag_opts loc w) ws)
    
    ... ... @@ -168,20 +170,70 @@ warnsToMessages diag_opts = foldr
    168 170
     --         Processing arguments
    
    169 171
     --------------------------------------------------------
    
    170 172
     
    
    171
    -processArgs :: Monad m
    
    172
    -            => [Flag m]               -- ^ cmdline parser spec
    
    173
    -            -> [Located String]       -- ^ args
    
    174
    -            -> (FilePath -> EwM m [Located String]) -- ^ response file handler
    
    175
    -            -> m ( [Located String],  -- spare args
    
    173
    +newtype CmdLineP s a = CmdLineP (forall m. (Monad m) => StateT s m a)
    
    174
    +  deriving (Functor)
    
    175
    +
    
    176
    +instance Monad (CmdLineP s) where
    
    177
    +    CmdLineP k >>= f = CmdLineP (k >>= \x -> case f x of CmdLineP g -> g)
    
    178
    +    return = pure
    
    179
    +
    
    180
    +instance Applicative (CmdLineP s) where
    
    181
    +    pure x = CmdLineP (pure x)
    
    182
    +    (<*>) = ap
    
    183
    +
    
    184
    +getCmdLineState :: CmdLineP s s
    
    185
    +getCmdLineState = CmdLineP State.get
    
    186
    +
    
    187
    +putCmdLineState :: s -> CmdLineP s ()
    
    188
    +putCmdLineState x = CmdLineP (State.put x)
    
    189
    +
    
    190
    +runCmdLineP :: CmdLineP s a -> s -> (a, s)
    
    191
    +runCmdLineP (CmdLineP k) s0 = runIdentity $ State.runStateT k s0
    
    192
    +
    
    193
    +-- | Flag specifications as a trie map
    
    194
    +--
    
    195
    +-- No two flags with the same 'flagName' are allowed.
    
    196
    +--
    
    197
    +-- See Note [Optimising the processing of command-line arguments]
    
    198
    +-- in GHC.Driver.Session.
    
    199
    +newtype FlagSpecTrie s =
    
    200
    +    -- | The key of each 'Flag' @f@ is @flagName f@.
    
    201
    +    FlagSpecTrie { unFlagSpecTrie :: StringTrie.StringTrie (Flag (CmdLineP s)) }
    
    202
    +
    
    203
    +-- | Construct a 'FlagSpecTrie' from a list of flags.
    
    204
    +--
    
    205
    +-- Precondition: No two flags in the list have the same flag name.
    
    206
    +mkFlagSpecTrie :: [Flag (CmdLineP s)] -> FlagSpecTrie s
    
    207
    +mkFlagSpecTrie flags =
    
    208
    +  FlagSpecTrie $ StringTrie.fromList [ (flagName f, f) | !f <- flags ]
    
    209
    +{-# INLINE mkFlagSpecTrie #-}
    
    210
    +
    
    211
    +-- | Add flags to an existing 'FlagSpecTrie'.
    
    212
    +--
    
    213
    +-- Precondition: No two flags in the trie plus the list have the same flag name.
    
    214
    +extendFlagSpecTrie :: FlagSpecTrie s -> [Flag (CmdLineP s)] -> FlagSpecTrie s
    
    215
    +extendFlagSpecTrie (FlagSpecTrie t) flags =
    
    216
    +  FlagSpecTrie $ foldl' (\acc f -> StringTrie.insert (flagName f) f acc) t flags
    
    217
    +{-# INLINE extendFlagSpecTrie #-}
    
    218
    +
    
    219
    +processArgs :: forall s m. Monad m
    
    220
    +            => FlagSpecTrie s             -- ^ cmdline parser spec
    
    221
    +            -> [Located String]           -- ^ args
    
    222
    +            -> (FilePath -> EwM (StateT s m) [Located String]) -- ^ response file handler
    
    223
    +            -> StateT s m
    
    224
    +                 ( [Located String],  -- spare args
    
    176 225
                        [Err],  -- errors
    
    177 226
                        Warns ) -- warnings
    
    178 227
     processArgs spec args handleRespFile = do
    
    179 228
         (errs, warns, spare) <- runEwM action
    
    180 229
         return (spare, bagToList errs, warns)
    
    181 230
       where
    
    231
    +    getCmdLineP :: CmdLineP s a -> StateT s m a
    
    232
    +    getCmdLineP (CmdLineP k) = k
    
    233
    +
    
    182 234
         action = process args []
    
    183 235
     
    
    184
    -    -- process :: [Located String] -> [Located String] -> EwM m [Located String]
    
    236
    +    process :: [Located String] -> [Located String] -> EwM (StateT s m) [Located String]
    
    185 237
         process [] spare = return (reverse spare)
    
    186 238
     
    
    187 239
         process (L _ ('@' : resp_file) : args) spare = do
    
    ... ... @@ -189,7 +241,7 @@ processArgs spec args handleRespFile = do
    189 241
             process (resp_args ++ args) spare
    
    190 242
     
    
    191 243
         process (locArg@(L _ ('-' : arg)) : args) spare =
    
    192
    -        case findArg spec arg of
    
    244
    +        case findArg (unFlagSpecTrie spec) arg of
    
    193 245
                 Just (rest, opt_kind) ->
    
    194 246
                     case processOneArg opt_kind rest arg args of
    
    195 247
                         Left err ->
    
    ... ... @@ -198,7 +250,7 @@ processArgs spec args handleRespFile = do
    198 250
     
    
    199 251
                         Right (action,rest) ->
    
    200 252
                             let b = process rest spare
    
    201
    -                        in (setArg locArg $ action) >> b
    
    253
    +                        in setArg locArg (hoistEwM getCmdLineP action) >> b
    
    202 254
     
    
    203 255
                 Nothing -> process args (locArg : spare)
    
    204 256
     
    
    ... ... @@ -246,31 +298,38 @@ processOneArg opt_kind rest arg args
    246 298
             OptPrefix f       -> Right (f rest_no_eq, args)
    
    247 299
             AnySuffix f       -> Right (f dash_arg, args)
    
    248 300
     
    
    249
    -findArg :: [Flag m] -> String -> Maybe (String, OptKind m)
    
    250
    -findArg spec arg =
    
    251
    -    case sortBy (compare `on` (length . fst)) -- prefer longest matching flag
    
    252
    -           [ (removeSpaces rest, optKind)
    
    253
    -           | flag <- spec,
    
    254
    -             let optKind  = flagOptKind flag,
    
    255
    -             Just rest <- [stripPrefix (flagName flag) arg],
    
    256
    -             arg_ok optKind rest arg ]
    
    257
    -    of
    
    258
    -        []      -> Nothing
    
    259
    -        (one:_) -> Just one
    
    260
    -
    
    261
    -arg_ok :: OptKind t -> [Char] -> String -> Bool
    
    262
    -arg_ok (NoArg           _)  rest _   = null rest
    
    263
    -arg_ok (HasArg          _)  _    _   = True
    
    264
    -arg_ok (SepArg          _)  rest _   = null rest
    
    265
    -arg_ok (Prefix          _)  _    _   = True -- Missing argument checked for in processOneArg t
    
    266
    -                                            -- to improve error message (#12625)
    
    267
    -arg_ok (OptIntSuffix    _)  _    _   = True
    
    268
    -arg_ok (IntSuffix       _)  _    _   = True
    
    269
    -arg_ok (Word64Suffix    _)  _    _   = True
    
    270
    -arg_ok (FloatSuffix     _)  _    _   = True
    
    271
    -arg_ok (OptPrefix       _)  _    _   = True
    
    272
    -arg_ok (PassFlag        _)  rest _   = null rest
    
    273
    -arg_ok (AnySuffix       _)  _    _   = True
    
    301
    +-- | Find a flag in the trie whose name is a prefix of the given string.
    
    302
    +--
    
    303
    +-- The unmatched suffix of the string is returned along with the 'OptKind'.
    
    304
    +--
    
    305
    +-- A flag is only returned if the suffix is valid for the 'OptKind' of the flag.
    
    306
    +-- For example, a flag with 'NoArg' will only be returned if the suffix is
    
    307
    +-- empty. More concretely, @-Wallx@ will produce an invalid suffix @"x"@ since
    
    308
    +-- @-Wall@ has @OptKind@ 'NoArg'.
    
    309
    +--
    
    310
    +-- If more than one flag name is a prefix of the string, the longest match with
    
    311
    +-- a valid suffix is returned.
    
    312
    +findArg :: StringTrie.StringTrie (Flag m) -> String -> Maybe (String, OptKind m)
    
    313
    +findArg trie arg =
    
    314
    +    case filter (uncurry valid_flag_suffix) $ StringTrie.lookup arg trie of
    
    315
    +        []               -> Nothing
    
    316
    +        (flag, rest) : _ -> Just (removeSpaces rest, flagOptKind flag)
    
    317
    +  where
    
    318
    +    valid_flag_suffix :: Flag m -> String -> Bool
    
    319
    +    valid_flag_suffix flag rest =
    
    320
    +      case flagOptKind flag of
    
    321
    +        NoArg _        -> null rest
    
    322
    +        HasArg _       -> True
    
    323
    +        SepArg _       -> null rest
    
    324
    +        Prefix _       -> True -- Missing argument checked for in processOneArg t
    
    325
    +                               -- to improve error message (#12625)
    
    326
    +        OptIntSuffix _ -> True
    
    327
    +        IntSuffix _    -> True
    
    328
    +        Word64Suffix _ -> True
    
    329
    +        FloatSuffix _  -> True
    
    330
    +        OptPrefix _    -> True
    
    331
    +        PassFlag _     -> null rest
    
    332
    +        AnySuffix _    -> True
    
    274 333
     
    
    275 334
     -- | Parse an Int
    
    276 335
     --
    

  • compiler/GHC/Driver/Session.hs
    ... ... @@ -181,7 +181,8 @@ module GHC.Driver.Session (
    181 181
             -- ** Available DynFlags
    
    182 182
             allNonDeprecatedFlags,
    
    183 183
             flagsAll,
    
    184
    -        flagsDynamic,
    
    184
    +        flagsAllTrie,
    
    185
    +        flagsDynamicTrie,
    
    185 186
             flagsPackage,
    
    186 187
             flagsForCompletion,
    
    187 188
     
    
    ... ... @@ -294,8 +295,7 @@ import System.Semaphore ( getSemaphoreProtocolVersion, semaphoreVersion )
    294 295
     import Data.IORef
    
    295 296
     import Control.Arrow ((&&&))
    
    296 297
     import Control.Monad
    
    297
    -import Control.Monad.Trans.State as State
    
    298
    -import Data.Functor.Identity
    
    298
    +import Control.Monad.Trans.State (runStateT)
    
    299 299
     
    
    300 300
     import Data.Ord
    
    301 301
     import Data.Char
    
    ... ... @@ -391,6 +391,37 @@ import Language.Haskell.Syntax.Text
    391 391
     --
    
    392 392
     --  See #4437 and #8176.
    
    393 393
     
    
    394
    +-- Note [Optimising the processing of command-line arguments]
    
    395
    +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    396
    +-- For each command line argument we need to search all the possible flag names to
    
    397
    +-- find the correct command-line argument processor (of type `CmdLineP DynFlags`)
    
    398
    +-- to complete parsing of the argument and modify the `DynFlags`.
    
    399
    +--
    
    400
    +-- GHC has a LOT of flags, and occasionally, users give a LOT of command line
    
    401
    +-- arguments. So in some cases it pays to optimize the search for a command-line
    
    402
    +-- argument processor (#25763).
    
    403
    +--
    
    404
    +-- An additional complication is that flag specs need to be looked up with a
    
    405
    +-- string that may contain the flag key as a prefix.
    
    406
    +--
    
    407
    +-- In general, the `=` sign is optional, and anything after the name of the flag,
    
    408
    +-- if it doesn't start with `=`, is considered the argument. E.g.
    
    409
    +-- `-odirsome/path`, or `-pgmFprogram`, or `-package-dbDiff`, or `-optc-DFOO`,
    
    410
    +-- or `-Isome/path`, or `-fpluginLiquidHaskell`, or `-fplugin-optLiquidHaskell:--save`.
    
    411
    +--
    
    412
    +-- To achieve this:
    
    413
    +--
    
    414
    +-- * We build global lookup tables
    
    415
    +--     * GHC.Driver.Session.flagsAllTrie :: FlagSpecTrie DynFlags
    
    416
    +--     * GHC.Driver.Session.flagsDynamicTrie :: FlagSpecTrie DynFlags
    
    417
    +--     * GHC.Driver.Session.Mode.mode_flags_trie :: FlagSpecTrie ModeMS
    
    418
    +--
    
    419
    +--   These are implemented by a trie indexed by Strings, see GHC.Data.StringTrie.
    
    420
    +--
    
    421
    +-- * As we process each command line argument, one by one, we
    
    422
    +--   look up in the `FlagSpecTrie` to find which processor
    
    423
    +--   to use.  See `GHC.Driver.CmdLine.processArgs`.
    
    424
    +
    
    394 425
     -- -----------------------------------------------------------------------------
    
    395 426
     -- DynFlags
    
    396 427
     
    
    ... ... @@ -837,7 +868,7 @@ parseDynamicFlagsCmdLine :: MonadIO m => Logger -> DynFlags -> [Located String]
    837 868
                              -> m (DynFlags, [Located String], Messages DriverMessage)
    
    838 869
                                 -- ^ Updated 'DynFlags', left-over arguments, and
    
    839 870
                                 -- list of warnings.
    
    840
    -parseDynamicFlagsCmdLine = parseDynamicFlagsFull flagsAll True
    
    871
    +parseDynamicFlagsCmdLine = parseDynamicFlagsFull flagsAllTrie True
    
    841 872
     
    
    842 873
     
    
    843 874
     -- | Like 'parseDynamicFlagsCmdLine' but does not allow the package flags
    
    ... ... @@ -847,42 +878,19 @@ parseDynamicFilePragma :: MonadIO m => Logger -> DynFlags -> [Located String]
    847 878
                            -> m (DynFlags, [Located String], Messages DriverMessage)
    
    848 879
                               -- ^ Updated 'DynFlags', left-over arguments, and
    
    849 880
                               -- list of warnings.
    
    850
    -parseDynamicFilePragma = parseDynamicFlagsFull flagsDynamic False
    
    851
    -
    
    852
    -newtype CmdLineP s a = CmdLineP (forall m. (Monad m) => StateT s m a)
    
    853
    -  deriving (Functor)
    
    854
    -
    
    855
    -instance Monad (CmdLineP s) where
    
    856
    -    CmdLineP k >>= f = CmdLineP (k >>= \x -> case f x of CmdLineP g -> g)
    
    857
    -    return = pure
    
    858
    -
    
    859
    -instance Applicative (CmdLineP s) where
    
    860
    -    pure x = CmdLineP (pure x)
    
    861
    -    (<*>) = ap
    
    862
    -
    
    863
    -getCmdLineState :: CmdLineP s s
    
    864
    -getCmdLineState = CmdLineP State.get
    
    865
    -
    
    866
    -putCmdLineState :: s -> CmdLineP s ()
    
    867
    -putCmdLineState x = CmdLineP (State.put x)
    
    868
    -
    
    869
    -runCmdLineP :: CmdLineP s a -> s -> (a, s)
    
    870
    -runCmdLineP (CmdLineP k) s0 = runIdentity $ runStateT k s0
    
    881
    +parseDynamicFilePragma = parseDynamicFlagsFull flagsDynamicTrie False
    
    871 882
     
    
    872 883
     -- | A helper to parse a set of flags from a list of command-line arguments, handling
    
    873 884
     -- response files.
    
    874 885
     processCmdLineP
    
    875 886
         :: forall s m. MonadIO m
    
    876
    -    => [Flag (CmdLineP s)]  -- ^ valid flags to match against
    
    887
    +    => FlagSpecTrie s          -- ^ valid flags to match against
    
    877 888
         -> s                    -- ^ current state
    
    878 889
         -> [Located String]     -- ^ arguments to parse
    
    879 890
         -> m (([Located String], [Err], [Warn]), s)
    
    880 891
                                 -- ^ (leftovers, errors, warnings)
    
    881 892
     processCmdLineP activeFlags s0 args =
    
    882
    -    runStateT (processArgs (map (hoistFlag getCmdLineP) activeFlags) args parseResponseFile) s0
    
    883
    -  where
    
    884
    -    getCmdLineP :: CmdLineP s a -> StateT s m a
    
    885
    -    getCmdLineP (CmdLineP k) = k
    
    893
    +    runStateT (processArgs activeFlags args parseResponseFile) s0
    
    886 894
     
    
    887 895
     -- | Parses the dynamically set flags for GHC. This is the most general form of
    
    888 896
     -- the dynamic flag parser that the other methods simply wrap. It allows
    
    ... ... @@ -890,7 +898,7 @@ processCmdLineP activeFlags s0 args =
    890 898
     -- arguments from the command line or from a file pragma.
    
    891 899
     parseDynamicFlagsFull
    
    892 900
         :: forall m. MonadIO m
    
    893
    -    => [Flag (CmdLineP DynFlags)]    -- ^ valid flags to match against
    
    901
    +    => FlagSpecTrie DynFlags         -- ^ valid flags to match against
    
    894 902
         -> Bool                          -- ^ are the arguments from the command line?
    
    895 903
         -> Logger                        -- ^ logger
    
    896 904
         -> DynFlags                      -- ^ current dynamic flags
    
    ... ... @@ -1012,14 +1020,22 @@ allFlagsDeps keepDeprecated = [ '-':flagName flag
    1012 1020
     flagsAll :: [Flag (CmdLineP DynFlags)]
    
    1013 1021
     flagsAll = map snd flagsAllDeps
    
    1014 1022
     
    
    1023
    +-- | Same as 'flagsAll' but in trie form for fast lookup.
    
    1024
    +--
    
    1025
    +-- See Note [Optimising the processing of command-line arguments]
    
    1026
    +flagsAllTrie :: FlagSpecTrie DynFlags
    
    1027
    +flagsAllTrie = extendFlagSpecTrie flagsDynamicTrie $ map snd package_flags_deps
    
    1028
    +
    
    1015 1029
     -- All dynamic flags present in GHC with deprecation information.
    
    1016 1030
     flagsAllDeps :: [(Deprecation, Flag (CmdLineP DynFlags))]
    
    1017 1031
     flagsAllDeps =  package_flags_deps ++ dynamic_flags_deps
    
    1018 1032
     
    
    1019 1033
     
    
    1020 1034
     -- All dynamic flags, minus package flags, present in GHC.
    
    1021
    -flagsDynamic :: [Flag (CmdLineP DynFlags)]
    
    1022
    -flagsDynamic = map snd dynamic_flags_deps
    
    1035
    +--
    
    1036
    +-- See Note [Optimising the processing of command-line arguments].
    
    1037
    +flagsDynamicTrie :: FlagSpecTrie DynFlags
    
    1038
    +flagsDynamicTrie = mkFlagSpecTrie $ map snd dynamic_flags_deps
    
    1023 1039
     
    
    1024 1040
     -- ALl package flags present in GHC.
    
    1025 1041
     flagsPackage :: [Flag (CmdLineP DynFlags)]
    
    ... ... @@ -2013,8 +2029,7 @@ dynamic_flags_deps = [
    2013 2029
      ++
    
    2014 2030
     
    
    2015 2031
             ------ Warning flags -------------------------------------------------
    
    2016
    -  [ make_ord_flag defFlag "W"       (NoArg (setWarningGroup W_extra))
    
    2017
    -  , make_ord_flag defFlag "Werror"
    
    2032
    +  [ make_ord_flag defFlag "Werror"
    
    2018 2033
                    (NoArg (do { setGeneralFlag Opt_WarnIsError
    
    2019 2034
                               ; setFatalWarningGroup W_everything }))
    
    2020 2035
       , make_ord_flag defFlag "Wwarn"
    
    ... ... @@ -2040,7 +2055,7 @@ dynamic_flags_deps = [
    2040 2055
         , (NotDeprecated, customOrUnrecognisedWarning "Werror="    setCustomWErrorFlag)
    
    2041 2056
         , (NotDeprecated, customOrUnrecognisedWarning "Wwarn="     unSetCustomFatalWarningFlag)
    
    2042 2057
         , (NotDeprecated, customOrUnrecognisedWarning "Wno-error=" unSetCustomFatalWarningFlag)
    
    2043
    -    , (NotDeprecated, customOrUnrecognisedWarning "W"          setCustomWarningFlag)
    
    2058
    +    , flagW
    
    2044 2059
         , (Deprecated,    customOrUnrecognisedWarning "fwarn-"     setCustomWarningFlag)
    
    2045 2060
         , (Deprecated,    customOrUnrecognisedWarning "fno-warn-"  unSetCustomWarningFlag)
    
    2046 2061
         ]
    
    ... ... @@ -2076,19 +2091,35 @@ warningControls set unset set_werror unset_fatal xs =
    2076 2091
      ++ map (mkFlag turnOn  "fwarn-"     set   . hideFlag) xs
    
    2077 2092
      ++ map (mkFlag turnOff "fno-warn-"  unset . hideFlag) xs
    
    2078 2093
     
    
    2094
    +-- | The "W" flag is special in that it can mean enable extra warnings ('W_extra')
    
    2095
    +-- or enable a custom warning, if the suffix is non-empty.
    
    2096
    +flagW :: (Deprecation, Flag (CmdLineP DynFlags))
    
    2097
    +flagW =
    
    2098
    +  make_ord_flag defFlag "W" $ OptPrefix $ \suffix ->
    
    2099
    +    if null suffix then do
    
    2100
    +      -- Reject "-W=" instead of taking it to mean "-W".
    
    2101
    +      arg <- getArg
    
    2102
    +      if arg == "-W" then
    
    2103
    +        setWarningGroup W_extra
    
    2104
    +      else
    
    2105
    +        addErr ("missing argument for flag: " ++ arg)
    
    2106
    +    else
    
    2107
    +      customOrUnrecognisedWarningAction "W" setCustomWarningFlag suffix
    
    2108
    +
    
    2079 2109
     -- | This is where we handle unrecognised warning flags. If the flag is valid as
    
    2080 2110
     -- an extended warning category, we call the supplied action. Otherwise, issue a
    
    2081 2111
     -- warning if -Wunrecognised-warning-flags is set. See #11429 for context.
    
    2082 2112
     -- See Note [Warning categories] in GHC.Unit.Module.Warnings.
    
    2083 2113
     customOrUnrecognisedWarning :: String -> (WarningCategory -> DynP ()) -> Flag (CmdLineP DynFlags)
    
    2084
    -customOrUnrecognisedWarning prefix custom = defHiddenFlag prefix (Prefix action)
    
    2085
    -  where
    
    2086
    -    action :: String -> DynP ()
    
    2087
    -    action flag
    
    2114
    +customOrUnrecognisedWarning prefix custom =
    
    2115
    +  defHiddenFlag prefix $ Prefix $ customOrUnrecognisedWarningAction prefix custom
    
    2116
    +
    
    2117
    +customOrUnrecognisedWarningAction :: String -> (WarningCategory -> DynP ()) -> String -> DynP ()
    
    2118
    +customOrUnrecognisedWarningAction prefix custom flag
    
    2088 2119
           | validWarningCategory cat = custom cat
    
    2089 2120
           | otherwise = unrecognised flag
    
    2090
    -      where
    
    2091
    -        cat = mkWarningCategory (packHText flag)
    
    2121
    +  where
    
    2122
    +    cat = mkWarningCategory (packHText flag)
    
    2092 2123
     
    
    2093 2124
         unrecognised flag = do
    
    2094 2125
           -- #23402 and #12056
    

  • compiler/ghc.cabal.in
    ... ... @@ -474,6 +474,7 @@ Library
    474 474
             GHC.Data.Stream
    
    475 475
             GHC.Data.Strict
    
    476 476
             GHC.Data.StringBuffer
    
    477
    +        GHC.Data.StringTrie
    
    477 478
             GHC.Data.TrieMap
    
    478 479
             GHC.Data.Unboxed
    
    479 480
             GHC.Data.UnionFind
    

  • ghc/GHC/Driver/Session/Mode.hs
    ... ... @@ -180,7 +180,7 @@ parseModeFlags :: [Located String]
    180 180
                           [Warn])
    
    181 181
     parseModeFlags args = do
    
    182 182
       ((leftover, errs1, warns), (mModeFlag, units, errs2, flags')) <-
    
    183
    -        processCmdLineP mode_flags (Nothing, [], [], []) args
    
    183
    +        processCmdLineP mode_flags_trie (Nothing, [], [], []) args
    
    184 184
       let mode = case mModeFlag of
    
    185 185
                  Nothing     -> doMakeMode
    
    186 186
                  Just (m, _) -> m
    
    ... ... @@ -191,10 +191,14 @@ parseModeFlags args = do
    191 191
     
    
    192 192
       return (mode, units, flags' ++ leftover, warns)
    
    193 193
     
    
    194
    -type ModeM = CmdLineP (Maybe (Mode, String), [String], [String], [Located String])
    
    194
    +type ModeMS = (Maybe (Mode, String), [String], [String], [Located String])
    
    195
    +type ModeM = CmdLineP ModeMS
    
    195 196
       -- mode flags sometimes give rise to new DynFlags (eg. -C, see below)
    
    196 197
       -- so we collect the new ones and return them.
    
    197 198
     
    
    199
    +mode_flags_trie :: FlagSpecTrie ModeMS
    
    200
    +mode_flags_trie = mkFlagSpecTrie mode_flags
    
    201
    +
    
    198 202
     mode_flags :: [Flag ModeM]
    
    199 203
     mode_flags =
    
    200 204
       [  ------- help / version ----------------------------------------------
    

  • testsuite/tests/MiniQuickCheck.hs
    ... ... @@ -28,6 +28,7 @@ module MiniQuickCheck
    28 28
       , propertyCompare
    
    29 29
       , propertyAnd
    
    30 30
       , propertyTrue
    
    31
    +  , propertyEqWithContext
    
    31 32
       , getCheck
    
    32 33
     
    
    33 34
         -- * QuickCheck test tree
    
    ... ... @@ -51,11 +52,15 @@ module MiniQuickCheck
    51 52
       , nonZero
    
    52 53
       , BoundedShiftAmount(..)
    
    53 54
       , BoundedBy(..)
    
    55
    +  , BoundedList(..)
    
    56
    +  , BoundedChar(..)
    
    54 57
       ) where
    
    55 58
     
    
    56 59
     -- base
    
    57 60
     import Control.Exception
    
    58 61
       ( SomeException, displayException, evaluate, try )
    
    62
    +import Control.Monad
    
    63
    +  ( replicateM )
    
    59 64
     import Control.Monad.IO.Class
    
    60 65
       ( liftIO )
    
    61 66
     import Data.Bits
    
    ... ... @@ -64,8 +69,6 @@ import Data.Bits
    64 69
       )
    
    65 70
     import Data.Int
    
    66 71
       ( Int8, Int16, Int32, Int64 )
    
    67
    -import Data.IORef
    
    68
    -  ( newIORef, atomicModifyIORef' )
    
    69 72
     import Data.Kind
    
    70 73
       ( Type )
    
    71 74
     import Data.List
    
    ... ... @@ -84,6 +87,8 @@ import System.Exit
    84 87
       ( die, exitFailure )
    
    85 88
     import Text.Read
    
    86 89
       ( readMaybe )
    
    90
    +import System.IO.Unsafe
    
    91
    +  ( unsafePerformIO )
    
    87 92
     
    
    88 93
     -- transformers
    
    89 94
     import Control.Monad.Trans.Reader
    
    ... ... @@ -141,6 +146,15 @@ propertyCompare s f a b = PropertyBinaryOp (f a b) s (show a) (show b)
    141 146
     (===) = propertyCompare "==" (==)
    
    142 147
     infix 4 ===
    
    143 148
     
    
    149
    +-- | Prints the given message when the two values are not equal.
    
    150
    +propertyEqWithContext :: (Eq a, Show a) => String -> a -> a -> PropertyCheck
    
    151
    +propertyEqWithContext msg =
    
    152
    +  propertyCompare
    
    153
    +    (intercalate "\n" $
    
    154
    +      "is not the expected" : map ("    " ++) (lines msg ++ ["expected:"])
    
    155
    +    )
    
    156
    +    (==)
    
    157
    +
    
    144 158
     -- | Conjunction of two property checks.
    
    145 159
     propertyAnd :: PropertyCheck -> PropertyCheck -> PropertyCheck
    
    146 160
     propertyAnd = PropertyAnd
    
    ... ... @@ -424,3 +438,28 @@ instance
    424 438
         where
    
    425 439
           n :: a
    
    426 440
           n = fromIntegral $ natVal @n Proxy
    
    441
    +
    
    442
    +type BoundedList :: Nat -> Type -> Type
    
    443
    +newtype BoundedList n a = BoundedList { getBoundedList :: [a] }
    
    444
    +  deriving (Eq, Ord, Show)
    
    445
    +
    
    446
    +instance
    
    447
    +  forall n a
    
    448
    +  . ( KnownNat n, Arbitrary a )
    
    449
    +  => Arbitrary ( BoundedList n a ) where
    
    450
    +  arbitrary = do
    
    451
    +    x <- arbitrary
    
    452
    +    let n = (x :: Word64) `mod` fromIntegral (natVal @n Proxy + 1)
    
    453
    +    BoundedList <$> replicateM (fromIntegral n) arbitrary
    
    454
    +
    
    455
    +instance (Arbitrary a, Arbitrary b) => Arbitrary (a, b) where
    
    456
    +  arbitrary = (,) <$> arbitrary <*> arbitrary
    
    457
    +
    
    458
    +type BoundedChar :: Nat -> Type
    
    459
    +newtype BoundedChar n = BoundedChar { getBoundedChar :: Char }
    
    460
    +  deriving (Eq, Ord, Show)
    
    461
    +
    
    462
    +instance KnownNat n => Arbitrary (BoundedChar n)  where
    
    463
    +  arbitrary = do
    
    464
    +    x <- getBoundedBy @_ @n <$> arbitrary
    
    465
    +    pure $ BoundedChar (['a'..] !! abs x)

  • testsuite/tests/driver/all.T
    ... ... @@ -99,6 +99,8 @@ test('driver200', [extra_files(['A200.hs', 'B200/', 'D200.hs'])], makefile_test,
    99 99
     
    
    100 100
     test('T2566', normalise_fun(normalise_errmsg), makefile_test, [])
    
    101 101
     
    
    102
    +test('stringtrie', [mini_quickcheck], compile_and_run, ['-package ghc'])
    
    103
    +
    
    102 104
     test('pragma001', normal, compile, [''])
    
    103 105
     test('pragma002', normal, compile, [''])
    
    104 106
     
    

  • testsuite/tests/driver/stringtrie.hs
    1
    +{-# LANGUAGE DataKinds #-}
    
    2
    +{-# LANGUAGE TypeApplications #-}
    
    3
    +{-# LANGUAGE TypeFamilies #-}
    
    4
    +
    
    5
    +import Data.Bifunctor (first)
    
    6
    +import Data.Coerce
    
    7
    +import Data.Function (on)
    
    8
    +import Data.List (nubBy, sortOn, stripPrefix)
    
    9
    +import GHC.Data.StringTrie as StringTrie
    
    10
    +import MiniQuickCheck
    
    11
    +
    
    12
    +main :: IO ()
    
    13
    +main = do
    
    14
    +  runTestsMain (Iterations 1000) tests
    
    15
    +  -- runTests (Iterations 100) 9856592474076851907 tests
    
    16
    +
    
    17
    +tests :: Test
    
    18
    +tests = Group "StringTrie"
    
    19
    +    [ Property "lookup_after_insert" prop_lookup_after_insert
    
    20
    +    , Property "fromList == foldl (flip $ uncurry insert) empty" prop_fromList_foldl
    
    21
    +    ]
    
    22
    +
    
    23
    +type BString4 = BoundedList 4 (BoundedChar 4)
    
    24
    +
    
    25
    +-- | @lookup qs (insert k v t)@ returns the values of matching keys ordered by
    
    26
    +-- suffix length.
    
    27
    +prop_lookup_after_insert
    
    28
    +     -- | query string as a bounded list
    
    29
    +  :: BoundedList 6 (BoundedChar 3)
    
    30
    +     -- | length of the key to insert in @t@
    
    31
    +  -> BoundedBy Int 3
    
    32
    +     -- | value to insert in @t@
    
    33
    +  -> BoundedBy Int 9
    
    34
    +     -- | list of key-value pairs to build the trie @t@
    
    35
    +  -> BoundedList 6 (BString4, Int `BoundedBy` 9)
    
    36
    +  -> PropertyCheck
    
    37
    +prop_lookup_after_insert bqs i0 v0 bkvs =
    
    38
    +  let kvs = coerce bkvs :: [(String, Int)]
    
    39
    +      t = fromList kvs
    
    40
    +      qs = coerce @_ @String bqs
    
    41
    +      i = min (abs (getBoundedBy i0)) (length qs)
    
    42
    +      k = take i qs
    
    43
    +      v = getBoundedBy v0
    
    44
    +      -- (v1, sfx1) pairs where (k1, v1) is in t and k1 is a prefix of qs
    
    45
    +      expected =
    
    46
    +        [ (v1, sfx1)
    
    47
    +        | kv@(k1, v1) <- nubBy ((==) `on` fst) $ (k, v) : reverse kvs
    
    48
    +        , Just sfx1 <- [stripPrefix k1 qs]
    
    49
    +        ]
    
    50
    +   in
    
    51
    +      propertyEqWithContext
    
    52
    +        (unlines
    
    53
    +            [ "k = " ++ show k
    
    54
    +            , "v = " ++ show v
    
    55
    +            , "qs = " ++ show qs
    
    56
    +            , "kvs = " ++ show kvs
    
    57
    +            ]
    
    58
    +        )
    
    59
    +        (StringTrie.lookup qs (insert k v t))
    
    60
    +        (sortOn (length . snd) expected)
    
    61
    +
    
    62
    +-- | @fromList xs == foldl (flip $ uncurry insert) empty xs@
    
    63
    +prop_fromList_foldl
    
    64
    +     -- | list of key-value pairs to build the trie @t@
    
    65
    +  :: Int `BoundedBy` 7
    
    66
    +  -> BoundedList 6 (BString4, Int `BoundedBy` 9)
    
    67
    +  -> PropertyCheck
    
    68
    +prop_fromList_foldl (BoundedBy sparseness) bkvs =
    
    69
    +  let kvs = map (first shiftString) $ coerce bkvs :: [(String, Int)]
    
    70
    +   in propertyEqWithContext
    
    71
    +        (unlines ["kvs = " ++ show kvs])
    
    72
    +        (fromListWithSparseness (sparseness - 1) kvs)
    
    73
    +        (foldl (flip $ uncurry insert) empty kvs)
    
    74
    +  where
    
    75
    +    -- Bring characters next to the end of the ASCII range to test the
    
    76
    +    -- array-based implementation of fromList.
    
    77
    +    shiftString = map shiftChar
    
    78
    +    shiftChar = charPlus (126 - fromEnum 'a')
    
    79
    +    charPlus x c = toEnum (x + fromEnum c)

  • testsuite/tests/driver/stringtrie.stdout
    1
    +Group StringTrie
    
    2
    +  Running lookup_after_insert
    
    3
    +    Passed 1000 iterations
    
    4
    +  Running fromList == foldl (flip $ uncurry insert) empty
    
    5
    +    Passed 1000 iterations

  • testsuite/tests/perf/compiler/T25763.stderr
    1
    +[1 of 1] Compiling Main             ( t25763.hs, nothing )

  • testsuite/tests/perf/compiler/all.T
    ... ... @@ -874,6 +874,16 @@ test ('T24984',
    874 874
           compile,
    
    875 875
           ['-O'])
    
    876 876
     
    
    877
    +# Performance regression test for the parser of command line arguments
    
    878
    +test(
    
    879
    +    'T25763',
    
    880
    +    [
    
    881
    +        collect_compiler_runtime(5),
    
    882
    +        pre_cmd(f'seq 400000 | sed "s/.*/-XCPP/" > t25763.txt && echo "main = pure ()" > t25763.hs'),
    
    883
    +    ],
    
    884
    +    multimod_compile,
    
    885
    +    ['t25763', '@t25763.txt -c -fno-code'])
    
    886
    +
    
    877 887
     test('interpreter_steplocal',
    
    878 888
          [ collect_compiler_stats('bytes allocated', 5)
    
    879 889
          ],
    

  • testsuite/tests/warnings/should_fail/WExpectedNoArgError.stderr
    1
    +ghc: on the commandline: missing argument for flag: -W=
    
    2
    +Usage: For basic information, try the `--help' option.

  • testsuite/tests/warnings/should_fail/all.T
    ... ... @@ -15,6 +15,7 @@ test('CaretDiagnostics1',
    15 15
          [normalise_whitespace_fun(normalise_whitespace_carefully)],
    
    16 16
          compile_fail,
    
    17 17
          ['-fdiagnostics-show-caret -ferror-spans'])
    
    18
    +test('WExpectedNoArgError', normal, compile_fail, ['-W='])
    
    18 19
     test('CaretDiagnostics2', normal, compile_fail, ['-fdiagnostics-show-caret'])
    
    19 20
     test('Colour', normal, compile_fail, ['-fdiagnostics-color=always'])
    
    20 21
     test('T20263', normal, compile_fail, ['-Wunicode-bidirectional-format-characters -Werror'])