Commentary/Compiler/Backends/NCG/RegisterAllocator: checkSpills.hs

File checkSpills.hs, 10.2 kB (added by guest, 1 year ago)

script to help test allocator performance over nofib suite

Line 
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