Qemu.js kwa usaidizi wa JIT: bado unaweza kugeuza mince nyuma

Miaka michache iliyopita Fabrice Bellard iliyoandikwa na jslinux ni emulator ya Kompyuta iliyoandikwa katika JavaScript. Baada ya hapo kulikuwa na angalau zaidi Virtual x86. Lakini wote, nijuavyo mimi, walikuwa wakalimani, ilhali Qemu, iliyoandikwa mapema zaidi na Fabrice Bellard yuleyule, na, pengine, emulator yeyote wa kisasa anayejiheshimu, hutumia mkusanyiko wa JIT wa msimbo wa wageni katika msimbo wa mfumo wa mwenyeji. Ilionekana kwangu kuwa ilikuwa wakati wa kutekeleza kazi iliyo kinyume kuhusiana na ile ambayo vivinjari hutatua: Mkusanyiko wa JIT wa nambari ya mashine kwenye JavaScript, ambayo ilionekana kuwa ya busara zaidi kwa bandari ya Qemu. Inaweza kuonekana, kwa nini Qemu, kuna emulators rahisi na ya kirafiki - VirtualBox sawa, kwa mfano - imewekwa na inafanya kazi. Lakini Qemu ina sifa kadhaa za kuvutia

  • chanzo wazi
  • uwezo wa kufanya kazi bila dereva wa kernel
  • uwezo wa kufanya kazi katika hali ya mkalimani
  • msaada kwa idadi kubwa ya usanifu wa mwenyeji na mgeni

Kuhusu jambo la tatu, sasa naweza kueleza kwamba kwa kweli, katika hali ya TCI, sio maagizo ya mashine ya wageni wenyewe ambayo yanafasiriwa, lakini bytecode iliyopatikana kutoka kwao, lakini hii haibadilishi kiini - ili kujenga na kukimbia. Qemu kwenye usanifu mpya, ikiwa una bahati, mkusanyaji wa A C inatosha - kuandika jenereta ya msimbo kunaweza kuahirishwa.

Na sasa, baada ya miaka miwili ya kuchezea kwa raha na msimbo wa chanzo wa Qemu katika wakati wangu wa bure, mfano wa kufanya kazi ulionekana, ambao unaweza tayari kukimbia, kwa mfano, Kolibri OS.

Emscripten ni nini

Siku hizi, watunzi wengi wameonekana, matokeo ya mwisho ambayo ni JavaScript. Baadhi, kama Hati ya Aina, awali ilikusudiwa kuwa njia bora ya kuandika kwa wavuti. Wakati huo huo, Emscripten ni njia ya kuchukua msimbo uliopo wa C au C++ na kuukusanya katika fomu inayoweza kusomeka kwenye kivinjari. Washa Ukurasa huu Tumekusanya bandari nyingi za programu zinazojulikana: hapaKwa mfano, unaweza kuangalia PyPy - kwa njia, wanadai kuwa tayari wana JIT. Kwa kweli, sio kila programu inaweza kukusanywa tu na kuendeshwa kwenye kivinjari - kuna nambari vipengele, ambayo lazima uvumilie, hata hivyo, kama maandishi kwenye ukurasa huo huo yanasema "Emscripten inaweza kutumika kukusanya karibu yoyote. portable Nambari ya C/C++ kwa JavaScript". Hiyo ni, kuna idadi ya shughuli ambazo hazijafafanuliwa tabia kulingana na kiwango, lakini kawaida hufanya kazi kwenye x86 - kwa mfano, ufikiaji usio sawa wa vigeu, ambavyo kwa ujumla ni marufuku kwenye usanifu fulani. Kwa ujumla. , Qemu ni programu ya majukwaa mtambuka na, nilitaka kuamini, na tayari haina tabia nyingi zisizoeleweka - ichukue na ukusanye, kisha ucheze kidogo na JIT - na umemaliza! kesi...

Jaribu kwanza

Kwa ujumla, mimi sio mtu wa kwanza kuja na wazo la kuhamisha Qemu kwa JavaScript. Kulikuwa na swali lililoulizwa kwenye jukwaa la ReactOS ikiwa hii ingewezekana kwa kutumia Emscripten. Hata mapema, kulikuwa na uvumi kwamba Fabrice Bellard alifanya hivyo kibinafsi, lakini tulikuwa tunazungumza juu ya jslinux, ambayo, kama ninavyojua, ni jaribio la kufikia utendaji wa kutosha katika JS, na iliandikwa kutoka mwanzo. Baadaye, Virtual x86 iliandikwa - vyanzo visivyoeleweka vilichapishwa kwa ajili yake, na, kama ilivyoelezwa, "uhalisia" mkubwa zaidi wa uigaji ulifanya iwezekane kutumia SeaBIOS kama programu dhibiti. Kwa kuongezea, kulikuwa na angalau jaribio moja la kuweka bandari Qemu kwa kutumia Emscripten - nilijaribu kufanya hivi soketi, lakini maendeleo, ninavyoelewa, yaliganda.

