QEMU.js: tagad nopietni un ar WASM

Reiz es nolēmu jautri pierādÄ«t procesa atgriezeniskumu un uzziniet, kā Ä£enerēt JavaScript (precÄ«zāk, Asm.js) no maŔīnas koda. Eksperimentam tika izvēlēts QEMU, un kādu laiku vēlāk tika uzrakstÄ«ts raksts par Habr. Komentāros man tika ieteikts pārtaisÄ«t projektu WebAssembly un pat izstāties pats gandriz beidzies Man kaut kā negribējās projektu... Darbs notika, bet ļoti lēni, un tagad, nesen tajā rakstā parādÄ«jās komentārs par tēmu "Kā tas viss beidzās?" Atbildot uz manu detalizēto atbildi, es dzirdēju: "Tas izklausās pēc raksta." Nu ja varēsi, bÅ«s raksts. VarbÅ«t kādam noderēs. No tā lasÄ«tājs uzzinās dažus faktus par QEMU koda Ä£enerÄ“Å”anas aizmugursistēmu dizainu, kā arÄ« par to, kā uzrakstÄ«t tÄ«mekļa lietojumprogrammai Just-in-Time kompilatoru.

uzdevumi

Tā kā jau biju iemācÄ«jies QEMU ā€œkaut kāā€ portēt uz JavaScript, tad Å”oreiz tika nolemts to darÄ«t gudri un neatkārtot vecās kļūdas.

Kļūda numur viens: atzarojums no punkta atbrīvoŔanas

Mana pirmā kļūda bija atdalÄ«t savu versiju no iepriekŔējās versijas 2.4.1. Tad man likās laba ideja: ja pastāv punkta izlaidums, tad tas droÅ”i vien ir stabilāks par vienkārÅ”u 2.4 un vēl jo vairāk par zaru. master. Un, tā kā es plānoju pievienot diezgan daudz savu kļūdu, man vispār nebija vajadzÄ«gas neviena cita. Tā tas laikam arÄ« sanāca. Bet Å”eit ir lieta: QEMU nestāv uz vietas, un kādā brÄ«dÄ« viņi pat paziņoja par Ä£enerētā koda optimizāciju par 10 procentiem. "Jā, tagad es iesaldÄ“Å”os," es nodomāju un sabojājos. Å eit mums ir jāizdara atkāpe: QEMU.js viena pavediena rakstura dēļ un tāpēc, ka sākotnējais QEMU nenozÄ«mē vairāku pavedienu neesamÄ«bu (tas ir, iespēju vienlaikus darbināt vairākus nesaistÄ«tus koda ceļus, un ne tikai ā€œizmantot visus kodolusā€), tam ir bÅ«tiska nozÄ«me, galvenās pavedienu funkcijas man bija ā€œjāizslēdzā€, lai varētu izsaukt no ārpuses. Tas apvienoÅ”anās laikā radÄ«ja dažas dabiskas problēmas. Tomēr tas, ka dažas izmaiņas no filiāles master, ar kuriem es mēģināju sapludināt savu kodu, arÄ« tika izvēlēti punkta izlaidumā (un lÄ«dz ar to manā filiālē), arÄ«, iespējams, nebÅ«tu papildu ērtÄ«bas.

Vispār es nolēmu, ka tomēr ir jēga izmest prototipu, izjaukt to daļām un izveidot jaunu versiju no nulles, pamatojoties uz kaut ko svaigāku un tagad no plkst. master.

Otrā kļūda: TLP metodoloģija

BÅ«tÄ«bā tā nav kļūda, kopumā tā ir tikai projekta izveides iezÄ«me pilnÄ«gas pārpratuma apstākļos gan par ā€œkur un kā pārvietoties?ā€, gan kopumā par ā€œvai mēs tur tiksim?ā€ Å ajos apstākļos neveikla programmÄ“Å”ana bija pamatots variants, bet, protams, es negribēju to bez vajadzÄ«bas atkārtot. Å oreiz gribēju to darÄ«t gudri: atomu apņemÅ”anās, apzinātas koda maiņas (un nevis ā€œnejauÅ”as rakstzÄ«mju virknÄ“Å”ana lÄ«dz kompilācijai (ar brÄ«dinājumiem)ā€, kā par kādu reiz teica Linuss Torvalds, saskaņā ar Wikiquote) utt.

