Magnus pushed to branch wip/mangoiv/27817 at Glasgow Haskell Compiler / GHC
Commits:
-
098bba70
by Facundo Domínguez at 2026-09-17T14:57:41-04:00
-
e01799c7
by Facundo Domínguez at 2026-09-17T14:57:42-04:00
-
3875b257
by Facundo Domínguez at 2026-09-17T14:57:42-04:00
-
5236634a
by Andreas Klebinger at 2026-09-17T14:58:24-04:00
-
04aecb97
by mangoiv at 2026-09-22T12:22:12+02:00
-
a14b3f58
by mangoiv at 2026-09-22T12:22:14+02:00
18 changed files:
- .gitlab-ci.yml
- + changelog.d/25763-cmdline-opt
- + compiler/GHC/Data/StringTrie.hs
- compiler/GHC/Data/Word64Map/Internal.hs
- compiler/GHC/Data/Word64Map/Strict/Internal.hs
- compiler/GHC/Data/Word64Set/Internal.hs
- compiler/GHC/Driver/CmdLine.hs
- compiler/GHC/Driver/Session.hs
- compiler/ghc.cabal.in
- ghc/GHC/Driver/Session/Mode.hs
- testsuite/tests/MiniQuickCheck.hs
- testsuite/tests/driver/all.T
- + testsuite/tests/driver/stringtrie.hs
- + testsuite/tests/driver/stringtrie.stdout
- + testsuite/tests/perf/compiler/T25763.stderr
- testsuite/tests/perf/compiler/all.T
- + testsuite/tests/warnings/should_fail/WExpectedNoArgError.stderr
- testsuite/tests/warnings/should_fail/all.T
Changes:
| ... | ... | @@ -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 | - |
|
| 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 |
| 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 |
| ... | ... | @@ -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 | -----------------------------------------------------------------------------
|
| 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 | -- |
|
| ... | ... | @@ -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 | -- |
|
| 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 | --
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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 ----------------------------------------------
|
| ... | ... | @@ -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) |
| ... | ... | @@ -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 |
| 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) |
| 1 | +Group StringTrie
|
|
| 2 | + Running lookup_after_insert
|
|
| 3 | + Passed 1000 iterations
|
|
| 4 | + Running fromList == foldl (flip $ uncurry insert) empty
|
|
| 5 | + Passed 1000 iterations |
| 1 | +[1 of 1] Compiling Main ( t25763.hs, nothing ) |
| ... | ... | @@ -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 | ],
|
| 1 | +ghc: on the commandline: missing argument for flag: -W=
|
|
| 2 | +Usage: For basic information, try the `--help' option. |
| ... | ... | @@ -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'])
|