2012年5月26日 星期六

[LLVM] LLVM、Clang、Clang Plugin、Clang Rewriter 筆記 @ Ubuntu 64bit

Source: http://en.wikipedia.org/wiki/LLVM
圖片來源:http://llvm.org/Logo.html


第一次看到 Open source project 覺得 LOGO 很殺 XD 這也代表 LLVM 真的有某個層度的威力!LLVM 全名為 Low Level Virtual Machine,翻譯為底層虛擬機器,並且從 wiki 介紹可以略知一二,就是透過虛擬技術來作最佳化。



  • http://llvm.org/

  • http://en.wikipedia.org/wiki/LLVM


    • LLVM (formerly Low Level Virtual Machine) is a compiler infrastructure written in C++ that is designed for compile-time, link-time, run-time, and "idle-time" optimization of programs written in arbitrary programming languages.


  • http://zh.wikipedia.org/zh-tw/LLVM


    • LLVM ,命名最早源自於底層虛擬機器(Low Level Virtual Machine)的縮寫[1]。它是一個編譯器的基礎建設,以C++寫成。它是為了任意一種程式語言寫成的程式,利用虛擬技術,創造出編譯時期,鏈結時期,執行時期以及「閒置時期」的最佳化。



談論到虛擬機器,對資訊領域最親近的程式語言是 Java,以及 Java bytecode、Java Virtual Machine (JVM) 和跨平台的關係。將 Java 程式碼 (.java) 編譯成 bytecode (.class) 後,透過 JVM 可執行結果,加上多種平台都有 JVM (Windows/Linux/Mac),提供開發者只需撰寫一份程式碼、僅需編譯一次,接著就可以拿著編譯完的結果到各類有支援 JVM 的平台執行,成果就達成了跨平台。若粗淺地看 LLVM 的話,也有類似的關係,只是這時架構稍微調整成:


N 種程式語言 (front ends) > 產生 LLVM Bitcode > 產生 M 個程式執行環境 (back ends)


而 LLVM 能做的事不止於此,還可進行程式效能最佳化處理等等。


首先提一下安裝 LLVM 的部分好了,參考官方文件的介紹,在此選擇 Ubuntu 64bit 環境:


$ uname -a
Linux 2.6.32-38-server #83-Ubuntu SMP Wed Jan 4 11:26:59 UTC 2012 x86_64 GNU/Linux
$ dmesg | grep -i cpu
Intel(R) Xeon(R) CPU E5420 @ 2.50GHz

下載程式碼:


$ cd ~/ && svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
$ cd ~/llvm/tools/ && svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
$ cd ~/llvm/projects && svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
$ cd ~/llvm/projects && svn co http://llvm.org/svn/llvm-project/test-suite/trunk test-suite

採用官方編譯方式:


$ cd ~/ && mkdir -p build out && cd ~/build && ../llvm/configure --prefix=$HOME/out
$ time make -j8
real 9m55.952s
user 47m7.270s
sys 5m49.610s

採用 cmake 編譯方式:


$ sudo apt-get install cmake
$ cd ~/ && mkdir -p build out && cd ~/build && cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/out ../llvm
$ time make -j8
real 5m39.994s
user 37m23.290s
sys 4m27.790s

原先是某次用 configure 編譯會過不了(trunk版本常碰到的現象),就嘗試用 CMAKE ,結果一用發現編得更快,從此我就都用 CMAKE 了 XDDD (或許有少編啥東西 :P 但不影響我研究,就沒計較了) 須留意編譯 Clang plugin 的部分,如果 llvm 採用 configure 編法,那只需切換到 clang plugin project 中執行 make 即可,其他細節可參考 PrintFunctionNames 裡的 README,如果採用 CMAKE 編法,記得要跑去 CMake 產生得相對目錄結構中,執行 make 才會編出來,並且編出來的位置是擺在在 lib 中(此例是~/build/lib)。


研究一下 clang 的用處好了,最直觀的用途是像 gcc 的編譯器功能(並非全部功能都支援, 這跟LLVM架構有關),把 source code 編譯成執行檔,而 compiler 有很多種,例如 one-pass 跟 multi-pass compiler,我想 LLVM 架構應該算 multi-pass 的,由 clang 作為 front end 的部份,將程式碼編譯成 LLVM Bitcode,接著在經由 LLVM 架構,搭配 back ends 端產出各種平台的 native code。而 clang 的功能不只於此,參考 Google 發表的相關文章 C++ at Google: Here Be Dragons,可以透過 clang 來處理一些錯誤偵錯,例如 bool *charge_acct 變數,操作上不小心弄成 charge_acct = false 的問題(正確使用應該是 charge_acct = NULL 或 *charge_acct = false),更多介紹請參考 Chromium Style Checker Errors。簡言之,透過 clang 可以幫忙掃 source code 找一些容易忽略的錯誤,並且輸出人性化的訊息。相關產品: Clang Static Analyzer


