QEMU.js: endi jiddiy va WASM bilan

Bir vaqtlar men o'yin-kulgiga qaror qildim jarayonning teskariligini isbotlang va mashina kodidan JavaScript (aniqrog'i, Asm.js) yaratishni o'rganing. Tajriba uchun QEMU tanlandi va bir muncha vaqt o'tgach, Xabrda maqola yozildi. Izohlarda menga loyihani WebAssembly-da qayta tiklash va hatto o'zimni tark etishni maslahat berishdi. deyarli tugadi Men qandaydir tarzda loyihani xohlamadim ... Ish davom etayotgan edi, lekin juda sekin va hozir, yaqinda o'sha maqolada paydo bo'ldi sharh "Xo'sh, hammasi qanday tugadi?" mavzusida. Mening batafsil javobimga javoban, men "bu maqolaga o'xshaydi" deb eshitdim. Xo'sh, agar imkoningiz bo'lsa, maqola bo'ladi. Ehtimol, kimdir buni foydali deb topadi. Undan o'quvchi QEMU kodini yaratish backendlarining dizayni, shuningdek, veb-ilova uchun Just-in-Time kompilyatorini qanday yozish haqida ba'zi faktlarni bilib oladi.

vazifalar

QEMU-ni JavaScript-ga qanday qilib "qandaydir" portlashni allaqachon o'rganganim uchun, bu safar buni oqilona qilishga va eski xatolarni takrorlamaslikka qaror qilindi.

Birinchi xato: chiqish nuqtasidan filial

Mening birinchi xatoim o'z versiyamni 2.4.1 yuqoridagi versiyadan ajratib olish edi. Keyin menga yaxshi fikr bo'lib tuyuldi: agar nuqta chiqarish mavjud bo'lsa, u oddiy 2.4 dan ko'ra barqarorroq va undan ham ko'proq filial master. Va men o'z xatolarimning adolatli miqdorini qo'shishni rejalashtirganim uchun, menga boshqa hech kimning xatolari kerak emas edi. Shunday bo'lsa kerak. Ammo bu erda gap: QEMU bir joyda turmaydi va bir nuqtada ular hatto yaratilgan kodni 10 foizga optimallashtirishni e'lon qilishdi."Ha, endi men muzlab qolaman", deb o'yladim va buzildim. Bu erda biz chekinishimiz kerak: QEMU.js ning bir torli tabiati va original QEMU ko'p tarmoqli yo'qligini anglatmasligi (ya'ni bir vaqtning o'zida bir-biriga bog'liq bo'lmagan bir nechta kod yo'llarini boshqarish qobiliyati va faqat "barcha yadrolardan foydalanish" emas) buning uchun juda muhim, men tashqaridan qo'ng'iroq qilishim uchun "o'chirish" kerak edi iplarning asosiy vazifalari. Bu birlashish jarayonida ba'zi tabiiy muammolarni keltirib chiqardi. Biroq, filialdan ba'zi o'zgarishlar haqiqatdir master, men kodimni birlashtirishga urinib ko'rganim, shuningdek, nuqta relizda (va shuning uchun mening filialimda) gilos tanlangan edi, ehtimol, bu qulaylik qo'shmagan bo'lardi.

Umuman olganda, men prototipni tashlab yuborish, uni qismlarga ajratish va noldan yangi va endi yangi narsaga asoslangan yangi versiyani yaratish mantiqiy deb qaror qildim. master.

Ikkinchi xato: TLP metodologiyasi

Aslida, bu xato emas, umuman olganda, bu "qaerga va qanday harakat qilish kerak?" va umuman, "biz u erga boramizmi?" ni to'liq tushunmaslik sharoitida loyiha yaratishning o'ziga xos xususiyati. Bunday sharoitlarda noqulay dasturlash asosli variant edi, lekin, tabiiyki, men buni keraksiz tarzda takrorlashni xohlamadim. Bu safar men buni donolik bilan qilishni xohladim: atomik majburiyatlar, kodni ongli ravishda o'zgartirish (va bir marta Linus Torvalds Wikiquotega ko'ra kimdir haqida aytganidek, "tasodifiy belgilarni kompilyatsiya qilmaguncha (ogohlantirishlar bilan) birlashtirish" emas) va hokazo.

