Qemu.js kalawan rojongan JIT: Anjeun masih bisa balik mince ka tukang

Sababaraha taun ka pengker Fabrice Bellard ditulis ku jslinux mangrupakeun émulator PC ditulis dina JavaScript. Sanggeus éta aya sahanteuna leuwih Virtual x86. Tapi sakabéh éta, sajauh I terang, éta juru, bari Qemu, ditulis loba saméméhna ku Fabrice Bellard sarua, sarta, meureun, sagala timer respecting émulator modern, ngagunakeun JIT kompilasi kode tamu kana kode sistem host. Ieu seemed kuring yén éta waktu pikeun ngalaksanakeun tugas sabalikna dina hubungan hiji nu browser ngajawab: JIT kompilasi kode mesin kana JavaScript, nu eta seemed paling logis port Qemu. Ieu bakal sigana, naha Qemu, aya emulators basajan tur ramah-pamaké - sarua VirtualBox, contona - dipasang na jalan. Tapi Qemu gaduh sababaraha fitur anu pikaresepeun

  • open source
  • kamampuhan pikeun digawé tanpa supir kernel
  • kamampuhan pikeun digawé dina mode juru
  • dukungan pikeun sajumlah ageung arsitéktur host sareng tamu

Ngeunaan titik katilu, kuring ayeuna tiasa ngajelaskeun yén kanyataanna, dina modeu TCI, sanés paréntah mesin tamu sorangan anu diinterpretasi, tapi bytecode dicandak ti aranjeunna, tapi ieu henteu ngarobih hakekatna - pikeun ngawangun sareng ngajalankeun. Qemu on arsitéktur anyar, lamun geus untung, A C kompiler cukup - nulis generator kode bisa ditunda.

Sareng ayeuna, saatos dua taun santai sareng kode sumber Qemu dina waktos luang kuring, prototipe kerja muncul, dimana anjeun parantos tiasa ngajalankeun, contona, Kolibri OS.

Naon Emscripten

Kiwari, seueur kompiler parantos muncul, hasil ahirna nyaéta JavaScript. Sababaraha, sapertos Type Script, asalna dimaksudkeun pikeun janten cara anu pangsaéna pikeun nyerat wéb. Dina waktos anu sami, Emscripten mangrupikeun cara nyandak kode C atanapi C ++ anu tos aya sareng nyusun kana bentuk anu tiasa dibaca ku browser. Dina kaca ieu Kami parantos ngumpulkeun seueur palabuhan program anu terkenal: di dieuContona, anjeun tiasa ningali PyPy - ku jalan kitu, aranjeunna ngaku geus boga JIT. Kanyataanna, teu unggal program bisa saukur disusun tur dijalankeun dina browser - aya sababaraha Fitur, anu anjeun kedah laksanakeun, sapertos prasasti dina halaman anu sami nyarios "Emscripten tiasa dianggo pikeun nyusun ampir sadayana gampang dimawa C / C ++ kode ka JavaScript ". Hartina, aya sababaraha operasi anu kabiasaan undefined nurutkeun standar, tapi biasana dianggo dina x86 - contona, aksés unaligned kana variabel, nu umumna dilarang dina sababaraha arsitéktur. , Qemu mangrupakeun program cross-platform na, Kuring hayang yakin, sarta eta teu acan ngandung loba kabiasaan undefined - nyandak eta sarta compile, lajeng tinker saeutik kalawan JIT - jeung anjeun geus rengse! kasus...

Usaha munggaran

Sacara umum, kuring sanés jalma anu munggaran anu gaduh ide pikeun ngalihkeun Qemu kana JavaScript. Aya patarosan anu ditaroskeun dina forum ReactOS upami ieu mungkin nganggo Emscripten. Malah baheula, aya gosip yén Fabrice Bellard ngalakukeun ieu sacara pribadi, tapi urang ngobrol ngeunaan jslinux, anu, sajauh kuring terang, ngan ukur usaha pikeun ngahontal prestasi anu cekap dina JS, sareng ditulis ti mimiti. Engké, Virtual x86 ditulis - sumber unobfuscated dipasang pikeun eta, sarta, sakumaha nyatakeun, "realisme" gede emulation ngamungkinkeun ngagunakeun SeaBIOS salaku firmware. Sajaba ti éta, aya sahanteuna hiji usaha port Qemu maké Emscripten - Kuring diusahakeun ngalakukeun ieu sapasang stop kontak, tapi ngembangkeun, sajauh I ngartos, ieu beku.

