Qemu.js mei JIT-stipe: jo kinne de mince noch efterút draaie

In pear jier lyn Fabrice Bellard skreaun troch jslinux is in PC emulator skreaun yn JavaScript. Dêrnei wie der teminsten mear Firtuele x86. Mar allegearre, sa fier as ik wit, wiene tolken, wylst Qemu, folle earder skreaun troch deselde Fabrice Bellard, en, wierskynlik, elke sels respektearjende moderne emulator, JIT-kompilaasje fan gastkoade brûkt yn hostsysteemkoade. It like my ta dat it tiid wie om de tsjinoerstelde taak út te fieren yn relaasje ta dejinge dy't browsers oplosse: JIT-kompilaasje fan masinekoade yn JavaSkript, wêrfoar it it meast logysk liket om Qemu te porten. It soe lykje, wêrom Qemu, d'r binne ienfâldiger en brûkerfreonlike emulators - deselde VirtualBox, bygelyks - ynstalleare en wurket. Mar Qemu hat ferskate nijsgjirrige funksjes

  • iepen Boarne
  • mooglikheid om te wurkjen sûnder in kernel driver
  • mooglikheid om te wurkjen yn tolkmodus
  • stipe foar in grut oantal host- en gastarsjitektueren

Wat it tredde punt oanbelanget, kin ik no útlizze dat yn feite, yn TCI-modus, it net de ynstruksjes fan 'e gastmasine sels binne dy't ynterpretearre wurde, mar de bytekoade dy't fan har krigen wurdt, mar dit feroaret de essinsje net - om te bouwen en te rinnen Qemu op in nije arsjitektuer, as jo gelok binne, is A C-kompiler genôch - it skriuwen fan in koadegenerator kin wurde útsteld.

En no, nei twa jier fan rêstich tinken mei de Qemu-boarnekoade yn myn frije tiid, ferskynde in wurkjend prototype, wêryn jo bygelyks Kolibri OS al kinne útfiere.

Wat is Emscripten

Tsjintwurdich binne in protte gearstallers ferskynd, wêrfan it einresultaat JavaScript is. Guon, lykas Type Script, wiene oarspronklik bedoeld om de bêste manier te wêzen om te skriuwen foar it web. Tagelyk is Emscripten in manier om besteande C- of C++-koade te nimmen en te kompilearjen yn in browser-lêsbere foarm. Op dizze side Wy hawwe in protte havens fan bekende programma's sammele: hjirJo kinne bygelyks sjen nei PyPy - trouwens, se beweare dat se al JIT hawwe. Yn feite, net elk programma kin gewoan wurde kompilearre en rinne yn in browser - der binne in oantal funksjes, dêr't jo lykwols mei ophâlde moatte, om't de ynskripsje op deselde side seit "Emscripten kin brûkt wurde om hast elke kompilaasje draachber C/C++ koade nei JavaScript". Dat wol sizze, der binne in oantal operaasjes dy't ûndefinieare gedrach binne neffens de standert, mar meastal wurkje op x86 - bygelyks unaligne tagong ta fariabelen, dy't oer it algemien ferbean is op guon arsjitektueren. Yn it algemien , Qemu is in cross-platform programma en , woe ik leauwe, en it befettet net al in protte ûndefinieare gedrach - nim it en kompilearje, tink dan in bytsje mei JIT - en jo binne klear! Mar dat is net de rjochtsaak...

Earst besykje

Yn 't algemien bin ik net de earste dy't mei it idee komt om Qemu nei JavaScript te portearjen. D'r waard in fraach steld op it ReactOS-forum as dit mooglik wie mei Emscripten. Noch earder wiene d'r geroften dat Fabrice Bellard dit persoanlik die, mar wy hiene it oer jslinux, dy't, foar safier't ik wit, gewoan in besykjen is om handich genôch prestaasjes yn JS te berikken, en fanôf it begjin skreaun is. Letter waard Virtual x86 skreaun - unobfuscated boarnen waarden pleatst foar it, en, lykas sein, it grutter "realisme" fan de emulaasje makke it mooglik om te brûken SeaBIOS as firmware. Derneist wie d'r op syn minst ien besykjen om Qemu te portearjen mei Emscripten - ik besocht dit te dwaan socketpair, mar ûntwikkeling, sa fier as ik begryp, wie beferzen.