Uchinchi xato: o'tish joyini bilmasdan suvga tushish

Men hali ham bundan butunlay xalos bo'lganim yo'q, lekin endi men hech qanday qarshilik ko'rsatish yo'lidan bormaslikka va buni "kattalardek" qilishga qaror qildim, ya'ni o'zimning TCG backendimni noldan yozishga qaror qildim. keyinroq aytish kerak: "Ha, bu, albatta, sekin, lekin men hamma narsani nazorat qila olmayman - TCI shunday yozilgan ..." Bundan tashqari, bu dastlab aniq yechim kabi tuyuldi, chunki Men ikkilik kodni yarataman. Ular aytganidek, "Gent yig'ildiу, lekin u emas": kod, albatta, ikkilikdir, lekin boshqaruvni unga shunchaki o'tkazib bo'lmaydi - uni kompilyatsiya qilish uchun brauzerga aniq surtish kerak, natijada JS dunyosidan ma'lum bir ob'ekt paydo bo'ladi, bu hali ham kerak bo'ladi. bir joyda qutqariladi. Biroq, oddiy RISC arxitekturalarida, men tushunganimdek, odatiy holat - bu qayta tiklangan kod uchun yo'riqnoma keshini aniq tiklash zarurati - agar bu bizga kerak bo'lmasa, har holda, bu yaqin. Bundan tashqari, oxirgi urinishimdan men boshqaruv translatsiya blokining o'rtasiga o'tkazilmaganligini bilib oldim, shuning uchun bizga hech qanday ofsetdan sharhlangan bayt-kod kerak emas va biz uni oddiygina TB funksiyasidan yaratishimiz mumkin. .

Ular kelib, tepishdi

Men iyul oyida kodni qayta yozishni boshlagan bo'lsam ham, sehrli zarba sezilmasdi: odatda GitHub'dan xatlar Muammolar va Pull so'rovlariga javoblar haqida bildirishnoma sifatida keladi, lekin bu erda, birdan mavzuda eslatib o'tish Binaryen qemu backend sifatida kontekstda, "U shunga o'xshash narsa qildi, ehtimol u biror narsa aytadi." Biz Emscriptenning tegishli kutubxonasidan foydalanish haqida gapirgan edik Binaryen WASM JIT yaratish uchun. Xo'sh, men sizda Apache 2.0 litsenziyasi borligini aytdim va QEMU umuman GPLv2 ostida tarqatiladi va ular juda mos kelmaydi. To'satdan litsenziya bo'lishi mumkinligi ma'lum bo'ldi uni qandaydir tarzda tuzating (Bilmayman: ehtimol uni o'zgartirish, ehtimol ikki tomonlama litsenziyalash, ehtimol boshqa narsa ...). Bu, albatta, meni xursand qildi, chunki o'sha vaqtga qadar men allaqachon diqqat bilan qaragan edim ikkilik format WebAssembly, va men qandaydir qayg'uli va tushunarsiz edim. Bundan tashqari, o'tish grafigi bilan asosiy bloklarni yutib yuboradigan, bayt kodini ishlab chiqaradigan va hatto kerak bo'lsa, uni tarjimonning o'zida ishga tushiradigan kutubxona ham mavjud edi.

Keyin ko'proq narsa bor edi maktub QEMU pochta ro'yxatida, lekin bu ko'proq "Bu kimga kerak?" Degan savolga tegishli. Va shunday birdan, zarur bo'lib chiqdi. Hech bo'lmaganda, agar u tez yoki kamroq ishlayotgan bo'lsa, quyidagi foydalanish imkoniyatlarini birlashtira olasiz:

  • hech qanday o'rnatmasdan ta'limga oid narsalarni ishga tushirish
  • iOS-da virtualizatsiya, bu erda, mish-mishlarga ko'ra, tezda kod yaratish huquqiga ega bo'lgan yagona dastur JS dvigatelidir (bu haqiqatmi?)
  • mini-OSni namoyish qilish - bitta floppi, o'rnatilgan, barcha turdagi proshivkalar va boshqalar ...