Kļūda numur trīs: iekāpŔana ūdenī, nepazīstot fordu

Joprojām neesmu pilnÄ«bā atbrÄ«vojies no Ŕī, bet tagad esmu nolēmis vispār neiet mazākās pretestÄ«bas ceļu un darÄ«t to ā€œkā pieauguÅ”aisā€, proti, rakstÄ«t savu TCG backend no nulles, lai ne vēlāk jāsaka: "Jā, tas, protams, notiek lēni, bet es nevaru visu kontrolēt - tā ir rakstÄ«ts TCI..." Turklāt sākotnēji tas Ŕķita acÄ«mredzams risinājums, jo Es Ä£enerēju bināro kodu. Kā viņi saka: ā€œGenta pulcējāsу, bet ne tas viensā€: kods, protams, ir binārs, taču vadÄ«bu uz to nevar vienkārÅ”i pārnest ā€“ tas ir nepārprotami jāiespiež pārlÅ«kprogrammā kompilÄ“Å”anai, kā rezultātā tiek izveidots konkrēts objekts no JS pasaules, kuram tomēr ir nepiecieÅ”ams kaut kur izglābties. Tomēr parastajās RISC arhitektÅ«rās, cik es saprotu, tipiska situācija ir nepiecieÅ”amÄ«ba skaidri atiestatÄ«t instrukciju keÅ”atmiņu reÄ£enerētajam kodam - ja tas nav tas, kas mums nepiecieÅ”ams, tad jebkurā gadÄ«jumā tas ir tuvu. Turklāt no mana pēdējā mēģinājuma es uzzināju, ka vadÄ«ba, Ŕķiet, netiek pārnesta uz tulkoÅ”anas bloka vidu, tāpēc mums nav Ä«sti nepiecieÅ”ams baitkods, kas interpretēts no jebkādas nobÄ«des, un mēs varam to vienkārÅ”i Ä£enerēt no TB funkcijas. .

Viņi nāca un spārda

Lai gan es sāku pārrakstÄ«t kodu jau jÅ«lijā, nemanot uznāca maÄ£isks sitiens: parasti vēstules no GitHub pienāk kā paziņojumi par atbildēm uz problēmām un izvilkÅ”anas pieprasÄ«jumiem, taču Å”eit pēkŔņi pieminēt pavedienā Binaryen kā qemu aizmugure kontekstā: "ViņŔ kaut ko tādu izdarÄ«ja, varbÅ«t viņŔ kaut ko pateiks." Mēs runājām par Emscripten saistÄ«tās bibliotēkas izmantoÅ”anu Binaryen lai izveidotu WASM JIT. Nu, es teicu, ka jums tur ir Apache 2.0 licence, un QEMU kopumā tiek izplatÄ«ts saskaņā ar GPLv2, un tie nav Ä«paÅ”i saderÄ«gi. PēkŔņi izrādÄ«jās, ka licence var bÅ«t kaut kā salabot (Es nezinu: varbÅ«t mainÄ«t to, varbÅ«t dubulto licencÄ“Å”anu, varbÅ«t kaut ko citu...). Tas, protams, mani iepriecināja, jo lÄ«dz tam laikam jau biju kārtÄ«gi apskatÄ«jusies binārais formāts WebAssembly, un man bija kaut kā skumji un nesaprotami. Bija arÄ« bibliotēka, kas aprija pamata blokus ar pārejas grafiku, izveidoja baitkodu un vajadzÄ«bas gadÄ«jumā pat palaist to paŔā tulkā.