Janten, sigana, ieu sumberna, ieu Emscripten - cokot sareng kompilkeun. Tapi aya ogé perpustakaan anu Qemu gumantung, sareng perpustakaan anu gumantung kana perpustakaan, jsb., sareng salah sahijina nyaéta libffi, nu glib gumantung kana. Aya gosip dina Internét yén aya hiji dina koleksi ageung palabuhan perpustakaan pikeun Emscripten, tapi kumaha waé hésé percanten: kahiji, éta henteu dimaksudkeun pikeun janten kompiler énggal, kadua, éta tingkat rendah teuing. perpustakaan ngan nyokot, sarta compile ka JS. Tur éta henteu ngan masalah inserts assembly - meureun, lamun pulas eta, pikeun sababaraha konvénsi nelepon bisa ngahasilkeun argumen diperlukeun dina tumpukan jeung nelepon fungsi tanpa aranjeunna. Tapi Emscripten mangrupikeun hal anu sesah: supados kode anu dibangkitkeun katingali akrab sareng pangoptimal mesin JS browser, sababaraha trik dianggo. Khususna, anu disebut relooping - generator kode anu nganggo LLVM IR anu ditampi sareng sababaraha petunjuk transisi abstrak nyobian nyiptakeun deui upami, puteran, jsb. Nya, kumaha argumen disalurkeun kana fungsina? Alami, salaku argumen pikeun fungsi JS, nyaeta, lamun mungkin, teu ngaliwatan tumpukan.

Dina awalna aya hiji gagasan pikeun saukur nulis gaganti pikeun libffi kalawan JS tur ngajalankeun tés baku, tapi tungtungna kuring meunang bingung ngeunaan kumaha carana sangkan file lulugu kuring ambéh maranéhanana bakal dianggo kalayan kode nu aya - naon bisa I do, sabab nyebutkeun, "Dupi tugas jadi kompléks "Naha urang jadi bodo?" Kuring kungsi port libffi mun arsitéktur sejen, jadi mun nyarita - untungna, Emscripten boga duanana macros pikeun assembly inline (dina Javascript, hehehehe - ogé, naon arsitéktur, jadi assembler nu), sarta kamampuhan pikeun ngajalankeun kode dihasilkeun dina laleur nu. Sacara umum, sanggeus tinkering kalawan fragmen libffi platform-gumantung pikeun sawatara waktu, Kuring meunang sababaraha kode compilable sarta ngajalankeun eta dina test munggaran kuring datang di sakuliah. Pikeun reuwas kuring, tés éta suksés. Stunned ku genius kuring - euweuh lulucon, éta digawé ti peluncuran munggaran - kuring, masih teu percanten panon kuring, indit ka kasampak di kode hasilna deui, pikeun evaluate dimana ngagali salajengna. Di dieu kuring balik kacangan pikeun kadua kalina - hijina hal fungsi kuring tuh éta ffi_call - ieu dilaporkeun panggero suksés. Aya henteu nelepon sorangan. Janten kuring ngintunkeun pamundut tarik anu munggaran, anu ngabenerkeun kasalahan dina tés anu jelas pikeun murid Olimpiade - angka riil henteu kedah dibandingkeun sareng a == b komo kumaha a - b < EPS - Anjeun oge kudu apal modul nu, disebutkeun 0 bakal tétéla jadi pisan sarua jeung 1/3 ... Sacara umum, kuring datang nepi ka port tangtu libffi, nu ngaliwatan tés pangbasajanna, jeung nu glib nyaeta disusun - Kuring mutuskeun eta bakal perlu, Kuring gé nambahan eta engké. Ningali payun, kuring bakal nyarios yén, tétéla, kompiler henteu kalebet fungsi libffi dina kode ahir.

Tapi, sakumaha anu kuring parantos nyarios, aya sababaraha watesan, sareng diantara panggunaan gratis tina sagala rupa paripolah anu teu jelas, fitur anu langkung pikaresepeun disumputkeun - JavaScript ku desain henteu ngadukung multithreading kalayan mémori anu dibagikeun. Sacara prinsip, ieu biasana malah bisa disebut mangrupakeun ide nu sae, tapi teu keur porting kode anu arsitéktur dihijikeun ka C threads. Umumna disebutkeun, Firefox keur experimenting kalawan ngarojong pagawe dibagikeun, sarta Emscripten boga palaksanaan pthread pikeun aranjeunna, tapi kuring teu hayang gumantung kana eta. Kuring kungsi lalaunan akar kaluar multithreading tina kode Qemu - nyaeta, manggihan dimana threads ngajalankeun, mindahkeun awak loop ngajalankeun dina thread ieu kana fungsi misah, sarta nelepon fungsi misalna hiji-hiji ti loop utama.

