【问题标题】:Code generation for compiler in HaskellHaskell 中编译器的代码生成
【发布时间】:2011-06-11 05:20:52
【问题描述】:

我正在为一种小型命令式语言编写编译器。目标语言为Java字节码,编译器用Haskell实现。

我已经为该语言编写了一个前端 - 即我有一个词法分析器、解析器和类型检查器。我无法弄清楚如何进行代码生成。

我保留了一个表示局部变量堆栈的数据结构。我可以使用局部变量的名称查询这个结构并获取它在堆栈中的位置。这个数据结构在我遍历语法树时被传递,变量在我进入和退出新范围时被弹出和推送。

我无法弄清楚如何发出字节码。在终端发出字符串并在更高级别连接它们似乎是一个糟糕的解决方案,无论是在清晰度还是性能方面。

tl;dr如何在遍历语法树时发出字节码?

【问题讨论】:

  • 这不值得一个完整的答案,并且显然涉及一种非常不同的语言风格,但是您可以查看a compiler written in Haskell you may be familiar with 的源代码以获得灵感。
  • 您的中间表示是否一对一映射到 jvm 操作码?如果不是,那么这是一个开始的地方:创建一个或多个数据类型来表示您所针对的 JVM 操作码(的子集)。然后遍历您的更高级别的 IR 并创建以 JVM 为中心的低级别 IR。

标签: haskell code-generation compiler-construction


【解决方案1】:

几个月前我在 Haskell 的第一个项目是编写一个 c 编译器,结果是一种相当幼稚的代码生成方法,我将在此进行介绍。请不要将此作为代码生成器的良好设计示例,而应将其视为一种快速而肮脏(最终是幼稚)的方法,以获得相当快速且性能良好的东西。

我首先定义了一个中间表示 LIR(下中间表示),它与我的指令集(在我的例子中为 x86_64)密切对应:

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)

接下来出现了一个 monad,它将在整个翻译过程中处理交错状态(当时我很高兴地不知道我们的朋友 - State Monad-):

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')

以及将通过各种翻译阶段“线程化”的状态:

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)
    }

为了方便,我还指定了一系列翻译器单子函数,例如:

-- |Increment our translator's label counter
incLabel :: LIRTranslator Int
incLabel = LIRTranslator (\ns@(Namespace{ labels = l }) -> (l, ns{ labels = (l+1) }))

然后我开始递归地模式匹配我的AST,逐个片段,产生许多形式的函数:

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))

(用于翻译目标语言的代码块)或

-- | Given a SymbolTree, Translate a single DecafMethodStm into [LIRInst]
translateStm st (DecafMethodStm mc _) =
    do (instructions, operand) <- translateMethodCall st mc
       final <- motionCode instructions
       return final

(用于翻译方法调用)或

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

实际生成一些 LIR 代码的示例。希望这三个例子能给你一个好的起点;最终,您需要慢慢来,一次只关注 AST 中的一个片段(或中间类型)。

【讨论】:

    【解决方案2】:

    如果您以前没有这样做过,您可以分小批进行: 1)为每条语句产生一些字节码(没有正确寻址的内存位置) 2) 完成后,如果你有循环、goto 等,请输入真实地址(你知道它们 现在你已经把一切都安排好了) 3)用正确的位置替换内存获取/存储 4) 将其转储到 JAR 文件中

    请注意,这是非常简化的,不会尝试进行任何性能优化。它将为您提供一个可以执行的功能程序。这还假设您知道 JVM 的代码(我假设您将在其中执行它。)

    首先,只需拥有执行顺序算术语句的语言子集。这将允许您弄清楚如何通过解析树将变量内存位置映射到语句。接下来添加一些循环以使跳转起作用。同样添加条件。最后,您可以添加语言的最后部分。

    【讨论】:

    • 只是一个问题:您从哪里得到 JVM 假设? (其他人也假设了,我这辈子都找不到)
    • @mathepic:问题的第二句,“目标语言是 Java 字节码 (...)”。
    • @camccann 啊,好的。我一定是盲人:D
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-22
    • 2014-06-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2015-07-05
    • 2015-05-30
    相关资源
    最近更新 更多