Tad bija vairāk vēstule QEMU adresātu sarakstā, bet tas vairāk attiecas uz jautājumu: "Kam tas vispār ir vajadzÄ«gs?" Un tā ir pēkŔņi, izrādÄ«jās, ka tas bija nepiecieÅ”ams. Ja tas darbojas vairāk vai mazāk ātri, varat saskrāpēt vismaz Ŕādas izmantoÅ”anas iespējas:

  • uzsākt kaut ko izglÄ«tojoÅ”u bez instalācijas
  • virtualizācija operētājsistēmā iOS, kur saskaņā ar baumām vienÄ«gā lietojumprogramma, kurai ir tiesÄ«bas Ä£enerēt kodu lidojumā, ir JS dzinējs (vai tā ir taisnÄ«ba?)
  • mini-OS demonstrācija - viena diskete, iebÅ«vēta, visa veida programmaparatÅ«ra utt.

Pārlūka izpildlaika funkcijas

Kā jau teicu, QEMU ir saistÄ«ts ar daudzpavedienu, bet pārlÅ«kprogrammai tā nav. Nu, tas ir, nē... Sākumā tas vispār neeksistēja, tad parādÄ«jās WebWorkers - cik es saprotu, tas ir vairāku pavedienu veidoÅ”ana, pamatojoties uz ziņojumu nodoÅ”anu bez koplietotiem mainÄ«gajiem. Protams, tas rada bÅ«tiskas problēmas, pārnesot esoÅ”o kodu, pamatojoties uz koplietojamās atmiņas modeli. Tad pēc sabiedrÄ«bas spiediena tas tika Ä«stenots ar nosaukumu SharedArrayBuffers. Tas tika pamazām ieviests, viņi svinēja tā palaiÅ”anu dažādās pārlÅ«kprogrammās, tad viņi svinēja Jauno gadu, un tad Meltdown... Pēc tam viņi nonāca pie secinājuma, ka laika mērÄ«Å”ana ir rupja vai rupja, bet ar kopÄ«gās atmiņas palÄ«dzÄ«bu un pavediens palielinot skaitÄ«tāju, tas viss ir vienāds tas izdosies diezgan precÄ«zi. Tāpēc mēs atspējojām daudzpavedienu savienojumu ar koplietojamo atmiņu. Å Ä·iet, ka viņi vēlāk to atkal ieslēdza, taču, kā kļuva skaidrs pirmajā eksperimentā, ir dzÄ«ve bez tā, un, ja tā, tad mēģināsim to izdarÄ«t, nepaļaujoties uz daudzpavedienu izmantoÅ”anu.

Otra iezÄ«me ir zema lÄ«meņa manipulāciju neiespējamÄ«ba ar steku: jÅ«s nevarat vienkārÅ”i paņemt, saglabāt paÅ”reizējo kontekstu un pārslēgties uz jaunu ar jaunu steku. Zvanu steku pārvalda JS virtuālā maŔīna. Å Ä·iet, kāda ir problēma, jo mēs tomēr nolēmām bijuŔās plÅ«smas pārvaldÄ«t pilnÄ«bā manuāli? Fakts ir tāds, ka QEMU bloka I/O tiek ieviests, izmantojot korutÄ«nas, un Å”eit noderētu zema lÄ«meņa steka manipulācijas. Par laimi Emscipten jau satur asinhrono darbÄ«bu mehānismu, pat divus: Asinhronizēt Šø Emprester. Pirmais darbojas ar ievērojamu Ä£enerētā JavaScript koda uzpÅ«Å”anos un vairs netiek atbalstÄ«ts. Otrais ir paÅ”reizējais "pareizais veids", un tas darbojas, izmantojot baitkoda Ä£enerÄ“Å”anu vietējam tulkam. Tas, protams, darbojas lēni, taču tas nepalielina kodu. Tiesa, Ŕī mehānisma korutÄ«nu atbalsts bija jāsniedz neatkarÄ«gi (bija jau Asyncify rakstÄ«tas korutÄ«nas, un Emterpreter bija ieviesta aptuveni tāda pati API, tikai vajadzēja tās savienot).