由於 Clang 可以分析 source code,那就存在很多玩法,且架構上提供 clang plugin 用法。範例程式:clang/examples/PrintFunctionNames/PrintFunctionNames.cpp,翻閱一下,可以看到一些關鍵字:PluginASTActionASTConsumer,其中 AST 在 Compiler 界全名為 Abstract Syntax Tree,就是把程式碼建立一個 tree 結構,方便分析。


至於寫一個 Clang Plugin 要做的事:(可從範例程式 PrintFunctionNames 複製一份再來修改)



  1. 撰寫 PluginASTAction


    • 關於 PluginASTAction 架構可參考 http://clang.llvm.org/doxygen/classclang_1_1PluginASTAction.html, 簡言之,包括 CreateASTConsumer 與 ParseArgs,其中 ParseArgs 可透過參數來初始化 plugin 操作環境或是區分要執行那個動作,而 CreateASTConsumer 則是核心項目,也就是依工作目的建立自己的 ASTConsumer。


  2. 撰寫 ASTConsumer


  3. 註冊操作時使用的名稱


    • 此 PrintFunctionNames 為 print-fns


  4. 編譯產生 xxx.so 或在 Mac 為 xxx.dylib 檔


    • 此 PrintFunctionNames  為 PrintFunctionNames.so 或 libPrintFunctionNames.so


  5. 操作使用 clang -cc1 -load /path/xxx.so -plugin YourPluginName YourInputFile 或 clang -cc1 -load /path/xxx.so -plugin YourPluginName -plugin-arg-YourPluginName YourArgs YourInputFile


    • clang -cc1 -load /path/PrintFunctionNames.so -plugin print-fns some-input-file.c

    • clang -cc1 -load /path/PrintFunctionNames.so -plugin print-fns -plugin-arg-print-fns -an-error some-input-file.c



在 PrintFunctionNames 例子來說,在 PluginASTAction::ParseArgs 有一個簡易判斷參數的架構;在 ASTConsumer 裡頭,使用 HandleTopLevelDecl 功能,用來輸出程式碼內的 top level function name。因此,若要寫 Clang Plugin 的話,可以從 PrintFunctionNames 的架構修改。接著更複雜的應用就是 source-to-source translation,可用 Google 搜尋 clang source to source translation 或 clang rewriter 看看,最經典的例子是 lib/Rewrite/RewriteObjC.cpp


使用 clang rewriter 進行 source to source translation,將 ObjC 程式碼轉成 C 語言:


$ sudo apt-get install llvm clang g++
$ vim Hello.m
#import
int main( int argc, const char *argv[] ) {
printf( "hello world\n" );
return 0;
}
$ clang -rewrite-objc Hello.m
$ cat Hello.cpp
#ifndef __OBJC2__
#define __OBJC2__
#endif
struct objc_selector; struct objc_class;
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
#ifndef _REWRITER_typedef_Protocol
typedef struct objc_object Protocol;
#define _REWRITER_typedef_Protocol
#endif
#define __OBJC_RW_DLLIMPORT extern
__OBJC_RW_DLLIMPORT void objc_msgSend(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void);
__OBJC_RW_DLLIMPORT void objc_msgSend_stret(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper_stret(void);
__OBJC_RW_DLLIMPORT void objc_msgSend_fpret(void);
__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);
__OBJC_RW_DLLIMPORT void objc_exception_throw( struct objc_object *);
__OBJC_RW_DLLIMPORT void objc_sync_enter( struct objc_object *);
__OBJC_RW_DLLIMPORT void objc_sync_exit( struct objc_object *);
__OBJC_RW_DLLIMPORT Protocol *objc_getProtocol(const char *);
#ifndef __FASTENUMERATIONSTATE
struct __objcFastEnumerationState {
unsigned long state;
void **itemsPtr;
unsigned long *mutationsPtr;
unsigned long extra[5];
};
__OBJC_RW_DLLIMPORT void objc_enumerationMutation(struct objc_object *);
#define __FASTENUMERATIONSTATE
#endif
#ifndef __NSCONSTANTSTRINGIMPL
struct __NSConstantStringImpl {
int *isa;
int flags;
char *str;
long length;
};
#ifdef CF_EXPORT_CONSTANT_STRING
extern "C" __declspec(dllexport) int __CFConstantStringClassReference[];
#else
__OBJC_RW_DLLIMPORT int __CFConstantStringClassReference[];
#endif
#define __NSCONSTANTSTRINGIMPL
#endif
#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// Runtime copy/destroy helper functions (from Block_private.h)
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32];
extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32];
#else
__OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int);
__OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int);
__OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32];
__OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32];
#endif
#endif
#define __block
#define __weak

