とりあえず日記

VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ→VIM→秀丸エディタ(いまここ🍄)

clang-interpreterを動かしてみた

clangのexampleディレクトリに含まれる clang-interpreter をビルドして動かしてみました。
制限はあるもののLLVMJITC++が動きました。
コンパイル不要なのでインタプリタ的な動作になります。

対象

llvm\tools\clang\examples\clang-interpreter\

環境

全ファイル。(Github)

https://github.com/ohtorii/clang_examples_visual_studio/tree/master/clang-interpreter
ビルド済みの実行ファイルあります。

clang-interpreterVisual Studio 2010 でビルドする

VC2010のinclude/libraryのパスを設定してやると実行ファイル(clang-interpreter.exe)を生成できます。
多分大きな問題は発生しないと思います、なのでビルドは割愛します。

動かしてみた。(printf)

//sample_0.cpp
include<stdio.h>

int main(int argc, char*argv[]){
	for(int i=0; i!=4 ; ++i){
		printf("i=%d\n",i);
	}
	return 0;
}

>clang-interpreter.exe sample_0.cpp
i=0
i=1
i=2
i=3

問題なく動いています。

動かしてみた。(file i/o)

//sample_1.cpp
#include<stdio.h>

int main(int argc, char*argv[]){
	FILE* fp=fopen("out.txt","wt");
	fprintf(fp,"%s\n","hello clang");
	fprintf(fp,"%s\n","こんにちわ、clang");
	fclose(fp);
	return 0;
}

>clang-interpreter.exe sample_1.cpp
>type out.txt
hello clang
こんにちわ、clang

これも、問題なく動いています。

動かしてみた。(class)

//sample_2.cpp
#include<stdio.h>

template<class T>
class add{
public:
	add(T v) : m_base(v){
	}
	T operator()(T v)const{
		return v + m_base;
	};
private:
	T	m_base;
};

int main(int argc, char*argv[]){
	add<int>	obj(10);
	printf("value=%d\n", obj(20));
	return 0;
}


問題なく動いています。

動かしてみた。(Global ctor/dtor)

//sample_3.cpp
#include<stdio.h>

class foo{
public:
	foo(){
		printf("ctor\n");
	}
	~foo(){
		printf("dtor\n");
	}
};

foo g;	//  <------- Global ctor/dtor.

int main(int argc, char*argv[]){
	printf("Enter main.\n");
	return 0;
}

デフォルのままだとグローバルスコープで定義されたctor/dtorは無視されます。今回はソースに手を加えてctor/dtorが呼び出されるよう修正しました。