Å obrÄ«d vēl nav izdevies sadalÄ«t kodu vienā kompilētā WASM un interpretēt, izmantojot Emterpreter, tāpēc blokierÄ«ces pagaidām nestrādā (skat. nākamajā sērijā, kā saka...). Tas ir, galu galā jums vajadzētu iegÅ«t kaut ko lÄ«dzÄ«gu Å”ai smieklÄ«gajai slāņainajai lietai:

  • interpretētais bloks I/O. Nu, vai tieŔām gaidÄ«jāt emulētu NVMe ar vietējo veiktspēju? šŸ™‚
  • statiski kompilēts galvenais QEMU kods (tulkotājs, citas emulētas ierÄ«ces utt.)
  • dinamiski kompilēts viesu kods WASM

QEMU avotu iezīmes

Kā jÅ«s droÅ”i vien jau uzminējāt, viesu arhitektÅ«ru emulÄ“Å”anas kods un resursdatora instrukciju Ä£enerÄ“Å”anas kods ir atdalÄ«ti QEMU. PatiesÄ«bā tas ir pat nedaudz sarežģītāk:

  • ir viesu arhitektÅ«ras
  • tur ir paātrinātāji, proti, KVM aparatÅ«ras virtualizācijai operētājsistēmā Linux (viesa un resursdatora sistēmām, kas ir saderÄ«gas savā starpā), TCG JIT koda Ä£enerÄ“Å”anai jebkur. Sākot ar QEMU 2.9, parādÄ«jās atbalsts HAXM aparatÅ«ras virtualizācijas standartam operētājsistēmā Windows (detaļas)
  • ja tiek izmantota TCG, nevis aparatÅ«ras virtualizācija, tad tai ir atseviŔķs koda Ä£enerÄ“Å”anas atbalsts katrai saimniekdatora arhitektÅ«rai, kā arÄ« universālajam tulkam
  • ... un ap to visu - emulētas perifērijas ierÄ«ces, lietotāja interfeiss, migrācija, ierakstu atkārtoÅ”ana utt.

Starp citu, vai zinājāt: QEMU var emulēt ne tikai visu datoru, bet arÄ« procesoru atseviŔķam lietotāja procesam resursdatora kodolā, ko izmanto, piemēram, AFL fuzzer binārajai instrumentācijai. VarbÅ«t kāds vēlētos portēt Å”o QEMU darbÄ«bas režīmu uz JS? šŸ˜‰

Tāpat kā lielākā daļa ilgstoŔās bezmaksas programmatÅ«ras, QEMU tiek veidota, izmantojot zvanu configure Šø make. Pieņemsim, ka nolemjat kaut ko pievienot: TCG aizmugursistēmu, pavedienu ievieÅ”anu vai kaut ko citu. Nesteidzieties priecāties/Å”ausmināties (vajadzÄ«go pasvÄ«trot) par iespēju sazināties ar Autoconf ā€“ patiesÄ«bā, configure QEMU's acÄ«mredzot ir paÅ”rakstÄ«ts un nav Ä£enerēts no nekā.

WebAssembly

Tātad, kas ir Ŕī lieta, ko sauc par WebAssembly (aka WASM)? Å is ir Asm.js aizstājējs, kas vairs neuzdodas par derÄ«gu JavaScript kodu. Gluži pretēji, tas ir tÄ«ri binārs un optimizēts, un pat vienkārÅ”i vesela skaitļa ierakstÄ«Å”ana tajā nav ļoti vienkārÅ”a: kompaktuma labad tas tiek saglabāts formātā LEB128.

Iespējams, esat dzirdējuÅ”i par Asm.js atkārtotas cilpas algoritmu ā€” Ŕī ir ā€œaugsta lÄ«meņaā€ plÅ«smas vadÄ«bas instrukciju (tas ir, ja-tad-else, cilpu utt.) atjaunoÅ”ana, kam ir paredzēti JS dzinēji, no plkst. zema lÄ«meņa LLVM IR, tuvāk procesora izpildÄ«tajam maŔīnas kodam. Protams, QEMU starpposma attēlojums ir tuvāks otrajam. Varētu Ŕķist, ka te ir, baitkods, moku beigas... Un tad ir bloki, ja-tad-citādi un cilpas!..