Usaha kadua

Di sawatara titik, éta janten jelas yén masalah éta kénéh aya, sarta yén haphazardly shoving crutches sabudeureun kode moal ngakibatkeun sagala alus. Kacindekan: urang kudu kumaha bae sistimatis prosés nambahkeun crutches. Ku alatan éta, vérsi 2.4.1, anu seger dina waktos éta, dicandak (sanés 2.5.0, sabab, saha anu terang, bakal aya bug dina vérsi énggal anu henteu acan katangkep, sareng kuring cekap ku bug kuring sorangan. ), sarta hal kahiji nya éta nulis ulang eta aman thread-posix.c. Nya, nyaéta, aman: upami aya anu nyobian ngalaksanakeun operasi anu ngarah ka blocking, fungsina langsung disebut abort() - tangtosna, ieu teu ngajawab sakabéh masalah sakaligus, tapi sahenteuna ieu kumaha bae leuwih pikaresepeun ti quietly narima data inconsistent.

Sacara umum, pilihan Emscripten pohara mantuan dina porting kode ka JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - aranjeunna nyekel sababaraha jinis paripolah anu teu ditangtukeun, sapertos nelepon ka alamat anu teu saluyu (anu henteu saluyu sareng kodeu pikeun susunan anu diketik sapertos HEAP32[addr >> 2] = 1) atawa nelepon hiji fungsi kalawan jumlah salah sahiji argumen.

Ku jalan kitu, kasalahan alignment mangrupikeun masalah anu misah. Sakumaha anu kuring parantos nyarios, Qemu gaduh backend interpretif "degenerate" pikeun generasi kode TCI (interpreter kode leutik), sareng ngawangun sareng ngajalankeun Qemu dina arsitéktur énggal, upami anjeun untung, kompiler C cekap. "lamun anjeun untung". Kuring sial, sareng tétéla yén TCI nganggo aksés anu teu kabeungkeut nalika nga-parsing bytecode na. Nyaéta, dina sagala jinis ARM sareng arsitéktur anu sanés kalayan aksés anu ditujukeun, Qemu ngumpulkeun sabab gaduh backend TCG normal anu ngahasilkeun kode asli, tapi naha TCI bakal ngerjakeun aranjeunna mangrupikeun patarosan sanés. Tapi, sakumaha tétéla, dokuméntasi TCI jelas nunjukkeun hal anu sami. Hasilna, sauran fungsi pikeun bacaan anu teu saluyu ditambah kana kodeu, anu kapanggih di bagian Qemu anu sanés.

Tumpukan karuksakan

Hasilna, aksés unaligned mun TCI dilereskeun, loop utama dijieun anu dina gilirannana disebut processor, RCU sarta sababaraha hal leutik lianna. Janten kuring ngaluncurkeun Qemu kalayan pilihan -d exec,in_asm,out_asm, nu hartina anjeun kudu nyebutkeun blok kode nu keur dieksekusi, sarta ogé dina waktu siaran nulis naon kodeu tamu, naon kode host jadi (dina hal ieu, bytecode). Dimimitian, ngalaksanakeun sababaraha blok tarjamahan, nyerat pesen debugging anu kuring tinggalkeun yén RCU ayeuna bakal ngamimitian sareng ... abort() di jero hiji fungsi free(). Ku tinkering kalawan fungsi free() Kami mendakan yén dina lulugu blok tumpukan, anu aya dina dalapan bait sateuacan mémori anu dialokasikeun, tibatan ukuran blok atanapi anu sami, aya sampah.

Karuksakan numpuk - kumaha lucu ... Dina kasus kawas, aya ubar mangpaat - ti (lamun mungkin) sumber anu sarua, ngumpul binér pituin tur ngajalankeun eta dina Valgrind. Sanggeus sababaraha waktu, binér geus siap. Kuring ngaluncurkeun éta kalayan pilihan anu sami - éta ngadat sanajan dina initialization, sateuacan leres-leres ngahontal palaksanaan. Éta pikaresepeun, tangtosna - katingalina, sumberna henteu persis sami, anu henteu héran, sabab konpigurasikeun milarian pilihan anu rada béda, tapi kuring ngagaduhan Valgrind - mimitina kuring bakal ngalereskeun bug ieu, teras, upami kuring untung. , anu asli bakal muncul. Kuring ngajalankeun hal anu sarua dina Valgrind ... Y-y-y, y-y-y, uh-uh, eta dimimitian, indit ngaliwatan initialization normal tur dipindahkeun kana kaliwat bug aslina tanpa peringatan tunggal ngeunaan aksés memori lepat, teu nyebut ngeunaan ragrag. Hirup, sakumaha anu aranjeunna nyarios, henteu nyiapkeun kuring pikeun ieu - program nabrak eureun nabrak nalika diluncurkeun dina Walgrind. Naon éta téh misteri hiji. Hipotesis kuring nyaéta sakali di sakuriling instruksi ayeuna saatos kacilakaan nalika initialization, gdb nunjukkeun padamelan. memset-a ku pointer valid ngagunakeun boh mmx, atawa xmm registers, lajeng sugan éta sababaraha jenis kasalahan alignment, sanajan éta kénéh hésé yakin.

