CompilerProbe.hs (6340B)
1 {-# LANGUAGE BangPatterns #-} 2 {-# OPTIONS_GHC -fno-full-laziness -fno-cse #-} 3 module CompilerProbe where 4 import Runtime.Tree 5 import Runtime.Value 6 import Control.Exception (evaluate) 7 import Control.Monad (forM_, unless) 8 import Data.Array (Array, listArray, (!), elems) 9 import Data.Bits ((.|.), shiftL) 10 import qualified Data.ByteString as BS 11 import qualified Data.Text as Text 12 import qualified Data.Text.Encoding as Text 13 import Data.Word (Word32) 14 import GHC.Clock (getMonotonicTimeNSec) 15 import GHC.Stats 16 import System.Environment (getArgs) 17 import System.Exit (die) 18 import System.Mem (performGC) 19 import Text.Printf (printf) 20 import Text.Read (readMaybe) 21 22 now :: IO Double 23 now = (/1e9) . fromIntegral <$> getMonotonicTimeNSec 24 utf8 :: FilePath -> IO String 25 utf8 path = do 26 b <- BS.readFile path 27 either (die . show) (pure . Text.unpack) (Text.decodeUtf8' b) 28 word :: BS.ByteString -> Int -> Int 29 word b at = fromIntegral (foldr (.|.) 0 [fromIntegral (BS.index b (at+j)) `shiftL` (8*j) | j<-[0..3]] :: Word32) 30 -- Bounds and backward references are checked before constructing the lazy array. 31 load :: FilePath -> IO (T,T) 32 load dir = do 33 (compiler, env, _) <- loadLibrary dir 34 pure (compiler, env) 35 36 loadLibrary :: FilePath -> IO (T,T,[(String,T)]) 37 loadLibrary dir = do 38 b <- BS.readFile (dir++"/compiler.dag") 39 unless (BS.length b >= 8) (die "truncated DAG") 40 let n=word b 0; root=word b 4 41 unless (n>0 && n<=1000000 && root>=0 && root<n && BS.length b==8+9*n) (die "invalid DAG header") 42 forM_ [0..n-1] $ \i -> do 43 let at=8+9*i; tag=BS.index b at; l=word b (at+1); r=word b (at+5) 44 prior x=x>=0 && x<i 45 unless (case tag of 0->l==0 && r==0; 1->prior l && r==0; 2->prior l && prior r; _->False) 46 (die "invalid DAG record") 47 let trees :: Array Int T 48 trees=listArray (0,n-1) [case BS.index b (8+9*i) of 49 0->Leaf; 1->Stem (trees ! word b (9+9*i)); _->Fork (trees ! word b (9+9*i)) (trees ! word b (13+9*i)) | i<-[0..n-1]] 50 -- Force every physical node and child pointer, never expand shared subtrees. 51 forM_ (elems trees) $ \t -> evaluate (case t of Leaf->(); Stem x->x `seq` (); Fork x y->x `seq` y `seq` ()) 52 bindings <- lines <$> utf8 (dir++"/environment.tsv") 53 env <- mapM (\line -> case words line of 54 [name,idx] | let i=read idx, i>=0 && i<n -> do 55 let key=ofString name 56 _<-evaluate (force key) 57 pure (Fork key (trees ! read idx)) 58 _ -> die "invalid environment binding") bindings 59 let environment=ofList env 60 _<-evaluate (forceSpine environment) 61 exported <- lines <$> utf8 (dir++"/compiler-exports.tsv") 62 library <- mapM (\line -> case words line of 63 [name,idx] | Just i <- readMaybe idx, i>=0 && i<n -> pure (name,trees!i) 64 _ -> die "invalid compiler export") exported 65 pure (trees!root, environment, library) 66 67 force :: T -> () 68 force Leaf=() 69 force (Stem x)=force x 70 force (Fork x y)=force x `seq` force y 71 forceSpine :: T -> () 72 forceSpine Leaf=() 73 forceSpine (Fork x xs)=x `seq` forceSpine xs 74 forceSpine _=error "invalid list" 75 76 pair :: T -> (T,T) 77 pair (Fork a b)=(a,b) 78 pair _=error "expected pair" 79 text :: T -> String 80 text=either error id . toString 81 nat :: T -> Integer 82 nat=either error id . toNumber 83 ternary :: T -> String 84 ternary t=go t [] where 85 go Leaf rest='0':rest 86 go (Stem x) rest='1':go x rest 87 go (Fork x y) rest='2':go x (go y rest) 88 fromTernary :: String -> T 89 fromTernary s=case parse s of (t,"")->t; _->error "trailing ternary" 90 parse :: String -> (T,String) 91 parse ('0':xs)=(Leaf,xs) 92 parse ('1':xs)=let (a,rest)=parse xs in (Stem a,rest) 93 parse ('2':xs)=let (a,rest)=parse xs; (b,end)=parse rest in (Fork a b,end) 94 parse _=error "invalid ternary" 95 96 diagnostic :: T -> String 97 diagnostic payload = 98 let (d,_)=pair payload; (code,tail1)=pair d; (expected,fields)=pair tail1 99 (_,locations)=pair fields; (location,_)=pair locations 100 whereAt=case location of Leaf->""; _->let (line,rest)=pair location; (column,_)=pair rest 101 in " at "++show (nat line)++":"++show (nat column) 102 c=nat code 103 label | c==2 = ": expected token kind "++show (nat expected) 104 | c==20 = ": unresolved name "++show (text expected) 105 | c>=30 = ": expected "++show (text expected) 106 | otherwise = "" 107 in "Compiler diagnostic "++show c++whereAt++label 108 109 definitions :: T -> Either String [(String,T)] 110 definitions result=case pair result of 111 (Leaf,payload)->Left (diagnostic payload) 112 (Stem Leaf,payload)->let (ds,_)=pair payload in Right 113 [(text name,program) | binding<-either error id (toList ds), let (name,program)=pair binding] 114 _->error "invalid compiler tag" 115 116 {-# OPAQUE compile #-} 117 compile :: T -> T -> T -> T 118 compile compiler env source=apply (apply compiler env) source 119 120 main :: IO () 121 main = do 122 args <- getArgs 123 (dir,caseDir)<-case args of [d,c]->pure(d,c); _->die "usage: compiler-probe DATA CASE" 124 start<-now 125 (compiler,env)<-load dir 126 source<-ofString <$> utf8 (caseDir++"/source") 127 _<-evaluate (force source) 128 expected<-lines <$> utf8 (caseDir++"/expected") 129 run<-lines <$> utf8 (caseDir++"/run") 130 loaded<-now 131 result<-evaluate (compile compiler env source) 132 whnf<-now 133 let defs=definitions result 134 -- Force export names/spine, not program trees: the site's selector boundary. 135 _<-evaluate (case defs of Left msg->length msg; Right ds->sum [length name | (name,_)<-ds]) 136 named<-now 137 valid<-evaluate $ case (expected,defs) of 138 (["error",message],Left actual)->message==actual 139 ("ok":rows,Right ds)->rows==[name++"\t"++ternary program | (name,program)<-ds] 140 _->False 141 unless valid (die "compiler golden mismatch") 142 complete<-now 143 case (defs,run) of 144 (Right ds,name:wanted:arguments)->case lookup name ds of 145 Just program->unless (ternary (foldl apply program (map fromTernary arguments))==wanted) (die "compiled program result mismatch") 146 Nothing->die "missing compiled export" 147 (Left _,[])->pure () 148 _->die "invalid run specification" 149 executed<-now 150 performGC 151 stats<-getRTSStats 152 printf "{\"load_s\":%.9f,\"whnf_s\":%.9f,\"names_s\":%.9f,\"complete_s\":%.9f,\"run_s\":%.9f,\"allocated_bytes\":%d,\"max_live_bytes\":%d,\"max_mem_in_use_bytes\":%d}\n" 153 (loaded-start) (whnf-loaded) (named-whnf) (complete-named) (executed-complete) 154 (allocated_bytes stats) (max_live_bytes stats) (max_mem_in_use_bytes stats)