Un tas ir vēl viens iemesls, kāpēc Binaryen ir noderīgs: tas dabiski var pieņemt augsta līmeņa blokus, kas ir tuvu tam, kas tiktu saglabāts WASM. Bet tas var arī izveidot kodu no pamata bloku un pāreju diagrammas starp tiem. Nu, es jau teicu, ka tas slēpj WebAssembly krātuves formātu aiz ērtās C/C++ API.

TCG (tiny Code Generator)

TCG sākotnēji bija aizmugure C kompilatoram. Tad acÄ«mredzot tas neizturēja konkurenci ar GCC, taču galu galā atrada savu vietu QEMU kā resursdatora platformas koda Ä£enerÄ“Å”anas mehānisms. Ir arÄ« TCG aizmugure, kas Ä£enerē kādu abstraktu baitkodu, kuru nekavējoties izpilda tulks, bet es nolēmu Å”oreiz izvairÄ«ties no tā izmantoÅ”anas. Taču tas, ka QEMU jau ir iespējams caur funkciju iespējot pāreju uz Ä£enerēto TB tcg_qemu_tb_exec, man tas izrādÄ«jās ļoti noderÄ«gi.

Lai QEMU pievienotu jaunu TCG aizmugursistēmu, ir jāizveido apakÅ”direktorijs tcg/<ŠøŠ¼Ń Š°Ń€Ń…ŠøтŠµŠŗтуры> (Å”ajā gadÄ«jumā, tcg/binaryen), un tajā ir divi faili: tcg-target.h Šø tcg-target.inc.c Šø izrakstÄ«t tas viss ir par configure. Tur var ievietot citus failus, taču, kā jau nojauÅ”at pēc Å”o divu nosaukumiem, tie abi tiks kaut kur iekļauti: viens kā parasts galvenes fails (tas ir iekļauts tcg/tcg.h, un tas jau atrodas citos direktoriju failos tcg, accel un ne tikai), otrs - tikai kā koda fragments iekŔā tcg/tcg.c, taču tai ir piekļuve savām statiskajām funkcijām.

Nolēmis, ka es tērÄ“Å”u pārāk daudz laika, lai detalizēti izpētÄ«tu, kā tas darbojas, es vienkārÅ”i nokopēju Å”o divu failu ā€œskeletusā€ no citas aizmugursistēmas ievieÅ”anas, godÄ«gi norādot to licences galvenē.

fails tcg-target.h satur galvenokārt iestatījumus formā #define-s:

  • cik reÄ£istru un kāds platums ir mērÄ·a arhitektÅ«rā (mums ir tik daudz, cik mēs gribam, cik mēs vēlamies - jautājums ir vairāk par to, ko pārlÅ«kprogramma Ä£enerēs efektÄ«vākā kodā "pilnÄ«gi mērÄ·a" arhitektÅ«rā ...)
  • resursdatora instrukciju saskaņoÅ”ana: uz x86 un pat TCI instrukcijas vispār netiek lÄ«dzinātas, bet es koda buferÄ« likÅ”u nemaz nevis instrukcijas, bet norādes uz Binaryen bibliotēkas struktÅ«rām, tāpēc teikÅ”u: 4 baiti
  • kādus izvēles norādÄ«jumus var Ä£enerēt aizmugure - mēs iekļaujam visu, ko atrodam Binaryen, ļaujam paātrinātājam paÅ”am sadalÄ«t pārējos vienkārŔākos
  • Kāds ir aptuvenais aizmugursistēmas pieprasÄ«tās TLB keÅ”atmiņas lielums. Fakts ir tāds, ka QEMU viss ir nopietni: lai gan ir palÄ«gfunkcijas, kas veic ielādi/uzglabāŔanu, ņemot vērā viesu MMU (kur mēs tagad bÅ«tu bez tā?), tās saglabā savu tulkoÅ”anas keÅ”atmiņu struktÅ«ras veidā, kuru apstrādi ir ērti iegult tieÅ”i apraides blokos. Jautājums ir, kādu nobÄ«di Å”ajā struktÅ«rā visefektÄ«vāk apstrādā neliela un ātra komandu secÄ«ba?
  • Å”eit varat pielāgot viena vai divu rezervēto reÄ£istru mērÄ·i, iespējot TB izsaukÅ”anu, izmantojot funkciju, un pēc izvēles aprakstÄ«t dažus mazus inline-funkcijas kā flush_icache_range (bet tas nav mÅ«su gadÄ«jums)

