Qemu.js með JIT stuðningi: þú getur samt snúið hakkinu afturábak

Fyrir nokkrum árum síðan Fabrice Bellard skrifað af jslinux er PC keppinautur skrifaður í JavaScript. Eftir það var að minnsta kosti meira Sýndar x86. En allir voru þeir, eftir því sem ég best veit, túlkar, á meðan Qemu, skrifað miklu fyrr af sama Fabrice Bellard, og líklega hvaða nútímahermi sem er með sjálfsvirðingu, notar JIT samantekt gestakóða í hýsilkerfiskóða. Mér fannst kominn tími til að innleiða hið gagnstæða verkefni í tengslum við það sem vafrar leysa: JIT samantekt vélkóða í JavaScript, sem það virtist rökréttast fyrir að flytja Qemu. Það virðist, hvers vegna Qemu, það eru einfaldari og notendavænt keppinautar - sama VirtualBox, til dæmis - uppsett og virkar. En Qemu hefur nokkra áhugaverða eiginleika

  • opinn uppspretta
  • getu til að vinna án kjarna drivers
  • hæfni til að vinna í túlkaham
  • stuðningur við mikinn fjölda bæði gestgjafa- og gestaarkitektúra

Varðandi þriðja atriðið get ég nú útskýrt að í raun, í TCI ham, eru það ekki leiðbeiningar gestavélarinnar sjálfar sem eru túlkaðar, heldur bækikóði sem fæst úr þeim, en þetta breytir ekki kjarnanum - til að byggja og keyra Qemu á nýjum arkitektúr, ef þú ert heppinn, þá er A C þýðanda nóg - hægt er að fresta því að skrifa kóðarafall.

Og núna, eftir tveggja ára rólega fikta við Qemu frumkóðann í frítíma mínum, birtist virka frumgerð, þar sem þú getur nú þegar keyrt, til dæmis, Kolibri OS.

Hvað er Emscripten

Nú á dögum hafa margir þýðendur birst, lokaniðurstaða þeirra er JavaScript. Sumum, eins og Type Script, var upphaflega ætlað að vera besta leiðin til að skrifa fyrir vefinn. Á sama tíma er Emscripten leið til að taka núverandi C eða C++ kóða og setja hann saman í vafralæsilegt form. Á Þessi síða Við höfum safnað mörgum höfnum af þekktum forritum: hérTil dæmis, þú getur skoðað PyPy - við the vegur, þeir segjast þegar hafa JIT. Reyndar er ekki hægt að setja öll forrit einfaldlega saman og keyra í vafra - það eru nokkrir eiginleikar, sem þú verður þó að sætta þig við, þar sem áletrunin á sömu síðu segir „Emscripten er hægt að nota til að setja saman nánast hvaða flytjanlegur C/C++ kóða í JavaScript". Það er að segja að það eru nokkrar aðgerðir sem eru óskilgreind hegðun samkvæmt staðlinum, en virka venjulega á x86 - til dæmis ójafnaðan aðgang að breytum, sem er almennt bannaður á sumum arkitektúrum. Almennt , Qemu er þvert á vettvang forrit og ég vildi trúa því, og það inniheldur ekki nú þegar mikið af óskilgreindri hegðun - taktu það og settu saman, pikkaðu svo aðeins með JIT - og þú ert búinn! En það er ekki Málið...

Fyrsta tilraun

Almennt séð er ég ekki sá fyrsti sem kemur með hugmyndina um að flytja Qemu yfir á JavaScript. Það var spurt á ReactOS spjallborðinu hvort þetta væri mögulegt með Emscripten. Jafnvel fyrr voru sögusagnir um að Fabrice Bellard hafi gert þetta persónulega, en við vorum að tala um jslinux, sem, eftir því sem ég best veit, er bara tilraun til að ná fram fullnægjandi frammistöðu handvirkt í JS, og var skrifað frá grunni. Seinna var Virtual x86 skrifað - ótvíræðar heimildir voru settar fyrir það og eins og fram hefur komið gerði meira „raunsæi“ kappakstursins mögulegt að nota SeaBIOS sem fastbúnað. Að auki var að minnsta kosti ein tilraun til að flytja Qemu með Emscripten - ég reyndi að gera þetta falspar, en þróunin, eftir því sem mér skilst, var frosin.

