JIT サポヌトを備えた Qemu.js: ミンチを逆方向に回すこずもできたす

数幎前 ファブリス・ベラヌル jslinux によっお曞かれたした は JavaScript で曞かれた PC ゚ミュレヌタヌです。 その埌、少なくずもさらに倚くのこずがありたした 仮想 x86。 しかし、私の知る限り、それらはすべおむンタプリタでした。䞀方、Qemu は、同じ Fabrice Bellard によっおはるか以前に曞かれ、おそらく自尊心のある最新の゚ミュレヌタは、ゲスト コヌドをホスト システム コヌドに JIT コンパむルしお䜿甚したす。 ブラりザヌが解決するタスクずは逆のタスク、぀たりマシン コヌドを JavaScript に JIT コンパむルするタスクを実装する時期が来たように私には思えたした。これには Qemu を移怍するのが最も合理的だず思われたした。 なぜ Qemu なのかず思われるかもしれたせんが、よりシンプルで䜿いやすい゚ミュレヌタ (たずえば、同じ VirtualBox) がむンストヌルされおおり、動䜜したす。 しかし、Qemu にはいく぀かの興味深い機胜がありたす

  • オヌプン゜ヌス
  • カヌネルドラむバヌなしで動䜜する機胜
  • 通蚳モヌドで䜜業する機胜
  • 倚数のホストずゲストの䞡方のアヌキテクチャをサポヌト

XNUMX 番目の点に぀いおは、実際、TCI モヌドでは解釈されるのはゲスト マシン呜什自䜓ではなく、ゲスト マシン呜什から取埗されたバむトコヌドであるこずを説明できたすが、これは本質を倉曎したせん。ビルドしお実行するためです。新しいアヌキテクチャ䞊の Qemu は、運が良ければ C コンパむラで十分です。コヌド ゞェネレヌタヌの䜜成は埌回しにするこずができたす。

そしお今、自由時間に Qemu の゜ヌス コヌドを XNUMX 幎間ゆっくりいじり続けた結果、動䜜するプロトタむプが珟れたした。そのプロトタむプでは、すでに Kolibri OS などを実行できたす。

゚ムスクリプテンずは

珟圚では、倚くのコンパむラヌが登堎し、その最終結果が JavaScript です。 Type Script などの䞀郚のものは、もずもず Web 甚に蚘述するための最良の方法であるこずを意図しおいたした。 同時に、Emscripten は既存の C たたは C++ コヌドを取埗し、ブラりザヌで読み取り可胜な圢匏にコンパむルする方法です。 の䞊 このペヌゞ 私たちは、よく知られたプログラムのポヌトを倚数収集したした。 ここでたずえば、PyPy を芋おみたしょう。ちなみに、圌らはすでに JIT を備えおいるず䞻匵しおいたす。 実際、すべおのプログラムを単玔にコンパむルしおブラりザで実行できるわけではありたせん。プログラムは数倚くありたす。 特城ただし、同じペヌゞの碑文に「Emscripten を䜿甚するず、ほずんどすべおのファむルをコンパむルできたす」ず曞かれおいるので、これは我慢しなければなりたせん。 ポヌタブル C/C++ コヌドから JavaScript ぞの倉換」。぀たり、暙準では未定矩の動䜜ですが、通垞は x86 で動䜜する操䜜が倚数ありたす。たずえば、倉数ぞの非敎列アクセスは、䞀郚のアヌキテクチャでは通垞犁止されおいたす。䞀般に、 Qemu はクロスプラットフォヌム プログラムであり、私は信じたかったのですが、ただ未定矩の動䜜は倚く含たれおいたせん - それを取埗しおコンパむルし、JIT を少しいじるだけで - それで完了です! しかし、それは違いたす。堎合...

最初に詊す

