欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LLVM开发插件以及遇到问题

程序员文章站 2024-03-23 20:14:10
...

理解

什么是LLVM

  • LLVM项目是模块化、可重用的编译器以及工具链技术的集合
  • 创始人就说Swift之父
  • LLVM本身不是首字母缩略词,它是项目的全名

传统的编译器架构

  • GCC、LLVM、Clang
    LLVM开发插件以及遇到问题

LLVM开发插件以及遇到问题

  • 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改
    4.相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起,所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就会变得特别困难
    5.LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)

Clang

什么是Clang

1.LLVM项目的一个子项目
2.基于LLVM架构的C/C++/Objective-C编译器前端

相比于GCC,Clang具有如下优点

1.编译速度快:在某些平台下,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GCC快3倍)
2.占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
3.模块化设计: Clang采用基于库的模块化设计,易于IDE(开发工具)集成及其他用途的重用
4.诊断信息可读性强:在编译过程中,Clang创建并保留了大量详细的元数据,有利于调试和错误诊断
5.设计清晰简单,容易理解,易于扩展增强
LLVM开发插件以及遇到问题
IR–>Pass→IR属于中间代码,可以自己编写

Oc源文件的编译过程

LLVM开发插件以及遇到问题

词法分析

1.词法分析,生成Token:$ clang -fmodules -E -Xclang -dump-tokens main.m

语法树-AST

1.词法分析,生成语法树(AST): $ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

  • 词法分析完成之后,会把token拼接起来变成语法树
    2.举例:
void test(int a, int b) {
   int c = a + b - 3;
}

LLVM开发插件以及遇到问题

LLVM IR

1.LLVM IR有3种表示形式(但本质是等价的,就好比水可以有气体、液体、固件3种形态)

  • text: 便于阅读的文本格式,类似于汇编语言,扩展名.ll ,$ clang -S -emit-llvm main.m
  • memory:内存格式
  • bitcode: 二进制格式,扩展名.bc $ clang -c -emit-llvm main.m
    LLVM开发插件以及遇到问题
    局部标识符以%开头
    语法参考: https://llvm.org/docs/LangRef.html

开始制作

源码下载