fails tcg-target.inc.c, protams, parasti ir daudz lielāka izmēra un satur vairākas obligātas funkcijas:

  • inicializācija, tostarp ierobežojumi attiecÄ«bā uz to, kuras instrukcijas var darboties ar kuriem operandiem. Es klaji nokopēju no cita aizmugure
  • funkcija, kas aizņem vienu iekŔējo baitkoda instrukciju
  • Å eit varat ievietot arÄ« palÄ«gfunkcijas, kā arÄ« varat izmantot statiskās funkcijas no tcg/tcg.c

Sev izvēlējos Ŕādu stratēģiju: nākamā tulkoÅ”anas bloka pirmajos vārdos pierakstÄ«ju četras norādes: sākuma atzÄ«me (noteikta vērtÄ«ba tuvumā 0xFFFFFFFF, kas noteica paÅ”reizējo TB stāvokli), kontekstu, Ä£enerēto moduli un maÄ£isko numuru atkļūdoÅ”anai. Sākumā tika ievietota atzÄ«me 0xFFFFFFFF - nKur n - neliels pozitÄ«vs skaitlis, un katru reizi, kad tas tika izpildÄ«ts caur tulku, tas palielinājās par 1. Kad tas sasniedza 0xFFFFFFFE, notika kompilācija, modulis tika saglabāts funkciju tabulā, importēts mazā ā€œpalaidējāā€, kurā nonāca izpilde no tcg_qemu_tb_exec, un modulis tika izņemts no QEMU atmiņas.

Pārfrāzējot klasiku, ā€œKruÄ·, cik daudz Å”ajā skanējumā savijas progera sirdij...ā€. Tomēr atmiņa kaut kur noplÅ«da. Turklāt tā bija QEMU pārvaldÄ«tā atmiņa! Man bija kods, kas, rakstot nākamo instrukciju (nu, tas ir, rādÄ«tāju), izdzēsa to, kura saite bija Å”ajā vietā agrāk, bet tas nepalÄ«dzēja. Faktiski vienkārŔākajā gadÄ«jumā QEMU startÄ“Å”anas laikā pieŔķir atmiņu un ieraksta tur Ä£enerēto kodu. Kad buferis beidzas, kods tiek izmests un tā vietā sāk rakstÄ«t nākamo.

Pēc koda izpētes es sapratu, ka triks ar maÄ£isko numuru ļāva man nepiedzÄ«vot kaudzÄ«tes iznÄ«cināŔanu, pirmajā piegājienā atbrÄ«vojot kaut ko nepareizu neinicializētā buferÄ«. Bet kurÅ” pārraksta buferi, lai vēlāk apietu manu funkciju? Kā iesaka Emscripten izstrādātāji, kad man radās problēma, es pārnesu iegÅ«to kodu atpakaļ uz vietējo lietojumprogrammu, iestatÄ«ju tajā Mozilla Record-Replay... Kopumā beigās es sapratu vienkārÅ”u lietu: katram blokam, a struct TranslationBlock ar tā aprakstu. Uzminiet, kur... TieÅ”i tā, tieÅ”i pirms bloka tieÅ”i buferÄ«. To saprotot, es nolēmu beigt lietot kruÄ·us (vismaz dažus) un vienkārÅ”i izmetu burvju skaitli un pārsÅ«tÄ«ju atlikuÅ”os vārdus uz struct TranslationBlock, izveidojot atseviŔķi saistÄ«tu sarakstu, kuru var ātri pārvietot, kad tulkoÅ”anas keÅ”atmiņa ir atiestatÄ«ta, un atbrÄ«vot atmiņu.

