Simon Jakobi pushed to branch wip/sjakobi/T27448 at Glasgow Haskell Compiler / GHC
Commits:
-
99f98637
by Simon Jakobi at 2026-11-04T20:17:20+01:00
8 changed files:
- compiler/GHC/CmmToAsm/CFG.hs
- + testsuite/tests/cmm/should_compile/T28083.cmm
- + testsuite/tests/cmm/should_compile/T28083.stderr
- testsuite/tests/cmm/should_compile/all.T
- + testsuite/tests/cmm/should_compile/selfloop_weights.cmm
- + testsuite/tests/cmm/should_compile/selfloop_weights.stderr
- testsuite/tests/perf/compiler/all.T
- + testsuite/tests/perf/compiler/genManyBasicBlocks
Changes:
| ... | ... | @@ -885,17 +885,12 @@ loopInfo cfg root = LoopInfo { liBackEdges = backEdges |
| 885 | 885 | in map (\n -> (n, loopCount n)) $ nodes :: [(BlockId, Int)]
|
| 886 | 886 | |
| 887 | 887 | mkDomMap :: Tree BlockId -> LabelMap LabelSet
|
| 888 | - mkDomMap root = mapFromList $ go setEmpty root
|
|
| 888 | + mkDomMap = go setEmpty mapEmpty
|
|
| 889 | 889 | where
|
| 890 | - go :: LabelSet -> Tree BlockId -> [(Label,LabelSet)]
|
|
| 891 | - go parents (Node lbl [])
|
|
| 892 | - = [(lbl, parents)]
|
|
| 893 | - go parents (Node _ leaves)
|
|
| 894 | - = let nodes = map rootLabel leaves
|
|
| 895 | - entries = map (\x -> (x,parents)) nodes
|
|
| 896 | - in entries ++ concatMap
|
|
| 897 | - (\n -> go (setInsert (rootLabel n) parents) n)
|
|
| 898 | - leaves
|
|
| 890 | + go :: LabelSet -> LabelMap LabelSet -> Tree BlockId -> LabelMap LabelSet
|
|
| 891 | + go doms acc (Node lbl children)
|
|
| 892 | + = let !doms' = setInsert lbl doms
|
|
| 893 | + in foldl' (go doms') (mapInsert lbl doms' acc) children
|
|
| 899 | 894 | |
| 900 | 895 | -- We make the CFG a Hoopl Graph, so we can reuse revPostOrder.
|
| 901 | 896 | newtype BlockNode (e :: Extensibility) (x :: Extensibility) = BN (BlockId,[BlockId])
|
| 1 | +#include "Cmm.h"
|
|
| 2 | + |
|
| 3 | +// The entry is a bare goto, so the Cmm control-flow optimiser makes L the
|
|
| 4 | +// entry, heading the loop L -> B -> L. Expected: L -> B outweighs the exit
|
|
| 5 | +// edge. Without loop detection they tie.
|
|
| 6 | +stg_loop {
|
|
| 7 | + goto L;
|
|
| 8 | +L:
|
|
| 9 | + if (R1 > 0) { goto B; }
|
|
| 10 | + jump stg_loop [R1];
|
|
| 11 | +B:
|
|
| 12 | + R1 = R1 - 1;
|
|
| 13 | + goto L;
|
|
| 14 | +} |
| 1 | +==================== CFG Weights - Final ====================
|
|
| 2 | +stg_loop:
|
|
| 3 | +digraph {
|
|
| 4 | + c1 -> c4[label="0.25000",weight="0.25000"];
|
|
| 5 | + c1 -> c2[label="0.75000",weight="0.75000"];
|
|
| 6 | + c2 -> c1[label="0.75000",weight="0.75000"];
|
|
| 7 | +} |
| ... | ... | @@ -21,3 +21,13 @@ test('T24474-cmm-opt-order', cmm_src, compile, ['-optc-DFOO ' |
| 21 | 21 | '-optCmmP-UFOO '
|
| 22 | 22 | '-optCmmP-UBAR '
|
| 23 | 23 | '-optc-DBAR'])
|
| 24 | +test('T28083', [cmm_src, only_ways(['optasm']),
|
|
| 25 | + unless(arch('x86_64'), skip),
|
|
| 26 | + when(leading_underscore(), skip),
|
|
| 27 | + multiline_grep_errmsg(r'=+ CFG Weights - Final[^}]*}')],
|
|
| 28 | + compile, ['-no-hs-main -ddump-cfg-weights'])
|
|
| 29 | +test('selfloop_weights', [cmm_src, only_ways(['optasm']),
|
|
| 30 | + unless(arch('x86_64'), skip),
|
|
| 31 | + when(leading_underscore(), skip),
|
|
| 32 | + multiline_grep_errmsg(r'=+ CFG Weights - Final[^}]*}')],
|
|
| 33 | + compile, ['-no-hs-main -ddump-cfg-weights']) |
| 1 | +#include "Cmm.h"
|
|
| 2 | + |
|
| 3 | +// S is a self-loop. The entry does work, so it isn't merged into S.
|
|
| 4 | +// Expected: the self edge outweighs the exit edge. Without loop detection
|
|
| 5 | +// both get weight 0.
|
|
| 6 | +stg_self {
|
|
| 7 | + R1 = R1 + 1;
|
|
| 8 | + goto S;
|
|
| 9 | +S:
|
|
| 10 | + R1 = R1 - 1;
|
|
| 11 | + if (R1 > 0) { goto S; }
|
|
| 12 | + jump stg_self [R1];
|
|
| 13 | +} |
| 1 | +==================== CFG Weights - Final ====================
|
|
| 2 | +stg_self:
|
|
| 3 | +digraph {
|
|
| 4 | + c1 -> c4[label="1.00000",weight="1.00000"];
|
|
| 5 | + c5 -> c1[label="1.00000",weight="1.00000"];
|
|
| 6 | + c1 -> c1[label="7.00000",weight="7.00000"];
|
|
| 7 | +} |
| ... | ... | @@ -890,3 +890,14 @@ test ('T26989', |
| 890 | 890 | multimod_compile,
|
| 891 | 891 | ['T26989', '-v0 -O'])
|
| 892 | 892 | |
| 893 | +test('ManyBasicBlocks',
|
|
| 894 | + [ only_ways(['optasm']),
|
|
| 895 | + unless(have_ncg(), skip),
|
|
| 896 | + cmm_src,
|
|
| 897 | + collect_compiler_stats('bytes allocated', 2),
|
|
| 898 | + pre_cmd('./genManyBasicBlocks'),
|
|
| 899 | + extra_files(['genManyBasicBlocks']),
|
|
| 900 | + ],
|
|
| 901 | + compile,
|
|
| 902 | + ['-O2 -v0'])
|
|
| 903 | + |
| 1 | +#!/usr/bin/env bash
|
|
| 2 | +set -euo pipefail
|
|
| 3 | + |
|
| 4 | +# Generate a Cmm source file that is intentionally heavy on control flow.
|
|
| 5 | +#
|
|
| 6 | +# The benchmark shape is one large procedure containing:
|
|
| 7 | +# - many split/left/right/join diamonds
|
|
| 8 | +# - periodic backedges that make each chunk a natural loop
|
|
| 9 | +#
|
|
| 10 | +# It produces thousands of actual basic blocks in a single Cmm graph,
|
|
| 11 | +# stressing the control-flow handling of the NCG and Wasm backends.
|
|
| 12 | +#
|
|
| 13 | +# Tuning knobs:
|
|
| 14 | +# STAGES Number of diamond stages to generate. Default: 2048
|
|
| 15 | +# LOOP_PERIOD Number of stages per loop chunk. Default: 16
|
|
| 16 | +# LOOP_TRIPS Runtime trip count for each chunk. Default: 2
|
|
| 17 | +# OUT Output file. Default: ManyBasicBlocks.cmm
|
|
| 18 | +#
|
|
| 19 | +# With STAGES=2, LOOP_PERIOD=2, LOOP_TRIPS=2 the output looks like:
|
|
| 20 | +#
|
|
| 21 | +# #include "Cmm.h"
|
|
| 22 | +#
|
|
| 23 | +# many_basic_blocks (W_ seed) {
|
|
| 24 | +# W_ acc;
|
|
| 25 | +# W_ mix;
|
|
| 26 | +# W_ trip0;
|
|
| 27 | +#
|
|
| 28 | +# entry:
|
|
| 29 | +# acc = seed;
|
|
| 30 | +# mix = seed + (17 :: W_);
|
|
| 31 | +# trip0 = 0;
|
|
| 32 | +# goto split0;
|
|
| 33 | +#
|
|
| 34 | +# split0:
|
|
| 35 | +# mix = mix + (1 :: W_);
|
|
| 36 | +# if (((mix + (2 :: W_)) % (7 :: W_)) < (3 :: W_)) {
|
|
| 37 | +# goto left0;
|
|
| 38 | +# }
|
|
| 39 | +# goto right0;
|
|
| 40 | +#
|
|
| 41 | +# left0:
|
|
| 42 | +# acc = acc + (3 :: W_);
|
|
| 43 | +# mix = mix + (4 :: W_);
|
|
| 44 | +# goto join0;
|
|
| 45 | +#
|
|
| 46 | +# right0:
|
|
| 47 | +# acc = acc + (4 :: W_);
|
|
| 48 | +# mix = mix + (3 :: W_);
|
|
| 49 | +# goto join0;
|
|
| 50 | +#
|
|
| 51 | +# join0:
|
|
| 52 | +# acc = acc + mix + (1 :: W_);
|
|
| 53 | +# goto split1;
|
|
| 54 | +#
|
|
| 55 | +# split1:
|
|
| 56 | +# mix = mix + (5 :: W_);
|
|
| 57 | +# if (((mix + (6 :: W_)) % (7 :: W_)) < (3 :: W_)) {
|
|
| 58 | +# goto left1;
|
|
| 59 | +# }
|
|
| 60 | +# goto right1;
|
|
| 61 | +#
|
|
| 62 | +# left1:
|
|
| 63 | +# acc = acc + (7 :: W_);
|
|
| 64 | +# mix = mix + (8 :: W_);
|
|
| 65 | +# goto join1;
|
|
| 66 | +#
|
|
| 67 | +# right1:
|
|
| 68 | +# acc = acc + (8 :: W_);
|
|
| 69 | +# mix = mix + (7 :: W_);
|
|
| 70 | +# goto join1;
|
|
| 71 | +#
|
|
| 72 | +# join1:
|
|
| 73 | +# acc = acc + mix + (5 :: W_);
|
|
| 74 | +# goto latch0;
|
|
| 75 | +#
|
|
| 76 | +# latch0:
|
|
| 77 | +# trip0 = trip0 + (1 :: W_);
|
|
| 78 | +# mix = mix + (1000000 :: W_);
|
|
| 79 | +# acc = acc + (2000000 :: W_);
|
|
| 80 | +# if (trip0 < (2 :: W_)) {
|
|
| 81 | +# goto split0;
|
|
| 82 | +# }
|
|
| 83 | +# return (acc);
|
|
| 84 | +#
|
|
| 85 | +# }
|
|
| 86 | + |
|
| 87 | +: "${STAGES:=2048}"
|
|
| 88 | +: "${LOOP_PERIOD:=16}"
|
|
| 89 | +: "${LOOP_TRIPS:=2}"
|
|
| 90 | +: "${OUT:=ManyBasicBlocks.cmm}"
|
|
| 91 | + |
|
| 92 | +die() {
|
|
| 93 | + printf '%s\n' "$*" >&2
|
|
| 94 | + exit 1
|
|
| 95 | +}
|
|
| 96 | + |
|
| 97 | +check_nat() {
|
|
| 98 | + local value=$1
|
|
| 99 | + local name=$2
|
|
| 100 | + |
|
| 101 | + [[ $value =~ ^[0-9]+$ ]] || die "$name must be a non-negative integer"
|
|
| 102 | +}
|
|
| 103 | + |
|
| 104 | +check_nat "$STAGES" STAGES
|
|
| 105 | +check_nat "$LOOP_PERIOD" LOOP_PERIOD
|
|
| 106 | +check_nat "$LOOP_TRIPS" LOOP_TRIPS
|
|
| 107 | + |
|
| 108 | +(( STAGES > 0 )) || die "STAGES must be greater than zero"
|
|
| 109 | +(( LOOP_PERIOD > 0 )) || die "LOOP_PERIOD must be greater than zero"
|
|
| 110 | +(( LOOP_TRIPS > 0 )) || die "LOOP_TRIPS must be greater than zero"
|
|
| 111 | + |
|
| 112 | +chunk_count=$(( (STAGES + LOOP_PERIOD - 1) / LOOP_PERIOD ))
|
|
| 113 | +block_count=$(( 1 + 4 * STAGES + chunk_count ))
|
|
| 114 | + |
|
| 115 | +emit() {
|
|
| 116 | + printf '%s\n' "$*"
|
|
| 117 | +}
|
|
| 118 | + |
|
| 119 | +{
|
|
| 120 | + emit "// Generated by genManyBasicBlocks"
|
|
| 121 | + emit "//"
|
|
| 122 | + emit "// Parameters:"
|
|
| 123 | + emit "// STAGES=${STAGES}"
|
|
| 124 | + emit "// LOOP_PERIOD=${LOOP_PERIOD}"
|
|
| 125 | + emit "// LOOP_TRIPS=${LOOP_TRIPS}"
|
|
| 126 | + emit "//"
|
|
| 127 | + emit "// Approximate block count: ${block_count}"
|
|
| 128 | + emit "#include \"Cmm.h\""
|
|
| 129 | + emit
|
|
| 130 | + emit "many_basic_blocks (W_ seed) {"
|
|
| 131 | + emit " W_ acc;"
|
|
| 132 | + emit " W_ mix;"
|
|
| 133 | + for ((chunk = 0; chunk < chunk_count; chunk++)); do
|
|
| 134 | + emit " W_ trip${chunk};"
|
|
| 135 | + done
|
|
| 136 | + emit
|
|
| 137 | + emit "entry:"
|
|
| 138 | + emit " acc = seed;"
|
|
| 139 | + emit " mix = seed + (17 :: W_);"
|
|
| 140 | + for ((chunk = 0; chunk < chunk_count; chunk++)); do
|
|
| 141 | + emit " trip${chunk} = 0;"
|
|
| 142 | + done
|
|
| 143 | + emit " goto split0;"
|
|
| 144 | + emit
|
|
| 145 | + |
|
| 146 | + for ((stage = 0; stage < STAGES; stage++)); do
|
|
| 147 | + next_stage=$(( stage + 1 ))
|
|
| 148 | + chunk=$(( stage / LOOP_PERIOD ))
|
|
| 149 | + stage1=$(( 4 * stage + 1 ))
|
|
| 150 | + stage2=$(( 4 * stage + 2 ))
|
|
| 151 | + stage3=$(( 4 * stage + 3 ))
|
|
| 152 | + stage4=$(( 4 * stage + 4 ))
|
|
| 153 | + |
|
| 154 | + emit "split${stage}:"
|
|
| 155 | + emit " mix = mix + (${stage1} :: W_);"
|
|
| 156 | + emit " if (((mix + (${stage2} :: W_)) % (7 :: W_)) < (3 :: W_)) {"
|
|
| 157 | + emit " goto left${stage};"
|
|
| 158 | + emit " }"
|
|
| 159 | + emit " goto right${stage};"
|
|
| 160 | + emit
|
|
| 161 | + |
|
| 162 | + emit "left${stage}:"
|
|
| 163 | + emit " acc = acc + (${stage3} :: W_);"
|
|
| 164 | + emit " mix = mix + (${stage4} :: W_);"
|
|
| 165 | + emit " goto join${stage};"
|
|
| 166 | + emit
|
|
| 167 | + |
|
| 168 | + emit "right${stage}:"
|
|
| 169 | + emit " acc = acc + (${stage4} :: W_);"
|
|
| 170 | + emit " mix = mix + (${stage3} :: W_);"
|
|
| 171 | + emit " goto join${stage};"
|
|
| 172 | + emit
|
|
| 173 | + |
|
| 174 | + emit "join${stage}:"
|
|
| 175 | + emit " acc = acc + mix + (${stage1} :: W_);"
|
|
| 176 | + if (( next_stage == STAGES || next_stage % LOOP_PERIOD == 0 )); then
|
|
| 177 | + emit " goto latch${chunk};"
|
|
| 178 | + else
|
|
| 179 | + emit " goto split${next_stage};"
|
|
| 180 | + fi
|
|
| 181 | + emit
|
|
| 182 | + done
|
|
| 183 | + |
|
| 184 | + for ((chunk = 0; chunk < chunk_count; chunk++)); do
|
|
| 185 | + loop_start=$(( chunk * LOOP_PERIOD ))
|
|
| 186 | + next_stage=$(( (chunk + 1) * LOOP_PERIOD ))
|
|
| 187 | + latch_mix=$(( 1000000 + chunk ))
|
|
| 188 | + latch_acc=$(( 2000000 + chunk ))
|
|
| 189 | + |
|
| 190 | + emit "latch${chunk}:"
|
|
| 191 | + emit " trip${chunk} = trip${chunk} + (1 :: W_);"
|
|
| 192 | + emit " mix = mix + (${latch_mix} :: W_);"
|
|
| 193 | + emit " acc = acc + (${latch_acc} :: W_);"
|
|
| 194 | + emit " if (trip${chunk} < (${LOOP_TRIPS} :: W_)) {"
|
|
| 195 | + emit " goto split${loop_start};"
|
|
| 196 | + emit " }"
|
|
| 197 | + if (( next_stage < STAGES )); then
|
|
| 198 | + emit " goto split${next_stage};"
|
|
| 199 | + else
|
|
| 200 | + emit " return (acc);"
|
|
| 201 | + fi
|
|
| 202 | + emit
|
|
| 203 | + done
|
|
| 204 | + |
|
| 205 | + emit "}"
|
|
| 206 | +} > "$OUT" |