Dat, it soe lykje, hjir binne de boarnen, hjir is Emscripten - nim it en kompilearje. Mar d'r binne ek biblioteken wêrfan Qemu ôfhinklik is, en biblioteken wêrfan dy bibleteken ôfhinklik binne, ensfh., En ien fan har is libffi, hokker glib hinget ôf fan. D'r wiene geroften op it ynternet dat d'r ien wie yn 'e grutte kolleksje havens fan bibleteken foar Emscripten, mar it wie op ien of oare manier lestich te leauwen: as earste wie it net de bedoeling om in nije gearstaller te wêzen, twad wie it in te leech nivo in bibleteek om gewoan op te heljen, en kompilearje nei JS. En it is net allinich in kwestje fan assemblage-ynfoegingen - wierskynlik, as jo it draaie, kinne jo foar guon opropkonvinsjes de nedige arguminten op 'e stapel generearje en de funksje sûnder har oproppe. Mar Emscripten is in lestich ding: om de generearre koade fertroud te meitsjen foar de browser JS-motoroptimizer, wurde guon trúkjes brûkt. Benammen de saneamde relooping - in koade generator mei help fan de ûntfongen LLVM IR mei wat abstrakte oergong ynstruksjes besiket te meitsjen plausibele ifs, loops, etc. No, hoe wurde de arguminten trochjûn oan 'e funksje? Fansels, as arguminten foar JS-funksjes, dat is, as it mooglik is, net troch de stapel.

Oan it begjin wie d'r in idee om gewoan in ferfanging te skriuwen foar libffi mei JS en standerttests út te fieren, mar op it lêst rekke ik yn 'e war oer hoe't ik myn koptekstbestannen meitsje sadat se wurkje mei de besteande koade - wat kin ik dwaan, sa't se sizze, "Binne de taken sa kompleks "Binne wy ​​sa dom?" Ik moast libffi nei in oare arsjitektuer portearje, sa te sizzen - gelokkich, Emscripten hat beide makro's foar inline-assemblage (yn Javascript, ja - goed, wat de arsjitektuer ek is, dus de assembler), en de mooglikheid om koade te generearjen op 'e flecht. Yn 't algemien, nei't ik in skoft mei platfoarm-ôfhinklike libffi-fragminten tinkt, krige ik wat kompileare koade en rûn it op' e earste test dy't ik tsjinkaam. Ta myn ferrassing wie de test suksesfol. Ferbjustere troch myn sjeny - gjin grap, it wurke fan 'e earste lansearring - ik, noch myn eagen net leauwde, gong ik wer nei de resultearjende koade te sjen, om te evaluearjen wêr't ik de folgjende grave moast. Hjir waard ik foar de twadde kear gek - it ienige wat myn funksje die wie ffi_call - dit rapportearre in suksesfolle oprop. Der wie gjin oprop sels. Dat ik stjoerde myn earste pull-fersyk, dy't in flater yn 'e test korrizjearre dy't dúdlik is foar elke Olympiade-studint - echte sifers moatte net wurde fergelike as a == b en sels hoe a - b < EPS - jo moatte ek de module ûnthâlde, oars sil 0 heul gelyk wurde oan 1/3 ... Yn 't algemien kaam ik mei in bepaalde haven fan libffi, dy't de ienfâldichste tests trochmakket, en wêrmei glib is gearstald - ik besleat dat it nedich wêze soe, ik sil it letter tafoegje. Foarútsjen sil ik sizze dat, sa die bliken, de kompilator de libffi-funksje net iens yn 'e definitive koade omfette.

Mar, lykas ik al sei, binne d'r wat beheiningen, en ûnder it fergese gebrûk fan ferskate ûndefinieare gedrach is in ûnaangenamere funksje ferburgen - JavaScript troch ûntwerp stipet gjin multithreading mei dield ûnthâld. Yn prinsipe kin dit meastentiids sels in goed idee neamd wurde, mar net foar it portearjen fan koade wêrfan de arsjitektuer ferbûn is oan C-threads. Oer it algemien eksperimintearret Firefox mei it stypjen fan dielde arbeiders, en Emscripten hat in pthread-ymplemintaasje foar har, mar ik woe der net fan ôfhingje. Ik moast stadichoan woartele multithreading út de Qemu koade - dat is, útfine wêr't de triedden rinne, ferpleatse it lichem fan 'e lus dy't rint yn dizze tried yn in aparte funksje, en neame sokke funksjes ien foar ien út de wichtichste lus.

