Simon Jakobi pushed to branch wip/sjakobi/T27602 at Glasgow Haskell Compiler / GHC
Commits:
-
db959f83
by Simon Jakobi at 2026-08-14T15:16:44-04:00
-
4f2b7d90
by Simon Jakobi at 2026-08-14T15:16:44-04:00
-
c4abddcb
by Simon Jakobi at 2026-08-14T15:16:44-04:00
-
4a7defa1
by Simon Jakobi at 2026-08-14T15:16:44-04:00
-
b757727a
by Vladislav Zavialov at 2026-08-14T15:17:27-04:00
-
30e97b01
by Simon Jakobi at 2026-08-15T17:08:49+02:00
-
dab95c58
by Simon Jakobi at 2026-08-15T17:08:49+02:00
-
748b9ae2
by Simon Jakobi at 2026-08-15T17:09:27+02:00
-
2965e0a2
by Simon Jakobi at 2026-08-15T17:09:48+02:00
20 changed files:
- .gitlab/ci.sh
- + changelog.d/T27586
- compiler/GHC/Tc/TyCl/PatSyn.hs
- libraries/base/tests/all.T
- libraries/base/tests/listThreads1.hs
- libraries/base/tests/listThreads1.stdout
- rts/js/thread.js
- testsuite/driver/perf_notes.py
- + testsuite/tests/concurrent/should_run/T16761.hs
- + testsuite/tests/concurrent/should_run/T16761.stdout
- testsuite/tests/concurrent/should_run/all.T
- testsuite/tests/perf/compiler/all.T
- testsuite/tests/rts/all.T
- + testsuite/tests/vdq-rta/should_fail/T27586a.hs
- + testsuite/tests/vdq-rta/should_fail/T27586a.stderr
- + testsuite/tests/vdq-rta/should_fail/T27586b.hs
- + testsuite/tests/vdq-rta/should_fail/T27586b.stderr
- + testsuite/tests/vdq-rta/should_fail/T27586c.hs
- + testsuite/tests/vdq-rta/should_fail/T27586c.stderr
- testsuite/tests/vdq-rta/should_fail/all.T
Changes:
| ... | ... | @@ -1120,9 +1120,10 @@ case ${1:-help} in |
| 1120 | 1120 | setup) setup && cleanup_submodules ;;
|
| 1121 | 1121 | configure) time_it "configure" configure ;;
|
| 1122 | 1122 | build_hadrian) time_it "build" build_hadrian ;;
|
| 1123 | - # N.B. Always push notes, even if the build fails. This is okay to do as the
|
|
| 1124 | - # testsuite driver doesn't record notes for tests that fail due to
|
|
| 1125 | - # correctness.
|
|
| 1123 | + # N.B. Always push notes, even if the build fails. Metrics from runs failing
|
|
| 1124 | + # a perf stat check are deliberately recorded too — discarding them would
|
|
| 1125 | + # bias the baseline towards whichever sample came first. Only correctness
|
|
| 1126 | + # failures record nothing.
|
|
| 1126 | 1127 | test_hadrian)
|
| 1127 | 1128 | fetch_perf_notes
|
| 1128 | 1129 | res=0
|
| 1 | +section: compiler
|
|
| 2 | +issues: #27586
|
|
| 3 | +mrs: !16440
|
|
| 4 | +synopsis:
|
|
| 5 | + Fix a panic on a required type argument in a pattern synonym RHS
|
|
| 6 | +description:
|
|
| 7 | + An argument of a pattern synonym that is matched against a required type
|
|
| 8 | + argument in the right-hand side no longer causes a panic; it is reported as
|
|
| 9 | + an illegal term-level use of a type variable. |
| ... | ... | @@ -137,7 +137,7 @@ tcInferPatSynDecl (PSB { psb_id = lname@(L _ name), psb_args = details |
| 137 | 137 | ; (tclvl, wanted, ((lpat', args), pat_ty))
|
| 138 | 138 | <- pushLevelAndCaptureConstraints $
|
| 139 | 139 | tcInferPat FRRPatSynArg PatSynCtx lpat $
|
| 140 | - mapM tcLookupId arg_names
|
|
| 140 | + mapM tcLookupPatSynArg arg_names
|
|
| 141 | 141 | |
| 142 | 142 | ; let (ex_tvs, prov_dicts) = tcCollectEx lpat'
|
| 143 | 143 | |
| ... | ... | @@ -472,7 +472,7 @@ tcCheckPatSynDecl psb@PSB{ psb_id = lname@(L _ name), psb_args = details |
| 472 | 472 | -- location to x's binding site in lpat, namely the 'x' in Just (x,True).
|
| 473 | 473 | -- Else the error message location is wherever tcCheckPat finished,
|
| 474 | 474 | -- namely the right-hand corner of the pattern
|
| 475 | - do { arg_id <- tcLookupId arg_name
|
|
| 475 | + do { arg_id <- tcLookupPatSynArg arg_name
|
|
| 476 | 476 | ; wrap <- tcSubTypeSigma (OccurrenceOf (idName arg_id))
|
| 477 | 477 | GenSigCtxt
|
| 478 | 478 | (idType arg_id)
|
| ... | ... | @@ -645,6 +645,19 @@ collectPatSynArgInfo details = |
| 645 | 645 | InfixCon _ name1 name2 -> (map unLoc [name1, name2], True)
|
| 646 | 646 | RecCon _ names -> (map (unLoc . recordPatSynPatVar) names, False)
|
| 647 | 647 | |
| 648 | +-- | Look up the 'Id' bound by the pattern for a declared argument of a pattern
|
|
| 649 | +-- synonym. With @RequiredTypeArguments@ the argument may turn out to be a type
|
|
| 650 | +-- variable, e.g. @pattern P x = MkT x@ where the argument of @MkT@ is a required
|
|
| 651 | +-- type argument; then we report an illegal term-level use of @x@ (#27586).
|
|
| 652 | +tcLookupPatSynArg :: Name -> TcM Id
|
|
| 653 | +tcLookupPatSynArg arg_name
|
|
| 654 | + = do { thing <- tcLookup arg_name
|
|
| 655 | + ; case thing of
|
|
| 656 | + ATcId { tct_id = id } -> return id
|
|
| 657 | + AGlobal (AnId id) -> return id
|
|
| 658 | + ATyVar {} -> failIllegalTyVar (noUserRdr arg_name)
|
|
| 659 | + _ -> pprPanic "tcLookupPatSynArg" (ppr arg_name) }
|
|
| 660 | + |
|
| 648 | 661 | wrongNumberOfParmsErr :: Name -> Arity -> Arity -> TcM a
|
| 649 | 662 | wrongNumberOfParmsErr name decl_arity missing
|
| 650 | 663 | = failWithTc $ TcRnPatSynArityMismatch name decl_arity missing
|
| ... | ... | @@ -80,7 +80,7 @@ test('length001', |
| 80 | 80 | # excessive amounts of stack space. So we specifically set a low
|
| 81 | 81 | # stack limit and mark it as failing under a few conditions.
|
| 82 | 82 | [extra_run_opts('+RTS -K8m -RTS'),
|
| 83 | - expect_fail_for(['normal', 'threaded1', 'llvm', 'nonmoving', 'nonmoving_thr', 'nonmoving_thr_ghc', 'ext-interp']),
|
|
| 83 | + expect_fail_for(['normal', 'threaded1', 'llvm', 'nonmoving', 'nonmoving_thr', 'nonmoving_thr_sanity', 'nonmoving_thr_ghc', 'ext-interp']),
|
|
| 84 | 84 | # JS doesn't support stack limit so the test sometimes passes just fine. Therefore the test is
|
| 85 | 85 | # marked as fragile.
|
| 86 | 86 | when(js_arch(), fragile(22921))],
|
| ... | ... | @@ -2,5 +2,10 @@ module Main where |
| 2 | 2 | |
| 3 | 3 | import GHC.Conc.Sync
|
| 4 | 4 | |
| 5 | +-- Regression test for the JS backend's ListThreadsOp, which used to omit the
|
|
| 6 | +-- running thread. Whatever other threads the RTS has is irrelevant here.
|
|
| 5 | 7 | main :: IO ()
|
| 6 | -main = listThreads >>= print |
|
| 8 | +main = do
|
|
| 9 | + tid <- myThreadId
|
|
| 10 | + ts <- listThreads
|
|
| 11 | + print (tid `elem` ts) |
| 1 | -[ThreadId 1] |
|
| 1 | +True |
| ... | ... | @@ -110,6 +110,10 @@ function h$rts_getThreadId(t) { // returns a CULLong |
| 110 | 110 | RETURN_UBX_TUP2((t.tid / Math.pow(2,32))>>>0, (t.tid & 0xFFFFFFFF)>>>0);
|
| 111 | 111 | }
|
| 112 | 112 | |
| 113 | +function h$eq_thread(t1,t2) {
|
|
| 114 | + return t1 === t2 ? 1 : 0;
|
|
| 115 | +}
|
|
| 116 | + |
|
| 113 | 117 | function h$cmp_thread(t1,t2) {
|
| 114 | 118 | if(t1.tid < t2.tid) return -1;
|
| 115 | 119 | if(t1.tid > t2.tid) return 1;
|
| ... | ... | @@ -84,8 +84,11 @@ PerfStat = NamedTuple('PerfStat', [('test_env', TestEnv), |
| 84 | 84 | ('value', float)])
|
| 85 | 85 | |
| 86 | 86 | # A baseline recovered form stored metrics.
|
| 87 | -Baseline = NamedTuple('Baseline', [('perfStat', PerfStat),
|
|
| 88 | - ('commit', GitHash)])
|
|
| 87 | +class Baseline(NamedTuple):
|
|
| 88 | + perfStat: PerfStat
|
|
| 89 | + commit: GitHash
|
|
| 90 | + # The raw samples the baseline value was averaged over.
|
|
| 91 | + samples: List[float] = []
|
|
| 89 | 92 | |
| 90 | 93 | # The type of exceptions which are thrown when computing the current stat value
|
| 91 | 94 | # fails.
|
| ... | ... | @@ -465,6 +468,10 @@ def get_allowed_changes(baseline_ref: Optional[GitRef]) -> Dict[TestName, List[A |
| 465 | 468 | # (bool , str ) -> (str , str , str , str) -> float
|
| 466 | 469 | _commit_metric_cache = {} # type: ignore
|
| 467 | 470 | |
| 471 | +# Like _commit_metric_cache, but mapping to the list of raw sample values the
|
|
| 472 | +# baseline was averaged over. Filled by get_commit_metric.
|
|
| 473 | +_commit_samples_cache = {} # type: ignore
|
|
| 474 | + |
|
| 468 | 475 | # Get the baseline of a test at a given commit. This is the expected value
|
| 469 | 476 | # *before* the commit is applied (i.e. on the parent commit).
|
| 470 | 477 | # This searches git notes from older commits for recorded metrics (locally and
|
| ... | ... | @@ -506,7 +513,8 @@ def baseline_metric(commit: GitHash, |
| 506 | 513 | if baseline_commit is not None:
|
| 507 | 514 | current_metric = get_commit_metric(namespace, baseline_commit, test_env, name, metric, way)
|
| 508 | 515 | if current_metric is not None:
|
| 509 | - return Baseline(current_metric, baseline_commit)
|
|
| 516 | + return Baseline(current_metric, baseline_commit,
|
|
| 517 | + get_commit_samples(namespace, baseline_commit, test_env, name, metric, way))
|
|
| 510 | 518 | else:
|
| 511 | 519 | return None
|
| 512 | 520 | |
| ... | ... | @@ -515,7 +523,8 @@ def baseline_metric(commit: GitHash, |
| 515 | 523 | # Check for a metric on this commit.
|
| 516 | 524 | current_metric = get_commit_metric(namespace, current_commit, test_env, name, metric, way)
|
| 517 | 525 | if current_metric is not None:
|
| 518 | - return Baseline(current_metric, current_commit)
|
|
| 526 | + return Baseline(current_metric, current_commit,
|
|
| 527 | + get_commit_samples(namespace, current_commit, test_env, name, metric, way))
|
|
| 519 | 528 | |
| 520 | 529 | # Stop if there is an expected change at this commit. In that case
|
| 521 | 530 | # metrics on ancestor commits will not be a valid baseline.
|
| ... | ... | @@ -598,8 +607,28 @@ def get_commit_metric(gitNoteRef, |
| 598 | 607 | |
| 599 | 608 | # Save baselines to the cache.
|
| 600 | 609 | _commit_metric_cache[cacheKeyA] = baseline_by_cache_key_b
|
| 610 | + _commit_samples_cache[cacheKeyA] = values_by_cache_key_b
|
|
| 601 | 611 | return baseline_by_cache_key_b.get(cacheKeyB)
|
| 602 | 612 | |
| 613 | +# Get the raw sample values that get_commit_metric averages over. Uses the
|
|
| 614 | +# cache filled by get_commit_metric, so no extra git calls after it has run.
|
|
| 615 | +def get_commit_samples(gitNoteRef,
|
|
| 616 | + ref: Union[GitRef, GitHash],
|
|
| 617 | + test_env: TestEnv,
|
|
| 618 | + name: TestName,
|
|
| 619 | + metric: MetricName,
|
|
| 620 | + way: WayName
|
|
| 621 | + ) -> List[float]:
|
|
| 622 | + get_commit_metric(gitNoteRef, ref, test_env, name, metric, way)
|
|
| 623 | + cacheKeyA = (gitNoteRef, commit_hash(ref))
|
|
| 624 | + cacheKeyB = (test_env, name, metric, way)
|
|
| 625 | + return _commit_samples_cache.get(cacheKeyA, {}).get(cacheKeyB, [])
|
|
| 626 | + |
|
| 627 | +# Metric samples are integral in practice; '%g' would render large byte
|
|
| 628 | +# counts in truncated scientific notation.
|
|
| 629 | +def format_samples(samples: List[float]) -> str:
|
|
| 630 | + return ', '.join(str(int(s)) if s == int(s) else str(s) for s in samples)
|
|
| 631 | + |
|
| 603 | 632 | def check_stats_change(actual: PerfStat,
|
| 604 | 633 | baseline: Baseline,
|
| 605 | 634 | acceptance_window: MetricAcceptanceWindow,
|
| ... | ... | @@ -654,9 +683,17 @@ def check_stats_change(actual: PerfStat, |
| 654 | 683 | ' baseline @ %s' % baseline.commit
|
| 655 | 684 | print(actual.metric, error + ':')
|
| 656 | 685 | dev = 100.0 if expected_val == 0 else round(((float(actual.value) * 100) / int(expected_val)) - 100, 1)
|
| 686 | + # A multi-sample baseline is a mean; show the samples so outliers
|
|
| 687 | + # corrupting the baseline are visible (#27602).
|
|
| 688 | + if len(baseline.samples) > 1:
|
|
| 689 | + samples_note = ('; baseline is mean of %d samples: %s'
|
|
| 690 | + % (len(baseline.samples),
|
|
| 691 | + format_samples(baseline.samples)))
|
|
| 692 | + else:
|
|
| 693 | + samples_note = ''
|
|
| 657 | 694 | change_line = (f'{actual.metric} {change.value} from {baseline.perfStat.test_env} '
|
| 658 | 695 | f'baseline @ {baseline.commit[:7]}: {expected_val} -> {actual.value} '
|
| 659 | - f'({dev:+g}%, allowed {acceptance_window.describe()})')
|
|
| 696 | + f'({dev:+g}%, allowed {acceptance_window.describe()}{samples_note})')
|
|
| 660 | 697 | result = failBecause('stat ' + change_line, tag='stat')
|
| 661 | 698 | |
| 662 | 699 | if not change_allowed or force_print:
|
| ... | ... | @@ -666,6 +703,10 @@ def check_stats_change(actual: PerfStat, |
| 666 | 703 | print(descr, str(val).rjust(length), extra)
|
| 667 | 704 | |
| 668 | 705 | display(' Expected ' + full_name + ' ' + actual.metric + ':', expected_val, acceptance_window.describe())
|
| 706 | + if len(baseline.samples) > 1:
|
|
| 707 | + display(' Samples ' + full_name + ' ' + actual.metric + ':',
|
|
| 708 | + len(baseline.samples),
|
|
| 709 | + '(' + format_samples(baseline.samples) + ')')
|
|
| 669 | 710 | display(' Lower bound ' + full_name + ' ' + actual.metric + ':', lowerBound, '')
|
| 670 | 711 | display(' Upper bound ' + full_name + ' ' + actual.metric + ':', upperBound, '')
|
| 671 | 712 | display(' Actual ' + full_name + ' ' + actual.metric + ':', actual.value, '')
|
| 1 | +-- Test that Eq ThreadId is based on thread identity (eq_thread),
|
|
| 2 | +-- not on the numeric thread id, which may wrap around (#16761).
|
|
| 3 | +module Main (main) where
|
|
| 4 | + |
|
| 5 | +import Control.Concurrent
|
|
| 6 | +import System.Mem (performGC)
|
|
| 7 | + |
|
| 8 | +main :: IO ()
|
|
| 9 | +main = do
|
|
| 10 | + t0 <- myThreadId
|
|
| 11 | + print (t0 == t0)
|
|
| 12 | + |
|
| 13 | + mv <- newEmptyMVar
|
|
| 14 | + _ <- forkIO (myThreadId >>= putMVar mv)
|
|
| 15 | + tChild <- takeMVar mv
|
|
| 16 | + print (t0 == tChild)
|
|
| 17 | + print (tChild == tChild)
|
|
| 18 | + |
|
| 19 | + -- Equality must be stable even after the GC moves the TSOs.
|
|
| 20 | + performGC
|
|
| 21 | + print (t0 == t0)
|
|
| 22 | + |
|
| 23 | + -- Ord must agree with Eq.
|
|
| 24 | + print (compare t0 tChild /= EQ)
|
|
| 25 | + print (compare t0 t0 == EQ) |
| 1 | +True
|
|
| 2 | +False
|
|
| 3 | +True
|
|
| 4 | +True
|
|
| 5 | +True
|
|
| 6 | +True |
| ... | ... | @@ -310,6 +310,8 @@ test('hs_try_putmvar003', |
| 310 | 310 | # Check forkIO exception determinism under optimization
|
| 311 | 311 | test('T13330', normal, compile_and_run, ['-O'])
|
| 312 | 312 | |
| 313 | +test('T16761', normal, compile_and_run, [''])
|
|
| 314 | + |
|
| 313 | 315 | test('T26341', normal, compile_and_run, [''])
|
| 314 | 316 | |
| 315 | 317 | # Test EINTR for async I/O interrupted by an exception (#26341)
|
| ... | ... | @@ -48,7 +48,9 @@ test('T4801', |
| 48 | 48 | [''])
|
| 49 | 49 | |
| 50 | 50 | test('T3064',
|
| 51 | - [collect_compiler_residency(20),
|
|
| 51 | + [# Demo-only: tightened from 20 so the bimodal max_bytes_used
|
|
| 52 | + # baseline on aarch64-linux-deb13-validate fails.
|
|
| 53 | + collect_compiler_residency(1),
|
|
| 52 | 54 | collect_compiler_runtime(2),
|
| 53 | 55 | only_ways(['normal']),
|
| 54 | 56 | ],
|
| ... | ... | @@ -679,7 +679,7 @@ test('T22859', |
| 679 | 679 | [js_skip,
|
| 680 | 680 | # This test is vulnerable to changes in allocation behaviour, so we disable it in some ways
|
| 681 | 681 | when(arch('wasm32'), skip),
|
| 682 | - omit_ways(llvm_ways)],
|
|
| 682 | + omit_ways(llvm_ways + ['nonmoving_thr', 'nonmoving_thr_sanity'])],
|
|
| 683 | 683 | compile_and_run, ['-with-rtsopts -A8K'])
|
| 684 | 684 | |
| 685 | 685 | # These tests need access to the internal RTS headers.
|
| 1 | +{-# LANGUAGE GADTs, RequiredTypeArguments, PatternSynonyms #-}
|
|
| 2 | + |
|
| 3 | +module T27586a where
|
|
| 4 | + |
|
| 5 | +data T a where
|
|
| 6 | + MkT :: forall a -> T a
|
|
| 7 | + |
|
| 8 | +pattern P :: Int -> T Int
|
|
| 9 | +pattern P x = MkT x |
| 1 | +T27586a.hs:9:19: error: [GHC-01928]
|
|
| 2 | + • Illegal term-level use of the type variable ‘x’
|
|
| 3 | + • bound at T27586a.hs:9:19
|
|
| 4 | + • In the declaration for pattern synonym ‘P’
|
|
| 5 | + |
| 1 | +{-# LANGUAGE GADTs, RequiredTypeArguments, PatternSynonyms #-}
|
|
| 2 | + |
|
| 3 | +module T27586b where
|
|
| 4 | + |
|
| 5 | +data T a where
|
|
| 6 | + MkT :: forall a -> T a
|
|
| 7 | + |
|
| 8 | +pattern P x = MkT x |
| 1 | +T27586b.hs:8:15: error: [GHC-01928]
|
|
| 2 | + • Illegal term-level use of the type variable ‘x’
|
|
| 3 | + • bound at T27586b.hs:8:19
|
|
| 4 | + • In the declaration for pattern synonym ‘P’
|
|
| 5 | + |
| 1 | +{-# LANGUAGE GADTs, RequiredTypeArguments, PatternSynonyms #-}
|
|
| 2 | + |
|
| 3 | +module T27586c where
|
|
| 4 | + |
|
| 5 | +data T a where
|
|
| 6 | + MkT :: forall a -> T a
|
|
| 7 | + |
|
| 8 | +pattern P :: Int -> T Int
|
|
| 9 | +pattern P x <- MkT x |
| 1 | +T27586c.hs:9:20: error: [GHC-01928]
|
|
| 2 | + • Illegal term-level use of the type variable ‘x’
|
|
| 3 | + • bound at T27586c.hs:9:20
|
|
| 4 | + • In the declaration for pattern synonym ‘P’
|
|
| 5 | + |
| ... | ... | @@ -35,3 +35,6 @@ test('T25127_fail_arity', normal, compile_fail, ['']) |
| 35 | 35 | |
| 36 | 36 | test('T27440e', normal, compile_fail, [''])
|
| 37 | 37 | test('T27583f', normal, compile_fail, [''])
|
| 38 | +test('T27586a', normal, compile_fail, [''])
|
|
| 39 | +test('T27586b', normal, compile_fail, [''])
|
|
| 40 | +test('T27586c', normal, compile_fail, ['']) |