Kwa hivyo, inaweza kuonekana, hapa kuna vyanzo, hapa kuna Emscripten - ichukue na uunde. Lakini pia kuna maktaba ambazo Qemu inategemea, na maktaba ambazo maktaba hizo zinazitegemea n.k., na mojawapo ni libfi, ambayo glib inategemea. Kulikuwa na uvumi kwenye mtandao kwamba kulikuwa na moja katika mkusanyiko mkubwa wa bandari za maktaba za Emscripten, lakini ilikuwa ngumu kuamini kwa njia fulani: kwanza, haikukusudiwa kuwa mkusanyaji mpya, pili, ilikuwa ya kiwango cha chini sana. maktaba ya kuchukua tu, na kukusanya kwa JS. Na sio tu suala la kuingizwa kwa kusanyiko - labda, ikiwa utaipotosha, kwa mikusanyiko mingine ya kupiga simu unaweza kutoa hoja zinazohitajika kwenye safu na kupiga kazi bila wao. Lakini Emscripten ni jambo gumu: ili kufanya nambari inayotokana ionekane inayojulikana kwa kiboreshaji cha injini ya JS ya kivinjari, hila zingine hutumiwa. Hasa, kinachojulikana kama kurudisha nyuma - jenereta ya nambari inayotumia LLVM IR iliyopokelewa na maagizo ya mpito ya dhahania hujaribu kuunda tena ifs zinazowezekana, vitanzi, n.k. Kweli, hoja zinapitishwaje kwa kazi? Kwa kawaida, kama hoja za kazi za JS, ambayo ni, ikiwezekana, sio kupitia safu.

Hapo mwanzo kulikuwa na wazo la kuandika tu uingizwaji wa libffi na JS na kuendesha vipimo vya kawaida, lakini mwishowe nilichanganyikiwa juu ya jinsi ya kutengeneza faili za kichwa changu ili zifanye kazi na nambari iliyopo - naweza kufanya nini, kama wanasema, "Je, kazi ni ngumu sana "Je, sisi ni wajinga sana?" Ilinibidi niweke libffi kwa usanifu mwingine, kwa kusema - kwa bahati nzuri, Emscripten ina macros zote mbili za kusanyiko la ndani (katika Javascript, ndio - vizuri, chochote cha usanifu, kwa hivyo mkusanyaji), na uwezo wa kuendesha nambari inayotokana na kuruka. Kwa ujumla, baada ya kuchezea vipande vya libffi vinavyotegemea jukwaa kwa muda fulani, nilipata nambari fulani inayoweza kuunganishwa na kuiendesha kwenye jaribio la kwanza nililopata. Kwa mshangao wangu, mtihani ulifanikiwa. Kushangazwa na fikra yangu - hakuna mzaha, ilifanya kazi kutoka kwa uzinduzi wa kwanza - mimi, bado sikuamini macho yangu, nilikwenda kutazama nambari iliyosababisha tena, kutathmini wapi kuchimba ijayo. Hapa nilikosa kwa mara ya pili - jambo pekee ambalo kazi yangu ilifanya ilikuwa ffi_call - hii iliripoti simu iliyofanikiwa. Hakukuwa na simu yenyewe. Kwa hivyo nilituma ombi langu la kwanza la kuvuta, ambalo lilirekebisha kosa katika jaribio ambalo liko wazi kwa mwanafunzi yeyote wa Olympiad - nambari halisi hazipaswi kulinganishwa kama a == b na hata jinsi gani a - b < EPS - unahitaji pia kukumbuka moduli, vinginevyo 0 itageuka kuwa sawa sana na 1/3 ... Kwa ujumla, nilikuja na bandari fulani ya libffi, ambayo hupita vipimo rahisi zaidi, na ambayo glib ni. iliyokusanywa - niliamua itakuwa muhimu, nitaiongeza baadaye. Kuangalia mbele, nitasema kwamba, kama ilivyotokea, mkusanyaji hakujumuisha hata kazi ya libffi katika msimbo wa mwisho.

Lakini, kama nilivyokwisha sema, kuna mapungufu, na kati ya utumiaji wa bure wa tabia tofauti ambazo hazijafafanuliwa, kipengele kisichopendeza zaidi kimefichwa - JavaScript kwa muundo haiungi mkono usomaji mwingi na kumbukumbu iliyoshirikiwa. Kimsingi, hii inaweza kuitwa wazo nzuri, lakini sio kwa nambari ya uhamishaji ambayo usanifu wake umefungwa kwa nyuzi za C. Kwa ujumla, Firefox inajaribu kusaidia wafanyikazi walioshirikiwa, na Emscripten ina utekelezaji wa pthread kwao, lakini sikutaka kuitegemea. Ilinibidi niondoe polepole usomaji mwingi kutoka kwa nambari ya Qemu - ambayo ni, kujua ni wapi nyuzi zinaendesha, kusonga mwili wa kitanzi kinachoendesha kwenye uzi huu kuwa kazi tofauti, na kuita kazi kama hizo moja baada ya nyingine kutoka kwa kitanzi kikuu.

