LLVM を Windows で使う(インタプリタ編)

2012年07月28日(土)19時08分

とりあえず

最近、いろいろなことに興味が分散しすぎて集中できず、気づくと前回更新から一か月以上経っており、未更新ペナルティの強制広告が表示されていました。
というわけで、もっとも最近に興味の対象として調べていた「LLVM」について、とりあえず何か書いて見ることにします。

LLVM とは

LLVM の「VM」は「Virtual Machine」の略、つまり「仮想機械」なのですが、しかし「Java 仮想マシン」のように「一つのソースコードで様々なプラットフォームに対応できる」というようなことが狙いではなく、もとは GCC の高性能化を目的にはじめられたプロジェクトだそうです。
全く持って浅い理解しかないのですが、例えば「C++」「FORTRAN」「Ada」といった様々な言語のコンパイラが、それぞれに自身のソースコードを直接ターゲットの機械語に変換する、というようなシステムでは、得られるバイナリの質が、そのコンパイラを作った人(々)の技量や知識に大きく左右されることになります。
そこで、それぞれのコンパイラは、いったん全てこの LLVM 用の中間言語に変換し、その後 LLVM 側でその中間言語を徹底的に最適化し、各プラットフォームの機械語に変換する、というようにすれば、どの言語で書こうとも、均一に高品位なバイナリが得られる、という仕組みのようです。
…ただ、今回これを書くために調べて見ると、GCC 自体が元からこういう「各言語 → 中間言語(ここで最適化) → バイナリ」な設計らしいのですが。

オリジナル言語に

で、今回これに興味を持った理由は、オリジナルな自作プログラミング言語、俗に「オレオレ言語」などと言われたりするものを作るのに役立ちそうだと思ったからです。
趣味の一つとして「プログラミング」を挙げるような方であれば、一度や二度はこういうものにあこがれたり、あるいは実際に作りかけてみたり、といった現実逃避をしたことがあるのではないかと思うのですが、自分も例にもれず幾度もあります。
そして、もし作るのであれば「自身を自身で記述できるような言語がいい」と夢見たりするのですが、i386 の機械語や EXE フォーマットの知識も、最適化の技術も、何もかもない自分にそのようなものができようはずもなく…。
しかし、もしこの LLVM を使うのであれば、要するに「LLVM 用の中間言語(LLVM アセンブリ言語)を出力できるようになる」ところまで頑張れば、その後のことは LLVM に面倒を見てもらえるわけです。

ポータブルで

そういう目的なこともあり、なるべく「LLVM の構造や動作を把握できるように」かつ「軽く」ということで、今回はできる限り作業に必要なファイルのみを抽出し、一つのフォルダにまとめて、その環境を持ち運べるような形態、つまり「ポータブル」な運用ができることを目標にしてみます。
…まあ、元が LinuxFreeBSD といった「レジストリ」のような概念がないプラットフォームで開発されているプロジェクトですので、「ポータブル」という言い方(または考え方)もおかしいのですが。
で、具体的には、「USB メモリをUドライブに割り当てた」と仮定し、「U:\LLVM\bin」フォルダ内に必要な実行ファイル類をまとめ、「U:\LLVM\home」フォルダを作業の場(コンパイルするソースコードやその生成物を置く)と想定します。

必要なファイル

まずは公式サイト「http://llvm.org/」から LLVM の Windows 版をダウンロードしてきます(2012/07/28 の時点でバージョンは 3.1、自分がダウンロードしてきたのは「clang+llvm-3.1-i386-mingw32-EXPERIMENTAL.tar.bz2」ですので、以降はこれを前提に話を進めます)。
この BZ2 ファイルを解凍(展開)し、含まれる「bin」フォルダ内の必要ファイルを「U:\LLVM\bin」フォルダにコピーします。
といっても、今回の内容に必要なファイルは「llvm-as.exe」「lli.exe」「llc.exe」の三つだけです。
llvm-as.exe」は LLVM 用の中間言語をバイトコードに変換するもの、「lli.exe」はインタプリタ、「llc.exe」はコンパイラです。

hello world

まずは基本の「hello world」、C言語でいうところの以下のコードを試してみます。

