Emscripten教程之代碼可移植性與限制(一)

Emscripten教程之代碼可移植性與限制(一)

翻譯:雲荒杯傾
本文是Emscripten-WebAssembly專欄系列文章之一,更多文章請查看專欄。
也能夠去做者的博客閱讀文章。
歡迎加入Wasm和emscripten技術交流羣,羣聊號碼:939206522。html

Emscripten代碼移植主題涵蓋了將C、C++代碼移植到Emscripten時須要考慮的全部核心考慮問題,以及通常的編碼和調試指南。
共有如下主題。c++

代碼可移植性與限制、emscripten的運行環境、鏈接C++與JavaScript、文件和文件系統、多媒體數據和圖形、調試、多線程編程支持、移植SIMD代碼、Asyncify、Emterpreter

每一部份內容都比較多,本文主要講第一部分,代碼可移植性與限制。下面是正文:git

代碼可移植性與限制

Emscripten幾乎能夠編譯任何可移植的c/c++代碼到JavaScript。但因爲瀏覽器環境限制和Emscripten編譯出來的代碼的限制,一些代碼爲了能被編譯須要作改動,本文就幫咱們找出這部分代碼。程序員

一、關於可移植性的指導


本節解釋了哪些類型的代碼是不可移植的(或者更難於移植);哪些代碼能夠編譯,但會運行得很慢。開發人員可使用這些信息來評估移植代碼和重寫代碼的工做量。github

1.1 不能編譯的代碼

爲了使Emscripten工做,下面類型的代碼須要重寫。(理論上,在使用模擬的狀況下,可使用Emscripten解決這些問題,但速度很是慢。)web

  • 代碼是多線程的,並使用共享狀態。JavaScript有線程(web workers),但它們不能共享狀態。它們傳遞消息postMessage()。
Note:
若是JavaScript標準機構將共享狀態添加到webworker中,支持多線程代碼將成爲可能。
  • 代碼依賴於大端序架構。Emscripten編譯的代碼目前須要一個little-endian主機運行,這種主機佔了鏈接到互聯網的99%的機器。這是由於JavaScript類型化數組服從主機字節序,LLVM須要知道目標字節序是什麼。
  • 依賴於x86對齊方式的代碼。x86容許未對齊的內存讀寫(例如,您能夠從一個非偶數地址讀取一個16位的值),可是其餘的架構不是。對於emscripten生成的JavaScript,其內存對齊方式是未定義的。若是您使用

SAFE_HEAP = 1構建您的代碼,那麼您將獲得一個清晰的運行時異常,參見調試編程

  • 使用本機環境的底層特性的代碼,例如setjmp / longjmp涉及的本地堆棧操做。(we support proper setjmp/longjmp, i.e.,

jumping down the stack, but not jumping up to an unwound stack, which is undefined behavior).segmentfault

  • 掃描寄存器或堆棧的代碼。由於在寄存器或堆棧上的變量多是被放置到一個並不能被掃描的js局部變量裏保存的。
NOTE:
若是你是一個喜歡本身寫垃圾回收程序的程序員,可能對這類代碼比較熟。。。
  • 具備特定於體系結構的內聯彙編(好比包含x86代碼的asm())的代碼是不可移植的。這段代碼須要用可移植的C或C++來替換。有時,代碼庫會將可移植的代碼和可選的內聯程序集寫在一塊兒做爲優化,你須要找到一個選項使內聯彙編代碼不可用。

1.2 能編譯可是運行得比較慢的代碼

Note:
當你要優化代碼的時候,就會知道了解這些事項是有用的。

下面類型的代碼會被編譯,可是可能運行的很慢:windows

  • 64位整型變量。數學運算(+,-,*,/)是慢的,由於它們是被模擬的。這是由於JavaScript沒有本地64位int類型,所以這是不可避免的。
  • C++異常。在JavaScript中,這些代碼一般會使JavaScript引擎關閉各類優化。所以,在- o1和上面的默認狀況下,異常會被關閉。要從新啓用它們,請運行emcc與- s DISABLE_EXCEPTION_CATCHING= 0。
  • setjmp also prevents relooping around it,迫使咱們使用一種效率較低的方法來模擬控制流。

二、API限制


瀏覽器環境和JavaScript不一樣於C/C++一般運行的本地環境。這些差別對如何調用和使用本地API施加了一些限制。本部分列出了一些比較明顯的限制。api

2.1 網絡

Emscripten支持libc庫的網絡函數,但您必須限制他們是異步(非阻塞)操做。這是由於底層的JavaScript網絡函數是異步的。

2.2 文件系統

Emscripten支持libc文件系統函數,C /C++代碼能夠以正常方式編寫。

在瀏覽器環境中運行的代碼是沙盒sandboxed,而且不直接訪問本地文件系統。而後,Emscripten就建立了一個虛擬文件系統,它能夠預裝數據,或者連接到url來懶加載。這會影響同步文件系統函數調用以及一個項目如何被編譯。關於這方面,請參見文件系統概述

2.3 主函數死循環

瀏覽器事件模型使用合做模式的多任務處理——每一個事件都有一個運行的「turn」,而後必須將控制權返回給瀏覽器事件循環,這樣其餘事件就能夠處理了。
HTML頁面掛起的一個常見緣由是JavaScript未完成而且未將控制權返回給瀏覽器。

這將影響含有死循環的主函數的代碼編寫。有關更多信息,請參見Emscripten Runtime環境