Jaribio la pili

Wakati fulani, ilionekana wazi kwamba tatizo lilikuwa bado lipo, na kwamba kusukuma mikongojo bila mpangilio kuzunguka kanuni hiyo haingeleta manufaa yoyote. Hitimisho: tunahitaji kwa namna fulani kupanga mchakato wa kuongeza viboko. Kwa hivyo, toleo la 2.4.1, ambalo lilikuwa safi wakati huo, lilichukuliwa (sio 2.5.0, kwa sababu, ni nani anayejua, kutakuwa na mende katika toleo jipya ambalo bado halijakamatwa, na nina mende zangu za kutosha. ), na jambo la kwanza lilikuwa kuandika upya kwa usalama thread-posix.c. Kweli, hiyo ni salama: ikiwa mtu alijaribu kufanya operesheni inayosababisha kuzuia, kazi hiyo iliitwa mara moja abort() - bila shaka, hii haikutatua matatizo yote mara moja, lakini angalau ilikuwa kwa namna fulani ya kupendeza zaidi kuliko kupokea kimya kimya data isiyofaa.

Kwa ujumla, chaguzi za Emscripten zinafaa sana katika kusambaza nambari kwa JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - wanashika aina fulani za tabia isiyofafanuliwa, kama vile kupiga simu kwa anwani ambayo haijapangwa (ambayo haiendani kabisa na nambari ya safu zilizochapwa kama HEAP32[addr >> 2] = 1) au kuita chaguo za kukokotoa na idadi isiyo sahihi ya hoja.

Kwa njia, makosa ya upatanishi ni suala tofauti. Kama nilivyokwisha sema, Qemu ina tafsiri "iliyoharibika" ya kuunda msimbo TCI (mkalimani mdogo wa msimbo), na kujenga na kuendesha Qemu kwenye usanifu mpya, ikiwa una bahati, kikusanya C kinatosha. "kama una bahati". Sikuwa na bahati, na ikawa kwamba TCI hutumia ufikiaji usio sawa wakati wa kuchanganua bytecode yake. Hiyo ni, kwenye kila aina ya ARM na usanifu mwingine wenye ufikiaji uliosawazishwa, Qemu inakusanya kwa sababu wana asili ya kawaida ya TCG ambayo hutoa msimbo asilia, lakini ikiwa TCI itayafanyia kazi ni swali lingine. Walakini, kama ilivyotokea, hati za TCI zilionyesha wazi kitu kama hicho. Kama matokeo, simu za kukokotoa za usomaji ambao haujasawazishwa ziliongezwa kwa msimbo, ambao ulipatikana katika sehemu nyingine ya Qemu.

Uharibifu wa lundo

Kama matokeo, ufikiaji usio na usawa wa TCI ulisahihishwa, kitanzi kikuu kiliundwa ambacho kwa upande wake kiliitwa processor, RCU na vitu vingine vidogo. Na kwa hivyo ninazindua Qemu na chaguo -d exec,in_asm,out_asm, ambayo ina maana kwamba unahitaji kusema ni vizuizi vipi vya msimbo vinavyotekelezwa, na pia wakati wa matangazo ili kuandika nambari ya mgeni ilikuwa nini, msimbo wa mwenyeji ulikua (katika kesi hii, bytecode). Huanza, kutekeleza vizuizi kadhaa vya utafsiri, huandika ujumbe wa utatuzi nilioacha kwamba RCU sasa itaanza na... huanguka. abort() ndani ya utendaji free(). Kwa kuchezea kipengele free() Tuliweza kujua kwamba katika kichwa cha block block, ambayo iko katika byte nane kabla ya kumbukumbu iliyotengwa, badala ya ukubwa wa block au kitu sawa, kulikuwa na takataka.

Uharibifu wa chungu - jinsi ya kupendeza ... Katika kesi hiyo, kuna dawa muhimu - kutoka (ikiwezekana) vyanzo sawa, kukusanya binary asili na kukimbia chini ya Valgrind. Baada ya muda, binary ilikuwa tayari. Ninaizindua na chaguo sawa - inaanguka hata wakati wa kuanzishwa, kabla ya kufikia utekelezaji. Haipendezi, kwa kweli - inaonekana, vyanzo havikuwa sawa, ambayo haishangazi, kwa sababu sanidi chaguzi tofauti kidogo, lakini nina Valgrind - kwanza nitarekebisha mdudu huu, halafu, ikiwa nina bahati. , ya awali itaonekana. Ninaendesha kitu sawa chini ya Valgrind ... Y-y-y, y-y-y, uh-uh, ilianza, ilipitia uanzishaji kawaida na kuendelea na mdudu wa awali bila onyo moja kuhusu upatikanaji usio sahihi wa kumbukumbu, bila kutaja kuhusu kuanguka. Maisha, kama wanasema, hayakunitayarisha kwa hili - programu ya kugonga huacha kugonga inapozinduliwa chini ya Walgrind. Ilivyokuwa ni siri. Nadharia yangu ni kwamba mara moja katika eneo la maagizo ya sasa baada ya ajali wakati wa uanzishaji, gdb ilionyesha kazi. memset-a yenye kielekezi halali kwa kutumia aidha mmx, au xmm rejista, basi labda ilikuwa aina fulani ya makosa ya upatanishi, ingawa bado ni ngumu kuamini.

