Qemu.js kanthi dhukungan JIT: sampeyan isih bisa ngowahi mince mundur

Sawetara taun kepungkur Fabrice Bellard ditulis dening jslinux yaiku emulator PC sing ditulis ing JavaScript. Sawise iku, paling ora ana liyane Virtual x86. Nanging kabeh mau, minangka adoh aku ngerti, padha juru, nalika Qemu, ditulis akeh sadurungé dening Fabrice Bellard padha, lan, mbokmenawa, sembarang emulator modern timer respecting, nggunakake JIT kompilasi kode tamu menyang kode sistem inang. Iku ketoke kanggo kula sing iku wektu kanggo ngleksanakake tugas ngelawan ing hubungan kanggo siji sing browser ngatasi: JIT kompilasi kode mesin menyang JavaScript, kang ketoke paling logis kanggo port Qemu. Iku bakal katon, kok Qemu, ana emulators prasaja lan pangguna-loropaken - padha VirtualBox, contone - diinstal lan dianggo. Nanging Qemu duwe sawetara fitur sing menarik

  • mbukak sumber
  • kemampuan kanggo bisa tanpa driver kernel
  • kemampuan kanggo bisa ing mode interpreter
  • dhukungan kanggo akeh arsitektur host lan tamu

Babagan titik katelu, aku saiki bisa nerangake yen nyatane, ing mode TCI, ora instruksi mesin tamu dhewe sing diinterpretasikake, nanging bytecode sing dipikolehi saka wong-wong mau, nanging iki ora ngganti inti - kanggo mbangun lan mbukak. Qemu ing arsitektur anyar, yen sampeyan lagi bejo, A C compiler cukup - nulis generator kode bisa ditundha.

Lan saiki, sawise rong taun santai karo kode sumber Qemu ing wektu luang, prototipe kerja muncul, sing sampeyan bisa mbukak, contone, Kolibri OS.

Apa iku Emscripten

Saiki, akeh kompiler sing muncul, asil pungkasan yaiku JavaScript. Sawetara, kaya Type Script, wiwitane dimaksudake minangka cara paling apik kanggo nulis kanggo web. Ing wektu sing padha, Emscripten minangka cara kanggo njupuk kode C utawa C ++ sing wis ana lan ngumpulake menyang formulir sing bisa diwaca browser. On kaca iki Kita wis ngumpulake akeh port program sing kondhang: keneContone, sampeyan bisa ndeleng PyPy - kanthi cara, dheweke ngaku wis duwe JIT. Nyatane, ora saben program bisa dikompilasi lan mbukak ing browser - ana nomer fitur, sing kudu sampeyan lakoni, nanging, amarga prasasti ing kaca sing padha ujar "Emscripten bisa digunakake kanggo ngumpulake meh kabeh hotspot C / C ++ kode kanggo JavaScript ". Yaiku, ana sawetara operasi sing prilaku undefined miturut standar, nanging biasane bisa ing x86 - contone, akses unaligned kanggo variabel, kang umume dilarang ing sawetara arsitektur. Umumé. , Qemu iku program salib-platform lan, Aku wanted kanggo pracaya, lan iku durung ngemot akèh prilaku undefined - njupuk lan ngumpulake, banjur tinker sethitik karo JIT - lan sampeyan wis rampung! kasus...

Usaha pertama

Umumé, aku dudu wong pisanan sing duwe ide porting Qemu menyang JavaScript. Ana pitakon ing forum ReactOS yen bisa nggunakake Emscripten. Malah sadurunge, ana gosip sing Fabrice Bellard nindakake iki kanthi pribadi, nanging kita ngomong babagan jslinux, sing, aku ngerti, mung minangka upaya kanggo entuk kinerja sing cukup ing JS kanthi manual, lan ditulis saka awal. Mengko, Virtual x86 wis ditulis - sumber unobfuscated dikirim kanggo iku, lan, minangka nyatakake, "realisme" luwih saka emulasi ndadekake iku bisa kanggo nggunakake SeaBIOS minangka perangkat kukuh. Kajaba iku, ana paling ora siji nyoba port Qemu nggunakake Emscripten - Aku nyoba kanggo nindakake iki pasangan soket, nanging pembangunan, minangka adoh aku ngerti, iki beku.

