具有 JIT 支援的 Qemu.js:您仍然可以向後轉動肉末

幾年前法布里斯·貝拉德 由 jslinux 撰寫 是一個用 JavaScript 寫的 PC 模擬器。 之後至少還有更多 虛擬x86。 但據我所知,它們都是解釋器,而 Qemu(很早由同一位 Fabrice Bellard 編寫)以及可能任何有自尊的現代模擬器都使用來賓程式碼的 JIT 編譯為主機系統程式碼。 在我看來,是時候實現與瀏覽器解決的任務相反的任務了:將機器碼 JIT 編譯為 JavaScript,為此移植 Qemu 似乎是最合乎邏輯的。 看起來,為什麼 Qemu 有更簡單且用戶友好的模擬器 - 例如相同的 VirtualBox - 安裝並運行。 但 Qemu 有幾個有趣的功能

  • 開源
  • 無需內核驅動程式即可工作的能力
  • 能夠在口譯模式下工作
  • 支援大量主機和來賓架構

關於第三點,我現在可以解釋一下,其實在TCI模式下,解釋的並不是客戶機器指令本身,而是從中獲得的字節碼,但這並沒有改變本質——為了建構和運行新架構上的Qemu,如果幸運的話,一個C 編譯器就足夠了——可以推遲編寫程式碼產生器。

現在,經過兩年空閒時間悠閒地修改 Qemu 原始程式碼,一個工作原型出現了,您已經可以在其中運行 Kolibri OS 等。

什麼是 Emscripten

現在出現了很多編譯器,最後的結果就是JavaScript。 有些(例如 Type Script)最初旨在成為網頁編寫的最佳方式。 同時,Emscripten 是一種獲取現有 C 或 C++ 程式碼並將其編譯為瀏覽器可讀形式的方法。 在 此頁 我們收集了很多知名程式的連接埠: 這裡例如,您可以查看 PyPy - 順便說一下,他們聲稱已經有了 JIT。 事實上,並不是每個程式都可以簡單地在瀏覽器中編譯和運行 - 有很多程序 特徵,但是,您必須忍受這一點,因為同一頁上的銘文說“Emscripten 可以用來編譯幾乎任何 手提 C/C++ 程式碼到JavaScript」。也就是說,有許多操作根據標準是未定義的行為,但通常在x86 上工作- 例如,對變數的未對齊訪問,這在某些體系結構上通常是禁止的。一般來說,Qemu 是一個跨平台程序,而且,我想相信,它還沒有包含很多未定義的行為 - 接受它並編譯,然後用 JIT 修改一下 - 你就完成了!但這不是案件.. .

第一次嘗試

總的來說,我並不是第一個提出將 Qemu 移植到 JavaScript 的想法的人。 ReactOS 論壇上有人詢問是否可以使用 Emscripten 來實現這一點。 甚至在更早之前,就有傳言說 Fabrice Bellard 親自做到了這一點,但我們談論的是 jslinux,據我所知,這只是一次嘗試在 JS 中手動實現足夠的性能,並且是從頭開始編寫的。 後來,編寫了 Virtual x86 - 為其發布了未混淆的源代碼,並且如上所述,仿真的更大“真實性”使得使用 SeaBIOS 作為固件成為可能。 此外,至少有一次嘗試使用 Emscripten 移植 Qemu - 我嘗試這樣做 套接字對,但據我了解,發展被凍結了。

所以,看起來,這裡是原始碼,這裡是 Emscripten - 取得並編譯。 但也有Qemu所依賴的函式庫,以及那些函式庫所依賴的函式庫等等,其中之一就是 庫菲,glib 取決於哪一個。 網路上有傳言說 Emscripten 的大量移植庫中有一個,但有點難以置信:首先,它並不是要成為一個新的編譯器,其次,它的等級太低了。直接取得函式庫並編譯為 JS。 這不僅僅是彙編插入的問題 - 也許,如果你扭轉它,對於某些呼叫約定,你可以在堆疊上產生必要的參數並在沒有它們的情況下呼叫函數。 但 Emscripten 是一件棘手的事情:為了讓產生的程式碼對於瀏覽器 JS 引擎優化器來說看起來很熟悉,使用了一些技巧。 特別是,所謂的重新循環 - 使用接收到的 LLVM IR 和一些抽象轉換指令的程式碼產生器嘗試重新建立合理的 if、循環等。 那麼,參數是如何傳遞給函數的呢? 當然,作為JS函數的參數,也就是說,如果可能的話,不要通過堆疊。