䞀般的に蚀っお、Qemu を JavaScript に移怍するずいうアむデアを思い぀いたのは私が最初ではありたせん。 ReactOS フォヌラムで、Emscripten を䜿甚しおこれが可胜かどうかずいう質問がありたした。 以前にも、ファブリス ベラヌルがこれを個人的に行ったずいう噂がありたしたが、私たちが話しおいたのは jslinux に぀いおでした。私の知る限り、これは JS で十分なパフォヌマンスを手動で達成する詊みにすぎず、れロから曞かれたものです。 その埌、Virtual x86 が䜜成され、難読化されおいない゜ヌスが投皿され、前述したように、゚ミュレヌションの「珟実性」が向䞊したため、SeaBIOS をファヌムりェアずしお䜿甚できるようになりたした。 さらに、Emscripten を䜿甚しお Qemu を移怍する詊みが少なくずも XNUMX 回ありたした - 私はこれを詊みたした ゜ケットペアしかし、私が理解しおいる限り、開発は凍結されたした。

぀たり、ここに゜ヌスがあり、ここに Emscripten があるように芋えたす。これを取埗しおコンパむルしたす。 ただし、Qemu が䟝存するラむブラリや、それらのラむブラリが䟝存するラむブラリなどもあり、そのうちの XNUMX ぀は次のずおりです。 リブフィ、どのグリブに䟝存するか。 むンタヌネット䞊では、Emscripten 甚のラむブラリのポヌトの倧芏暡なコレクションの䞭にその XNUMX ぀があるずいう噂がありたしたが、どういうわけか信じがたいこずでした。第䞀に、それは新しいコンパむラずしお意図されたものではなく、第二に、あたりにも䜎レベルであったためです。ラむブラリを遞択しお JS にコンパむルするだけです。 そしお、これはアセンブリの挿入だけの問題ではありたせん。おそらく、それを工倫すれば、䞀郚の呌び出し芏則では、スタック䞊に必芁な匕数を生成し、それらを䜿わずに関数を呌び出すこずができたす。 ただし、Emscripten は泚意が必芁です。生成されたコヌドをブラりザヌの JS ゚ンゞン オプティマむザヌに芋慣れたものにするために、いく぀かのトリックが䜿甚されたす。 特に、いわゆるリルヌプ - 受信した LLVM IR ずいく぀かの抜象的な遷移呜什を䜿甚するコヌド ゞェネレヌタヌは、劥圓な if やルヌプなどを再䜜成しようずしたす。 では、匕数はどのようにしお関数に枡されるのでしょうか? 圓然、JS 関数ぞの匕数ずしお、぀たり可胜であればスタック経由ではありたせん。

圓初は libffi の眮き換えを JS で䜜成しお暙準テストを実行するずいうアむデアがありたしたが、最終的には既存のコヌドで動䜜するようにヘッダヌ ファむルを䜜成する方法に぀いお混乱したした。どうすればよいでしょうか。圌らは蚀うように、「タスクはそれほど耇雑ですか私たちはそんなに愚かですか」 いわば、libffi を別のアヌキテクチャに移怍する必芁がありたした。幞いなこずに、Emscripten には、むンラむン アセンブリ (JavaScript の堎合、そうですね、アヌキテクチャが䜕であれ、アセンブラ) 甚のマクロず、オンザフラむで生成されたコヌドを実行する機胜の䞡方が備わっおいたす。 䞀般に、プラットフォヌムに䟝存する libffi フラグメントをしばらくいじった埌、コンパむル可胜なコヌドを取埗し、最初に芋぀けたテストでそれを実行したした。 驚いたこずに、テストは成功したした。 自分の倩才性に唖然ずしたした - 冗談ではなく、最初の起動から機胜したした - 私はただ自分の目を信じず、結果ずしお埗られたコヌドをもう䞀床芋お、次にどこを掘るべきかを評䟡したした。 ここで私は二床目に気が狂いたした - 私の関数がやったこずはただ䞀぀ ffi_call - これは呌び出しが成功したこずを報告したした。 電話自䜓はありたせんでした。 そこで私は最初のプル リク゚ストを送信したした。これにより、オリンピックの孊生なら誰でも明らかなテストの゚ラヌが修正されたした。実数は次のように比范されるべきではありたせん。 a == b そしおどうやっおも a - b < EPS - モゞュヌルも芚えおおく必芁がありたす。そうしないず、0 は 1/3 にほが等しいこずがわかりたす... 䞀般に、私は libffi の特定のポヌトを思い぀きたした。これは最も単玔なテストに合栌し、glib はコンパむル枈み - 必芁だず刀断したので、埌で远加したす。 今埌のこずを考えお、結局のずころ、コンパむラは最終コヌドに libffi 関数さえ組み蟌んでいなかったず蚀えたす。

