2012年,Mozilla 的工程師 Alon Zakai 在研究 LLVM 編譯器時突發奇想:許多 3D 游戲都是用 C / C++ 語言寫的,如果能將 C / C++ 語言編譯成 JavaScript 代碼,它們不就能在瀏覽器里運行了嗎?眾所周知,JavaScript 的基本語法與 C 語言高度相似。
于是,他開始研究怎么才能實現這個目標,為此專門做了一個編譯器項目 Emscripten。這個編譯器可以將 C / C++ 代碼編譯成 JS 代碼,但不是普通的 JS,而是一種叫做 asm.js 的 JavaScript 變體。
)
asm.js的簡介
asm.js是一個中間語言被設計用于如C運行Web應用程序的。同時保持性能能夠高于標準的JS的計算機軟件。
對于傳統而言,C/C++編譯成JS有兩個最大的困難。
C / C++ 是靜態類型語言,而 JS 是動態類型語言
C / C++ 是手動內存管理,而 JS 依靠垃圾回收機制
這里需要說明的是,C是弱類型靜態語言。而js是弱類型動態語言。關于這方面也查閱了一部分資料。
簡而言之:
前兩者,弱/強類型指的是語言類型系統的類型檢查的嚴格程度。后兩者指的是變量與類型的綁定方法。
弱類型相對于強類型來說類型檢查更不嚴格,比如說允許變量類型的隱式轉換,允許強制類型轉換等等。強類型語言一般不允許這么做。
靜態類型指的是編譯器在compile time執行類型檢查,動態類型指的是編譯器(虛擬機)在runtime執行類型檢查。簡單地說,在聲明了一個變量之后,不能改變它的類型的語言,是靜態語言;能夠隨時改變它的類型的語言,是動態語言。因為動態語言的特性,一般需要運行時虛擬機支持。
asm.js 就是為了解決這兩個問題而設計的:它的變量一律都是靜態類型,并且取消垃圾回收機制。除了這兩點,它與 JavaScript 并無差異,也就是說,asm.js 是 JavaScript 的一個嚴格的子集,只能使用后者的一部分語法。
一旦 JavaScript 引擎發現運行的是 asm.js,就知道這是經過優化的代碼,可以跳過語法分析這一步,直接轉成匯編語言。另外,瀏覽器還會調用 WebGL 通過 GPU 執行 asm.js,即 asm.js 的執行引擎與普通的 JavaScript 腳本不同。這些都是 asm.js 運行較快的原因。據稱,asm.js 在瀏覽器里的運行速度,大約是原生代碼的50%左右。
值得注意的是
asm.js 沒有垃圾回收機制,所有內存操作都由程序員自己控制。asm.js 通過 TypedArray 直接讀寫內存。
var buffer = new ArrayBuffer(32768);
var HEAP8 = new Int8Array(buffer);
function compiledCode(ptr) {
HEAP[ptr] = 12;
return HEAP[ptr + 4];
}
如果設計到指針,也是一樣處理。
size_t strlen(char *ptr) {
char *curr = ptr;
while (*curr != 0) {
curr++;
}
return (curr - ptr);
}
上面代碼編譯成asm.js.就是下面這樣。
function strlen(ptr) {
ptr = ptr|0;
var curr = 0;
curr = ptr;
while (MEM8[curr]|0 != 0) {
curr = (curr + 1)|0;
}
return (curr - ptr)|0;
}
Emscripten 編譯器
雖然 asm.js 可以手寫,但是它從來就是編譯器的目標語言,要通過編譯產生。目前,生成 asm.js 的主要工具是 Emscripten。
Emscripten 的底層是 LLVM 編譯器,理論上任何可以生成 LLVM IR(Intermediate Representation)的語言,都可以編譯生成 asm.js。 但是實際上,Emscripten 幾乎只用于將 C / C++ 代碼編譯生成 asm.js
C/C++ ? LLVM ==> LLVM IR ? Emscripten ? asm.js
hello world
#include <iostream>
int main() {
std::cout << "Hello World!" << std::endl;
}
然后,將這個程序轉成asm.js
$ emcc hello.cc
$ node a.out.js
Hello World!
上面代碼中,emcc命令用于編譯源碼,默認生成a.out.js。使用 Node 執行a.out.js,就會在命令行輸出 Hello World。
注意,asm.js 默認自動執行main函數。
emcc是 Emscripten 的編譯命令。它的用法非常簡單。
asm.js的用途
asm.js 不僅能讓瀏覽器運行 3D 游戲,還可以運行各種服務器軟件,比如 Lua、Ruby 和 SQLite。 這意味著很多工具和算法,都可以使用現成的代碼,不用重新寫一遍。
另外,由于 asm.js 的運行速度較快,所以一些計算密集型的操作(比如計算 Hash)可以使用 C / C++ 實現,再在 JS 中調用它們。
真實的轉碼實例可以看一下 gzlib 的編譯,參考它的 Makefile 怎么寫。
參考鏈接
知乎關于弱類型,強類型,動態,靜態的區別--作者:Alan Li
Asm.js: The JavaScript Compile Target, by John Resig
Emscripten & asm.js: C++'s role in the modern web, by Alon Zakai
An Introduction to Web Development with Emscripten, by Charles Ofria