asm.js和Emscripten

2012年,Mozilla 的工程師 Alon Zakai 在研究 LLVM 編譯器時突發(fā)奇想:許多 3D 游戲都是用 C / C++ 語言寫的,如果能將 C / C++ 語言編譯成 JavaScript 代碼,它們不就能在瀏覽器里運行了嗎?眾所周知,JavaScript 的基本語法與 C 語言高度相似。

于是,他開始研究怎么才能實現(xiàn)這個目標(biāo),為此專門做了一個編譯器項目 Emscripten。這個編譯器可以將 C / C++ 代碼編譯成 JS 代碼,但不是普通的 JS,而是一種叫做 asm.js 的 JavaScript 變體。

關(guān)于asm.js的理解

)

asm.js的簡介

asm.js是一個中間語言被設(shè)計用于如C運行Web應(yīng)用程序的。同時保持性能能夠高于標(biāo)準(zhǔn)的JS的計算機軟件。

對于傳統(tǒng)而言,C/C++編譯成JS有兩個最大的困難。

C / C++ 是靜態(tài)類型語言,而 JS 是動態(tài)類型語言
C / C++ 是手動內(nèi)存管理,而 JS 依靠垃圾回收機制

這里需要說明的是,C是弱類型靜態(tài)語言。而js是弱類型動態(tài)語言。關(guān)于這方面也查閱了一部分資料。
關(guān)于強弱類型的圖

簡而言之:

前兩者,弱/強類型指的是語言類型系統(tǒng)的類型檢查的嚴(yán)格程度。后兩者指的是變量與類型的綁定方法。

弱類型相對于強類型來說類型檢查更不嚴(yán)格,比如說允許變量類型的隱式轉(zhuǎn)換,允許強制類型轉(zhuǎn)換等等。強類型語言一般不允許這么做。
靜態(tài)類型指的是編譯器在compile time執(zhí)行類型檢查,動態(tài)類型指的是編譯器(虛擬機)在runtime執(zhí)行類型檢查。簡單地說,在聲明了一個變量之后,不能改變它的類型的語言,是靜態(tài)語言;能夠隨時改變它的類型的語言,是動態(tài)語言。因為動態(tài)語言的特性,一般需要運行時虛擬機支持。

asm.js 就是為了解決這兩個問題而設(shè)計的:它的變量一律都是靜態(tài)類型,并且取消垃圾回收機制。除了這兩點,它與 JavaScript 并無差異,也就是說,asm.js 是 JavaScript 的一個嚴(yán)格的子集,只能使用后者的一部分語法。

一旦 JavaScript 引擎發(fā)現(xiàn)運行的是 asm.js,就知道這是經(jīng)過優(yōu)化的代碼,可以跳過語法分析這一步,直接轉(zhuǎn)成匯編語言。另外,瀏覽器還會調(diào)用 WebGL 通過 GPU 執(zhí)行 asm.js,即 asm.js 的執(zhí)行引擎與普通的 JavaScript 腳本不同。這些都是 asm.js 運行較快的原因。據(jù)稱,asm.js 在瀏覽器里的運行速度,大約是原生代碼的50%左右。

值得注意的是
asm.js 沒有垃圾回收機制,所有內(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è)計到指針,也是一樣處理。

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-圖

Emscripten 的底層是 LLVM 編譯器,理論上任何可以生成 LLVM IR(Intermediate Representation)的語言,都可以編譯生成 asm.js。 但是實際上,Emscripten 幾乎只用于將 C / C++ 代碼編譯生成 asm.js

C/C++ ? LLVM ==> LLVM IR ? Emscripten ? asm.js

圖解asm.js的過程

hello world

#include <iostream>

int main() {
  std::cout << "Hello World!" << std::endl;
}

然后,將這個程序轉(zhuǎn)成asm.js

$ emcc hello.cc
$ node a.out.js
Hello World!

上面代碼中,emcc命令用于編譯源碼,默認生成a.out.js。使用 Node 執(zhí)行a.out.js,就會在命令行輸出 Hello World。
注意,asm.js 默認自動執(zhí)行main函數(shù)。
emcc是 Emscripten 的編譯命令。它的用法非常簡單。

asm.js的用途

asm.js 不僅能讓瀏覽器運行 3D 游戲,還可以運行各種服務(wù)器軟件,比如 Lua、Ruby 和 SQLite。 這意味著很多工具和算法,都可以使用現(xiàn)成的代碼,不用重新寫一遍。
另外,由于 asm.js 的運行速度較快,所以一些計算密集型的操作(比如計算 Hash)可以使用 C / C++ 實現(xiàn),再在 JS 中調(diào)用它們。

真實的轉(zhuǎn)碼實例可以看一下 gzlib 的編譯,參考它的 Makefile 怎么寫。

參考鏈接

wikipedia - asm.js

wiki地址-Strong typing

wiki地址-Type system

阮一峰老師的網(wǎng)絡(luò)日志

知乎關(guān)于弱類型,強類型,動態(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容