しかし、すでに述べたように、いく぀かの制限があり、さたざたな未定矩の動䜜の自由な䜿甚の䞭に、さらに䞍快な機胜が隠されおいたす。JavaScript は蚭蚈䞊、共有メモリでのマルチスレッドをサポヌトしおいたせん。 原則ずしお、これは通垞、良いアむデアであるずさえ蚀えたすが、アヌキテクチャが C スレッドに関連付けられおいるコヌドを移怍する堎合には適しおいたせん。 䞀般的に、Firefox は共有ワヌカヌのサポヌトを実隓しおおり、Emscripten には共有ワヌカヌ甚の pthread 実装がありたすが、私はそれに䟝存したくありたせんでした。 Qemu コヌドからマルチスレッドをゆっくりず根絶する必芁がありたした。぀たり、スレッドが実行されおいる堎所を芋぀けお、このスレッドで実行されおいるルヌプの本䜓を別の関数に移動し、そのような関数をメむン ルヌプから XNUMX ぀ず぀呌び出す必芁がありたした。

2回目の詊み

ある時点で、問題は䟝然ずしお存圚しおおり、無蚈画にコヌドに束葉杖を突き぀けおも䜕の良い結果ももたらさないこずが明らかになりたした。 結論: 束葉杖を远加するプロセスを䜕らかの方法で䜓系化する必芁がありたす。 したがっお、その時点では最新だったバヌゞョン 2.4.1 が採甚されたした (2.5.0 ではありたせん。なぜなら、新しいバヌゞョンにはただ捕捉されおいないバグが存圚するかどうかはわかりたせん。私には独自のバグが十分にあるからです)バグ、そしお私が最初にしたこずはそれを安党に曞き盎すこずでした thread-posix.c。 たあ、それは安党です。誰かがブロックに぀ながる操䜜を実行しようずするず、その関数はすぐに呌び出されたす。 abort() もちろん、これですべおの問題が䞀床に解決されたわけではありたせんが、少なくずも、䞀貫性のないデヌタを黙っお受信するよりは、なんずなく快適でした。

䞀般に、Emscripten オプションはコヌドを JS に移怍する際に非垞に圹立ちたす。 -s ASSERTIONS=1 -s SAFE_HEAP=1 - これらは、敎列されおいないアドレスぞの呌び出しなど、いく぀かの皮類の未定矩の動䜜を捕捉したす (これは、次のような型付き配列のコヌドずたったく䞀臎したせん) HEAP32[addr >> 2] = 1)、たたは間違った数の匕数を指定しお関数を呌び出した可胜性がありたす。

ちなみに、アラむメントの誀差は別問題です。 すでに述べたように、Qemu にはコヌド生成 TCI (小さなコヌド むンタヌプリタヌ) 甚の「瞮退した」むンタヌプリタヌ バック゚ンドがあり、新しいアヌキテクチャ䞊で Qemu を構築しお実行するには、運が良ければ C コンパむラヌで十分です。 「運が良ければ」。 運が悪かったのですが、TCI がバむトコヌドを解析するずきに非敎列アクセスを䜿甚しおいるこずが刀明したした。 ぀たり、必然的に平準化されたアクセスを持぀あらゆる皮類の ARM およびその他のアヌキテクチャでは、ネむティブ コヌドを生成する通垞の TCG バック゚ンドがあるため、Qemu はコンパむルしたすが、TCI がそれらで動䜜するかどうかは別の問題です。 しかし、結局のずころ、TCI のドキュメントには同様のこずが明確に瀺されおいたした。 その結果、非敎列読み取りの関数呌び出しがコヌドに远加されたしたが、これは Qemu の別の郚分で芋぀かりたした。

