Bir necha yil oldin Fabris Bellard
- ochiq manba
- yadro drayverisiz ishlash qobiliyati
- tarjimon rejimida ishlash qobiliyati
- ko'p sonli mezbon va mehmon arxitekturasini qo'llab-quvvatlash
Uchinchi nuqtaga kelsak, men endi tushuntira olamanki, aslida TCI rejimida mehmon mashinasi ko'rsatmalarining o'zi emas, balki ulardan olingan bayt-kod talqin qilinadi, lekin bu mohiyatni o'zgartirmaydi - qurish va ishga tushirish uchun Qemu yangi arxitekturada, agar omadingiz bo'lsa, C kompilyatori kifoya qiladi - kod generatorini yozishni keyinga qoldirish mumkin.
Va endi, ikki yillik bo'sh vaqtimda Qemu manba kodi bilan bemalol o'ylab ko'rganimdan so'ng, ishlaydigan prototip paydo bo'ldi, unda siz allaqachon ishga tushirishingiz mumkin, masalan, Kolibri OS.
Emscripten nima
Hozirgi vaqtda ko'plab kompilyatorlar paydo bo'ldi, ularning yakuniy natijasi JavaScript hisoblanadi. Ba'zilari, masalan, Type Script, dastlab veb uchun yozishning eng yaxshi usuli bo'lishga mo'ljallangan edi. Shu bilan birga, Emscripten mavjud C yoki C++ kodini olish va uni brauzer tomonidan o'qiladigan shaklga kompilyatsiya qilish usulidir. Yoniq
Birinchi urinib ko'ring
Umuman olganda, men Qemu-ni JavaScript-ga ko'chirish g'oyasini ilgari surgan birinchi odam emasman. ReactOS forumida bu Emscripten yordamida mumkinmi degan savol bor edi. Ilgari, Fabris Bellard buni shaxsan qilgani haqida mish-mishlar bor edi, lekin biz jslinux haqida gapirgan edik, bu men bilishimcha, bu JS-da etarli darajada ishlashga qo'lda erishishga urinishdir va noldan yozilgan. Keyinchalik Virtual x86 yozildi - buning uchun aniq manbalar joylashtirildi va ta'kidlanganidek, emulyatsiyaning kattaroq "realizmi" SeaBIOS-dan proshivka sifatida foydalanishga imkon berdi. Bundan tashqari, Emscripten yordamida Qemu portiga kamida bitta urinish bo'lgan - men buni qilishga harakat qildim
Shunday qilib, ko'rinadi, mana manbalar, mana Emscripten - uni oling va tuzing. Ammo Qemu bog'liq bo'lgan kutubxonalar va bu kutubxonalar bog'liq bo'lgan kutubxonalar va boshqalar ham bor va ulardan biri
Boshida JS bilan libffi o'rniga oddiygina yozish va standart testlarni o'tkazish g'oyasi bor edi, lekin oxirida men sarlavha fayllarimni mavjud kod bilan ishlashi uchun qanday qilib yaratishni bilmay qoldim - nima qilishim mumkin? Ular aytganidek, "Vazifalar shunchalik murakkab "Biz shunchalik ahmoqmizmi?" Men libffi-ni boshqa arxitekturaga o‘tkazishim kerak edi, ta’bir joiz bo‘lsa, Emscripten-da ichki yig‘ish uchun ikkala makros ham (Javascriptda, ha – arxitektura qanday bo‘lishidan qat’iy nazar, assembler) va tezda yaratilgan kodni ishga tushirish imkoniyati mavjud. Umuman olganda, bir muncha vaqt platformaga bog'liq bo'lgan libffi fragmentlari bilan shug'ullanganimdan so'ng, men kompilyatsiya qilinadigan kodni oldim va uni birinchi duch kelgan testda ishlatdim. Ajablanarlisi shundaki, sinov muvaffaqiyatli o'tdi. Mening dahoyimdan hayratda qoldim - hazil emas, u birinchi ishga tushirilgandanoq ishladi - men hali ham ko'zlarimga ishonmay, natijada paydo bo'lgan kodga yana bir bor qaradim, keyin qayerda qazishni baholadim. Bu erda men ikkinchi marta aqldan ozdim - mening vazifam qilgan yagona narsa ffi_call
- bu muvaffaqiyatli qo'ng'iroq haqida xabar berdi. Hech qanday qo'ng'iroq yo'q edi. Shunday qilib, men birinchi tortishish so'rovimni yubordim, u testdagi har qanday olimpiada o'quvchisi uchun tushunarli bo'lgan xatoni tuzatdi - haqiqiy raqamlarni taqqoslamaslik kerak a == b
va hatto qanday qilib a - b < EPS
- siz modulni ham eslab qolishingiz kerak, aks holda 0 1/3 ga teng bo'lib chiqadi... Umuman olganda, men eng oddiy testlardan o'tadigan va glib bilan ishlaydigan ma'lum bir libffi portini o'ylab topdim. kompilyatsiya qilingan - zarur bo'lishiga qaror qildim, keyinroq qo'shaman. Oldinga qarab, shuni aytamanki, kompilyator yakuniy kodga libffi funktsiyasini ham kiritmagan.
Ammo, yuqorida aytib o'tganimdek, ba'zi cheklovlar mavjud va turli xil noaniq xatti-harakatlardan bepul foydalanish orasida yanada noxush xususiyat yashiringan - dizayn bo'yicha JavaScript umumiy xotira bilan ko'p ish zarralarini qo'llab-quvvatlamaydi. Asosan, buni odatda yaxshi g'oya deb atash mumkin, lekin arxitekturasi C iplari bilan bog'langan kodni ko'chirish uchun emas. Umuman olganda, Firefox umumiy ishchilarni qo'llab-quvvatlash bilan tajriba o'tkazmoqda va Emscripten ular uchun pthread dasturiga ega, ammo men bunga bog'liq bo'lishni xohlamadim. Men asta-sekin Qemu kodidan multithreadingni yo'q qilishim kerak edi - ya'ni iplar qayerda ishlayotganini bilib oling, ushbu ipda ishlaydigan tsiklning tanasini alohida funktsiyaga o'tkazing va bunday funktsiyalarni asosiy tsikldan birma-bir chaqirishim kerak edi.
Ikkinchi urinib ko'ring
Bir paytlar muammo hali ham borligi va kod atrofida tasodifan qo'ltiq tayoqchalarini silkitish hech qanday yaxshilikka olib kelmasligi ayon bo'ldi. Xulosa: tayoqchalarni qo'shish jarayonini qandaydir tarzda tizimlashtirishimiz kerak. Shuning uchun, o'sha paytda yangi bo'lgan 2.4.1 versiyasi olindi (2.5.0 emas, chunki kim biladi, yangi versiyada hali qo'lga olinmagan xatolar bo'ladi va menda o'zimning xatolarim etarli. ) va birinchi narsa uni xavfsiz tarzda qayta yozish edi thread-posix.c
. Xo'sh, ya'ni xavfsiz: agar kimdir blokirovkaga olib keladigan operatsiyani bajarishga harakat qilsa, darhol funktsiya chaqiriladi abort()
- Albatta, bu barcha muammolarni birdaniga hal qilmadi, lekin hech bo'lmaganda bu nomuvofiq ma'lumotlarni jimgina olishdan ko'ra yoqimliroq edi.
Umuman olganda, Emscripten variantlari kodni JS ga ko'chirishda juda foydali -s ASSERTIONS=1 -s SAFE_HEAP=1
- ular aniqlanmagan xatti-harakatlarning ba'zi turlarini, masalan, tekislanmagan manzilga qo'ng'iroqlarni (bu kabi terilgan massivlar uchun kodga mutlaqo mos kelmaydi) ushlaydi. HEAP32[addr >> 2] = 1
) yoki noto'g'ri argumentlar soni bilan funktsiyani chaqirish.
Aytgancha, hizalama xatolar alohida masala. Yuqorida aytib o'tganimdek, Qemu kod ishlab chiqarish uchun TCI (kichik kod tarjimoni) va Qemu'ni yangi arxitekturada qurish va ishga tushirish uchun "degenerativ" sharhlovchi backendga ega, agar omadingiz bo'lsa, C kompilyatori kifoya qiladi. "Agar omadingiz bo'lsa". Menga omad kulib boqmadi va ma'lum bo'ldiki, TCI bayt kodini tahlil qilishda tenglashtirilmagan kirishdan foydalanadi. Ya'ni, har xil turdagi ARM va boshqa arxitekturalarda, albatta, tekislangan kirishda Qemu kompilyatsiya qiladi, chunki ular mahalliy kodni yaratadigan oddiy TCG backendiga ega, ammo TCI ular ustida ishlaydimi yoki yo'qmi - bu boshqa savol. Biroq, ma'lum bo'lishicha, TCI hujjatlari shunga o'xshash narsani aniq ko'rsatgan. Natijada, Qemu-ning boshqa qismida topilgan kodga tenglashtirilmagan o'qish uchun funksiya chaqiruvlari qo'shildi.
To'pni yo'q qilish
Natijada, TCI-ga tenglashtirilmagan kirish tuzatildi, asosiy halqa yaratildi, u o'z navbatida protsessor, RCU va boshqa kichik narsalar deb ataladi. Va shuning uchun men variant bilan Qemu-ni ishga tushiraman -d exec,in_asm,out_asm
, ya'ni qaysi kod bloklari bajarilayotganini, shuningdek, translyatsiya paytida mehmon kodi nima ekanligini, xost kodi qanday bo'lganini yozishingiz kerak (bu holda bayt kod). U ishga tushadi, bir nechta tarjima bloklarini bajaradi, men qoldirgan disk raskadrovka xabarini yozadi, RCU endi ishga tushadi va... ishlamay qoladi. abort()
funksiya ichida free()
. Funksiya bilan shug'ullanish orqali free()
Ajratilgan xotiradan oldingi sakkiz baytda joylashgan yig'ma blokning sarlavhasida blok hajmi yoki shunga o'xshash narsa o'rniga axlat borligini aniqlashga muvaffaq bo'ldik.
Uyumni yo'q qilish - naqadar yoqimli ... Bunday holatda, foydali vosita bor - (agar iloji bo'lsa) bir xil manbalardan, mahalliy binarni yig'ing va Valgrind ostida boshqaring. Biroz vaqt o'tgach, ikkilik tayyor bo'ldi. Men uni bir xil variantlar bilan ishga tushiraman - u hatto ishga tushirish vaqtida ham ishlamay qoladi. Bu, albatta, yoqimsiz - aftidan, manbalar bir xil emas edi, bu ajablanarli emas, chunki konfiguratsiya biroz boshqacha variantlarni ko'rib chiqdi, lekin menda Valgrind bor - avval men bu xatoni tuzataman, keyin omadim kelsa. , asl nusxasi paydo bo'ladi. Men Valgrind ostida xuddi shu narsani ishlayapman ... Y-y-y, y-y-y, uh-uh, u boshlandi, odatdagidek ishga tushirildi va xotiraga noto'g'ri kirish haqida hech qanday ogohlantirishsiz asl xatolikdan o'tdi, qulash haqida gapirmasa ham. Hayot, ular aytganidek, meni bunga tayyorlamadi - halokatli dastur Walgrind ostida ishga tushirilganda ishdan chiqishni to'xtatadi. Bu nima bo'lganligi sir. Mening farazim shundan iboratki, ishga tushirish vaqtidagi avariyadan so'ng joriy ko'rsatma yaqinida bir marta gdb ish ko'rsatdi. memset
-a ikkalasidan ham foydalangan holda to'g'ri ko'rsatgich bilan mmx
, yoki xmm
registrlar, keyin, ehtimol, bu qandaydir moslashtirish xatosi edi, garchi bunga ishonish hali ham qiyin.
Mayli, Valgrind bu yerda yordam bermayapti. Va bu erda eng jirkanch narsa boshlandi - hamma narsa hatto boshlanganga o'xshaydi, lekin millionlab ko'rsatmalar oldin sodir bo'lishi mumkin bo'lgan voqea tufayli mutlaqo noma'lum sabablarga ko'ra ishdan chiqadi. Uzoq vaqt davomida qanday qilib yaqinlashish ham aniq emas edi. Oxir-oqibat, men hali ham o'tirib, disk raskadrovka qilishim kerak edi. Sarlavha qayta yozilganini chop etish uning raqamga o'xshamasligini, balki qandaydir ikkilik ma'lumotlarga o'xshashligini ko'rsatdi. Va, mana, bu ikkilik satr BIOS faylida topildi - ya'ni endi bu bufer to'lib ketganligini oqilona ishonch bilan aytish mumkin edi va hatto bu buferga yozilganligi aniq. Xo'sh, shunga o'xshash narsa - Emscripten-da, xayriyatki, manzil maydonining tasodifiyligi yo'q, unda teshiklar ham yo'q, shuning uchun siz oxirgi ishga tushirishdan boshlab ko'rsatgich orqali ma'lumotlarni chiqarish uchun kodning o'rtasida biron bir joyga yozishingiz mumkin, ma'lumotlarga qarang, ko'rsatkichga qarang va agar u o'zgarmagan bo'lsa, o'ylash uchun oziq-ovqat oling. To'g'ri, har qanday o'zgarishlardan keyin ulanish uchun bir necha daqiqa kerak bo'ladi, lekin nima qila olasiz? Natijada, BIOS-ni vaqtinchalik buferdan mehmon xotirasiga ko'chiradigan ma'lum bir qator topildi - va, albatta, buferda etarli joy yo'q edi. Ushbu g'alati bufer manzilining manbasini topish funktsiyaga olib keldi qemu_anon_ram_alloc
faylda oslib-posix.c
- mantiq shunday edi: ba'zida manzilni 2 MB hajmdagi ulkan sahifaga tekislash foydali bo'lishi mumkin, buning uchun biz so'raymiz. mmap
avval bir oz ko'proq, keyin esa yordam bilan ortiqchasini qaytaramiz munmap
. Va agar bunday tekislash talab qilinmasa, biz 2 MB o'rniga natijani ko'rsatamiz getpagesize()
- mmap
u hali ham tekislangan manzilni beradi ... Shunday qilib, Emscripten'da mmap
shunchaki qo'ng'iroq qiladi malloc
, lekin, albatta, u sahifada tekislanmaydi. Umuman olganda, bir necha oy davomida meni xafa qilgan xato o'zgartirish orqali tuzatildi dvux chiziqlar.
Chaqiruv funksiyalarining xususiyatlari
Va endi protsessor nimanidir hisoblamoqda, Qemu ishdan chiqmaydi, lekin ekran yoqilmaydi va protsessor chiqishiga qarab tezda halqalarga o'tadi. -d exec,in_asm,out_asm
. Gipoteza paydo bo'ldi: taymer uzilishlari (yoki umuman, barcha uzilishlar) kelmaydi. Haqiqatan ham, agar siz biron bir sababga ko'ra ishlagan mahalliy yig'ilishdagi uzilishlarni burab qo'ysangiz, xuddi shunday rasmga ega bo'lasiz. Ammo bu umuman javob emas edi: yuqoridagi variant bilan chiqarilgan izlarni taqqoslash shuni ko'rsatdiki, ijro etilish traektoriyalari juda erta farqlanadi. Bu erda shuni aytish kerakki, ishga tushirish moslamasi yordamida yozilgan narsalarni taqqoslash emrun
mahalliy yig'ilishning chiqishi bilan disk raskadrovka chiqishi butunlay mexanik jarayon emas. Brauzerda ishlaydigan dastur qanday ulanishini aniq bilmayman emrun
, lekin chiqishdagi ba'zi chiziqlar qayta tartibga solingan bo'lib chiqadi, shuning uchun farqdagi farq traektoriyalar ajralib chiqdi deb taxmin qilish uchun hali sabab emas. Umuman olganda, ko'rsatmalarga ko'ra aniq bo'ldi ljmpl
turli manzillarga o'tish mavjud bo'lib, yaratilgan bayt-kod tubdan farq qiladi: birida yordamchi funksiyani chaqirish bo'yicha ko'rsatma mavjud, ikkinchisida esa yo'q. Ko'rsatmalarni o'rganib chiqqandan so'ng va ushbu ko'rsatmalarni tarjima qiladigan kodni o'rganib chiqqandan so'ng, birinchi navbatda, registrda ro'yxatdan o'tganligi ma'lum bo'ldi. cr0
protsessorni himoyalangan rejimga o'tkazgan, ikkinchidan, JS versiyasi hech qachon himoyalangan rejimga o'tmaganligi haqida yordamchi yordamida yozuv ham amalga oshirildi. Ammo haqiqat shundaki, Emscripten-ning yana bir xususiyati - bu ko'rsatmalarni bajarish kabi kodlarga toqat qilishni istamasligi. call
TCI da, bu har qanday funktsiya ko'rsatkichi turiga olib keladi long long f(int arg0, .. int arg9)
- funktsiyalar to'g'ri argumentlar soni bilan chaqirilishi kerak. Agar ushbu qoida buzilgan bo'lsa, disk raskadrovka sozlamalariga qarab, dastur buziladi (bu yaxshi) yoki umuman noto'g'ri funktsiyani chaqiradi (bu disk raskadrovka qilish achinarli bo'ladi). Uchinchi variant ham bor - argumentlarni qo'shadigan / olib tashlaydigan o'ramlarni yaratishni yoqing, lekin umuman olganda, bu o'ramlar juda ko'p joy egallaydi, garchi aslida menga yuzdan bir oz ko'proq o'ram kerak. Buning o'zi juda achinarli, ammo jiddiyroq muammo paydo bo'ldi: o'rash funktsiyalarining yaratilgan kodida argumentlar o'zgartirildi va aylantirildi, lekin ba'zida yaratilgan argumentlar bilan funktsiya chaqirilmadi - yaxshi, xuddi mening libffi dasturim. Ya'ni, ba'zi yordamchilar shunchaki qatl qilinmagan.
Yaxshiyamki, Qemu-da sarlavha fayli ko'rinishidagi mashina tomonidan o'qiladigan yordamchi ro'yxatlar mavjud.
DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)
Ular juda kulgili ishlatiladi: birinchidan, makrolar eng g'alati tarzda qayta ta'riflanadi DEF_HELPER_n
, va keyin yoqiladi helper.h
. Ibratli tuzilmani ishga tushirish va vergulga kengaytirilganda, so'ngra massiv aniqlanadi va elementlar o'rniga - #include <helper.h>
Natijada, men kutubxonani ish joyida sinab ko'rish imkoniyatiga ega bo'ldim
Shunday qilib, shundan keyin protsessor ishlayotgandek tuyuldi. Buning sababi, memtest86+ mahalliy yig'ilishda ishlay olgan bo'lsa-da, ekran hech qachon ishga tushirilmaganga o'xshaydi. Bu erda Qemu blokining I/U kodi koroutinlarda yozilganligini aniqlashtirish kerak. Emscripten o'zining juda murakkab dasturiga ega, ammo u hali ham Qemu kodida qo'llab-quvvatlanishi kerak edi va siz hozir protsessorni disk raskadrovka qilishingiz mumkin: Qemu variantlarni qo'llab-quvvatlaydi -kernel
, -initrd
, -append
, uning yordamida siz Linuxni yoki, masalan, memtest86+ ni blokli qurilmalardan umuman foydalanmasdan yuklashingiz mumkin. Ammo bu erda muammo: mahalliy yig'ilishda Linux yadrosi chiqishini konsolga ko'rish mumkin. -nographic
, va brauzerdan u ishga tushirilgan joydan terminalga chiqmaydi emrun
, kelmadi. Ya'ni, bu aniq emas: protsessor ishlamayapti yoki grafik chiqishi ishlamayapti. Keyin biroz kutish xayolimga keldi. Ma'lum bo'lishicha, "protsessor uxlamayapti, shunchaki sekin miltillaydi" va taxminan besh daqiqadan so'ng yadro konsolga bir nechta xabarlarni tashladi va osishda davom etdi. Protsessor, umuman olganda, ishlashi aniq bo'ldi va biz SDL2 bilan ishlash uchun kodni o'rganishimiz kerak. Afsuski, men bu kutubxonadan qanday foydalanishni bilmayman, shuning uchun ba'zi joylarda tasodifiy harakat qilishim kerak edi. Bir nuqtada, parallel0 chizig'i ko'k fonda ekranda miltilladi, bu ba'zi fikrlarni taklif qildi. Oxir-oqibat, muammo Qemu bir jismoniy oynada bir nechta virtual oynalarni ochishida bo'lib chiqdi, ular orasida Ctrl-Alt-n yordamida o'tish mumkin: u mahalliy tuzilmada ishlaydi, lekin Emscripten-da emas. Variantlar yordamida keraksiz oynalardan qutulganingizdan so'ng -monitor none -parallel none -serial none
va har bir ramkada butun ekranni kuch bilan qayta chizish bo'yicha ko'rsatmalar, hamma narsa birdan ishladi.
Korutinlar
Shunday qilib, brauzerda emulyatsiya ishlaydi, lekin siz unda biron bir qiziqarli bitta floppi ishga tushira olmaysiz, chunki kiritish-chiqarish bloki yo'q - siz koroutinlarni qo'llab-quvvatlashni amalga oshirishingiz kerak. Qemu allaqachon bir nechta koroutin orqa tomonlarga ega, ammo JavaScript va Emscripten kod generatorining tabiati tufayli siz shunchaki steklarni jonglyor qilishni boshlay olmaysiz. Ko'rinishidan, "hamma narsa yo'qoldi, gips olib tashlandi", ammo Emscripten ishlab chiquvchilari allaqachon hamma narsaga g'amxo'rlik qilishgan. Bu juda kulgili amalga oshiriladi: keling, bunday shubhali funktsiya chaqiruvini chaqiraylik emscripten_sleep
va Asyncify mexanizmidan foydalanadigan bir qancha boshqalar, shuningdek, ko'rsatgich qo'ng'iroqlari va har qanday funktsiyaga qo'ng'iroqlar, agar oldingi ikkita holatdan biri stekning pastroqda sodir bo'lishi mumkin. Va endi, har bir shubhali qo'ng'iroqdan oldin, biz asinxron kontekstni tanlaymiz va qo'ng'iroqdan so'ng darhol asinxron qo'ng'iroq sodir bo'lganligini tekshiramiz va agar mavjud bo'lsa, biz ushbu asinxron kontekstda barcha mahalliy o'zgaruvchilarni saqlaymiz, qaysi funktsiyani ko'rsatamiz. boshqaruvni bajarishni davom ettirishimiz kerak bo'lgan vaqtga o'tkazish va joriy funktsiyadan chiqish. Bu erda ta'sirni o'rganish uchun imkoniyatlar mavjud -O3
. Men yaratilgan kodni ishga tushiraman va Chromium xotirani iste'mol qiladi va ishdan chiqadi. Keyin tasodifan uning yuklab olmoqchi bo'lgan narsasiga qaradim... Xo'sh, nima deyman, agar mendan 500+ MB Javascriptni o'ylab o'rganishni va optimallashtirishni so'rashsa, men ham qotib qolgan bo'lardim.
Afsuski, Asyncify qo'llab-quvvatlash kutubxonasi kodidagi tekshiruvlar to'liq mos kelmadi longjmp
-s virtual protsessor kodida ishlatiladi, lekin bu tekshiruvlarni o'chirib qo'yadigan va kontekstlarni kuch bilan tiklaydigan kichik tuzatishdan so'ng, kod ishladi. Va keyin g'alati narsa boshlandi: ba'zida sinxronizatsiya kodidagi tekshiruvlar ishga tushirildi - agar ijro mantig'iga ko'ra, uni blokirovka qilish kerak bo'lsa, kodni buzadiganlar - kimdir allaqachon qo'lga kiritilgan mutexni olishga harakat qilgan. Yaxshiyamki, bu ketma-ketlashtirilgan kodda mantiqiy muammo emas edi - men oddiygina Emscripten tomonidan taqdim etilgan standart asosiy tsikl funksiyasidan foydalanardim, lekin ba'zida asinxron qo'ng'iroq stekni to'liq ochadi va o'sha paytda u muvaffaqiyatsiz bo'ladi. setTimeout
asosiy tsikldan - shunday qilib, kod oldingi iteratsiyadan chiqmasdan asosiy sikl iteratsiyasiga kirdi. Cheksiz tsiklda qayta yozildi va emscripten_sleep
, va mutekslar bilan bog'liq muammolar to'xtadi. Kod yanada mantiqiy bo'ldi - aslida menda keyingi animatsiya ramkasini tayyorlaydigan biron bir kod yo'q - protsessor shunchaki biror narsani hisoblab chiqadi va ekran vaqti-vaqti bilan yangilanadi. Biroq, muammolar shu bilan to'xtab qolmadi: ba'zida Qemu ijrosi hech qanday istisnosiz yoki xatoliksiz jimgina tugaydi. O'sha paytda men undan voz kechdim, lekin oldinga qarab, muammo bu ekanligini aytaman: koroutin kod, aslida, foydalanmaydi. setTimeout
(yoki hech bo'lmaganda siz o'ylagandek tez-tez emas): funktsiya emscripten_yield
shunchaki asinxron chaqiruv bayrog'ini o'rnatadi. Gap shundaki emscripten_coroutine_next
asinxron funksiya emas: ichki tomondan u bayroqni tekshiradi, uni qayta o'rnatadi va boshqaruvni kerakli joyga o'tkazadi. Ya'ni, stekning reklamasi shu erda tugaydi. Muammo shundaki, men mavjud coroutine backenddan muhim kod qatorini ko‘chirmaganim uchun korutin hovuzi o‘chirilganida paydo bo‘lgan use-after-free tufayli, funksiya qemu_in_coroutine
rost qaytarildi, aslida u yolg'onni qaytarishi kerak edi. Bu qo'ng'iroqqa sabab bo'ldi emscripten_yield
, yuqorida stackda hech kim yo'q edi emscripten_coroutine_next
, stack eng yuqoriga ochildi, lekin yo'q setTimeout
, men allaqachon aytganimdek, namoyish etilmagan.
JavaScript kodini yaratish
Va bu erda, aslida, va'da qilingan "qiyma go'shtni orqaga qaytarish". Unchalik emas. Albatta, agar biz brauzerda Qemu-ni va unda Node.js-ni ishga tushirsak, Qemu-da kod ishlab chiqarilgandan so'ng, biz mutlaqo noto'g'ri JavaScript-ga ega bo'lamiz. Ammo baribir, qandaydir teskari o'zgarish.
Birinchidan, Qemu qanday ishlashi haqida bir oz. Iltimos, meni darhol kechiring: men professional Qemu dasturchisi emasman va mening xulosalarim ba'zi joylarda noto'g'ri bo'lishi mumkin. Ular aytganidek, "talabaning fikri o'qituvchining fikriga, Peanoning aksiomatikasiga va sog'lom fikrga mos kelishi shart emas". Qemuda ma'lum miqdordagi qo'llab-quvvatlanadigan mehmon arxitekturasi mavjud va ularning har biri uchun o'xshash katalog mavjud target-i386
. Qurilishda siz bir nechta mehmon arxitekturasini qo'llab-quvvatlashni belgilashingiz mumkin, ammo natijada bir nechta ikkilik bo'ladi. Mehmon arxitekturasini qo'llab-quvvatlash uchun kod, o'z navbatida, TCG (Tiny Code Generator) allaqachon xost arxitekturasi uchun mashina kodiga aylanadigan ba'zi ichki Qemu operatsiyalarini yaratadi. Tcg katalogida joylashgan readme faylida aytilganidek, bu dastlab oddiy C kompilyatorining bir qismi bo'lib, keyinchalik JIT uchun moslashtirilgan. Shuning uchun, masalan, ushbu hujjat nuqtai nazaridan maqsadli arxitektura endi mehmon arxitekturasi emas, balki xost arxitekturasidir. Bir vaqtning o'zida yana bir komponent paydo bo'ldi - Tiny Code Interpreter (TCI), u ma'lum bir xost arxitekturasi uchun kod generatori bo'lmaganda kodni (deyarli bir xil ichki operatsiyalarni) bajarishi kerak. Aslida, uning hujjatlarida ta'kidlanganidek, bu tarjimon har doim ham tezlik bo'yicha nafaqat miqdoriy, balki sifat jihatidan ham JIT kod ishlab chiqaruvchisi kabi ishlamasligi mumkin. Garchi men uning tavsifi to'liq tegishli ekanligiga ishonchim komil emas.
Avvaliga men to'liq huquqli TCG backend yaratishga harakat qildim, lekin tezda manba kodida va bayt-kod ko'rsatmalarining to'liq aniq bo'lmagan tavsifida chalkashib ketdim, shuning uchun men TCI tarjimonini o'rashga qaror qildim. Bu bir qator afzalliklarni berdi:
- kod generatorini amalga oshirishda siz ko'rsatmalar tavsifiga emas, balki tarjimon kodiga qarashingiz mumkin
- Siz har bir duch kelgan tarjima bloki uchun emas, balki, masalan, faqat yuzinchi bajarilgandan keyin funktsiyalarni yaratishingiz mumkin.
- agar yaratilgan kod o'zgarsa (va bu mumkin bo'lsa, yamoq so'zini o'z ichiga olgan nomlarga ega bo'lgan funktsiyalarga ko'ra), men yaratilgan JS kodini bekor qilishim kerak, lekin hech bo'lmaganda uni qayta tiklash uchun biror narsaga ega bo'laman.
Uchinchi nuqtaga kelsak, kod birinchi marta bajarilgandan keyin tuzatish mumkinligiga ishonchim komil emas, lekin birinchi ikkita nuqta etarli.
Dastlab, kod asl bayt-kod ko'rsatmasi manzilida katta kalit ko'rinishida yaratilgan, ammo keyin Emscripten, yaratilgan JS-ni optimallashtirish va qayta ishlash haqidagi maqolani eslab, men ko'proq inson kodini yaratishga qaror qildim, ayniqsa empirik tarzda. Ma'lum bo'lishicha, tarjima blokiga yagona kirish nuqtasi uning Boshlash nuqtasidir. Bir muncha vaqt o'tgach, bizda kod generatori paydo bo'ldi, u ifs bilan kod yaratdi (garchi tsikllarsiz). Ammo omadsizlik yuz berdi, u qulab tushdi va ko'rsatmalar noto'g'ri uzunlikda ekanligi haqida xabar berdi. Bundan tashqari, ushbu rekursiya darajasidagi oxirgi ko'rsatma edi brcond
. OK, men ushbu ko'rsatmani rekursiv qo'ng'iroqdan oldin va keyin hosil qilish uchun bir xil tekshiruvni qo'shaman va ... ulardan hech biri bajarilmadi, lekin tasdiqlashni o'zgartirgandan keyin ular hali ham muvaffaqiyatsiz bo'ldi. Oxir-oqibat, yaratilgan kodni o'rganib chiqqanimdan so'ng, men kommutatsiyadan so'ng joriy ko'rsatmaga ko'rsatgich stekdan qayta yuklanganligini va ehtimol yaratilgan JavaScript kodi bilan yozilishini angladim. Va shunday bo'ldi. Buferni bir megabaytdan o'nga oshirish hech narsaga olib kelmadi va kod generatori aylanalarda ishlayotgani aniq bo'ldi. Biz hozirgi sil chegarasidan tashqariga chiqmaganimizni tekshirishimiz kerak edi va agar chiqsak, keyingi TB manzilini minus belgisi bilan bering, shunda biz ijroni davom ettiramiz. Bunga qo'shimcha ravishda, bu "agar ushbu bayt-kod qismi o'zgargan bo'lsa, qaysi yaratilgan funktsiyalarni bekor qilish kerak?" Degan muammoni hal qiladi. — faqat ushbu tarjima blokiga mos keladigan funksiyani bekor qilish kerak. Aytgancha, men Chromium-da hamma narsani tuzatgan bo'lsam ham (men Firefox-dan foydalanaman va tajribalar uchun alohida brauzerdan foydalanish menga qulayroq), Firefox menga asm.js standarti bilan nomuvofiqliklarni tuzatishga yordam berdi, shundan so'ng kod tezroq ishlay boshladi. Chromium.
Yaratilgan kodga misol
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"]
xulosa
Shunday qilib, ish hali tugallanmagan, lekin men yashirincha bu uzoq muddatli qurilishni mukammallikka olib kelishdan charchadim. Shuning uchun men hozir bor narsamni nashr etishga qaror qildim. Kod joylarda biroz qo'rqinchli, chunki bu tajriba va nima qilish kerakligi oldindan aniq emas. Ehtimol, Qemu-ning zamonaviyroq versiyasi ustiga oddiy atom majburiyatlarini chiqarishga arziydi. Ayni paytda, Gita-da blog formatida mavzu mavjud: hech bo'lmaganda qandaydir tarzda o'tgan har bir "daraja" uchun rus tilida batafsil sharh qo'shilgan. Aslida, ushbu maqola ko'p jihatdan xulosani qayta ko'rib chiqishdir git log
.
Hammasini sinab ko'rishingiz mumkin
Nima allaqachon ishlaydi:
- x86 virtual protsessor ishlaydi
- JIT kod generatorining mashina kodidan JavaScript-ga ishlaydigan prototipi mavjud
- Boshqa 32-bitli mehmon arxitekturalarini yig'ish uchun shablon mavjud: hozirda siz Linuxni yuklash bosqichida brauzerda muzlab qolgan MIPS arxitekturasiga qoyil qolishingiz mumkin.
Yana nima qila olasiz
- Emulyatsiyani tezlashtiring. JIT rejimida ham u Virtual x86 dan sekinroq ishlaydi (lekin potentsial ravishda ko'plab emulyatsiya qilingan apparat va arxitekturaga ega butun Qemu mavjud)
- Oddiy interfeys yaratish uchun - ochig'ini aytsam, men yaxshi veb-dasturchi emasman, shuning uchun men hozircha standart Emscripten qobig'ini iloji boricha qayta yaratdim.
- Keyinchalik murakkab Qemu funktsiyalarini ishga tushirishga harakat qiling - tarmoq, VM migratsiyasi va boshqalar.
- UPS: Qemu va boshqa loyihalarning oldingi porterlari singari siz o'zingizning bir nechta ishlanmalaringiz va xatolar haqida hisobotlarni Emscripten upstream-ga topshirishingiz kerak bo'ladi. Mening vazifamning bir qismi sifatida Emscripten-ga qo'shgan hissalarini bilvosita ishlata olganlari uchun ularga rahmat.
Manba: www.habr.com