Svo virðist sem hér eru heimildirnar, hér er Emscripten - taktu það og settu saman. En það eru líka bókasöfn sem Qemu er háð, og bókasöfn sem þessi bókasöfn eru háð o.s.frv., og eitt þeirra er libffi, sem glib fer eftir. Það voru orðrómar á netinu um að það væri einn í stóru safni bókahafna fyrir Emscripten, en það var einhvern veginn erfitt að trúa því: í fyrsta lagi var það ekki ætlað að vera nýr þýðandi, í öðru lagi var það of lágt stig. bókasafn til að taka bara upp og safna saman í JS. Og þetta snýst ekki bara um samsetningarinnskot - sennilega, ef þú snýrð því, fyrir suma kallasamninga geturðu búið til nauðsynleg rök á staflanum og kallað fallið án þeirra. En Emscripten er erfiður hlutur: til þess að mynda kóðann líti kunnuglega út fyrir JS vélahagræðingarvél vafrans, eru nokkur brögð notuð. Sérstaklega reynir svokölluð relooping - kóðarafall sem notar móttekið LLVM IR með einhverjum óhlutbundnum umbreytingarleiðbeiningum að endurskapa trúverðug efs, lykkjur osfrv. Jæja, hvernig eru rökin send til fallsins? Auðvitað, sem rök fyrir JS virka, það er, ef mögulegt er, ekki í gegnum stafla.

Í upphafi var hugmynd um að skrifa einfaldlega skipti fyrir libffi með JS og keyra stöðluð próf, en á endanum ruglaðist ég í því hvernig ég ætti að búa til hausskrárnar mínar þannig að þær myndu virka með núverandi kóða - hvað get ég gert, eins og þeir segja, "Eru verkefnin svo flókin "Erum við svo heimsk?" Ég þurfti að flytja libffi yfir í annan arkitektúr, ef svo má að orði komast - sem betur fer, Emscripten hefur bæði fjölvi fyrir samsetningu í línu (í Javascript, já - jæja, hver sem arkitektúrinn er, svo assemblerinn), og getu til að keyra kóða sem er búinn til á flugu. Almennt séð, eftir að hafa fiktað við vettvangsháð libffi brot í nokkurn tíma, fékk ég samhæfðan kóða og keyrði hann í fyrsta prófinu sem ég rakst á. Mér til undrunar gekk prófið vel. Snilld af snilli minni - ekkert grín, það virkaði frá fyrstu kynningu - ég trúði samt ekki eigin augum og fór að skoða kóðann sem kom aftur til að meta hvar ég ætti að grafa næst. Hér varð ég brjálaður í annað sinn - það eina sem hlutverk mitt gerði var ffi_call - þetta tilkynnti vel heppnað símtal. Það var ekkert símtal sjálft. Þannig að ég sendi fyrstu dráttarbeiðnina mína, sem leiðrétti villu í prófinu sem er öllum ólympíunemendum ljós - rauntölur ættu ekki að bera saman sem a == b og jafnvel hvernig a - b < EPS - þú þarft líka að muna eininguna, annars reynist 0 vera mjög jafnt og 1/3... Almennt séð kom ég með ákveðna port af libffi, sem stenst einföldustu prófin, og með sem glib er tekið saman - ég ákvað að það væri nauðsynlegt, ég bæti því við síðar. Þegar ég horfi fram á veginn mun ég segja að, eins og það kom í ljós, var þýðandinn ekki einu sinni með libffi fallið í lokakóðann.

En eins og ég sagði þegar, það eru nokkrar takmarkanir, og meðal frjálsrar notkunar á ýmsum óskilgreindri hegðun hefur óþægilegri eiginleiki verið falinn - JavaScript í hönnun styður ekki fjölþráða með sameiginlegu minni. Í grundvallaratriðum er yfirleitt jafnvel hægt að kalla þetta góð hugmynd, en ekki fyrir flutningskóða þar sem arkitektúr er bundinn við C þræði. Almennt séð er Firefox að gera tilraunir með að styðja sameiginlega starfsmenn og Emscripten er með pthread útfærslu fyrir þá, en ég vildi ekki treysta á það. Ég þurfti hægt og rólega að útrýma multithreading úr Qemu kóðanum - það er að finna út hvar þræðir eru í gangi, færa meginmál lykkjunnar sem keyrir í þessum þræði yfir í sérstakt fall og kalla slíkar aðgerðir ein af annarri úr aðallykkjunni.

Annað tilraun