一開始有一個想法,簡單地用 JS 編寫 libffi 的替代品並運行標準測試,但最終我對如何製作頭文件以便它們能夠與現有代碼一起工作感到困惑 - 我能做什麼,正如他們所說, “任務有那麼複雜嗎?”“我們有那麼愚蠢嗎?” 我必須將libffi 移植到另一個體系結構,可以這麼說- 幸運的是,Emscripten 具有用於內聯彙編的巨集(在Javascript 中,是的- 好吧,無論什麼體系結構,所以彙編器),以及運行動態產生的程式碼的能力。 總的來說,在對依賴平台的 libffi 片段進行了一段時間的修改之後,我得到了一些可編譯的程式碼,並在我遇到的第一個測試中運行了它。 令我驚訝的是,測試成功了。 被我的天才震驚了 - 不是開玩笑,它從第一次啟動就起作用了 - 我仍然不敢相信自己的眼睛,再次查看生成的代碼,以評估下一步該在哪裡挖掘。 在這裡我第二次發瘋了——我的函數唯一做的就是 ffi_call - 這報告了一次成功的呼叫。 本身沒有電話。 因此,我發送了第一個拉取請求,該請求糾正了測試中的一個錯誤,任何奧運學生都清楚這一錯誤 - 真實數字不應與 a == b 甚至如何 a - b < EPS - 你還需要記住模組,否則 0 將非常等於 1/3... 一般來說,我想出了 libffi 的某個端口,它通過了最簡單的測試,並且 glib 與它一起使用已編譯-我認為這是必要的,稍後我會添加它。 展望未來,我要說的是,事實證明,編譯器甚至沒有在最終程式碼中包含 libffi 函數。

但是,正如我已經說過的,存在一些限制,並且在各種未定義行為的自由使用中,隱藏了一個更令人不快的功能 - JavaScript 在設計上不支援共享記憶體的多線程。 原則上,這通常甚至可以被稱為一個好主意,但不適用於移植其體系結構與 C 執行緒相關的程式碼。 一般來說,Firefox 正在嘗試支援共享工作線程,Emscripten 為它們提供了 pthread 實現,但我不想依賴它。 我必須慢慢地從Qemu 程式碼中根除多線程- 也就是說,找出線程在哪裡運行,將在該線程中運行的循環體移動到一個單獨的函數中,並從主循環中一一調用這些函數。

第二次嘗試

在某些時候,很明顯問題仍然存在,隨意地在程式碼周圍推拐杖不會帶來任何好處。 結論:我們需要以某種方式系統化添加拐杖的過程。 因此,採用了當時新鮮的2.4.1版本(不是2.5.0,因為,誰知道,新版本中還會有尚未捕獲的bug,而我自己的bug已經夠多了) ),第一件事就是安全地重寫它 thread-posix.c。 嗯,也就是說,同樣安全:如果有人試圖執行導致阻塞的操作,函數會立即被調用 abort() ——當然,這並不能一次解決所有問題,但至少比悄悄收到不一致的數據要令人愉快一些。

總的來說,Emscripten 選項對於將程式碼移植到 JS 非常有幫助 -s ASSERTIONS=1 -s SAFE_HEAP=1 - 它們捕獲某些類型的未定義行為,例如對未對齊地址的呼叫(這與類型化數組的程式碼完全不一致,例如 HEAP32[addr >> 2] = 1) 或呼叫參數數量錯誤的函數。