#include
struct __NSContainer_literal {
void * *arr;
__NSContainer_literal (unsigned int count, ...) {
va_list marker;
va_start(marker, count);
arr = new void *[count];
for (unsigned i = 0; i < count; i++)
arr[i] = va_arg(marker, void *);
va_end( marker );
};
~__NSContainer_literal() {
delete[] arr;
}
};
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
#include

int main( int argc, const char *argv[] ) {
printf( "hello world\n" );
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

$ g++ -o Hello Hello.cpp
$ ./Hello
hello world

至於如何寫 Clang Rewriter 呢,就是直接參考 lib/Rewrite/RewriteObjC.cpp 這隻幾千行的程式碼吧!裡頭已經有豐富的資料可以參考,此例主要的有兩個 class :


class RewriteObjC : public ASTConsumer
class RewriteObjCFragileABI : public RewriteObjC


程式主體是 RewriteObjC,但使用時是用 RewriteObjCFragileABI:


ASTConsumer *clang::CreateObjCRewriter(const std::string& InFile, raw_ostream* OS, DiagnosticsEngine &Diags, const LangOptions &LOpts, bool SilenceRewriteMacroWarning) {
return new RewriteObjCFragileABI(InFile, OS, Diags, LOpts, SilenceRewriteMacroWarning);
}

一開始的程式進入點在 ASTConsumer constructor,隨後還有 virtual void Initialize(ASTContext &context),接著就跟 clang plugin example PrintFunctionNames 流程差不多,可以追 ASTConsumer 的 public functions 瞧瞧,如 virtual bool HandleTopLevelDecl(DeclGroupRef D) 等。


因此,撰寫的主體大概擺在 bool HandleTopLevelDecl(DeclGroupRef D) 裡頭,此例在 RewriteObjC 的宣告及 HandleTopLevelDecl 定義處:


// Top Level Driver code.
virtual bool HandleTopLevelDecl(DeclGroupRef D) {
for (DeclGroupRef::iterator I = D.begin(), E = D.end(); I != E; ++I) {
if (ObjCInterfaceDecl *Class = dyn_cast<ObjCInterfaceDecl>(*I)) {
if (!Class->isThisDeclarationADefinition()) {
RewriteForwardClassDecl(D);
break;
}
}

if (ObjCProtocolDecl *Proto = dyn_cast<ObjCProtocolDecl>(*I)) {
if (!Proto->isThisDeclarationADefinition()) {
RewriteForwardProtocolDecl(D);
break;
}
}

HandleTopLevelSingleDecl(*I);
}
return true;
}

另外一塊就是 void RewriteObjC::HandleTopLevelSingleDecl(Decl *D):


//===----------------------------------------------------------------------===//
// Top Level Driver Code
//===----------------------------------------------------------------------===//

void RewriteObjC::HandleTopLevelSingleDecl(Decl *D) {
//
//...
//

// If we have a decl in the main file, see if we should rewrite it.
if (SM->isFromMainFile(Loc)) // SM = SourceManager ; Loc = SourceLocation
return HandleDeclInMainFile(D);
}

別忘了瞧瞧 HandleDeclInMainFile 做了甚麼事囉!裡頭有豐富的 type checking 以及對應的處理機制,最後,則是文字取代的部分:


void ReplaceText(SourceLocation Start, unsigned OrigLength, StringRef Str) {
// If removal succeeded or warning disabled return with no warning.
if (!Rewrite.ReplaceText(Start, OrigLength, Str) || SilenceRewriteMacroWarning)
return;
Diags.Report(Context->getFullLoc(Start), RewriteFailedDiag);
}

如此一來,對 Clang Rewriter 的架構也熟悉了!


沒有留言:

張貼留言