Á einhverjum tímapunkti varð ljóst að vandamálið var enn til staðar og að það myndi ekki leiða til góðs að troða hækjum af tilviljun í kringum kóðann. Ályktun: við þurfum einhvern veginn að setja kerfisbundið ferli við að bæta við hækjum. Þess vegna var útgáfa 2.4.1, sem var ný á þeim tíma, tekin (ekki 2.5.0, því hver veit, það verða villur í nýju útgáfunni sem hafa ekki enn náðst og ég á nóg af mínum eigin villum ), og það fyrsta var að endurskrifa það á öruggan hátt thread-posix.c. Jæja, það er eins öruggt: ef einhver reyndi að framkvæma aðgerð sem leiddi til lokunar var aðgerðin strax kölluð abort() - auðvitað leysti þetta ekki öll vandamálin í einu, en það var allavega einhvern veginn notalegra en að fá ósamræmileg gögn í hljóði.

Almennt séð eru Emscripten valkostir mjög hjálplegir við að flytja kóða til JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - þeir ná einhverjum tegundum af óskilgreindri hegðun, eins og símtöl í ósamræmt heimilisfang (sem er alls ekki í samræmi við kóðann fyrir vélritaðar fylki eins og HEAP32[addr >> 2] = 1) eða kallar fall með röngum fjölda frumbreyta.

Við the vegur, röðun villur eru sérstakt mál. Eins og ég sagði þegar, Qemu er með „úrkynjaðan“ túlkunarbakenda fyrir kóðagerð TCI (pínulítill kóðatúlkur) og til að byggja og keyra Qemu á nýjum arkitektúr, ef þú ert heppinn, er C þýðandi nóg. "ef þú ert heppinn". Ég var óheppinn og það kom í ljós að TCI notar ójafnaðan aðgang þegar hann greinir bækikóðann. Það er að segja að á alls kyns ARM og öðrum arkitektúrum með nauðsynlega jafnaðan aðgang, safnar Qemu saman vegna þess að þeir eru með venjulegan TCG bakenda sem býr til innfæddan kóða, en hvort TCI muni virka á þeim er önnur spurning. Hins vegar, eins og það kom í ljós, gaf TCI skjölin greinilega til kynna eitthvað svipað. Þess vegna var fallköllum fyrir ósamræmdan lestur bætt við kóðann, sem fundust í öðrum hluta Qemu.

Hrúgueyðing

Fyrir vikið var ósamræmdur aðgangur að TCI leiðréttur, búið til aðallykkja sem aftur kallaði örgjörva, RCU og eitthvað annað smálegt. Og svo ræsir ég Qemu með möguleikanum -d exec,in_asm,out_asm, sem þýðir að þú þarft að segja hvaða kóðablokkir eru keyrðar, og einnig við útsendingu að skrifa hvað gestakóði var, hvaða hýsilkóði varð (í þessu tilviki bætikóði). Það byrjar, keyrir nokkrar þýðingarblokkir, skrifar villuleitarskilaboðin sem ég skildi eftir að RCU muni nú ræsast og... hrynur abort() inni í falli free(). Með því að fikta í aðgerðinni free() Okkur tókst að komast að því að í hausnum á hrúgublokkinni, sem liggur í átta bætum á undan úthlutað minni, í stað blokkastærðarinnar eða eitthvað álíka, var rusl.

Eyðing á hrúgunni - hversu krúttlegt... Í slíku tilviki er gagnlegt úrræði - frá (ef mögulegt er) sömu heimildum, settu saman innfæddan tvöfalda og keyrðu hana undir Valgrind. Eftir nokkurn tíma var tvöfaldurinn tilbúinn. Ég ræsi það með sömu valmöguleikum - það hrynur jafnvel við frumstillingu, áður en það nær raunverulega framkvæmd. Það er auðvitað óþægilegt - eins og gefur að skilja voru heimildirnar ekki nákvæmlega þær sömu, sem kemur ekki á óvart, því að stilla út aðeins mismunandi valkosti, en ég er með Valgrind - fyrst laga ég þessa villu og síðan, ef ég er heppinn , upprunalega birtist. Ég er að keyra það sama undir Valgrind... Y-y-y, y-y-y, uh-uh, það byrjaði, fór í gegnum frumstillingu venjulega og fór framhjá upprunalegu villunni án einustu viðvörunar um rangan aðgang að minni, svo ekki sé minnst á fall. Lífið, eins og sagt er, undirbjó mig ekki fyrir þetta - hrunforrit hættir að hrynja þegar það er ræst undir Walgrind. Hvað það var er ráðgáta. Tilgáta mín er sú að einu sinni í nágrenni núverandi leiðbeiningar eftir hrun við frumstillingu sýndi gdb vinnu memset-a með gildum bendi með því að nota annað hvort mmx, eða xmm skrár, þá var þetta kannski einhvers konar stillingarvilla, þó að það sé enn erfitt að trúa því.