#include <stdio.h>
int main() {
  printf("hello world\n");
  return 0;
}

これを LLVM アセンブリにすると以下のようになります。

@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1
define i32 @main() nounwind {
entry:
  %retval = alloca i32, align 4
  store i32 0, i32* %retval
  %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([13 x i8]* @.str, i32 0, i32 0)) nounwind
  ret i32 0
}
declare i32 @printf(i8*, ...) nounwind

ということで、上記を「hello.s」という名で「U:\LLVM\home」フォルダに TEXT ファイルとして保存します。
そしてもう一つ、以下の内容を「hello.bat」という名で「U:\LLVM\home」フォルダに TEXT ファイルとして保存し、それを実行しますと、「hello.bc」というファイルが生成された後、「hello world」が表示されるはずです。

set path=..\bin
llvm-as -o hello.bc hello.s
lli hello.bc
pause

S ファイルも BC ファイルも同じ

上記 BAT ファイルでは「llvm-as.exe」で S(LLVM アセンブリ言語)ファイルを BC(LLVM ビットコード)ファイルに変換し、「lli.exe」で変換後の BC ファイルを実行する、というような手順になっていますが、LLVM 的には S ファイルも BC ファイルも、単に「表現方法が違うだけで同じものである」という扱いらしく、実は今回の処理でいえば、いちいち BC ファイルを作らずとも、「lli hello.s」とすれば同じ結果になります。

MessageBox

Win32API も使えるようです。
例えばC言語で以下のようなコード。

#include <windows.h>
int main() {
  MessageBox(NULL,"Text","Caption",MB_OK);
  return 0;
}

これを LLVM アセンブリにすると以下のようになります。

%struct.HWND__ = type { i32 }
@.str = private unnamed_addr constant [5 x i8] c"Text\00", align 1
@.str1 = private unnamed_addr constant [8 x i8] c"Caption\00", align 1
define i32 @main() nounwind {
entry:
  %retval = alloca i32, align 4
  store i32 0, i32* %retval
  %call = call x86_stdcallcc i32 @MessageBoxA(%struct.HWND__* null, i8* getelementptr inbounds ([5 x i8]* @.str, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8]* @.str1, i32 0, i32 0), i32 0)
  ret i32 0
}
declare x86_stdcallcc i32 @MessageBoxA(%struct.HWND__*, i8*, i8*, i32)

上記を「MessageBox.s」として保存し、「hello world」のときと同様に実行すれば、メッセージボックスが表示されます。

Window

ウインドウの生成・表示もできました。
例えばC言語で以下のようなコード。

#include <windows.h>
LRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) {
  switch(msg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return(DefWindowProc(hWnd,msg,wParam,lParam));
  }
  return 0L;
}
int main() {
  HWND hWnd;
  WNDCLASS wc;
  MSG msg;
  wc.style=CS_HREDRAW|CS_VREDRAW;
  wc.lpfnWndProc=WndProc;
  wc.cbClsExtra=0;
  wc.cbWndExtra=0;
  wc.hInstance=NULL;
  wc.hIcon=NULL;
  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
  wc.hbrBackground=GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName=NULL;
  wc.lpszClassName="ClassName";
  if(RegisterClass(&wc)==0)return FALSE;
  hWnd=CreateWindow(wc.lpszClassName,"WindowName",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,NULL,NULL);
  ShowWindow(hWnd,SW_SHOW);
  UpdateWindow(hWnd);
  while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}
  return (int)msg.wParam;
}

これを LLVM アセンブリにすると以下のようになります。