Twadde besykje

Op in stuit waard dúdlik dat it probleem der noch wie, en dat it samar krukken om de koade skodzje soe net ta goeds komme. Konklúzje: wy moatte op ien of oare manier it proses fan it tafoegjen fan krukken systematisearje. Dêrom waard ferzje 2.4.1, dy't op dat stuit farsk wie, nommen (net 2.5.0, om't, jo witte noait, d'r sille bugs yn 'e nije ferzje wêze dy't noch net binne fongen, en ik haw genôch fan myn eigen bugs), en it earste wat ik dien wie it feilich oerskriuwe thread-posix.c. No, dat is, sa feilich: as immen besocht in operaasje út te fieren dy't liedt ta blokkearjen, waard de funksje fuortendaliks oproppen abort() - fansels, dit hat net alle problemen yn ien kear oplost, mar it wie teminsten op ien of oare manier nofliker as rêstich ynkonsistinte gegevens te ûntfangen.

Yn 't algemien binne Emscripten-opsjes heul nuttich by it portearjen fan koade nei JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - se fange guon soarten net-definieare gedrach, lykas oproppen nei in net-ôfstimd adres (wat hielendal net oerienkomt mei de koade foar typte arrays lykas HEAP32[addr >> 2] = 1) of in funksje oproppe mei it ferkearde oantal arguminten.

Trouwens, ôfstimmingsflaters binne in apart probleem. Lykas ik al sei, hat Qemu in "degenerearre" ynterpretative backend foar koadegeneraasje TCI (lytse koade-interpreter), en om Qemu op in nije arsjitektuer te bouwen en út te fieren, as jo gelok binne, is in C-kompiler genôch. "as jo gelok hawwe". Ik hie pech, en it die bliken dat TCI brûkt unaligned tagong doe't parsing syn bytecode. Dat is, op alle soarten fan ARM en oare arsjitektuer mei needsaaklikerwize nivellered tagong, Qemu compiles omdat se hawwe in normale TCG backend dat generearret native koade, mar oft TCI sil wurkje op harren is in oare fraach. Lykwols, sa die bliken, de TCI dokumintaasje dúdlik oanjûn wat ferlykber. As gefolch, funksje oproppen foar unaligned lêzen waarden tafoege oan de koade, dy't waarden ûntdutsen yn in oar part fan Qemu.

Heap ferneatiging

As resultaat waard unaligned tagong ta TCI korrizjearre, in haadloop waard makke dy't op syn beurt de prosessor, RCU en guon oare lytse dingen neamde. En sa starte ik Qemu mei de opsje -d exec,in_asm,out_asm, wat betsjut dat jo moatte sizze hokker blokken fan koade wurde útfierd, en ek op 'e tiid fan útstjoering om te skriuwen wat gastkoade wie, hokker hostkoade waard (yn dit gefal, bytecode). It begjint, fiert ferskate oersettingsblokken út, skriuwt it debuggen berjocht dat ik liet dat RCU no sil begjinne en ... crashes abort() binnen in funksje free(). Troch te tinken mei de funksje free() Wy slagge om út te finen dat yn 'e kop fan' e heapblok, dy't leit yn 'e acht bytes foarôfgeand oan it tawiisde ûnthâld, yn stee fan' e blokgrutte of sokssawat, wie jiskefet.

Ferneatiging fan 'e heap - hoe leuk ... Yn sa'n gefal is d'r in nuttige remedie - fan (as mooglik) deselde boarnen, sammelje in lânseigen binêr en rinne it ûnder Valgrind. Nei ferrin fan tiid wie de binary klear. Ik lansearje it mei deselde opsjes - it crasht sels by inisjalisaasje, foardat it eins eksekúsje berikt. It is fansels onaangenaam - blykber wiene de boarnen net krekt itselde, wat net ferrassend is, om't konfiguraasje in bytsje ferskillende opsjes útsocht, mar ik haw Valgrind - earst reparearje ik dizze brek, en dan, as ik gelok bin , de oarspronklike sil ferskine. Ik rinne itselde ding ûnder Valgrind ... Y-y-y, y-y-y, uh-uh, it begon, gie normaal troch inisjalisaasje en ferhuze foarby de orizjinele brek sûnder ien inkelde warskôging oer ferkearde ûnthâld tagong, net te hawwen oer fallen. It libben, sa't se sizze, hat my hjir net op taret - in crashend programma hâldt op mei crashen as it wurdt lansearre ûnder Walgrind. Wat it wie is in mystearje. Myn hypoteze is dat gdb ienris yn 'e buert fan' e hjoeddeistige ynstruksje nei in crash by inisjalisaasje wurk toande memset-a mei in jildige oanwizer mei help fan beide mmx,of xmm registers, dan miskien wie it in soarte fan alignment flater, hoewol't it is noch altyd lestich te leauwen.

