是否可以將不同語言編譯到LLVM IR層面鏈接?如果可以,與傳統的編譯為目標代碼鏈接有什麼不同?

完整的LLVM工具鏈是可以拆成這樣一系列步驟:

源代碼編譯為IR,IR之間互相鏈接,鏈接後IR編譯為目標文件,目標文件鏈接為可執行文件。

傳統上的跨語言鏈接是在目標文件層面進行的,那麼是否可以在IR層面跨語言鏈接(即不同語言的IR鏈接為同一個IR,並編譯至同一個目標文件)?

SO已有的問題:LLVM as base compiler for different languages


stackoverflow的解釋已經很清楚了,他說的是Rust和C,其實SWIFT和C++也是可以的。也感謝樓主邀請我回答這個問題,因為這個問題,我想把SWIFT和C++ / Rust 在LLVM IR混合,所以查了一下SWIFT和Rust的語法,以及如何與C/C++代碼混合,了解了SWIFT的橋接機制。

那麼,我接下來展示一下如何把SWIFT和C++ / Rust 的LLVM IR融合。

首先我有一段簡單的SWIFT代碼,我命名為main.swift

print("The integer from C++ is (magic())")

即列印來自magic函數的返回結果,而這個magic()將會來自C++。不過我們這裡不會如傳統的利用SWIFT編譯器來橋接編譯,而是利用LLVM,把C++吐出的LLVM IR拿過來。

那麼,C++代碼我命名為magic.cpp,代碼如下所示:

class Magic
{
public:
Magic(int num) : number_(num)
{
}

int getMagicNumber() const
{
return number_;
}

private:
int number_;
};

extern "C" int magic()
{
return Magic(42).getMagicNumber();
}

也就是magic函數會返回數字42,同時我利用extern "C",採用C鏈接,防止C++ Name Mangling。

同時,為了讓SWIFT代碼知道有magic函數,我利用一個橋接的頭文件,我命名為bridge.h

int magic();

準備工作差不多了,接下來我們分別吐出LLVM IR。

首先是C++部分,我們利用clang++來完成,命令是:

clang++ -c -emit-llvm magic.cpp -o magic.bc

隨後是SWIFT部分,我們利用swiftc來完成,命令是:

swiftc -emit-bc main.swift -import-objc-header bridge.h -o main.bc

這裡的 -import-objc-header bridge.h則是橋接使用,讓SWIFT識別出來magic函數。

隨後,我們利用llvm-link,把產生的main.bc, magic.bc合併稱為一個LLVM IR的bitcode(bc)文件。

llvm-link main.bc magic.bc -o swiftcxx.bc

這個時候我們已經得到了SWIFT和C++混合的LLVM IR文件,名字叫做swiftcxx.bc.

然後,我們利用llc編譯這個混合LLVM IR文件,產生一個目標文件,我命名為swiftcxx.o

llc swiftcxx.bc -filetype=obj -o swiftcxx.o

隨後,我們利用鏈接器產生可執行文件, 我命名為swiftcxx

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld swiftcxx.o -force_load /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_macosx.a -framework CoreFoundation -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk -lobjc -lSystem -arch x86_64 -L /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx -rpath /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx -macosx_version_min 10.9.0 -no_objc_category_merging -o swiftcxx

隨後,我們執行swiftcxx.

./swiftcxx

這結果如你所願。

或許有同學會問為什麼我怎麼知道這麼複雜的鏈接器參數,其實我是不知道的,你可以使用swiftc swiftcxx.o -v 來查看,只是我想演示如何單獨使用工具來達到這一切。

那麼,我們再次嘗試一下Rust,也就是SWIFT調用Rust的代碼,我們main.swift 和 bridge.h 都不變,但是我們用Rust來實現magic函數。

我命名為rust.rs,返回值為47,區分於C++的42,不過SWIFT的print字元串內容The integer from C++ 我並沒有修改,這一點希望不要感到驚訝。

#![crate_type = "dylib"]

#[no_mangle]
pub extern fn magic() -&> i32 {
return 47;
}

隨後,使用rustc命令產生LLVM IR文件:

rustc --emit llvm-bc rust.rs -o rust.bc

接下來,我們利用llvm-link把SWIFT的main.bc 與 rust.bc進行融合,我命名為 swiftrust.bc.

llvm-link main.bc rust.bc -o swiftrust.bc

然後利用llc把產生的SWIFT與Rust混合swiftrust.bc編譯成目標文件swiftrust.o

llc swiftrust.bc -filetype=obj -o swiftrust.o

接下來,我們使用鏈接器,把swiftrust.o編譯成可執行文件swiftrust

這結果如我們所想。


雖然技術上可以這麼做。實際上應該不比直接鏈接目標文件容易多少


推薦閱讀:

libgccjit和LLVM相比,有哪些優點?
LLVM相比於JVM,有哪些技術優勢?
過程間和過程內的數據流分析演算法在類似LLVM的IR或HotSpot C1的HIR中,是如何實現的?

TAG:編程語言 | 編譯原理 | 編譯器 | LLVM | 中間表示編譯原理 |