Allt í lagi, Valgrind virðist ekki hjálpa hér. Og hér byrjaði það ógeðslegasta - allt virðist jafnvel byrja, en hrynur af algjörlega óþekktum ástæðum vegna atburðar sem gæti hafa gerst fyrir milljónum leiðbeininga síðan. Í langan tíma var ekki einu sinni ljóst hvernig ætti að nálgast. Á endanum þurfti ég samt að setjast niður og kemba. Að prenta það sem hausinn var endurskrifaður með sýndi að hann leit ekki út eins og tölu, heldur einhvers konar tvíundargögn. Og sjá, þessi tvöfaldur strengur fannst í BIOS skránni - það er að segja nú var hægt að segja með sanngjörnu öryggi að þetta væri biðminni yfirfall, og það er meira að segja ljóst að það var skrifað á þennan biðminni. Jæja, þá eitthvað eins og þetta - í Emscripten, sem betur fer, er engin slembival á heimilisfangarýminu, það eru engin göt í því heldur, svo þú getur skrifað einhvers staðar í miðjum kóðanum til að gefa út gögn með bendili frá síðustu sjósetningu, skoðaðu gögnin, skoðaðu bendilinn og, ef þau hafa ekki breyst, fáðu umhugsunarefni. Að vísu tekur það nokkrar mínútur að tengja eftir breytingar, en hvað geturðu gert? Fyrir vikið fannst ákveðin lína sem afritaði BIOS frá tímabundnum biðminni yfir í gestaminnið - og reyndar var ekki nóg pláss í biðminni. Að finna uppruna þessa undarlega biðminnisvistfangs leiddi til falls qemu_anon_ram_alloc í skrá oslib-posix.c - rökfræðin þar var þessi: stundum getur verið gagnlegt að samræma heimilisfangið við risastóra síðu sem er 2 MB að stærð, til þess munum við spyrja mmap fyrst aðeins meira og svo skilum við því sem er umfram með hjálpinni munmap. Og ef slík röðun er ekki krafist, þá munum við gefa til kynna niðurstöðuna í stað 2 MB getpagesize() - mmap það mun samt gefa út samræmt heimilisfang... Svo í Emscripten mmap hringir bara malloc, en það passar auðvitað ekki á síðunni. Almennt séð var villa sem pirraði mig í nokkra mánuði leiðrétt með breytingu á двух línur.

Eiginleikar hringingaraðgerða

Og nú er örgjörvinn að telja eitthvað, Qemu hrynur ekki, en skjárinn kviknar ekki á og örgjörvinn fer fljótt í lykkjur, miðað við úttakið -d exec,in_asm,out_asm. Tilgáta hefur komið fram: Tímaratruflanir (eða almennt allar truflanir) berast ekki. Og reyndar, ef þú skrúfar truflana af innfædda samkomunni, sem af einhverjum ástæðum virkaði, færðu svipaða mynd. En þetta var alls ekki svarið: samanburður á ummerkjum sem gefin voru út með ofangreindum valkosti sýndi að aftökuferlar skiptust mjög snemma. Hér verður að segjast að samanburður á því sem var tekið upp með því að nota sjósetjarann emrun kembiforrit með framleiðsla innfæddra samsetningar er ekki algjörlega vélrænt ferli. Ég veit ekki nákvæmlega hvernig forrit sem keyrir í vafra tengist emrun, en sumar línur í úttakinu reynast vera endurraðaðar, þannig að munurinn á diffinum er ekki enn ástæða til að ætla að brautirnar hafi farið í sundur. Almennt varð ljóst að samkvæmt leiðbeiningum ljmpl það er skipt yfir í mismunandi vistföng og bækikóði sem myndast er í grundvallaratriðum öðruvísi: einn inniheldur leiðbeiningar um að kalla á hjálparaðgerð, hinn ekki. Eftir að hafa googlað leiðbeiningarnar og kynnt sér kóðann sem þýðir þessar leiðbeiningar kom í ljós að í fyrsta lagi strax á undan honum í skránni. cr0 var gerð upptaka - einnig með hjálpartæki - sem kveikti á örgjörvanum í verndaða stillingu, og í öðru lagi að js útgáfan skipti aldrei yfir í verndarstillingu. En staðreyndin er sú að annar eiginleiki Emscripten er tregða þess til að þola kóða eins og útfærslu leiðbeininga call í TCI, sem hvaða aðgerðabendill sem er leiðir til gerð long long f(int arg0, .. int arg9) - Kalla þarf föll með réttum fjölda frumbreyta. Ef þessi regla er brotin, allt eftir villuleitarstillingum, mun forritið annað hvort hrynja (sem er gott) eða kalla á ranga aðgerð yfirleitt (sem verður leiðinlegt að kemba). Það er líka þriðji valmöguleikinn - virkja kynslóð umbúðir sem bæta við / fjarlægja rök, en samtals taka þessar umbúðir mikið pláss, þrátt fyrir að í raun þarf ég aðeins meira en hundrað umbúðir. Þetta eitt og sér er mjög sorglegt, en það reyndist vera alvarlegra vandamál: í myndaða kóða umbúðafallanna var rökunum breytt og breytt, en stundum var fallið með mynduðu rökunum ekki kallað - ja, alveg eins og í libffi útfærslu mína. Það er að segja, sumir aðstoðarmenn voru einfaldlega ekki teknir af lífi.