Brauzerning ishlash vaqti xususiyatlari

Yuqorida aytib o'tganimdek, QEMU multithreading bilan bog'langan, ammo brauzerda u yo'q. Xo'sh, ya'ni, yo'q ... Avvaliga u umuman yo'q edi, keyin WebWorkers paydo bo'ldi - men tushunganimdek, bu xabarlarni uzatishga asoslangan ko'p qirrali. umumiy o'zgaruvchilarsiz. Tabiiyki, bu umumiy xotira modeli asosida mavjud kodni ko'chirishda jiddiy muammolarni keltirib chiqaradi. Keyin jamoatchilik bosimi ostida bu nom ostida ham amalga oshirildi SharedArrayBuffers. Bu asta-sekin joriy etildi, ular turli brauzerlarda uning ishga tushirilishini nishonladilar, keyin ular Yangi yilni nishonlashdi, keyin esa Meltdown... Shundan so'ng ular vaqtni o'lchashni qo'pol yoki qo'pol, lekin umumiy xotira yordamida va a hisoblagichni oshiruvchi ip, hammasi bir xil u juda aniq ishlaydi. Shunday qilib, biz umumiy xotira bilan multithreadingni o'chirib qo'ydik. Ko'rinishidan, ular keyinchalik uni qayta yoqdilar, ammo birinchi tajribadan ma'lum bo'lishicha, usiz hayot bor va agar shunday bo'lsa, biz buni ko'p oqimga tayanmasdan qilishga harakat qilamiz.

Ikkinchi xususiyat - bu stek bilan past darajadagi manipulyatsiyalarning mumkin emasligi: siz shunchaki qabul qila olmaysiz, joriy kontekstni saqlay olmaysiz va yangi stek bilan yangisiga o'tolmaysiz. Qo'ng'iroqlar to'plami JS virtual mashinasi tomonidan boshqariladi. Ko'rinishidan, muammo nimada, chunki biz avvalgi oqimlarni to'liq qo'lda boshqarishga qaror qildik? Gap shundaki, QEMU-da kirish/chiqarish bloki koroutinlar orqali amalga oshiriladi va bu erda past darajadagi stek manipulyatsiyasi foydali bo'ladi. Yaxshiyamki, Emscipten allaqachon asinxron operatsiyalar uchun mexanizmni o'z ichiga oladi, hatto ikkitasi: Asinxifikatsiya и Tarjimon. Birinchisi yaratilgan JavaScript kodida sezilarli shishish orqali ishlaydi va endi qo'llab-quvvatlanmaydi. Ikkinchisi joriy "to'g'ri yo'l" bo'lib, mahalliy tarjimon uchun bayt-kod yaratish orqali ishlaydi. Bu, albatta, sekin ishlaydi, lekin kodni shishirmaydi. To'g'ri, ushbu mexanizm uchun koroutinlarni qo'llab-quvvatlash mustaqil ravishda amalga oshirilishi kerak edi (Asyncify uchun allaqachon koroutinlar yozilgan va Emterpreter uchun taxminan bir xil API amalga oshirilgan edi, siz ularni ulashingiz kerak edi).

Ayni paytda men kodni WASM-da tuzilgan va Emterpreter yordamida talqin qilingan kodga bo'lishning uddasidan chiqmadim, shuning uchun blokirovka qurilmalari hali ishlamayapti (keyingi seriyalarga qarang, ular aytganidek ...). Ya'ni, oxirida siz bunday kulgili qatlamli narsaga ega bo'lishingiz kerak:

  • izohlangan blok I/U. Xo'sh, siz haqiqatan ham NVMe-ni mahalliy ishlashi bilan taqlid qilishni kutganmidingiz? 🙂
  • statik ravishda tuzilgan asosiy QEMU kodi (tarjimon, boshqa emulyatsiya qilingan qurilmalar va boshqalar).
  • WASM-ga dinamik ravishda tuzilgan mehmon kodini

QEMU manbalarining xususiyatlari

