| 1 |
{-# OPTIONS -fglasgow-exts #-} |
|---|
| 2 |
|
|---|
| 3 |
-- Driver for testing out register allocator performance. |
|---|
| 4 |
-- Run this from the root of the nofib suite |
|---|
| 5 |
-- It will run the tests writing the results to a file "spill-report" in the current directory. |
|---|
| 6 |
-- Results are appended to spill-report incrementally, so you can tail it as checkSpills runs |
|---|
| 7 |
-- to see the results so far. |
|---|
| 8 |
-- |
|---|
| 9 |
-- Each benchmark run with each of the allocators, linear, graph, iterative while counting |
|---|
| 10 |
-- spills/realoads added and reg-reg moves remaining in code. |
|---|
| 11 |
-- |
|---|
| 12 |
-- Gathering spill stats can cause the compiler hold on to intermediate structures |
|---|
| 13 |
-- and thus use much more space than it would otherwise, especially when doing |
|---|
| 14 |
-- graph allocation. For this reason we compile again without stat gathering when checking |
|---|
| 15 |
-- compile times. |
|---|
| 16 |
-- |
|---|
| 17 |
import System |
|---|
| 18 |
import System.Directory |
|---|
| 19 |
import System.Posix.Process |
|---|
| 20 |
import Text.Regex |
|---|
| 21 |
import Control.Monad |
|---|
| 22 |
|
|---|
| 23 |
import Data.Maybe |
|---|
| 24 |
import Data.List |
|---|
| 25 |
|
|---|
| 26 |
---------- |
|---|
| 27 |
debug = True |
|---|
| 28 |
|
|---|
| 29 |
----------- |
|---|
| 30 |
-- | spills, clobbers, reloads, joins reg-reg, joins reg-mem, reg-reg-moves remaining in code |
|---|
| 31 |
type StatsLinear = (Int, Int, Int, Int, Int, Int) |
|---|
| 32 |
|
|---|
| 33 |
-- | spills, reloads, reg-reg-moves remaining in code |
|---|
| 34 |
type StatsGraph = (Int, Int, Int) |
|---|
| 35 |
|
|---|
| 36 |
-- | test results for one module |
|---|
| 37 |
type TestResult |
|---|
| 38 |
= ( String -- module name |
|---|
| 39 |
, StatsLinear |
|---|
| 40 |
, Float -- compile time with linear allocator |
|---|
| 41 |
, StatsGraph |
|---|
| 42 |
, Float -- compile time with graph coloring allocator |
|---|
| 43 |
, StatsGraph |
|---|
| 44 |
, Float) -- compile time with graph coloring allocator + iterative coalescing |
|---|
| 45 |
|
|---|
| 46 |
----- |
|---|
| 47 |
main :: IO () |
|---|
| 48 |
main |
|---|
| 49 |
= do testsArg <- getArgs |
|---|
| 50 |
|
|---|
| 51 |
dirBase <- getCurrentDirectory |
|---|
| 52 |
let fileReport = dirBase ++ "/checkSpills.report" |
|---|
| 53 |
let fileWork = dirBase ++ "/checkSpills.tests" |
|---|
| 54 |
|
|---|
| 55 |
-- If there is a test list then read add that to the list of test to do |
|---|
| 56 |
exists <- doesFileExist fileWork |
|---|
| 57 |
testsFile <- if exists |
|---|
| 58 |
then do file <- readFile fileWork |
|---|
| 59 |
let fileLines = filter (/= "") $ lines file |
|---|
| 60 |
return fileLines |
|---|
| 61 |
else return [] |
|---|
| 62 |
|
|---|
| 63 |
-- run all the tests the tests |
|---|
| 64 |
let tests = if null testsArg |
|---|
| 65 |
then testsFile |
|---|
| 66 |
else testsArg |
|---|
| 67 |
|
|---|
| 68 |
stats <- mapM (doTest fileReport) tests |
|---|
| 69 |
|
|---|
| 70 |
-- total up al the stats |
|---|
| 71 |
appendFile fileReport ("\n\n" ++ (makeReportLine $ makeTotal stats)) |
|---|
| 72 |
return () |
|---|
| 73 |
|
|---|
| 74 |
|
|---|
| 75 |
-- Run the tests on the program in this dir |
|---|
| 76 |
-- |
|---|
| 77 |
doTest :: FilePath -- ^ file to append test results to |
|---|
| 78 |
-> FilePath -- ^ dir of nofib benchmark |
|---|
| 79 |
-> IO ( FilePath -- dir of nofib benchmark |
|---|
| 80 |
, [TestResult]) -- results for each module in the program |
|---|
| 81 |
|
|---|
| 82 |
doTest fileReport dirTest |
|---|
| 83 |
= do putStr $ "* Test " ++ dirTest ++ "\n" |
|---|
| 84 |
dirOld <- getCurrentDirectory |
|---|
| 85 |
|
|---|
| 86 |
-- change to test dir |
|---|
| 87 |
setCurrentDirectory dirTest |
|---|
| 88 |
|
|---|
| 89 |
-- run the test |
|---|
| 90 |
cmd "make boot" |
|---|
| 91 |
|
|---|
| 92 |
-- build with linear / graph / iterative allocator |
|---|
| 93 |
(statsLinear, timesLinear) <- doTestRun "" slurpSpillStatsLinear |
|---|
| 94 |
(statsGraph, timesGraph) <- doTestRun "-fregs-graph" slurpSpillStatsGraph |
|---|
| 95 |
(statsIter, timesIter) <- doTestRun "-fregs-iterative" slurpSpillStatsGraph |
|---|
| 96 |
|
|---|
| 97 |
-- builds stats |
|---|
| 98 |
let stats = |
|---|
| 99 |
map (collateResults timesLinear statsGraph timesGraph statsIter timesIter) statsLinear |
|---|
| 100 |
|
|---|
| 101 |
let out = (dirTest, stats) |
|---|
| 102 |
|
|---|
| 103 |
putStr $ makeSection out |
|---|
| 104 |
appendFile fileReport $ makeSection out |
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 |
-- change back to old dir |
|---|
| 108 |
setCurrentDirectory dirOld |
|---|
| 109 |
|
|---|
| 110 |
return out |
|---|
| 111 |
|
|---|
| 112 |
|
|---|
| 113 |
-- For some reason make doesn't always build the modules in the same order, so we can't |
|---|
| 114 |
-- do a simple zip here. It's all a bit tiring, really. |
|---|
| 115 |
collateResults |
|---|
| 116 |
:: [(String, Float)] |
|---|
| 117 |
-> [(String, StatsGraph)] |
|---|
| 118 |
-> [(String, Float)] |
|---|
| 119 |
-> [(String, StatsGraph)] |
|---|
| 120 |
-> [(String, Float)] |
|---|
| 121 |
-> (String, StatsLinear) |
|---|
| 122 |
-> (String, StatsLinear, Float, StatsGraph, Float, StatsGraph, Float) |
|---|
| 123 |
|
|---|
| 124 |
collateResults lTimes gStats gTimes iStats iTimes (name, lStat) |
|---|
| 125 |
| Just lTime <- lookup name lTimes |
|---|
| 126 |
, Just gStat <- lookup name gStats |
|---|
| 127 |
, Just gTime <- lookup name gTimes |
|---|
| 128 |
, Just iStat <- lookup name iStats |
|---|
| 129 |
, Just iTime <- lookup name iTimes |
|---|
| 130 |
= (name, lStat, lTime, gStat, gTime, iStat, iTime) |
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
doTestRun |
|---|
| 134 |
:: String -- ^ flag to use |
|---|
| 135 |
-> (String -> IO stats) -- ^ fn to extract stats from dropped -dump-asm-stats file |
|---|
| 136 |
-> IO ( [(String, stats)] |
|---|
| 137 |
, [(String, Float)] ) |
|---|
| 138 |
|
|---|
| 139 |
doTestRun flag slurpStats |
|---|
| 140 |
= do |
|---|
| 141 |
-- build with the requested flag |
|---|
| 142 |
cleanDumps |
|---|
| 143 |
cmd $ "make clean" |
|---|
| 144 |
cmd $ "make EXTRA_HC_OPTS=\"-Wnot -O2 -fasm -ddump-to-file -ddump-asm-stats " ++ flag ++ " \"" |
|---|
| 145 |
|
|---|
| 146 |
statFiles <- getStatFiles |
|---|
| 147 |
stats <- mapM slurpStats statFiles |
|---|
| 148 |
|
|---|
| 149 |
let moduleNames = map slurpModuleName statFiles |
|---|
| 150 |
|
|---|
| 151 |
-- build again without dumping to get the compile time. |
|---|
| 152 |
-- turning on dumping can make the compiler use much more space than it usually would. |
|---|
| 153 |
-- so we wan't to run it again without to get a true compile time. |
|---|
| 154 |
cleanDumps |
|---|
| 155 |
cmd "make clean" |
|---|
| 156 |
Just (out, err) <- cmdOE $ "make EXTRA_HC_OPTS=\"-Wnot -O2 -fasm " ++ flag ++ " \"" |
|---|
| 157 |
|
|---|
| 158 |
let compileTimes = slurpCompileTimes (lines out) |
|---|
| 159 |
|
|---|
| 160 |
return ( zip moduleNames stats |
|---|
| 161 |
, compileTimes ) |
|---|
| 162 |
|
|---|
| 163 |
|
|---|
| 164 |
-- Slurp out the compile times for each module from the nofib dump |
|---|
| 165 |
slurpCompileTimes |
|---|
| 166 |
:: [String] -> [(String, Float)] |
|---|
| 167 |
|
|---|
| 168 |
slurpCompileTimes (l0:l1:l2:l3:ls) |
|---|
| 169 |
| Just [modName] <- matchRegex (mkRegex "==nofib== .* time to compile (.*) follows") l0 |
|---|
| 170 |
, Just [min,sec,hsec] <- matchRegex (mkRegex " ([^ ]*):(.*)\\.(.*)elapsed") l3 |
|---|
| 171 |
= ( modName |
|---|
| 172 |
, read min * 60 + read sec + read hsec / 100) |
|---|
| 173 |
: slurpCompileTimes ls |
|---|
| 174 |
|
|---|
| 175 |
| otherwise |
|---|
| 176 |
= slurpCompileTimes (l1:l2:l3:ls) |
|---|
| 177 |
|
|---|
| 178 |
slurpCompileTimes _ = [] |
|---|
| 179 |
|
|---|
| 180 |
|
|---|
| 181 |
makeTotal :: [(FilePath, [TestResult])] -> TestResult |
|---|
| 182 |
makeTotal ts |
|---|
| 183 |
= let results = concat $ map snd ts |
|---|
| 184 |
total = foldr addTestResults (zeroTestResult "TOTAL") results |
|---|
| 185 |
in total |
|---|
| 186 |
|
|---|
| 187 |
|
|---|
| 188 |
makeSection :: (FilePath, [TestResult]) -> String |
|---|
| 189 |
makeSection (testName, stats) |
|---|
| 190 |
= "* " ++ testName ++ "\n" |
|---|
| 191 |
++ (concat $ map makeReportLine stats) |
|---|
| 192 |
++ "\n" |
|---|
| 193 |
|
|---|
| 194 |
|
|---|
| 195 |
makeReportLine :: TestResult -> String |
|---|
| 196 |
makeReportLine |
|---|
| 197 |
( name |
|---|
| 198 |
, statsLinear @(ls, lc, lr, _, _, lm) |
|---|
| 199 |
, timeLinear |
|---|
| 200 |
, statsGraph @(gs, gr, gm) |
|---|
| 201 |
, timeGraph |
|---|
| 202 |
, statsIterative @(is, ir, im) |
|---|
| 203 |
, timeIterative) |
|---|
| 204 |
|
|---|
| 205 |
= padL 30 name |
|---|
| 206 |
++ " " ++ padL 20 (show statsLinear) |
|---|
| 207 |
++ " ~ " ++ padL 15 (show (ls + lc, lr, lm)) |
|---|
| 208 |
++ " " ++ padL 5 (fmtFloat timeLinear) |
|---|
| 209 |
|
|---|
| 210 |
++ " " ++ (score statsLinear statsGraph) |
|---|
| 211 |
++ " " ++ padL 15 (show statsGraph) |
|---|
| 212 |
++ " " ++ padL 5 (fmtFloat timeGraph) |
|---|
| 213 |
|
|---|
| 214 |
++ " " ++ (score statsLinear statsIterative) |
|---|
| 215 |
++ " " ++ padL 15 (show statsIterative) |
|---|
| 216 |
++ " " ++ padL 5 (fmtFloat timeIterative) |
|---|
| 217 |
++ "\n" |
|---|
| 218 |
|
|---|
| 219 |
fmtFloat :: Float -> String |
|---|
| 220 |
fmtFloat f |
|---|
| 221 |
= let str = show f |
|---|
| 222 |
first = takeWhile (/= '.') str |
|---|
| 223 |
rest = drop (length first) str |
|---|
| 224 |
in first ++ take 3 rest |
|---|
| 225 |
|
|---|
| 226 |
|
|---|
| 227 |
|
|---|
| 228 |
score :: StatsLinear -> StatsGraph -> String |
|---|
| 229 |
score (ls, lc, lr, _, _, lm) (gs, gr, gm) |
|---|
| 230 |
|
|---|
| 231 |
-- exact same score |
|---|
| 232 |
| ls + lc == gs |
|---|
| 233 |
, lr == gr |
|---|
| 234 |
, lm == gm |
|---|
| 235 |
= "-----" |
|---|
| 236 |
|
|---|
| 237 |
|
|---|
| 238 |
-- Outright success |
|---|
| 239 |
| gMem < lMem || gReg < lReg |
|---|
| 240 |
, gMem <= lMem |
|---|
| 241 |
|
|---|
| 242 |
-- allow ourselves 2 extra reg-reg moves for each mem op saved |
|---|
| 243 |
-- one to shift out and 1 to shift back in. |
|---|
| 244 |
, lessMem <- lMem - gMem |
|---|
| 245 |
, gReg <= (lReg + 2 * lessMem) |
|---|
| 246 |
= " WIN " |
|---|
| 247 |
|
|---|
| 248 |
-- same total spills/reloads/moves |
|---|
| 249 |
| gMem <= lMem |
|---|
| 250 |
, gReg <= lReg |
|---|
| 251 |
= " OK " |
|---|
| 252 |
|
|---|
| 253 |
-- no good |
|---|
| 254 |
| otherwise |
|---|
| 255 |
= "LOSE " |
|---|
| 256 |
|
|---|
| 257 |
where gMem = gs + gr |
|---|
| 258 |
gReg = gm |
|---|
| 259 |
|
|---|
| 260 |
lMem = ls + lc + lr |
|---|
| 261 |
lReg = lm |
|---|
| 262 |
|
|---|
| 263 |
|
|---|
| 264 |
padL n s |
|---|
| 265 |
= s ++ replicate (n - length s) ' ' |
|---|
| 266 |
|
|---|
| 267 |
|
|---|
| 268 |
getStatFiles :: IO [FilePath] |
|---|
| 269 |
getStatFiles |
|---|
| 270 |
= do files <- getDirectoryContents "." |
|---|
| 271 |
let statFiles = filter (match "dump-asm-stats") files |
|---|
| 272 |
return statFiles |
|---|
| 273 |
|
|---|
| 274 |
|
|---|
| 275 |
cleanDumps :: IO () |
|---|
| 276 |
cleanDumps |
|---|
| 277 |
= do files <- getDirectoryContents "." |
|---|
| 278 |
let junkFiles = filter (match "dump") files |
|---|
| 279 |
mapM_ removeFile junkFiles |
|---|
| 280 |
|
|---|
| 281 |
|
|---|
| 282 |
---- TestResult |
|---|
| 283 |
|
|---|
| 284 |
addTestResults :: TestResult -> TestResult -> TestResult |
|---|
| 285 |
addTestResults |
|---|
| 286 |
(n1, ls1, lt1, gs1, gt1, is1, it1) |
|---|
| 287 |
(n2, ls2, lt2, gs2, gt2, is2, it2) |
|---|
| 288 |
|
|---|
| 289 |
= ( n2 |
|---|
| 290 |
, addStatsLinear ls1 ls2, lt1 + lt2 |
|---|
| 291 |
, addStatsGraph gs1 gs2, gt1 + gt2 |
|---|
| 292 |
, addStatsGraph is1 is2, it1 + it2) |
|---|
| 293 |
|
|---|
| 294 |
|
|---|
| 295 |
zeroTestResult :: String -> TestResult |
|---|
| 296 |
zeroTestResult name |
|---|
| 297 |
= ( name |
|---|
| 298 |
, (0, 0, 0, 0, 0, 0) |
|---|
| 299 |
, 0 |
|---|
| 300 |
, (0, 0, 0) |
|---|
| 301 |
, 0 |
|---|
| 302 |
, (0, 0, 0) |
|---|
| 303 |
, 0) |
|---|
| 304 |
|
|---|
| 305 |
|
|---|
| 306 |
----- |
|---|
| 307 |
-- at the moment the spill stats are always on line 3.. |
|---|
| 308 |
-- spills, loads, moves |
|---|
| 309 |
slurpSpillStatsGraph :: FilePath -> IO StatsGraph |
|---|
| 310 |
slurpSpillStatsGraph fileName |
|---|
| 311 |
= do str <- readFile fileName |
|---|
| 312 |
return $ read $ lines str !! 2 |
|---|
| 313 |
|
|---|
| 314 |
addStatsGraph :: StatsGraph -> StatsGraph -> StatsGraph |
|---|
| 315 |
addStatsGraph (a1, a2, a3) (b1, b2, b3) |
|---|
| 316 |
= (a1 + b1, a2 + b2, a3 + b3) |
|---|
| 317 |
|
|---|
| 318 |
|
|---|
| 319 |
-- spills, clobbers, reloads, ? ?, moves |
|---|
| 320 |
slurpSpillStatsLinear :: FilePath -> IO StatsLinear |
|---|
| 321 |
slurpSpillStatsLinear fileName |
|---|
| 322 |
= do str <- readFile fileName |
|---|
| 323 |
return $ read $ lines str !! 2 |
|---|
| 324 |
|
|---|
| 325 |
addStatsLinear :: StatsLinear -> StatsLinear -> StatsLinear |
|---|
| 326 |
addStatsLinear (a1, a2, a3, a4, a5, a6) (b1, b2, b3, b4, b5, b6) |
|---|
| 327 |
= (a1 + b1, a2 + b2, a3 + b3, a4 + b4, a5 + b5, a6 + b6) |
|---|
| 328 |
|
|---|
| 329 |
|
|---|
| 330 |
slurpModuleName :: FilePath -> String |
|---|
| 331 |
slurpModuleName fileName |
|---|
| 332 |
= takeWhile (/= '.') fileName |
|---|
| 333 |
|
|---|
| 334 |
|
|---|
| 335 |
match :: String -> String -> Bool |
|---|
| 336 |
match regex str |
|---|
| 337 |
= isJust $ matchRegex (mkRegex regex) str |
|---|
| 338 |
|
|---|
| 339 |
|
|---|
| 340 |
|
|---|
| 341 |
|
|---|
| 342 |
--------------------------------------------------------------------------- |
|---|
| 343 |
-- Command handling |
|---|
| 344 |
-- |
|---|
| 345 |
|
|---|
| 346 |
-- scan the output for error messages |
|---|
| 347 |
-- we use this instead of just checking the return code because it |
|---|
| 348 |
-- gets clobbered by the call to tee when debugging is on. |
|---|
| 349 |
hasErrors :: String -> Bool |
|---|
| 350 |
hasErrors str |
|---|
| 351 |
| match "expected stdout not matched by reality" str |
|---|
| 352 |
= True |
|---|
| 353 |
|
|---|
| 354 |
| match "make .* Error" str |
|---|
| 355 |
= True |
|---|
| 356 |
|
|---|
| 357 |
| otherwise |
|---|
| 358 |
= False |
|---|
| 359 |
|
|---|
| 360 |
|
|---|
| 361 |
-- | Run a system command, peeling off stderr and stdout |
|---|
| 362 |
-- |
|---|
| 363 |
cmdOE :: String -- command to run |
|---|
| 364 |
-> IO (Maybe (String, String)) -- exitcode, stdout, stderr |
|---|
| 365 |
|
|---|
| 366 |
cmdOE str |
|---|
| 367 |
= do (code, out, err) <- cmdCode str |
|---|
| 368 |
|
|---|
| 369 |
case code of |
|---|
| 370 |
ExitSuccess -> return $ Just (out, err) |
|---|
| 371 |
ExitFailure n -> return Nothing |
|---|
| 372 |
|
|---|
| 373 |
|
|---|
| 374 |
-- | Run a command, expecting it to succeed |
|---|
| 375 |
cmd :: String -> IO () |
|---|
| 376 |
cmd str |
|---|
| 377 |
= do (code, out, err) <- cmdCode str |
|---|
| 378 |
|
|---|
| 379 |
if (hasErrors out || hasErrors err || code /= ExitSuccess) |
|---|
| 380 |
then error "checkSpills: command failed" |
|---|
| 381 |
else return () |
|---|
| 382 |
|
|---|
| 383 |
|
|---|
| 384 |
-- | Do a command |
|---|
| 385 |
cmdCode :: String -- command to run |
|---|
| 386 |
-> IO (ExitCode, String, String) -- exitcode, stdout, stderr |
|---|
| 387 |
|
|---|
| 388 |
cmdCode str |
|---|
| 389 |
= do when debug |
|---|
| 390 |
$ do putStr $ "DEBUG cmdCode: " ++ str ++ "\n" |
|---|
| 391 |
|
|---|
| 392 |
pid <- getProcessID |
|---|
| 393 |
let fileStdout = "/tmp/" ++ show pid ++ ".stdout" |
|---|
| 394 |
let fileStderr = "/tmp/" ++ show pid ++ ".stderr" |
|---|
| 395 |
|
|---|
| 396 |
code <- if debug |
|---|
| 397 |
then system (str ++ " | tee " ++ fileStdout ++ " 2>&1") |
|---|
| 398 |
else system (str ++ " | tee " ++ fileStdout ++ " > /dev/null") |
|---|
| 399 |
|
|---|
| 400 |
outStd <- readFile fileStdout |
|---|
| 401 |
-- outErr <- readFile fileStderr |
|---|
| 402 |
|
|---|
| 403 |
removeFile fileStdout |
|---|
| 404 |
-- removeFile fileStderr |
|---|
| 405 |
|
|---|
| 406 |
when debug |
|---|
| 407 |
$ putStr "\n" |
|---|
| 408 |
|
|---|
| 409 |
return (code, outStd, "") |
|---|
| 410 |
|
|---|
| 411 |
|
|---|
| 412 |
|
|---|
| 413 |
|
|---|