Oke, Valgrind sigana henteu ngabantosan di dieu. Sarta di dieu hal paling disgusting dimimitian - sagalana sigana malah dimimitian, tapi ngadat alesan pisan kanyahoan alatan hiji kajadian anu bisa lumangsung jutaan parentah ka tukang. Lila-lila, teu jelas kumaha carana ngadeukeutan. Tungtungna, kuring masih kedah calik sareng debug. Nyitak naon lulugu ieu ditulis deui kalawan némbongkeun yén éta teu kasampak kawas angka, tapi rada sababaraha jenis data binér. Na, lo na behold, string binér ieu kapanggih dina file mios - nyaeta, ayeuna kasebut nyaéta dimungkinkeun pikeun nyebutkeun kalawan kayakinan lumrah yén ieu panyangga ngabahekeun, sarta eta malah jelas yén ieu ditulis kana panyangga ieu. Nya, teras sapertos kieu - dina Emscripten, untungna, henteu aya acak ruang alamat, henteu aya liang di dinya, ku kituna anjeun tiasa nyerat dimana waé di tengah kode pikeun kaluaran data ku pointer ti peluncuran anu terakhir. tingali dina data, tingali dina pointer, jeung, lamun teu robah, meunang dahareun pikeun pamikiran. Leres, peryogi sababaraha menit pikeun nyambungkeun saatos parobihan, tapi naon anu anjeun tiasa laksanakeun? Hasilna, hiji garis husus kapanggih yén disalin mios ti panyangga samentara ka mémori tamu - na, saleresna, aya teu cukup spasi dina panyangga. Pananjung sumber éta alamat panyangga aneh nyababkeun hiji fungsi qemu_anon_ram_alloc dina file oslib-posix.c - logika aya kieu: sakapeung tiasa mangpaat pikeun align alamat ka kaca badag ukuranana 2 MB, pikeun ieu kami bakal nanya mmap mimitina saeutik deui, lajeng urang bakal balik kaleuwihan jeung pitulung munmap. Sareng upami alignment sapertos kitu henteu diperyogikeun, maka kami bakal nunjukkeun hasil tibatan 2 MB getpagesize() - mmap eta masih bakal masihan kaluar alamat Blok ... Jadi di Emscripten mmap ngan nelepon malloc, Tapi tangtu teu align dina kaca. Sacara umum, bug anu ngaganggu kuring salami sababaraha bulan dilereskeun ku parobihan двух garis.

Fitur fungsi nelepon

Sareng ayeuna prosesor ngitung hiji hal, Qemu henteu nabrak, tapi layarna henteu hurung, sareng prosésor gancang-gancang janten puteran, ditilik ku kaluaran. -d exec,in_asm,out_asm. Hiji hipotesa geus mecenghul: timer interrupts (atawa, sacara umum, sadaya interrupts) teu datang. Jeung memang, lamun unscrew nu interrupts ti assembly asli, nu keur sababaraha alesan digawé, anjeun meunang gambar nu sarupa. Tapi ieu sanés jawabanna: ngabandingkeun jejak anu dikaluarkeun sareng pilihan di luhur nunjukkeun yén lintasan palaksanaan diverged pisan awal. Di dieu kudu disebutkeun yen ngabandingkeun tina naon anu dirékam maké launcher nu emrun kaluaran debugging kalawan kaluaran assembly asli sanes prosés lengkep mékanis. Kuring henteu weruh persis kumaha program ngajalankeun dina browser nyambung ka emrun, Tapi sababaraha garis dina kaluaran tétéla disusun ulang, jadi bédana dina diff teu acan alesan pikeun nganggap yén trajectories geus diverged. Sacara umum, éta jelas yén nurutkeun parentah ljmpl aya transisi ka alamat béda, sarta bytecode dihasilkeun fundamentally béda: hiji ngandung instruksi pikeun nelepon hiji fungsi nulungan, nu sejenna henteu. Saatos googling petunjuk sareng diajar kode anu narjamahkeun paréntah ieu, janten jelas yén, mimitina, langsung sateuacanna dina daptar cr0 rekaman dijieun - ogé ngagunakeun asisten - nu switched processor kana mode ditangtayungan, jeung Bréh, yén versi JS pernah switched ka modeu ditangtayungan. Tapi kanyataanna nyaéta fitur anu sanés tina Emscripten nyaéta horéam pikeun toleran kode sapertos palaksanaan paréntah. call dina TCI, nu mana wae fungsi pointer hasil dina tipe long long f(int arg0, .. int arg9) - fungsi kudu disebut kalawan jumlah bener argumen. Upami aturan ieu dilanggar, gumantung kana setélan debugging, program bakal nabrak (anu saé) atanapi nyauran fungsi anu salah pisan (anu bakal sedih pikeun debug). Aya ogé pilihan katilu - aktipkeun generasi wrappers nu nambahkeun / miceun argumen, tapi dina total wrappers ieu nyokot loba spasi, sanajan kanyataan yén dina kanyataanana kuring ngan butuh saeutik leuwih ti saratus wrappers. Ieu nyalira pisan hanjelu, tapi tétéla janten masalah anu langkung serius: dina kode anu dihasilkeun tina fungsi wrapper, argumen dirobih sareng dirobih, tapi sakapeung fungsi sareng argumen anu dibangkitkeun henteu disebut - ogé, sapertos dina palaksanaan libffi kuring. Hartina, sababaraha pembantuna ngan saukur teu dieksekusi.

