| 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 | |
|---|