Qemu.js JIT euskarria duena: oraindik bira atzerantz biratu dezakezu

Duela urte batzuk Fabrice Bellard jslinux-ek idatzia JavaScript-en idatzitako ordenagailuko emuladorea da. Horren ondoren, gutxienez gehiago izan zen x86 birtuala. Baina denak, nik dakidala, interpreteak ziren, eta Qemu, Fabrice Bellard berak askoz lehenago idatzitakoa, eta, ziurrenik, edozein emulatzaile moderno errespetatzen duena, JIT sistema gonbidatuaren kodearen konpilazioa erabiltzen du sistema ostalariaren kodean. Nabigatzaileek konpontzen dutenaren aldean kontrako zeregina ezartzeko garaia zela iruditu zitzaidan: JIT makina kodea JavaScript-era konpilatzea, horretarako logikoena iruditu zitzaidan Qemu porturatzea. Badirudi, zergatik Qemu, emuladore sinpleagoak eta erabilerrazagoak daudela - VirtualBox bera, adibidez - instalatuta eta funtzionatzen du. Baina Qemu-k hainbat ezaugarri interesgarri ditu

  • kode irekia
  • nukleoko kontrolatzailerik gabe lan egiteko gaitasuna
  • interprete moduan lan egiteko gaitasuna
  • ostalari zein gonbidatu arkitektura ugarientzako laguntza

Hirugarren puntuari dagokionez, orain azaldu dezaket, hain zuzen ere, TCI moduan, ez direla makina gonbidatuaren instrukzioak beraiek interpretatzen direnak, haietatik lortutako bytecodea baizik, baina horrek ez duela funtsa aldatzen - eraiki eta exekutatu ahal izateko. Qemu arkitektura berri batean, zortea baduzu, C konpilatzailea nahikoa da - kode-sorgailu bat idaztea atzeratu daiteke.

Eta orain, nire denbora librean Qemu iturburu-kodea patxadaz moldatzen bi urteren ondoren, lan-prototipo bat agertu zen, eta bertan dagoeneko exekutatu dezakezun, adibidez, Kolibri OS.

Zer da Emscripten

Gaur egun, konpilatzaile asko agertu dira, eta horien azken emaitza JavaScript da. Batzuk, Type Script bezalakoak, hasieran sarerako idazteko modurik onena izan nahi zuten. Aldi berean, Emscripten lehendik dagoen C edo C++ kodea hartu eta arakatzailean irakur daitekeen forma batean biltzeko modu bat da. On Orri honetan Programa ezagunen ataka asko bildu ditugu: HemenAdibidez, PyPy-ra begiratu dezakezu - bide batez, dagoeneko JIT dutela diote. Izan ere, programa guztiak ezin dira besterik gabe konpilatu eta arakatzaile batean exekutatu - zenbaki bat daude Ezaugarriak, jarri behar duzuna, ordea, orrialde bereko inskripzioak dioen bezala β€œEmscripten erabil daiteke ia edozein biltzeko eramangarriak C/C++ kodea JavaScript-era". Hau da, estandarraren arabera definitu gabeko portaera duten hainbat eragiketa daude, baina normalean x86-n funtzionatzen dute; adibidez, aldagaietarako lerrokatu gabeko sarbidea, arkitektura batzuetan normalean debekatuta dagoena. Oro har. , Qemu plataforma anitzeko programa bat da eta , sinetsi nahi nuen, eta ez dauka dagoeneko definitu gabeko portaera asko - hartu eta konpilatu, gero txikitu pixka bat JITrekin - eta listo! Baina hori ez da kasua...

Lehenengo proba

Oro har, ez naiz Qemu JavaScript-era eramateko ideia bururatu zitzaion lehen pertsona. ReactOS foroan galdera bat egin zen Emscripten erabiliz hori posible zen. Lehenago ere, zurrumurruak zeuden Fabrice Bellardek hori pertsonalki egin zuela, baina jslinux-i buruz ari ginen, nik dakidala, JS-en eskuz errendimendu nahikoa lortzeko saiakera bat besterik ez da, eta hutsetik idatzi zen. Geroago, Virtual x86 idatzi zen - lausotu gabeko iturriak argitaratu ziren, eta, esan bezala, emulazioaren "errealismo" handiagoak SeaBIOS firmware gisa erabiltzea ahalbidetu zuen. Horrez gain, gutxienez saiakera bat egon zen Emscripten erabiliz Qemu porturatzeko - hau egiten saiatu nintzen socket-pare, baina garapena, nik ulertzen dudanez, izoztuta zegoen.