三、函數指針的問題


函數指針有三個主要的問題:
一、指針類型轉換會引發指針調用失敗。

針對函數聲明時的簽名不一樣,函數指針會被存儲到不一樣的表中。當一個函數被調用時,它會在與當前函數指針簽名關聯的表中搜索它。若是你進行了指針類型轉換,而指針和全部的表並無被修改,則調用代碼將在錯誤的表中查找。而錯誤的表中實際上極可能並無一個叫該名字的指針,這樣就出錯了。

例如,一個聲明爲int(int)(返回int,接收int)的函數,會被添加到表FUNCTION_TABLE_ii。若是您將一個指向該函數指針投射到void(int)(不返回,接收int),那麼代碼將在FUNCTION_TABLE_vi中查找函數。

你可能看到編譯警告:

warning: implicit declaration of function

推薦的解決方案是重構代碼以免這種狀況,以下面的Asm指針轉換所描述的那樣。

二、當你使用-o2以及更高優化級別的時候,比較不一樣類型的函數指針會產生錯誤的結果,而錯誤的函數指針可能更具誤導性。要檢查你的代碼出問題的緣由,能夠將aliasing_function_pointer設爲零,(- s aliasing_function_pointer= 0)進行編譯。

NOTE:
在asm.js中,函數指針存儲在特定函數類型的表中。如FUNCTION_TABLE_ii。

在較低級別的優化中,每一個函數指針在全部函數類型表上都有一個唯一的索引值(一個函數指針只在其中一個表的某個索引位置存在,在全部其餘表中
這個索引位置都是一個空槽)。所以,比較函數指針(索引)能給出了一個準確的結果,但若是是試圖在錯誤的表中調用函數指針,將會拋出一個錯誤,由於該索引是空的。

在-o2和更高級別的優化設置下,表被優化,以致於全部函數指針都在順序索引中。這是一個有用的優化,由於若是沒有全部空槽,表就更緊湊,
但它確實意味着函數索引再也不是「全局」的唯一(由於一個函數指針在這張表中的索引位置與在另外一張表中的索引位置不一樣了)。此時須要一張特定的表
和在這樣表中的特定位置索引纔可以惟一索引到一個函數。

所以,高級別的優化編譯:
一、因爲不一樣類型的函數能夠有相同的索引(儘管在不一樣的表中),函數指針的比較可能會產生錯誤的結果。
二、函數指針代碼中的錯誤更難於調試,由於它們致使錯誤的代碼被調用,而不是顯式的錯誤(就像在表中的「漏洞」中那樣)。

三、結構體按值傳遞時,老版本的clang會爲c和c++代碼生成兩種不一樣的代碼,這兩種格式的代碼不兼容,你可能會收到一個警告。
解決方案是按引用傳遞結構體,或者不要在有結構體的位置混淆c和c++(好比,重命名.c爲.cpp)。

Asm指針轉換

如上所述,在asm.js模式下,函數指針必須使用正確的類型調用,不然調用將失敗。這是由於在函數聲明的時候,每一個函數指針會基於這個函數的簽名被存儲在一個特定的表中: 將指針轉換爲另外一個類型會致使調用代碼在錯誤的位置(表)查找函數指針。

NOTE:
對於每種類型的函數指針都有一個單獨的表,可讓JavaScript引擎知道每一個函數指針調用的確切類型,這樣也好進而優化他們。

有三種解決辦法,優先選擇第二種:

  • 調用者在函數指針被調用以前把指針類型再轉回原來的指針類型,這是有問題的由於這須要調用者知道它原來的類型是什麼,而調用者實際上並不知道一個指針以前的類型是什麼。
  • 建立一個不須要轉換的適配器函數,從適配器函數調用原始函數。
  • 使用EMULATE_FUNCTION_POINTER_CASTS。當你使用-s EMULATE_FUNCTION_POINTER_CASTS=1編譯時,Emscripten將發出代碼來模擬運行時的函數指針類型轉換,

添加額外的參數/刪除參數/更改參數類型/添加或刪除返回類型等。這能夠增長顯著的運行時開銷,所以不推薦,但值得嘗試。

四、特定瀏覽器限制


本頁面列出了一些 與Emscripten編譯出來的應用程序和遊戲相關的 主要瀏覽器的最新版本之間的差別:

  • 函數emscripten_get_now()以毫秒的形式返回一個wallclock time。

Opera 12.16和Windows谷歌Chrome 28.0.1500.95有一個限制,即計時器的精度僅爲毫秒。
在其餘主流瀏覽器上(IE10,firefox22,非windows的Chrome 28),也都是亞毫秒精度。

  • WebGL並無在Internet Explorer獲得徹底支持(至少在IE12以前)。
  • Opera 12.16對W3C的File API的支持有限。特別是它不支持createObjectURL函數,

這意味着不可能使用瀏覽器的圖像編解碼器來解碼Emscripten虛擬文件系統中的預加載文件。

  • Emscripten中OpenAL和SDL audio的支持依賴於Web Audio API

Emscripten代碼移植系列文章

Emscripten代碼移植主題系列文章是emscripten中文站點的一部份內容。
第一個主題介紹代碼可移植性與限制
第二個主題介紹Emscripten的運行時環境
第三個主題第一篇文章介紹鏈接C++和JavaScript
第三個主題第二篇文章介紹embind
第四個主題介紹文件和文件系統
第六個主題介紹Emscripten如何調試代碼

相關文章
相關標籤/搜索