Pirms dažiem gadiem Fabriss Belards
- atvÄrtais avots
- spÄja strÄdÄt bez kodola draivera
- spÄja strÄdÄt tulka režīmÄ
- atbalsts lielam skaitam resursdatora un viesu arhitektūru
AttiecÄ«bÄ uz treÅ”o punktu tagad varu paskaidrot, ka patiesÄ«bÄ TCI režīmÄ tiek interpretÄtas nevis paÅ”as viesu maŔīnas instrukcijas, bet gan no tÄm iegÅ«tais baitkods, taÄu tas nemaina bÅ«tÄ«bu - lai izveidotu un palaistu. Qemu uz jaunas arhitektÅ«ras, ja paveicas, pietiek ar C kompilatoru - kodu Ä£eneratora rakstÄ«Å”anu var atlikt.
Un tagad pÄc divu gadu nesteidzÄ«gas ÄaloÅ”anÄs ar Qemu pirmkodu brÄ«vajÄ laikÄ parÄdÄ«jÄs strÄdÄjoÅ”s prototips, kurÄ jau var palaist, piemÄram, Kolibri OS.
Kas ir Emscripten
MÅ«sdienÄs ir parÄdÄ«juÅ”ies daudzi kompilatori, kuru gala rezultÄts ir JavaScript. Daži, piemÄram, Type Script, sÄkotnÄji bija paredzÄti kÄ labÄkais veids, kÄ rakstÄ«t tÄ«meklÄ«. TajÄ paÅ”Ä laikÄ Emscripten ir veids, kÄ izmantot esoÅ”o C vai C++ kodu un apkopot to pÄrlÅ«kprogrammÄ lasÄmÄ formÄ. IeslÄgts
Pirmais mÄÄ£inÄjums
VispÄrÄ«gi runÄjot, es neesmu pirmÄ persona, kas nÄkusi klajÄ ar ideju par Qemu pÄrneÅ”anu uz JavaScript. ReactOS forumÄ tika uzdots jautÄjums, vai tas ir iespÄjams, izmantojot Emscripten. Jau agrÄk klÄ«da baumas, ka Fabriss Belards to izdarÄ«jis personÄ«gi, bet mÄs runÄjÄm par jslinux, kas, cik man zinÄms, ir tikai mÄÄ£inÄjums manuÄli panÄkt pietiekamu veiktspÄju JS, un tika rakstÄ«ts no nulles. VÄlÄk tika uzrakstÄ«ts Virtual x86 - tam tika ievietoti neapslÄpti avoti, un, kÄ teikts, lielÄkais emulÄcijas āreÄlismsā ļÄva izmantot SeaBIOS kÄ programmaparatÅ«ru. TurklÄt bija vismaz viens mÄÄ£inÄjums portÄt Qemu, izmantojot Emscripten - es mÄÄ£inÄju to izdarÄ«t
TÄtad, Ŕķiet, Å”eit ir avoti, Å”eit ir Emscripten - Åemiet to un apkopojiet. Bet ir arÄ« bibliotÄkas, no kurÄm ir atkarÄ«gs Qemu, un bibliotÄkas, no kurÄm ir atkarÄ«gas Ŕīs bibliotÄkas utt., un viena no tÄm ir
SÄkumÄ bija doma vienkÄrÅ”i uzrakstÄ«t libffi nomaiÅu ar JS un palaist standarta testus, bet beigÄs samulsu, kÄ savus header failus uztaisÄ«t tÄ, lai tie strÄdÄtu ar esoÅ”o kodu - ko darÄ«t? kÄ saka: "Vai uzdevumi ir tik sarežģīti "Vai mÄs esam tik stulbi?" Man bija jÄportÄ libffi uz citu arhitektÅ«ru, tÄ teikt - par laimi Emscripten ir gan makro inline montÄžai (Javascript, jÄ - nu, lai kÄda arhitektÅ«ra, tÄtad montÄtÄjs), gan iespÄja palaist lidojumÄ Ä£enerÄtu kodu. KopumÄ, kÄdu laiku ÄakarÄjot ar platformas atkarÄ«giem libffi fragmentiem, es ieguvu kompilÄjamu kodu un palaistÄ«ju to pirmajÄ testÄ, ar kuru saskÄros. Man par pÄrsteigumu tests bija veiksmÄ«gs. ApdullinÄts no sava Ä£enialitÄtes - ne pa jokam, tas nostrÄdÄja jau no pirmÄs palaiÅ”anas - es, joprojÄm neticot savÄm acÄ«m, devos vÄlreiz apskatÄ«t iegÅ«to kodu, lai izvÄrtÄtu, kur tÄlÄk rakt. Å eit es sajuku prÄtÄ otro reizi - vienÄ«gais, ko darÄ«ja mana funkcija, bija ffi_call
- tas ziÅoja par veiksmÄ«gu zvanu. PaÅ”a zvana nebija. TÄpÄc es nosÅ«tÄ«ju savu pirmo vilkÅ”anas pieprasÄ«jumu, kurÄ tika izlabota kļūda testÄ, kas ir skaidra jebkuram olimpiÄdes skolÄnam - reÄlos skaitļus nevajadzÄtu salÄ«dzinÄt kÄ a == b
un pat kÄ a - b < EPS
- jÄatceras arÄ« modulis, pretÄjÄ gadÄ«jumÄ 0 izrÄdÄ«sies ļoti vienÄds ar 1/3... VispÄr es izdomÄju noteiktu libffi portu, kas iztur visvienkÄrÅ”Äkos testus un ar kuru ir glibs sastÄdÄ«ts - nolÄmu, ka vajadzÄs, pievienoÅ”u vÄlÄk. Raugoties uz priekÅ”u, teikÅ”u, ka, kÄ izrÄdÄ«jÄs, kompilators pat neiekļÄva libffi funkciju gala kodÄ.
Bet, kÄ jau teicu, ir daži ierobežojumi, un starp dažÄdu nedefinÄtu uzvedÄ«bu brÄ«vu izmantoÅ”anu ir paslÄpta vÄl nepatÄ«kamÄka iezÄ«me - JavaScript pÄc dizaina neatbalsta daudzpavedienu izmantoÅ”anu ar koplietojamo atmiÅu. PrincipÄ to parasti var pat saukt par labu ideju, bet ne koda pÄrneÅ”anai, kura arhitektÅ«ra ir saistÄ«ta ar C pavedieniem. VispÄrÄ«gi runÄjot, Firefox eksperimentÄ ar koplietojamo darbinieku atbalstu, un Emscripten viÅiem ir pthread ievieÅ”ana, taÄu es negribÄju no tÄ paļauties. Man vajadzÄja lÄnÄm izskaust no Qemu koda multithreading - tas ir, noskaidrot, kur pavedieni darbojas, pÄrvietot Å”ajÄ pavedienÄ esoÅ”Äs cilpas pamattekstu atseviÅ”Ä·Ä funkcijÄ un izsaukt Å”Ädas funkcijas pa vienai no galvenÄs cilpas.
Otrais mÄÄ£inÄjums
KÄdÄ brÄ«dÄ« kļuva skaidrs, ka problÄma joprojÄm pastÄv un ka nejauÅ”a kruÄ·u grÅ«stÄ«Å”ana ap kodu neko labu nenovedÄ«s. SecinÄjums: mums kaut kÄ jÄsistematizÄ kruÄ·u pievienoÅ”anas process. TÄpÄc tika paÅemta 2.4.1 versija, kas tobrÄ«d bija svaiga (nevis 2.5.0, jo, kas zina, jaunajÄ versijÄ bÅ«s bugs, kas vÄl nav noÄ·erts, un man pietiek ar savÄm kļūdÄm ), un pirmÄ lieta bija to droÅ”i pÄrrakstÄ«t thread-posix.c
. Nu, tas ir, kÄ droÅ”i: ja kÄds mÄÄ£inÄja veikt darbÄ«bu, kas noveda pie bloÄ·ÄÅ”anas, funkcija nekavÄjoties tika izsaukta abort()
- tas, protams, neatrisinÄja visas problÄmas uzreiz, bet tas vismaz bija kaut kÄ patÄ«kamÄk nekÄ klusi saÅemt pretrunÄ«gus datus.
KopumÄ Emscripten opcijas ir ļoti noderÄ«gas koda pÄrneÅ”anai uz JS -s ASSERTIONS=1 -s SAFE_HEAP=1
- tie uztver dažus nenoteiktas darbÄ«bas veidus, piemÄram, izsaukumus uz nesaskaÅotu adresi (kas nepavisam neatbilst drukÄto masÄ«vu kodam, piemÄram, HEAP32[addr >> 2] = 1
) vai funkcijas izsaukŔana ar nepareizu argumentu skaitu.
Starp citu, izlÄ«dzinÄÅ”anas kļūdas ir atseviŔķa problÄma. KÄ jau teicu, Qemu ir ādeÄ£enerÄtaā interpretÄcijas aizmugure koda Ä£enerÄÅ”anai TCI (tiny code interpreter), un, lai izveidotu un palaistu Qemu uz jaunas arhitektÅ«ras, ja jums paveicas, pietiek ar C kompilatoru. AtslÄgvÄrdi "ja tev paveicas". Man nepaveicÄs, un izrÄdÄ«jÄs, ka TCI izmanto nesaskaÅotu piekļuvi, parsÄjot savu baitkodu. Tas ir, uz visu veidu ARM un citÄm arhitektÅ«rÄm ar obligÄti izlÄ«dzinÄtu piekļuvi, Qemu kompilÄ, jo tiem ir parasta TCG aizmugure, kas Ä£enerÄ vietÄjo kodu, bet vai TCI darbosies uz tiem, tas ir cits jautÄjums. TomÄr, kÄ izrÄdÄ«jÄs, TCI dokumentÄcija skaidri norÄdÄ«ja uz kaut ko lÄ«dzÄ«gu. RezultÄtÄ kodam tika pievienoti funkciju izsaukumi nesaskaÅotai lasÄ«Å”anai, kas tika atrasti citÄ Qemu daļÄ.
Kaudzes iznÄ«cinÄÅ”ana
RezultÄtÄ tika izlabota nesaskaÅota piekļuve TCI, tika izveidota galvenÄ cilpa, kas savukÄrt sauca procesoru, RCU un dažas citas mazas lietas. Un tÄpÄc es palaidu Qemu ar opciju -d exec,in_asm,out_asm
, kas nozÄ«mÄ, ka jums ir jÄpasaka, kuri koda bloki tiek izpildÄ«ti, kÄ arÄ« apraides laikÄ jÄuzraksta, kÄds bija viesa kods, par ko kļuva resursdatora kods (Å”ajÄ gadÄ«jumÄ baitkods). Tas sÄkas, izpilda vairÄkus tulkoÅ”anas blokus, raksta atkļūdoÅ”anas ziÅojumu, ko atstÄju, ka RCU tagad sÄksies un... avarÄ abort()
funkcijas iekÅ”pusÄ free()
. PielÄgojot funkciju free()
Mums izdevÄs noskaidrot, ka kaudzes bloka galvenÄ, kas atrodas astoÅos baitos pirms pieŔķirtÄs atmiÅas, bloka lieluma vai tamlÄ«dzÄ«ga vietÄ atradÄs atkritumi.
KaudzÄ«tes iznÄ«cinÄÅ”ana - cik mīļi... TÄdÄ gadÄ«jumÄ ir noderÄ«gs lÄ«dzeklis - no (ja iespÄjams) no tiem paÅ”iem avotiem samontÄ native binÄru un palaist zem Valgrind. PÄc kÄda laika binÄrais fails bija gatavs. Es to palaižu ar tÄdÄm paÅ”Äm opcijÄm - tas avarÄ pat inicializÄcijas laikÄ, pirms faktiski sasniedz izpildi. Tas, protams, ir nepatÄ«kami - acÄ«mredzot avoti nebija gluži tie paÅ”i, kas nav pÄrsteidzoÅ”i, jo konfigurÄÅ”ana izpÄtÄ«ja nedaudz atŔķirÄ«gas iespÄjas, bet man ir Valgrind - vispirms izlaboÅ”u Å”o kļūdu un pÄc tam, ja paveiksies , parÄdÄ«sies oriÄ£inÄlais. Es palaižu to paÅ”u zem Valgrind... Y-y-y, y-y-y, uh-uh, tas sÄkÄs, normÄli tika inicializÄts un tika novÄrsta sÄkotnÄjÄ kļūda, bez brÄ«dinÄjuma par nepareizu piekļuvi atmiÅai, nemaz nerunÄjot par kritieniem. DzÄ«ve, kÄ saka, mani tam nesagatavoja - avaroÅ”a programma pÄrstÄj avarÄt, kad tÄ tiek palaista zem Walgrind. Kas tas bija, ir noslÄpums. Mana hipotÄze ir tÄda, ka vienu reizi paÅ”reizÄjÄs instrukcijas tuvumÄ pÄc avÄrijas inicializÄcijas laikÄ gdb parÄdÄ«ja darbu memset
-a ar derÄ«gu rÄdÄ«tÄju, izmantojot vai nu mmx
, vai xmm
reÄ£istros, tad, iespÄjams, tÄ bija sava veida izlÄ«dzinÄÅ”anas kļūda, lai gan joprojÄm ir grÅ«ti noticÄt.
Labi, Valgrinds Å”eit nepalÄ«dz. Un te sÄkÄs pats pretÄ«gÄkais - Ŕķiet, ka viss pat sÄkas, bet pilnÄ«gi nezinÄmu iemeslu dÄļ avarÄ notikums, kas varÄja notikt pirms miljoniem instrukciju. Ilgu laiku pat nebija skaidrs, kÄ pieiet. BeigÄs tomÄr nÄcÄs apsÄsties un atkļūdot. IzdrukÄjot to, ar ko virsraksts tika pÄrrakstÄ«ts, izrÄdÄ«jÄs, ka tas neizskatÄ«jÄs pÄc skaitļa, bet gan pÄc kaut kÄdiem binÄriem datiem. Un, lÅ«k, Ŕī binÄrÄ virkne tika atrasta BIOS failÄ - tas ir, tagad varÄja ar pamatotu pÄrliecÄ«bu teikt, ka tÄ ir bufera pÄrpilde, un ir pat skaidrs, ka tÄ tika ierakstÄ«ta Å”ajÄ buferÄ«. Nu, tad kaut kas lÄ«dzÄ«gs Å”im - Emscripten, par laimi, nav adreÅ”u telpas randomizÄcijas, tajÄ arÄ« nav nekÄdu caurumu, tÄpÄc var ierakstÄ«t kaut kur koda vidÅ«, lai izvadÄ«tu datus pÄc rÄdÄ«tÄja no pÄdÄjÄ palaiÅ”anas, apskatiet datus, skatiet rÄdÄ«tÄju un, ja tas nav mainÄ«jies, iegÅ«stiet vielu pÄrdomÄm. Tiesa, saiÅ”u izveide pÄc jebkÄdÄm izmaiÅÄm aizÅem pÄris minÅ«tes, bet ko jÅ«s varat darÄ«t? RezultÄtÄ tika atrasta Ä«paÅ”a rinda, kas kopÄja BIOS no pagaidu bufera uz viesa atmiÅu - un, patieÅ”Äm, buferÄ« nebija pietiekami daudz vietas. Atrodot Ŕīs dÄ«vainÄs bufera adreses avotu, tika izveidota funkcija qemu_anon_ram_alloc
failÄ oslib-posix.c
- loÄ£ika bija Å”Äda: dažreiz var bÅ«t noderÄ«gi pielÄgot adresi milzÄ«gai 2 MB lielai lapai, tÄpÄc mÄs jautÄsim mmap
vispirms nedaudz vairÄk, un tad mÄs ar palÄ«dzÄ«bu atgriezÄ«sim pÄrpalikumu munmap
. Un, ja Å”Äda izlÄ«dzinÄÅ”ana nav nepiecieÅ”ama, mÄs norÄdÄ«sim rezultÄtu, nevis 2 MB getpagesize()
SÄkot no mmap
tas joprojÄm izdos saskaÅotu adresi... TÄtad Emscripten mmap
tikai zvani malloc
, bet, protams, tas nesakrÄ«t lapÄ. KopumÄ kļūda, kas mani sarÅ«gtinÄja pÄris mÄneÅ”us, tika izlabota, veicot izmaiÅas divi lÄ«nijas.
ZvanīŔanas funkciju iezīmes
Un tagad procesors kaut ko skaita, Qemu neavar, bet ekrÄns neieslÄdzas, un procesors Ätri nonÄk cilpÄs, spriežot pÄc izejas -d exec,in_asm,out_asm
. Ir parÄdÄ«jusies hipotÄze: taimera pÄrtraukumi (vai vispÄr visi pÄrtraukumi) neienÄk. Un patieÅ”Äm, ja atskrÅ«vÄjat pÄrtraukumus no vietÄjÄs montÄžas, kas kaut kÄdu iemeslu dÄļ darbojÄs, jÅ«s iegÅ«stat lÄ«dzÄ«gu attÄlu. Bet tÄ nepavisam nebija atbilde: salÄ«dzinot izsekoÅ”anas pÄdas ar iepriekÅ” minÄto iespÄju, tika parÄdÄ«ts, ka izpildes trajektorijas atŔķīrÄs ļoti agri. Å eit jÄsaka, ka salÄ«dzinÄjums ar to, kas tika ierakstÄ«ts, izmantojot palaiÅ”anas programmu emrun
izvades atkļūdoÅ”ana ar sÄkotnÄjÄs montÄžas izvadi nav pilnÄ«bÄ mehÄnisks process. Es precÄ«zi nezinu, kÄ programma, kas darbojas pÄrlÅ«kprogrammÄ, izveido savienojumu ar emrun
, bet dažas lÄ«nijas izvadÄ izrÄdÄs pÄrkÄrtotas, tÄpÄc atŔķirÄ«bas atŔķirÄ«ba vÄl nav iemesls uzskatÄ«t, ka trajektorijas ir novirzÄ«juÅ”Äs. KopumÄ kļuva skaidrs, ka saskaÅÄ ar instrukcijÄm ljmpl
notiek pÄreja uz dažÄdÄm adresÄm, un Ä£enerÄtais baitkods bÅ«tiski atŔķiras: vienÄ ir instrukcija izsaukt palÄ«gfunkciju, otrÄ nav. PÄc instrukciju googlÄÅ”anas un koda izpÄtÄ«Å”anas, kas tulko Ŕīs instrukcijas, kļuva skaidrs, ka, pirmkÄrt, tieÅ”i pirms tÄs reÄ£istrÄ cr0
tika veikts ieraksts - arÄ« izmantojot palÄ«gu - kas pÄrslÄdza procesoru uz aizsargÄto režīmu, otrkÄrt, ka js versija nekad nepÄrslÄdzÄs uz aizsargÄto režīmu. Bet fakts ir tÄds, ka vÄl viena Emscripten iezÄ«me ir tÄ nevÄlÄÅ”anÄs paciest kodu, piemÄram, instrukciju ievieÅ”anu. call
TCI, kurÄ jebkuras funkcijas rÄdÄ«tÄjs rada veidu long long f(int arg0, .. int arg9)
- funkcijas ir jÄizsauc ar pareizo argumentu skaitu. Ja Å”is noteikums tiek pÄrkÄpts, atkarÄ«bÄ no atkļūdoÅ”anas iestatÄ«jumiem programma vai nu avarÄs (kas ir labi), vai vispÄr izsauks nepareizo funkciju (kuru bÅ«s bÄdÄ«gi atkļūdot). Ir arÄ« treÅ”Ä iespÄja - iespÄjot iesaiÅotÄju Ä£enerÄÅ”anu, kas pievieno/noÅem argumentus, bet kopumÄ Å”ie iesaiÅotÄji aizÅem daudz vietas, neskatoties uz to, ka patiesÄ«bÄ man vajag tikai nedaudz vairÄk par simts iesaiÅojumiem. Tas vien ir ļoti skumji, bet izrÄdÄ«jÄs nopietnÄka problÄma: Ä£enerÄtajÄ iesaiÅojuma funkciju kodÄ tika konvertÄti un pÄrveidoti argumenti, bet dažreiz funkcija ar Ä£enerÄtajiem argumentiem netika izsaukta - nu, tÄpat kÄ mana libffi ievieÅ”ana. Tas ir, daži palÄ«gi vienkÄrÅ”i netika izpildÄ«ti.
Par laimi, Qemu ir maŔīnlasÄmi palÄ«gu saraksti galvenes faila veidÄ, piemÄram
DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)
Tie tiek izmantoti diezgan smieklÄ«gi: pirmkÄrt, makro tiek definÄti no jauna visdÄ«vainÄkajÄ veidÄ DEF_HELPER_n
, un pÄc tam ieslÄdzas helper.h
. CiktÄl makro tiek izvÄrsts struktÅ«ras inicializatorÄ un komatÄ, un pÄc tam tiek definÄts masÄ«vs un elementu vietÄ - #include <helper.h>
RezultÄtÄ man beidzot bija iespÄja darbÄ izmÄÄ£inÄt bibliotÄku
Un tÄ, pÄc tam Ŕķita, ka procesors strÄdÄja. Å Ä·iet, ka tas ir tÄpÄc, ka ekrÄns nekad netika inicializÄts, lai gan memtest86+ varÄja darboties sÄkotnÄjÄ komplektÄcijÄ. Å eit jÄprecizÄ, ka Qemu bloka I/O kods ir ierakstÄ«ts korutÄ«nÄs. Emscripten ir sava ļoti sarežģīta ievieÅ”ana, taÄu tÄ joprojÄm bija jÄatbalsta Qemu kodÄ, un tagad varat atkļūdot procesoru: Qemu atbalsta opcijas -kernel
, -initrd
, -append
, ar kuru var palaist Linux vai, piemÄram, memtest86+, vispÄr neizmantojot blokierÄ«ces. Bet Å”eit ir problÄma: vietÄjÄ komplektÄcijÄ var redzÄt Linux kodola izvadi konsolei ar opciju -nographic
, un nav izvades no pÄrlÅ«kprogrammas uz terminÄli, no kurienes tas tika palaists emrun
, nenÄca. Tas ir, nav skaidrs: procesors nedarbojas vai grafikas izvade nedarbojas. Un tad man ienÄca prÄtÄ nedaudz pagaidÄ«t. IzrÄdÄ«jÄs, ka āprocesors neguļ, bet vienkÄrÅ”i lÄni mirgoā, un pÄc apmÄram piecÄm minÅ«tÄm kodols iemeta konsolei virkni ziÅojumu un turpinÄja karÄties. Kļuva skaidrs, ka procesors kopumÄ darbojas, un mums ir jÄiedziļinÄs kodÄ darbam ar SDL2. DiemžÄl es nezinu, kÄ izmantot Å”o bibliotÄku, tÄpÄc dažÄs vietÄs man bija jÄrÄ«kojas nejauÅ”i. KÄdÄ brÄ«dÄ« ekrÄnÄ uz zila fona mirgoja paralÄla 0 lÄ«nija, kas lika domÄt par dažÄm pÄrdomÄm. Galu galÄ izrÄdÄ«jÄs, ka problÄma bija tÄ, ka Qemu vienÄ fiziskajÄ logÄ atver vairÄkus virtuÄlos logus, starp kuriem var pÄrslÄgties, izmantojot Ctrl-Alt-n: tas darbojas vietÄjÄ bÅ«vniecÄ«bÄ, bet ne Emscripten. PÄc atbrÄ«voÅ”anÄs no nevajadzÄ«giem logiem, izmantojot opcijas -monitor none -parallel none -serial none
un norÄdÄ«jumi, lai piespiedu kÄrtÄ pÄrzÄ«mÄtu visu ekrÄnu katrÄ kadrÄ, viss pÄkÅ”Åi nostrÄdÄja.
Korutīnas
TÄtad emulÄcija pÄrlÅ«kprogrammÄ darbojas, taÄu tajÄ nevar palaist neko interesantu vienÄ disketÄ, jo nav bloka I/O - jums ir jÄievieÅ” korutÄ«nu atbalsts. Qemu jau ir vairÄkas korutÄ«nas aizmugursistÄmas, taÄu JavaScript un Emscripten koda Ä£eneratora bÅ«tÄ«bas dÄļ jÅ«s nevarat vienkÄrÅ”i sÄkt žonglÄt ar kaudzÄm. Å Ä·iet, ka "viss ir pagÄjis, apmetums tiek noÅemts", bet Emscripten izstrÄdÄtÄji jau ir visu parÅ«pÄjuÅ”ies. Tas ir ieviests diezgan smieklÄ«gi: sauksim Å”Ädu funkcijas izsaukumu par aizdomÄ«gu emscripten_sleep
un vairÄkas citas, kas izmanto Asyncify mehÄnismu, kÄ arÄ« rÄdÄ«tÄja izsaukumus un izsaukumus uz jebkuru funkciju, ja viens no diviem iepriekÅ”Äjiem gadÄ«jumiem var notikt tÄlÄk steka. Un tagad pirms katra aizdomÄ«ga zvana atlasÄ«sim asinhrono kontekstu un uzreiz pÄc zvana pÄrbaudÄ«sim, vai nav noticis asinhronais zvans, un ja ir, tad Å”ajÄ asinhronajÄ kontekstÄ saglabÄsim visus lokÄlos mainÄ«gos, norÄdÄ«sim, kura funkcija lai pÄrsÅ«tÄ«tu kontroli uz laiku, kad jÄturpina izpilde, un izietu no paÅ”reizÄjÄs funkcijas. Å eit ir iespÄja izpÄtÄ«t efektu -O3
. Es palaižu Ä£enerÄto kodu, un Chromium patÄrÄ atmiÅu un avarÄ. Es tad nejauÅ”i paskatÄ«jos, ko viÅÅ” mÄÄ£ina lejupielÄdÄt... Nu, ko lai saka, es arÄ« bÅ«tu sastingusi, ja man bÅ«tu palÅ«gts pÄrdomÄti izpÄtÄ«t un optimizÄt 500+ MB Javascript.
DiemžÄl pÄrbaudes Asyncify atbalsta bibliotÄkas kodÄ nebija gluži draudzÄ«gas longjmp
-s, kas tiek izmantoti virtuÄlÄ procesora kodÄ, bet pÄc neliela ielÄpa, kas atspÄjo Ŕīs pÄrbaudes un piespiedu kÄrtÄ atjauno kontekstus, it kÄ viss bÅ«tu kÄrtÄ«bÄ, kods darbojÄs. Un tad sÄkÄs dÄ«vaina lieta: dažkÄrt tika iedarbinÄtas sinhronizÄcijas koda pÄrbaudes - tÄs paÅ”as, kas kodu sabojÄ, ja pÄc izpildes loÄ£ikas tas bÅ«tu jÄbloÄ·Ä - kÄds mÄÄ£inÄja sagrÄbt jau notvertu mutex. Par laimi, izrÄdÄ«jÄs, ka seriÄlÄ kodÄ tÄ nav loÄ£iska problÄma - es vienkÄrÅ”i izmantoju Emscripten nodroÅ”inÄto standarta galvenÄs cilpas funkcionalitÄti, bet dažreiz asinhronais izsaukums pilnÄ«bÄ atŔķetinÄja steku, un tajÄ brÄ«dÄ« tas neizdodas. setTimeout
no galvenÄs cilpas - tÄdÄjÄdi kods ievadÄ«ja galvenÄs cilpas iterÄciju, neatstÄjot iepriekÅ”Äjo iterÄciju. PÄrrakstÄ«ja uz bezgalÄ«gas cilpas un emscripten_sleep
, un problÄmas ar muteksiem apstÄjÄs. Kods ir kļuvis pat loÄ£iskÄks - galu galÄ man nav koda, kas sagatavotu nÄkamo animÄcijas kadru - procesors vienkÄrÅ”i kaut ko aprÄÄ·ina, un ekrÄns tiek periodiski atjauninÄts. TomÄr problÄmas ar to neapstÄjÄs: dažreiz Qemu izpilde vienkÄrÅ”i klusi tika pÄrtraukta bez izÅÄmumiem vai kļūdÄm. TajÄ brÄ«dÄ« es no tÄ atteicos, bet, skatoties uz priekÅ”u, teikÅ”u, ka problÄma bija Å”Äda: korutÄ«nas kods faktiski neizmanto setTimeout
(vai vismaz ne tik bieži, kÄ jÅ«s varÄtu domÄt): funkcija emscripten_yield
vienkÄrÅ”i iestata asinhronÄ zvana karogu. Visa bÅ«tÄ«ba ir tÄda emscripten_coroutine_next
nav asinhrona funkcija: iekÅ”Äji tÄ pÄrbauda karogu, atiestata to un nodod kontroli tur, kur tas ir nepiecieÅ”ams. Tas ir, steka veicinÄÅ”ana ar to beidzas. ProblÄma bija tÄda, ka sakarÄ ar lietoÅ”anas pÄc-free, kas parÄdÄ«jÄs, kad korutÄ«nas pÅ«ls tika atspÄjots, jo es nenokopÄju svarÄ«gu koda rindiÅu no esoÅ”Äs korutÄ«nas aizmugursistÄmas, funkcija qemu_in_coroutine
atgriezta patiesa, lai gan patiesÄ«bÄ tai vajadzÄja atgriezt nepatiesu. Tas noveda pie zvana emscripten_yield
, virs kura uz kaudzes neviena nebija emscripten_coroutine_next
, kaudze atlocÄ«jÄs lÄ«dz paÅ”ai augÅ”ai, bet nÄ setTimeout
, kÄ jau teicu, netika izstÄdÄ«ts.
JavaScript koda Ä£enerÄÅ”ana
Un patiesÄ«bÄ Å”eit ir apsolÄ«tais āmaltÄs gaļas pagrieÅ”ana atpakaļā. Ne Ä«sti. Protams, ja pÄrlÅ«kprogrammÄ palaižam Qemu un tajÄ Node.js, tad, protams, pÄc koda Ä£enerÄÅ”anas Qemu mÄs saÅemsim pilnÄ«gi nepareizu JavaScript. Bet tomÄr kaut kÄda reversa transformÄcija.
PirmkÄrt, nedaudz par to, kÄ darbojas Qemu. LÅ«dzu, piedodiet man uzreiz: es neesmu profesionÄls Qemu izstrÄdÄtÄjs, un mani secinÄjumi dažviet var bÅ«t kļūdaini. KÄ viÅi saka, "skolÄna viedoklim nav jÄsakrÄ«t ar skolotÄja viedokli, Peano aksiomÄtiku un veselo saprÄtu." Qemu ir noteikts skaits atbalstÄ«to viesu arhitektÅ«ru, un katrai ir savs direktorijs target-i386
. Veidojot, varat norÄdÄ«t atbalstu vairÄkÄm viesu arhitektÅ«rÄm, taÄu rezultÄts bÅ«s tikai vairÄki binÄrie faili. Viesu arhitektÅ«ras atbalsta kods savukÄrt Ä£enerÄ dažas iekÅ”ÄjÄs Qemu darbÄ«bas, kuras TCG (Tiny Code Generator) jau pÄrvÄrÅ” maŔīnkodÄ resursdatora arhitektÅ«rai. KÄ norÄdÄ«ts failÄ readme, kas atrodas tcg direktorijÄ, tas sÄkotnÄji bija daļa no parastÄ C kompilatora, kas vÄlÄk tika pielÄgots JIT. TÄpÄc, piemÄram, mÄrÄ·a arhitektÅ«ra Ŕī dokumenta izteiksmÄ vairs nav viesu arhitektÅ«ra, bet gan resursdatora arhitektÅ«ra. KÄdÄ brÄ«dÄ« parÄdÄ«jÄs vÄl viens komponents - Tiny Code Interpreter (TCI), kuram vajadzÄtu izpildÄ«t kodu (gandrÄ«z tÄs paÅ”as iekÅ”ÄjÄs darbÄ«bas), ja nav koda Ä£eneratora konkrÄtai resursdatora arhitektÅ«rai. Faktiski, kÄ norÄdÄ«ts dokumentÄcijÄ, Å”is tulks ne vienmÄr var darboties tik labi kÄ JIT koda Ä£enerators ne tikai kvantitatÄ«vi Ätruma, bet arÄ« kvalitatÄ«vi. Lai gan es neesmu pÄrliecinÄts, ka viÅa apraksts ir pilnÄ«bÄ atbilstoÅ”s.
SÄkumÄ mÄÄ£inÄju izveidot pilnvÄrtÄ«gu TCG aizmuguri, taÄu Ätri apmulsu avota kodÄ un ne visai skaidrÄ baitkoda instrukciju aprakstÄ, tÄpÄc nolÄmu ietÄ«t TCI tulku. Tas deva vairÄkas priekÅ”rocÄ«bas:
- ievieÅ”ot koda Ä£eneratoru, varÄja skatÄ«ties nevis instrukciju aprakstu, bet gan tulka kodu
- funkcijas var Ä£enerÄt ne katram sastaptajam tulkoÅ”anas blokam, bet, piemÄram, tikai pÄc simtÄs izpildes
- ja Ä£enerÄtais kods mainÄ«sies (un tas Ŕķiet iespÄjams, spriežot pÄc funkcijÄm ar nosaukumiem, kas satur vÄrdu patch), man vajadzÄs anulÄt Ä£enerÄto JS kodu, bet vismaz man bÅ«s no kÄ to atjaunot
AttiecÄ«bÄ uz treÅ”o punktu es neesmu pÄrliecinÄts, ka ielÄpÄÅ”ana ir iespÄjama pÄc koda pirmÄs izpildes, taÄu pietiek ar pirmajiem diviem punktiem.
SÄkotnÄji kods tika Ä£enerÄts liela slÄdža veidÄ oriÄ£inÄlÄs baitkoda instrukcijas adresÄ, bet pÄc tam, atceroties rakstu par Emscripten, Ä£enerÄtÄ JS optimizÄciju un relooping, es nolÄmu Ä£enerÄt vairÄk cilvÄka koda, jo Ä«paÅ”i tÄpÄc, ka empÄ«riski tÄ izrÄdÄ«jÄs, ka vienÄ«gais ieejas punkts tulkoÅ”anas blokÄ ir tÄ sÄkums. PÄc kÄda laika mums bija kodu Ä£enerators, kas Ä£enerÄja kodu ar ifs (kaut arÄ« bez cilpÄm). Bet nepaveicÄs, tas avarÄja, dodot ziÅu, ka instrukcijas bija nepareiza garuma. TurklÄt pÄdÄjÄ instrukcija Å”ajÄ rekursijas lÄ«menÄ« bija brcond
. Labi, es pievienoÅ”u identisku pÄrbaudi Ŕīs instrukcijas Ä£enerÄÅ”anai pirms un pÄc rekursÄ«vÄ izsaukuma, un... neviens no tiem netika izpildÄ«ts, taÄu pÄc pÄrslÄgÅ”anas Assert tie joprojÄm neizdevÄs. BeigÄs, izpÄtot Ä£enerÄto kodu, sapratu, ka pÄc pÄrslÄgÅ”anas rÄdÄ«tÄjs uz paÅ”reizÄjo instrukciju tiek pÄrlÄdÄts no steka un, iespÄjams, tiek pÄrrakstÄ«ts ar Ä£enerÄto JavaScript kodu. Un tÄ arÄ« izrÄdÄ«jÄs. Bufera palielinÄÅ”ana no viena megabaita lÄ«dz desmit ne pie kÄ neizraisÄ«ja, un kļuva skaidrs, ka kodu Ä£enerators darbojas riÅÄ·Ä«. NÄcÄs pÄrbaudÄ«t, vai neesam izgÄjuÅ”i Ärpus esoÅ”Äs TB robežÄm, un ja tikÄm, tad izdot nÄkamÄs TB adresi ar mÄ«nusa zÄ«mi, lai varÄtu turpinÄt izpildi. TurklÄt tas atrisina problÄmu "kuras Ä£enerÄtÄs funkcijas bÅ«tu jÄanulÄ, ja Å”is baitkoda gabals ir mainÄ«jies?" ā ir jÄanulÄ tikai funkcija, kas atbilst Å”im tulkoÅ”anas blokam. Starp citu, lai gan es visu atkļūdoju pÄrlÅ«kÄ Chromium (tÄ kÄ es izmantoju Firefox un man ir vieglÄk eksperimentiem izmantot atseviŔķu pÄrlÅ«kprogrammu), Firefox man palÄ«dzÄja izlabot nesaderÄ«bu ar standartu asm.js, pÄc tam kods sÄka darboties ÄtrÄk Chromium.
Ä¢enerÄtÄ koda piemÄrs
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"]
SecinÄjums
TÄtad, darbs joprojÄm nav pabeigts, bet man ir apnicis slepeni pilnveidot Å”o ilgtermiÅa bÅ«vniecÄ«bu. TÄpÄc nolÄmu publicÄt to, kas man Å”obrÄ«d ir. Kods vietÄm ir nedaudz biedÄjoÅ”s, jo tas ir eksperiments, un iepriekÅ” nav skaidrs, kas jÄdara. IespÄjams, tad ir vÄrts izdot parastÄs atomu saistÄ«bas papildus kÄdai modernÄkai Qemu versijai. TikmÄr GitÄ ir pavediens bloga formÄtÄ: katram vismaz kaut kÄ nokÄrtotam ālÄ«menimā pievienots detalizÄts komentÄrs krievu valodÄ. PatiesÄ«bÄ Å”is raksts lielÄ mÄrÄ ir secinÄjuma pÄrstÄsts git log
.
JÅ«s varat to visu izmÄÄ£inÄt
Kas jau darbojas:
- darbojas x86 virtuÄlais procesors
- Ir pieejams JIT koda ģeneratora prototips no maŔīnas koda uz JavaScript
- Ir veidne citu 32 bitu viesu arhitektÅ«ru montÄžai: Å”obrÄ«d varat apbrÄ«not Linux, jo MIPS arhitektÅ«ra iesaldÄ pÄrlÅ«kprogrammÄ ielÄdes stadijÄ.
Ko vÄl var darÄ«t
- PaÄtrinÄt emulÄciju. Pat JIT režīmÄ Å”Ä·iet, ka tas darbojas lÄnÄk nekÄ Virtual x86 (bet, iespÄjams, ir vesels Qemu ar daudz emulÄtas aparatÅ«ras un arhitektÅ«ras)
- Lai izveidotu normÄlu interfeisu - atklÄti sakot, es neesmu labs tÄ«mekļa izstrÄdÄtÄjs, tÄpÄc Å”obrÄ«d esmu pÄrtaisÄ«jis standarta Emscripten apvalku, cik vien labi varu.
- MÄÄ£iniet palaist sarežģītÄkas Qemu funkcijas - tÄ«klu veidoÅ”anu, VM migrÄciju utt.
- UPD: jums bÅ«s jÄiesniedz savi daži uzlabojumi un kļūdu ziÅojumi Emscripten augÅ”pus, kÄ to darÄ«ja iepriekÅ”Äjie Qemu un citu projektu nesÄji. Paldies viÅiem par iespÄju netieÅ”i izmantot savu ieguldÄ«jumu Emscripten kÄ daļu no mana uzdevuma.
Avots: www.habr.com