Sem betur fer hefur Qemu véllæsanlega lista yfir aðstoðarmenn í formi hausskrár eins og

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

Þau eru notuð frekar fyndin: í fyrsta lagi eru fjölvi endurskilgreind á undarlegasta hátt DEF_HELPER_n, og kveikir síðan á helper.h. Að því marki sem fjölvi er stækkað í frumstillingu og kommu, og síðan er fylki skilgreint, og í stað þátta - #include <helper.h> Fyrir vikið fékk ég loksins tækifæri til að prófa bókasafnið í vinnunni pyparsing, og handrit var skrifað sem býr til nákvæmlega þessar umbúðir fyrir nákvæmlega þær aðgerðir sem þær eru nauðsynlegar fyrir.

Og svo, eftir það virtist örgjörvinn virka. Það virðist vera vegna þess að skjárinn var aldrei frumstilltur, þó memtest86+ hafi getað keyrt í innfædda samsetningunni. Hér er nauðsynlegt að skýra að Qemu blokk I/O kóðinn er skrifaður í coroutines. Emscripten hefur sína eigin mjög erfiðu útfærslu, en það þurfti samt að styðja hana í Qemu kóðanum og þú getur kembiforritið núna: Qemu styður valkosti -kernel, -initrd, -append, þar sem þú getur ræst Linux eða til dæmis memtest86+, án þess að nota blokkartæki yfirleitt. En hér er vandamálið: í innfæddri samsetningu gætirðu séð Linux kjarnaúttakið á stjórnborðið með möguleikanum -nographic, og engin útgangur frá vafranum í flugstöðina þar sem hann var ræstur emrun, kom ekki. Það er, það er ekki ljóst: örgjörvinn virkar ekki eða grafíkúttakið virkar ekki. Og þá datt mér í hug að bíða aðeins. Það kom í ljós að „örgjörvinn sefur ekki, heldur blikkar hann hægt,“ og eftir um það bil fimm mínútur henti kjarninn fullt af skilaboðum á stjórnborðið og hélt áfram að hanga. Það varð ljóst að örgjörvinn virkar almennt og við þurfum að grafa ofan í kóðann til að vinna með SDL2. Því miður veit ég ekki hvernig á að nota þetta bókasafn, svo sums staðar þurfti ég að bregðast við af handahófi. Á einhverjum tímapunkti blikkaði línan parallel0 á skjánum á bláum bakgrunni, sem gaf til kynna nokkrar hugsanir. Á endanum kom í ljós að vandamálið var að Qemu opnar nokkra sýndarglugga í einum líkamlegum glugga, þar sem þú getur skipt á milli með því að nota Ctrl-Alt-n: það virkar í innfæddri byggingu, en ekki í Emscripten. Eftir að hafa losnað við óþarfa glugga með því að nota valkosti -monitor none -parallel none -serial none og leiðbeiningar um að endurteikna allan skjáinn af krafti á hverjum ramma, allt virkaði allt í einu.

Coroutines