Beraz, badirudi, hona hemen iturriak, hona hemen Emscripten - hartu eta konpilatu. Baina Qemuren mende dauden liburutegiak ere badaude, eta liburutegi horien mende dauden liburutegiak, etab., eta horietako bat da. libffi, zein glib-en araberakoa den. Interneten zurrumurruak zeuden Emscripten liburutegien portuen bilduma handian bat bazegoela, baina nolabait zaila zen sinestea: lehenik eta behin, ez zen konpilatzaile berri bat izan nahi, bigarrenik, maila baxuegia zen. liburutegia jaso eta JS-ra konpilatu. Eta ez da muntaia-txertatzeen kontua soilik - ziurrenik, bihurrituz gero, dei-konbentzio batzuetarako pilan beharrezko argumentuak sor ditzakezu eta horiek gabe funtzioari dei dezakezu. Baina Emscripten gauza delikatua da: sortutako kodea arakatzailearen JS motor optimizatzaileari ezaguna izan dadin, trikimailu batzuk erabiltzen dira. Bereziki, birlooping deiturikoak - jasotako LLVM IR-a erabiltzen duen kode-sorgailuak trantsizio-agindu abstraktu batzuekin ifs, begiztak eta abar sinesgarriak birsortzen saiatzen da. Beno, nola pasatzen dira argumentuak funtziora? Jakina, JS funtzioen argumentu gisa, hau da, ahal bada, ez pila bidez.

Hasieran libffi-ren ordezko bat JS-rekin idazteko eta proba estandarrak exekutatzeko ideia bat zegoen, baina azkenean nahastu nintzen nire goiburuko fitxategiak nola egin lehendik dagoen kodearekin funtzionatzeko - zer egin dezaket, esaten duten moduan, "Hain konplexuak al dira zereginak "Hain ergelak al gara?" Libffi beste arkitektura batera eraman behar izan nuen, nolabait esateko - zorionez, Emscriptenek lineako muntaia egiteko makroak ditu (Javascript-en, bai - tira, edozein arkitektura, beraz, mihiztatzailea), eta hegan sortutako kodea exekutatzeko gaitasuna. Orokorrean, plataformaren menpeko libffi zatiekin denbora pixka bat egin ondoren, kode konpilagarri bat lortu nuen eta topatu nuen lehen proban exekutatu nuen. Nire harridurarako, proba arrakastatsua izan zen. Nire jenioarekin txundituta - txantxarik ez, lehen abian jarri zenetik funtzionatu zuen -, oraindik begiei sinistu gabe, lortutako kodea berriro ikustera joan nintzen, hurrengo non zulatu ebaluatzera. Hemen bigarren aldiz txoratu nintzen - nire funtzioak egin zuen gauza bakarra zen ffi_call - honek dei arrakastatsua izan duela jakinarazi du. Ez zegoen berez deirik. Beraz, nire lehen tira-eskaera bidali nuen, edozein Olinpiadetako ikasleentzat argi dagoen proban errore bat zuzendu zuen - zenbaki errealak ez dira alderatu behar. a == b eta baita nola a - b < EPS - modulua ere gogoratu behar duzu, bestela 0 1/3-ren oso berdina izango da... Orokorrean, libffi-ren ataka jakin bat asmatu dut, proba errazenak gainditzen dituena eta glib-a dena. bilduta - beharrezkoa izango zela erabaki nuen, geroago gehituko dut. Aurrera begira, esango dut, ondorioz, konpilatzaileak ez zuela libffi funtzioa azken kodean sartu ere egin.

Baina, lehen esan dudan bezala, muga batzuk daude, eta definitu gabeko hainbat jokabideren erabilera librearen artean, ezaugarri desatseginago bat ezkutatu da - JavaScriptek diseinuz ez du onartzen memoria partekatuarekin multithreading. Printzipioz, normalean ideia ona ere dei daiteke, baina ez bere arkitektura C hariekin lotuta dagoen kodea eramateko. Oro har, Firefox partekatutako langileak onartzen ari da esperimentatzen, eta Emscripten-ek pthread inplementazio bat dauka haientzat, baina ez nuen horren menpe egon nahi. Poliki-poliki Qemu kodeatik multithreading errotik atera behar izan nuen, hau da, hariak non exekutatzen ari diren aurkitu, hari honetan exekutatzen den begiztaren gorputza funtzio bereizi batera eraman eta funtzio horiei banan-banan deitu begizta nagusitik.

Bigarren proba