順便說一句,對齊錯誤是一個單獨的問題。 正如我已經說過的,Qemu 有一個用於程式碼生成TCI(微型程式碼解釋器)的「退化」解釋後端,並且要在新架構上建置和運行Qemu,如果幸運的話,一個C 編譯器就足夠了。 “如果你幸運的話”。 我很不幸,結果發現 TCI 在解析其字節碼時使用了未對齊的存取。 也就是說,在各種 ARM 和其他具有必要等級存取權限的架構上,Qemu 可以編譯,因為它們具有生成本機程式碼的普通 TCG 後端,但 TCI 是否適用於它們是另一個問題。 然而,事實證明,TCI 文件清楚地表明了類似的內容。 結果,程式碼中添加了未對齊讀取的函數調用,這是在 Qemu 的另一部分中發現的。

堆破壞

結果,對 TCI 的未對齊存取得到了修正,創建了一個主循環,依序呼叫處理器、RCU 和其他一些小東西。 所以我啟動 Qemu 並選擇 -d exec,in_asm,out_asm,這意味著您需要說明正在執行哪些程式碼區塊,並且在廣播時寫入來賓程式碼是什麼,主機程式碼變成了什麼(在本例中為字節碼)。 它啟動,執行幾個翻譯區塊,寫入我留下的偵錯訊息,RCU 現在將啟動,然後...崩潰 abort() 在函數內部 free()。 透過修改函數 free() 我們設法發現,在堆塊的標頭(位於分配的記憶體之前的八個位元組)中,存在垃圾,而不是塊大小或類似的東西。

堆的破壞 - 多麼可愛......在這種情況下,有一個有用的補救措施 - 從(如果可能)相同的來源,組裝一個本機二進製文件並在 Valgrind 下運行它。 一段時間後,二進位就準備好了。 我使用相同的選項啟動它 - 即使在初始化期間,在實際執行之前它也會崩潰。 當然,這令人不愉快 - 顯然,來源並不完全相同,這並不奇怪,因為配置發現了略有不同的選項,但我有 Valgrind - 首先我會修復這個錯誤,然後,如果我幸運的話,就會出現原來的。 我在Valgrind 下運行同樣的東西...Y-y-y,y-y-y,呃-呃,它開始了,正常地進行了初始化,並繼續過去的原始錯誤,沒有關於不正確的內存訪問的任何警告,更不用說跌倒了。 正如他們所說,生活並沒有讓我為此做好準備——一個崩潰的程式在 Walgrind 下啟動時就不再崩潰了。 那是什麼是個謎。 我的假設是,一旦在初始化過程中崩潰後位於當前指令附近,gdb 就會顯示工作 memset-a 使用有效指針 mmx, 或者 xmm 寄存器,那麼也許是某種對齊錯誤,儘管仍然很難相信。

好吧,Valgrind 似乎在這裡沒有幫助。 最噁心的事情開始了——一切似乎都開始了,但是由於一個可能在數百萬條指令之前發生的事件而由於絕對未知的原因而崩潰。 很長一段時間,連如何接近都不清楚。 最終我還是要坐下來調試。 列印標題被重寫的內容表明它看起來不像數字,而是某種二進位資料。 而且,你瞧,這個二進位字串是在 BIOS 檔案中找到的 - 也就是說,現在可以有合理的信心說它是緩衝區溢出,甚至很清楚它被寫入了這個緩衝區。 好吧,然後是這樣的 - 在 Emscripten 中,幸運的是,地址空間沒有隨機化,也沒有漏洞,所以你可以在代碼中間的某個地方編寫,通過上次啟動的指針輸出數據,看數據,看指針,如果沒有變化,就深思熟慮。 確實,任何更改後都需要幾分鐘才能鏈接,但是您能做什麼呢? 結果,發現了一條特定的行將 BIOS 從臨時緩衝區複製到客戶記憶體 - 事實上,緩衝區中沒有足夠的空間。 找到那個奇怪的緩衝區位址的來源產生了一個函數 qemu_anon_ram_alloc 在文件中 oslib-posix.c - 邏輯是這樣的:有時將地址與 2 MB 大小的大頁面對齊可能很有用,為此我們會詢問 mmap 先多一點,然後我們會在幫助下退還多餘的部分 munmap。 如果不需要這樣的對齊,那麼我們將指示結果而不是 2 MB getpagesize() - mmap 它仍然會給出一個對齊的地址...所以在 Emscripten 中 mmap 只是打電話 malloc,但它當然不會在頁面上對齊。 一般來說,一個讓我沮喪了幾個月的錯誤已經透過以下更改得到糾正: двух 線。