Dadi, mesthine, iki sumber, kene Emscripten - njupuk lan kompilasi. Nanging ana uga perpustakaan sing Qemu gumantung, lan perpustakaan sing gumantung perpustakaan kasebut, lan liya-liyane, lan salah sijine yaiku libffi, kang glib gumantung ing. Ana gosip ing Internet sing ana siji ing koleksi gedhe saka port perpustakaan kanggo Emscripten, nanging piye wae hard kanggo pracaya: pisanan, iku ora dimaksudaké kanggo dadi compiler anyar, sareh, iku banget kurang-tingkat a perpustakaan kanggo mung Pick munggah, lan ngumpulake kanggo JS. Lan iku ora mung bab sisipan Déwan - mbokmenawa, yen corak iku, kanggo sawetara konvènsi nelpon sampeyan bisa generate bantahan perlu ing tumpukan lan nelpon fungsi tanpa wong. Nanging Emscripten minangka perkara sing angel: supaya kode sing digawe katon akrab karo pangoptimal mesin JS browser, sawetara trik digunakake. Utamane, sing diarani relooping - generator kode nggunakake IR LLVM sing ditampa kanthi sawetara instruksi transisi abstrak nyoba nggawé ulang ifs, loops, etc. Inggih, kados pundi argumentasi ingkang dipunlebetaken dhateng fungsi kasebut? Alami, minangka argumen kanggo fungsi JS, yaiku, yen bisa, ora liwat tumpukan.

Ing wiwitan ana gagasan kanggo mung nulis panggantos libffi karo JS lan nglakokake tes standar, nanging pungkasane aku bingung carane nggawe file header supaya bisa nggarap kode sing wis ana - apa sing bisa aku lakoni, lagi ngomong, "Apa tugas dadi Komplek" Apa kita dadi bodho? Aku kudu port libffi kanggo arsitektur liyane, ngandika - untunge, Emscripten wis loro macro kanggo Déwan inline (ing Javascript, ya - uga, apa arsitektur, supaya assembler), lan kemampuan kanggo mbukak kode kui ing fly. Umumé, sawise tinkering karo pecahan libffi gumantung platform kanggo sawetara wektu, Aku entuk sawetara kode compilable lan mlayu ing test pisanan aku teka tengen. Aku kaget, tes kasebut sukses. Stunned dening genius sandi - ora guyon, iku makarya saka Bukak pisanan - Aku, isih ora pracaya mripatku, banjur kanggo katon ing kode asil maneh, kanggo ngevaluasi ngendi kanggo dig sabanjuré. Ing kene aku dadi gila kanggo kaping pindho - mung fungsiku ffi_call - iki kacarita telpon sukses. Ora ana telpon dhewe. Dadi aku ngirim panjaluk tarik pisanan, sing mbenerake kesalahan ing tes sing jelas kanggo siswa Olimpiade - nomer nyata ora kudu dibandhingake karo a == b lan malah carane a - b < EPS - sampeyan uga kudu ngelingi modul, digunakake 0 bakal dadi banget padha karo 1/3 ... Umumé, aku teka munggah karo port tartamtu saka libffi, kang liwat tes paling prasaja, lan karo kang glib punika disusun - Aku mutusaké iku bakal perlu, Aku bakal nambah mengko. Looking ahead, Aku bakal ngomong sing, ternyata, compiler malah ora kalebu fungsi libffi ing kode final.

Nanging, kaya sing wis dakkandhakake, ana sawetara watesan, lan ing antarane panggunaan gratis saka macem-macem prilaku sing ora ditemtokake, fitur sing luwih ora nyenengake wis didhelikake - JavaScript kanthi desain ora ndhukung multithreading kanthi memori sing dienggo bareng. Ing asas, iki biasane malah bisa disebut apike, nanging ora kanggo porting kode kang arsitektur disambungake menyang C Utas. Umumé, Firefox nyobi karo ndhukung buruh bareng, lan Emscripten duwe implementasine pthread kanggo wong-wong mau, nanging aku ora pengin gumantung ing. Aku kudu alon-alon mbusak multithreading saka kode Qemu - yaiku, ngerteni endi benang mlaku, mindhah awak daur ulang sing mlaku ing benang iki menyang fungsi sing kapisah, lan nelpon fungsi kasebut siji-sijine saka daur ulang utama.