Noizbait, argi geratu zen arazoa oraindik hor zegoela, eta makuluak kodearen inguruan kasualitatez sakatzeak ez zuela onik ekarriko. Ondorioa: makuluak gehitzeko prozesua nolabait sistematizatu behar dugu. Horregatik, garai hartan freskoa zegoen 2.4.1 bertsioa hartu zen (ez 2.5.0, nork daki, bertsio berrian oraindik harrapatu ez diren akatsak egongo direlako, eta nire akatsekin nahikoa daukat. ), eta lehenengo gauza segurtasunez berridaztea izan zen thread-posix.c. Tira, hau da, seguru bezain seguru: norbait blokeatzeko eragiketa bat egiten saiatzen bazen, funtzioari berehala deitzen zitzaion abort() - noski, honek ez zituen arazo guztiak aldi berean konpondu, baina behintzat nolabait atseginagoa izan zen datu koherenteak lasai jasotzea baino.

Oro har, Emscripten aukerak oso lagungarriak dira kodea JSra eramateko -s ASSERTIONS=1 -s SAFE_HEAP=1 - Definitu gabeko portaera mota batzuk atzematen dituzte, hala nola lerrokatu gabeko helbide baterako deiak (esaterako idatzitako matrizeen kodearekin bat ez datoz bat HEAP32[addr >> 2] = 1) edo argumentu kopuru okerra duen funtzio bati deitzea.

Bide batez, lerrokatze akatsak aparteko arazo bat dira. Lehen esan dudan bezala, Qemu-k interpretazio backend "endekatua" du TCI (kode-interprete txikia) sortzeko, eta Qemu arkitektura berri batean eraiki eta exekutatzeko, zortea baduzu, nahikoa da C konpilatzailea. "zortea baduzu". Zorte txarra izan nuen, eta TCI-k lerrokatu gabeko sarbidea erabiltzen duela bere byte-kodea analizatzerakoan. Hau da, nahitaez berdindutako sarbidea duten ARM eta beste arkitektura guztietan, Qemu-k konpilatzen du jatorrizko kodea sortzen duen TCG backend normal bat duelako, baina TCIk horietan funtzionatuko duen ala ez beste galdera bat da. Hala ere, ondorioztatu zenez, TCIren dokumentazioak argi eta garbi adierazten zuen antzeko zerbait. Ondorioz, lerrokatu gabeko irakurketa egiteko funtzio-deiak gehitu ziren kodeari, Qemu-ren beste zati batean aurkitu zirenak.

Heap suntsitzea

Ondorioz, lerrokatu gabeko TCIrako sarbidea zuzendu zen, prozesadorea, RCU eta beste gauza txiki batzuk deitzen zituen begizta nagusi bat sortu zen. Eta, beraz, Qemu abiarazten dut aukerarekin -d exec,in_asm,out_asm, horrek esan nahi du zein kode-bloke exekutatzen ari diren esan behar duzula, eta baita igorpen unean gonbidatutako kodea zer zen, zer ostalari-kode bihurtu den (kasu honetan, bytecode) idazteko. Abiatzen da, hainbat itzulpen bloke exekutatzen ditu, utzi dudan arazketa-mezua idazten du orain RCU abiaraziko dela eta... huts egiten du. abort() funtzio baten barruan free(). Funtzioa moldatuz free() Esleitutako memoriaren aurreko zortzi byteetan dagoen heap blokearen goiburuan blokearen tamainaren edo antzeko zerbaiten ordez zaborra zegoela jakitea lortu genuen.

Piloaren suntsipena - zein polita... Halakoetan, erremedio erabilgarria dago - iturri beretik (ahal bada), muntatu jatorrizko bitar bat eta exekutatu Valgrind-en pean. Denbora pixka bat igaro ondoren, bitarra prest zegoen. Aukera berberekin abiarazten dut - hastapenean ere huts egiten du, exekuziora iritsi baino lehen. Desatsegina da, noski - itxuraz, iturriak ez ziren guztiz berdinak, eta hori ez da harritzekoa, konfigurazioak aukera apur bat desberdinak aurkitu zituelako, baina Valgrind dut - lehenik akats hau konponduko dut, eta gero, zortea badut. , jatorrizkoa agertuko da. Gauza bera exekutatzen ari naiz Valgrind-en... Y-y-y, y-y-y, uh-uh, hasi zen, hasieratik normaltasunez igaro zen eta jatorrizko akatsetik aurrera joan zen memoria okerren sarbideari buruzko abisurik gabe, erorketei buruz ahaztu gabe. Bizitzak, esaten den bezala, ez ninduen horretarako prestatu - hutsegite programa batek huts egiteari uzten dio Walgrind-en abiarazitakoan. Zer izan zen misterio bat da. Nire hipotesia da behin uneko instrukzioaren inguruan hasieratzean huts egin ostean, gdb-k lana erakutsi zuela memset-a balizko erakusle batekin bia erabiliz mmx, edo xmm erregistroak, orduan agian lerrokatze-erroreren bat izan zen, nahiz eta oraindik zaila den sinestea.