Sawa, Valgrind haionekani kusaidia hapa. Na hapa jambo la kuchukiza zaidi lilianza - kila kitu kinaonekana kuanza, lakini huanguka kwa sababu zisizojulikana kabisa kutokana na tukio ambalo lingeweza kutokea mamilioni ya maagizo iliyopita. Kwa muda mrefu, haikuwa wazi hata jinsi ya kukaribia. Mwishowe, bado ilibidi niketi na kurekebisha. Kuchapisha kile kichwa kiliandikwa upya kulionyesha kuwa haikuonekana kama nambari, lakini aina fulani ya data ya binary. Na, tazama, kamba hii ya binary ilipatikana kwenye faili ya BIOS - yaani, sasa iliwezekana kusema kwa ujasiri mzuri kwamba ilikuwa buffer kufurika, na ni wazi hata kuwa imeandikwa kwa buffer hii. Kweli, basi kitu kama hiki - katika Emscripten, kwa bahati nzuri, hakuna bahati nasibu ya nafasi ya anwani, hakuna mashimo ndani yake pia, kwa hivyo unaweza kuandika mahali fulani katikati ya nambari ili kutoa data kwa pointer kutoka kwa uzinduzi wa mwisho, angalia data, angalia pointer, na, ikiwa haijabadilika, pata chakula cha mawazo. Ni kweli, inachukua dakika chache kuunganisha baada ya mabadiliko yoyote, lakini unaweza kufanya nini? Kama matokeo, mstari maalum ulipatikana ambao ulinakili BIOS kutoka kwa buffer ya muda hadi kumbukumbu ya wageni - na, kwa kweli, hapakuwa na nafasi ya kutosha kwenye bafa. Kutafuta chanzo cha anwani hiyo ya ajabu ya bafa ilisababisha kazi qemu_anon_ram_alloc katika faili oslib-posix.c - mantiki ilikuwa hii: wakati mwingine inaweza kuwa muhimu kusawazisha anwani kwa ukurasa mkubwa wa 2 MB kwa ukubwa, kwa hili tutauliza. mmap kwanza kidogo zaidi, na kisha tutarudi ziada kwa usaidizi munmap. Na ikiwa usawa kama huo hauhitajiki, basi tutaonyesha matokeo badala ya 2 MB getpagesize() - mmap bado itatoa anwani iliyosawazishwa... Kwa hivyo katika Emscripten mmap wito tu malloc, lakini bila shaka hailingani kwenye ukurasa. Kwa ujumla, mdudu ambao ulinikatisha tamaa kwa miezi kadhaa ulirekebishwa na mabadiliko mbili mistari.

Vipengele vya kazi za kupiga simu

Na sasa processor inahesabu kitu, Qemu haina ajali, lakini skrini haina kugeuka, na processor haraka huenda kwenye vitanzi, kwa kuzingatia matokeo. -d exec,in_asm,out_asm. Dhana imeibuka: kipima saa kinakatiza (au, kwa ujumla, usumbufu wote) haufiki. Na kwa kweli, ikiwa utaondoa usumbufu kutoka kwa kusanyiko la asili, ambalo kwa sababu fulani lilifanya kazi, unapata picha kama hiyo. Lakini hili halikuwa jibu hata kidogo: kulinganisha kwa athari iliyotolewa na chaguo hapo juu ilionyesha kuwa njia za utekelezaji zilitofautiana mapema sana. Hapa ni lazima kusema kwamba kulinganisha ya kile kilichoandikwa kwa kutumia launcher emrun pato la kurekebisha na matokeo ya mkusanyiko wa asili sio mchakato wa kiufundi kabisa. Sijui jinsi programu inayoendesha kwenye kivinjari inavyounganishwa emrun, lakini baadhi ya mistari kwenye pato inageuka kuwa imepangwa upya, kwa hivyo tofauti katika tofauti bado sio sababu ya kudhani kuwa trajectories zimetofautiana. Kwa ujumla, ikawa wazi kuwa kulingana na maagizo ljmpl kuna mpito kwa anwani tofauti, na bytecode inayozalishwa ni tofauti kimsingi: moja ina maagizo ya kupiga kazi ya msaidizi, nyingine haina. Baada ya kutazama maagizo na kusoma nambari inayotafsiri maagizo haya, ikawa wazi kuwa, kwanza, mara moja kabla yake kwenye rejista. cr0 rekodi ilifanywa - pia kwa kutumia msaidizi - ambayo ilibadilisha processor kwa hali iliyolindwa, na pili, kwamba toleo la js halijabadilika kuwa hali iliyolindwa. Lakini ukweli ni kwamba kipengele kingine cha Emscripten ni kusita kwake kuvumilia kanuni kama vile utekelezaji wa maagizo. call katika TCI, ambayo kiashiria chochote cha utendakazi kinasababisha aina long long f(int arg0, .. int arg9) - kazi lazima ziitwe na idadi sahihi ya hoja. Ikiwa sheria hii inakiukwa, kulingana na mipangilio ya utatuzi, programu itaanguka (ambayo ni nzuri) au itaita kazi isiyo sahihi kabisa (ambayo itakuwa ya kusikitisha kutatua). Pia kuna chaguo la tatu - kuwezesha kizazi cha vifuniko vinavyoongeza / kuondoa hoja, lakini kwa jumla vifuniko hivi vinachukua nafasi nyingi, licha ya ukweli kwamba kwa kweli ninahitaji tu vifuniko zaidi ya mia moja. Hii peke yake ni ya kusikitisha sana, lakini iliibuka kuwa shida kubwa zaidi: katika nambari iliyotengenezwa ya kazi za kanga, hoja zilibadilishwa na kubadilishwa, lakini wakati mwingine kazi na hoja zinazozalishwa haikuitwa - vizuri, kama vile katika. utekelezaji wangu wa libffi. Hiyo ni, wasaidizi wengine hawakuuawa.

