2012年偶房,Mozilla 的工程師 Alon Zakai 在研究 LLVM 編譯器時(shí)突發(fā)奇想:許多 3D 游戲都是用 C / C++ 語言寫的寞酿,如果能將 C / C++ 語言編譯成 JavaScript 代碼漫贞,它們不就能在瀏覽器里運(yùn)行了嗎革半?眾所周知医增,JavaScript 的基本語法與 C 語言高度相似谆构。
于是,他開始研究怎么才能實(shí)現(xiàn)這個(gè)目標(biāo)铡买,為此專門做了一個(gè)編譯器項(xiàng)目 Emscripten更鲁。這個(gè)編譯器可以將 C / C++ 代碼編譯成 JS 代碼,但不是普通的 JS奇钞,而是一種叫做 asm.js 的 JavaScript 變體岁经。
)
asm.js的簡(jiǎn)介
asm.js是一個(gè)中間語言被設(shè)計(jì)用于如C運(yùn)行Web應(yīng)用程序的。同時(shí)保持性能能夠高于標(biāo)準(zhǔn)的JS的計(jì)算機(jī)軟件蛇券。
對(duì)于傳統(tǒng)而言缀壤,C/C++編譯成JS有兩個(gè)最大的困難。
C / C++ 是靜態(tài)類型語言纠亚,而 JS 是動(dòng)態(tài)類型語言
C / C++ 是手動(dòng)內(nèi)存管理塘慕,而 JS 依靠垃圾回收機(jī)制
這里需要說明的是,C是弱類型靜態(tài)語言蒂胞。而js是弱類型動(dòng)態(tài)語言图呢。關(guān)于這方面也查閱了一部分資料。
簡(jiǎn)而言之:
前兩者骗随,弱/強(qiáng)類型指的是語言類型系統(tǒng)的類型檢查的嚴(yán)格程度蛤织。后兩者指的是變量與類型的綁定方法。
弱類型相對(duì)于強(qiáng)類型來說類型檢查更不嚴(yán)格鸿染,比如說允許變量類型的隱式轉(zhuǎn)換指蚜,允許強(qiáng)制類型轉(zhuǎn)換等等。強(qiáng)類型語言一般不允許這么做涨椒。
靜態(tài)類型指的是編譯器在compile time執(zhí)行類型檢查摊鸡,動(dòng)態(tài)類型指的是編譯器(虛擬機(jī))在runtime執(zhí)行類型檢查。簡(jiǎn)單地說蚕冬,在聲明了一個(gè)變量之后免猾,不能改變它的類型的語言,是靜態(tài)語言囤热;能夠隨時(shí)改變它的類型的語言猎提,是動(dòng)態(tài)語言。因?yàn)閯?dòng)態(tài)語言的特性旁蔼,一般需要運(yùn)行時(shí)虛擬機(jī)支持锨苏。
asm.js 就是為了解決這兩個(gè)問題而設(shè)計(jì)的:它的變量一律都是靜態(tài)類型,并且取消垃圾回收機(jī)制牌芋。除了這兩點(diǎn)蚓炬,它與 JavaScript 并無差異松逊,也就是說躺屁,asm.js 是 JavaScript 的一個(gè)嚴(yán)格的子集,只能使用后者的一部分語法经宏。
一旦 JavaScript 引擎發(fā)現(xiàn)運(yùn)行的是 asm.js犀暑,就知道這是經(jīng)過優(yōu)化的代碼驯击,可以跳過語法分析這一步,直接轉(zhuǎn)成匯編語言耐亏。另外徊都,瀏覽器還會(huì)調(diào)用 WebGL 通過 GPU 執(zhí)行 asm.js,即 asm.js 的執(zhí)行引擎與普通的 JavaScript 腳本不同广辰。這些都是 asm.js 運(yùn)行較快的原因暇矫。據(jù)稱,asm.js 在瀏覽器里的運(yùn)行速度择吊,大約是原生代碼的50%左右李根。
值得注意的是
asm.js 沒有垃圾回收機(jī)制,所有內(nèi)存操作都由程序員自己控制几睛。asm.js 通過 TypedArray 直接讀寫內(nèi)存房轿。
var buffer = new ArrayBuffer(32768);
var HEAP8 = new Int8Array(buffer);
function compiledCode(ptr) {
HEAP[ptr] = 12;
return HEAP[ptr + 4];
}
如果設(shè)計(jì)到指針,也是一樣處理所森。
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 可以手寫,但是它從來就是編譯器的目標(biāo)語言焕济,要通過編譯產(chǎn)生纷妆。目前,生成 asm.js 的主要工具是 Emscripten晴弃。
Emscripten 的底層是 LLVM 編譯器凭需,理論上任何可以生成 LLVM IR(Intermediate Representation)的語言,都可以編譯生成 asm.js肝匆。 但是實(shí)際上粒蜈,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;
}
然后,將這個(gè)程序轉(zhuǎn)成asm.js
$ emcc hello.cc
$ node a.out.js
Hello World!
上面代碼中旗国,emcc命令用于編譯源碼枯怖,默認(rèn)生成a.out.js。使用 Node 執(zhí)行a.out.js能曾,就會(huì)在命令行輸出 Hello World度硝。
注意,asm.js 默認(rèn)自動(dòng)執(zhí)行main函數(shù)寿冕。
emcc是 Emscripten 的編譯命令蕊程。它的用法非常簡(jiǎn)單。
asm.js的用途
asm.js 不僅能讓瀏覽器運(yùn)行 3D 游戲驼唱,還可以運(yùn)行各種服務(wù)器軟件藻茂,比如 Lua、Ruby 和 SQLite。 這意味著很多工具和算法辨赐,都可以使用現(xiàn)成的代碼优俘,不用重新寫一遍。
另外掀序,由于 asm.js 的運(yùn)行速度較快帆焕,所以一些計(jì)算密集型的操作(比如計(jì)算 Hash)可以使用 C / C++ 實(shí)現(xiàn),再在 JS 中調(diào)用它們不恭。
真實(shí)的轉(zhuǎn)碼實(shí)例可以看一下 gzlib 的編譯叶雹,參考它的 Makefile 怎么寫。
參考鏈接
知乎關(guān)于弱類型换吧,強(qiáng)類型浑娜,動(dòng)態(tài),靜態(tài)的區(qū)別--作者: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