Ados, Valgrindek ez dirudi hemen laguntzen duenik. Eta hemen hasi zen gauza nazkagarriena: dena hasi dela dirudi, baina arrazoi guztiz ezezagunengatik kraskatzen da duela milioika argibide gerta zitekeen gertaera baten ondorioz. Aspaldian, ez zegoen argi nola hurbildu ere. Azkenean, oraindik eseri eta arazketa egin behar nuen. Goiburua berridatzi denarekin inprimatzeak ez zuela zenbaki baten itxura erakutsi zuen, datu bitarren bat baizik. Eta, hara, kate bitar hau BIOS fitxategian aurkitu zen - hau da, orain zentzuzko konfiantzaz esan daiteke buffer gainezka zela, eta argi dago buffer honetan idatzi zela. Beno, orduan horrelako zerbait - Emscripten-en, zorionez, ez dago helbide-espazioaren ausazko banaketarik, ez dago zulorik ere, beraz, kodearen erdian nonbait idatzi dezakezu azken abiaraztetik erakuslearen datuak ateratzeko, begiratu datuak, begiratu erakuslea, eta, aldatu ez bada, pentsatzeko. Egia da, minutu pare bat behar izaten dira edozein aldaketaren ondoren lotzeko, baina zer egin dezakezu? Ondorioz, lerro zehatz bat aurkitu zen BIOSa behin-behineko bufferretik gonbidatutako memoriara kopiatzen zuena, eta, hain zuzen ere, ez zegoen leku nahikorik bufferean. Buffer helbide bitxi horren iturria aurkitzeak funtzio bat sortu zuen qemu_anon_ram_alloc fitxategian oslib-posix.c - Logika hau zen: batzuetan erabilgarria izan daiteke helbidea 2 MB-ko tamainako orrialde handi batera lerrokatzea, horretarako eskatuko dugu mmap lehenik pixka bat gehiago, eta gero laguntzarekin itzuliko dugu soberakina munmap. Eta lerrokatzea beharrezkoa ez bada, emaitza adieraziko dugu 2 MBren ordez getpagesize() - mmap oraindik lerrokaturiko helbidea emango du... Beraz, Emscripten-en mmap deiak besterik ez malloc, baina noski ez da orrialdean lerrokatzen. Orokorrean, hilabete pare batez zapuztu ninduen akats bat aldatu egin zen bi lerroak.

Deitzeko funtzioen ezaugarriak

Eta orain prozesadorea zerbait kontatzen ari da, Qemu ez da huts egiten, baina pantaila ez da pizten, eta prozesadorea azkar sartzen da begiztak, irteeraren arabera. -d exec,in_asm,out_asm. Hipotesi bat sortu da: tenporizadorearen etenaldiak (edo, oro har, eten guztiak) ez dira iristen. Eta, hain zuzen ere, jatorrizko asanbladako etenak askatzen badituzu, arrazoiren batengatik funtzionatu zuena, antzeko irudia lortuko duzu. Baina hori ez zen batere erantzuna: goiko aukerarekin igorritako aztarnak alderatuz gero, exekuzio ibilbideak oso goiz aldendu zirela erakutsi zuen. Hemen esan behar da abiarazlea erabiliz grabatutakoaren konparaketa hori emrun jatorrizko muntaketaren irteerarekin irteera araztea ez da prozesu guztiz mekanikoa. Ez dakit zehazki nola konektatzen den arakatzaile batean exekutatzen den programa bat emrun, baina irteerako lerro batzuk berrantolatu egiten dira, beraz, desberdintasunaren aldea oraindik ez da ibilbideak aldendu egin direla pentsatzeko arrazoia. Oro har, argi geratu zen argibideen arabera ljmpl helbide ezberdinetarako trantsizioa dago, eta sortutako bytecodea funtsean ezberdina da: batek laguntzaile funtzio bati deitzeko instrukzioa dauka, besteak ez. Argibideak googlen jarri eta argibide hauek itzultzen dituen kodea aztertu ondoren, argi geratu zen, lehenik eta behin, berehala erregistroan. cr0 grabaketa bat egin zen - laguntzaile bat ere erabiliz - prozesadorea modu babestu batera aldatu zuena, eta, bigarrenik, js bertsioa ez zela inoiz modu babestura aldatu. Baina kontua da Emscripten-en beste ezaugarri bat argibideak ezartzea bezalako kodea jasateko errezeloa dela call TCIn, zeina edozein funtzio-erakusleak motan long long f(int arg0, .. int arg9) - funtzioei argumentu kopuru egokiarekin deitu behar zaie. Arau hau urratzen bada, arazketa-ezarpenen arabera, programak huts egingo du (ona da) edo funtzio okerrera deituko du (araztea tristea izango da). Hirugarren aukera bat ere badago: argumentuak gehitzen / kentzen dituzten bilgarrien sorkuntza gaitu, baina guztira bilgarri hauek leku asko hartzen dute, egia esan ehun bilgarri baino apur bat baino gehiago behar ditudan arren. Hau bakarrik oso tristea da, baina arazo larriagoa izan da: bilgarri-funtzioen sortutako kodean, argumentuak bihurtu eta bihurtu ziren, baina batzuetan sortutako argumentuekin funtzioari ez zitzaion deitzen - tira, hemen bezala. nire libffi inplementazioa. Hau da, laguntzaile batzuk ez ziren besterik gabe exekutatu.

