[LLVM/Clang] LLVM/Clangを知る【はじめに】

LLVM/Clang

iOSアプリケーション開発においてなくてはならない重要な技術の一つです。
普段ほとんど気にする人はいないかと思いますが、Xcodeで開発する上で、裏側の処理はどうなっているかを少し覗いてみます

なぜLLVM/Clang?

Androidでも実験的にARTなどLLVMを基盤にした技術が導入され始めているため、モバイル・アプリケーション開発にこれからも関わる気があるならば少し基盤の方も知っておいた方が良いかと思った次第です。
まあ、ほぼ趣味ですが。

参考書籍

以下の書籍がさくさく読める内容であったため、LLVM/Clangに興味を持つきっかけになりました。

レビューはえらい書かれようですが、コンパイラに関する基礎知識がある程度あれば、LLVM/Clangのエッセンスについて広く知ることが出来ました。コンパイラの動作やどのようにコンピュータが実行可能なファイルに変換されるのかの動作を何となく知っている方、またクロスコンパイルがどういうものかなどうっすら知ってる方であれば、この書籍でLLVM/Clangの魅力の一端を感じれるのではないでしょうか。

LLVM, Clangの関係

コンパイラはフロントエンド、バックエンドに分かれ、それぞれに役割があります。大きく分けて、ソースコードを入力として字句解析を行うフェーズ、そして実際に環境や指定に合わせたマシン語生成を行うフェーズ。
Clangは前者を担当し、LLVMは後者を担当するようです。

Clang

Clangは、LLVMと共に提供されるコンパイラフロントエンドです。
基本的にコンパイルに関わる全ての命令(コンパイルする対象の指定からターゲットアーキテクチャの指定や最適化の指定など諸々)はこちらで受け付け、字句解析、意味解析、解析木の生成などマシン語に変換する際に使う下準備を行う、それらを指定や環境に合わせたコンパイラ等にコマンドを受け渡す役割を担います。我々が実際にコンパイラに実行をお願いするときには、Clangを経由して命令を送ることになるかと思います。

manコマンドで確認してみます。

CLANG(1)                   Clang Tools Documentation                  CLANG(1)

NAME
       clang - the Clang C, C++, and Objective-C compiler

SYNOPSIS
       clang [-c|-S|-E] -std=standard -g
         [-O0|-O1|-O2|-O3|-Ofast|-Os|-Oz|-O|-O4]
         -Wwarnings... -pedantic
         -Idir... -Ldir...
         -Dmacro[=defn]
         -ffeature-option...
         -mmachine-option...
         -o output-file
         -stdlib=library
         input-filenames

DESCRIPTION
       clang is a C, C++, and Objective-C compiler which encompasses
       preprocessing, parsing, optimization, code generation, assembly, and
       linking.  Depending on which high-level mode setting is passed, Clang
       will stop before doing a full link.  While Clang is highly integrated,
       it is important to understand the stages of compilation, to understand
       how to invoke it.  These stages are:
       
       (snip)

思った以上に長かったので一部だけ。
前処理、解析、最適化、コード生成、アセンブリ、リンクといった単語が見えます。完全なリンクを完了する前に停止します、とあります。まあ、これはコンパイルという処理にはいくつかの段階があるので、Clangでカバーする領域というのはコンパイルという処理における一部分ということでしょう。

オプションなどの説明がかなり大量に詳細に書いてあるので、こちらはそのうち機会があれば読み込んでおきます。

LLVM

LLVMとはコンパイラ基盤の一つです。中間言語を介して、対象のアーキテクチャに最適なマシン語へ変換することができます。中間言語を介して実行されるため、JavaVMのような仮想マシンと比較されることも多いようですが、厳密な意味で仮想マシンとは別物という認識で良いようです。

御託は良いのでこちらもmanコマンドで確認してみます。

$ llvm-g
llvm-g++  llvm-gcc  

Xcodeがインストールされている環境であれば、この2つが候補として出力されます。今回は、llvm-g++の方を見てみます。

$ man llvm-g++

出力結果は以下のとおり。

llvm-gcc(1)               BSD General Commands Manual              llvm-gcc(1)

NAME
     llvm-gcc