Coba kaping pindho

Ing sawetara titik, dadi cetha sing masalah isih ana, lan sing sembarangan shoving crutches sak kode ora bakal mimpin kanggo apa apik. Kesimpulan: kita kudu piye wae sistematis proses nambah crutches. Mulane, versi 2.4.1, sing seger nalika iku, dijupuk (ora 2.5.0, amarga, sapa ngerti, bakal ana bug ing versi anyar sing durung kejiret, lan aku duwe cukup kewan omo dhewe. ), lan sing sepisanan yaiku nulis ulang kanthi aman thread-posix.c. Yaiku, aman: yen ana wong sing nyoba nindakake operasi sing nyebabake pamblokiran, fungsi kasebut langsung diarani abort() - mesthi, iki ora ngrampungake kabeh masalah bebarengan, nanging ing paling iku piye wae luwih penake saka quietly nampa data inconsistent.

Umumé, opsi Emscripten mbiyantu banget kanggo ngirim kode menyang JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - padha nyekel sawetara jinis prilaku sing ora ditemtokake, kayata nelpon menyang alamat sing ora selaras (sing ora konsisten karo kode kanggo array sing diketik kaya HEAP32[addr >> 2] = 1) utawa nelpon fungsi kanthi nomer argumen sing salah.

Miturut cara, kesalahan alignment minangka masalah sing kapisah. Kaya sing wis dakkandhakake, Qemu duwe backend interpretatif "degenerate" kanggo generasi kode TCI (interpreter kode cilik), lan kanggo mbangun lan mbukak Qemu ing arsitektur anyar, yen sampeyan lagi bejo, compiler C cukup. "yen sampeyan beruntung". Aku apes, lan iku nguripake metu sing TCI nggunakake akses unaligned nalika parsing bytecode sawijining. Yaiku, ing kabeh jinis ARM lan arsitektur liyane kanthi akses leveled, Qemu nglumpukake amarga duwe backend TCG normal sing ngasilake kode asli, nanging apa TCI bakal bisa digunakake minangka pitakonan liyane. Nanging, ternyata, dokumentasi TCI kanthi jelas nuduhake sing padha. Akibaté, fungsi telpon kanggo maca unaligned ditambahake menyang kode, kang ditemokaké ing bagean liyane saka Qemu.

Tumpukan karusakan

Akibaté, akses unaligned kanggo TCI didandani, daur ulang utama digawe sing siji disebut prosesor, RCU lan sawetara bab cilik liyane. Dadi aku miwiti Qemu kanthi pilihan -d exec,in_asm,out_asm, sing tegese sampeyan kudu ngomong pamblokiran kode sing lagi dieksekusi, lan uga ing wektu siaran kanggo nulis apa kode tamu, apa kode host dadi (ing kasus iki, bytecode). Diwiwiti, nglakokake sawetara blok terjemahan, nulis pesen debugging sing daktinggalake yen RCU saiki bakal diwiwiti lan ... abort() nang sawijining fungsi free(). Kanthi tinkering karo fungsi free() Kita bisa ngerteni manawa ing header blok tumpukan, sing ana ing wolung bait sadurunge memori sing diparengake, tinimbang ukuran blok utawa sing padha, ana sampah.

Karusakan tumpukan - carane lucu ... Ing kasus kaya mengkono, ana obat sing migunani - saka (yen bisa) sumber sing padha, ngumpulake binar asli lan mbukak ing Valgrind. Sawise sawetara wektu, binar wis siyap. Aku miwiti karo opsi padha - tubrukan malah sak initialization, sadurunge bener tekan eksekusi. Iku karu, mesthi - ketoke, sumber padha ora persis padha, kang ora ngagetne, amarga konfigurasi scouted metu opsi rada beda, nanging aku duwe Valgrind - pisanan aku bakal ndandani bug iki, lan banjur, yen aku begja , sing asli bakal katon. Aku mlaku bab sing padha ing Valgrind ... Y-y-y, y-y-y, uh-uh, iku diwiwiti, tindak liwat initialization biasane lan dipindhah liwat bug asli tanpa bebaya siji bab akses memori salah, ora kanggo sebutno bab tiba. Urip, kaya sing dikandhakake, ora nyiapake aku - program nabrak mandheg nalika diluncurake ing Walgrind. Apa iku misteri. Hipotesisku yaiku yen ing sacedhake instruksi saiki sawise kacilakan nalika wiwitan, gdb nuduhake karya memset-a karo pitunjuk bener nggunakake salah siji mmx, utawa xmm ndhaptar, banjur mbok menawa ana sawetara jenis kesalahan alignment, senajan isih hard kanggo pracaya.