Zorionez, Qemu-k makinaz irakur daitezkeen laguntzaileen zerrenda ditu goiburuko fitxategi moduan

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

Nahiko barregarriak erabiltzen dira: lehenik eta behin, makroak modu bitxienean birdefinitzen dira DEF_HELPER_n, eta gero pizten da helper.h. Makroa egitura hasierako eta koma batera zabaltzen den neurrian, eta gero array bat definitzen da, eta elementuen ordez - #include <helper.h> Ondorioz, azkenean liburutegia lanean probatzeko aukera izan nuen pyparsing, eta behar diren funtzioetarako zehatz-mehatz bilgarri horiek sortzen dituen script bat idatzi zen.

Eta, horren ondoren, prozesadoreak funtzionatzen zuela zirudien. Pantaila inoiz hasieratu ez zelako dirudi, nahiz eta memtest86+ jatorrizko muntaian exekutatu gai izan. Hemen argitu behar da Qemu bloke I/O kodea koroutinetan idatzita dagoela. Emscripten-ek bere inplementazio oso zaila du, baina oraindik Qemu kodean onartu behar zuen, eta orain prozesadorea arazketa dezakezu: Qemu-k aukerak onartzen ditu. -kernel, -initrd, -append, zeinekin Linux edo, adibidez, memtest86+ abiarazi dezakezu, bloke-gailurik erabili gabe. Baina hona hemen arazoa: jatorrizko muntaian Linux kernelaren irteera ikus zitekeen kontsolarako aukerarekin -nographic, eta ez dago arakatzailearen irteerarik abiarazi den terminalera emrun, ez zen etorri. Hau da, ez dago argi: prozesadorea ez dabil edo irteera grafikoa ez dabil. Eta orduan bururatu zitzaidan pixka bat itxarotea. "Prozesadorea ez dagoela lotan, poliki-poliki keinu egiten ari da" eta bost minuturen buruan nukleoak mezu mordo bat bota zituen kontsolara eta zintzilik jarraitu zuen. Argi geratu zen prozesadoreak, oro har, funtzionatzen duela, eta SDL2rekin lan egiteko kodean sakondu behar dugu. Zoritxarrez, ez dakit nola erabili liburutegi hau, beraz, leku batzuetan ausaz jokatu behar izan nuen. Noizbait, paralelo0 lerroa keinu egin zen pantailan hondo urdin baten gainean, eta horrek pentsamendu batzuk iradoki zituen. Azkenean, arazoa Qemu-k hainbat leiho birtual irekitzen dituela leiho fisiko batean, eta horien artean Ctrl-Alt-n erabiliz alda dezakezu: jatorrizko eraikuntzan funtzionatzen du, baina ez Emscripten-en. Aukerak erabiliz beharrezkoak ez diren leihoak kendu ondoren -monitor none -parallel none -serial none eta fotograma bakoitzean pantaila osoa indarrez berriro marrazteko argibideak, dena funtzionatu zuen bat-batean.

Koroutinak

