教你使用swift寫編譯器玩具(3)

前言

本章對應官方教程第3章,本章介紹如何將抽象語法樹(AST)轉換爲中間代碼(LLVM IR)。html

教程以下:git

教你使用swift寫編譯器玩具(0)github

教你使用swift寫編譯器玩具(1)express

教你使用swift寫編譯器玩具(2)swift

教你使用swift寫編譯器玩具(3)數組

教你使用swift寫編譯器玩具(4)bash

教你使用swift寫編譯器玩具(5)數據結構

教你使用swift寫編譯器玩具(6)app

教你使用swift寫編譯器玩具(7)函數

教你使用swift寫編譯器玩具(8)

倉庫在這

開始

在生成IR開始以前咱們須要爲爲ExprAST協議定一個codeGen方法,並返回LLVM的IRValue對象。這個方法表示該AST所表示的IR。

protocol ExprAST {
    
    func codeGen() -> IRValue?
    
}
複製代碼

接着咱們建立Module對象theModule,它是一個包含函數和全局變量的LLVM數據結構,它擁有咱們生成的因此IR的內存。

var theModule: Module! = Module(name: "main")
複製代碼

接着咱們建立IRBuilder對象builder,它能夠用來生成LLVM指令。

let builder = IRBuilder(module: theModule)
複製代碼

定義代碼符號表namedValues,表中的內容爲當前範圍內的值以及他們的IRValue。在Kaleidoscope中,它會在函數體生成的時候用到。

var namedValues: [String: IRValue] = [:]
複製代碼

表達式代碼生成

首先咱們先從最簡單的寫起,那就是NumberExprAST

func codeGen() -> IRValue? {
        return FloatType.double.constant(value)
    }
複製代碼

這段代碼把swift的Double表示轉化爲了LLVM IR的Double表示。

變量的codeGen也十分簡單

func codeGen() -> IRValue? {
        let value = namedValues[name]
        guard value != nil else {
            fatalError("unknow variable name.")
        }
				return value!.asLLVM()
    }
複製代碼

實際上目前namedValues中的內容只惟一有函數參數的變量。因此在返回值以前先要檢查一下是否以及是被解析爲函數的參數了。

二元運算符的代碼生成思路是遞歸的生成左側的IRValue以及右側的IRValue

func codeGen() -> IRValue? {
        let l = lhs!.codeGen()
        let r = rhs!.codeGen()
        guard l != nil && r != nil else {
            return nil
        }
        switch op! {
        case "+":
            return builder.buildAdd(l!, r!, name: "add")
        case "-":
            return builder.buildSub(l!, r!, name: "sub")
        case "*":
            return builder.buildMul(l!, r!, name: "mul")
        case "<":
            return builder.buildFCmp(l!, r!, .unorderedLessThan, name: "boolCmp")
        default:
            fatalError("Invalid binary operator.")
        }
    }
複製代碼

在上面的代碼中,builder是知道在哪裏插入指令,因此咱們須要作的僅僅只是指定使用哪一個指令而已。好比說buildAdd或者buildFCmp

LLVM指令有很嚴格的約束,好比說buildAdd指令的的左右兩側都必須是同一類型,可是在Kaleidoscope中咱們只支持了Double類型,因此不是很須要操心。

另外,符號"<"對應的指令buildFCmp始終返回i1類型,在這裏咱們與官方教程不同的一點就是咱們並不須要操心這個類型不是Double類型,咱們只須要返回出去便可。

接着咱們來實現函數調用的codeGen

func codeGen() -> IRValue? {
        let calleeF = theModule.function(named: callee!)
        guard calleeF != nil else {
            return nil
        }
        if calleeF!.parameterCount != args!.count {
            fatalError("Incorrect arguments passed.")
        }
        var argsV: [IRValue] = []
        for arg in args! {
            if let gen = arg.codeGen() {
                argsV.append(gen)
            } else {
                return nil
            }
        }
        return builder.buildCall(calleeF!, args: argsV, name: "call")
    }