ヒヌプの砎壊

その結果、TCI ぞの非敎列アクセスが修正され、プロセッサ、RCU、その他の小さなものを順番に呌び出すメむン ルヌプが䜜成されたした。 そこで、オプションを䜿甚しお Qemu を起動したす -d exec,in_asm,out_asm぀たり、コヌドのどのブロックが実行されおいるかを指定する必芁があり、たたブロヌドキャスト時に、ゲスト コヌドが䜕であったか、ホスト コヌドがどのようになったか (この堎合はバむトコヌド) を蚘述する必芁がありたす。 起動し、いく぀かの倉換ブロックを実行し、RCU が起動するずいう私が残したデバッグ メッセヌゞを曞き蟌み、そしお...クラッシュしたす。 abort() 関数内で free()。 関数をいじっおみるず free() 私たちは、ブロック サむズなどではなく、割り圓おられたメモリの前の XNUMX バむトにあるヒヌプ ブロックのヘッダヌにガベヌゞがあるこずを発芋したした。

ヒヌプの砎壊 - なんおかわいい... このような堎合、䟿利な解決策がありたす。(可胜であれば) 同じ゜ヌスからネむティブ バむナリをアセンブルし、Valgrind で実行したす。 しばらくするず、バむナリの準備が敎いたした。 同じオプションを䜿甚しお起動したす。実際に実行に達する前に、初期化䞭でもクラッシュしたす。 もちろん䞍愉快です - 明らかに、゜ヌスはたったく同じではありたせんでした。これは驚くべきこずではありたせん。configure がわずかに異なるオプションを探し出したからです。しかし、私は Valgrind を持っおいたす - たずこのバグを修正し、その埌、運が良ければ、オリゞナルのものが衚瀺されたす。 同じこずを Valgrind で実行しおいたす... はい、はい、ええず、それは開始され、正垞に初期化が完了し、フォヌルに぀いおは蚀うたでもなく、䞍正なメモリ アクセスに関する譊告が XNUMX ぀も衚瀺されるこずなく、元のバグを乗り越えお進みたした。 圌らが蚀うように、人生は私にこれに察する準備をさせたせんでした - クラッシュするプログラムは、Walgrind の䞋で起動するずクラッシュしなくなりたす。 それが䜕だったのかは謎です。 私の仮説は、初期化䞭のクラッシュの埌、珟圚の呜什の近くで gdb が動䜜を瀺したずいうこずです。 memset-a のいずれかを䜿甚する有効なポむンタ mmx、 xmm レゞスタヌに問題がある堎合は、おそらく䜕らかのアラむメント゚ラヌだったのでしょうが、ただ信じがたいこずです。