Beraz, arakatzailean emulazioa funtzionatzen du, baina ezin duzu diskete bakarreko ezer interesgarririk exekutatu, ez baitago bloke I/Orik - koroutinetarako laguntza ezarri behar duzu. Qemu-k dagoeneko hainbat korutine backend ditu, baina JavaScript eta Emscripten kode-sorgailuaren izaera dela eta, ezin zara malabareak egiten hasi. Badirudi Β«dena desagertu dela, igeltsua kentzen ari direlaΒ», baina Emscripteneko garatzaileek dena arduratu dute dagoeneko. Hau nahiko dibertigarria inplementatzen da: dei diezaiogun susmagarri hau bezalako funtzio-dei bati emscripten_sleep eta beste hainbat Asyncify mekanismoa erabiliz, baita erakusle-deiak eta aurreko bi kasuetako bat pilatik beherago gerta daitekeen edozein funtziorako deiak ere. Eta orain, dei susmagarri bakoitzaren aurretik, testuinguru asinkrono bat hautatuko dugu, eta deia egin eta berehala, dei asinkronorik gertatu den egiaztatuko dugu, eta hala bada, aldagai lokal guztiak gordeko ditugu testuinguru asinkrono honetan, zein funtzio adierazi. kontrola exekutatzen jarraitu behar dugunean transferitzeko eta uneko funtziotik irteteko. Hor dago eragina aztertzeko aukera xahutzea β€” Dei asinkrono batetik itzuli ondoren kodea exekutatzen jarraitzeko beharretarako, konpilatzaileak dei susmagarri baten ondoren hasten den funtzioaren "stubs" sortzen ditu - honela: n dei susmagarri badaude, funtzioa nonbait zabalduko da n/2 aldiz β€” hau da, hala ere, kontuan izan asinkronoa izan daitekeen dei bakoitzaren ondoren, jatorrizko funtzioari aldagai lokal batzuk gordetzea gehitu behar diozula. Ondoren, Python-en script soil bat ere idatzi behar izan nuen, ustez "asinkronia berez pasatzen uzten ez duten" funtzio multzo jakin batean oinarrituta (hau da, pila-sustapena eta deskribatu berri dudan guztia ez dute). haietan lan egiten du), erakusleen bidez deiak adierazten ditu zeinetan funtzioei ez ikusi egin behar dien konpilatzaileak, funtzio horiek asinkronotzat har ez daitezen. Eta gero, 60 MB baino gutxiagoko JS fitxategiak gehiegi dira - demagun gutxienez 30. Nahiz eta, behin muntaia script bat konfiguratzen ari nintzen, eta ustekabean estekatzaileen aukerak bota nituen, horien artean zegoen. -O3. Sortutako kodea exekutatzen dut, eta Chromium-ek memoria jaten du eta huts egiten du. Orduan ustekabean begiratu nuen zer deskargatzen saiatzen ari zen... Tira, zer esango nuke, ni ere izoztu egingo nintzateke 500 MB baino gehiagoko Javascript bat ongi aztertu eta optimizatzeko eskatu izan banu.

Zoritxarrez, Asyncify laguntza-liburutegiaren kodearen egiaztapenak ez ziren guztiz adiskidetsuak izan longjmpProzesadore birtualeko kodean erabiltzen diren -ak, baina egiaztapen hauek desgaitu eta testuinguruak indarrez leheneratzen dituen adabaki baten ondoren, dena ondo egongo balitz bezala, kodea funtzionatu zuen. Eta orduan gauza arraro bat hasi zen: batzuetan sinkronizazio-kodearen egiaztapenak abiarazten ziren - exekuzio-logikaren arabera blokeatu behar bazen kodea huts egiten duten berberak - norbait saiatu zen jada harrapatutako mutex bat hartzen. Zorionez, hori ez zen arazo logikoa izan serializatutako kodean - Emscripten-ek emandako begizta nagusiaren funtzionalitate estandarra erabiltzen ari nintzen, baina batzuetan dei asinkronoak pila erabat desegiten zuen, eta une horretan huts egingo zuen. setTimeout begizta nagusitik - horrela, kodea begizta nagusian sartu zen aurreko iteraziotik irten gabe. Begizta infinitu batean berridatzi eta emscripten_sleep, eta mutexekin arazoak gelditu ziren. Kodea are logikoagoa bihurtu da - azken finean, egia esan, ez dut hurrengo animazio fotograma prestatzen duen koderik - prozesadoreak zerbait kalkulatzen du eta pantaila aldian-aldian eguneratzen da. Hala ere, arazoak ez ziren hor gelditu: batzuetan Qemu exekuzioa besterik gabe amaitzen zen isilik, inolako salbuespen edo akatsik gabe. Momentu horretan bertan behera utzi nuen, baina, aurrera begira, arazoa hau zela esango dut: koroutine-kodeak, hain zuzen ere, ez du erabiltzen setTimeout (edo ez behintzat uste bezain maiz): funtzioa emscripten_yield besterik gabe, dei asinkronoaren bandera ezartzen du. Kontua da hori emscripten_coroutine_next ez da funtzio asinkrono bat: barnean bandera egiaztatzen du, berrezarri eta kontrola behar den tokira eramaten du. Hau da, pilaren promozioa hor amaitzen da. Arazoa zen erabilera-ondoren doakoa dela eta, koroutine igerilekua desgaitu zenean agertu zena, lehendik dagoen coroutine backendetik kode lerro garrantzitsu bat kopiatu ez nuelako, funtzioa. qemu_in_coroutine egia itzultzen du, hain zuzen ere false itzuli behar zuenean. Horrek dei bat ekarri zuen emscripten_yield, horren gainean ez zegoen inor pila gainean emscripten_coroutine_next, pila goraino zabaldu zen, baina ez setTimeout, lehen esan dudan bezala, ez zen erakutsi.