Svo, eftirlíking í vafranum virkar, en þú getur ekki keyrt neitt áhugavert á einum disklingi í honum, vegna þess að það er engin blokk I/O - þú þarft að innleiða stuðning fyrir coroutines. Qemu er nú þegar með nokkra coroutine bakenda, en vegna eðlis JavaScript og Emscripten kóðarafallsins geturðu ekki bara byrjað að tjúlla saman stafla. Svo virðist sem „allt sé farið, verið er að fjarlægja gifsið,“ en Emscripten verktaki hefur þegar séð um allt. Þetta er útfært frekar fyndið: við skulum kalla fallsímtal eins og þetta grunsamlegt emscripten_sleep og nokkrir aðrir sem nota Asyncify vélbúnaðinn, svo og bendikalla og kalla á hvaða aðgerð sem er þar sem annað af tveimur fyrri tilfellunum gæti komið neðar í staflanum. Og nú, fyrir hvert grunsamlegt símtal, munum við velja ósamstillt samhengi og strax eftir símtalið munum við athuga hvort ósamstillt símtal hafi átt sér stað og ef það hefur átt sér stað munum við vista allar staðbundnar breytur í þessu ósamstillta samhengi, gefa til kynna hvaða aðgerð til að flytja stjórn til þess þegar við þurfum að halda áfram keyrslu og hætta núverandi aðgerð. Hér er svigrúm til að rannsaka áhrifin sóun — til að halda áfram að keyra kóða eftir að hafa komið til baka úr ósamstilltu símtali, býr þýðandinn til „stubba“ af aðgerðinni sem byrjar eftir grunsamlegt símtal — svona: ef það eru n grunsamleg símtöl, þá verður aðgerðin stækkuð einhvers staðar n/2 sinnum — þetta er samt, ef ekki Hafðu í huga að eftir hvert hugsanlega ósamstillt símtal þarftu að bæta vistun nokkrum staðbundnum breytum við upprunalegu fallið. Í kjölfarið þurfti ég meira að segja að skrifa einfalt handrit í Python, sem byggt á tilteknu setti af sérstaklega ofnotuðum aðgerðum sem á að „leyfa ekki ósamstillingu að fara í gegnum sig“ (þ. vinna í þeim), gefur til kynna símtöl í gegnum ábendingar þar sem aðgerðir ættu að hunsa af þýðanda svo að þessar aðgerðir teljist ekki ósamstilltar. Og svo eru JS skrár undir 60 MB greinilega of mikið - við skulum segja að minnsta kosti 30. Þó var ég einu sinni að setja upp samsetningarforskrift og henti óvart út tengivalkostunum, þar á meðal var -O3. Ég keyri útbúna kóðann og Chromium étur upp minni og hrynur. Ég horfði svo óvart á það sem hann var að reyna að hlaða niður... Jæja, hvað get ég sagt, ég hefði líka frosið ef ég hefði verið beðinn um að rannsaka og fínstilla 500+ MB Javascript.

Því miður voru ávísanir í Asyncify stuðningsbókasafnskóðanum ekki alveg vingjarnlegar longjmp-s sem eru notuð í sýndar örgjörva kóðanum, en eftir smá plástur sem gerir þessar athuganir óvirkar og endurheimtir samhengi af krafti eins og allt væri í lagi, virkaði kóðinn. Og svo byrjaði skrítið: Stundum voru athuganir á samstillingarkóðanum ræstar - þær sömu og hrynja kóðann ef, samkvæmt framkvæmdarrógíkinni, ætti að loka honum - einhver reyndi að grípa þegar tekið mutex. Sem betur fer reyndist þetta ekki vera rökrétt vandamál í raðnúmerakóðanum - ég var einfaldlega að nota staðlaða aðallykkjuvirkni sem Emscripten býður upp á, en stundum tók ósamstillta símtalið upp staflanum alveg og á því augnabliki myndi það mistakast setTimeout frá aðallykkju - þannig fór kóðinn inn í aðallykkjuendurtekningu án þess að fara út úr fyrri endurtekningu. Endurskrifað á óendanlega lykkju og emscripten_sleep, og vandamálin með mutexes hættu. Kóðinn er jafnvel orðinn rökréttari - þegar allt kemur til alls, ég hef reyndar ekki einhvern kóða sem undirbýr næsta hreyfimyndaramma - örgjörvinn reiknar bara eitthvað og skjárinn er uppfærður reglulega. Hins vegar hættu vandamálin ekki þar: stundum hætti Qemu framkvæmd einfaldlega hljóðlaust án undantekninga eða villna. Á því augnabliki gafst ég upp á því, en þegar ég horfi fram á veginn segi ég að vandamálið hafi verið þetta: Coroutine kóðinn notar í raun ekki setTimeout (eða að minnsta kosti ekki eins oft og þú gætir haldið): virka emscripten_yield setur einfaldlega ósamstillta kalla fána. Aðalatriðið er það emscripten_coroutine_next er ekki ósamstillt fall: innbyrðis athugar það flaggið, endurstillir það og flytur stjórn þangað sem það er þörf. Það er, kynning á stafla endar þar. Vandamálið var að vegna notkunar-eftir-frjáls, sem birtist þegar coroutine-laugin var óvirk vegna þess að ég afritaði ekki mikilvæga línu af kóða úr núverandi coroutine-bakenda, aðgerðin qemu_in_coroutine skilaði satt þegar það hefði í raun átt að skila ósatt. Þetta leiddi til símtals emscripten_yield, þar fyrir ofan var enginn á stokknum emscripten_coroutine_next, stakkurinn braut sig alveg upp, en nei setTimeout, eins og ég sagði þegar, var ekki sýnd.