Kwa bahati nzuri, Qemu ina orodha za wasaidizi zinazosomeka kwa mashine katika mfumo wa faili ya kichwa kama

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

Zinatumika kuchekesha kabisa: kwanza, macros hufafanuliwa upya kwa njia ya kushangaza zaidi DEF_HELPER_n, na kisha kuwasha helper.h. Kwa kiwango ambacho macro hupanuliwa kuwa kianzilishi cha muundo na koma, na kisha safu hufafanuliwa, na badala ya vitu - #include <helper.h> Matokeo yake, hatimaye nilipata nafasi ya kujaribu maktaba kazini pyparsing, na hati iliandikwa ambayo hutoa kanga hizo haswa kwa kazi ambazo zinahitajika.

Na hivyo, baada ya hapo processor ilionekana kufanya kazi. Inaonekana ni kwa sababu skrini haikuanzishwa kamwe, ingawa memtest86+ iliweza kufanya kazi katika mkusanyiko asilia. Hapa inahitajika kufafanua kuwa nambari ya I/O ya Qemu imeandikwa katika coroutines. Emscripten ina utekelezaji wake wa hila sana, lakini bado ilihitaji kuungwa mkono katika nambari ya Qemu, na unaweza kurekebisha kichakataji sasa: Qemu inasaidia chaguzi. -kernel, -initrd, -append, ambayo unaweza boot Linux au, kwa mfano, memtest86+, bila kutumia vifaa vya kuzuia kabisa. Lakini hapa ndio shida: kwenye kusanyiko la asili mtu anaweza kuona pato la Linux kwenye koni na chaguo -nographic, na hakuna matokeo kutoka kwa kivinjari hadi terminal kutoka ambapo ilizinduliwa emrun, hakuja. Hiyo ni, haijulikani: processor haifanyi kazi au pato la graphics haifanyi kazi. Na kisha ilitokea kwangu kusubiri kidogo. Ilibadilika kuwa "mchakataji hajalala, lakini anapepesa polepole," na baada ya kama dakika tano punje ikatupa rundo la ujumbe kwenye koni na kuendelea kunyongwa. Ikawa wazi kuwa processor, kwa ujumla, inafanya kazi, na tunahitaji kuchimba msimbo wa kufanya kazi na SDL2. Kwa bahati mbaya, sijui jinsi ya kutumia maktaba hii, kwa hivyo katika sehemu zingine nililazimika kuchukua hatua bila mpangilio. Wakati fulani, mstari wa sambamba0 uliangaza kwenye skrini kwenye historia ya bluu, ambayo ilipendekeza mawazo fulani. Mwishowe, ikawa kwamba shida ilikuwa kwamba Qemu inafungua madirisha kadhaa ya kawaida kwenye dirisha moja la kimwili, kati ya ambayo unaweza kubadili kwa kutumia Ctrl-Alt-n: inafanya kazi katika kujenga asili, lakini si kwa Emscripten. Baada ya kuondokana na madirisha yasiyo ya lazima kwa kutumia chaguzi -monitor none -parallel none -serial none na maagizo ya kuchora upya skrini nzima kwa nguvu kwenye kila fremu, kila kitu kilifanya kazi ghafla.

Coroutines