呼叫函數的特點

現在處理器正在計算一些東西,Qemu沒有崩潰,但是螢幕沒有打開,並且處理器很快就進入循環,從輸出來看 -d exec,in_asm,out_asm。 出現了一個假設:定時器中斷(或一般來說,所有中斷)都沒有到達。 事實上,如果您從本機程式集中擰下中斷(由於某種原因該中斷有效),您會得到類似的圖片。 但這根本不是答案:與上述選項發出的軌蹟的比較表明,執行軌跡很早就出現了分歧。 這裡必須說一下使用啟動器記錄的內容的比較 emrun 使用本機程式集的輸出偵錯輸出並不是一個完全機械的過程。 我不知道瀏覽器中運行的程式如何連接到 emrun,但輸出中的某些行被重新排列,因此 diff 的差異還不是假設軌跡已發散的理由。 總的來說,很明顯,根據說明 ljmpl 存在到不同位址的轉換,產生的字節碼根本不同:一個包含呼叫輔助函數的指令,另一個則不包含。 在谷歌上搜尋指令並研究翻譯這些指令的程式碼後,很明顯,首先,在緊鄰寄存器中的指令之前 cr0 進行了記錄 - 也使用了幫助程式 - 將處理器切換到保護模式,其次,js 版本從未切換到保護模式。 但事實是 Emscripten 的另一個特點是它不願意容忍指令的執行等程式碼 call 在TCI中,任何函數指標都會產生類型 long long f(int arg0, .. int arg9) - 必須使用正確數量的參數呼叫函數。 如果違反此規則,根據調試設置,程式將崩潰(這很好)或根本調用錯誤的函數(這對於調試來說會很糟糕)。 還有第三個選項 - 啟用添加/刪除參數的包裝器的生成,但這些包裝器總共佔用了大量空間,儘管事實上我只需要一百多個包裝器。 僅此一點就非常令人悲傷,但結果出現了一個更嚴重的問題:在包裝函數的生成代碼中,參數被轉換再轉換,但有時帶有生成參數的函數沒有被調用 - 好吧,就像我的libffi 實作。 也就是說,有些幫手根本就沒有被處死。

幸運的是,Qemu 有機器可讀的幫助器列表,其形式是頭文件,例如

DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)

它們的使用非常有趣:首先,巨集以最奇怪的方式重新定義 DEF_HELPER_n,然後打開 helper.h。 在某種程度上,巨集被擴展為結構初始值設定項和逗號,然後定義一個數組,而不是元素 - #include <helper.h> 結果,我終於有機會在工作中嘗試圖書館了 Python解析,並編寫了一個腳本來準確地產生那些包裝器,以準確地滿足所需的功能。