JavaScript kóða kynslóð

Og hér er í rauninni lofað að „snúa hakkinu til baka“. Eiginlega ekki. Auðvitað, ef við keyrum Qemu í vafranum, og Node.js í honum, þá munum við náttúrulega fá algjörlega rangt JavaScript eftir kóðagerð í Qemu. En samt, einhvers konar öfug umbreyting.

Fyrst, smá um hvernig Qemu virkar. Vinsamlegast fyrirgefðu mér strax: Ég er ekki faglegur Qemu verktaki og ályktanir mínar gætu verið rangar á sumum stöðum. Eins og þeir segja, "álit nemandans þarf ekki að vera í samræmi við skoðun kennarans, axiomatics Peano og skynsemi." Qemu hefur ákveðinn fjölda studdra gestaarkitektúra og fyrir hvern er möppu eins og target-i386. Þegar þú byggir geturðu tilgreint stuðning fyrir nokkra gestaarkitektúra, en niðurstaðan verður bara nokkrir tvíþættir. Kóðinn til að styðja gestaarkitektúrinn myndar aftur á móti nokkrar innri Qemu-aðgerðir, sem TCG (Tiny Code Generator) breytir nú þegar í vélkóða fyrir hýsilarkitektúrinn. Eins og fram kemur í readme skránni sem staðsett er í tcg möppunni, var þetta upphaflega hluti af venjulegum C þýðanda, sem síðar var lagaður fyrir JIT. Þess vegna, til dæmis, er markarkitektúr samkvæmt þessu skjali ekki lengur gestaarkitektúr, heldur gestgjafaarkitektúr. Á einhverjum tímapunkti birtist annar hluti - Tiny Code Interpreter (TCI), sem ætti að framkvæma kóða (nánast sömu innri aðgerðir) í fjarveru kóðarafalls fyrir ákveðinn hýsilarkitektúr. Reyndar, eins og fram kemur í skjölum þess, getur þessi túlkur ekki alltaf staðið sig eins vel og JIT kóða rafall, ekki aðeins magn hvað varðar hraða, heldur einnig eigindlega. Þó ég sé ekki viss um að lýsing hans eigi alveg við.

Í fyrstu reyndi ég að búa til fullgildan TCG bakenda, en ruglaðist fljótt í frumkóðanum og ekki alveg skýrri lýsingu á bækikóðaleiðbeiningunum, svo ég ákvað að pakka inn TCI túlknum. Þetta gaf nokkra kosti:

  • þegar þú innleiðir kóðarafall gætirðu ekki skoðað lýsingu á leiðbeiningum, heldur túlkkóðann
  • þú getur ekki búið til aðgerðir fyrir hverja þýðingarblokk sem þú finnur fyrir, heldur, til dæmis, aðeins eftir hundraðustu framkvæmdina
  • ef útbúinn kóðinn breytist (og þetta virðist vera mögulegt, miðað við föllin með nöfnum sem innihalda orðið plástur), þá þarf ég að ógilda myndaða JS kóðann, en ég mun allavega hafa eitthvað til að endurskapa hann út frá

Varðandi þriðja atriðið er ég ekki viss um að plástra sé möguleg eftir að kóðinn er keyrður í fyrsta skipti, en fyrstu tveir punktarnir eru nóg.