Kwa hivyo, uigaji kwenye kivinjari hufanya kazi, lakini huwezi kuendesha kitu chochote cha kupendeza cha floppy ndani yake, kwa sababu hakuna block I/O - unahitaji kutekeleza msaada kwa coroutines. Qemu tayari ina viambajengo kadhaa vya nyuma, lakini kwa sababu ya asili ya JavaScript na jenereta ya msimbo wa Emscripten, huwezi tu kuanza kugonganisha rafu. Inaweza kuonekana kuwa "kila kitu kimekwenda, plasta inaondolewa," lakini watengenezaji wa Emscripten tayari wametunza kila kitu. Hii inatekelezwa ya kuchekesha sana: wacha tuite simu ya kukokotoa kama hii ya kutiliwa shaka emscripten_sleep na zingine kadhaa zinazotumia utaratibu wa Asyncify, pamoja na simu za vielelezo na simu kwa utendakazi wowote ambapo moja ya matukio mawili ya awali yanaweza kutokea chini ya mrundikano. Na sasa, kabla ya kila simu inayoshukiwa, tutachagua muktadha wa kusawazisha, na mara baada ya simu hiyo, tutaangalia ikiwa simu isiyolingana imetokea, na ikiwa imetokea, tutahifadhi anuwai zote za ndani katika muktadha huu wa async, zinaonyesha ni kitendakazi gani. kuhamisha udhibiti hadi tunapohitaji kuendelea kutekeleza , na uondoke kwenye chaguo la kukokotoa la sasa. Hapa ndipo kuna wigo wa kusoma athari ubadhirifu - kwa mahitaji ya kuendelea na utekelezaji wa nambari baada ya kurudi kutoka kwa simu isiyolingana, mkusanyaji hutoa "vijiti" vya chaguo la kukokotoa kuanzia baada ya simu inayotiliwa shaka - kama hii: ikiwa kuna simu zinazotiliwa shaka, basi chaguo la kukokotoa litapanuliwa mahali fulani n/2. nyakati - hii bado, ikiwa sivyo Kumbuka kwamba baada ya kila simu inayoweza kutosawazisha, unahitaji kuongeza kuokoa anuwai za ndani kwa chaguo la kukokotoa asili. Baadaye, ilibidi hata niandike hati rahisi katika Python, ambayo, kwa msingi wa seti fulani ya kazi zilizotumiwa sana ambazo inadaiwa "haziruhusu asynchrony kupita zenyewe" (hiyo ni, ukuzaji wa stack na kila kitu ambacho nimeelezea hivi punde. fanya kazi ndani yao), inaonyesha simu kupitia viashiria ambavyo kazi zinapaswa kupuuzwa na mkusanyaji ili kazi hizi zisichukuliwe kuwa za asynchronous. Na kisha faili za JS chini ya 60 MB ni wazi sana - hebu sema angalau 30. Ingawa, mara moja nilikuwa nikiweka hati ya mkutano, na kwa bahati mbaya nikatupa chaguzi za kiunganishi, kati ya hizo zilikuwa. -O3. Ninaendesha msimbo uliotengenezwa, na Chromium hula kumbukumbu na kuacha kufanya kazi. Kisha kwa bahati mbaya niliangalia kile alichokuwa anajaribu kupakua... Naam, ninaweza kusema nini, ningeganda pia ikiwa ningeulizwa kusoma kwa uangalifu na kuongeza Javascript ya 500+ MB.

Kwa bahati mbaya, ukaguzi katika msimbo wa maktaba ya usaidizi wa Asyncify haukuwa rafiki kabisa longjmp-s ambazo hutumika katika msimbo wa kichakataji pepe, lakini baada ya kiraka kidogo ambacho huzima ukaguzi huu na kurejesha muktadha kwa nguvu kana kwamba kila kitu kilikuwa sawa, msimbo ulifanya kazi. Na kisha jambo la kushangaza lilianza: wakati mwingine hundi katika msimbo wa maingiliano zilisababishwa - zile zile ambazo zinavunja msimbo ikiwa, kwa mujibu wa mantiki ya utekelezaji, inapaswa kuzuiwa - mtu alijaribu kunyakua bubu iliyokamatwa tayari. Kwa bahati nzuri, hii iligeuka kuwa sio shida ya kimantiki katika nambari ya serial - nilikuwa nikitumia tu utendakazi wa kitanzi kuu uliotolewa na Emscripten, lakini wakati mwingine simu ya asynchronous ingefungua kabisa safu, na wakati huo ingeshindwa. setTimeout kutoka kwa kitanzi kikuu - kwa hivyo, msimbo uliingia iteration kuu ya kitanzi bila kuacha iteration ya awali. Andika upya kwenye kitanzi kisicho na mwisho na emscripten_sleep, na shida na bubu zilisimamishwa. Nambari hiyo imekuwa ya kimantiki zaidi - baada ya yote, kwa kweli, sina msimbo fulani unaotayarisha sura inayofuata ya uhuishaji - kichakataji huhesabu kitu na skrini inasasishwa mara kwa mara. Hata hivyo, matatizo hayakuishia hapo: wakati mwingine utekelezaji wa Qemu ungeisha tu kimya bila ubaguzi au makosa yoyote. Wakati huo nilikata tamaa, lakini, nikitazama mbele, nitasema kwamba shida ilikuwa hii: kanuni ya coroutine, kwa kweli, haitumii. setTimeout (au angalau sio mara nyingi kama unavyoweza kufikiria): kazi emscripten_yield huweka tu bendera ya simu isiyolingana. Jambo zima ni hilo emscripten_coroutine_next si kazi isiyolingana: ndani hukagua bendera, huiweka upya na kuhamisha udhibiti mahali inapohitajika. Hiyo ni, uendelezaji wa stack unaishia hapo. Shida ilikuwa kwamba kwa sababu ya utumiaji-baada ya bure, ambayo ilionekana wakati bwawa la coroutine limezimwa kwa sababu sikunakili safu muhimu ya nambari kutoka kwa hali ya nyuma ya coroutine, kazi hiyo. qemu_in_coroutine ilirudi kweli wakati kwa kweli ilipaswa kurudi uongo. Hii ilisababisha simu emscripten_yield, juu ambayo hapakuwa na mtu kwenye stack emscripten_coroutine_next, rundo lilifunuliwa hadi juu kabisa, lakini hapana setTimeout, kama nilivyokwisha sema, haikuonyeshwa.