因此,在那之後處理器似乎開始工作了。 這似乎是因為螢幕從未初始化,儘管 memtest86+ 能夠在本機程式集中運作。 這裡有必要澄清一下,Qemu塊I/O程式碼是用協程寫的。 Emscripten 有自己非常棘手的實現,但它仍然需要在 Qemu 程式碼中得到支持,並且您現在可以調試處理器:Qemu 支援選項 -kernel, -initrd, -append,您可以使用它來引導 Linux 或 memtest86+,而無需使用區塊裝置。 但問題是:在本機程式集中,可以透過選項看到 Linux 核心輸出到控制台 -nographic,而且瀏覽器沒有輸出到啟動它的終端 emrun,沒有來。 也就是說,不清楚:處理器不工作或圖形輸出不工作。 然後我想到要等一下。 事實證明,“處理器沒有休眠,只是緩慢地閃爍”,大約五分鐘後,核心將一堆訊息扔到控制台上並繼續掛起。 很明顯,處理器總體上是可以工作的,我們需要深入研究使用 SDL2 的程式碼。 不幸的是,我不知道如何使用這個庫,所以在某些地方我必須隨意行動。 在某個時刻,parallel0 行在螢幕上以藍色背景閃爍,這表明了一些想法。 最後發現問題在於 Qemu 在一個實體視窗中開啟了多個虛擬窗口,您可以使用 Ctrl-Alt-n 在這些虛擬視窗之間進行切換:它在本機構建中有效,但在 Emscripten 中無效。 使用選項擺脫不必要的視窗後 -monitor none -parallel none -serial none 以及在每一幀上強制重繪整個螢幕的指令,一切突然都正常了。

協程

因此,瀏覽器中的模擬可以工作,但您無法在其中運行任何有趣的單軟盤,因為沒有塊 I/O - 您需要實現對協程的支援。 Qemu 已經有幾個協程後端,但由於 JavaScript 和 Emscripten 程式碼產生器的性質,您不能只是開始處理堆疊。 看起來“一切都消失了,石膏正在被移除”,但 Emscripten 開發人員已經處理好了一切。 這實現起來很有趣:讓我們呼叫這樣一個可疑的函數 emscripten_sleep 以及使用 Asyncify 機制的其他幾個,以及指標調用和對任何函數的調用,其中前兩種情況之一可能發生在堆疊的更深處。 現在,在每個可疑調用之前,我們都會選擇一個非同步上下文,並且在調用之後,我們會立即檢查是否發生了非同步調用,如果有,我們將在這個非同步上下文中保存所有局部變量,指示哪個函數當我們需要繼續執行時將控制權轉移到目前函數並退出。 這是有研究效果的地方 揮霍 — 為了從非同步調用返回後繼續執行程式碼的需要,編譯器在可疑調用後開始產生函數的「存根」 — 像這樣:如果有 n 個可疑調用,則該函數將在某處展開 n/2次-這仍然是,如果不是請記住,在每次潛在的非同步呼叫之後,您需要將保存一些局部變數新增至原始函數。 隨後,我甚至不得不用Python 編寫一個簡單的腳本,該腳本基於一組給定的特別過度使用的函數,這些函數據稱“不允許異步通過自身傳遞”(即堆疊提升和我剛才描述的所有內容都不允許)在其中工作),指示透過指標進行的調用,其中的函數應被編譯器忽略,以便這些函數不被視為非同步。 然後 60 MB 以下的 JS 檔案顯然太多了 - 假設至少有 30 個。儘管如此,有一次我在設定彙編腳本時,不小心拋出了連結器選項,其中有 -O3。 我運行生成的程式碼,Chromium 耗盡記憶體並崩潰。 然後我無意中看到了他試圖下載的內容...好吧,我能說什麼,如果我被要求仔細研究和優化 500+ MB 的 Javascript,我也會凍結。

