Simon Jakobi pushed to branch wip/sjakobi/T16720 at Glasgow Haskell Compiler / GHC Commits: 14b86d18 by Simon Jakobi at 2026-06-11T16:15:41+02:00 Simplifier: reduce intermediate lists in abstractFloats Build the SCC triples for depAnal directly from the Rec pairs instead of going through unzip/map/zip3, and process a CyclicSCC with a single mapM over the triples instead of unzip3/mapAndUnzipM plus two zips. No change in behaviour; node order, and hence depAnal determinism, is preserved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> - - - - - f27bf621 by Simon Jakobi at 2026-06-11T20:01:19+02:00 Simplifier: thread the subst through abstractFloats' cyclic case Instead of collecting (id, poly_id, poly_app, rhs) quadruples and then building an [(id, poly_app)] list for extendSubstList, thread the substitution through the mk_poly1 pass with mapAccumLM, extending it directly via extendIdSubst (extendSubstList is just a fold of extendSubst, which for these RHSs is extendIdSubst). This leaves only two lists per cyclic group: the (poly_id, rhs) pairs and the result. Also force the free-var sets in to_sccs: depAnal always demands them when building edges, so the per-element thunk was pure overhead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> - - - - - 0e3818b6 by Simon Jakobi at 2026-06-11T22:04:52+02:00 testsuite: Repeat output of unexpected failures in the final summary Previously the end-of-run summary named each unexpectedly failing test with only a one-line reason such as "bad exit code (2)". The output explaining *why* the test failed was printed mid-run, where it is interleaved with unrelated test chatter and, in CI, buried thousands of lines up the log (#16720). The driver already captures a failing test's stdout/stderr in TestResult for junit.xml, so reuse that: after the failure lists, print each unexpected failure's captured output, truncated to 100 lines per stream to keep pathological tests from flooding the log. Also attach the captured run stdout/stderr to bad-exit-code failures in simple_run, which previously recorded no output at all - neither junit.xml nor the new summary section could explain such failures. The interactive-run path already did this. Addresses #16720. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> - - - - - a1623b26 by Simon Jakobi at 2026-06-11T22:04:57+02:00 DO NOT MERGE: Break some tests to exercise the failure summary Deliberately break three tests, one per failure mode, to see the new end-of-run failure output in CI: * cgrun001: wrong expected stdout -> bad stdout (diff in summary) * T18619: wrong expected stderr -> bad stderr (diff in summary) * cgrun002: program now exits 3 and writes to stderr -> bad exit code, exercising the newly captured run stdout/stderr in simple_run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> - - - - - d7380efd by Simon Jakobi at 2026-06-11T22:12:00+02:00 testsuite: Colorize the failure-output summary, also in CI Make the per-test blocks in the "Output of unexpected failures" section easier to scan: * the '=====> test(way) [reason]' header is red, * the 'Captured stdout/stderr:' labels are cyan, Colors were previously disabled in CI because the driver only emits them when stdout is a tty, yet the GitLab log viewer renders ANSI colors fine. Add a --force-colors driver flag and pass it from .gitlab/ci.sh. Note that config.supports_colors stays tty-based, since it also guards terminal-title escape sequences, which must not end up in a CI log. The SUMMARY header and the new section now honor summary()'s color parameter, so the plain-text summary file no longer receives escape codes when colors are enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> - - - - - 7 changed files: - .gitlab/ci.sh - compiler/GHC/Core/Opt/Simplify/Utils.hs - testsuite/driver/runtests.py - testsuite/driver/testlib.py - testsuite/tests/codeGen/should_run/cgrun001.stdout - testsuite/tests/codeGen/should_run/cgrun002.hs - testsuite/tests/numeric/should_run/T18619.stderr Changes: ===================================== .gitlab/ci.sh ===================================== @@ -644,6 +644,10 @@ function test_hadrian() { check_msys2_deps _build/stage1/bin/ghc --version check_release_build + # GitLab's log viewer renders ANSI colors, but stdout here is not a tty, + # so the driver must be told to emit them. + RUNTEST_ARGS="${RUNTEST_ARGS:-} --force-colors" + # Ensure that statically-linked builds are actually static if [[ "${BUILD_FLAVOUR}" = *static* ]]; then bad_execs="" ===================================== compiler/GHC/Core/Opt/Simplify/Utils.hs ===================================== @@ -2432,12 +2432,10 @@ abstractFloats uf_opts top_lvl main_tvs floats body -- for why we need to re-do dependency analysis to_sccs :: OutBind -> [SCC (Id, CoreExpr, VarSet)] to_sccs (NonRec id e) = [AcyclicSCC (id, e, emptyVarSet)] -- emptyVarSet: abstract doesn't need it - to_sccs (Rec prs) = sccs - where - (ids,rhss) = unzip prs - sccs = depAnal (\(id,_rhs,_fvs) -> [getName id]) - (\(_id,_rhs,fvs) -> nonDetStrictFoldVarSet ((:) . getName) [] fvs) -- Wrinkle (AB3) - (zip3 ids rhss (map exprFreeVars rhss)) + to_sccs (Rec prs) + = depAnal (\(id,_rhs,_fvs) -> [getName id]) + (\(_id,_rhs,fvs) -> nonDetStrictFoldVarSet ((:) . getName) [] fvs) -- Wrinkle (AB3) + [ (id, rhs, fvs) | (id, rhs) <- prs, let !fvs = exprFreeVars rhs ] abstract :: GHC.Core.Subst.Subst -> SCC (Id, CoreExpr, VarSet) -> SimplM (GHC.Core.Subst.Subst, OutBind) abstract subst (AcyclicSCC (id, rhs, _empty_var_set)) @@ -2452,14 +2450,15 @@ abstractFloats uf_opts top_lvl main_tvs floats body tvs_here = choose_tvs (exprSomeFreeVars isTyVar rhs') abstract subst (CyclicSCC trpls) - = do { (poly_ids, poly_apps) <- mapAndUnzipM (mk_poly1 tvs_here) ids - ; let subst' = GHC.Core.Subst.extendSubstList subst (ids `zip` poly_apps) - poly_pairs = [ mk_poly2 poly_id tvs_here rhs' - | (poly_id, rhs) <- poly_ids `zip` rhss - , let rhs' = GHC.Core.Subst.substExpr subst' rhs ] + = do { (subst', poly_prs) <- mapAccumLM mk1 subst trpls + ; let poly_pairs = [ mk_poly2 poly_id tvs_here + (GHC.Core.Subst.substExpr subst' rhs) + | (poly_id, rhs) <- poly_prs ] ; return (subst', Rec poly_pairs) } where - (ids,rhss,_fvss) = unzip3 trpls + mk1 s (id, rhs, _fvs) + = do { (poly_id, poly_app) <- mk_poly1 tvs_here id + ; return (GHC.Core.Subst.extendIdSubst s id poly_app, (poly_id, rhs)) } -- tvs_here: see Note [Which type variables to abstract over] tvs_here = choose_tvs (mapUnionVarSet get_bind_fvs trpls) ===================================== testsuite/driver/runtests.py ===================================== @@ -94,6 +94,8 @@ parser.add_argument("--ignore-perf-failures", choices=['increases','decreases',' help="Do not fail due to out-of-tolerance perf tests") parser.add_argument("--only-report-hadrian-deps", type=Path, help="Dry run the testsuite and report all extra hadrian dependencies needed on the given file") +parser.add_argument("--force-colors", action="store_true", + help="emit ANSI colors even when stdout is not a tty (e.g. for CI logs)") args = parser.parse_args() @@ -259,7 +261,9 @@ def supports_colors(): return True config.supports_colors = supports_colors() -term_color.enable_color = config.supports_colors +# config.supports_colors deliberately stays tty-based: it also guards +# terminal-title updates, which must not end up in a CI log. +term_color.enable_color = config.supports_colors or args.force_colors # This has to come after arg parsing as the args can change the compiler get_compiler_info() @@ -587,7 +591,7 @@ else: print(Perf.allow_changes_string([(m.change, m.stat) for m in t.metrics])) print('-' * 25) - summary(t, sys.stdout, color=config.supports_colors) + summary(t, sys.stdout, color=term_color.enable_color) # Write perf stats if any exist or if a metrics file is specified. stats_metrics = [stat for (_, stat, __) in t.metrics] # type: List[PerfStat] ===================================== testsuite/driver/testlib.py ===================================== @@ -2442,7 +2442,9 @@ async def simple_run(name: TestName, way: WayName, prog: str, extra_run_opts: st dump_stdout(name) dump_stderr(name) message = format_bad_exit_code_message(exit_code) - return failBecause(message) + return failBecause(message, + stderr=read_stderr(name), + stdout=read_stdout(name)) if not (opts.ignore_stderr or await stderr_ok(name, way) or opts.combined_output): return failBecause('bad stderr', @@ -3541,7 +3543,8 @@ def summary(t: TestRun, file: TextIO, color=False) -> None: summary_color = Color.GREEN assert t.start_time is not None - file.write(colored(summary_color, 'SUMMARY') + ' for test run started at ' + summary_header = colored(summary_color, 'SUMMARY') if color else 'SUMMARY' + file.write(summary_header + ' for test run started at ' + t.start_time.strftime("%c %Z") + '\n' + str(datetime.datetime.now() - t.start_time).rjust(8) + ' spent to go through\n' @@ -3593,6 +3596,10 @@ def summary(t: TestRun, file: TextIO, color=False) -> None: file.write('Framework warnings:\n') printTestInfosSummary(file, t.framework_warnings) + if t.unexpected_failures: + file.write('Output of unexpected failures:\n\n') + printTestOutputSummary(file, t.unexpected_failures, color) + if stopping(): file.write('WARNING: Testsuite run was terminated early\n') @@ -3606,6 +3613,34 @@ def printUnexpectedTests(file: TextIO, testInfoss): file.write('TEST="' + ' '.join(sorted(unexpected)) + '"\n') file.write('\n') +# Per-stream cap on a failing test's output repeated in the final summary. +MAX_SUMMARY_OUTPUT_LINES = 100 + +def printTestOutputSummary(file: TextIO, testInfos, color: bool=False) -> None: + # Repeat failing tests' captured output in the summary, so one needn't + # hunt for it earlier in a possibly very long log; see #16720. + for result in sorted(testInfos, key=lambda r: (r.testname.lower(), r.way, r.directory)): + header = '=====> {}({}) [{}]'.format(result.testname, result.way, result.reason) + if color: + header = colored(Color.RED, header) + file.write(header + '\n') + for stream_name, contents in [('stdout', result.stdout), ('stderr', result.stderr)]: + if contents and contents.strip(): + label = 'Captured {}:'.format(stream_name) + if color: + label = colored(Color.CYAN, label) + lines = contents.rstrip('\n').split('\n') + if len(lines) > MAX_SUMMARY_OUTPUT_LINES: + omitted = len(lines) - MAX_SUMMARY_OUTPUT_LINES + lines = lines[:MAX_SUMMARY_OUTPUT_LINES] \ + + ['... ({} more lines omitted, see junit.xml)'.format(omitted)] + s = label + '\n' + ''.join(l + '\n' for l in lines) + # Test output can contain characters that file's encoding + # cannot represent; replace rather than crash (cf safe_print). + enc = getattr(file, 'encoding', None) or 'utf-8' + file.write(s.encode(enc, errors='replace').decode(enc)) + file.write('\n') + def printTestInfosSummary(file: TextIO, testInfos): maxDirLen = max(len(tr.directory) for tr in testInfos) for result in sorted(testInfos, key=lambda r: (r.testname.lower(), r.way, r.directory)): ===================================== testsuite/tests/codeGen/should_run/cgrun001.stdout ===================================== @@ -1 +1 @@ --42 +-43 ===================================== testsuite/tests/codeGen/should_run/cgrun002.hs ===================================== @@ -1,4 +1,10 @@ -main = print ((f id2) (10 + thirty_two)) +import System.Exit +import System.IO + +main = do + print ((f id2) (10 + thirty_two)) + hPutStrLn stderr "deliberate breakage to test summary output" + exitWith (ExitFailure 3) where f x = g x where ===================================== testsuite/tests/numeric/should_run/T18619.stderr ===================================== @@ -1,3 +1,3 @@ T18619: Uncaught exception ghc-internal:GHC.Internal.Exception.Type.ArithException: -arithmetic overflow +arithmetic underflow View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/3d1919a5194d22eb960eac3c6a44927... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/3d1919a5194d22eb960eac3c6a44927... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Simon Jakobi (@sjakobi2)