Oke, Valgrind kayane ora nulungi kene. Lan ing kene sing paling njijiki diwiwiti - kabeh katon diwiwiti, nanging tubrukan amarga alasan sing ora dingerteni amarga kedadeyan sing bisa kedadeyan jutaan instruksi kepungkur. Suwe-suwe, ora jelas carane nyedhaki. Ing pungkasan, aku isih kudu lungguh lan debug. Nyetak header sing ditulis maneh nuduhake yen ora katon kaya angka, nanging sawetara jinis data binar. Lan, lah, senar binar iki ditemokaké ing file BIOS - sing, saiki bisa ngomong karo kapercayan cukup sing iku kebanjiran buffer, lan iku malah cetha sing iki ditulis kanggo buffer iki. Inggih, kaya mangkene - ing Emscripten, untunge, ora ana acak ruang alamat, ora ana bolongan, supaya sampeyan bisa nulis ing endi wae ing tengah kode kanggo ngasilake data kanthi pointer saka peluncuran pungkasan. katon ing data, katon ing pointer, lan, yen durung diganti, njaluk pangan kanggo dipikir. Bener, butuh sawetara menit kanggo nyambung sawise owah-owahan, nanging apa sing bisa ditindakake? Akibaté, baris tartamtu ketemu sing nyalin BIOS saka buffer sauntara kanggo memori tamu - lan, tenan, ana ora cukup papan ing buffer. Nemokake sumber alamat buffer aneh kasebut ngasilake fungsi qemu_anon_ram_alloc ing file oslib-posix.c - logika ana iki: kadhangkala bisa migunani kanggo nyelarasake alamat menyang kaca gedhe kanthi ukuran 2 MB, mula kita bakal takon mmap pisanan sethitik liyane, lan banjur kita bakal bali keluwihan karo bantuan munmap. Lan yen Alignment kuwi ora dibutuhake, banjur kita bakal nuduhake asil tinimbang 2 MB getpagesize() - mmap iku isih bakal menehi metu alamat selaras ... Dadi ing Emscripten mmap mung nelpon malloc, nanging mesthi ora selaras ing kaca. Umumé, bug sing ngganggu aku sajrone sawetara wulan didandani kanthi owah-owahan двух baris.

Fitur saka fungsi nelpon