不幸的是,Asyncify 支援庫程式碼中的檢查並不完全友好 longjmp-s 在虛擬處理器程式碼中使用,但是在一個小補丁禁用這些檢查並強制恢復上下文(就好像一切正​​常一樣)之後,程式碼就可以工作了。 然後奇怪的事情開始了:有時會觸發同步程式碼中的檢查 - 如果根據執行邏輯,程式碼應該被阻止,則相同的檢查會導致程式碼崩潰 - 有人試圖取得已捕獲的互斥體。 幸運的是,這在序列化程式碼中並不是一個邏輯問題——我只是使用 Emscripten 提供的標準主循環功能,但有時非同步呼叫會完全解開堆疊,此時它會失敗 setTimeout 從主循環 - 因此,程式碼進入主循環迭代而沒有離開前一個迭代。 無限循環重寫並 emscripten_sleep,互斥體的問題就停止了。 程式碼甚至變得更加合乎邏輯——畢竟,事實上,我沒有一些準備下一個動畫幀的程式碼——處理器只是計算一些東西,螢幕會定期更新。 然而,問題並沒有就此停止:有時 Qemu 執行會簡單地終止,沒有任何異常或錯誤。 那一刻我放棄了它,但是,展望未來,我會說問題是這樣的:協程程式碼實際上並沒有使用 setTimeout (或至少不像你想像的那麼頻繁):函數 emscripten_yield 只需設定異步呼叫標誌即可。 重點是 emscripten_coroutine_next 不是非同步函數:它在內部檢查標誌,重置它並將控制權轉移到需要的地方。 也就是說,棧的提升到此結束。 問題是,由於我沒有從現有協程後端複製重要的程式碼行,導致協程池被停用時出現 use-after-free,該函數 qemu_in_coroutine 傳回 true,而實際上它應該回傳 false。 這導致了一個電話 emscripten_yield,上面堆疊上沒有人 emscripten_coroutine_next,堆疊展開到最頂部,但沒有 setTimeout正如我已經說過的,沒有展出。

JavaScript 程式碼生成

事實上,這就是承諾的「把肉末變回來」。 並不真地。 當然,如果我們在瀏覽器中運行 Qemu,並在其中運行 Node.js,那麼很自然地,在 Qemu 中生成程式碼後,我們會得到完全錯誤的 JavaScript。 但仍然是某種反向轉換。

首先,簡單介紹一下 Qemu 的工作原理。 請立即原諒我:我不是專業的 Qemu 開發人員,我的結論可能在某些地方是錯的。 正如他們所說,“學生的觀點不必與老師的觀點、皮亞諾的公理和常識相一致。” Qemu 有一定數量的支援的客戶架構,每個架構都有目錄,例如 target-i386。 建置時,您可以指定對多個來賓架構的支持,但結果將只是幾個二進位檔案。 支援來賓架構的程式碼反過來會產生一些內部 Qemu 操作,TCG(微型程式碼產生器)已將其轉換為主機架構的機器碼。 正如位於 tcg 目錄中的自述文件中所述,這最初是常規 C 編譯器的一部分,後來針對 JIT 進行了改編。 因此,例如,本文檔中的目標架構不再是客戶架構,而是主機架構。 在某個時候,出現了另一個元件 - Tiny Code Interpreter (TCI),它應該在沒有特定主機架構的程式碼產生器的情況下執行程式碼(幾乎相同的內部操作)。 事實上,正如其文件所述,該解釋器的性能可能並不總是與 JIT 程式碼產生器一樣好,不僅在速度上如此,而且在品質上也是如此。 儘管我不確定他的描述是否完全相關。

起初,我嘗試製作一個成熟的 TCG 後端,但很快就對原始程式碼感到困惑,並且對字節碼指令的描述也不完全清晰,因此我決定包裝 TCI 解釋器。 這帶來了幾個優點:

  • 在實作程式碼產生器時,您可以不看指令的描述,而可以看解釋器程式碼
  • 您可以不為遇到的每個翻譯區塊產生函數,而是僅在第一百次執行之後產生函數
  • 如果生成的程式碼發生變化(從名稱中包含單字 patch 的函數來看,這似乎是可能的),我將需要使生成的 JS 程式碼無效,但至少我將有一些東西可以從中重新生成它

關於第三點,我不確定程式碼第一次執行後是否可以打補丁,但前兩點就夠了。