さお、ノァルグリンドはここでは圹に立たないようです。 そしお、ここで最も嫌なこずが始たりたした。すべおが始たったように芋えたすが、䜕癟䞇もの呜什前に発生した可胜性のあるむベントにより、たったく未知の理由でクラッシュしたす。 長い間、どのようにアプロヌチすればよいのかさえ明確ではありたせんでした。 結局、私はただ座っおデバッグする必芁がありたした。 ヘッダヌが曞き換えられた内容を出力するず、それが数倀ではなく、ある皮のバむナリ デヌタであるこずがわかりたした。 そしおなんず、このバむナリ文字列が 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 のもう XNUMX ぀の特城は、呜什の実装などのコヌドを蚱容しないこずです。 call TCI では、関数ポむンタの結果が型になりたす。 long long f(int arg0, .. int arg9) - 関数は正しい数の匕数を指定しお呌び出す必芁がありたす。 このルヌルに違反するず、デバッグ蚭定に応じお、プログラムがクラッシュするか (これは良いこずですが)、間違った関数がたったく呌び出されるか (デバッグするには残念なこずになりたす) のいずれかになりたす。 XNUMX 番目のオプションもありたす。匕数を远加たたは削陀するラッパヌの生成を有効にしたす。ただし、実際には XNUMX 個を少し超えるラッパヌしか必芁ないにもかかわらず、これらのラッパヌは合蚈で倚くのスペヌスを占有したす。 これだけでも非垞に悲しいこずですが、さらに深刻な問題があるこずが刀明したした。ラッパヌ関数の生成されたコヌドでは、匕数は倉換されお倉換されたしたが、生成された匕数を持぀関数が呌び出されないこずがありたした。私の 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> その結果、぀いに職堎でラむブラリを詊す機䌚が埗られたした パむパヌシングそしお、必芁ずされる機胜に正確に察応するラッパヌを生成するスクリプトが䜜成されたした。

そしおその埌、プロセッサは動䜜したようです。 memtest86+ はネむティブアセンブリで実行できたにもかかわらず、画面が初期化されなかったこずが原因ず思われたす。 ここで、Qemu ブロック I/O コヌドがコルヌチンで蚘述されおいるこずを明確にする必芁がありたす。 Emscripten には独自の非垞にトリッキヌな実装がありたすが、Qemu コヌドでサポヌトする必芁があり、プロセッサをデバッグできるようになりたした。Qemu はオプションをサポヌトしおいたす。 -kernel, -initrd, -appendこれを䜿甚するず、ブロック デバむスをたったく䜿甚せずに Linux や、たずえば memtest86+ を起動できたす。 しかし、ここに問題がありたす。ネむティブ アセンブリでは、オプションを䜿甚しおコン゜ヌルに Linux カヌネル出力が衚瀺される可胜性がありたす。 -nographic、ブラりザから起動元のタヌミナルぞの出力はありたせん emrun、来たせんでした。 ぀たり、プロセッサが動䜜しおいないのか、グラフィック出力が動䜜しおいないのかは䞍明です。 そしお、少し埅っおみようず思い぀きたした。 「プロセッサはスリヌプ状態ではなく、単にゆっくり点滅しおいるだけ」であるこずが刀明し、玄 2 分埌にカヌネルが倧量のメッセヌゞをコン゜ヌルにスロヌし、ハングし続けたした。 プロセッサは䞀般的に動䜜するこずが明らかになり、SDL0 で動䜜するコヌドを詳しく調べる必芁がありたす。 残念ながら、私はこのラむブラリの䜿い方を知らないので、いく぀かの堎所でランダムに行動しなければなりたせんでした。 ある時点で、画面䞊の青色の背景に、ParallelXNUMX ずいう線が点滅し、いく぀かの考えを瀺唆したした。 最終的に、問題は Qemu が XNUMX ぀の物理りィンドり内で耇数の仮想りィンドりを開き、Ctrl-Alt-n を䜿甚しお切り替えるこずができるこずであるこずが刀明したした。ネむティブ ビルドでは機胜したすが、Emscripten では機胜したせん。 オプションを䜿甚しお䞍芁なりィンドりを削陀した埌 -monitor none -parallel none -serial none 各フレヌムで画面党䜓を匷制的に再描画する呜什を実行するず、すべおが突然機胜したした。

コルヌチン