Okee, Valgrind liket hjir net te helpen. En hjir begon it meast walgelijke ding - alles liket sels te begjinnen, mar crasht foar absolút ûnbekende redenen troch in evenemint dat miljoenen ynstruksjes lyn koe hawwe bard. Lange tiid wie it net iens dúdlik hoe't se oanpakke moasten. Op it lêst moast ik noch sitten en debuggen. It printsjen wêrmei't de koptekst wer skreaun wie liet sjen dat it net op in nûmer like, mar in soarte fan binêre gegevens. En sjoch, dizze binêre tekenrige waard fûn yn 'e BIOS-bestân - dat is, no wie it mooglik om te sizzen mei ridlik fertrouwen dat it in bufferoerstream wie, en it is sels dúdlik dat it nei dizze buffer skreaun is. No, dan soksawat - yn Emscripten is d'r gelokkich gjin randomisaasje fan 'e adresromte, d'r binne ek gjin gatten yn, dus jo kinne earne yn 'e midden fan' e koade skriuwe om gegevens troch oanwizer út te jaan fan 'e lêste lansearring, sjoch nei de gegevens, sjoch nei de oanwizer, en, as it is net feroare, krije stof foar tinken. Wier, it duorret in pear minuten om te keppeljen nei elke feroaring, mar wat kinne jo dwaan? As resultaat waard in spesifike line fûn dy't de BIOS kopieare fan 'e tydlike buffer nei it gastûnthâld - en d'r wie yndie net genôch romte yn' e buffer. It finen fan de boarne fan dat frjemde bufferadres resultearre yn in funksje qemu_anon_ram_alloc yn triem oslib-posix.c - de logika dêr wie dit: soms kin it nuttich wêze om it adres ôf te rjochtsjen op in enoarme side fan 2 MB yn grutte, hjirfoar sille wy freegje mmap earst in bytsje mear, en dan sille wy werom it oerskot mei help munmap. En as sa'n ôfstimming net nedich is, dan sille wy it resultaat oanjaan ynstee fan 2 MB getpagesize() - mmap it sil noch in ôfstimd adres jaan... Sa yn Emscripten mmap gewoan ropt malloc, mar fansels is it net align op 'e side. Yn 't algemien waard in brek dy't my in pear moannen frustrearre waard korrizjearre troch in feroaring yn twa rigels.

Funksjes fan opropfunksjes