Siz allaqachon taxmin qilganingizdek, mehmon arxitekturasini taqlid qilish uchun kod va xost mashinasi ko'rsatmalarini yaratish uchun kod QEMU-da ajratilgan. Aslida, bu biroz hiyla-nayrang:

  • mehmon arxitekturalari mavjud
  • bo'ladi tezlatgichlar, ya'ni, Linuxda apparat virtualizatsiyasi uchun KVM (mehmon va xost tizimlari uchun bir-biriga mos keladi), JIT kodini istalgan joyda yaratish uchun TCG. QEMU 2.9 dan boshlab, Windows-da HAXM apparat virtualizatsiya standartini qo'llab-quvvatlash paydo bo'ldi (tafsilotlar)
  • Agar apparat virtualizatsiyasi emas, balki TCG ishlatilsa, u har bir xost arxitekturasi, shuningdek universal tarjimon uchun alohida kod ishlab chiqarishni qo'llab-quvvatlaydi.
  • ... va bularning barchasi atrofida - taqlid qilingan tashqi qurilmalar, foydalanuvchi interfeysi, migratsiya, yozib olish-qayta o'ynash va boshqalar.

Aytgancha, bilasizmi: QEMU nafaqat butun kompyuterni, balki xost yadrosidagi alohida foydalanuvchi jarayoni uchun protsessorni ham taqlid qilishi mumkin, bu, masalan, ikkilik asboblar uchun AFL fuzzer tomonidan qo'llaniladi. Ehtimol, kimdir QEMU ning ushbu ish rejimini JS ga o'tkazishni xohlaydi? 😉

Ko'p yillik bepul dasturiy ta'minot singari, QEMU qo'ng'iroq orqali qurilgan configure и make. Aytaylik, siz biror narsa qo'shishga qaror qildingiz: TCG backend, ipni amalga oshirish, boshqa narsa. Autoconf bilan aloqa o'rnatishdan xursand bo'lishga/dahshatga tushishga shoshilmang (kerakli hollarda tagini chizing) - aslida, configure QEMU ko'rinishidan o'z-o'zidan yozilgan va hech narsadan yaratilmagan.

WebAssembly

Xo'sh, WebAssembly (aka WASM) deb ataladigan narsa nima? Bu Asm.js oʻrnini egallaydi, endi oʻzini yaroqli JavaScript kodi sifatida koʻrsatmaydi. Aksincha, u faqat ikkilik va optimallashtirilgan va hatto unga butun sonni yozish juda oddiy emas: ixchamlik uchun u formatda saqlanadi. LEB128.

Asm.js uchun qayta ishlash algoritmi haqida eshitgan bo'lishingiz mumkin - bu "yuqori darajadagi" oqimni boshqarish yo'riqnomalarini (ya'ni, agar-bo'lmasa, halqalar va boshqalar) qayta tiklash, ular uchun JS dvigatellari ishlab chiqilgan. past darajadagi LLVM IR, protsessor tomonidan bajariladigan mashina kodiga yaqinroq. Tabiiyki, QEMU ning oraliq vakili ikkinchisiga yaqinroq. Mana, bayt-kod, azobning oxiri bo'lganga o'xshaydi... Va keyin bloklar, agar-then-else va halqalar bor!..

Va bu Binaryen foydali bo'lishining yana bir sababi: u tabiiy ravishda WASM-da saqlanadigan narsaga yaqin yuqori darajadagi bloklarni qabul qilishi mumkin. Lekin u asosiy bloklar va ular orasidagi oʻtishlar grafigidan kod ham ishlab chiqishi mumkin. Xo'sh, men allaqachon WebAssembly saqlash formatini qulay C/C++ API orqasida yashirishini aytdim.

TCG (Tiny Code Generator)

TCG asli edi C kompilyatori uchun backend.Keyin, aftidan, u GCC bilan raqobatga dosh bera olmadi, lekin oxirida u QEMUda xost platformasi uchun kod yaratish mexanizmi sifatida o'z o'rnini topdi. Bundan tashqari, tarjimon tomonidan darhol bajariladigan ba'zi mavhum bayt kodlarini yaratadigan TCG backend ham mavjud, ammo men bu safar uni ishlatishdan qochishga qaror qildim. Biroq, QEMU-da funktsiya orqali hosil bo'lgan silga o'tishni yoqish mumkinligi tcg_qemu_tb_exec, bu men uchun juda foydali bo'lib chiqdi.