Lan saiki prosesor ngetung soko, Qemu ora nabrak, nanging layar ora nguripake, lan prosesor cepet dadi puteran, kang menehi kritik dening output. -d exec,in_asm,out_asm. Hipotesis wis muncul: interrupts timer (utawa, umume, kabeh interrupts) ora teka. Lan pancene, yen sampeyan mbusak interrupts saka Déwan asli, kang sakperangan alesan makarya, sampeyan njaluk gambar padha. Nanging iki dudu jawaban: perbandingan jejak sing ditanggepi karo pilihan ing ndhuwur nuduhake yen lintasan eksekusi diverged banget awal. Ing kene kudu dikandhakake yen perbandingan apa sing direkam nggunakake peluncur emrun debugging output karo output saka Déwan native ora proses rampung mechanical. Aku ora ngerti persis carane program mlaku ing browser nyambung menyang emrun, nanging sawetara garis ing output dadi disusun maneh, mula bedane bedane durung dadi alesan kanggo nganggep yen lintasan wis diverged. Umumé, dadi cetha yen miturut pandhuane ljmpl ana transisi kanggo alamat beda, lan bytecode kui dhasar beda: siji ngandhut instruksi kanggo nelpon fungsi helper, liyane ora. Sawise googling instruksi lan sinau kode sing nerjemahake instruksi kasebut, dadi jelas yen, pisanan, langsung sadurunge ing registrasi. cr0 rekaman digawe - uga nggunakake helper - kang ngalih prosesor kanggo mode dilindhungi, lan sareh, sing versi js tau ngalih menyang mode dilindhungi. Nanging kasunyatane yaiku fitur liyane saka Emscripten yaiku ora gelem ngidinke kode kayata implementasine instruksi. call ing TCI, kang sembarang pitunjuk fungsi asil ing jinis long long f(int arg0, .. int arg9) - fungsi kudu diarani kanthi jumlah argumen sing bener. Yen aturan iki dilanggar, gumantung ing setelan debugging, program bakal nabrak (sing apik) utawa nelpon fungsi salah ing kabeh (sing bakal sedih kanggo debug). Ana uga pilihan katelu - ngaktifake generasi wrappers sing nambah / mbusak bantahan, nanging ing total wrappers iki njupuk akeh papan, senadyan kasunyatan sing aku mung perlu sethitik luwih saka satus wrappers. Iki mung sedhih banget, nanging ana masalah sing luwih serius: ing kode sing digawe saka fungsi bungkus, argumen kasebut diowahi lan diowahi, nanging kadhangkala fungsi karo argumen sing digawe ora diarani - uga, kaya ing implementasine libffi sandi. Tegese, sawetara pembantu mung ora dieksekusi.

Untunge, Qemu duwe dhaptar pembantu sing bisa diwaca mesin ing wangun file header kaya

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

Iki digunakake cukup lucu: pisanan, makro didefinisikan maneh kanthi cara sing paling aneh DEF_HELPER_n, banjur nguripake helper.h. Nganti makro ditambahi dadi initializer struktur lan koma, banjur array ditetepake, lan tinimbang unsur - #include <helper.h> Akibaté, aku pungkasanipun duwe kesempatan kanggo nyoba perpustakaan ing karya pyparsing, lan skrip ditulis sing ngasilake bungkus kasebut kanthi tepat kanggo fungsi sing dibutuhake.

Dadi, sawise prosesor katon bisa digunakake. Iku misale jek amarga layar ora tau initialized, sanajan memtest86+ bisa mbukak ing Déwan native. Kene iku perlu kanggo njlentrehake sing pemblokiran Qemu / kode O ditulis ing coroutines. Emscripten duwe implementasine dhewe sing angel banget, nanging isih kudu didhukung ing kode Qemu, lan sampeyan bisa debug prosesor saiki: Qemu ndhukung opsi -kernel, -initrd, -append, karo sampeyan bisa boot Linux utawa, contone, memtest86+, tanpa nggunakake piranti pamblokiran ing kabeh. Nanging iki masalah: ing Déwan asli siji bisa ndeleng output kernel Linux menyang console karo pilihan -nographic, lan ora ana output saka browser menyang terminal saka ngendi iku dibukak emrun, ora teka. Yaiku, ora jelas: prosesor ora bisa digunakake utawa output grafis ora bisa digunakake. Banjur kepikiran aku ngenteni sethithik. Ternyata "prosesor ora turu, nanging mung kedhip alon," lan sawise kira-kira limang menit kernel mbuwang akeh pesen menyang konsol lan terus digantung. Iku dadi cetha yen prosesor, ing umum, bisa, lan kita kudu dig menyang kode kanggo nggarap SDL2. Sayange, aku ora ngerti carane nggunakake perpustakaan iki, supaya ing sawetara panggonan aku kudu tumindak kanthi acak. Ing sawetara titik, garis parallel0 sumunar ing layar kanthi latar mburi biru, sing menehi saran sawetara. Pungkasane, masalah kasebut yaiku Qemu mbukak sawetara windows virtual ing siji jendela fisik, ing antarane sampeyan bisa ngalih nggunakake Ctrl-Alt-n: kerjane ing bangunan asli, nanging ora ing Emscripten. Sawise nyisihake windows sing ora perlu nggunakake opsi -monitor none -parallel none -serial none lan instruksi kanggo forcefully redraw kabeh layar ing saben pigura, kabeh dumadakan bisa.