JavaScript kodea sortzea

Eta hona hemen, hain zuzen ere, "haragi xehatuari buelta ematea" agindutakoa. Benetan ez. Noski, Qemu arakatzailean exekutatzen badugu, eta Node.js bertan, orduan, jakina, Qemu-n kodea sortu ondoren JavaScript guztiz okerra izango dugu. Baina hala ere, nolabaiteko alderantzizko eraldaketa.

Lehenik eta behin, Qemu-k nola funtzionatzen duen buruz apur bat. Mesedez, barka iezadazu berehala: ez naiz Qemu garatzaile profesionala eta baliteke nire ondorioak okerrak izatea leku batzuetan. Esaten dutenez, β€œikaslearen iritziak ez du zertan irakaslearen iritziarekin bat etorri behar, Peanoren axiomatikarekin eta zentzuarekin”. Qemu-k onartzen dituen arkitektura gonbidatu kopuru jakin bat du eta bakoitzarentzat bezalako direktorio bat dago target-i386. Eraikitzean, hainbat gonbidatutako arkitekturaren euskarria zehaztu dezakezu, baina emaitza hainbat bitar besterik ez da izango. Gonbidatuen arkitektura onartzen duen kodeak, berriz, Qemu barneko eragiketa batzuk sortzen ditu, eta TCG (Tiny Code Generator) ostalariaren arkitekturarako makina-kode bihurtzen ditu dagoeneko. tcg direktorioan kokatutako readme fitxategian esaten den bezala, hasiera batean C konpiladore arrunt baten parte zen, geroago JITerako egokitu zena. Hori dela eta, adibidez, xede-arkitektura dokumentu honen arabera jada ez da arkitektura gonbidatu bat, ostalari-arkitektura bat baizik. Noizbait, beste osagai bat agertu zen - Tiny Code Interpreter (TCI), kodea exekutatu beharko lukeena (ia barne-eragiketa berdinak) ostalari-arkitektura zehatz baterako kode-sorgailurik ezean. Izan ere, bere dokumentazioak dioen bezala, baliteke interprete honek ez beti JIT kode-sorgailu batek bezain ondo funtzionatzea, ez bakarrik kuantitatiboki abiadurari dagokionez, baita kualitatiboki ere. Nahiz eta ez nagoen ziur bere deskribapena guztiz garrantzitsua denik.

Hasieran TCG backend oso bat egiten saiatu nintzen, baina azkar nahastu nintzen iturburu-kodean eta bytecodearen argibideen deskribapen guztiz argia ezean, beraz, TCI interpretea biltzea erabaki nuen. Horrek hainbat abantaila eman zituen:

  • kode-sorgailu bat inplementatzen duzunean, ezin zenioke argibideen deskribapena begiratu, interpretearen kodea baizik
  • Funtzioak sor ditzakezu ez aurkitutako itzulpen bloke bakoitzeko, baina, adibidez, ehungarren exekuzioaren ondoren bakarrik
  • sortutako kodea aldatzen bada (eta hori posible dela dirudi, adabaki hitza duten izenak dituzten funtzioen arabera), sortutako JS kodea baliogabetu beharko dut, baina gutxienez berregiteko zerbait izango dut.

Hirugarren puntuari dagokionez, ez nago ziur adabakia posible denik kodea lehen aldiz exekutatu ondoren, baina lehen bi puntuak nahikoak dira.