SYNOPSIS
     llvm-gcc  [-c|-S|-E] [-std=standard]
               [-g] [-pg] [-Olevel]
               [-Wwarn...] [-pedantic]
               [-Idir...] [-Ldir...]
               [-Dmacro[=defn]...] [-Umacro]
               [-foption...] [-mmachine-option...]
               [-o outfile] infile...

     llvm-g++  [-c|-S|-E] [-std=standard]
               [-g] [-pg] [-Olevel]
               [-Wwarn...] [-pedantic]
               [-Idir...] [-Ldir...]
               [-Dmacro[=defn]...] [-Umacro]
               [-foption...] [-mmachine-option...]
               [-o outfile] infile...

DESCRIPTION
     llvm-gcc is a C, C++, Objective-C and Objective-C++ compiler. llvm-g++ is
     a compiler driver for C++. llvm-gcc uses gcc front-end and gcc's command
     line interface. Consult the cc(1) man page for command line options sup-
     ported by llvm-gcc. Only selected LLVM specific options are listed here.

     -O       Unlike gcc, -O means -O2 instead of -O1.

SEE ALSO
     cc(1,) ld(1)

Darwin                         December 11, 2008                        Darwin

基本的にはllvm-gccがコンパイラ本体のようです。llvm-g++は、C++用のコンパイラドライバとの記述が見えます。

clangで各種ソースコードをコンパイルしてみる

Clang, LLVMの概要は何となく見えたので、C, C++, Objective-Cのソースコードをコンパイルしてみます。

C

#include<stdio.h>

int main(void) {
  printf("Hello C world");
  return 0;
}

コンパイル

$ clang helloworld.c -o helloworld.o

実行結果

$ ./helloworld.o
Hello C world

C++

#include

int main() { std::cout << "Hello, C++ world!" << std::endl; } [/cpp]

コンパイル

$ clang++ -o helloworld.o helloworld.cpp

clangではなくclang++であることに注意してください。
ちなみにclangで実行するとこんな結果になります。

$ clang -o helloworld.o helloworld.cpp
Undefined symbols for architecture x86_64:
  "std::__1::locale::use_facet(std::__1::locale::id&) const", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
  "std::__1::ios_base::getloc() const", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
  "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__init(unsigned long, char)", referenced from:
      std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) in helloworld-8d6d9f.o
  "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string()", referenced from:
      std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) in helloworld-8d6d9f.o
  "std::__1::basic_ostream<char, std::__1::char_traits<char> >::put(char)", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
  "std::__1::basic_ostream<char, std::__1::char_traits<char> >::flush()", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
  "std::__1::basic_ostream<char, std::__1::char_traits<char> >::sentry::sentry(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
  "std::__1::basic_ostream<char, std::__1::char_traits<char> >::sentry::~sentry()", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
  "std::__1::cout", referenced from:
      _main in helloworld-8d6d9f.o
  "std::__1::ctype<char>::id", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
  "std::__1::locale::~locale()", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
  "std::__1::ios_base::__set_badbit_and_consider_rethrow()", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
  "std::__1::ios_base::clear(unsigned int)", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
  "std::terminate()", referenced from:
      ___clang_call_terminate in helloworld-8d6d9f.o
  "___cxa_begin_catch", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
      ___clang_call_terminate in helloworld-8d6d9f.o
  "___cxa_end_catch", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
  "___gxx_personality_v0", referenced from:
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<<<std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) in helloworld-8d6d9f.o
      std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::endl<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) in helloworld-8d6d9f.o
      std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) in helloworld-8d6d9f.o
      Dwarf Exception Unwind Info (__eh_frame) in helloworld-8d6d9f.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

実行結果

$ ./helloworld.o
Hello, C++ world!

Objective-C

#import <Foundation/NSObject.h>
#import <stdio.h>

@interface Person : NSObject
  - (void) sayHello;
@end

@implementation Person

- (void) sayHello{
  printf("Hello Objective-C World.\n");
}

@end

int main(void) {
  id person = [Person alloc];
  [person sayHello];

  return 0;
}

コンパイル

$ clang helloworld.m -framework Foundation -o helloworld.o

ちょっとだけオプションがついています。
Foundationというオプションを指定してあげる必要があるようです。

実行結果

$ ./helloworld.o 
Hello Objective-C World.

Clangで3種類のソースコードをコンパイルして実行することができました。
次はLLVMの中間コード、そしてクロスコンパイルについて見て行きたいと思います。

参照