//llvm\tools\clang\examples\clang-interpreter\main.cpp
static int Execute( llvm::Module *Mod, char *const *envp ) {
	llvm::InitializeNativeTarget();
			
			:
			:
			:
			:
			
	// FIXME: Support passing arguments.
	std::vector<std::string> Args;
	Args.push_back( Mod->getModuleIdentifier() );

	//
	//2011/7/10 ohtorii. Add global ctor dtor.
	//
	EE->runStaticConstructorsDestructors(Mod,false);
	int ret=EE->runFunctionAsMain( EntryFn, Args, envp );
	EE->runStaticConstructorsDestructors(Mod,true);
	return ret;

実行すると・・・・

C:\Users\hoge\Documents\clang-interpreter>clang-interpreter.exe sample_3.cpp
LLVM ERROR: Could not resolve external global address: __dso_handle
Stack dump:
0.      Running pass 'X86 Machine Code Emitter' on function '@__cxx_global_var_init'

oooooops!!
Global ctor/dtor が呼ばれるように LLVM にパッチを当てます。

LLVMのパッチ

Bug 9213 - Static initializers (C++) are not called with Microsoft Visual Studio Linker
http://llvm.org/bugs/show_bug.cgi?id=9213

llvm\tools\clang\lib\CodeGen\CGDeclCXX.cpp

void
CodeGenFunction::EmitCXXGlobalDtorRegistration(llvm::Constant *DtorFn,
                                               llvm::Constant *DeclPtr) {
  // Generate a global destructor entry if not using __cxa_atexit.
  if (!CGM.getCodeGenOpts().CXAAtExit) {
            
            :
            :
            :
            :
            
#if 1
/*Patch
http://llvm.org/bugs/show_bug.cgi?id=9213
Bug 9213 - Static initializers (C++) are not called with Microsoft Visual Studio Linker
*/
  const llvm::FunctionType *AtExitFnTy =
    llvm::FunctionType::get(ConvertType(getContext().IntTy), Params, false);
  llvm::Constant *AtExitFn = CGM.CreateRuntimeFunction(AtExitFnTy,
                                                       "atexit");
  if (llvm::Function *Fn = dyn_cast<llvm::Function>(AtExitFn))
    Fn->setDoesNotThrow();

  llvm::Value *Args[3] = { llvm::ConstantExpr::getBitCast(DtorFn, DtorFnTy),
                           llvm::ConstantExpr::getBitCast(DeclPtr, Int8PtrTy),
                           llvm::ConstantPointerNull::get(VoidPtrTy) };
  Builder.CreateCall(AtExitFn, &Args[0], llvm::array_endof(Args));
#else
  /*original*/
  // Get the __cxa_atexit function type
  // extern "C" int __cxa_atexit ( void (*f)(void *), void *p, void *d );
  const llvm::FunctionType *AtExitFnTy =
    llvm::FunctionType::get(ConvertType(getContext().IntTy), Params, false);

  llvm::Constant *AtExitFn = CGM.CreateRuntimeFunction(AtExitFnTy,
                                                       "__cxa_atexit");

  llvm::Constant *Handle = CGM.CreateRuntimeVariable(Int8PtrTy,
                                                     "__dso_handle");
  llvm::Value *Args[3] = { llvm::ConstantExpr::getBitCast(DtorFn, DtorFnTy),
                           llvm::ConstantExpr::getBitCast(DeclPtr, Int8PtrTy),
                           llvm::ConstantExpr::getBitCast(Handle, Int8PtrTy) };
  Builder.CreateCall(AtExitFn, &Args[0], llvm::array_endof(Args));
#endif
}

結果は・・・

ctorは呼び出されましたが、何故かdtorが呼び出されません、、、、何ででしょうか?
分からないので次に進みます。

動かしてみた。(STL)

//sample_4.cpp
#include<vector>
int main(int argc, char*argv[]){
	std::vector<int> v;
}

LLVM ERROR: Program used external function '_ZdlPv' which could not be resolved!

Stack dump:
0.      Running pass 'X86 Machine Code Emitter' on function '@_ZNSaIiE10deallocateEPij'

動きません。
諦めて次に進みます。

動かしてみた。(windows.h)

#include<windows.h>
int main(int argc, char*argv[]){
    DWORD t=MessageBox(NULL,"foo","bar", MB_OK);
    return 0;
}

C:\Users\hoge\Documents\clang-interpreter>clang-interpreter.exe sample_5.cpp
In file included from sample_5.cpp:1:
In file included from C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\\include/windows.h:201:
In file included from C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\\include/ole2.h:37:
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\\include/objbase.h(239) :  error:
      unknown type name 'IUnknown'
        static_cast<IUnknown*>(*pp);    // make sure everyone derives ...
                    ^
1 error generated.
こちらを参考にしてヘッダファイルを修正します。

http://www.nakaguchi.org/index.php?OpenCV/Tips

修正するファイルと行番号。
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include\WinBase.h
Line 235.

windows系のヘッダファイルを書き換えるわけなのでご自分の判断でお願いします。

Before.

extern "C++"
{
    template<typename T> void** IID_PPV_ARGS_Helper(T** pp) 
    {
        static_cast<IUnknown*>(*pp);    // make sure everyone derives from IUnknown
        return reinterpret_cast<void**>(pp);
    }
}

After.

extern "C++"
{
    #include <wtypes.h>       //  <-------------------- Append.
    #include <unknwn.h>       //  <--------------------
    template<typename T> void** IID_PPV_ARGS_Helper(T** pp) 
    {
        static_cast<IUnknown*>(*pp);    // make sure everyone derives from IUnknown
        return reinterpret_cast<void**>(pp);
    }
}

気を取り直して再度実行すると、

C:\Users\hoge\Documents\clang-interpreter>clang-interpreter.exe sample_5.cpp
LLVM ERROR: Program used external function 'MessageBoxA' which could not be resolved!
Stack dump:
0.      Running pass 'X86 Machine Code Emitter' on function '@main'

LLVMのMLで下記スレッドを見つけました、
http://lists.cs.uiuc.edu/pipermail/llvmdev/2010-August/033984.html
llvm::ExecutionEngine::addGlobalMapping へ外部関数を登録すればいい?
今回は諦めました。

問題点

素直に、linux/macの環境で開発すれば良いのかもしれない・・・
LLVM3.0で解決しているかもしれない?(未調査です)

まとめ

下記の問題があるものの、インタープリタ上でC++が動きました。

C言語の標準ライブラリ(printf/fopen...)は呼び出すことが出来たので、小規模なC++のコードを書いて動作確認を行うインタプリタ的な用途に使用できそうでした。

あと、今回はエラーの原因を特定するためデバッガでJITの内部動作を追ったので、llvmの内部構造がおぼろげながら見えてきました。
今ならコマンドラインから「実行する関数名・引数」を与えて、実行した関数の返値を表示するプログラムが書けそうな気がします。そのうち作ってみるつもりです。

//foo.cpp
int add(int a, int b){
	return a+b;
}
int sub(int a, int b){
	return a-b;
}

動作イメージ。

>my_cpp_interpreter.exe foo.cpp add 1 2
3