%struct.HWND__ = type { i32 }
%struct._WNDCLASSA = type { i32, i32 (%struct.HWND__*, i32, i32, i32)*, i32, i32, %struct.HINSTANCE__*, %struct.HICON__*, %struct.HICON__*, %struct.HBRUSH__*, i8*, i8* }
%struct.HINSTANCE__ = type { i32 }
%struct.HICON__ = type { i32 }
%struct.HBRUSH__ = type { i32 }
%struct.tagMSG = type { %struct.HWND__*, i32, i32, i32, i32, %struct.tagPOINT }
%struct.tagPOINT = type { i32, i32 }
%struct.HMENU__ = type { i32 }
@.str = private unnamed_addr constant [10 x i8] c"ClassName\00", align 1
@.str1 = private unnamed_addr constant [11 x i8] c"WindowName\00", align 1
define x86_stdcallcc i32 @WndProc(%struct.HWND__* %hWnd, i32 %msg, i32 %wParam, i32 %lParam) nounwind {
entry:
  %retval = alloca i32, align 4
  %hWnd.addr = alloca %struct.HWND__*, align 4
  %msg.addr = alloca i32, align 4
  %wParam.addr = alloca i32, align 4
  %lParam.addr = alloca i32, align 4
  store %struct.HWND__* %hWnd, %struct.HWND__** %hWnd.addr, align 4
  store i32 %msg, i32* %msg.addr, align 4
  store i32 %wParam, i32* %wParam.addr, align 4
  store i32 %lParam, i32* %lParam.addr, align 4
  %0 = load i32* %msg.addr, align 4
  switch i32 %0, label %sw.default [
    i32 2, label %sw.bb
  ]
sw.bb:                                            ; preds = %entry
  call x86_stdcallcc void @PostQuitMessage(i32 0)
  br label %sw.epilog
sw.default:                                       ; preds = %entry
  %1 = load %struct.HWND__** %hWnd.addr, align 4
  %2 = load i32* %msg.addr, align 4
  %3 = load i32* %wParam.addr, align 4
  %4 = load i32* %lParam.addr, align 4
  %call = call x86_stdcallcc i32 @DefWindowProcA(%struct.HWND__* %1, i32 %2, i32 %3, i32 %4)
  store i32 %call, i32* %retval
  br label %return
sw.epilog:                                        ; preds = %sw.bb
  store i32 0, i32* %retval
  br label %return
return:                                           ; preds = %sw.epilog, %sw.default
  %5 = load i32* %retval
  ret i32 %5
}
declare x86_stdcallcc void @PostQuitMessage(i32)
declare x86_stdcallcc i32 @DefWindowProcA(%struct.HWND__*, i32, i32, i32)
define i32 @main() nounwind {
entry:
  %retval = alloca i32, align 4
  %hWnd = alloca %struct.HWND__*, align 4
  %wc = alloca %struct._WNDCLASSA, align 4
  %msg = alloca %struct.tagMSG, align 4
  store i32 0, i32* %retval
  %style = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 0
  store i32 3, i32* %style, align 4
  %lpfnWndProc = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 1
  store i32 (%struct.HWND__*, i32, i32, i32)* @WndProc, i32 (%struct.HWND__*, i32, i32, i32)** %lpfnWndProc, align 4
  %cbClsExtra = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 2
  store i32 0, i32* %cbClsExtra, align 4
  %cbWndExtra = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 3
  store i32 0, i32* %cbWndExtra, align 4
  %hInstance = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 4
  store %struct.HINSTANCE__* null, %struct.HINSTANCE__** %hInstance, align 4
  %hIcon = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 5
  store %struct.HICON__* null, %struct.HICON__** %hIcon, align 4
  %call = call x86_stdcallcc %struct.HICON__* @LoadCursorA(%struct.HINSTANCE__* null, i8* inttoptr (i32 32512 to i8*))
  %hCursor = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 6
  store %struct.HICON__* %call, %struct.HICON__** %hCursor, align 4
  %call1 = call x86_stdcallcc i8* @GetStockObject(i32 0)
  %0 = bitcast i8* %call1 to %struct.HBRUSH__*
  %hbrBackground = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 7
  store %struct.HBRUSH__* %0, %struct.HBRUSH__** %hbrBackground, align 4
  %lpszMenuName = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 8
  store i8* null, i8** %lpszMenuName, align 4
  %lpszClassName = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 9
  store i8* getelementptr inbounds ([10 x i8]* @.str, i32 0, i32 0), i8** %lpszClassName, align 4
  %call2 = call x86_stdcallcc zeroext i16 @RegisterClassA(%struct._WNDCLASSA* %wc)
  %conv = zext i16 %call2 to i32
  %cmp = icmp eq i32 %conv, 0
  br i1 %cmp, label %if.then, label %if.end
if.then:                                          ; preds = %entry
  store i32 0, i32* %retval
  br label %return
if.end:                                           ; preds = %entry
  %lpszClassName4 = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 9
  %1 = load i8** %lpszClassName4, align 4
  %call5 = call x86_stdcallcc %struct.HWND__* @CreateWindowExA(i32 0, i8* %1, i8* getelementptr inbounds ([11 x i8]* @.str1, i32 0, i32 0), i32 13565952, i32 -2147483648, i32 -2147483648, i32 -2147483648, i32 -2147483648, %struct.HWND__* null, %struct.HMENU__* null, %struct.HINSTANCE__* null, i8* null)
  store %struct.HWND__* %call5, %struct.HWND__** %hWnd, align 4
  %2 = load %struct.HWND__** %hWnd, align 4
  %call6 = call x86_stdcallcc i32 @ShowWindow(%struct.HWND__* %2, i32 5)
  %3 = load %struct.HWND__** %hWnd, align 4
  %call7 = call x86_stdcallcc i32 @UpdateWindow(%struct.HWND__* %3)
  br label %while.cond
while.cond:                                       ; preds = %while.body, %if.end
  %call8 = call x86_stdcallcc i32 @GetMessageA(%struct.tagMSG* %msg, %struct.HWND__* null, i32 0, i32 0)
  %tobool = icmp ne i32 %call8, 0
  br i1 %tobool, label %while.body, label %while.end
while.body:                                       ; preds = %while.cond
  %call9 = call x86_stdcallcc i32 @TranslateMessage(%struct.tagMSG* %msg)
  %call10 = call x86_stdcallcc i32 @DispatchMessageA(%struct.tagMSG* %msg)
  br label %while.cond
while.end:                                        ; preds = %while.cond
  %wParam = getelementptr inbounds %struct.tagMSG* %msg, i32 0, i32 2
  %4 = load i32* %wParam, align 4
  store i32 %4, i32* %retval
  br label %return
return:                                           ; preds = %while.end, %if.then
  %5 = load i32* %retval
  ret i32 %5
}
declare x86_stdcallcc %struct.HICON__* @LoadCursorA(%struct.HINSTANCE__*, i8*)
declare x86_stdcallcc i8* @GetStockObject(i32)
declare x86_stdcallcc zeroext i16 @RegisterClassA(%struct._WNDCLASSA*)
declare x86_stdcallcc %struct.HWND__* @CreateWindowExA(i32, i8*, i8*, i32, i32, i32, i32, i32, %struct.HWND__*, %struct.HMENU__*, %struct.HINSTANCE__*, i8*)
declare x86_stdcallcc i32 @ShowWindow(%struct.HWND__*, i32)
declare x86_stdcallcc i32 @UpdateWindow(%struct.HWND__*)
declare x86_stdcallcc i32 @GetMessageA(%struct.tagMSG*, %struct.HWND__*, i32, i32)
declare x86_stdcallcc i32 @TranslateMessage(%struct.tagMSG*)
declare x86_stdcallcc i32 @DispatchMessageA(%struct.tagMSG*)