Untungna, Qemu ngagaduhan daptar pembantu anu tiasa dibaca ku mesin dina bentuk file header sapertos

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

Éta dianggo rada lucu: kahiji, makro didefinisikeun deui ku cara anu paling anéh DEF_HELPER_n, teras hurungkeun helper.h. Pikeun extent yén makro nu dimekarkeun jadi initializer struktur jeung koma, lajeng hiji Asép Sunandar Sunarya diartikeun, sarta tinimbang elemen - #include <helper.h> Hasilna, kuring tungtungna ngagaduhan kasempetan pikeun nyobian perpustakaan di tempat damel pyparsing, sareng skrip ditulis anu ngahasilkeun persis bungkus éta pikeun fungsi anu diperyogikeun.

Jeung saterusna, sanggeus éta processor seemed jalan. Éta sigana kusabab layar henteu pernah diinisialisasi, sanaos memtest86+ tiasa dijalankeun dina rakitan asli. Di dieu perlu netelakeun yén blok Qemu I / O kode ditulis dina coroutines. Emscripten boga palaksanaan pisan tricky sorangan, tapi masih perlu dirojong dina kode Qemu, tur anjeun tiasa debug prosésor ayeuna: Qemu ngarojong pilihan. -kernel, -initrd, -append, anu anjeun tiasa boot Linux atanapi, contona, memtest86+, tanpa nganggo alat blok. Tapi ieu masalahna: dina assembly asli bisa ningali kaluaran kernel Linux Ubuntu kana konsol jeung pilihan -nographic, tur euweuh kaluaran ti browser ka terminal ti mana eta dibuka emrun, teu datang. Hartina, teu jelas: prosésor henteu jalan atanapi kaluaran grafik henteu jalan. Lajeng éta lumangsung pikeun kuring ngadagoan saeutik. Tétéla yén "processor henteu bobo, tapi ngan saukur kedip-kedip lalaunan," sareng saatos kira-kira lima menit kernel ngalungkeun sakumpulan pesen kana konsol sareng teras ngagantung. Janten jelas yén prosésor, sacara umum, tiasa dianggo, sareng urang kedah ngagali kodeu pikeun damel sareng SDL2. Hanjakalna, kuring henteu terang kumaha ngagunakeun perpustakaan ieu, janten di sababaraha tempat kuring kedah ngalakukeun sacara acak. Di sawatara titik, garis parallel0 flashed dina layar dina latar biru, nu nyarankeun sababaraha pikiran. Tungtungna, tétéla yén masalahna nyaéta yén Qemu muka sababaraha windows virtual dina hiji jandela fisik, antara anu anjeun tiasa ngalih nganggo Ctrl-Alt-n: éta tiasa dianggo dina ngawangun asli, tapi henteu dina Emscripten. Saatos ngaleungitkeun windows anu teu perlu nganggo pilihan -monitor none -parallel none -serial none jeung parentah pikeun forcefully redraw sakabéh layar dina unggal pigura, sagalana dumadakan digawé.

Coroutines

