LLVM開發插件以及遇到問題

理解

什麼是LLVM

  • LLVM項目是模塊化、可重用的編譯器以及工具鏈技術的集合
  • 創始人就說Swift之父
  • LLVM本身不是首字母縮略詞,它是項目的全名

傳統的編譯器架構

  • GCC、LLVM、Clang
    在這裏插入圖片描述

在這裏插入圖片描述

  • 優化階段是一個通用的階段,它針對的是統一的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.設計清晰簡單,容易理解,易於擴展增強
在這裏插入圖片描述
IR–>Pass→IR屬於中間代碼,可以自己編寫

Oc源文件的編譯過程

在這裏插入圖片描述

詞法分析

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 IR

1.LLVM IR有3種表示形式(但本質是等價的,就好比水可以有氣體、液體、固件3種形態)

  • text: 便於閱讀的文本格式,類似於彙編語言,擴展名.ll ,$ clang -S -emit-llvm main.m
  • memory:內存格式
  • bitcode: 二進制格式,擴展名.bc $ clang -c -emit-llvm main.m
    在這裏插入圖片描述
    局部標識符以%開頭
    語法參考: 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一下重新編譯
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章