Uzalishaji wa msimbo wa JavaScript

Na hapa, kwa kweli, ni "kurudisha nyama ya kusaga." Si kweli. Bila shaka, ikiwa tunaendesha Qemu katika kivinjari, na Node.js ndani yake, basi, kwa kawaida, baada ya kizazi cha msimbo katika Qemu tutapata JavaScript isiyo sahihi kabisa. Lakini bado, aina fulani ya mabadiliko ya reverse.

Kwanza, kidogo kuhusu jinsi Qemu inavyofanya kazi. Tafadhali nisamehe mara moja: Mimi si mtaalamu wa kuunda Qemu na hitimisho langu linaweza kuwa na makosa katika baadhi ya maeneo. Kama wanasema, "maoni ya mwanafunzi sio lazima yalingane na maoni ya mwalimu, axiomatics ya Peano na akili ya kawaida." Qemu ina idadi fulani ya usanifu wa wageni unaotumika na kwa kila moja kuna saraka kama target-i386. Wakati wa kujenga, unaweza kutaja msaada kwa usanifu kadhaa wa wageni, lakini matokeo yatakuwa tu binaries kadhaa. Msimbo wa kuauni usanifu wa mgeni, kwa upande wake, huzalisha shughuli za ndani za Qemu, ambazo TCG (Kizalishaji cha Msimbo Ndogo) tayari hugeuza kuwa msimbo wa mashine kwa usanifu wa mwenyeji. Kama ilivyoonyeshwa kwenye faili ya kusoma iliyo kwenye saraka ya tcg, hii hapo awali ilikuwa sehemu ya mkusanyaji wa kawaida wa C, ambayo baadaye ilibadilishwa kwa JIT. Kwa hiyo, kwa mfano, usanifu wa lengo kwa mujibu wa hati hii sio usanifu wa wageni, lakini usanifu wa jeshi. Wakati fulani, sehemu nyingine ilionekana - Mfasiri wa Msimbo wa Tiny (TCI), ambayo inapaswa kutekeleza msimbo (karibu shughuli sawa za ndani) kwa kutokuwepo kwa jenereta ya kanuni kwa usanifu maalum wa jeshi. Kwa kweli, kama nyaraka zake zinavyosema, mkalimani huyu hawezi kufanya kazi kila wakati kama jenereta ya nambari ya JIT, sio tu kwa kiwango cha kasi, lakini pia kwa ubora. Ingawa sina uhakika kuwa maelezo yake yanafaa kabisa.

Mwanzoni nilijaribu kutengeneza hali kamili ya nyuma ya TCG, lakini haraka nilichanganyikiwa katika nambari ya chanzo na maelezo yasiyo wazi kabisa ya maagizo ya bytecode, kwa hivyo niliamua kumfunga mkalimani wa TCI. Hii ilitoa faida kadhaa:

  • wakati wa kutekeleza jenereta ya msimbo, unaweza kutazama sio maelezo ya maagizo, lakini kwa msimbo wa mkalimani
  • unaweza kuzalisha kazi si kwa kila kizuizi cha tafsiri kilichokutana, lakini, kwa mfano, tu baada ya utekelezaji wa mia
  • ikiwa nambari inayotokana itabadilika (na hii inaonekana kuwa inawezekana, kwa kuhukumu kazi zilizo na majina yaliyo na kiraka cha maneno), nitahitaji kubatilisha nambari iliyotengenezwa ya JS, lakini angalau nitakuwa na kitu cha kuifanya upya kutoka.

Kuhusu hoja ya tatu, sina uhakika kwamba kuweka kiraka kunawezekana baada ya msimbo kutekelezwa kwa mara ya kwanza, lakini pointi mbili za kwanza zinatosha.

