Edited at

LLVMに実行可能コードの生成をまかせたい

More than 1 year has passed since last update.


目的


  • 独自言語を作りたい

  • 実行可能コードの生成はLLVMにまかせたい

  • Windowsで開発したい(Visual Studioを使いたい)

独自フロントエンドからLLVM IRを出力し、あとはすべてLLVMに任せる。


Visual Studio Community 2017 と llvm-5.0.0

VS 2017にはCMake機能が含まれているので、VSだけでビルドが完結します。


  • VSをセットアップします


    • C++を追加

    • CMakeを追加(デフォで選択済み)

    • Windows SDKを追加

    • Pythonを追加



  • 適当にllvmとlldのソースを用意する


LLVM


  • "ファイル"->"開く"->"フォルダー"から、llvmのCMakeLists.txtがあるディレクトリを開きます

  • しばらく待ちます

  • 必要なら構成を変えて、例えばx86-Releaseなど、またしばらく待ちます

  • "CMake"->"インストール"->"CMakeFiles.txt"でビルド

  • 全部ビルドするので、かなーり待ちます

  • デフォルトの出力先はユーザーのディレクトリのようです

<user>/CMakeBuilds/<適当な名前>/build/<構成名>/install


LLD

基本的にllvmと同じ。

LLVM_CONFIG_PATHが無いとエラーが出ると思うので、"CMake"->"CMakeの設定を変更"からcmakeCommandArgsにllvm-configへのパスを指定する。頭の"-D"を忘れずに。

例:



-DLLVM_CONFIG_PATH=D:\\sdk\\llvm-5.0.0\\bin\\llvm-config.exe


  • "CMake"->"キャッシュ生成"で再作成

  • インストール


Hello World


プロジェクトの設定


  • インクルードパスの追加

    (llvmのインストール先)/include


  • ライブラリディレクトリの追加

    (llvmのインストール先)/lib


  • 追加の依存ファイル

    LLVMCore.lib;LLVMSupport.lib;LLVMBitWriter.lib;LLVMMC.lib;LLVMBinaryFormat.lib;LLVMObject.lib;LLVMBitReader.lib;LLVMMCParser.lib


  • プリプロセッサを追加

    _SCL_SECURE_NO_WARNINGS


  • C4146のエラーが出た場合、SDLのチェックを外すとビルドできます



IRの出力

MessageBox() を使って "Hello World" と表示するだけのコード。

#include "llvm/IR/LLVMContext.h"

#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"
#if LLVM_VERSION_MAJOR >= 4
#include "llvm/Bitcode/BitcodeWriter.h"
#else
#include "llvm/Bitcode/ReaderWriter.h"
#endif
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"

static llvm::Value* create_global_ptr(llvm::IRBuilder<>& builder, llvm::Module* module, llvm::Constant* c)
{
llvm::GlobalVariable* gv = new llvm::GlobalVariable(*module,
c->getType(), true, llvm::GlobalValue::PrivateLinkage, c);
#if LLVM_VERSION_MAJOR >= 4
gv->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Local);
#else
gv->setUnnamedAddr(true);
#endif
llvm::Value* zero = builder.getInt32(0);
llvm::Value* args[] = {zero, zero};
llvm::Value* ptr = builder.CreateInBoundsGEP(gv, args, "");
return ptr;
}