QEMU-ga yangi TCG backend qo'shish uchun siz quyi katalog yaratishingiz kerak tcg/<имя архитектуры> (Ushbu holatda, tcg/binaryen) va u ikkita faylni o'z ichiga oladi: tcg-target.h и tcg-target.inc.c и ro'yxatdan o'tish hammasi haqida configure. Siz u erga boshqa fayllarni qo'yishingiz mumkin, ammo bu ikkisining nomlaridan taxmin qilganingizdek, ular ikkalasi ham bir joyga qo'shiladi: biri oddiy sarlavha fayli sifatida (u ichiga kiritilgan) tcg/tcg.h, va u allaqachon kataloglardagi boshqa fayllarda tcg, accel va nafaqat), ikkinchisi - faqat kod parchasi sifatida tcg/tcg.c, lekin u o'zining statik funktsiyalariga kirish huquqiga ega.

Uning qanday ishlashini batafsil o'rganishga ko'p vaqt sarflashga qaror qilib, men ushbu ikkita faylning "skeletlari" ni boshqa backend ilovasidan ko'chirib oldim va buni litsenziya sarlavhasida ko'rsatib berdim.

Fayl tcg-target.h asosan shakldagi sozlamalarni o'z ichiga oladi #define-s:

  • maqsadli arxitekturada qancha registr va qancha kenglik bor (bizda xohlagancha, xohlagancha ko'p bor - savol ko'proq "to'liq maqsadli" arxitekturada brauzer tomonidan samaraliroq kodga nima yaratilishi haqida. ...)
  • xost ko'rsatmalarini tekislash: x86 da va hatto TCI da ko'rsatmalar umuman tekislanmagan, lekin men kod buferiga umuman ko'rsatmalarni emas, balki Binaryen kutubxonasi tuzilmalariga ko'rsatgichlarni qo'ymoqchiman, shuning uchun aytaman: 4 bayt
  • Backend qanday ixtiyoriy ko'rsatmalarni yaratishi mumkin - biz Binaryen-da topilgan hamma narsani o'z ichiga olamiz, tezlatgich qolganlarini oddiyroqlarga ajratsin.
  • Backend tomonidan so'ralgan TLB keshining taxminiy hajmi qancha. Gap shundaki, QEMUda hamma narsa jiddiy: mehmon MMUni hisobga olgan holda yuklash/do‘konni amalga oshiradigan yordamchi funksiyalar mavjud bo‘lsa-da (usiz biz hozir qayerda bo‘lardik?), ular tarjima keshini tuzilma shaklida saqlaydi, ularni qayta ishlash to'g'ridan-to'g'ri translyatsiya bloklariga joylashtirish uchun qulay. Savol shundaki, ushbu tuzilmadagi qaysi ofset kichik va tezkor buyruqlar ketma-ketligi bilan eng samarali tarzda qayta ishlanadi?
  • Bu yerda siz bir yoki ikkita rezervlangan registrlarning maqsadini sozlashingiz, funktsiya orqali TB chaqiruvini yoqishingiz va ixtiyoriy ravishda bir nechta kichik registrlarni tavsiflashingiz mumkin. inlinekabi funktsiyalarni bajaradi flush_icache_range (lekin bu bizning holatimizda emas)

Fayl tcg-target.inc.c, albatta, odatda o'lchamlari ancha katta va bir nechta majburiy funktsiyalarni o'z ichiga oladi:

  • ishga tushirish, shu jumladan qaysi ko'rsatmalar qaysi operandlarda ishlashi mumkinligi bo'yicha cheklovlar. Men boshqa backenddan ochiqchasiga ko'chirib oldim
  • bitta ichki bayt-kod ko'rsatmasini oladigan funksiya
  • Bu yerda siz yordamchi funksiyalarni ham qo'yishingiz mumkin va statik funksiyalardan ham foydalanishingiz mumkin tcg/tcg.c