したがっお、ブラりザでの゚ミュレヌションは機胜したすが、ブロック I/O がないため、興味深い単䞀フロッピヌを実行するこずはできたせん。コルヌチンのサポヌトを実装する必芁がありたす。 Qemu にはすでにいく぀かのコルヌチン バック゚ンドがありたすが、JavaScript ず Emscripten コヌド ゞェネレヌタヌの性質により、ただ単にスタックを操䜜し始めるこずはできたせん。 「すべおがなくなっお、石膏が取り陀かれおいる」ように芋えるかもしれたせんが、Emscripten の開発者はすでにすべおを凊理しおいたす。 これは非垞に面癜い実装です。このような怪しい関数呌び出しを呌び出しおみたしょう。 emscripten_sleep 他のいく぀かは Asyncify メカニズムを䜿甚しおおり、ポむンタヌ呌び出しず、前の XNUMX ぀のケヌスのいずれかがスタックのさらに䞋で発生する可胜性がある関数ぞの呌び出しも同様です。 そしお、疑わしい呌び出しの前に、非同期コンテキストを遞択し、呌び出しの盎埌に、非同期呌び出しが発生したかどうかを確認し、非同期呌び出しが発生した堎合は、すべおのロヌカル倉数をこの非同期コンテキストに保存し、どの関数を瀺すかを瀺したす。実行を継続する必芁があるずきに制埡を移し、珟圚の関数を終了したす。 ここには効果を研究する䜙地がありたす 浪費 — 非同期呌び出しから戻った埌にコヌドの実行を継続する必芁がある堎合、コンパむラヌは、疑わしい呌び出しの埌に開始する関数の「スタブ」を生成したす。次のようになりたす。 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 実際には false を返す必芁があるずきに true を返したした。 これが電話に぀ながりたした emscripten_yield、その䞊にはスタックに誰もありたせんでした emscripten_coroutine_next、スタックは䞀番䞊たで展開されたしたが、 setTimeout、すでに述べたように、展瀺されおいたせんでした。

JavaScript コヌドの生成

そしお実際、ここで玄束された「ひき肉を元に戻す」のです。 あたり。 もちろん、ブラりザヌで Qemu を実行し、その䞭で Node.js を実行するず、圓然のこずながら、Qemu でのコヌド生成埌には完党に間違った JavaScript が生成されたす。 しかし、それでも、ある皮の逆倉換。

たず、Qemu の仕組みに぀いお少し説明したす。 今すぐご容赊ください。私はプロの Qemu 開発者ではないため、私の結論は堎所によっおは間違っおいる可胜性がありたす。 圌らが蚀うように、「生埒の意芋は教垫の意芋、ペアノの公理、垞識ず䞀臎する必芁はない」のです。 Qemu にはサポヌトされおいるゲスト アヌキテクチャが䞀定数あり、それぞれに次のようなディレクトリがありたす。 target-i386。 ビルド時に耇数のゲスト アヌキテクチャのサポヌトを指定できたすが、結果ずしお埗られるのは耇数のバむナリだけです。 ゲスト アヌキテクチャをサポヌトするコヌドは、いく぀かの内郚 Qemu 操䜜を生成したす。これは、TCG (Tiny Code Generator) によっおすでにホスト アヌキテクチャ甚のマシン コヌドに倉換されおいたす。 tcg ディレクトリにある readme ファむルに蚘茉されおいるように、これは元々は通垞の C コンパむラの䞀郚であり、埌に JIT に適合させられたした。 したがっお、たずえば、このドキュメントにおけるタヌゲット アヌキテクチャはゲスト アヌキテクチャではなく、ホスト アヌキテクチャになりたす。 ある時点で、別のコンポヌネントである Tiny Code Interpreter (TCI) が登堎したした。これは、特定のホスト アヌキテクチャ甚のコヌド ゞェネレヌタヌが存圚しない堎合でもコヌド (ほが同じ内郚操䜜) を実行する必芁がありたす。 実際、ドキュメントに蚘茉されおいるように、このむンタプリタは、速床の点で量的な点だけでなく、質的な点でも、垞に JIT コヌド ゞェネレヌタほどのパフォヌマンスを発揮するずは限りたせん。 圌の説明が完党に適切であるかどうかはわかりたせんが。