Coroutines

Dadi, emulasi ing browser bisa digunakake, nanging sampeyan ora bisa mbukak apa wae sing menarik siji-floppy, amarga ora ana blok I / O - sampeyan kudu ngetrapake dhukungan kanggo coroutine. Qemu wis duwe sawetara backend coroutine, nanging amarga sifat JavaScript lan generator kode Emscripten, sampeyan ora bisa miwiti juggling tumpukan. Iku bakal katon yen "kabeh wis ilang, plester dibusak," nanging pangembang Emscripten wis ngurus kabeh. Iki dileksanakake cukup lucu: ayo nelpon telpon fungsi kaya iki curiga emscripten_sleep lan sawetara liyane nggunakake mekanisme Asyncify, uga pointer nelpon lan nelpon kanggo sembarang fungsi ngendi siji saka rong kasus sadurungé bisa kelakon luwih mudhun tumpukan. Lan saiki, sadurunge saben telpon curiga, kita bakal milih konteks async, lan sanalika sawise telpon, kita bakal mriksa apa telpon bedo wis kedaden, lan yen wis, kita bakal nyimpen kabeh variabel lokal ing konteks async iki, nuduhake fungsi kang kanggo nransfer kontrol nalika kita kudu nerusake eksekusi, lan metu saka fungsi saiki. Ing kene ana ruang kanggo sinau efek kasebut ngobrak-abrik - kanggo kabutuhan eksekusi kode terus sawise bali saka telpon bedo, compiler ngasilake "stubs" saka fungsi wiwit sawise telpon curiga - kaya iki: yen ana n telpon curiga, banjur fungsi bakal ditambahi nang endi wae n / 2 kaping - iki isih, yen ora Tetep wonten ing pikiran sing sawise saben telpon potensial bedo, sampeyan kudu nambah nyimpen sawetara variabel lokal kanggo fungsi asli. Sabanjure, aku malah kudu nulis skrip sing prasaja ing Python, sing adhedhasar sawetara fungsi sing asring digunakake sing mesthine "ora ngidini asynchrony ngliwati awake dhewe" (yaiku, promosi tumpukan lan kabeh sing dakcritakake ora. bisa digunakake ing wong-wong mau), nuduhake telpon liwat penunjuk ing ngendi fungsi kudu diabaikan dening compiler supaya fungsi kasebut ora dianggep ora sinkron. Banjur file JS ing sangisore 60 MB cetha banget - ayo ngomong paling ora 30. Sanajan, nalika aku nyiyapake skrip perakitan, lan ora sengaja mbuwang opsi linker, ing antarane yaiku -O3. Aku mbukak kode kui, lan Chromium mangan memori lan tubrukan. Aku banjur ora sengaja ndeleng apa sing arep diundhuh.

Sayange, mriksa ing kode perpustakaan dhukungan Asyncify ora kabeh ramah longjmp-s sing digunakake ing kode prosesor virtual, nanging sawise tembelan cilik sing mateni kir iki lan forcefully mulihake konteks minangka yen kabeh iku nggoleki, kode makarya. Banjur ana sing aneh: kadhangkala mriksa kode sinkronisasi dipicu - sing padha sing nabrak kode yen, miturut logika eksekusi, kudu diblokir - ana wong sing nyoba nyekel mutex sing wis dijupuk. Untunge, iki ora dadi masalah logis ing kode serialisasi - aku mung nggunakake fungsi loop utama standar sing diwenehake dening Emscripten, nanging kadhangkala telpon bedo bakal rampung mbukak tumpukan, lan ing wektu iku bakal gagal. setTimeout saka daur ulang utama - saéngga, kode kasebut mlebu pengulangan daur ulang utama tanpa ninggalake pengulangan sadurunge. Rewrote ing daur ulang tanpa wates lan emscripten_sleep, lan masalah karo mutexes mandheg. Kode kasebut malah dadi luwih logis - sawise kabeh, nyatane, aku ora duwe kode sing nyiapake pigura animasi sabanjure - prosesor mung ngetung lan layar dianyari sacara periodik. Nanging, masalah ora mandheg ing kana: kadhangkala eksekusi Qemu mung bakal mandheg kanthi meneng tanpa pangecualian utawa kesalahan. Ing wayahe aku nyerah, nanging, ngarepake, aku bakal ujar manawa masalahe yaiku: kode coroutine, nyatane, ora nggunakake setTimeout (utawa ing paling ora minangka asring sampeyan bisa mikir): fungsi emscripten_yield mung nyetel gendera telpon bedo. Titik kabeh iku emscripten_coroutine_next ora fungsi bedo: internal mriksa flag, ngreset lan transfer kontrol menyang ngendi iku perlu. Sing, promosi tumpukan ends ana. Masalah iki amarga nggunakake-sawise-free, kang katon nalika blumbang coroutine dipatèni amarga kasunyatan sing aku ora nyalin baris penting kode saka backend coroutine ana, fungsi qemu_in_coroutine bali bener nalika nyatane kudu bali palsu. Iki nyebabake telpon emscripten_yield, ing ndhuwur ora ana wong ing tumpukan emscripten_coroutine_next, tumpukan mbukak menyang ndhuwur banget, nanging ora setTimeout, kaya sing wis dakkandhakake, ora dipamerake.

