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

File checkSpills.hs, 10.2 KB (added by guest, 4 years 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--     
17import System
18import System.Directory
19import System.Posix.Process
20import Text.Regex
21import Control.Monad
22
23import Data.Maybe
24import Data.List
25
26----------
27debug   = True
28
29-----------
30-- | spills, clobbers, reloads, joins reg-reg, joins reg-mem, reg-reg-moves remaining in code
31type StatsLinear        = (Int, Int, Int, Int, Int, Int)
32
33-- | spills, reloads, reg-reg-moves remaining in code
34type StatsGraph         = (Int, Int, Int)
35
36-- | test results for one module
37type 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-----
47main :: IO ()
48main
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--     
77doTest  :: 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
82doTest 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.
115collateResults
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
124collateResults 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
133doTestRun 
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
139doTestRun  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
165slurpCompileTimes
166        :: [String] -> [(String, Float)]
167       
168slurpCompileTimes (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
178slurpCompileTimes _     = []
179
180       
181makeTotal :: [(FilePath, [TestResult])] -> TestResult
182makeTotal ts
183 = let  results         = concat $ map snd ts
184        total           = foldr addTestResults (zeroTestResult "TOTAL") results
185   in   total
186   
187
188makeSection :: (FilePath, [TestResult]) -> String
189makeSection (testName, stats)
190        = "* " ++ testName ++ "\n"
191        ++ (concat $ map makeReportLine stats)
192        ++ "\n"
193
194
195makeReportLine :: TestResult -> String
196makeReportLine
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
219fmtFloat :: Float -> String
220fmtFloat 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
228score :: StatsLinear -> StatsGraph -> String
229score (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
264padL n s
265        = s ++ replicate (n - length s) ' '
266
267
268getStatFiles :: IO [FilePath]
269getStatFiles
270 = do   files   <- getDirectoryContents "."
271        let statFiles   = filter (match "dump-asm-stats") files
272        return statFiles
273       
274       
275cleanDumps :: IO ()
276cleanDumps
277 = do   files   <- getDirectoryContents "."
278        let junkFiles   = filter (match "dump") files
279        mapM_ removeFile junkFiles
280
281
282---- TestResult
283
284addTestResults :: TestResult -> TestResult -> TestResult
285addTestResults 
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
295zeroTestResult :: String -> TestResult
296zeroTestResult 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
309slurpSpillStatsGraph    :: FilePath -> IO StatsGraph
310slurpSpillStatsGraph fileName
311 = do   str     <- readFile fileName
312        return  $ read $ lines str !! 2
313       
314addStatsGraph :: StatsGraph -> StatsGraph -> StatsGraph
315addStatsGraph (a1, a2, a3) (b1, b2, b3)
316        = (a1 + b1, a2 + b2, a3 + b3)
317
318
319-- spills, clobbers, reloads, ? ?, moves
320slurpSpillStatsLinear :: FilePath -> IO StatsLinear
321slurpSpillStatsLinear fileName
322 = do   str     <- readFile fileName
323        return  $ read $ lines str !! 2
324
325addStatsLinear :: StatsLinear -> StatsLinear -> StatsLinear
326addStatsLinear (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
330slurpModuleName :: FilePath -> String
331slurpModuleName fileName
332 = takeWhile (/= '.') fileName
333
334
335match :: String -> String -> Bool
336match 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.
349hasErrors :: String -> Bool
350hasErrors 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--
363cmdOE   :: String                       -- command to run
364        -> IO (Maybe (String, String))  -- exitcode, stdout, stderr
365
366cmdOE 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
375cmd     :: String -> IO ()
376cmd 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
385cmdCode :: String                               -- command to run
386        -> IO (ExitCode, String, String)        -- exitcode, stdout, stderr
387
388cmdCode 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