Daži kruÄ·i paliek: piemēram, iezÄ«mētas norādes koda buferÄ« - daži no tiem ir vienkārÅ”i BinaryenExpressionRef, tas ir, viņi skatās uz izteiksmēm, kuras lineāri jāieliek Ä£enerētajā pamatblokā, daļa ir nosacÄ«jums pārejai starp BB, daļa ir kur iet. Nu jau ir sagatavoti bloki Relooper, kas jāsavieno atbilstoÅ”i nosacÄ«jumiem. Lai tos atŔķirtu, tiek izmantots pieņēmums, ka tie visi ir izlÄ«dzināti par vismaz četriem baitiem, tāpēc etiÄ·etei varat droÅ”i izmantot vismazākos divus bitus, tikai jāatceras to noņemt, ja nepiecieÅ”ams. Starp citu, Ŕādas etiÄ·etes jau tiek izmantotas QEMU, lai norādÄ«tu iemeslu izieÅ”anai no TCG cilpas.

Izmantojot Binaryen

WebAssembly moduļos ir funkcijas, no kurām katra satur pamattekstu, kas ir izteiksme. Izteiksmes ir unāras un bināras darbÄ«bas, bloki, kas sastāv no citu izteiksmju sarakstiem, vadÄ«bas plÅ«smas utt. Kā jau teicu, vadÄ«bas plÅ«sma Å”eit tiek organizēta precÄ«zi kā augsta lÄ«meņa filiāles, cilpas, funkciju izsaukumi utt. Funkciju argumenti netiek nodoti stekā, bet tieÅ”i, tāpat kā JS. Ir arÄ« globālie mainÄ«gie, bet es tos neesmu izmantojis, tāpēc par tiem nestāstÄ«Å”u.

Funkcijām ir arÄ« lokālie mainÄ«gie, kas numurēti no nulles, tipa int32 / int64 / float / double. Å ajā gadÄ«jumā pirmie n vietējie mainÄ«gie ir funkcijai nodotie argumenti. LÅ«dzu, ņemiet vērā, ka, lai gan vadÄ«bas plÅ«smas ziņā viss Å”eit nav pilnÄ«gi zems, veseliem skaitļiem joprojām nav atribÅ«ta ā€œparakstÄ«ts/neparakstÄ«tsā€: skaitļa darbÄ«ba ir atkarÄ«ga no darbÄ«bas koda.

VispārÄ«gi runājot, Binaryen nodroÅ”ina vienkārÅ”s C-API: jÅ«s izveidojat moduli, viņā izveidot izteiksmes - unāras, bināras, blokus no citām izteiksmēm, kontroles plÅ«smu utt. Pēc tam jÅ«s izveidojat funkciju ar izteiksmi kā tās pamattekstu. Ja jums, tāpat kā man, ir zema lÄ«meņa pārejas grafiks, relooper komponents jums palÄ«dzēs. Cik es saprotu, blokā ir iespējams izmantot augsta lÄ«meņa izpildes plÅ«smas vadÄ«bu, ja vien tas nepārsniedz bloka robežas - tas ir, ir iespējams padarÄ«t iekŔējo ātro ceļu / lēnu ceļŔ, kas sazarojas iebÅ«vētajā TLB keÅ”atmiņas apstrādes kodā, bet netraucē ā€œÄrējaiā€ vadÄ«bas plÅ«smai. AtbrÄ«vojot relooper, tiek atbrÄ«voti tā bloki; atbrÄ«vojot moduli, pazÅ«d tam pieŔķirtās izteiksmes, funkcijas utt. arēna.