Generasi kode JavaScript

Lan ing kene, nyatane, ana janji "nguripake daging cincang." Ora temenan. Mesthi, yen kita mbukak Qemu ing browser, lan Node.js ing, banjur, alamiah, sawise generasi kode ing Qemu kita bakal njaluk rampung salah JavaScript. Nanging isih, sawetara jinis transformasi mbalikke.

Pisanan, sethithik babagan cara kerja Qemu. Nyuwun pangapunten: Aku dudu pangembang Qemu profesional lan kesimpulanku bisa uga salah ing sawetara panggonan. Kaya sing dikandhakake, "panemu siswa ora kudu cocog karo pendapat guru, aksioma Peano lan akal sehat." Qemu duwe sawetara arsitektur tamu sing didhukung lan kanggo saben ana direktori kaya target-i386. Nalika mbangun, sampeyan bisa nemtokake dhukungan kanggo sawetara arsitektur tamu, nanging asile mung sawetara binari. Kode kanggo ndhukung arsitektur tamu, ing siji, ngasilake sawetara operasi Qemu internal, kang TCG (Tiny Code Generator) wis dadi kode mesin kanggo arsitektur host. Kaya sing kasebut ing file readme sing ana ing direktori tcg, iki asline minangka bagian saka kompiler C biasa, sing banjur diadaptasi kanggo JIT. Mulane, contone, arsitektur target ing babagan dokumen iki ora maneh arsitektur tamu, nanging arsitektur host. Ing sawetara titik, komponen liyane muncul - Tiny Code Interpreter (TCI), sing kudu nglakokake kode (meh operasi internal sing padha) tanpa ana generator kode kanggo arsitektur host tartamtu. Nyatane, minangka dokumentasi nyatakake, interpreter iki bisa uga ora tansah nindakake minangka generator kode JIT, ora mung kuantitatif saka segi kacepetan, nanging uga kualitatif. Sanajan aku ora yakin manawa katrangane pancen cocog.

Ing kawitan aku nyoba kanggo nggawe backend TCG lengkap, nanging cepet bingung ing kode sumber lan gambaran ora tanggung cetha saka instruksi bytecode, aku mutusaké kanggo Lebokake TCI interpreter. Iki menehi sawetara kaluwihan:

  • nalika ngleksanakake generator kode, sampeyan bisa ndeleng ora ing gambaran saka instruksi, nanging kode interpreter
  • sampeyan bisa generate fungsi ora kanggo saben pemblokiran terjemahan ketemu, nanging, contone, mung sawise eksekusi satus
  • yen kode kui diganti (lan iki misale jek bisa, kang menehi kritik dening fungsi karo jeneng ngemot tembung patch), Aku kudu mbatalake kode JS kui, nanging paling aku bakal duwe soko kanggo regenerate saka

Babagan titik katelu, Aku ora yakin sing patching bisa sawise kode kaleksanan pisanan, nanging loro TCTerms pisanan cukup.