Hapo awali, nambari hiyo ilitolewa kwa njia ya swichi kubwa kwenye anwani ya maagizo ya asili ya bytecode, lakini basi, nikikumbuka nakala kuhusu Emscripten, uboreshaji wa JS iliyotengenezwa na kurudisha nyuma, niliamua kutoa nambari zaidi ya kibinadamu, haswa kwani kwa nguvu. iligeuka kuwa sehemu pekee ya kuingia kwenye kizuizi cha tafsiri ni Mwanzo wake. Mara tu baada ya kusema, baada ya muda tulikuwa na jenereta ya nambari ambayo ilitoa nambari na ifs (ingawa bila vitanzi). Lakini kwa bahati mbaya, ilianguka, ikitoa ujumbe kwamba maagizo yalikuwa ya urefu usio sahihi. Zaidi ya hayo, maagizo ya mwisho katika kiwango hiki cha kujirudia yalikuwa brcond. Sawa, nitaongeza hundi sawa kwa kizazi cha maagizo haya kabla na baada ya simu ya kujirudia na ... hakuna hata mmoja wao aliyetekelezwa, lakini baada ya kubadili kudai bado wameshindwa. Mwishowe, baada ya kusoma msimbo uliotengenezwa, niligundua kuwa baada ya kubadili, pointer kwa maagizo ya sasa inapakiwa tena kutoka kwa stack na labda imeandikwa tena na msimbo wa JavaScript uliozalishwa. Na hivyo ikawa. Kuongeza buffer kutoka megabyte moja hadi kumi haikuongoza kwa chochote, na ikawa wazi kwamba jenereta ya kanuni ilikuwa inaendesha kwenye miduara. Ilitubidi kuangalia kwamba hatukuvuka mipaka ya TB ya sasa, na ikiwa tulifanya hivyo, basi tutoe anwani ya TB inayofuata na ishara ya kuondoa ili tuweze kuendelea na utekelezaji. Kwa kuongeza, hii hutatua tatizo "ni kazi gani zinazozalishwa zinapaswa kubatilishwa ikiwa kipande hiki cha bytecode kimebadilika?" β€” ni kipengele cha kukokotoa ambacho kinalingana na kizuizi hiki cha tafsiri kinahitaji kubatilishwa. Kwa njia, ingawa nilitatua kila kitu kwenye Chromium (kwa kuwa mimi hutumia Firefox na ni rahisi kwangu kutumia kivinjari tofauti kwa majaribio), Firefox ilinisaidia kusahihisha kutokubaliana na kiwango cha asm.js, baada ya hapo nambari ilianza kufanya kazi haraka katika Chromium.

Mfano wa kanuni zinazozalishwa

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

Hitimisho

Kwa hiyo, kazi bado haijakamilika, lakini nimechoka kwa siri kuleta ujenzi huu wa muda mrefu kwa ukamilifu. Kwa hivyo, niliamua kuchapisha niliyo nayo kwa sasa. Nambari hiyo inatisha kidogo katika maeneo, kwa sababu hii ni jaribio, na haijulikani mapema kile kinachohitajika kufanywa. Labda, basi inafaa kutoa ahadi za kawaida za atomiki juu ya toleo la kisasa zaidi la Qemu. Wakati huo huo, kuna thread katika Gita katika muundo wa blogu: kwa kila "ngazi" ambayo imepitishwa angalau kwa namna fulani, ufafanuzi wa kina katika Kirusi umeongezwa. Kwa kweli, nakala hii kwa kiwango kikubwa ni urejeshaji wa hitimisho git log.

Unaweza kujaribu yote hapa (Jihadharini na trafiki).

Nini tayari inafanya kazi:

  • kichakataji cha x86 kinachoendesha
  • Kuna mfano unaofanya kazi wa jenereta ya msimbo wa JIT kutoka kwa msimbo wa mashine hadi JavaScript
  • Kuna kiolezo cha kukusanya usanifu mwingine wa wageni wa 32-bit: hivi sasa unaweza kupendeza Linux kwa kufungia kwa usanifu wa MIPS kwenye kivinjari kwenye hatua ya upakiaji.

Nini kingine unaweza kufanya

  • Ongeza kasi ya uigaji. Hata katika hali ya JIT inaonekana kwenda polepole kuliko Virtual x86 (lakini kuna uwezekano wa Qemu nzima iliyo na vifaa vingi vya kuigwa na usanifu)
  • Ili kutengeneza kiolesura cha kawaida - kusema ukweli, mimi si msanidi programu mzuri wa wavuti, kwa hivyo kwa sasa nimerekebisha ganda la kawaida la Emscripten kadiri niwezavyo.
  • Jaribu kuzindua kazi ngumu zaidi za Qemu - mitandao, uhamiaji wa VM, nk.
  • UPS: utahitaji kuwasilisha ripoti zako chache za maendeleo na hitilafu kwa Emscripten juu ya mkondo, kama wapagazi wa awali wa Qemu na miradi mingine walivyofanya. Asante kwao kwa kuweza kutumia mchango wao kwa Emscripten kama sehemu ya kazi yangu.

Chanzo: mapenzi.com

Kuongeza maoni