Tomēr, ja vēlaties interpretēt kodu lidojumā bez nevajadzÄ«gas tulka instances izveides un dzÄ“Å”anas, var bÅ«t lietderÄ«gi Å”o loÄ£iku ievietot C++ failā un no turienes tieÅ”i pārvaldÄ«t visu bibliotēkas C++ API, apejot gatavu izgatavoti iesaiņojumi.

Tātad, lai Ä£enerētu nepiecieÅ”amo kodu

// Š½Š°ŃŃ‚Ń€Š¾Šøть Š³Š»Š¾Š±Š°Š»ŃŒŠ½Ń‹Šµ ŠæŠ°Ń€Š°Š¼ŠµŃ‚ры (Š¼Š¾Š¶Š½Š¾ ŠæŠ¾Š¼ŠµŠ½ŃŃ‚ŃŒ ŠæŠ¾Ń‚Š¾Š¼)
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);

... ja es kaut ko aizmirsu, atvainojiet, tas ir tikai, lai attēlotu mērogu, un informācija ir norādīta dokumentācijā.

Un tagad sākas kreka-fex-pex, apmēram Ŕādi:

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);

Lai kaut kā savienotu QEMU un JS pasaules un tajā paŔā laikā ātri piekļūtu sastādÄ«tajām funkcijām, tika izveidots masÄ«vs (funkciju tabula importÄ“Å”anai palaiÅ”anas programmā), un tajā tika ievietotas Ä£enerētās funkcijas. Lai ātri aprēķinātu indeksu, sākotnēji tika izmantots nulles vārda tulkoÅ”anas bloka indekss, bet pēc tam indekss, kas aprēķināts, izmantojot Å”o formulu, sāka vienkārÅ”i iekļauties laukā struct TranslationBlock.

Starp citu, demo (Å”obrÄ«d ar neskaidru licenci) labi darbojas tikai Firefox. Chrome izstrādātāji bija kaut kā nav gatavs uz to, ka kāds vēlētos izveidot vairāk nekā tÅ«kstoÅ” WebAssembly moduļu gadÄ«jumu, tāpēc viņi vienkārÅ”i katram atvēlēja gigabaitu virtuālās adreÅ”u telpas...

Tas pagaidām ir viss. Varbūt būs vēl kāds raksts, ja kādu interesē. Proti, paliek vismaz tikai lai bloku ierīces darbotos. Varētu būt arī lietderīgi WebAssembly moduļu kompilāciju padarīt asinhronu, kā tas ir ierasts JS pasaulē, jo joprojām ir tulks, kas to visu var paveikt, līdz vietējais modulis ir gatavs.

Beidzot mÄ«kla: esat sastādÄ«jis bināro failu 32 bitu arhitektÅ«rā, bet kods, izmantojot atmiņas darbÄ«bas, paceļas no Binaryen, kaut kur stekā vai kaut kur citur 2 bitu adreÅ”u telpas augŔējos 32 GB. Problēma ir tāda, ka no Binaryen viedokļa tas piekļūst pārāk lielai iegÅ«tajai adresei. Kā to apiet?

Administratora veidā

Es nebeidzu to pārbaudÄ«t, bet mana pirmā doma bija: "Ko darÄ«t, ja es instalētu 32 bitu Linux?" Tad adreÅ”u telpas augŔējo daļu aizņems kodols. Jautājums tikai, cik bÅ«s aizņemts: 1 vai 2 Gb.

Programmētāja veidā (opcija praktiķiem)

IzpÅ«tÄ«sim burbuli adreses telpas augÅ”daļā. Es pats nesaprotu, kāpēc tas darbojas - tur jau ir jābÅ«t kaudzei. Bet "mēs esam praktiÄ·i: mums viss darbojas, bet neviens nezina, kāpēc..."

// 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));
}

... tiesa, ka tas nav savienojams ar Valgrind, bet, par laimi, pats Valgrinds ļoti efektīvi izstumj visus no turienes :)

Varbūt kāds labāk paskaidros, kā Ŕis mans kods darbojas...

Avots: www.habr.com

Pievieno komentāru