Paar aastat tagasi Fabrice Bellard
- avatud lähtekoodiga
- võime töötada ilma kerneli draiverita
- võime töötada tõlgi režiimis
- toetus suurele hulgale nii host- kui ka külalisarhitektuuridele
Kolmanda punkti kohta võin nüüd seletada, et tegelikult ei tõlgendata TCI režiimis külalismasina käske endid, vaid nendest saadud baitkoodi, kuid see ei muuda olemust – et ehitada ja käivitada. Qemu uuel arhitektuuril, kui veab, piisab C-kompilaatorist – koodigeneraatori kirjutamine võib edasi lükata.
Ja nüüd, pärast kaheaastast rahulikku vabal ajal Qemu lähtekoodi kallal nokitsemist, ilmus töötav prototüüp, milles saab juba näiteks Kolibri OS-i käivitada.
Mis on Emscripten
Tänapäeval on ilmunud palju kompilaatoreid, mille lõpptulemuseks on JavaScript. Mõned, nagu Type Script, olid algselt mõeldud parimaks viisiks veebi kirjutamiseks. Samas on Emscripten võimalus võtta olemasolev C või C++ kood ja kompileerida see brauseris loetavasse vormi. Peal
Esmalt proovige
Üldiselt ei ole ma esimene inimene, kellel on idee teisaldada Qemu JavaScripti. ReactOS-i foorumis küsiti, kas see on Emscripteni abil võimalik. Juba varem levisid jutud, et Fabrice Bellard tegi seda isiklikult, kuid jutt oli jslinuxist, mis minu teada on lihtsalt katse JS-is käsitsi piisavat jõudlust saavutada ja sai kirjutatud nullist. Hiljem kirjutati Virtual x86 - selle jaoks postitati hägustamata allikad ja nagu öeldud, võimaldas emulatsiooni suurem "realism" kasutada SeaBIOS-i püsivarana. Lisaks tehti vähemalt üks katse Qemu portimiseks Emscripteni abil – proovisin seda teha
Nii et tundub, et siin on allikad, siin on Emscripten - võtke see ja koostage. Kuid on ka raamatukogusid, millest Qemu sõltub, ja raamatukogusid, millest need raamatukogud sõltuvad jne, ja üks neist on
Alguses oli mõte kirjutada lihtsalt libffi asendus JS-iga ja teha standardtestid, aga lõpuks jäi segadusse, kuidas oma päisefaile teha nii, et need olemasoleva koodiga töötaksid - mida teha? nagu öeldakse: "Kas ülesanded on nii keerulised "Kas me oleme nii lollid?" Pidin libffi nii-öelda teisele arhitektuurile portima - õnneks on Emscriptenil nii makrod inline assembly jaoks (Javascriptis jah - noh, olgu arhitektuur milline tahes, nii et assembler), kui ka võimalus käivitada käigu pealt genereeritud koodi. Üldiselt, pärast mõnda aega platvormist sõltuvate libffi fragmentidega nokitsemist, sain kompileeritava koodi ja käivitasin selle esimese katsega, mis mulle ette sattusin. Minu üllatuseks oli test edukas. Oma geniaalsusest uimastatuna – ilma naljata, see töötas esimesest käivitamisest peale – läksin ikka veel oma silmi mitte uskudes saadud koodi uuesti vaatama, et hinnata, kuhu edasi kaevata. Siin läksin ma teist korda hulluks – ainus, mida mu funktsioon tegi, oli ffi_call
- see teatas edukast kõnest. Ise kõnet ei tulnud. Seega saatsin oma esimese tõmbetaotluse, mis parandas igale olümpiaadiõpilasele arusaadava vea testis – reaalnumbreid ei tohiks võrrelda a == b
ja isegi kuidas a - b < EPS
- peate ka moodulit meeles pidama, vastasel juhul osutub 0 väga võrdseks 1/3-ga... Üldiselt mõtlesin välja kindla libffi pordi, mis läbib kõige lihtsamad testid ja millega glib on koostatud - otsustasin, et on vaja, lisan hiljem. Tulevikku vaadates ütlen, et nagu selgus, ei lisanud kompilaator lõppkoodi isegi libffi funktsiooni.
Kuid nagu ma juba ütlesin, on mõned piirangud ja erinevate määratlemata käitumiste vaba kasutamise hulka on peidetud ebameeldivam funktsioon - JavaScript by design ei toeta ühismäluga mitme lõimega töötamist. Põhimõtteliselt võib seda tavaliselt isegi heaks ideeks nimetada, kuid mitte koodi teisaldamiseks, mille arhitektuur on seotud C lõimedega. Üldiselt katsetab Firefox jagatud töötajate toetamist ja Emscriptenil on nende jaoks pthread-rakendus, kuid ma ei tahtnud sellest sõltuda. Pidin Qemu koodist aeglaselt välja juurima multithreadingu – ehk uurima, kus lõimed jooksevad, teisaldama selles lõimes jooksva tsükli keha eraldi funktsiooniks ja selliseid funktsioone ükshaaval peatsüklist välja kutsuma.
Teine proovida
Mingil hetkel sai selgeks, et probleem on endiselt alles ja juhuslikult koodi ümber karkudega ajamine ei too kaasa midagi head. Järeldus: peame kuidagi süstematiseerima karkude lisamise protsessi. Seetõttu võeti sel ajal värske versioon 2.4.1 (mitte 2.5.0, sest kes teab, siis uues versioonis on veel vigu, mida pole veel tabatud ja mul on piisavalt oma vigu ) ja esimene asi oli see ohutult ümber kirjutada thread-posix.c
. Noh, see tähendab ohutult: kui keegi üritas teha blokeerimiseni viivat toimingut, kutsuti see funktsioon kohe välja abort()
- loomulikult ei lahendanud see kõiki probleeme korraga, aga vähemalt oli see kuidagi meeldivam kui vaikselt ebaühtlaste andmete vastuvõtmine.
Üldiselt on Emscripteni valikud koodi JS-i teisaldamisel väga abiks -s ASSERTIONS=1 -s SAFE_HEAP=1
- nad tabavad teatud tüüpi määratlemata käitumist, näiteks kõnesid joondamata aadressil (mis ei ole üldse kooskõlas trükitud massiivide koodiga, näiteks HEAP32[addr >> 2] = 1
) või vale argumentide arvuga funktsiooni kutsumine.
Muide, joondusvead on omaette teema. Nagu ma juba ütlesin, on Qemul koodi genereerimise TCI (tiny code interpreter) jaoks "mandunud" interpreteeriv taustaprogramm ning Qemu ehitamiseks ja käitamiseks uuel arhitektuuril, kui teil veab, piisab C-kompilaatorist. Märksõnad "kui sul veab". Mul ei vedanud ja selgus, et TCI kasutab baitkoodi sõelumisel joondamata juurdepääsu. See tähendab, et kõikvõimalike ARM-i ja muude arhitektuuride puhul, millel on tingimata nivelleeritud juurdepääs, kompileerib Qemu, kuna neil on tavaline TCG-taustaprogramm, mis genereerib natiivset koodi, kuid kas TCI ka nende peal töötab, on teine küsimus. Kuid nagu selgus, näitas TCI dokumentatsioon selgelt midagi sarnast. Selle tulemusena lisati koodile funktsioonikutsed joondamata lugemiseks, mis avastati Qemu teises osas.
Kuhja hävitamine
Selle tulemusena parandati joondamata juurdepääs TCI-le, tekkis põhisilmus, mis omakorda kutsus protsessorit, RCU-d ja mõnda muud pisiasja. Ja nii ma käivitan Qemu valikuga -d exec,in_asm,out_asm
, mis tähendab, et peate ütlema, milliseid koodiplokke käivitatakse, ja ka edastuse ajal, et kirjutada, mis oli külaliskood, milliseks hostikoodiks sai (antud juhul baitkood). Käivitub, täidab mitu tõlkeplokki, kirjutab minu jäetud silumissõnumi, et RCU käivitub nüüd ja... jookseb kokku abort()
funktsiooni sees free()
. Funktsiooni kallal nokitsedes free()
Meil õnnestus välja selgitada, et kuhjaploki päises, mis asub eraldatud mälule eelnevas kaheksas baidis, oli ploki suuruse või muu sarnase asemel prügi.
Kuhja hävitamine - kui armas... Sellisel juhul on kasulik abinõu - (võimalusel) samadest allikatest, koostage native binary ja käivitage see Valgrindi all. Mõne aja pärast oli kahendfail valmis. Käivitan selle samade valikutega – see jookseb kokku isegi initsialiseerimise ajal, enne kui päriselt täitmiseni jõuab. See on muidugi ebameeldiv - ilmselt ei olnud allikad täpselt samad, mis pole ka üllatav, sest konfigureerimine otsis veidi erinevaid võimalusi, aga mul on Valgrind - kõigepealt parandan selle vea ja siis, kui veab , kuvatakse originaal. Ma jooksen sama asja Valgrindi all... Y-y-y, y-y-y, uh-ah, see algas, läbis algseadistuse normaalselt ja liikus algsest veast mööda, ilma et oleks ühtegi hoiatust vale mälupääsu kohta, rääkimata kukkumisest. Elu, nagu öeldakse, ei valmistanud mind selleks ette – kokkujooksev programm lakkab jooksmast, kui Walgrindi all käivitatakse. Mis see oli, on mõistatus. Minu hüpotees on, et kord praeguse käsu läheduses pärast initsialiseerimise ajal toimunud krahhi näitas gdb tööd memset
-a kehtiva kursoriga, kasutades kumbagi mmx
, või xmm
registreid, siis võib-olla oli see mingi joondusviga, kuigi seda on siiani raske uskuda.
Olgu, Valgrind siin ei aita. Ja siit algaski kõige vastikum – kõik tundub isegi algavat, kuid jookseb täiesti teadmata põhjustel kokku sündmuse tõttu, mis oleks võinud juhtuda miljoneid juhiseid tagasi. Pikka aega polnud isegi selge, kuidas läheneda. Lõpuks pidin ikka maha istuma ja siluma. Trükkides seda, millega päis ümber kirjutati, selgus, et see ei paistnud numbrina, vaid pigem mingi binaarandmetena. Ja ennäe ennäe, see binaarne string leiti BIOS-i failist – ehk siis nüüd sai mõistliku kindlusega väita, et tegemist oli puhvri ülevooluga ja on isegi selge, et see on sellesse puhvrisse kirjutatud. No siis midagi sellist - Emscriptenis pole õnneks aadressiruumi randomiseerimist, seal pole ka auke, nii et saab kirjutada kuskile koodi keskele, et viimasest käivitamisest kursori kaupa andmeid väljastada, vaadake andmeid, vaadake kursorit ja kui see pole muutunud, hankige mõtlemisainet. Tõsi, pärast muudatusi kulub linkimiseks paar minutit, kuid mida saate teha? Selle tulemusena leiti konkreetne rida, mis kopeeris BIOS-i ajutisest puhvrist külalismällu - ja tõepoolest, puhvris ei olnud piisavalt ruumi. Selle kummalise puhvri aadressi allika leidmine andis tulemuseks funktsiooni qemu_anon_ram_alloc
failis oslib-posix.c
- loogika oli järgmine: mõnikord võib olla kasulik joondada aadress suurele 2 MB suurusele lehele, selleks küsime mmap
kõigepealt natuke rohkem ja siis tagastame abiga ülejäägi munmap
. Ja kui sellist joondamist pole vaja, näitame 2 MB asemel tulemuse getpagesize()
- mmap
see annab ikka välja joondatud aadressi... Nii et Emscriptenis mmap
lihtsalt helistab malloc
, kuid loomulikult ei joondu see lehel. Üldiselt parandati viga, mis mind paar kuud frustreeris, muudatusega kaks read.
Helistamisfunktsioonide omadused
Ja nüüd loendab protsessor midagi, Qemu ei jookse kokku, kuid ekraan ei lülitu sisse ja protsessor läheb väljundi järgi otsustades kiiresti silmustesse -d exec,in_asm,out_asm
. On tekkinud hüpotees: taimeri katkestused (või üldiselt kõik katkestused) ei jõua kohale. Ja tõepoolest, kui keerate lahti nn sõlmest katkestused, mis mingil põhjusel töötasid, saate sarnase pildi. Kuid see ei olnud üldse vastus: ülaltoodud variandiga väljastatud jälgede võrdlus näitas, et hukkamisteekonnad lahknesid väga varakult. Siinkohal tuleb öelda, et kanderaketi abil salvestatu võrdlus emrun
väljundi silumine algkoostu väljundiga ei ole täiesti mehaaniline protsess. Ma ei tea täpselt, kuidas brauseris töötav programm ühenduse loob emrun
, kuid väljundis osutuvad mõned read ümber paigutatuks, seega ei ole erinevuse erinevus veel põhjust eeldada, et trajektoorid on lahknenud. Üldiselt sai selgeks, et vastavalt juhistele ljmpl
toimub üleminek erinevatele aadressidele ja genereeritav baitkood on põhimõtteliselt erinev: üks sisaldab käsku kutsuda abifunktsiooni, teine mitte. Pärast juhiste guugeldamist ja neid juhiseid tõlkiva koodi uurimist sai selgeks, et esiteks vahetult enne seda registris cr0
tehti salvestus - ka abistaja abil -, mis lülitas protsessori kaitstud režiimile ja teiseks, et js versioon ei lülitu kunagi kaitstud režiimi. Kuid tõsiasi on see, et Emscripteni teine omadus on vastumeelsus taluda sellist koodi nagu juhiste rakendamine call
TCI-s, mille mis tahes funktsiooni osuti tulemuseks on tüüp long long f(int arg0, .. int arg9)
- funktsioonid tuleb kutsuda õige arvu argumentidega. Kui seda reeglit rikutakse, siis olenevalt silumisseadetest programm kas jookseb kokku (mis on hea) või kutsub üldse välja vale funktsiooni (mida on kurb siluda). On ka kolmas võimalus - lubage argumente lisavate/eemaldavate ümbriste genereerimine, kuid kokku võtavad need ümbrised palju ruumi, hoolimata sellest, et tegelikult on mul vaja vaid veidi rohkem kui sada ümbrist. Ainuüksi see on väga kurb, kuid osutus tõsisemaks probleemiks: ümbrisfunktsioonide loodud koodis teisendati ja teisendati argumendid, kuid mõnikord ei kutsutud genereeritud argumentidega funktsiooni välja - noh, täpselt nagu minu libffi rakendus. See tähendab, et mõnda abilist lihtsalt ei hukatud.
Õnneks on Qemul masinloetavad abiliste nimekirjad päisefaili kujul nagu
DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)
Neid kasutatakse üsna naljakalt: esiteks määratletakse makrod ümber kõige veidramal viisil DEF_HELPER_n
ja seejärel lülitub sisse helper.h
. Sel määral, mil makro laiendatakse struktuuri initsialisaatoriks ja komadeks ning seejärel määratakse massiiv ja elementide asemel - #include <helper.h>
Selle tulemusena sain lõpuks võimaluse proovida raamatukogu tööl
Ja nii, pärast seda tundus protsessor töötavat. Tundub, et selle põhjuseks on asjaolu, et ekraani ei lähtestatud kunagi, kuigi memtest86+ sai käitada algkoosseisus. Siin on vaja selgitada, et Qemu ploki I/O kood on kirjutatud korutiinides. Emscriptenil on oma väga keeruline teostus, kuid seda oli siiski vaja Qemu koodis toetada ja saate nüüd protsessori siluda: Qemu toetab valikuid -kernel
, -initrd
, -append
, millega saab käivitada Linuxi või näiteks memtest86+, ilma plokkseadmeid kasutamata. Kuid siin on probleem: algkoostis võis näha Linuxi kerneli väljundit konsooli valikuga -nographic
ja brauserist pole väljundit terminali, kust see käivitati emrun
, ei tulnud. See tähendab, et pole selge: protsessor ei tööta või graafikaväljund ei tööta. Ja siis tuli mulle pähe, et ootaks veidi. Selgus, et "protsessor ei maga, vaid lihtsalt vilgub aeglaselt" ja umbes viie minuti pärast viskas kernel konsooli hunniku sõnumeid ja jätkas rippumist. Sai selgeks, et protsessor üldiselt töötab ja peame SDL2-ga töötamiseks koodi süvenema. Kahjuks ma ei tea, kuidas seda raamatukogu kasutada, nii et mõnes kohas pidin tegutsema juhuslikult. Mingil hetkel vilkus ekraanil sinisel taustal paralleel0 joon, mis pakkus mõtteid. Lõpuks selgus, et probleem seisnes selles, et Qemu avab ühes füüsilises aknas mitu virtuaalset akent, mille vahel saab Ctrl-Alt-n abil lülituda: native buildis töötab, aga Emscriptenis mitte. Pärast tarbetutest akendest vabanemist valikute abil -monitor none -parallel none -serial none
ja juhiseid kogu ekraani iga kaadri jõuga ümber joonistamiseks, kõik äkki töötas.
Korutiinid
Niisiis, emuleerimine brauseris töötab, kuid te ei saa selles käivitada midagi huvitavat ühes disketis, kuna pole ploki I/O-d - peate juurutama korutiinide toe. Qemul on juba mitu korutiini taustaprogrammi, kuid JavaScripti ja Emscripteni koodigeneraatori olemuse tõttu ei saa te lihtsalt virnadega žongleerima hakata. Näib, et "kõik on kadunud, krohv eemaldatakse", kuid Emscripteni arendajad on juba kõige eest hoolitsenud. See on üsna naljakas: nimetame sellist funktsioonikutset kahtlaseks emscripten_sleep
ja mitmed teised, mis kasutavad Asyncify mehhanismi, aga ka kursorikutsed ja kõned mis tahes funktsioonidele, kus üks kahest eelmisest juhtumist võib esineda pinu allpool. Ja nüüd, enne iga kahtlast kõnet, valime asünkroonse konteksti ja kohe pärast kõnet kontrollime, kas asünkroonne kõne on toimunud ja kui on, siis salvestame kõik kohalikud muutujad selles asünkroonses kontekstis, näitame, milline funktsioon et anda juhtimine üle, kui peame täitmist jätkama, ja väljuda praegusest funktsioonist. Siin on ruumi mõju uurimiseks -O3
. Käivitan loodud koodi ja Chromium sööb mälu ära ja jookseb kokku. Vaatasin siis kogemata, mida ta üritas alla laadida... No mis ma oskan öelda, ma oleksin ka tardunud, kui mul oleks palutud mõtlikult uurida ja optimeerida 500+ MB Javascripti.
Kahjuks ei olnud Asyncify tugiteegi koodi kontrollid täiesti sõbralikud longjmp
-s, mida kasutatakse virtuaalse protsessori koodis, kuid pärast väikest paika, mis keelab need kontrollid ja taastab jõuliselt kontekstid, nagu kõik oleks korras, kood töötas. Ja siis algas kummaline asi: mõnikord käivitati sünkroonimiskoodi kontrollid - samad, mis jooksevad koodi kokku, kui täitmisloogika kohaselt peaks see olema blokeeritud - keegi üritas haarata juba püütud mutexi. Õnneks ei osutunud see serialiseeritud koodis loogiliseks probleemiks – kasutasin lihtsalt Emscripteni pakutavat standardset põhisilmuse funktsionaalsust, kuid mõnikord keeras asünkroonne kõne pinu täielikult lahti ja sel hetkel see ebaõnnestus. setTimeout
põhitsüklist - seega sisenes kood põhitsükli iteratsiooni eelmisest iteratsioonist lahkumata. Kirjutas ümber lõpmatul tsüklil ja emscripten_sleep
ja mutexidega seotud probleemid lõppesid. Kood on muutunud isegi loogilisemaks - mul pole ju tegelikult koodi, mis järgmise animatsioonikaadri ette valmistaks - protsessor lihtsalt arvutab midagi ja ekraani värskendatakse perioodiliselt. Probleemid aga sellega ei piirdunud: mõnikord lõppes Qemu täitmine lihtsalt vaikselt ilma erandite ja vigadeta. Sel hetkel loobusin sellest, kuid tulevikku vaadates ütlen, et probleem oli järgmine: korutiinikood tegelikult ei kasuta setTimeout
(või vähemalt mitte nii sageli, kui arvate): funktsioon emscripten_yield
seab lihtsalt asünkroonse kõne lipu. Kogu point on selles emscripten_coroutine_next
ei ole asünkroonne funktsioon: see kontrollib sisemiselt lippu, lähtestab selle ja edastab juhtimise sinna, kus seda vaja on. See tähendab, et stäki reklaamimine lõpeb sellega. Probleem seisnes selles, et kasutuse pärast vaba kasutamise tõttu, mis ilmnes korutiinikogumi väljalülitamisel, kuna ma ei kopeerinud olemasolevast korutiini taustaprogrammist olulist koodirida, funktsioon qemu_in_coroutine
tagastas tõene, kuigi tegelikult oleks pidanud tagastama vale. See viis kõneni emscripten_yield
, mille kohal ei olnud virna peal kedagi emscripten_coroutine_next
, pakk läks lahti päris ülaossa, aga ei setTimeout
, nagu ma juba ütlesin, ei eksponeeritud.
JavaScripti koodi genereerimine
Ja siin on tegelikult lubatud "hakkliha tagasipööramine". Mitte päris. Muidugi, kui käivitame brauseris Qemu ja selles Node.js, siis loomulikult saame pärast Qemus koodi genereerimist täiesti vale JavaScripti. Aga ikkagi, mingi pöördtransformatsioon.
Esiteks natuke sellest, kuidas Qemu töötab. Palun andke mulle kohe andeks: ma ei ole professionaalne Qemu arendaja ja minu järeldused võivad kohati olla ekslikud. Nagu öeldakse, "õpilase arvamus ei pea ühtima õpetaja arvamusega, Peano aksiomaatika ja terve mõistusega." Qemul on teatud arv toetatud külalisarhitektuure ja igaühe jaoks on kataloog nagu target-i386
. Ehitamisel saate määrata mitme külalisarhitektuuri toe, kuid tulemuseks on lihtsalt mitu kahendfaili. Külalisarhitektuuri toetav kood genereerib omakorda mõned sisemised Qemu toimingud, mille TCG (Tiny Code Generator) muudab juba hostarhitektuuri masinkoodiks. Nagu kataloogis tcg asuvas readme-failis öeldud, oli see algselt osa tavalisest C-kompilaatorist, mida hiljem kohandati JIT-i jaoks. Seetõttu ei ole näiteks selle dokumendi mõistes sihtarhitektuur enam külalisarhitektuur, vaid hostarhitektuur. Mingil hetkel ilmus veel üks komponent - Tiny Code Interpreter (TCI), mis peaks konkreetse hostarhitektuuri jaoks koodigeneraatori puudumisel käivitama koodi (peaaegu samad sisetoimingud). Tegelikult, nagu selle dokumentatsioon ütleb, ei pruugi see tõlk alati nii hästi toimida kui JIT-koodigeneraator, mitte ainult kvantitatiivselt kiiruse, vaid ka kvalitatiivselt. Kuigi ma pole kindel, et tema kirjeldus on täiesti asjakohane.
Alguses proovisin teha täisväärtuslikku TCG taustaprogrammi, kuid sattusin kiiresti segadusse lähtekoodi ja baitkoodi juhiste ebaselge kirjeldusega, nii et otsustasin TCI tõlgi pakkida. See andis mitmeid eeliseid:
- koodigeneraatori realiseerimisel võiks vaadata mitte juhiste kirjeldust, vaid interpretaatori koodi
- Funktsioone saab genereerida mitte iga kohatud tõlkeploki jaoks, vaid näiteks alles pärast sajandat täitmist
- kui genereeritud kood muutub (ja see tundub olevat võimalik, otsustades funktsioonide järgi, mille nimed sisaldavad sõna patch), pean genereeritud JS-koodi kehtetuks tunnistama, kuid mul on vähemalt midagi, millest see uuesti luua.
Mis puutub kolmandasse punkti, siis ma pole kindel, et pärast koodi esmakordset käivitamist on lappimine võimalik, kuid esimesest kahest punktist piisab.
Algselt genereeriti kood suure lüliti kujul algse baitkoodi käsu aadressil, kuid siis, meenutades artiklit Emscripteni, genereeritud JS-i optimeerimise ja uuesti loopimise kohta, otsustasin genereerida rohkem inimkoodi, eriti kuna empiiriliselt selgus, et tõlkeploki ainus sisenemispunkt on selle algus. Varsti öeldud, kui tehtud, mõne aja pärast oli meil koodigeneraator, mis genereeris koodi if-idega (kuigi ilma silmusteta). Kuid halb õnn, see jooksis kokku, andes teate, et juhised on ebaõige pikkusega. Pealegi oli viimane juhend sellel rekursioonitasemel brcond
. Olgu, ma lisan selle juhise genereerimisele identse kontrolli enne ja pärast rekursiivset kõnet ja... ühtegi neist ei käivitatud, kuid pärast kinnituslülitit need ikkagi ebaõnnestusid. Lõpuks, pärast genereeritud koodi uurimist, sain aru, et pärast ümberlülitamist laetakse pinust uuesti jooksva käsu osuti ja genereeritud JavaScripti kood kirjutab tõenäoliselt üle. Ja nii see välja tuli. Puhvri suurendamine ühelt megabaidilt kümnele ei toonud kaasa midagi ja selgus, et koodigeneraator töötab ringi. Pidime kontrollima, et me ei välju praeguse TB piiridest ja kui läksime, siis väljastama järgmise TB aadressi miinusmärgiga, et saaksime täitmist jätkata. Lisaks lahendab see probleemi "millised loodud funktsioonid tuleks kehtetuks tunnistada, kui see baitkooditükk on muutunud?" — ainult sellele tõlkeplokile vastav funktsioon tuleb tühistada. Muide, kuigi silusin kõike Chromiumis (kuna ma kasutan Firefoxi ja mul on lihtsam katseteks kasutada eraldi brauserit), aitas Firefox mul parandada asm.js standardiga vastuolusid, misjärel hakkas kood aastal kiiremini töötama. Kroom.
Näide genereeritud koodist
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"]
Järeldus
Nii et töö pole ikka veel lõpetatud, aga ma olen väsinud selle pikaajalise ehituse salaja täiuseni viimisest. Seetõttu otsustasin avaldada selle, mis mul praegu on. Kood on kohati pisut hirmutav, sest see on eksperiment ja pole eelnevalt selge, mida teha tuleb. Tõenäoliselt tasub siis Qemu mõne moodsama versiooni peale väljastada tavalised atomaarsed kohustused. Gitas on vahepeal blogiformaadis lõim: iga vähemalt kuidagi läbitud “taseme” kohta on lisatud üksikasjalik venekeelne kommentaar. Tegelikult on see artikkel suures osas järelduse ümberjutustus git log
.
Saate seda kõike proovida
Mis juba töötab:
- x86 virtuaalne protsessor töötab
- On olemas JIT-koodigeneraatori töötav prototüüp masinkoodist JavaScriptini
- Muude 32-bitiste külalisarhitektuuride kokkupanemiseks on mall: praegu saate imetleda Linuxi MIPS-arhitektuuri, mis brauseris laadimisetapis hangub.
Mida sa muud saad teha
- Emuleerimise kiirendamine. Tundub, et isegi JIT-režiimis töötab see aeglasemalt kui Virtual x86 (kuid potentsiaalselt on olemas terve Qemu, millel on palju emuleeritud riistvara ja arhitektuuri)
- Tavalise liidese loomiseks – ausalt öeldes ei ole ma hea veebiarendaja, nii et praegu olen standardse Emscripteni kesta nii hästi kui võimalik ümber teinud
- Proovige käivitada keerukamaid Qemu funktsioone - võrgundus, VM-i migratsioon jne.
- UPD: peate esitama oma mõned arendused ja veateated Emscriptenile ülesvoolu, nagu tegid Qemu ja teiste projektide varasemad kandjad. Tänan neid selle eest, et nad said kaudselt kasutada oma panust Emscripteni osana minu ülesandest.
Allikas: www.habr.com