LLVM编译器Kaleidoscope(万花筒)入门教学

LLVM学习笔记

LLVM是一款比较新的编译器工具。网上对LLVM使用的相关文档比较少,大部分都是官方给的例子。翻译过后晦涩难懂。在次,写一篇心得,也是为了课程任务,也是为了自己的一个学习总结,总结一下LLVM的学习过程,以及LLVM的万花筒实例。

首先和大家说一说LLVM是干什么的。LLVM是一个编译器。一个帮助你更简单写编译器的一个框架。在LLVM中有很多文件,这些文件我们都看不懂,也不用管,需要什么,找什么就可以。我们需要看的就是里面的万花筒的例子,在Examples文件里面的。

这个万花筒实现了一个简单的函数式语言,前面几个例子都不是完整的,从第四个开始,你可以用例子将他的语言完整的运行起来。

估计很多初学者和我一样,看着这个LLVM有点摸不到头脑,不知道如何去做,没关系,继续向下看,下面帮大家梳理的一下LLVM框架,通过例子,帮大家更好的理解。

在开始之前,我想先请大家想一想,一个完整的编译器都需要什么?词法分析,语法分析,语义分析,中间代码生成。(语法制导和优化在LLVM中几乎已经被做了,初学者也不用管他)

  1. 词法分析。在这部,你肯定要把一个一个的词读出来,看看是否和你规定的语言一样。(比如说,C语言,整数声明是 int a,你写一个in a 肯定是错的)在这里,你就要读取字符,和你自己规定的对比一下,看看是否是正确的。
  2. 语法分析,当你分析出输入的东西是你规定的时候,就要考虑语法的正确性,还是C语言的例子(int , 要求后边是一个变量的名字,你肯定不可以写int 3 ,这种就是错误的。)在这一步,你可以给所有的类型标记为1,所有字母标记为2,所有数字标记为3.当年你识别到 1(类型标记)之后,你应该继续识别到2才正确,3不可以在1 的后面。(例子可能不是那么严谨,但是对于理解来说是没问题的)
  3. 语义分析,基本上就是检测一些函数调用啥的合不合理。
  4. 中间代码生成LLVM都帮你做了,我们几乎不需要做啥,只需要你遵守LLVM的接口,用他的一些类,就可以了。

万花筒是LLVM里面提供的一个实例,完成了一个比较简单地语言。通过学习这个语言,可以增强对LLVM的理解。首先说一下LLVM安装。首先下载LLVM以及Clang,将LLVM转化成VS2017可执行版。在这里不再赘述,网上有很多安装教程。
LLVM编译器Kaleidoscope(万花筒)入门教学_第1张图片
首先生成一下解决方案。大概半个多小时吧。LLVM是真的大。话不多说,直接点开Example文件。里面的Kaleidoscope便是LLVM自带的简单语言的例子。
LLVM编译器Kaleidoscope(万花筒)入门教学_第2张图片

Kaleidoscope2

万花筒一里面几乎什么都没有。我们直接从第二个开始看起。第二个主要完成的是词法分析和语法分析。我们从main函数开始看起。

LLVM编译器Kaleidoscope(万花筒)入门教学_第3张图片

Main函数中主要包含3部分。符号优先级设定(那几个BinopPrecedence),getNextToken函数,MainLoop函数。关于符号的优先级,我们先不去管他,我们先开一下getToken函数。
在这里插入图片描述
返回的是一个CurTok值,好吧,我们进到里面继续看。(这个CurTok其实就是我说的那个1 2 3 的标记)
LLVM编译器Kaleidoscope(万花筒)入门教学_第4张图片

