Simon Jakobi pushed to branch wip/sjakobi/T27653 at Glasgow Haskell Compiler / GHC
Commits:
-
218d6445
by Simon Jakobi at 2026-08-13T11:52:41+02:00
-
2e568d97
by Simon Jakobi at 2026-08-13T12:05:11+02:00
-
8e164679
by Simon Jakobi at 2026-08-13T12:06:47+02:00
7 changed files:
- libraries/base/tests/all.T
- testsuite/driver/README.md
- testsuite/driver/testlib.py
- testsuite/tests/bytecode/TLinkable/all.T
- testsuite/tests/perf/compiler/all.T
- testsuite/tests/perf/compiler/large-project/all.T
- testsuite/tests/perf/space_leaks/all.T
Changes:
| ... | ... | @@ -243,7 +243,7 @@ test('T8684', expect_broken(8684), compile_and_run, ['']) |
| 243 | 243 | test('hWaitForInput-accurate-stdin', [js_broken(22349), expect_broken_for(16535, threaded_ways), req_process], compile_and_run, [''])
|
| 244 | 244 | test('T9826',normal, compile_and_run,[''])
|
| 245 | 245 | test('T9848',
|
| 246 | - [ collect_stats('bytes allocated')
|
|
| 246 | + [ collect_stats('bytes allocated', 2)
|
|
| 247 | 247 | , only_ways(['normal'])
|
| 248 | 248 | , js_broken(22261)
|
| 249 | 249 | ],
|
| ... | ... | @@ -35,11 +35,13 @@ differences, and things such as that are not necessary to be considered by the |
| 35 | 35 | test writer anymore. This is due to the fact that the test comparison relies
|
| 36 | 36 | entirely on locally collected metrics on the testing machine.
|
| 37 | 37 | |
| 38 | -As such, it is perfectly sufficient to write `collect_stats('all',20)` in the
|
|
| 39 | -".T" files to measure the 3 potential stats that can be collected for that test
|
|
| 40 | -and automatically test them for regressions, failing if there is more than a 20%
|
|
| 41 | -change in any direction. In fact, even that is not necessary as
|
|
| 42 | -`collect_stats()` defaults to 'all', and 20% deviation allowed.
|
|
| 38 | +A test states which metric to measure and how much deviation to allow, e.g.
|
|
| 39 | +`collect_stats('bytes allocated', 2)` in the ".T" files. Such a test fails if
|
|
| 40 | +the metric changes by more than 2% in either direction. To gate several metrics,
|
|
| 41 | +use one `collect_stats` call per metric, so that each gets a tolerance matched
|
|
| 42 | +to its noise profile: allocations are nearly deterministic and support tight
|
|
| 43 | +windows, while the residency metrics need considerably slacker ones (see
|
|
| 44 | +Note [Measuring residency] in testlib.py).
|
|
| 43 | 45 | |
| 44 | 46 | The function `collect_compiler_stats()` is completely equivalent in every way to
|
| 45 | 47 | `collect_stats` except that it measures the performance of the compiler itself
|
| ... | ... | @@ -24,7 +24,7 @@ import subprocess |
| 24 | 24 | from testglobals import config, ghc_env, default_testopts, brokens, t, \
|
| 25 | 25 | TestRun, TestResult, TestOptions, PerfMetric
|
| 26 | 26 | from testutil import strip_quotes, lndir, link_or_copy_file, passed, \
|
| 27 | - failBecause, testing_metrics, residency_testing_metrics, \
|
|
| 27 | + failBecause, residency_testing_metrics, \
|
|
| 28 | 28 | stable_perf_counters, \
|
| 29 | 29 | PassFail, badResult, str_warn, str_removeprefix
|
| 30 | 30 | from term_color import Color, colored
|
| ... | ... | @@ -816,28 +816,20 @@ def _collect_generic_stat(name : TestName, opts, metric_infos): |
| 816 | 816 | |
| 817 | 817 | # -----
|
| 818 | 818 | |
| 819 | -# Defaults to "test everything, and only break on extreme cases"
|
|
| 820 | -#
|
|
| 821 | -# The inputs to this function are slightly interesting:
|
|
| 822 | -# metric can be either:
|
|
| 823 | -# - 'all', in which case all 3 possible metrics are collected and compared.
|
|
| 824 | -# - The specific metric one wants to use in the test.
|
|
| 825 | -# - A set of the metrics one wants to use in the test.
|
|
| 826 | -#
|
|
| 827 | -# Deviation defaults to 20% because the goal is correctness over performance.
|
|
| 828 | -# The testsuite should avoid breaking when there is not an actual error.
|
|
| 829 | -# Instead, the testsuite should notify of regressions in a non-breaking manner.
|
|
| 819 | +# metric is either a single metric name or a set of metric names. Use one
|
|
| 820 | +# call per metric so each gets a tolerance matched to its noise profile.
|
|
| 821 | +# See Note [Measuring residency] for the residency metrics.
|
|
| 830 | 822 | #
|
| 831 | 823 | # collect_compiler_stats is used when the metrics collected are about the compiler.
|
| 832 | 824 | # collect_stats is used in the majority case when the metrics to be collected
|
| 833 | 825 | # are about the performance of the runtime code generated by the compiler.
|
| 834 | -def collect_compiler_stats(metric='all',deviation=20):
|
|
| 826 | +def collect_compiler_stats(metric, deviation):
|
|
| 835 | 827 | def f(name, opts, m=metric, d=deviation):
|
| 836 | 828 | no_lint(name, opts)
|
| 837 | 829 | return _collect_stats(name, opts, m, d, None, True)
|
| 838 | 830 | return f
|
| 839 | 831 | |
| 840 | -def collect_stats(metric='all', deviation=20, static_stats_file=None):
|
|
| 832 | +def collect_stats(metric, deviation, static_stats_file=None):
|
|
| 841 | 833 | return lambda name, opts, m=metric, d=deviation, s=static_stats_file: _collect_stats(name, opts, m, d, s)
|
| 842 | 834 | |
| 843 | 835 | def statsFile(comp_test: bool, name: str) -> str:
|
| ... | ... | @@ -864,12 +856,9 @@ def _collect_stats(name: TestName, opts, metrics, deviation: Optional[int], |
| 864 | 856 | # This is a bit weird, though.
|
| 865 | 857 | return
|
| 866 | 858 | |
| 867 | - # Normalize metrics to a list of strings.
|
|
| 859 | + # Normalize metrics to a set of strings.
|
|
| 868 | 860 | if isinstance(metrics, str):
|
| 869 | - if metrics == 'all':
|
|
| 870 | - metrics = testing_metrics()
|
|
| 871 | - else:
|
|
| 872 | - metrics = { metrics }
|
|
| 861 | + metrics = { metrics }
|
|
| 873 | 862 | |
| 874 | 863 | opts.is_stats_test = True
|
| 875 | 864 | if is_compiler_stats_test:
|
| ... | ... | @@ -3,7 +3,8 @@ |
| 3 | 3 | # after they have been loaded into the `LoaderState`.
|
| 4 | 4 | # However, this property is currently not validated automatically.
|
| 5 | 5 | test('LinkableUsage01'
|
| 6 | - , [ collect_compiler_stats('all', 2)
|
|
| 6 | + , [ collect_compiler_stats('bytes allocated', 2)
|
|
| 7 | + , collect_compiler_stats('max_bytes_used', 5)
|
|
| 7 | 8 | , extra_files(['genLinkables.sh', 'BCOTemplate.hs'])
|
| 8 | 9 | , pre_cmd('$MAKE -s --no-print-directory LinkableUsage01_Prep')
|
| 9 | 10 | , req_bco
|
| ... | ... | @@ -18,7 +19,8 @@ test('LinkableUsage01' |
| 18 | 19 | |
| 19 | 20 | # Performance test for bytecode `Linkable`s.
|
| 20 | 21 | test('LinkableUsage02'
|
| 21 | - , [ collect_compiler_stats('all', 2)
|
|
| 22 | + , [ collect_compiler_stats('bytes allocated', 2)
|
|
| 23 | + , collect_compiler_stats('max_bytes_used', 5)
|
|
| 22 | 24 | , extra_files(['genLinkables.sh', 'BCOTemplate.hs'])
|
| 23 | 25 | , pre_cmd('$MAKE -s --no-print-directory LinkableUsage02_Prep')
|
| 24 | 26 | , req_bco
|
| ... | ... | @@ -640,13 +640,16 @@ test ('T15164', |
| 640 | 640 | ],
|
| 641 | 641 | compile,
|
| 642 | 642 | ['-v0 -O'])
|
| 643 | +# T15630/T15630a guard against exponential simplifier blowup when inlining
|
|
| 644 | +# join points (#15630). T15630a is a monomorphic variant that has blown up
|
|
| 645 | +# even when T15630 was fine; see the comment in T15630.hs.
|
|
| 643 | 646 | test('T15630',
|
| 644 | - [collect_compiler_stats()
|
|
| 647 | + [collect_compiler_stats('bytes allocated', 2)
|
|
| 645 | 648 | ],
|
| 646 | 649 | compile,
|
| 647 | 650 | ['-O2'])
|
| 648 | 651 | test('T15630a',
|
| 649 | - [collect_compiler_stats()
|
|
| 652 | + [collect_compiler_stats('bytes allocated', 2)
|
|
| 650 | 653 | ],
|
| 651 | 654 | compile,
|
| 652 | 655 | ['-O2'])
|
| ... | ... | @@ -770,12 +773,19 @@ test ('T9198', |
| 770 | 773 | compile,
|
| 771 | 774 | [''])
|
| 772 | 775 | |
| 776 | +# Guards against quadratic demand-analysis cost on wide recursive data
|
|
| 777 | +# types, which manifested as a compile-time memory blowup (#11545).
|
|
| 773 | 778 | test('T11545',
|
| 774 | - [ collect_compiler_stats('all', 15) ],
|
|
| 779 | + [ collect_compiler_stats('bytes allocated', 2)
|
|
| 780 | + , collect_compiler_residency(15) ],
|
|
| 775 | 781 | compile, ['-O'])
|
| 776 | 782 | |
| 783 | +# Guards against the compile-time memory blowup of #15304, caused by
|
|
| 784 | +# over-keen inlining and demand-analysis memory usage on a module with
|
|
| 785 | +# many wide strict constructors.
|
|
| 777 | 786 | test('T15304',
|
| 778 | - [ collect_compiler_stats('all', 10) ],
|
|
| 787 | + [ collect_compiler_stats('bytes allocated', 2)
|
|
| 788 | + , collect_compiler_residency(10) ],
|
|
| 779 | 789 | compile, ['-O'])
|
| 780 | 790 | test ('T20049',
|
| 781 | 791 | [ collect_compiler_stats('bytes allocated',2) ],
|
| ... | ... | @@ -797,8 +807,10 @@ test('T16875', # Testing one hole-fit with a lot in scope for #16875 |
| 797 | 807 | collect_compiler_runtime(4),
|
| 798 | 808 | compile, ['-fdefer-type-errors -fno-max-valid-hole-fits -package ghc'])
|
| 799 | 809 | |
| 810 | +# Guards against renamer/typechecker allocation regressions on very large
|
|
| 811 | +# generated modules (#20261, a Happy-generated parser).
|
|
| 800 | 812 | test ('T20261',
|
| 801 | - [collect_compiler_stats('all')],
|
|
| 813 | + [collect_compiler_stats('bytes allocated', 2)],
|
|
| 802 | 814 | compile,
|
| 803 | 815 | [''])
|
| 804 | 816 | |
| ... | ... | @@ -807,8 +819,7 @@ test ('T20261', |
| 807 | 819 | # does not sensibly handle one test acting as both
|
| 808 | 820 | # a compile-time and a run-time performance test
|
| 809 | 821 | test('T21839c',
|
| 810 | - [ collect_compiler_stats('all', 10),
|
|
| 811 | - collect_compiler_runtime(1),
|
|
| 822 | + [ collect_compiler_runtime(1),
|
|
| 812 | 823 | only_ways(['normal'])],
|
| 813 | 824 | compile,
|
| 814 | 825 | ['-O'])
|
| ... | ... | @@ -874,8 +885,12 @@ test('interpreter_steplocal', |
| 874 | 885 | ghci_script,
|
| 875 | 886 | ['interpreter_steplocal.script'])
|
| 876 | 887 | |
| 888 | +# Guards against the compile-time memory blowup of #26425; primarily
|
|
| 889 | +# stresses OccAnal and unfolding performance on a long chain of nested
|
|
| 890 | +# join points and cases.
|
|
| 877 | 891 | test ('T26425',
|
| 878 | - [ collect_compiler_stats('all',20) ],
|
|
| 892 | + [ collect_compiler_stats('bytes allocated', 2)
|
|
| 893 | + , collect_compiler_residency(20) ],
|
|
| 879 | 894 | compile,
|
| 880 | 895 | ['-O'])
|
| 881 | 896 |
| ... | ... | @@ -7,7 +7,7 @@ def large_project_makedepend(num): |
| 7 | 7 | return test(
|
| 8 | 8 | f'large-project-makedepend-{num}',
|
| 9 | 9 | [
|
| 10 | - collect_compiler_stats('bytes allocated'),
|
|
| 10 | + collect_compiler_stats('bytes allocated', 10),
|
|
| 11 | 11 | pre_cmd(f'./large-project.sh {num}'),
|
| 12 | 12 | extra_files(['large-project.sh']),
|
| 13 | 13 | ignore_stderr,
|
| 1 | 1 | setTestOpts(js_skip)
|
| 2 | 2 | |
| 3 | 3 | test('space_leak_001',
|
| 4 | - # This could potentially be replaced with
|
|
| 5 | - # collect_stats('all',5) to test all 3 with
|
|
| 6 | - # 5% possible deviation.
|
|
| 7 | 4 | [ collect_stats('bytes allocated',5),
|
| 8 | 5 | collect_runtime_residency(15),
|
| 9 | 6 | omit_ways(['profasm','profthreaded','threaded1','threaded2',
|