浅谈LLVM

2021-06-24  本文已影响0人  住册新账号

何为LLVM

在LLVM的官网(https://llvm.org/)中写到The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.翻译过来的意思是“LLVM项目是模块化、可重用的编译器以及工具链技术的集合。”
在理解LLVM时,我们可以认为它包括了一个广义的LLVM和一个狭义的LLVM。广义的LLVM就是指整个LLVM编译器架构,包括了前端、后端、优化器、众多的库函数以及很多的模块;而狭义的LLVM其实就是聚焦于编译器后端功能(代码生成、代码优化、JIT等)的一系列模块和库。

LLVM背景

目前常见的编译器有以下两种:

LLVM的作者是Chris Lattner,此人既是LLVM之父也是Swift之父同时还是Clang的主要贡献者。在使用LLVM之前,Apple公司一直使用GCC作为编译器,但是Apple对Objective-C新增的特性,GCC并未配合给予实现,Apple自己开发的GCC模块又很难得到GCC委员会的合并,因此Apple在Chris Lattner毕业时,把他招入靡下开发自己的编译器架构,即LLVM。

Xcode版本 应用编译器
<Xcode3 GCC
Xcode3 GCC+LLVM
Xcode4.2 默认LLVM-Clang
>Xcode5 废弃GCC

传统的编译器架构和LLVM架构

传统编译器架构.jpg
LLVM架构.jpg

Clang和LLVM

LLVM-Clang.jpg
因为LLVM只是一个编译器框架,所以还需要一个前端来支撑整个系统,所以Apple又拨款拨人一起研发了Clang(http://clang.llvm.org/),作为整个编译器的前端,Clang是LLVM项目的一个子项目,用来编译C、C++和Objective-C。
相比于GCC,Clang具有如下优点:

OC源文件的编译过程

对于iOS开发者来说,整个编译流程可以简要概括为 Clang对代码进行处理形成中间层作为输出,llvm把CLang的输出作为输入生成机器码
Clang的执行过程包含以下几步:

#import <stdio.h>
#define height 5

int main(int argc, const char * argv[]) {
    
    int a = 2;
    int b = 3;
    int c = a + b + height;
    
    return 0;
}

词法分析

void test(int a, int b) {
    int c = a + b - 3;
}

语法分析

LLVM IR

LLVM IR有三种表示,一种是便于阅读的文本格式,类似于汇编代码,但其实它介于高等语言和汇编之间,这种表示就是给人看的,磁盘文件后缀为.ll;第二种是不可读的二进制IR,被称作位码(bitcode),磁盘文件后缀为.bc;第三种表示是一种内存格式,只保存在内存中,所以谈不上文件格式和文件后缀,这种格式是LLVM之所以编译快的一个原因,它不像gcc,每个阶段结束会生成一些中间过程文件,它编译的中间数据都是这第三种表示的IR。三种格式是完全等价的,我们可以在Clang/LLVM工具的参数中指定生成这些文件,可以通过llvm-as和llvm-dis来在前两种文件之间做转换。
clang -c -emit-llvm main.m 编译产生字节码
clang -S -emit-llvm main.m 编译产生可视化字节码
llvm-dis main.bc main.ll bc字节码转为可视化字节码ll
llvm-as main.ll main.bc 可视化字节码转为字节码bc

源码下载

git clone https://git.llvm.org/git/llvm.git/
下载clang
cd llvm/tools
git clone https://git.llvm.org/git/clang.git/

源码编译

安装cmake和ninja(使用ninja编译LLVM比较快,大约10几分钟,否则需要一个多小时。先安装brew,https://brew.sh/)
brew install cmake
brew install ninja
ninja如果安装失败,可以直接从github获取release版放入【/usr/local/bin】中
https://github.com/ninja-build/ninja/releases
在LLVM源码同级目录下新建一个【llvm_build】目录(最终会在【llvm_build】目录下生成【build.ninja】)
cd llvm_build
cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安装路径
更多cmake相关选项,可以参考: https://llvm.org/docs/CMake.html
依次执行编译、安装指令
ninja
编译完毕后, 【llvm_build】目录大概 20多G
ninja install
安装完毕后,安装目录大概 10多G

应用与实践

安装完毕,下面就可以开发一个Clang插件来练练手啦。

#include <iostream>
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendPluginRegistry.h"

using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;

namespace WJBPlugin {
    class WJBHandler : public MatchFinder::MatchCallback {
    private:
        CompilerInstance &ci;
        
    public:
        WJBHandler(CompilerInstance &ci) :ci(ci) {}
        
        void run(const MatchFinder::MatchResult &Result) {
            if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) {
                size_t pos = decl->getName().find('_');
                if (pos != StringRef::npos) {
                    DiagnosticsEngine &D = ci.getDiagnostics();
                    SourceLocation loc = decl->getLocation().getLocWithOffset(pos);
                    D.Report(loc, D.getCustomDiagID(DiagnosticsEngine::Error, "报错:类名中不能带有下划线"));
                }
            }
        }
    };
    
    class WJBASTConsumer: public ASTConsumer {
    private:
        MatchFinder matcher;
        WJBHandler handler;
        
    public:
        WJBASTConsumer(CompilerInstance &ci) :handler(ci) {
            matcher.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
        }
        
        void HandleTranslationUnit(ASTContext &context) {
            matcher.matchAST(context);
        }
    };

    class WJBASTAction: public PluginASTAction {
    public:
        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
            return unique_ptr<WJBASTConsumer> (new WJBASTConsumer(ci));
        }

        bool ParseArgs(const CompilerInstance &ci, const vector<string> &args) {
            return true;
        }
    };
}

static FrontendPluginRegistry::Add<WJBPlugin::WJBASTAction>
X("WJBPlugin", "The WJBPlugin is my first clang-plugin.");

选择WJBPlugin这个target进行编译,编译完会在Products生成一个动态库文件【WJBPlugin.dylib】

测试插件

总结

文章不长,这看似简单的过程也花费了我很多的时间,主要是LLVM过于强大,以至于我不得不在标题前加上了“浅谈”二字,本文属实只是介绍了些皮毛和入门知识,更深层的汪洋大海还需慢慢探索。
参考文献:
LLVM框架/LLVM编译流程/Clang前端/LLVM IR/LLVM应用与实践
LLVM基本概念入门
初探 Clang
LLVM编译原理和使用

上一篇下一篇

猜你喜欢

热点阅读