1.xcode内置了一个clang,但是如果要自己制作,需要新下载一个编译
2.下载LLVM: git clone https://git.llvm.org/git/llvm.git/
3.下载clang:cd llvm/tools
git clone https://git.llvm.org/git/clang.git/
4.编译源码

  • 安装cmake和ninja(先安装brew)
  • brew install cmake
  • brew install ninja(ninja如果安装失败,可以直接从github获取release版放入/usr/local/bin中 https://github.com/ninja-build/ninja/releases)
  • 在LLVM源码同级目录下新建llvm_build
  • cd llvm_build ; cmake -G Ninja …/llvm -DCMAKE_INSTALL_PREFIX=LLVM的安装路径 //(完成之后,如果llvm_build下有文件build.ninja说明成功,DCMAKE_INSTALL_PREFIX表示将来llvm编辑好的东西放置的位置建立放在LLVM源码同级目录下新建llvm_release)
  • 依次执行编译、安装指令 ninja //编译完成后,llvm_build目录大概21.05G; ninja install //安装完毕后,目录大概11.92G

应用与实践

  1. libclang、libTooling
    官方参考: htttps://clang.llvm.org/docs/Tooling.html
    应用: 语法树分析、语法转换等
  2. Clang插件开发
    官方参考
    htttps://clang.llvm.org/docs/ClangPlugins.html
    htttps://clang.llvm.org/docs/ExternalClangExamples.html
    htttps://clang.llvm.org/docs/RAVFrontendAction.html
    应用: 代码检查(命名规范、代码规范)等
  3. Pass开发
    官方参考
    htttps:/llvm.org/docs/WritingAnLLVMPass.html
    应用: 代码优化、代码混淆等
  4. 开发新的编程语音
    https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
    https://kaleidoscope-llvm-tutorial-zh-cn.readthedoc.io/zh_CN/latest

clang插件开发-插件目录

  1. 首先在llvm/tools/clang/tools下新建立一个文件夹(如MJPlugin)
  2. 打开CMakeLists.txt,copy最后一句,把括号里名字改完自己建立的文件名
  3. 来到自定义文件夹目录下的控制台下: touch MJPlugin.cpp //插件都是用c++写的
  4. 再把CMakeLists.txt,copy一份到MJPlugin.cpp同级目录下,清空所有内容,加上如下内容
add_llvm_loadable_module(MJPlugin 
MJPlugin.cpp
MJPlugin1.cpp
)

亲测上面方法不行,改成如下:

add_llvm_library(mj-plugin MODULE MJPlugin.cpp PLUGIN_TOOL clang)

if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  set(LLVM_LINK_COMPONENTS
    Support
  )
  clang_target_link_libraries(mj-plugin PRIVATE
    clangAST
    clangBasic
    clangFrontend
    LLVMSupport
    )
endif()
  1. llvm_build同级目录下新建立llvm_xcode;再在命令行执行: cmake -G Xcode …/llvm
    之后会生成模版,xcode打开,在loadable modules下会看到mj_plugin,也可以鼠标选中target,键盘直接敲mj_plugin
    会搜到mj_plugin,mj_plugin下有CMakeLists.txt和MJPlugin.cpp
    打开MJPlugin.cpp进行代码编写,target选择MJPlugin进行编译
#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"
#include "llvm/Support/raw_ostream.h"
#include "clang/Sema/Sema.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/AST/DeclObjC.h"

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

namespace MJPlugin {

   class MJHandler: public MatchFinder::MatchCallback {
   private:
       CompilerInstance &ci;
   public:
       MJHandler(CompilerInstance &ci) : ci(ci) {}
       
       // 找到之后,会调用handler的run方法
       void run(const MatchFinder::MatchResult &Result) {
           if (const ObjCInterfaceDecl *decl = Result.Nodes.getNodeAs<ObjCInterfaceDecl>("ObjCInterfaceDecl")) { // ObjCInterfaceDecl类名
               size_t pos = decl->getName().find('_'); // 拿到名字找下划线
               if (pos != StringRef::npos) { // 一旦发现pos != 找不到  npos找不到的意思
                   DiagnosticsEngine &D = ci.getDiagnostics();
                   SourceLocation loc = decl->getLocation().getLocWithOffset(pos); // 位置->类声明的位置->下划线的位置  所以报错的时候会报错的准备的位置,并且下划线会是红色的
                   D.Report(loc,D.getCustomDiagID(DiagnosticsEngine::Error, "周周--类名中不能带下划线"));
               }
           }
       }
   };

   class MJConsumer: public ASTConsumer {
       
   private:
       MatchFinder macther;
       MJHandler handler;
   public:
        
       MJConsumer(CompilerInstance &ci) : handler(ci) {
           // 构造函数,告诉matcher要找什么,找到了之后调用handler
           macther.addMatcher(objcInterfaceDecl().bind("ObjCInterfaceDecl"), &handler);
       }
       // 生成语法树之后会走这里
       void HandleTranslationUnit(ASTContext &Ctx) {
           
           cout << "MJPlugin-HandleTranslationUnit" << endl;
           macther.matchAST(Ctx); // 查找语法树的节点,Ctx包含语法树信息
           
       }
   };
   // 指定action后,会调用CreateASTConsumer方法
   class MJAction : public PluginASTAction {
   public:
       unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &ci, StringRef iFile) {
           return unique_ptr<MJConsumer>(new MJConsumer(ci)); // 创建一个consumer,完成之后会生成语法树,
       }
       bool ParseArgs(const CompilerInstance &ci,const vector<string> &args) {
           return true;
       }
   };
}

// 注册插件--指定action
static FrontendPluginRegistry::Add<MJPlugin::MJAction>
X("MJPlugin","The MJPlugin is my first clang-plugin.");

编写成功后,在Products文件夹下点击show in finder会看到同名动态库 // llvm_xcode/Debug/lib

clang插件开发-Hack Xcode

  1. 下载XcodeHacking,右键【HackedClang.xcplugin】点击"显示包内容"打开修改【HackedClang.xcplugin/Contents/Resources/HackedClang.xcspec】的内容
    ExecPath = “” // 放编译好的clang的全路径 如 /Users/zhousuhua/Zhou_Zhou/llvm/llvm_build/bin/clang
  2. XcodeHacking目录下的命令行下执行:
  3. sudo mv HackedClang.xcplugin ‘xcode-select -print-path’/…/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
  4. sudo mv HackedBuildSystem.xcspec 'xcode-select -print-path`/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications

clang插件开发- Xcode 配置

  1. 在想要使用该插件的项目中→build setting 下搜索 other_c 找到Other C Flags
  2. 添加 _Xclang -load _Xclang 动态库绝对路径 _Xclang -add-plugin -Xclang 插件名称(MJPlugin)
  3. build setting 下搜索 compiler 找到 Compiler for C/C++/Objective-C
  4. 选择 Clang LLVM Trunk // 需要Hack Xcode
  5. 每次动态库更新,都需要clean一下重新编译
相关标签: Oc