Janten, emulation dina browser tiasa dianggo, tapi anjeun moal tiasa ngajalankeun naon waé anu pikaresepeun dina floppy tunggal, sabab henteu aya blok I / O - anjeun kedah ngalaksanakeun dukungan pikeun coroutines. Qemu geus boga sababaraha backends coroutine, tapi alatan sipat JavaScript jeung generator kode Emscripten, anjeun teu bisa ngan ngamimitian juggling tumpukan. Éta sigana yén "sadayana musna, moyok dipiceun," tapi pamekar Emscripten parantos ngurus sadayana. Ieu dilaksanakeun rada lucu: hayu urang nelepon fungsi kawas kieu curiga emscripten_sleep sarta sababaraha séjén ngagunakeun mékanisme Asyncify, kitu ogé pointer nelepon jeung nelepon ka sagala fungsi mana salah sahiji dua kasus saméméhna bisa lumangsung salajengna handap tumpukan. Sareng ayeuna, sateuacan unggal telepon anu curiga, urang bakal milih kontéks async, sareng langsung saatos telepon, urang bakal mariksa naha telepon asynchronous parantos kajantenan, sareng upami aya, urang bakal nyimpen sadaya variabel lokal dina kontéks async ieu, nunjukkeun fungsi mana. pikeun mindahkeun kontrol ka nalika urang kudu neruskeun palaksanaan , tur kaluar tina fungsi ayeuna. Ieu dimana aya wengkuan pikeun diajar pangaruh ngaboro-boro - pikeun kaperluan neraskeun palaksanaan kode sanggeus balik ti panggero Asynchronous, kompiler ngahasilkeun "stubs" tina fungsi dimimitian sanggeus panggero curiga - kawas kieu: lamun aya n telepon curiga, fungsi bakal dilegakeun wae n / 2 kali - ieu masih, lamun teu Émut yén sanggeus unggal panggero berpotensi Asynchronous, Anjeun kudu nambahkeun nyimpen sababaraha variabel lokal kana fungsi aslina. Salajengna, kuring malah kedah nyerat skrip saderhana dina Python, anu, dumasar kana set anu dipasihkeun tina fungsi anu sering dianggo anu konon "henteu ngijinkeun asynchrony ngalangkungan diri" (nyaéta, promosi tumpukan sareng sadayana anu kuring nerangkeun henteu. dianggo di aranjeunna), nunjukkeun telepon ngaliwatan pointer dimana fungsina kedah dipaliré ku kompiler supados fungsi ieu henteu dianggap asinkron. Lajeng file JS sahandapeun 60 MB jelas teuing - hayu urang nyebutkeun sahenteuna 30. Sanajan, sakali kuring nyetel hiji Aksara assembly, sarta ngahaja threw kaluar pilihan linker, diantara nu éta. -O3. Kuring ngajalankeun kodeu dihasilkeun, sarta Chromium eats up memori sareng ngadat. Kuring lajeng ngahaja nempo naon anjeunna nyobian pikeun ngundeur ... Muhun, naon bisa kuring ngomong, abdi bakal beku teuing lamun kuring geus dipenta pikeun thoughtfully diajar tur ngaoptimalkeun a 500+ MB Javascript.

Hanjakalna, cek dina kode perpustakaan dukungan Asyncify henteu sapinuhna ramah longjmp-s nu dipaké dina kode processor virtual, tapi sanggeus hiji patch leutik nu disables cék ieu sarta forcefully restores konteks saolah-olah sagalana éta rupa, kode digawé. Lajeng hiji hal aneh dimimitian: kadang cek dina kode sinkronisasi dipicu - sarua nu ngadat kodeu lamun, nurutkeun logika palaksanaan, éta kudu diblokir - batur nyoba grab mutex geus direbut. Untungna, ieu tétéla teu jadi masalah logis dina kode serialized - Kuring ngan ngagunakeun pungsionalitas loop utama baku disadiakeun ku Emscripten, tapi kadang panggero Asynchronous sagemblengna unwrap tumpukan, sarta dina momen eta bakal gagal. setTimeout ti loop utama - sahingga, kode diasupkeun Iteration loop utama tanpa ninggalkeun Iteration saméméhna. Rewrote on hiji loop tanpa wates jeung emscripten_sleep, jeung masalah sareng mutexes dieureunkeun. Kodeu malah janten langkung logis - barina ogé, kanyataanna, kuring henteu ngagaduhan kode anu nyiapkeun pigura animasi salajengna - prosesor ngan ukur ngitung hiji hal sareng layarna diropéa périodik. Tapi, masalahna henteu eureun di dinya: sakapeung palaksanaan Qemu ngan saukur ngeureunkeun cicingeun tanpa aya pengecualian atanapi kasalahan. Dina waktos éta kuring nyerah, tapi, ningali payun, kuring bakal nyarios yén masalahna nyaéta ieu: kode coroutine, kanyataanna, henteu nganggo. setTimeout (atanapi sahenteuna henteu sering anjeun pikir): fungsi emscripten_yield ngan saukur nyetel bandéra panggero Asynchronous. Sakabeh titik éta emscripten_coroutine_next sanes hiji fungsi Asynchronous: internal eta cek bandéra, ngareset eta sarta mindahkeun kontrol ka mana eta diperlukeun. Hartina, promosi tumpukan ends dinya. Masalahna éta alatan pamakéan-sanggeus-gratis, nu mucunghul nalika kolam renang coroutine ditumpurkeun alatan kanyataan yén kuring teu nyalin hiji garis penting kode ti backend coroutine aya, fungsi. qemu_in_coroutine dipulangkeun leres padahal saleresna kedah dipulangkeun palsu. Ieu ngarah ka nelepon emscripten_yield, di luhur teu aya hiji dina tumpukan emscripten_coroutine_next, tumpukan unfolded ka pisan luhur, tapi euweuh setTimeout, sakumaha kuring geus ngomong, teu exhibited.