複製代碼

咱們只須要在LLVM Module中查找函數名並設置參數便可生成函數調用的IRValue

功能代碼生成

原型和函數的codeGen比較複雜一些。值得注意的是原型和函數的codeGen方法的返回類型並非IRValue類型而是Function類型。

func codeGen() -> Function {
        let doubles = Array(repeating: FloatType.double, count: args.count)
        let ft = FunctionType(doubles, FloatType.double, variadic: false)
        var f: Function = theModule.addFunction(name, type: ft)
        //這實際上是默認linkage,這裏爲了和官方教程保持一致,顯示的寫一下
        f.linkage = .external
        //設置參數名
        var p = f.firstParameter
        for i in 0..<args.count {
            p?.name = args[i]
            p = p?.next()
        }
        return f
    }
複製代碼

FunctionType第一個參數爲一個類型的數組表明這個函數每一個參數的類型,第二個參數爲函數返回值的類型,第三個參數的含義爲是否爲可變變量,這裏設置爲false。

接着咱們要在FunctionASTcodeGen中定義函數體。

func codeGen() -> Function? {
        functionProtos[proto!.name!] = proto
        let theFunction = getFunction(named: proto!.name!)
        guard theFunction != nil else {
            return nil
        }
        
      	//建立一個基本塊(BasicBlock)
        let entry = theFunction!.appendBasicBlock(named: "entry")
      	//這行代碼告訴bulder應該把新的指令插在基本塊的末尾,你能夠理解爲插在名爲entry的這個基本塊裏
        builder.positionAtEnd(of: entry)
        
      	//咱們把函數參數添加到namedValues中以便VariableExprAST能夠訪問到
        namedValues.removeAll()
        var p = theFunction!.firstParameter
        while p != nil {
            namedValues[p!.name] = p!
            p = p?.next()
        }
        
      	//解析函數體對應的IRValue
        if let retValue = body!.codeGen() {
            builder.buildRet(retValue)
            do {
              	//驗證函數,這個方法能夠檢查出函數生成IR是否出現問題。
                try theModule.verify()
                return theFunction
            } catch {
                print("verify failure: \(error)")
            }
        }
        //函數體出現問題,移除函數
        theFunction!.eraseFromParent()
        return nil
    }
複製代碼

函數體解析過程當中的要點都在註釋中體現了,下面咱們可使用Function對象的dump()方法打印出函數的IR。

咱們在Parser中實現Lexer的代理LexerDelegate

extension Parser: LexerDelegate {
    
    func lexerWithDefinition(_ lexer: Lexer) {
        if let p = parseDefinition() {
            if let f = p.codeGen() {
                print("Read function definition:")
                f.dump()
            }
        } else {
            lexer.nextToken()
        }
    }
    
    func lexerWithExtern(_ lexer: Lexer) {
        let p = parseExtern()
        let f = p.codeGen()
        f.dump()
        functionProtos[p.name] = p
    }
    
    func lexerWithTopLevelExpression(_ lexer: Lexer) {
        if let p = parseTopLevelExpr() {
            if let f = p.codeGen() {
                print("Read top-level expression:")
                f.dump()
            }
        } else {
            lexer.nextToken()
        }
    }
    
}
複製代碼

如今就能夠打印出IR了。

咱們將在下一章實現IR的優化以及對JIT的支持。

測試

咱們編寫一個擴展類型爲.k的文件

def foo(a b) a*a + 2*a*b + b*b;
複製代碼
Read function definition:

define i64 @foo(i64 %a, i64 %b) {
entry:
  %mul = mul i64 %a, %a
  %mul1 = mul i64 2, %a
  %mul2 = mul i64 %mul1, %b
  %add = add i64 %mul, %mul2
  %mul3 = mul i64 %b, %b
  %add4 = add i64 %add, %mul3
  ret i64 %add4
}
複製代碼

請忽略我生成的IR中值的type是i64。由於我在最開始實現時並無按照教程說的使用Double類型。

相關文章
相關標籤/搜索