最初,程式碼是在原始字節碼指令的地址處以大開關的形式生成的,但是後來,記住有關Emscripten、生成的JS 的優化和重新循環的文章,我決定生成更多的人類代碼,特別是因為根據經驗它事實證明,翻譯區塊的唯一入口點是它的 Start。 說到做到,過了一會兒,我們就有了一個程式碼產生器,可以使用 ifs 產生程式碼(儘管沒有循環)。 但運氣不好,它崩潰了,並給出一條訊息,說明指令的長度不正確。 此外,該遞歸層級的最後一條指令是 brcond。 好的,我將在遞歸呼叫之前和之後對該指令的生成添加相同的檢查,並且......其中沒有一個被執行,但在斷言切換之後它們仍然失敗。 最後,在研究生成的程式碼後,我意識到切換後,指向當前指令的指標會從堆疊中重新加載,並且可能會被生成的 JavaScript 程式碼覆蓋。 結果就是這樣。 將緩衝區從 XNUMX MB 增加到 XNUMX MB 並沒有產生任何結果,而且很明顯程式碼產生器正在循環運行。 我們必須檢查是否超出了當前 TB 的邊界,如果超出,則發出帶有減號的下一個 TB 的位址,以便我們可以繼續執行。 此外,這也解決了「如果這段字節碼發生變化,哪些生成函數應該失效?」的問題。 — 只需要使與該翻譯區塊對應的函數無效。 順便說一句,雖然我在Chromium 中調試了所有內容(因為我使用Firefox,而且使用單獨的瀏覽器進行實驗對我來說更容易),但Firefox 幫助我糾正了與asm.js 標準的不相容之處,之後程式碼開始在鉻。

生成程式碼範例

Compiling 0x15b46d0:
CompiledTB[0x015b46d0] = function(stdlib, ffi, heap) {
"use asm";
var HEAP8 = new stdlib.Int8Array(heap);
var HEAP16 = new stdlib.Int16Array(heap);
var HEAP32 = new stdlib.Int32Array(heap);
var HEAPU8 = new stdlib.Uint8Array(heap);
var HEAPU16 = new stdlib.Uint16Array(heap);
var HEAPU32 = new stdlib.Uint32Array(heap);

var dynCall_iiiiiiiiiii = ffi.dynCall_iiiiiiiiiii;
var getTempRet0 = ffi.getTempRet0;
var badAlignment = ffi.badAlignment;
var _i64Add = ffi._i64Add;
var _i64Subtract = ffi._i64Subtract;
var Math_imul = ffi.Math_imul;
var _mul_unsigned_long_long = ffi._mul_unsigned_long_long;
var execute_if_compiled = ffi.execute_if_compiled;
var getThrew = ffi.getThrew;
var abort = ffi.abort;
var qemu_ld_ub = ffi.qemu_ld_ub;
var qemu_ld_leuw = ffi.qemu_ld_leuw;
var qemu_ld_leul = ffi.qemu_ld_leul;
var qemu_ld_beuw = ffi.qemu_ld_beuw;
var qemu_ld_beul = ffi.qemu_ld_beul;
var qemu_ld_beq = ffi.qemu_ld_beq;
var qemu_ld_leq = ffi.qemu_ld_leq;
var qemu_st_b = ffi.qemu_st_b;
var qemu_st_lew = ffi.qemu_st_lew;
var qemu_st_lel = ffi.qemu_st_lel;
var qemu_st_bew = ffi.qemu_st_bew;
var qemu_st_bel = ffi.qemu_st_bel;
var qemu_st_leq = ffi.qemu_st_leq;
var qemu_st_beq = ffi.qemu_st_beq;

function tb_fun(tb_ptr, env, sp_value, depth) {
  tb_ptr = tb_ptr|0;
  env = env|0;
  sp_value = sp_value|0;
  depth = depth|0;
  var u0 = 0, u1 = 0, u2 = 0, u3 = 0, result = 0;
  var r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0;
  var r10 = 0, r11 = 0, r12 = 0, r13 = 0, r14 = 0, r15 = 0, r16 = 0, r17 = 0, r18 = 0, r19 = 0;
  var r20 = 0, r21 = 0, r22 = 0, r23 = 0, r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0;
  var r30 = 0, r31 = 0, r41 = 0, r42 = 0, r43 = 0, r44 = 0;
    r14 = env|0;
    r15 = sp_value|0;
  START: do {
    r0 = HEAPU32[((r14 + (-4))|0) >> 2] | 0;
    r42 = 0;
    result = ((r0|0) != (r42|0))|0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445321] = r14;
    if(result|0) {
    HEAPU32[1445322] = r15;
    return 0x0345bf93|0;
    }
    r0 = HEAPU32[((r14 + (16))|0) >> 2] | 0;
    r42 = 8;
    r0 = ((r0|0) - (r42|0))|0;
    HEAPU32[(r14 + (16)) >> 2] = r0;
    r1 = 8;
    HEAPU32[(r14 + (44)) >> 2] = r1;
    r1 = r0|0;
    HEAPU32[(r14 + (40)) >> 2] = r1;
    r42 = 4;
    r0 = ((r0|0) + (r42|0))|0;
    r2 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    HEAPU32[1445309] = r2;
    HEAPU32[1445321] = r14;
    HEAPU32[1445322] = r15;
    qemu_st_lel(env|0, r0|0, r2|0, 34, 22759218);
if(getThrew() | 0) abort();
    r0 = 3241038392;
    HEAPU32[1445307] = r0;
    r0 = qemu_ld_leul(env|0, r0|0, 34, 22759233)|0;
if(getThrew() | 0) abort();
    HEAPU32[(r14 + (24)) >> 2] = r0;
    r1 = HEAPU32[((r14 + (12))|0) >> 2] | 0;
    r2 = HEAPU32[((r14 + (40))|0) >> 2] | 0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    HEAPU32[1445309] = r2;
    qemu_st_lel(env|0, r2|0, r1|0, 34, 22759265);
if(getThrew() | 0) abort();
    r0 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
    HEAPU32[(r14 + (40)) >> 2] = r0;
    r1 = 24;
    HEAPU32[(r14 + (52)) >> 2] = r1;
    r42 = 0;
    result = ((r0|0) == (r42|0))|0;
    if(result|0) {
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    }
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    return execute_if_compiled(22759392|0, env|0, sp_value|0, depth|0) | 0;
    return execute_if_compiled(23164080|0, env|0, sp_value|0, depth|0) | 0;
    break;
  } while(1); abort(); return 0|0;
}
return {tb_fun: tb_fun};
}(window, CompilerFFI, Module.buffer)["tb_fun"]