void create_helloworld()
{
#if LLVM_VERSION_MAJOR >= 4
llvm::LLVMContext context;
#else
llvm::LLVMContext& context = llvm::getGlobalContext();
#endif
llvm::IRBuilder<> builder(context);

// module
llvm::Module* module = new llvm::Module("top", context);

// create main function
llvm::Function* mainfunc = llvm::Function::Create(
llvm::FunctionType::get(builder.getVoidTy(), false),
llvm::Function::ExternalLinkage, "main", module);
mainfunc->setCallingConv(llvm::CallingConv::X86_StdCall);

llvm::BasicBlock* entry = llvm::BasicBlock::Create(context, "entrypoint", mainfunc);
builder.SetInsertPoint(entry);

// declare MessageBox
llvm::Function* messagebox_func;
{
// int32 MessageBoxW(void*, const wchar_t*, const wchar_t*, uint32);
std::vector<llvm::Type*> args;
args.push_back(builder.getInt8PtrTy());
args.push_back(builder.getInt16Ty()->getPointerTo());
args.push_back(builder.getInt16Ty()->getPointerTo());
args.push_back(builder.getInt32Ty());
llvm::FunctionType* messagebox_type = llvm::FunctionType::get(builder.getInt32Ty(), args, false);
messagebox_func = llvm::Function::Create(
messagebox_type, llvm::Function::ExternalLinkage, "MessageBoxW", module);
messagebox_func->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// call MessageBox(0, "Hello World", "caption", 0);
{
llvm::Constant* a1 = llvm::ConstantDataArray::get(context,
llvm::ArrayRef<uint16_t>((uint16_t*)L"Hello World\0", 12));
llvm::Constant* a2 = llvm::ConstantDataArray::get(context,
llvm::ArrayRef<uint16_t>((uint16_t*)L"caption\0", 8));

std::vector<llvm::Value*> args;
args.push_back(llvm::ConstantPointerNull::getNullValue(builder.getInt8PtrTy()));
args.push_back(create_global_ptr(builder, module, a1));
args.push_back(create_global_ptr(builder, module, a2));
args.push_back(builder.getInt32(0));// MB_OK
llvm::CallInst* inst = builder.CreateCall(messagebox_func, args);
inst->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// declare ExitProcess
llvm::Function* exitprocess_func;
{
// void ExitProcess(uint32)
llvm::FunctionType* exitprocess_type = llvm::FunctionType::get(
builder.getVoidTy(), llvm::ArrayRef<llvm::Type*>{builder.getInt32Ty()}, false);
exitprocess_func = llvm::Function::Create(
exitprocess_type, llvm::Function::ExternalLinkage, "ExitProcess", module);
exitprocess_func->setCallingConv(llvm::CallingConv::X86_StdCall);
}
// call ExitProcess(0)
{
llvm::CallInst* inst = builder.CreateCall(exitprocess_func, builder.getInt32(0));
inst->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// return;
builder.CreateRetVoid();

// dump IR code for debug
#if !defined(NDEBUG)
module->dump();
#endif

// write bitcode
#if LLVM_VERSION_MAJOR >= 4
std::error_code ec;
llvm::raw_fd_ostream os("helloworld.bc", ec, llvm::sys::fs::OpenFlags::F_None);
#else
std::string error_info;
llvm::raw_fd_ostream os("helloworld.bc", error_info, llvm::sys::fs::OpenFlags::F_None);
#endif
llvm::WriteBitcodeToFile(module, os);
os.close();
}


ビルド

出力されたIRをllcでビルド、lldでリンク。

d:\sdk\llvm-5.0.0\bin\llc.exe -march=x86 -filetype=obj -o=helloworld.o helloworld.bc

d:\sdk\lld-5.0.0\bin\lld.exe -flavor link /out:helloworld.exe /entry:main@0 /subsystem:windows helloworld.o /libpath:"C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86" kernel32.lib user32.lib

Windows SDKへのパスはインストールしているバージョンで場所が変わります。

Visual Studioで作ったexeよりもサイズが大きいのがきになるが、まあ成功した。


コンソール出力

デバッグのためにコンソールに書式付きで出力できると便利そうなので。

void writefln(const char* fmt, ...);

のような感じ。

#include "llvm/IR/LLVMContext.h"

#include "llvm/IR/Module.h"
#include "llvm/IR/IRBuilder.h"
#if LLVM_VERSION_MAJOR >= 4
#include "llvm/Bitcode/BitcodeWriter.h"
#else
#include "llvm/Bitcode/ReaderWriter.h"
#endif
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/Intrinsics.h"
#include <stdarg.h>

void create_writefln()
{
#if LLVM_VERSION_MAJOR >= 4
llvm::LLVMContext context;
#else
llvm::LLVMContext& context = llvm::getGlobalContext();
#endif
llvm::IRBuilder<> builder(context);

// module
llvm::Module* module = new llvm::Module("top", context);

// create function
llvm::Function* func;
{
// void writefln(const char*, ...)
std::vector<llvm::Type*> args;
args.push_back(builder.getInt8PtrTy());
bool is_var_args = true;
func = llvm::Function::Create(
llvm::FunctionType::get(builder.getVoidTy(), args, is_var_args),
llvm::Function::ExternalLinkage, "writefln", module);
func->setCallingConv(llvm::CallingConv::C);
}
llvm::Argument* args_[1];
assert(func->arg_size() == 1);
int i = 0;
for(llvm::Function::arg_iterator itr = func->arg_begin(); itr != func->arg_end(); itr++){
args_[i++] = itr;
}

llvm::BasicBlock* entry = llvm::BasicBlock::Create(context, "entrypoint", func);
builder.SetInsertPoint(entry);

// declare wvsprintfA
llvm::Function* wvsprintf_func;
{
// int32 wvsprintfA(char*, const char*, va_list)
std::vector<llvm::Type*> args;
args.push_back(builder.getInt8PtrTy());
args.push_back(builder.getInt8PtrTy());
args.push_back(builder.getInt8PtrTy());
llvm::FunctionType* func_type = llvm::FunctionType::get(builder.getInt32Ty(), args, false);
wvsprintf_func = llvm::Function::Create(
func_type, llvm::Function::ExternalLinkage, "wvsprintfA", module);
wvsprintf_func->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// declare GetStdHandle
llvm::Function* getstdhandle_func;
{
// void* GetStdHandle(uint32)
llvm::FunctionType* func_type = llvm::FunctionType::get(
builder.getInt8PtrTy(), llvm::ArrayRef<llvm::Type*>{builder.getInt32Ty()}, false);
getstdhandle_func = llvm::Function::Create(
func_type, llvm::Function::ExternalLinkage, "GetStdHandle", module);
getstdhandle_func->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// declare WriteConsoleA
llvm::Function* writeconsolea_func;
{
// int32 WriteConsoleA(void*, const char*, uint32, uint32*, void*)
std::vector<llvm::Type*> args;
args.push_back(builder.getInt8PtrTy());
args.push_back(builder.getInt8PtrTy());
args.push_back(builder.getInt32Ty());
args.push_back(builder.getInt32Ty()->getPointerTo());
args.push_back(builder.getInt8PtrTy());
llvm::FunctionType* func_type = llvm::FunctionType::get(
builder.getInt32Ty(), args, false);
writeconsolea_func = llvm::Function::Create(
func_type, llvm::Function::ExternalLinkage, "WriteConsoleA", module);
writeconsolea_func->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// char buf[256];
llvm::Value* buf = builder.CreateAlloca(builder.getInt8Ty(), builder.getInt32(256));
// int32 len_out[1];
llvm::Value* len_out = builder.CreateAlloca(builder.getInt32Ty());
// va_list ap[1];
llvm::Value* ap_ = builder.CreateAlloca(builder.getInt8PtrTy());
llvm::Value* ap = builder.CreateBitCast(ap_, builder.getInt8PtrTy());

// va_start(ap);
llvm::Function* vastart = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::vastart);
builder.CreateCall(vastart, ap);

// len = wvsprintfA(buf, fmt, ap);
llvm::Value* len;
{
std::vector<llvm::Value*> args;
args.push_back(buf);
args.push_back(args_[0]);
args.push_back(builder.CreateLoad(ap_));
llvm::CallInst* inst = builder.CreateCall(wvsprintf_func, args);
inst->setCallingConv(llvm::CallingConv::X86_StdCall);
len = inst;
}

// va_end(ap);
llvm::Function* vaend = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::vaend);
builder.CreateCall(vaend, ap);

// buf[len++] = '\r';
// buf[len++] = '\n';
builder.CreateStore(builder.getInt8('\r'), builder.CreateGEP(buf, len));
len = builder.CreateAdd(len, builder.getInt32(1));
builder.CreateStore(builder.getInt8('\n'), builder.CreateGEP(buf, len));
len = builder.CreateAdd(len, builder.getInt32(1));

// handle = GetStdHandle(-11);//stdout
llvm::Value* handle;
{
llvm::CallInst* inst = builder.CreateCall(getstdhandle_func, builder.getInt32(-11));
inst->setCallingConv(llvm::CallingConv::X86_StdCall);
handle = inst;
}

// WriteConsoleA(handle, buf, len, len_out, null);
{
std::vector<llvm::Value*> args;
args.push_back(handle);
args.push_back(buf);
args.push_back(len);
args.push_back(len_out);
args.push_back(llvm::ConstantPointerNull::getNullValue(builder.getInt8PtrTy()));
llvm::CallInst* inst = builder.CreateCall(writeconsolea_func, args);
inst->setCallingConv(llvm::CallingConv::X86_StdCall);
}

// return;
builder.CreateRetVoid();

// dump IR code for debug
#if !defined(NDEBUG)
module->dump();
#endif

// write bitcode
#if LLVM_VERSION_MAJOR >= 4
std::error_code ec;
llvm::raw_fd_ostream os("writefln.bc", ec, llvm::sys::fs::OpenFlags::F_None);
#else
std::string error_info;
llvm::raw_fd_ostream os("writefln.bc", error_info, llvm::sys::fs::OpenFlags::F_None);
#endif
llvm::WriteBitcodeToFile(module, os);
os.close();
}

void call_writefln(llvm::IRBuilder<> builder, llvm::ArrayRef<llvm::Value*> args)
{
llvm::Module* module = builder.GetInsertBlock()->getParent()->getParent();
llvm::Function* func = module->getFunction("writefln");
if(func == nullptr){
std::vector<llvm::Type*> args;
args.push_back(builder.getInt8PtrTy());
bool is_var_args = true;
func = llvm::Function::Create(
llvm::FunctionType::get(builder.getVoidTy(), args, is_var_args),
llvm::Function::ExternalLinkage, "writefln", module);
func->setCallingConv(llvm::CallingConv::C);
}

llvm::CallInst* inst = builder.CreateCall(func, args);
inst->setCallingConv(func->getCallingConv());
}

void create_helloworld_console()
{
#if LLVM_VERSION_MAJOR >= 4
llvm::LLVMContext context;
#else
llvm::LLVMContext& context = llvm::getGlobalContext();
#endif
llvm::IRBuilder<> builder(context);

// module
llvm::Module* module = new llvm::Module("top", context);

// create main function
llvm::Function* mainfunc = llvm::Function::Create(
llvm::FunctionType::get(builder.getVoidTy(), false),
llvm::Function::ExternalLinkage, "main", module);
mainfunc->setCallingConv(llvm::CallingConv::X86_StdCall);

llvm::BasicBlock* entry = llvm::BasicBlock::Create(context, "entrypoint", mainfunc);
builder.SetInsertPoint(entry);

// writefln("Hello World");
{
std::vector<llvm::Value*> args;
args.push_back(builder.CreateGlobalStringPtr("Hello World"));
call_writefln(builder, args);
}

// writefln("[%s]", "oops");
{
std::vector<llvm::Value*> args;
args.push_back(builder.CreateGlobalStringPtr("[%s]"));
args.push_back(builder.CreateGlobalStringPtr("oops"));
call_writefln(builder, args);
}

// writefln("%s:%d", "foo", 123);
{
std::vector<llvm::Value*> args;
args.push_back(builder.CreateGlobalStringPtr("%s:%d"));
args.push_back(builder.CreateGlobalStringPtr("foo"));
args.push_back(builder.getInt32(123));
call_writefln(builder, args);
}

// writefln("%d - %s - %s", 456, "bar", "hoge");
{
std::vector<llvm::Value*> args;
args.push_back(builder.CreateGlobalStringPtr("%d - %s - %s"));
args.push_back(builder.getInt32(456));
args.push_back(builder.CreateGlobalStringPtr("bar"));
args.push_back(builder.CreateGlobalStringPtr("hoge"));
call_writefln(builder, args);
}

// return;
builder.CreateRetVoid();

// dump IR code for debug
#if !defined(NDEBUG)
module->dump();
#endif

// write bitcode
#if LLVM_VERSION_MAJOR >= 4
std::error_code ec;
llvm::raw_fd_ostream os("helloworld_console.bc", ec, llvm::sys::fs::OpenFlags::F_None);
#else
std::string error_info;
llvm::raw_fd_ostream os("helloworld_console.bc", error_info, llvm::sys::fs::OpenFlags::F_None);
#endif
llvm::WriteBitcodeToFile(module, os);
os.close();
}


ビルド

d:\sdk\llvm-5.0.0\bin\llc.exe -filetype=obj -o=writefln.o writefln.bc

d:\sdk\llvm-5.0.0\bin\llc.exe -filetype=obj -o=helloworld_console.o helloworld_console.bc
d:\sdk\lld-5.0.0\bin\lld.exe -flavor link /out:helloworld_console.exe /entry:main@0 /subsystem:console helloworld_console.o writefln.o /libpath:"C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86" kernel32.lib user32.lib

subsystemをconsoleにしないと出ない。


参考

Getting Started with the LLVM System using Microsoft Visual Studio

LLVM フレームワークで実用的なコンパイラーを作成する: 第 1 回

きつねさんでもわかるLLVM


Visual Studio Express 2013 と llvm-3.5.0 の場合

古い情報ですが残しておきます。


用意する物


  • Visual Studio Express 2013

  • Python(CMakeが必要とする)

  • CMake


llvmのビルド

llvm-x.x.x-src をダウンロードし適当に展開する。

CMake GUIを起動

設定例:

Source path: D:/tmp/llvm-3.5.0.src

Build path: D:/tmp/llvm-3.5.0.build
Entry
CMAKE_INSTALL_PREFIX: D:/sdk/llvm-3.5.0

CMAKE_INSTALL_PREFIX にインストール先を設定する。デフォルトでは"C:\Program Files~"が選ばれるので、別の場所にしたほうがよい。

Configureして問題なければ、Generateし、"Visual Studio 12 2013", "Use default compilers"を選択し、Ok。

LLVM.sln ができているので、CMakePredefinedTargets/INSTALLプロジェクトをビルドする。

成功したら、インストール先にlibやincludeなどができている。


lldのビルド

さくっとビルドできないので、Windows向けのclangをインストールしてlldだけ持ってきたほうが楽

lld-x.x.x-src をダウンロードして、llvmと同じようにCMakeする。

設定例:

Source path: D:/tmp/lld-3.5.0.src

Build path: D:/tmp/lld-3.5.0.build
Entry
CMAKE_INSTALL_PREFIX: D:/sdk/lld-3.5.0
LLD_PATH_TO_LLVM_BUILD: D:/sdk/llvm-3.5.0

これだけで十分な様な気がするが、色々問題が発生する。

(lld src)/lib/ReaderWriter/ELF/CMakeLists.txt を修正する。

foreach内でサブディレクトリを追加しているが、機能していない。

if文内にある

//CMakeLists.txt

という文字列を

/CMakeLists.txt

に直す。スラッシュが多い。2カ所ある。

CMake Error at D:/sdk/llvm-3.5.0/share/llvm/cmake/TableGen.cmake:13 (message):

LLVM_MAIN_SRC_DIR not set

LLVM_MAIN_SRC_DIRが設定されていないと言われる。Descriptionが表示されないため詳細不明だが、とりあえずインストールされたLLVMのパスを設定すればうまくいった。

例:

LLVM_MAIN_SRC_DIR: D:/sdk/llvm-3.5.0

CMake Error at D:/sdk/llvm-3.5.0/share/llvm/cmake/AddLLVM.cmake:443 (set_output_directory):

set_output_directory Function invoked with incorrect arguments for function
named: set_output_directory

関数の引数が不正ですとか訳わからん状態ですが・・・

ここによると何かバグっぽいので

(llvm install)/share/llvm/cmake/AddLLVM.cmake

LLVM_RUNTIME_OUTPUT_INTDIRを使っているところ(2カ所)の前に

set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)

set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib)

を追加

CMake Error at D:/sdk/llvm-3.5.0/share/llvm/cmake/AddLLVM.cmake:623 (string):

string sub-command REPLACE requires at least four arguments.

まったくわけがわからないが、LLVM_LIBRARY_DIRが未定義のように見える。試しにLLVM_BINARY_DIRに置き換え。

  set(LLVM_SOURCE_DIR ${LLVM_MAIN_SRC_DIR})

set(LLVM_BINARY_DIR ${LLVM_BINARY_DIR})
string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} LLVM_TOOLS_DIR ${LLVM_TOOLS_BINARY_DIR})
string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} LLVM_LIBS_DIR ${LLVM_LIBRARY_DIR}) <---ここ

Generateが成功したら、lld.slnを開いて、INSTALLプロジェクトをビルドする。