Generasi kode JavaScript

Sareng di dieu, kanyataanna, jangji "ngabalikeun daging minced." Henteu ogé. Tangtosna, upami urang ngajalankeun Qemu dina browser, sareng Node.js di jerona, maka, sacara alami, saatos generasi kode di Qemu urang bakal nampi JavaScript anu salah. Tapi tetep, sababaraha jenis transformasi sabalikna.

Kahiji, saeutik ngeunaan kumaha Qemu jalan. Hapunten kuring langsung: Abdi sanés pamekar Qemu profésional sareng kasimpulan kuring tiasa salah dina sababaraha tempat. Sakumaha aranjeunna nyarios, "pendapat murid henteu kedah saluyu sareng pendapat guru, axiomatics sareng akal sehat Peano." Qemu gaduh sababaraha arsitéktur tamu anu dirojong sareng masing-masing aya diréktori sapertos target-i386. Nalika ngawangun, anjeun tiasa netepkeun dukungan pikeun sababaraha arsitéktur tamu, tapi hasilna ngan ukur sababaraha binér. Kodeu pikeun ngarojong arsitektur tamu, kahareupna ngahasilkeun sababaraha operasi Qemu internal, nu TCG (Tiny Code generator) geus robah jadi kode mesin keur arsitektur host. Sakumaha anu dinyatakeun dina file readme anu aya dina diréktori tcg, ieu asalna bagian tina kompiler C biasa, anu engké diadaptasi pikeun JIT. Ku alatan éta, contona, arsitéktur udagan dina hal dokumén ieu sanés deui arsitéktur tamu, tapi arsitéktur host. Di sawatara titik, komponén séjén némbongan - Tiny Code Interpreter (TCI), nu kedah ngajalankeun kode (ampir operasi internal sarua) dina henteuna generator kode pikeun arsitéktur host husus. Kanyataanna, sakumaha dokuméntasi na nyatakeun, juru ieu teu salawasna ngalakukeun sakumaha ogé generator kode JIT, teu ukur kuantitatif dina watesan speed, tapi ogé kualitatif. Sanajan kuring teu yakin yén pedaran na sagemblengna relevan.

Mimitina kuring nyoba nyieun backend TCG full-fledged, tapi gancang meunang bingung dina kode sumber sarta pedaran teu sagemblengna jelas ngeunaan parentah bytecode, jadi kuring mutuskeun pikeun mungkus juru TCI. Ieu masihan sababaraha kaunggulan:

  • Nalika ngalaksanakeun generator kode, anjeun moal tiasa ningali kana pedaran paréntah, tapi dina kode juru
  • Anjeun tiasa ngahasilkeun fungsi teu keur unggal blok tarjamah encountered, tapi, contona, ngan sanggeus palaksanaan hundredth
  • lamun kodeu dihasilkeun robah (jeung ieu sigana mungkin, ditilik ku fungsi kalawan ngaran ngandung kecap patch), abdi bakal perlu invalidate kode JS dihasilkeun, tapi sahenteuna kuring bakal boga hal pikeun regenerate tina.

Ngeunaan titik katilu, Kaula teu yakin kana éta patching mungkin sanggeus kode dieksekusi pikeun kahiji kalina, tapi dua titik munggaran cukup.