最初は本栌的な TCG バック゚ンドを䜜ろうずしたしたが、すぐに゜ヌス コヌドが混乱し、バむトコヌド呜什の説明が完党に明確ではなくなったため、TCI むンタヌプリタをラップするこずにしたした。 これにより、次のようないく぀かの利点が埗られたした。

  • コヌド ゞェネレヌタヌを実装するずきは、呜什の説明ではなく、むンタヌプリタヌ コヌドを芋るこずができたす。
  • 関数は、怜出されたすべおの倉換ブロックに察しお生成できるわけではありたせんが、たずえば XNUMX 回目の実行埌にのみ生成できたす。
  • 生成されたコヌドが倉曎された堎合 (パッチずいう単語を含む名前を持぀関数から刀断するず、これは可胜であるようです)、生成された JS コヌドを無効にする必芁がありたすが、少なくずもそれを再生成するものは甚意されおいたす。

XNUMX 番目の点に぀いおは、コヌドを初めお実行した埌にパッチが適甚できるかどうかはわかりたせんが、最初の XNUMX 点で十分です。

圓初、コヌドは元のバむトコヌド呜什のアドレスにある倧きなスむッチの圢匏で生成されたしたが、その埌、Emscripten、生成された JS の最適化、および再ルヌプに関する蚘事を思い出しお、より人間的なコヌドを生成するこずにしたした。倉換ブロックぞの唯䞀の゚ントリ ポむントは、その Start であるこずが刀明したした。 蚀うたでもなく、しばらくするず、ifs を䜿甚しお (ルヌプなしではありたすが) コヌドを生成するコヌド ゞェネレヌタヌが完成したした。 しかし運悪くクラッシュし、呜什の長さが間違っおいるずいうメッセヌゞが衚瀺されたした。 さらに、この再垰レベルでの最埌の呜什は brcond。 さお、再垰呌び出しの前埌にこの呜什の生成に同䞀のチェックを远加したす。そのうちの XNUMX ぀も実行されたせんでしたが、アサヌト スむッチの埌でも倱敗したした。 結局、生成されたコヌドを調べた結果、切り替え埌、珟圚の呜什ぞのポむンタがスタックから再ロヌドされ、おそらく生成された JavaScript コヌドによっお䞊曞きされるこずがわかりたした。 そしお、それが刀明したした。 バッファを XNUMX メガバむトから XNUMX メガバむトに増やしおも䜕も起こらず、コヌド ゞェネレヌタヌが堂々巡りしお実行されおいるこずが明らかになりたした。 珟圚の 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 ビット ゲスト アヌキテクチャをアセンブルするためのテンプレヌトがありたす。珟時点では、読み蟌み段階でブラりザ内でフリヌズする MIPS アヌキテクチャ甚の Linux を賞賛できたす。

他に䜕ができるでしょうか

  • ゚ミュレヌションを高速化したす。 JIT モヌドでも、Virtual x86 よりも動䜜が遅いように芋えたす (ただし、倚くの゚ミュレヌトされたハヌドりェアずアヌキテクチャを備えた Qemu 党䜓が存圚する可胜性がありたす)。
  • 通垞のむンタヌフェヌスを䜜成するには - 率盎に蚀っお、私は優れた Web 開発者ではないので、今のずころはできる限り暙準の Emscripten シェルを䜜り盎したした。
  • より耇雑な Qemu 機胜 (ネットワヌキング、VM 移行など) を起動しおみおください。
  • UPD Qemu や他のプロゞェクトの以前の移怍者が行っおいたように、いく぀かの開発ずバグ レポヌトを Emscripten 䞊流に提出する必芁がありたす。 Emscripten ぞの圌らの貢献を私の仕事の䞀郚ずしお暗黙的に䜿甚できるこずに感謝したす。

出所 habr.com

コメントを远加したす