在gettok函数里,可以看到,首先使用一个while循环去除输入的空格和换行。然后分析是否是字母。当后面的字符是数字和字母的时候,将其加到IndentifierStr上。最后分析这个串是不是def 以及extern。其实这一步就是保存声明的变量或者函数名的关键字的。将以字母为首,后面是字母和数字的一个串提取,保存到一起。生成一个identifier,再与已有的关键字对比,检查是否是关键字。
LLVM编译器Kaleidoscope(万花筒)入门教学_第5张图片
继续向下看,是一个isdigit函数,是一个检查是否是数字的函数,后面还有小数点检查。检查当前输入是否是一个数字串。如果是数字,将其保存到NumVal并返回Tok_number代表当前输入是数字。
LLVM编译器Kaleidoscope(万花筒)入门教学_第6张图片
下面则是继续判断,如果不是字母,也不是数字,是不是终结符号或者“#”号。“#”在这里表示注释,他读取完当前这一行。大概就是这样一个内容。我们可以知道,这部分应该就是一个简单的词法分析。分析当前输入部分的类型,来返回不同的值,赋给Curtok这个静态变量。
LLVM编译器Kaleidoscope(万花筒)入门教学_第7张图片
我们继续看main函数。Main函数下面的是MainLoop函数。在MainLoop中使用到了词法分析中的CurTok,通过不同的CurTok进入不同的函数。当识别到eof时直接return,代表已经结束,函数返回。在为“;’”时,继续读取下一个关键字。当读到def时,代表读取到声明函数名字,下面要开始函数定义。关于extern函数拓展我们先不管他。先去def声明后看一看,看看是如何讲函数定义的。
LLVM编译器Kaleidoscope(万花筒)入门教学_第8张图片

继续进入ParseDefinition函数中。
LLVM编译器Kaleidoscope(万花筒)入门教学_第9张图片
在这个函数中,可以看到我们想要的东西。他生命的一个Proto的一个结构体。调用了ParsePrototype函数。我们先看看这个声明的PrototypeAST结构体中。
LLVM编译器Kaleidoscope(万花筒)入门教学_第10张图片
这个结构体,声明的一个名字,一个参数数组,就是一个用来存函数名字和参数的。那么ParseExpression函数就应该是读取函数的名字和参数名字的。我们进去确认一下。
LLVM编译器Kaleidoscope(万花筒)入门教学_第11张图片
如图所示,在这个函数中,首先判断CurTok是否为identifier,既当前字符串是否是一个以字母开头的字符串。如果是。将这个值赋给FnName即函数名字。后又判断函数名后是否是含有括号,如果没括号,报错,当有括号的时候,声明参数名的Vevtor容器,一个while循环,将所有参数如Vector。在这里,我们会有疑问,为什么没有参数类型。后来发现,这里面只有一个double类型,没有其他类型,所以默认类型为double,再添加其他类型时,应在这个while循环中添加一个参数的类型变量。之后识别“)”返回一个函数体。该函数结束,与我们预想的一样。
继续回到刚才的函数,下面判断返回函数是否为0.不是0。继续分析。又来了个ExprAST这个结构体。让我们再进去看看这个是什么。
LLVM编译器Kaleidoscope(万花筒)入门教学_第12张图片

这是个虚类,让我们看看他的实体类。
LLVM编译器Kaleidoscope(万花筒)入门教学_第13张图片
有一个NumberExprAST类,这个只有一个Val,应该是一个存数字的类。
LLVM编译器Kaleidoscope(万花筒)入门教学_第14张图片
还有变量类,放变量名字。还有一个二元表达式类。一个符号Op,还有左右节点也是ExprAST。
LLVM编译器Kaleidoscope(万花筒)入门教学_第15张图片
韩包含一个CallExprAST,这个就是一个调用类。返回上一步,这个ExprAST里面有数字,有变量,有二元表达式,有调用。我们进入函数看一下。
LLVM编译器Kaleidoscope(万花筒)入门教学_第16张图片
是一个LHS,二元表达式形式。再进入ParseExpression函数中
LLVM编译器Kaleidoscope(万花筒)入门教学_第17张图片
到这里,又是一连串。让我们想一想,定义一个函数,最开始是定义他的名字,然后又读取了他的形参。下一步是干什么?应该是函数的定义,函数里面的操作。他上面还有一个二元表达式,应该就是把操作串起来,串成一个二元表达式。最后把这个整体串起来的保存起来,就是一个完整的函数体。我们首先看这里面的switch。他有字母,数字,左括号三个种类。让我们先看看。字母的函数ParseIdentifierExpr。
LLVM编译器Kaleidoscope(万花筒)入门教学_第18张图片
如果后面没有做括号,返回变量名。想想也对,正常也不应该有括号,不就是一个变量名吗,也就是声明了一个变量呗。返回了一个variableExprAST,构成了之前函数的那部左子树,他返回了一个0,LHS。让我们再进入ParseBinOpRHS函数中看一看。
LLVM编译器Kaleidoscope(万花筒)入门教学_第19张图片
这里又是一片东西,慢慢看一看,首先是GetTOkPRecedence函数,点进去看一下。
LLVM编译器Kaleidoscope(万花筒)入门教学_第20张图片
这里面用上了BinopPrecedence函数,这个就是在Main函数里面声明的那个符号的优先级。在这个函数中,获取了优先级,别进行返回。可以看到,如果当前优先级,小于ExprPrec的有限级,返回左节点。否则继续运行。在下面有继续读取,读取右子树,重复了左边的内容,后又进行比较,最后生成了一个LHS总节点。此时,一个函数体就构成了。回到Function函数里面,直接return0;函数畸形返回,函数构造完成。