Kaping pisanan, kode kasebut digawe ing wangun switch gedhe ing alamat instruksi bytecode asli, nanging banjur, ngelingi artikel babagan Emscripten, optimasi JS sing digawe lan relooping, aku mutusake kanggo ngasilake luwih akeh kode manungsa, utamane amarga sacara empiris. dadi siji-sijine titik mlebu menyang blok terjemahan yaiku Mulai. Ora cepet ngandika saka rampung, sawise nalika kita wis generator kode sing kui kode karo ifs (sanajan tanpa puteran). Nanging nasib ala, tabrakan, menehi pesen manawa instruksi kasebut dawane salah. Kajaba iku, instruksi pungkasan ing tingkat rekursi iki yaiku brcond. Oke, aku bakal nambah mriksa podho rupo kanggo generasi instruksi iki sadurunge lan sawise telpon rekursif lan ... ora siji saka wong-wong mau kaleksanan, nanging sawise ngalih negasake isih gagal. Pungkasane, sawise sinau kode sing digawe, aku ngerti yen sawise ngalih, pointer menyang instruksi saiki diisi ulang saka tumpukan lan bisa uga ditindih dening kode JavaScript sing digawe. Lan ternyata. Nambah buffer saka siji megabyte kanggo sepuluh ora mimpin kanggo apa-apa, lan dadi cetha yen generator kode mlaku ing bunderan. Kita kudu mriksa manawa kita ora ngluwihi wates TB saiki, lan yen ditindakake, banjur ngetokake alamat TB sabanjure kanthi tandha minus supaya bisa nerusake eksekusi. Kajaba iku, iki ngrampungake masalah "fungsi sing digawe sing kudu dibatalake yen potongan bytecode iki wis diganti?" — mung fungsi sing cocog karo pemblokiran terjemahan iki sing kudu dibatalake. Miturut cara, sanajan aku debugged kabeh ing Chromium (amarga aku nggunakake Firefox lan luwih gampang kanggo kula kanggo nggunakake browser kapisah kanggo nyobi), Firefox mbantu kula incompatibilities mbenerake karo standar asm.js, sawise kode wiwit bisa luwih cepet ing. Kromium.

Tuladha kode sing digawe

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"]

kesimpulan

Dadi, karya isih durung rampung, nanging aku kesel meneng-menengan nggawa konstruksi jangka panjang iki kanthi sampurna. Mulane, aku mutusake kanggo nerbitake apa sing aku duwe saiki. Kode punika sethitik medeni ing panggonan, amarga iki eksperimen, lan iku ora cetha ing advance apa kudu rampung. Mbokmenawa, banjur kudu ngetokake komitmen atom normal ing ndhuwur sawetara versi Qemu sing luwih modern. Ing sawetoro wektu, ana utas ing Gita ing format blog: kanggo saben "tingkat" sing paling ora wis liwati, komentar rinci ing basa Rusia wis ditambahake. Bener, artikel iki umume nyritakake kesimpulan git log.

Sampeyan bisa nyoba kabeh kene (ati-ati karo lalu lintas).

Apa sing wis digunakake:

  • x86 prosesor virtual mlaku
  • Ana prototipe kerja generator kode JIT saka kode mesin menyang JavaScript
  • Ana cithakan kanggo ngrakit arsitektur tamu 32-bit liyane: saiki sampeyan bisa ngujo Linux amarga arsitektur MIPS beku ing browser ing tahap loading.

Apa maneh sing bisa ditindakake

  • Nyepetake emulasi. Malah ing mode JIT katon luwih alon tinimbang Virtual x86 (nanging ana potensial Qemu kanthi akeh hardware lan arsitektur sing ditiru)
  • Kanggo nggawe antarmuka normal - terus terang, aku dudu pangembang web sing apik, mula saiki aku wis nggawe maneh cangkang Emscripten standar sing paling apik.
  • Coba bukak fungsi Qemu sing luwih rumit - jaringan, migrasi VM, lsp.
  • UPD: sampeyan kudu ngirim sawetara perkembangan lan laporan bug menyang Emscripten hulu, kaya porter Qemu lan proyek liyane sadurunge. Thanks kanggo wong-wong mau amarga bisa nggunakake kontribusi kanggo Emscripten minangka bagean saka tugasku.

Source: www.habr.com

Add a comment