En no telt de prosessor wat, Qemu crasht net, mar it skerm wurdt net ynskeakele, en de prosessor giet fluch yn loops, te beoardieljen nei de útfier -d exec,in_asm,out_asm. In hypoteze is ûntstien: timerinterrupts (of, yn it algemien, alle interrupts) komme net. En yndied, as jo de ûnderbrekkingen fan 'e lânseigen gearkomste, dy't om ien of oare reden wurke, ûntbrekke, krije jo in ferlykber byld. Mar dit wie net it antwurd hielendal: in ferliking fan de spoaren útjûn mei de boppesteande opsje die bliken dat de útfiering trajekten divergearre hiel betiid. Hjir moat sein wurde dat ferliking fan wat waard opnommen mei de launcher emrun debuggen útfier mei de útfier fan 'e lânseigen gearkomste is net in folslein meganysk proses. Ik wit net krekt hoe't in programma dat rint yn in blêder oanslút emrun, mar guon rigels yn 'e útgong blike op 'e nij ynrjochte te wêzen, sadat it ferskil yn 'e diff noch gjin reden is om oan te nimmen dat de trajekten útinoar binne. Yn it algemien waard dúdlik dat neffens de ynstruksjes ljmpl der is in oergong nei ferskate adressen, en de bytekoade oanmakke is fûneminteel oars: ien befettet in ynstruksje foar in belje in helper funksje, de oare net. Nei it googlejen fan 'e ynstruksjes en it bestudearjen fan' e koade dy't dizze ynstruksjes oerset, waard it dúdlik dat, earst, fuortendaliks foardat it yn it register cr0 der waard in opname makke - ek mei help fan in helper - dy't de prosessor yn 'e beskerme modus skeakele, en twad, dat de js-ferzje nea oergie nei de beskerme modus. Mar it feit is dat in oar skaaimerk fan Emscripten is har ûnwilligens om koade te tolerearjen lykas de ymplemintaasje fan ynstruksjes call yn TCI, dy't elke funksje-oanwizer resultearret yn type long long f(int arg0, .. int arg9) - funksjes moatte wurde oanroppen mei it juste oantal arguminten. As dizze regel wurdt skeind, ôfhinklik fan de debuggen ynstellings, it programma sil of crashe (dat is goed) of neame de ferkearde funksje op alle (dat sil wêze tryst te debuggen). D'r is ek in tredde opsje - ynskeakelje de generaasje fan wrappers dy't arguminten tafoegje / ferwiderje, mar yn totaal nimme dizze wrappers in soad romte yn, nettsjinsteande it feit dat ik yn feite mar in bytsje mear as hûndert wrappers nedich is. Dit allinnich is tige spitich, mar d'r blykte in serieuzer probleem te wêzen: yn 'e oanmakke koade fan' e wrapperfunksjes waarden de arguminten omset en konvertearre, mar soms waard de funksje mei de generearre arguminten net neamd - no, krekt as yn myn libffi ymplemintaasje. Dat is, guon helpers binne gewoan net útfierd.

Gelokkich, Qemu hat masine-lêsbere listen fan helpers yn 'e foarm fan in koptekst triem lykas

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

Se wurde frij grappich brûkt: earst wurde makro's op 'e meast bisarre manier opnij definieare DEF_HELPER_n, en giet dan oan helper.h. Foar safier't de makro wurdt útwreide yn in struktuerinitialisator en in komma, en dan wurdt in array definieare, en ynstee fan eleminten - #include <helper.h> As gefolch hie ik einliks in kâns om de bibleteek op it wurk te besykjen pyparsing, en in skript waard skreaun dat krekt dy wrappers genereart foar krekt de funksjes wêrfoar se nedich binne.

En sa, nei dat de prosessor like te wurkjen. It liket te wêzen om't it skerm nea inisjalisearre is, hoewol memtest86+ koe rinne yn 'e lânseigen gearkomste. Hjir is it nedich om te ferdúdlikjen dat de Qemu-blok I / O-koade is skreaun yn koroutines. Emscripten hat in eigen heul lestige ymplemintaasje, mar it moast noch wurde stipe yn 'e Qemu-koade, en jo kinne de prosessor no debuggen: Qemu stipet opsjes -kernel, -initrd, -append, wêrmei jo Linux of, bygelyks, memtest86+ kinne boote, sûnder blokapparaten hielendal te brûken. Mar hjir is it probleem: yn 'e lânseigen gearkomste koe men de Linux-kernelútfier nei de konsole sjen mei de opsje -nographic, en gjin útfier fan 'e browser nei de terminal fan wêr't it waard lansearre emrun, kaam net. Dat is, it is net dúdlik: de prosessor wurket net of de grafyske útfier wurket net. En doe kaam it yn my op om efkes te wachtsjen. It die bliken dat "de prosessor net sliept, mar gewoan stadich knippert," en nei sawat fiif minuten smiet de kernel in boskje berjochten op 'e konsole en bleau hingjen. It waard dúdlik dat de prosessor, yn it algemien, wurket, en wy moatte grave yn 'e koade foar wurkjen mei SDL2. Spitigernôch wit ik net hoe't ik dizze bibleteek brûke moat, dus op guon plakken moast ik willekeurich hannelje. Op in stuit flitse de line parallel0 op it skerm op in blauwe eftergrûn, wat guon gedachten suggerearre. Uteinlik die bliken dat it probleem wie dat Qemu ferskate firtuele finsters iepenet yn ien fysyk finster, tusken wêryn jo kinne wikselje mei Ctrl-Alt-n: it wurket yn 'e native build, mar net yn Emscripten. Nei it fuortheljen fan ûnnedige finsters mei opsjes -monitor none -parallel none -serial none en ynstruksjes om it hiele skerm op elk frame krêftich opnij te tekenjen, wurke alles ynienen.

Coroutines