Hasieran, kodea etengailu handi baten moduan sortzen zen jatorrizko bytecode instrukzioaren helbidean, baina gero, Emscripten-i buruzko artikulua gogoratuz, sortutako JS-en optimizazioa eta birlooping-a, giza kode gehiago sortzea erabaki nuen, batez ere enpirikoki. Itzulpen-blokean sartzeko puntu bakarra hasiera dela ondorioztatu zen. Esan baino lehen, pixka bat igaro ondoren, ifs-ekin kodea sortzen zuen kode-sorgailu bat genuen (begiztarik gabe bada ere). Baina zorte txarra, huts egin zuen, argibideek luzera okerreko mezua emanez. Gainera, errekurtsio maila honetako azken instrukzioa izan zen brcond. Ados, kontrol berdina gehituko diot instrukzio honen sorrerari dei errekurtsiboaren aurretik eta ondoren eta... horietako bat ere ez zen exekutatu, baina assert-aldaketaren ondoren huts egin zuten. Azkenean, sortutako kodea aztertu ondoren, konturatu nintzen aldaketaren ondoren, uneko instrukzioaren erakuslea pilatik berriro kargatzen dela eta, ziurrenik, sortutako JavaScript kodea gainidazten duela. Eta halaxe atera zen. Buffer-a megabyte batetik hamarrera handitzeak ez zuen ezer ekarri, eta argi geratu zen kode-sorgailua zirkuluetan zebilela. Egiaztatu behar izan genuen ez ginela egungo TBaren mugetatik haratago joan, eta hala egin ezkero, hurrengo TBren helbideari minus zeinu batekin igorri, exekutatzen jarraitu ahal izateko. Horrez gain, honek arazoa konpontzen du "sortutako zein funtzio baliogabetu beharko lirateke bytecode zati hau aldatu bada?" β€” itzulpen-bloke honi dagokion funtzioa bakarrik baliogabetu behar da. Bide batez, Chromium-en dena arazketa egin nuen arren (Firefox erabiltzen dudanez eta esperimentuetarako arakatzaile bereizia erabiltzea errazagoa zaidanez), Firefoxek asm.js estandarrarekiko bateraezintasunak zuzentzen lagundu zidan, eta ondoren kodea azkarrago hasi zen lanean. Kromoa.

Sortutako kodearen adibidea

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

Ondorioa

Beraz, oraindik ez dago lana amaitu, baina nekatuta nago epe luzerako eraikuntza hau perfekziora eramateaz. Horregatik, oraingoz dudana argitaratzea erabaki nuen. Kodea pixka bat beldurgarria da leku batzuetan, hau esperimentu bat delako, eta aldez aurretik ez dago argi zer egin behar den. Seguruenik, merezi du konpromezu atomiko arruntak ematea Qemuren bertsio modernoago baten gainean. Bitartean, hari bat dago Gita-n blog formatuan: nolabait behintzat gainditu den β€œmaila” bakoitzeko, errusieraz iruzkin zehatza gehitu da. Egia esan, artikulu hau, neurri handi batean, ondorioaren berriztapena da git log.

Dena probatu dezakezu Hemen (kontuz trafikoarekin).

Dagoeneko lanean ari dena:

  • x86 prozesadore birtuala martxan
  • JIT kode-sorgailu baten prototipo bat dago makina-kodetik JavaScript-era
  • 32 biteko gonbidatutako beste arkitektura batzuk muntatzeko txantiloi bat dago: oraintxe bertan Linux miretsi dezakezu MIPS arkitektura arakatzailean kargatzeko fasean izoztuta dagoelako.

Zer gehiago egin dezakezu

  • Emulazioa bizkortu. JIT moduan ere Virtual x86 baino motelago exekutatzen dela dirudi (baina potentzialki Qemu oso bat dago emulatutako hardware eta arkitektura asko dituena)
  • Interfaze normal bat egiteko - egia esan, ez naiz web garatzaile ona, beraz, oraingoz Emscripten shell estandarra birsortu dut ahal dudan moduan.
  • Saiatu Qemu funtzio konplexuagoak abiarazten - sareak, VM migrazioa, etab.
  • UPS: Zure garapenak eta akatsen txostenak Emscripten-era igo beharko dituzu, Qemu eta beste proiektu batzuen aurreko atezainek egin zuten bezala. Eskerrik asko Emscripten-i egindako ekarpena inplizituki nire zereginaren barruan erabili ahal izateagatik.

Iturria: www.habr.com

Gehitu iruzkin berria