Upphaflega var kóðinn búinn til í formi stórs rofa á heimilisfangi upprunalegu bækakóðans leiðbeiningarinnar, en síðan, þegar ég man eftir greininni um Emscripten, hagræðingu á mynduðu JS og relooping, ákvað ég að búa til meiri mannlegan kóða, sérstaklega þar sem það var reynslusögulega séð. kom í ljós að eini inngangsstaðurinn í þýðingarblokkina er upphaf þess. Ekki fyrr sagt en gert, eftir smá stund vorum við með kóðarafall sem bjó til kóða með ifs (að vísu án lykkja). En óheppni, það hrundi og gaf skilaboð um að leiðbeiningarnar væru af einhverri rangri lengd. Þar að auki var síðasta kennslan á þessu endurkomustigi brcond. Allt í lagi, ég bæti samskonar ávísun við gerð þessarar leiðbeiningar fyrir og eftir endurkvæma símtalið og... ekki einn þeirra var keyrður, en eftir fullyrðingarskiptin mistókust þeir samt. Að lokum, eftir að hafa rannsakað myndaða kóðann, áttaði ég mig á því að eftir skiptinguna er bendillinn á núverandi leiðbeiningar endurhlaðinn úr staflanum og er líklega skrifað yfir af myndaða JavaScript kóðanum. Og svo varð það. Það að stækka biðminni úr einu megabæti í tíu leiddi ekki til neins og ljóst varð að kóðarafallið var í hringi. Við þurftum að athuga að við færum ekki út fyrir mörk núverandi berkla og ef við gerðum það, þá gefa út heimilisfang næsta berkla með mínusmerki svo að við gætum haldið aftökunni áfram. Að auki leysir þetta vandamálið „hvaða myndaðar aðgerðir ættu að vera ógildar ef þessi bitakóði hefur breyst? — aðeins aðgerðina sem samsvarar þessum þýðingarreit þarf að ógilda. Við the vegur, þó að ég hafi villt allt í Chromium (þar sem ég nota Firefox og það er auðveldara fyrir mig að nota sérstakan vafra fyrir tilraunir), þá hjálpaði Firefox mér að leiðrétta ósamrýmanleika við asm.js staðalinn, eftir það fór kóðinn að virka hraðar í Króm.

Dæmi um myndaðan kóða

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

Ályktun

Þannig að verkinu er enn ekki lokið, en ég er þreyttur á að koma þessari langtímabyggingu í leyni. Þess vegna ákvað ég að birta það sem ég á í bili. Kóðinn er svolítið skelfilegur á stöðum vegna þess að þetta er tilraun og það er ekki fyrirfram ljóst hvað þarf að gera. Sennilega, þá er það þess virði að gefa út venjulegar atómaskuldbindingar ofan á einhverja nútímalegri útgáfu af Qemu. Í millitíðinni er þráður í Gita í bloggformi: fyrir hvert „stig“ sem hefur verið farið að minnsta kosti einhvern veginn hefur ítarlegri athugasemd á rússnesku verið bætt við. Í raun er þessi grein að miklu leyti endursögn á niðurstöðunni git log.

Þú getur prófað allt hér (varið ykkur á umferð).

Hvað er nú þegar að virka:

  • x86 sýndargjörvi í gangi
  • Það er vinnandi frumgerð af JIT kóða rafall frá vélkóða til JavaScript
  • Það er til sniðmát til að setja saman aðra 32-bita gestaarkitektúr: núna geturðu dáðst að Linux fyrir MIPS arkitektúrinn sem frýs í vafranum á hleðslustigi

Hvað annað geturðu gert

  • Flýttu eftirlíkingu. Jafnvel í JIT ham virðist það keyra hægar en Virtual x86 (en það er hugsanlega heilt Qemu með fullt af hermum vélbúnaði og arkitektúr)
  • Til að búa til eðlilegt viðmót - satt að segja er ég ekki góður vefhönnuður, svo í bili hef ég endurgert venjulegu Emscripten skelina eins og ég get
  • Reyndu að ræsa flóknari Qemu aðgerðir - netkerfi, VM flutning osfrv.
  • UPP: þú þarft að senda inn fáu þróunina þína og villuskýrslur til Emscripten andstreymis, eins og fyrri flutningsmenn Qemu og annarra verkefna gerðu. Þakka þeim fyrir að geta notað framlag þeirra til Emscripten óbeint sem hluta af verkefni mínu.

Heimild: www.habr.com

Bæta við athugasemd