clangのexampleディレクトリに含まれる clang-interpreter をビルドして動かしてみました。
制限はあるもののLLVMのJITでC++が動きました。
コンパイル不要なのでインタプリタ的な動作になります。
対象
llvm\tools\clang\examples\clang-interpreter\
環境
- windows7 64bit
- llvm 2.9
- clang 2.9
- Visual Studio 2010
全ファイル。(Github)
https://github.com/ohtorii/clang_examples_visual_studio/tree/master/clang-interpreter
ビルド済みの実行ファイルあります。
clang-interpreter を Visual 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 へ外部関数を登録すればいい?
今回は諦めました。
まとめ
下記の問題があるものの、インタープリタ上で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