{- --------------------------------------------------------------------------- Mini-interpreter for pretty-printing bytecodes into C array declarations -} module EmitState where import Char (isLower) import GcodeLow (foreignfun) import List (nub,isPrefixOf) import Tree234 -- , {-type-} EmitState -- , emitState -- , startEmitState -- accumulators: -- (1) current absolute word offset -- (2) current relative byte offset -- (3) current incomplete word -- (4) label defns -- (5) bytecode in C -- This module, as originally written, had 2 enormous space leaks. -- 1. The EmitState was originally built in a single pass, but we used -- one part of it early, and another part late. The late part hung -- around as big closures for a long time. So, we separated out the -- two phases into Labels (early) and Code (late). The Code closures -- are now very much smaller. -- 2. When the Code closure was finally evaluated, it spiked, because -- the large number of small closures inside it were all being built -- suddenly before any of them were actually evaluated. These closures -- were compositions of 'shows'. We now keep a list of Strings instead, -- (in reverse order) and 'concat' them afterwards. It still spikes, -- but the spike is about half the size. infixl >|> data EmitState = ES !Int !Int Incomplete [Label] [String] type Incomplete = (String,String,String,String) data Label = Define GL String Int | Use String Int data GL = Global | Local data Pass = Labels | Code -- to avoid space leaks, we build the deriving Eq -- state value in two passes instance Eq Label where -- only used to eliminate duplicate extern decls, so not real equality. (Use s _) == (Use t _) = s==t _ == _ = error "Misuse of equality in compiler/EmitState" eszero = "0" empty :: Incomplete empty = (eszero,eszero,eszero,eszero) first :: String -> Incomplete first x = (x,eszero,eszero,eszero) preSym :: String preSym = "startLabel" startEmitState :: Pass -> EmitState startEmitState Labels = ES 0 0 empty [] [] startEmitState Code = ES 0 0 empty [] [begin] where begin = "\nstatic Node " ++ preSym ++ "[] = {\n " emitByte :: Pass -> String -> EmitState -> EmitState emitByte Labels a (ES n 0 word labs code) = ES n 1 word labs code emitByte Labels a (ES n 1 word labs code) = ES n 2 word labs code emitByte Labels a (ES n 2 word labs code) = ES n 3 word labs code emitByte Labels a (ES n 3 word labs code) = ES n 4 word labs code emitByte Labels a (ES n 4 word labs code) = ES (n+1) 1 word labs code emitByte Code a (ES n 0 word labs code) = ES n 1 (first a) labs code emitByte Code a (ES n 1 (w,x,y,z) labs code) = ES n 2 (w,a,y,z) labs code emitByte Code a (ES n 2 (w,x,y,z) labs code) = ES n 3 (w,x,a,z) labs code emitByte Code a (ES n 3 (w,x,y,z) labs code) = ES n 4 (w,x,y,a) labs code emitByte Code a (ES n 4 word labs code) = ES (n+1) 1 (first a) labs (outBytes word code) emitWord :: Pass -> String -> EmitState -> EmitState emitWord Labels a (ES n 0 word labs code) = ES (n+1) 0 empty labs code emitWord Code a (ES n 0 word labs code) = ES (n+1) 0 empty labs (outWord a code) emitWord Labels a (ES n b word labs code) = ES (n+2) 0 empty labs code emitWord Code a (ES n b word labs code) = ES (n+2) 0 empty labs (outWord a (outBytes word code)) emitString :: Pass -> String -> EmitState -> EmitState emitString pass = foldr (>|>) (emitByte pass ("0")) . map (emitByte pass.show.fromEnum) emitAlign :: Pass -> EmitState -> EmitState emitAlign _ es@(ES n 0 word labs code) = es emitAlign Labels (ES n b word labs code) = ES (n+1) 0 empty labs code emitAlign Code (ES n b word labs code) = ES (n+1) 0 empty labs (outBytes word code) emitAlignDouble :: Pass -> EmitState -> EmitState emitAlignDouble pass es@(ES n 0 word labs code) | n `div` 2 == 0 = es | otherwise && pass==Labels = ES (n+1) 0 word labs code | otherwise && pass==Code = ES (n+1) 0 word labs (outBytes empty code) emitAlignDouble Labels (ES n b word labs code) = emitAlignDouble Labels (ES (n+1) 0 empty labs code) emitAlignDouble Code (ES n b word labs code) = emitAlignDouble Code (ES (n+1) 0 empty labs (outBytes word code)) defineLabel :: Pass -> GL -> String -> EmitState -> EmitState defineLabel Labels Local sym (ES n b word labs code) = ES n b word (Define Local (sym) (n*4+b): labs) code defineLabel Code Local sym (ES n b word labs code) = ES n b word labs (comment:code) where comment = "\t/* " ++ sym ++ ": (byte " ++ show b ++ ") */\n " defineLabel Labels Global sym (ES n 0 word labs code) = ES n 0 word (Define Global (sym) (n*4): labs) code defineLabel Code Global sym (ES n 0 word labs code) = ES n 0 word labs (newlab:code) where newlab = "};\nNode " ++ sym ++ "[] = {\n " defineLabel pass Global ss es = defineLabel pass Global ss (emitAlign pass es) useLabel :: Pass -> String -> EmitState -> EmitState useLabel Labels sym (ES n b word labs code) = emitWord Labels (wrapUse sym) (ES n b word (Use (sym) (n*4+b): labs) code) useLabel Code sym es@(ES n b word labs code) = emitWord Code (wrapUse sym) es mentionLabel :: Pass -> String -> EmitState -> EmitState mentionLabel Labels sym (ES n b word labs code) = ES n b word (Use (sym) (n*4+b): labs) code mentionLabel Code sym es@(ES n b word labs code) = es wrapUse :: String -> String wrapUse sym = "useLabel(" ++ sym ++ ")" outBytes :: Incomplete -> [String] -> [String] outBytes (w,x,y,z) code = four:code where four = " bytes2word(" ++ w ++ ',' : x ++ ',' : y ++ ',' : z ++ ")\n," outWord :: String -> [String] -> [String] outWord x code = wx:code where wx = ' ': x ++ "\n," (>|>) :: (a->a) -> (a->a) -> (a->a) left >|> right = right . left emit :: Pass -> EmitState -> [String] emit Labels (ES _ _ _ rlabs _) = let labs = reverse rlabs locals = filter isLocal labs uses = filter isUse labs externs = nub $ filter (not . definedLabel) uses defines = treeFromList min cmp (map defAt (filter isDefine labs)) where cmp (k, _) (k', _) = compare k k' defAt (Define Local sym _) = (sym, minBound) -- Before any use defAt (Define Global sym def) = (sym, def) definedLabel (Use s u) = treeSearch False (\(_, d) -> u >= d) cmp defines where cmp (k, _) = compare s k isLocal (Define Local _ _) = True isLocal _ = False isDefine (Define _ _ _) = True isDefine _ = False isUse (Use _ _) = True isUse _ = False doLocal (Define Local sym def) = showString "#define " . showString sym . showString "\t((void*)" . showString preSym . showChar '+' . shows def . showString ")\n" doExtern (Use sym _) -- This is a dreadful hack for distinguishing primitives from bytecode! | isLower (head sym) = showString "extern void *" . showString sym . showString "();\n" -- It is somewhat easier to distinguish foreign imports. | foreignfun `isPrefixOf` sym = showString "void " . showString sym . showString "(void);\n" -- If nothing else, it must be bytecode. | otherwise = showString "extern Node " . showString sym . showString "[];\n" in map ($"") (map doLocal locals ++ map doExtern externs) emit Code es = let (ES _ _ _ _ code) = emitAlign Code es in reverse ("};\n":code) {- End EmitState -------------------------------------------------------------}