Dat, emulaasje yn 'e browser wurket, mar jo kinne neat ynteressant single-floppy yn útfiere, om't d'r gjin blok I / O is - jo moatte stipe foar koroutines ymplementearje. Qemu hat al ferskate coroutine-backends, mar troch de aard fan JavaSkript en de Emscripten-koadegenerator kinne jo net gewoan begjinne mei it jongleren fan stapels. It soe lykje dat "alles is fuort, it gips wurdt fuorthelle," mar de Emscripten ûntwikkelders hawwe al soarge foar alles. Dit is hiel grappich ymplementearre: lit ús in funksjeoprop lykas dizze fertocht neame emscripten_sleep en ferskate oaren mei help fan de Asyncify meganisme, likegoed as oanwizer oproppen en ropt nei eltse funksje dêr't ien fan de foarige twa gefallen kin foarkomme fierder del yn 'e steapel. En no, foar elke fertochte oprop, sille wy in asyngroane oprop selektearje, en fuort nei de oprop sille wy kontrolearje oft der in asynchrone oprop is bard, en as it hat, sille wy alle lokale fariabelen yn dizze asyngroane kontekst bewarje, oanjaan hokker funksje om kontrôle oer te bringen nei wannear't wy de útfiering trochgean moatte, en de hjoeddeistige funksje ferlitte. Dit is wêr't romte is foar it bestudearjen fan it effekt fergriemerij - foar de behoeften fan trochgeande útfiering fan koade nei it weromkommen fan in asynchrone oprop, genereart de kompilator "stubs" fan 'e funksje dy't begjint nei in fertochte oprop - sa: as d'r n fertochte oproppen binne, dan sil de funksje earne útwreide wurde n/2 kear - dit is noch, as net Hâld der rekken mei dat nei eltse potinsjeel asynchronous oprop, Jo moatte tafoegje bewarjen guon lokale fariabelen oan de oarspronklike funksje. Dêrnei moast ik sels in ienfâldich skript skriuwe yn Python, dat, basearre op in bepaalde set fan benammen tefolle brûkte funksjes dy't sabeare "net asynchrony troch harsels passe litte" (dat is, stackpromoasje en alles wat ik krekt beskreaun net wurkje yn harren), jout oanroppen troch oanwizers wêryn funksjes moatte wurde negearre troch de kompilator, sadat dizze funksjes net asynchronysk wurde beskôge. En dan binne JS-bestannen ûnder 60 MB dúdlik te folle - lit ús sizze op syn minst 30. Hoewol't ik ienris in gearstallingsskript ynstelde, en per ongeluk de linkeropsjes útsmite, wêrûnder wie -O3. Ik rinne de oanmakke koade, en Chromium yt ûnthâld en crashes. Ik seach doe by ûngelok wat er besocht te downloaden ... No, wat kin ik sizze, ik soe ek beferzen wêze as ik frege wie om in 500+ MB Javascript te studearjen en te optimalisearjen.

Spitigernôch wiene de kontrôles yn 'e Asyncify-stipe biblioteekkoade net hielendal freonlik mei longjmp-s dy't wurde brûkt yn de firtuele prosessor koade, mar nei in lytse patch dy't útskeakelje dizze kontrôles en krêftich herstelt konteksten as wie alles goed, de koade wurke. En doe begûn in nuver ding: soms waarden kontrôles yn 'e syngronisaasjekoade oanlutsen - deselde dy't de koade crashe as it neffens de útfieringslogika blokkearre wurde soe - ien besocht in al fêstlein mutex te pakken. Gelokkich die bliken dat dit gjin logysk probleem wie yn 'e serialisearre koade - ik brûkte gewoan de standert haadloopfunksjonaliteit levere troch Emscripten, mar soms soe de asynchrone oprop de stapel folslein útpakke, en op dat stuit soe it mislearje setTimeout fan 'e haadloop - sadwaande kaam de koade de iteraasje fan' e haadlus yn sûnder de foarige iteraasje te ferlitten. Rewrote op in ûneinige loop en emscripten_sleep, en de problemen mei mutexes stoppe. De koade is noch logysker wurden - ik haw ommers gjin koade dy't it folgjende animaasjeframe taret - de prosessor berekkent gewoan wat en it skerm wurdt periodyk bywurke. De problemen stopten dêr lykwols net: soms soe de útfiering fan Qemu gewoan stil beëinigje sûnder útsûnderings of flaters. Op dat stuit joech ik it op, mar, foarút sjen, sil ik sizze dat it probleem dit wie: de koroutine-koade brûkt trouwens net setTimeout (of op syn minst net sa faak as jo miskien tinke): funksje emscripten_yield set gewoan de asynchrone oprop flagge. It hiele punt is dat emscripten_coroutine_next is gjin asynchrone funksje: yntern kontrolearret it de flagge, set it werom en draacht kontrôle oer nei wêr't it nedich is. Dat is, de promoasje fan 'e steapel einiget dêr. It probleem wie dat troch gebrûk-nei-fergees, dy't ferskynde doe't de coroutine-pool wie útskeakele fanwegen it feit dat ik gjin wichtige rigel koade kopieare fan 'e besteande coroutine-backend, de funksje qemu_in_coroutine kaam wier werom doe't it yn feite falsk weromkomme soe. Dit late ta in oprop emscripten_yield, dêr't gjinien op 'e steapel wie emscripten_coroutine_next, de steapel ûntfolde nei de top, mar nee setTimeout, sa't ik al sei, waard net útstald.