我们再重新回到MainLoop中来,看看HandleTolLevelExpression()函数。看看这个函数中的内容。
LLVM编译器Kaleidoscope(万花筒)入门教学_第21张图片
和解析函数体如此的相似,继续进入函数ParseTopLevelRxpr。
LLVM编译器Kaleidoscope(万花筒)入门教学_第22张图片
在这里面也调用了ParseExpression函数。在这里,官方教程说是调用函数,不过我看和声明函数差不多,看了好半天也没看明白,先不管他。

总结:在万花筒二中,实现了词法分析和语法分析,解析了一个函数,还用了大量的结构体,包括数字,变量等。代码比较易懂,在这里面学到了很多东西,加深了对编译器的理解。也增加了自己对代码结构的了解。提升了自己的代码能力。

Kaleidoscope3

看完了2,继续看三,LLVM IR生成。代码生成应该就是中间代码生成了吧,生成中间代码。最后再使用。LLVM在这里真的很强大,他给了你一个类似固定的模板,你把你的东西,放到它里面,然后他就给你生成了中间代码,你就可以实现跨平台的跑代码。(猜测,没有实践)。
我们来看看IR是如何生成的。
LLVM编译器Kaleidoscope(万花筒)入门教学_第23张图片
首先,它添加了好多codegen方法。
下面有这些codegen的方法定义。
LLVM编译器Kaleidoscope(万花筒)入门教学_第24张图片
首先是NumberExprAST,它使用了ConstantFP,后面还有APFloat,应该是吧Val转化了一种形式,存在ConstantFP里面。
LLVM编译器Kaleidoscope(万花筒)入门教学_第25张图片
关于变量的。通过变量名找变量值,并返回,使用了一个map容器。
LLVM编译器Kaleidoscope(万花筒)入门教学_第26张图片

在这里就清晰的很多,首先将左右节点都进行codegen,最后返回的不就是数字吗,就是那个变量或数字。也可能是表达式,不过最后返回的应该都是个结果。下面是看符号。加号就在Builder中调用加法函数,不同的符号调用不同的形式。
LLVM编译器Kaleidoscope(万花筒)入门教学_第27张图片
还有一个调用函数。这个函数从THeModel里面获取函数名,存到了一个Function类里面,这个类是LLVM自带的类,大概就是存函数的、下面还有args应该是形参、下面又进行了形参的构造,最后创建了一个调用,放到builder里面。应该是形参赋值后调用的作用。
LLVM编译器Kaleidoscope(万花筒)入门教学_第28张图片
函数体的codegen,这个没有什么东西,就是使用了一个Creat函数创建了一个函数。
LLVM编译器Kaleidoscope(万花筒)入门教学_第29张图片
最后又有个function的构造,这里面有BasicBlock,还有函数体的构造,函数的构造,这个应该是将函数名与函数体和在一个,构造一个完整的函数。

总结:在IR生成这部分,将我们的东西换成了LLVM的东西,相当于一个桥梁,使用了ConstantFP存数值,Function类存函数起了一个过渡的作用。

Kaleidoscope4

网上的中文教程就到了第四节,在四里面,函数就可以调用了,添加了JIT支持,我还没时间理解这个JIT支持是什么意思,知道这个位置的效果,函数可以调用了,可以有结果了。快到期末考试了,也没时间继续往下看了。
LLVM编译器Kaleidoscope(万花筒)入门教学_第30张图片
就是将TheModule函数添加到JIT里面。

并且在里一个地方使用find来达到函数调用的目的。

你可能感兴趣的:(编程语言,编辑器,llvm,编译器)