上記を「Window.s」として保存し、「hello world」のときと同様に実行すれば、メッセージボックスが表示されます。

「main()」?

上記「Window」サンプルのC言語版は「main()」関数になっています。
通常、Win32API でウインドウを出すようなプログラムでは「WinMain()」関数を使用するのですが、今回の場合、そうすると「main()がない」というようなことを言われ、実行できません。
これは、どうも LLVM インタプリタ実行環境である「lli.exe」が、通常のコンソールツールを想定したものであることが原因のようです。
結果的に、「lli.exe」を使ってインタプリタ方式で実行する場合には、コンソール画面が必ず表示されることになります。

OBJECT ファイルを生成する

llvm-as.exe」で生成した BC ファイルは LLVM 用の機械語バイナリで、実行にはインタプリタ「lli.exe」が必要になります。
しかし、今回の目的を考えた場合、単体で直接実行可能な形式のファイルを作りたいところです。
これにはまず、もう一つコピーしておいた LLVM アセンブリ用のコンパイラである「llc.exe」を使います。
これで「llc -filetype=obj hello.bc」とすれば、i386 用の機械語バイナリである OBJECT ファイル「hello.o」が生成されます。
なお、前述のように BC ファイルも S ファイルも扱いは同じですので、ここを「llc -filetype=obj hello.s」としても結果は同様です。
で、あとはこの「hello.o」に適切なライブラリを付け加え、実行ファイルにすればいい、ということになるのですが…。