JavaSkript koade generaasje

En hjir is yn feite de taseine "it gehakt weromdraaie." Net wirklik. Fansels, as wy Qemu yn 'e browser útfiere, en Node.js dêryn, dan sille wy fansels nei koade generaasje yn Qemu folslein ferkeard JavaScript krije. Mar dochs, in soarte fan omkearde transformaasje.

Earst in bytsje oer hoe't Qemu wurket. Ferjou my asjebleaft daliks: ik bin gjin profesjonele Qemu-ûntwikkelder en myn konklúzjes kinne op guon plakken ferkeard wêze. As se sizze, "de miening fan 'e studint hoecht net oerien te kommen mei de miening fan' e learaar, Peano's axiomatyk en sûn ferstân." Qemu hat in bepaald oantal stipe gastarsjitektueren en foar elk is d'r in map lykas target-i386. By it bouwen kinne jo stipe oanjaan foar ferskate gastarsjitektuer, mar it resultaat sil gewoan ferskate binaries wêze. De koade om de gastarsjitektuer te stypjen genereart op syn beurt wat ynterne Qemu-operaasjes, dy't de TCG (Tiny Code Generator) al feroaret yn masinekoade foar de hostarsjitektuer. Lykas sein yn 'e readme-bestân yn' e tcg-map, wie dit oarspronklik diel fan in gewoane C-kompiler, dy't letter waard oanpast foar JIT. Dêrom, bygelyks, doel arsjitektuer yn termen fan dit dokumint is net langer in gast arsjitektuer, mar in host arsjitektuer. Op in stuit ferskynde in oare komponint - Tiny Code Interpreter (TCI), dy't koade moat útfiere (hast deselde ynterne operaasjes) by it ûntbrekken fan in koadegenerator foar in spesifike hostarsjitektuer. Yn feite, lykas syn dokumintaasje stelt, kin dizze tolk net altyd sa goed prestearje as in JIT-koadegenerator, net allinich kwantitatyf yn termen fan snelheid, mar ek kwalitatyf. Al bin ik der net wis fan dat syn beskriuwing folslein relevant is.

