Mi primer proyecto en Haskell hace unos meses era escribir un compilador C, y lo que resultó fue un enfoque bastante ingenuo como para la generación de código, lo que voy a caminar por aquí. Por favor, haga no toman esto como un ejemplo de buen diseño para un generador de código, sino más bien lo ven como una manera rápida y sucia (y en última instancia ingenuo) para conseguir algo que funciona bastante rápidamente con un rendimiento decente.
empecé mediante la definición de una representación intermedia LIR (Intermedio Bajo Representación), que estrechamente correspondió al conjunto de instrucciones (x86_64 en mi caso):
data LIRInst = LIRRegAssignInst LIRReg LIRExpr
| LIRRegOffAssignInst LIRReg LIRReg LIRSize LIROperand
| LIRStoreInst LIRMemAddr LIROperand
| LIRLoadInst LIRReg LIRMemAddr
| LIREnterInst LIRInt
| LIRJumpLabelInst LIRLabel
| LIRIfInst LIRRelExpr LIRLabel LIRLabel -- false, then true
| LIRCallInst LIRLabel LIRLabel -- method label, return label
| LIRCalloutInst String
| LIRRetInst [LIRLabel] String -- list of successors, and the name of the method returning from
| LIRLabelInst LIRLabel
deriving (Show, Eq, Typeable)
Después vino una mónada que se ocuparía de intercalado de Estado en todo el traducción (yo estaba felizmente inconscientes de nuestro amigo el State Monad
-en el tiempo):
newtype LIRTranslator a = LIRTranslator
{ runLIR :: Namespace -> (a, Namespace) }
instance Monad LIRTranslator where
return a = LIRTranslator (\s -> (a, s))
m >>= f = LIRTranslator (\s ->
let (a, s') = runLIR m s
in runLIR (f a) s')
junto con el estado que serían 'rosca' a través de las distintas fases de traducción:
data Namespace = Namespace
{ temp :: Int -- id's for new temporaries
, labels :: Int -- id's for new labels
, scope :: [(LIRLabel, LIRLabel)] -- current program scope
, encMethod :: String -- current enclosing method
, blockindex :: [Int] -- index into the SymbolTree
, successorMap :: Map.Map String [LIRLabel]
, ivarStack :: [(LIRReg, [CFGInst])] -- stack of ivars (see motioned code)
}
Para mayor comodidad, también especifica una serie de funciones monádicos traductor, por ejemplo:
-- |Increment our translator's label counter
incLabel :: LIRTranslator Int
incLabel = LIRTranslator (\[email protected](Namespace{ labels = l }) -> (l, ns{ labels = (l+1) }))
Entonces procedí a recursivamente patrón-fósforo mi AST, fragmento por fragmento, resultando en muchos funciones de la forma:
translateBlock :: SymbolTree -> ASTBlock -> LIRTranslator [LIRInst]
translateBlock st (DecafBlock _ [] _) = withBlock (return [])
translateBlock st block =
withBlock (do b <- getBlock
let st' = select b st
declarations <- mapM (translateVarDeclaration st') (blockVars block)
statements <- mapM (translateStm st') (blockStms block)
return (concat declarations ++ concat statements))
(para la traducción de un bloque de código de la lengua de destino) o
-- | Given a SymbolTree, Translate a single DecafMethodStm into [LIRInst]
translateStm st (DecafMethodStm mc _) =
do (instructions, operand) <- translateMethodCall st mc
final <- motionCode instructions
return final
(para la traducción de una llamada de método) o
translateMethodPrologue :: SymbolTree -> DecafMethod -> LIRTranslator [LIRInst]
translateMethodPrologue st (DecafMethod _ ident args _ _) =
do let numRegVars = min (length args) 6
regvars = map genRegVar (zip [LRDI, LRSI, LRDX, LRCX, LR8, LR9] args)
stackvars <- mapM genStackVar (zip [1..] (drop numRegVars args))
return (regvars ++ stackvars)
where
genRegVar (reg, arg) =
LIRRegAssignInst (symVar arg st) (LIROperExpr $ LIRRegOperand reg)
genStackVar (index, arg) =
do let mem = LIRMemAddr LRBP Nothing ((index + 1) * 8) qword --^[rbp] = old rbp; [rbp + 8] = ret address; [rbp + 16] = first stack param
return $ LIRLoadInst (symVar arg st) mem
para un ejemplo de generar realmente algún código LIR. Espero que estos tres ejemplos te den un buen punto de partida; en última instancia, querrás ir despacio, centrándote en un fragmento (o tipo intermedio) dentro de tu AST a la vez.
Esto no vale una respuesta completa, y obviamente implica un estilo de lenguaje muy diferente, pero hay [un compilador escrito en Haskell con el que puede estar familiarizado] (http://hackage.haskell.org/trac/ ghc/wiki/Commentary/Compiler/Backends) cuyo código fuente puede buscar como inspiración. –
¿Representa su representación intermedia uno a uno para los códigos de operación jvm? De lo contrario, ese es un lugar para comenzar: cree uno o más tipos de datos para representar (un subconjunto de) los códigos de operación de JVM a los que se dirige. Luego recorra su IR de nivel superior y cree el IR de bajo nivel centrado en JVM. – Lambdageek