Men o'zim uchun quyidagi strategiyani tanladim: keyingi tarjima blokining birinchi so'zlarida men to'rtta ko'rsatkichni yozdim: boshlang'ich belgisi (yaqindagi ma'lum bir qiymat). 0xFFFFFFFF, bu TBning hozirgi holatini aniqladi), kontekst, yaratilgan modul va disk raskadrovka uchun sehrli raqam. Avvaliga belgi qo'yildi 0xFFFFFFFF - nqayerda n - kichik ijobiy raqam va har safar tarjimon orqali bajarilganda u 1 ga ko'paydi. U yetganida 0xFFFFFFFE, kompilyatsiya bo'lib o'tdi, modul funktsiyalar jadvalida saqlangan, kichik "ishga tushirish moslamasi" ga import qilingan tcg_qemu_tb_exec, va modul QEMU xotirasidan olib tashlandi.

Klassiklarni talqin qilish uchun: "Crutch, bu tovushda progerning yuragi uchun qanchalik bir-biriga bog'langan ...". Biroq, xotira qayerdandir oqayotgan edi. Bundan tashqari, bu QEMU tomonidan boshqariladigan xotira edi! Menda kod bor edi, u keyingi ko'rsatmani yozayotganda (yaxshi, ya'ni ko'rsatgich) ilgari shu joyda havolasi bo'lgan birini o'chirib tashladi, ammo bu yordam bermadi. Aslida, eng oddiy holatda, QEMU ishga tushganda xotirani ajratadi va yaratilgan kodni u erda yozadi. Bufer tugagach, kod tashqariga tashlanadi va uning o'rniga keyingisi yozila boshlaydi.

Kodni o'rganib chiqqanimdan so'ng, men sehrli raqam bilan hiyla birinchi o'tishda ishga tushirilmagan buferda biron bir noto'g'ri narsani bo'shatib, to'pni yo'q qilishda muvaffaqiyatsizlikka uchramaslikka imkon berganini angladim. Ammo keyinroq funktsiyamni chetlab o'tish uchun buferni kim qayta yozadi? Emscripten ishlab chiquvchilari maslahat berganidek, muammoga duch kelganimda, natijada paydo bo'lgan kodni yana mahalliy dasturga o'tkazdim, unga Mozilla Record-Replay-ni o'rnatdim ... Umuman olganda, men oddiy narsani tushundim: har bir blok uchun, a struct TranslationBlock uning tavsifi bilan. Tasavvur qiling, qayerda ... To'g'ri, buferdagi blokdan oldin. Buni tushunib, men qo'ltiq tayoqchalaridan foydalanishni to'xtatishga qaror qildim (hech bo'lmaganda ba'zilari) va shunchaki sehrli raqamni tashladim va qolgan so'zlarni o'tkazdim. struct TranslationBlock, tarjima keshi qayta o'rnatilganda tezda o'tish mumkin bo'lgan yagona bog'langan ro'yxat yaratish va xotirani bo'shatish.

Ba'zi tayoqchalar qoladi: masalan, kod buferidagi belgilangan ko'rsatkichlar - ulardan ba'zilari oddiy BinaryenExpressionRef, ya'ni ular hosil qilingan asosiy blokga chiziqli qo'yilishi kerak bo'lgan ifodalarni ko'rib chiqadilar, qism - BBlar o'rtasida o'tish sharti, qism - qaerga borish kerak. Xo'sh, shartlarga muvofiq ulanishi kerak bo'lgan Relooper uchun allaqachon tayyorlangan bloklar mavjud. Ularni ajratib ko'rsatish uchun ularning barchasi kamida to'rt baytga tenglashtirilgan degan taxmindan foydalaniladi, shuning uchun yorliq uchun kamida muhim ikki bitni xavfsiz ishlatishingiz mumkin, agar kerak bo'lsa, uni olib tashlashni unutmasligingiz kerak. Aytgancha, bunday teglar allaqachon QEMU-da TCG tsiklidan chiqish sababini ko'rsatish uchun qo'llaniladi.

Binaryen-dan foydalanish