結論

所以,工作還沒完成,但我厭倦了偷偷地把這個長期的建設做到完美。 因此,我決定發布我現在所擁有的內容。 程式碼有些地方有點嚇人,因為這是一個實驗,事先並不清楚需要做什麼。 也許,在一些更現代的 Qemu 版本之上發布正常的原子提交是值得的。 同時,《梵歌》中以部落格的形式有一條線索:對於至少以某種方式通過的每個“級別”,都添加了俄語的詳細評論。 其實這篇文章很大程度上是對結論的複述 git log.

你可以全部嘗試一下 這裡 (注意交通)。

什麼已經在工作:

  • x86 虛擬處理器運行
  • 有一個從機器碼到 JavaScript 的 JIT 程式碼產生器的工作原型
  • 有一個用於組裝其他 32 位元客戶架構的範本:現在您可以欣賞 Linux 在載入階段凍結在瀏覽器中的 MIPS 架構

你還能做什麼

  • 加快仿真速度。 即使在 JIT 模式下,它的運行速度似乎也比 Virtual x86 慢(但可能存在一個包含大量模擬硬體和架構的完整 Qemu)
  • 為了製作一個正常的介面 - 坦白說,我不是一個優秀的 Web 開發人員,所以現在我已經盡我所能重新製作了標準的 Emscripten shell
  • 嘗試啟動更複雜的 Qemu 功能 - 網路、VM 遷移等。
  • UPD: 您需要向 Emscripten 上游提交少量開發和錯誤報告,就像之前 Qemu 和其他專案的搬運工所做的那樣。 感謝他們能夠隱式地使用他們對 Emscripten 的貢獻作為我任務的一部分。

來源: www.habr.com

添加評論