Mimitina, kodeu dihasilkeun dina bentuk switch badag dina alamat tina instruksi bytecode aslina, tapi lajeng, remembering artikel ngeunaan Emscripten, optimasi JS dihasilkeun sarta relooping, abdi mutuskeun pikeun ngahasilkeun leuwih kode manusa, utamana saprak émpiris eta. Tétéla yén hiji-hijina titik asup kana blok tarjamahan nyaéta Start na. Taya sooner ceuk ti rengse, sanggeus bari urang tadi generator kode anu dihasilkeun kode kalawan ifs (sanajan tanpa loop). Tapi nasib goréng, éta nabrak, masihan pesen yén paréntahna panjangna salah. Leuwih ti éta, instruksi panungtungan di tingkat recursion ieu brcond. Oké, kuring bakal nambahan hiji dipariksa idéntik jeung generasi instruksi ieu saméméh jeung sanggeus panggero recursive na ... teu salah sahijina dieksekusi, tapi sanggeus switch negeskeun maranéhna masih gagal. Tungtungna, sanggeus diajar kode dihasilkeun, abdi sadar yén sanggeus switch, nu pointer kana instruksi ayeuna reloaded tina tumpukan jeung meureun overwritten ku kode JavaScript dihasilkeun. Jeung kitu tétéla. Ngaronjatkeun panyangga ti hiji megabyte ka sapuluh teu ngakibatkeun nanaon, sarta eta janten jelas yén generator kode ngajalankeun di bunderan. Urang kedah pariksa yén urang henteu ngalangkungan wates TB ayeuna, sareng upami urang ngalakukeun, teras ngaluarkeun alamat TB salajengna kalayan tanda minus supados urang tiasa neraskeun palaksanaan. Salaku tambahan, ieu ngarengsekeun masalah "fungsi anu dibangkitkeun kedah dibatalkeun upami potongan bytecode ieu parantos robih?" — ngan fungsi anu pakait sareng blok tarjamahan ieu anu kedah dibatalkeun. Ku jalan kitu, sanajan kuring debugged sagalana di Chromium (saprak kuring make Firefox sarta leuwih gampang pikeun kuring ngagunakeun browser misah pikeun percobaan), Firefox mantuan kuring ngabenerkeun incompatibilities jeung standar asm.js, nu satutasna kode mimiti dianggo gancang dina. Kromium.

Conto kode anu dihasilkeun

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

kacindekan

Janten, padamelan éta henteu acan réngsé, tapi kuring bosen cicingeun ngajantenkeun konstruksi jangka panjang ieu kasampurnaan. Ku alatan éta, kuring mutuskeun pikeun nyebarkeun naon anu kuring gaduh ayeuna. Kodeu rada pikasieuneun di tempat-tempat, sabab ieu mangrupikeun percobaan, sareng éta henteu écés sateuacanna naon anu kedah dilakukeun. Panginten, maka éta patut ngaluarkeun komitmen atom normal di luhur sababaraha versi Qemu anu langkung modern. Samentawis waktos, aya thread dina Gita dina format blog: pikeun tiap "tingkat" nu geus sahenteuna kumaha bae lulus, a commentary lengkep dina Rusia geus ditambahkeun. Sabenerna, artikel ieu sabagéan ageung mangrupikeun nyaritakeun deui kacindekan git log.

Anjeun tiasa nyobian sadayana di dieu (Awas lalulintas).

Naon anu parantos dianggo:

  • x86 processor virtual ngajalankeun
  • Aya prototipe kerja tina generator kode JIT tina kode mesin ka JavaScript
  • Aya témplat pikeun ngarakit arsitéktur tamu 32-bit anu sanés: ayeuna anjeun tiasa muji Linux pikeun katirisan arsitéktur MIPS dina browser dina tahap loading.

Naon deui anu anjeun tiasa laksanakeun

  • Ngagancangkeun émulasi. Malah dina modeu JIT sigana ngajalankeun leuwih laun ti Virtual x86 (tapi aya berpotensi sakabeh Qemu kalawan loba hardware jeung arsitéktur emulated)
  • Pikeun ngadamel antarmuka anu normal - terus terang, kuring sanés pamekar wéb anu saé, janten ayeuna kuring parantos ngadamel cangkang Emscripten standar sabisa-bisa.
  • Coba ngajalankeun fungsi Qemu anu langkung kompleks - jaringan, migrasi VM, jsb.
  • UPS: anjeun kedah ngalebetkeun sababaraha kamajuan sareng laporan bug anjeun ka Emscripten hulu, sapertos portir Qemu sareng proyék-proyék sanésna. Hatur nuhun ka aranjeunna pikeun tiasa sacara implisit ngagunakeun kontribusina ka Emscripten salaku bagian tina tugas kuring.

sumber: www.habr.com

Tambahkeun komentar