WebAssembly modullari funksiyalarni o'z ichiga oladi, ularning har biri ifoda bo'lgan tanani o'z ichiga oladi. Ifodalar - birlik va ikkilik operatsiyalar, boshqa ifodalar ro'yxatidan iborat bloklar, boshqaruv oqimi va boshqalar. Yuqorida aytib o'tganimdek, bu erda boshqaruv oqimi aniq yuqori darajadagi filiallar, tsikllar, funktsiya chaqiruvlari va boshqalar sifatida tashkil etilgan. Funksiyalarga argumentlar stekga uzatilmaydi, lekin xuddi JSdagi kabi aniq. Global o'zgaruvchilar ham bor, lekin men ulardan foydalanmadim, shuning uchun ular haqida sizga aytmayman.

Funktsiyalarda noldan boshlab raqamlangan mahalliy o'zgaruvchilar ham mavjud: int32 / int64 / float / double. Bunday holda, birinchi n mahalliy o'zgaruvchilar funktsiyaga uzatiladigan argumentlardir. Shuni esda tutingki, bu erda hamma narsa nazorat oqimi nuqtai nazaridan juda past darajada bo'lmasa-da, butun sonlar hali ham "imzolangan/imzosiz" atributiga ega emas: raqam qanday ishlashi operatsiya kodiga bog'liq.

Umuman olganda, Binaryen beradi oddiy C-API: siz modul yaratasiz, unda ifodalarni yaratish - birlik, ikkilik, boshqa ifodalardan bloklar, boshqaruv oqimi va boshqalar. Keyin siz uning tanasi sifatida ifoda bilan funksiya yaratasiz. Agar siz, men kabi, past darajadagi o'tish grafigiga ega bo'lsangiz, relooper komponenti sizga yordam beradi. Men tushunganimdek, blokning chegarasidan tashqariga chiqmasa, blokda bajarilish oqimini yuqori darajadagi boshqarishdan foydalanish mumkin - ya'ni ichki tez yo'l / sekin qilish mumkin. o'rnatilgan TLB keshini qayta ishlash kodida tarmoqlangan yo'l, lekin "tashqi" boshqaruv oqimiga xalaqit bermaslik uchun. Qayta ishlab chiqaruvchini bo'shatganingizda, uning bloklari bo'shatiladi; modulni bo'shatganingizda, unga ajratilgan ifodalar, funktsiyalar va boshqalar yo'qoladi. arena.

Biroq, agar siz tarjimon nusxasini keraksiz yaratmasdan va o'chirmasdan kodni tezda sharhlashni istasangiz, bu mantiqni C++ fayliga qo'yish va u erdan to'g'ridan-to'g'ri kutubxonaning butun C++ API-ni boshqarish uchun tayyor bo'lishi mumkin. o‘ramlar yasagan.

Shunday qilib, sizga kerak bo'lgan kodni yaratish uchun

// настроить глобальные параметры (можно поменять потом)
BinaryenSetAPITracing(0);

BinaryenSetOptimizeLevel(3);
BinaryenSetShrinkLevel(2);

// создать модуль
BinaryenModuleRef MODULE = BinaryenModuleCreate();

// описать типы функций (как создаваемых, так и вызываемых)
helper_type  BinaryenAddFunctionType(MODULE, "helper-func", BinaryenTypeInt32(), int32_helper_args, ARRAY_SIZE(int32_helper_args));
// (int23_helper_args приоб^Wсоздаются отдельно)

// сконструировать супер-мега выражение
// ... ну тут уж вы как-нибудь сами :)

// потом создать функцию
BinaryenAddFunction(MODULE, "tb_fun", tb_func_type, func_locals, FUNC_LOCALS_COUNT, expr);
BinaryenAddFunctionExport(MODULE, "tb_fun", "tb_fun");
...
BinaryenSetMemory(MODULE, (1 << 15) - 1, -1, NULL, NULL, NULL, NULL, NULL, 0, 0);
BinaryenAddMemoryImport(MODULE, NULL, "env", "memory", 0);
BinaryenAddTableImport(MODULE, NULL, "env", "tb_funcs");

// запросить валидацию и оптимизацию при желании
assert (BinaryenModuleValidate(MODULE));
BinaryenModuleOptimize(MODULE);

... agar biror narsani unutgan bo'lsam, afsuski, bu faqat o'lchovni ifodalash uchun va tafsilotlar hujjatlarda.