Earst besocht ik in folsleine TCG-backend te meitsjen, mar waard fluch betize yn 'e boarnekoade en in net folslein dúdlike beskriuwing fan' e bytecode-ynstruksjes, dus besleat ik de TCI-tolk te wikkeljen. Dit joech ferskate foardielen:

  • by it útfieren fan in koadegenerator, kinne jo net sjen nei de beskriuwing fan ynstruksjes, mar nei de tolkkoade
  • jo kinne funksjes generearje net foar elk oantroffen oersetblok, mar bygelyks pas nei de hûndertste útfiering
  • as de oanmakke koade feroaret (en dit liket mooglik te wêzen, te oardieljen nei de funksjes mei nammen dy't it wurd patch befetsje), sil ik de oanmakke JS-koade ûnjildich meitsje moatte, mar ik sil teminsten wat hawwe om it te regenerearjen fan

Oangeande it tredde punt, ik bin der net wis fan dat patching mooglik is neidat de koade wurdt útfierd foar de earste kear, mar de earste twa punten binne genôch.

Yn earste ynstânsje waard de koade oanmakke yn 'e foarm fan in grutte skeakel op it adres fan' e orizjinele bytecode-ynstruksje, mar doe, oantinken oan it artikel oer Emscripten, optimisaasje fan generearre JS en relooping, besleat ik mear minsklike koade te generearjen, benammen om't empirysk it die bliken dat it ienige yngongspunt yn it oersettingsblok syn Start is. Net earder sein as dien, nei in skoftke wy hiene in koade generator dy't generearre koade mei ifs (alhoewol't sûnder loops). Mar pech, it stoarte, en joech in berjocht dat de ynstruksjes fan wat ferkearde lingte wiene. Boppedat wie de lêste ynstruksje op dit rekursjenivo brcond. Okee, ik sil in identike kontrôle tafoegje oan 'e generaasje fan dizze ynstruksje foar en nei de rekursive oprop en ... net ien fan har waard útfierd, mar nei de assert-skeakel mislearre se noch. Oan 'e ein, nei it bestudearjen fan' e generearre koade, realisearre ik dat nei de skeakel de oanwizer nei de aktuele ynstruksje wurdt opnij laden fan 'e stapel en wurdt wierskynlik oerskreaun troch de generearre JavaScript-koade. En sa die bliken. It fergrutsjen fan de buffer fan ien megabyte nei tsien hat net liede ta neat, en it waard dúdlik dat de koade generator draaide yn sirkels. Wy moasten kontrolearje dat wy net oer de grinzen fan 'e hjoeddeistige TB gienen, en as wy dat diene, dan it adres fan 'e folgjende TB mei in minteken útjaan, sadat wy de útfiering trochgean kinne. Derneist lost dit it probleem op "hokker generearre funksjes moatte ûnjildich wurde as dit stik bytekoade feroare is?" - allinich de funksje dy't oerienkomt mei dit oersettingsblok moat ûnjildich wurde. Trouwens, hoewol ik alles yn Chromium debuggen haw (omdat ik Firefox brûk en it makliker is foar my om in aparte browser te brûken foar eksperiminten), hat Firefox my holpen ynkompatibiliteiten mei de asm.js-standert te korrigearjen, wêrnei't de koade rapper begon te wurkjen yn Chromium.

Foarbyld fan oanmakke koade

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

konklúzje

Sa, it wurk is noch net klear, mar ik bin wurch fan it stikem bringen dizze lange-termyn konstruksje ta folsleinens. Dêrom haw ik besletten om te publisearjen wat ik foar no haw. De koade is in bytsje Scary op plakken, want dit is in eksperimint, en it is net dúdlik fan tefoaren wat moat dien wurde. Wierskynlik, dan is it wurdich om normale atomêre commits út te jaan boppe op wat modernere ferzje fan Qemu. Yn 'e tuskentiid is der in tried yn' e Gita yn in blogformaat: foar elk "nivo" dat op syn minst ien of oare manier trochjûn is, is in detaillearre kommentaar yn it Russysk tafoege. Eins is dit artikel foar in grut part in wertelling fan de konklúzje git log.

Jo kinne it allegear besykje hjir (pas op foar ferkear).

Wat wurket al:

  • x86 firtuele prosessor rint
  • D'r is in wurkjend prototype fan in JIT-koadegenerator fan masinekoade oant JavaScript
  • D'r is in sjabloan foar it gearstallen fan oare 32-bit gastarsjitektueren: op it stuit kinne jo Linux bewûnderje foar de MIPS-arsjitektuer dy't befriest yn 'e browser by it ladenstadium

Wat kinne jo dwaan

  • Fersnelle emulaasje. Sels yn JIT-modus liket it stadiger te rinnen dan Virtual x86 (mar d'r is mooglik in hiele Qemu mei in protte emulearre hardware en arsjitektuer)
  • Om in normale ynterface te meitsjen - earlik sein, ik bin gjin goede webûntwikkelder, dus foar no haw ik de standert Emscripten-shell sa goed mooglik makke
  • Besykje mear komplekse Qemu-funksjes te starten - netwurken, VM-migraasje, ensfh.
  • UPD: jo moatte jo pear ûntwikkelingen en brekrapporten streamop nei Emscripten yntsjinje, lykas eardere porters fan Qemu en oare projekten diene. Mei tank oan harren dat se har bydrage oan Emscripten ymplisyt brûke kinne as ûnderdiel fan myn taak.

Boarne: www.habr.com

Add a comment