EXE ファイルは生成できない

…なるのですが、現時点(2012/07/28)でダウンロードしてきた LLVM に含まれるコマンド類で可能なのはここまでのようです。
つまり「hello.o」に適切なライブラリを付け加えるために必要な「リンカ」や、あるいは「適切なライブラリ」自体も、現状 LLVM には用意されておらず、この先である「OBJECT ファイル → EXE ファイル」の処理を行うためには、他のツールの手助けが必要になります(多分)。
ということで、次回はそのあたりのことについて書いてみようと思います。

C++ ファイルを生成する

ついでで、LLVM アセンブリ用のコンパイラである「llc.exe」なのですが、「llc -march=cpp hello.bc」のようにすると、 C++ のソースコードに逆変換できます。
「C++」…といっても、上で例示した「hello world」のC言語版サンプルのようなシンプルなものではなく、何やらいろいろと付け加えられた長く複雑なコードです。
ですが、「hello world」であることには違いないはずですので、これを何かしらの C++ コンパイラでコンパイルしても、実行可能ファイルが得られるのかなと思います(未確認なのでわかりませんが)。

関連記事

【同じタグを付けた記事の一覧】
ソースコード プログラミング ポータブル C++ LLVM

スポンサーサイト

コメントの投稿

非公開コメント

最新記事
最新コメント
Amazonおまかせリンク
カテゴリ
タグクラウド
Amazonお買い得ウィジェット
カレンダー
04 | 2017/05 | 06
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
月別アーカイブ
プロフィール

電脳太助

Author:電脳太助
Website:電脳スピーチ web

RSSリンクの表示
メールフォーム

名前:
メール:
件名:
本文:

サイト内検索
Ads by Google
FC2アクセスランキング
Ads by Google
FC2拍手ランキング
ユーザータグ

音楽管理(66)
ポータブル(57)
ソフト紹介(44)
プログラミング(42)
音声技術(41)
自作ソフト(35)
サイト運営(32)
FC2(31)
ブログ(30)
iTunes(27)
Windows(25)
LISMO(24)
音声合成(23)
音声認識(22)
x-アプリ(22)
電子ブック(22)
eラーニング(20)
バックアップ(19)
語学学習(19)
foobar2000(18)
ソースコード(18)
WindowsLiveWriter(15)
画像管理(15)
C++(14)
アフィリエイト(10)
DnspTools(10)
fi-6130(9)
FLAC(9)
JavaScript(9)
ウォークマン(9)
英語音読学習計画(8)
Gracenote(8)
Prolog(8)
ベクター(8)
雑記(8)
CodeBlocks(7)
SyntaxHighlighter(7)
TraConv(7)
spcbght(7)
wxWidgets(7)
VirtualBox(6)
W63CA(6)
DCP-J552N(6)
WinRT(6)
WindowsLiveMesh(6)
iGoinLM(6)
英語発音矯正実験(6)
ExactAudioCopy(6)
MP3Gain(6)
LAME(5)
音楽技術(5)
Mery(5)
楽器演奏(5)
GalateaTalk(4)
nLite(4)
WindowsLiveSkyDrive(4)
ホームページ(4)
GalateaProject(4)
MIDI(4)
LLVM(4)
PC-98(3)
カウンター(3)
AACGain(3)
iTCDini(3)
OverCutChecker(3)
拍手(3)
PK-513L(3)
UniversalExtractor(3)
アクセスランキング(3)
ImageCompositeEditor(2)
アクセス解析(2)
OCR(2)
qtaacenc(2)
資格試験(1)
AquesTalk(1)
AquesCmdDl(1)

FC2アクセスランキング
最新トラックバック
アクセスランキング
[ジャンルランキング]
コンピュータ
90位
アクセスランキングを見る>>

[サブジャンルランキング]
ソフトウェア
6位
アクセスランキングを見る>>
FC2カウンター