Va endi crack-fex-pex boshlanadi, shunga o'xshash narsa:

static char buf[1 << 20];
BinaryenModuleOptimize(MODULE);
BinaryenSetMemory(MODULE, 0, -1, NULL, NULL, NULL, NULL, NULL, 0, 0);
int sz = BinaryenModuleWrite(MODULE, buf, sizeof(buf));
BinaryenModuleDispose(MODULE);
EM_ASM({
  var module = new WebAssembly.Module(new Uint8Array(wasmMemory.buffer, $0, $1));
  var fptr = $2;
  var instance = new WebAssembly.Instance(module, {
      'env': {
          'memory': wasmMemory,
          // ...
      }
  );
  // и вот уже у вас есть instance!
}, buf, sz);

QEMU va JS dunyolarini qandaydir tarzda bog'lash va shu bilan birga kompilyatsiya qilingan funktsiyalarga tezda kirish uchun massiv yaratildi (boshlovchiga import qilish uchun funktsiyalar jadvali) va yaratilgan funktsiyalar u erda joylashtirildi. Indeksni tezda hisoblash uchun dastlab nol so'zli tarjima blokining indeksi ishlatilgan, ammo keyin ushbu formula yordamida hisoblangan indeks oddiygina maydonga mos kela boshladi. struct TranslationBlock.

Ayni paytda, demo (hozirda noaniq litsenziya bilan) faqat Firefox-da yaxshi ishlaydi. Chrome ishlab chiquvchilari edi negadir tayyor emas Kimdir WebAssembly modullarining mingdan ortiq nusxalarini yaratmoqchi bo'lganligi sababli ular har biriga bir gigabayt virtual manzil maydoni ajratdilar...

Hozircha hammasi shu. Ehtimol, kimdir qiziqsa, boshqa maqola bo'ladi. Ya'ni, hech bo'lmaganda qoladi bor yo `g` i blokli qurilmalarning ishlashini ta'minlash. JS dunyosida odatiy hol bo'lganidek, WebAssembly modullarining kompilyatsiyasini asinxron qilish ham mantiqiy bo'lishi mumkin, chunki mahalliy modul tayyor bo'lgunga qadar bularning barchasini qila oladigan tarjimon hali ham mavjud.

Nihoyat bir topishmoq: siz 32-bitli arxitekturada ikkilik faylni tuzdingiz, lekin kod xotira operatsiyalari orqali Binaryen-dan, stekning biron bir joyidan yoki 2-bitli manzil maydonining yuqori 32 GB qismidagi boshqa joydan ko'tariladi. Muammo shundaki, Binaryen nuqtai nazaridan bu juda katta natijali manzilga kirishdir. Buni qanday aylanib o'tish mumkin?

Admin tomonidan

Men buni sinab ko'rmadim, lekin mening birinchi fikrim: "Agar men 32-bitli Linuxni o'rnatsam nima bo'ladi?" Keyin manzil maydonining yuqori qismini yadro egallaydi. Bitta savol qancha band bo'ladi: 1 yoki 2 Gb.

Dasturchi usulida (amaliyotchilar uchun variant)

Keling, manzil maydonining yuqori qismida pufakchani puflaymiz. Men o'zim ham tushunmayapman - bu nima uchun ishlaydi allaqachon to'plam bo'lishi kerak. Ammo "biz amaliyotchilarmiz: hamma narsa biz uchun ishlaydi, lekin nima uchun hech kim bilmaydi ..."

// 2gbubble.c
// Usage: LD_PRELOAD=2gbubble.so <program>

#include <sys/mman.h>
#include <assert.h>

void __attribute__((constructor)) constr(void)
{
  assert(MAP_FAILED != mmap(1u >> 31, (1u >> 31) - (1u >> 20), PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
}

... bu Valgrind bilan mos emasligi to'g'ri, lekin, xayriyatki, Valgrindning o'zi hammani u erdan juda samarali tarzda itarib yuboradi :)

Ehtimol, kimdir mening bu kodim qanday ishlashini yaxshiroq tushuntirib beradi ...

Manba: www.habr.com

a Izoh qo'shish