QEMU.js: nyt vakavasti ja WASM:n kanssa

Kerran päätin huvin vuoksi todistaa prosessin palautuvuuden ja opit luomaan JavaScriptiä (tarkemmin Asm.js) konekoodista. QEMU valittiin kokeeseen, ja jonkin aikaa myöhemmin kirjoitettiin artikkeli Habr. Kommenteissa minua neuvottiin tekemään projektista uudelleen WebAssemblyssa ja jopa lopettamaan itseni melkein valmis En jotenkin halunnut projektia... Työ eteni, mutta hyvin hitaasti, ja nyt, äskettäin tuo artikkeli ilmestyi kommentti aiheesta "Kuinka kaikki päättyi?" Vastauksena yksityiskohtaiseen vastaukseeni kuulin "Tämä kuulostaa artikkelilta". No, jos voit, tulee artikkeli. Ehkä joku löytää siitä hyötyä. Siitä lukija oppii joitain faktoja QEMU-koodin sukupolven taustaohjelmien suunnittelusta sekä kuinka kirjoittaa Just-in-Time-kääntäjä verkkosovellukselle.

tehtävät

Koska olin jo oppinut QEMU:n "jollakin tavalla" portaamisen JavaScriptiin, niin tällä kertaa päätettiin tehdä se viisaasti ja olla toistamatta vanhoja virheitä.

Virhe numero yksi: haara pisteen vapautuksesta

Ensimmäinen virheeni oli erottaa versioni ylävirran versiosta 2.4.1. Silloin se tuntui minusta hyvältä ajatukselta: jos pistevapautus on olemassa, niin se on luultavasti vakaampi kuin yksinkertainen 2.4, ja vielä enemmän haara master. Ja koska suunnittelin lisääväni melkoisen määrän omia bugejani, en tarvinnut ollenkaan kenenkään muun. Näin siinä varmaan kävi. Mutta tässä on asia: QEMU ei pysähdy, ja jossain vaiheessa he jopa ilmoittivat generoidun koodin optimoinnista 10 prosentilla. "Joo, nyt jään", ajattelin ja murtuin. Tässä meidän on tehtävä poikkeama: johtuen QEMU.js:n yksisäikeisestä luonteesta ja siitä, että alkuperäinen QEMU ei tarkoita monisäikeisyyden puuttumista (eli kykyä käyttää samanaikaisesti useita toisiinsa liittymättömiä koodipolkuja, ja ei vain "käytä kaikkia ytimiä") on sille kriittinen, säikeiden päätoiminnot piti "kääntää ulos" voidakseni kutsua ulkopuolelta. Tämä aiheutti luonnollisia ongelmia yhdistymisen aikana. Kuitenkin se, että jotkut muutokset haara master, joiden kanssa yritin yhdistää koodini, olivat myös pistejulkaisussa (ja siksi haarassani) poimittuja, eikä myöskään luultavasti olisi lisännyt mukavuutta.

Yleisesti päätin, että on silti järkevää heittää prototyyppi pois, purkaa se osiin ja rakentaa uusi versio tyhjästä perustuen johonkin tuoreempaan ja nyt master.

Virhe numero kaksi: TLP-metodologia

Pohjimmiltaan tämä ei ole virhe, yleensä se on vain ominaisuus projektin luomiseen olosuhteissa, joissa ei ymmärretä täydellisesti "minne ja miten muuttaa?" ja yleensä "pääsemmekö sinne?" Näissä olosuhteissa kömpelö ohjelmointi oli perusteltu vaihtoehto, mutta luonnollisesti en halunnut toistaa sitä turhaan. Tällä kertaa halusin tehdä sen viisaasti: atomiset sitoumukset, tietoiset koodimuutokset (eikä "satunnaisten merkkien ketjuttaminen yhteen, kunnes se kääntää (varoituksella)", kuten Linus Torvalds kerran sanoi jostain Wikisitaatin mukaan) jne.

Virhe numero kolme: mennä veteen tietämättä kaakelaa

En ole vieläkään täysin päässyt tästä eroon, mutta nyt olen päättänyt olla seuraamatta vähimmän vastustuksen polkua ja tehdä sen "aikuisena" eli kirjoittaa TCG-taustani tyhjästä, jottei täytyy sanoa myöhemmin: "Kyllä, tämä on tietysti hitaasti, mutta en voi hallita kaikkea - näin TCI on kirjoitettu..." Lisäksi tämä vaikutti aluksi itsestään selvältä ratkaisulta, koska Luon binaarikoodia. Kuten he sanovat: "Ghent kokoontuiу, mutta ei sitä”: koodi on tietysti binäärinen, mutta siihen ei voi yksinkertaisesti siirtää ohjausta - se on suoraan työnnettävä selaimeen käännöstyötä varten, jolloin tuloksena on tietty objekti JS-maailmasta, joka tarvitsee vielä pelastua jonnekin. Kuitenkin normaaleissa RISC-arkkitehtuureissa, sikäli kuin ymmärrän, tyypillinen tilanne on tarve nimenomaisesti nollata ohjevälimuisti regeneroidulle koodille - jos tämä ei ole sitä, mitä tarvitsemme, niin se on joka tapauksessa lähellä. Lisäksi opin viimeisestä yrityksestäni, että ohjaus ei näytä siirtyvän käännöslohkon keskelle, joten emme todellakaan tarvitse mistään offsetista tulkittua tavukoodia, vaan voimme yksinkertaisesti luoda sen TB:n funktiosta. .

He tulivat ja potkivat

Vaikka aloin kirjoittaa koodia uudelleen jo heinäkuussa, taikapotku hiipii huomaamatta: yleensä kirjeet GitHubista saapuvat ilmoituksina vastauksista Issues- ja Pull-pyyntöihin, mutta tässä yhtäkkiä mainita ketjussa Binaryen qemu-taustaohjelmana kontekstissa "Hän teki jotain sellaista, ehkä hän sanoo jotain." Puhuimme Emscriptenin liittyvän kirjaston käytöstä Binaryen luodaksesi WASM JIT. No, sanoin, että sinulla on siellä Apache 2.0 -lisenssi ja QEMU kokonaisuudessaan jaetaan GPLv2:lla, eivätkä ne ole kovin yhteensopivia. Yhtäkkiä kävi ilmi, että lisenssi voi olla korjata se jotenkin (En tiedä: ehkä muuta sitä, ehkä kaksoislisenssiä, ehkä jotain muuta...). Tämä tietysti ilahdutti minua, sillä siihen mennessä olin jo katsonut tarkkaan binäärimuoto WebAssembly, ja olin jotenkin surullinen ja käsittämätön. Siellä oli myös kirjasto, joka nielee peruslohkot siirtymägraafin kanssa, tuotti tavukoodin ja jopa suoritti sen tarvittaessa itse tulkissa.

Sitten oli enemmän kirje QEMU-postituslistalla, mutta tämä koskee enemmän kysymystä "Kuka tarvitsee sitä?" Ja se on yhtäkkiä, se osoittautui tarpeelliseksi. Voit raaputtaa ainakin seuraavat käyttömahdollisuudet, jos se toimii enemmän tai vähemmän nopeasti:

  • käynnistää jotain opettavaista ilman asennusta
  • virtualisointi iOS:ssä, jossa huhujen mukaan ainoa sovellus, jolla on oikeus koodin luomiseen lennossa, on JS-moottori (onko totta?)
  • mini-OS:n esittely - yksi levyke, sisäänrakennettu, kaikenlaiset laiteohjelmistot jne...

Selaimen ajonaikaiset ominaisuudet

Kuten jo sanoin, QEMU on sidottu monisäikeiseen, mutta selaimessa ei ole sitä. No, eli ei... Aluksi sitä ei ollut ollenkaan, sitten ilmestyi WebWorkers - ymmärtääkseni tämä on monisäikeistä viestinvälitykseen perustuvaa ilman jaettuja muuttujia. Luonnollisesti tämä aiheuttaa merkittäviä ongelmia siirrettäessä olemassa olevaa koodia jaetun muistin mallin perusteella. Sitten se toteutettiin julkisen painostuksen alla myös nimellä SharedArrayBuffers. Se otettiin käyttöön vähitellen, sen julkaisua juhlittiin eri selaimissa, sitten juhlittiin uutta vuotta ja sitten Meltdownia... Sen jälkeen he päätyivät siihen tulokseen, että aikamittaus on karkeaa tai karkeaa, mutta yhteisen muistin ja lanka kasvattaa laskuria, se on sama se onnistuu melko tarkasti. Joten poistimme monisäikeen jaetun muistin. Näyttää siltä, ​​​​että he ottavat sen myöhemmin uudelleen käyttöön, mutta kuten ensimmäisestä kokeilusta selvisi, elämää on ilman sitä, ja jos on, yritämme tehdä sen luottamatta monisäikeiseen.

Toinen ominaisuus on mahdottomuus manipuloida pinon kanssa: et voi yksinkertaisesti ottaa, tallentaa nykyistä kontekstia ja vaihtaa uuteen uudella pinolla. Puhelupinoa hallinnoi JS-virtuaalikone. Vaikuttaa siltä, ​​mikä on ongelma, koska päätimme silti hallita entisiä virtoja täysin manuaalisesti? Tosiasia on, että QEMU:n lohko-I/O on toteutettu korutiinien kautta, ja tässä matalan tason pinomanipulaatiot olisivat hyödyllisiä. Onneksi Emscipten sisältää jo mekanismin asynkronisille operaatioille, jopa kaksi: Asynkronoida и Emterpreter. Ensimmäinen toimii luodun JavaScript-koodin huomattavan turvotuksen kautta, eikä sitä enää tueta. Toinen on nykyinen "oikea tapa" ja toimii tavukoodin generoinnin kautta alkuperäiselle tulkille. Se toimii tietysti hitaasti, mutta se ei paisuta koodia. Totta, tämän mekanismin korutiinien tuki oli annettava itsenäisesti (Asyncifylle oli jo kirjoitettu korutiinit ja Emterpreterille oli toteutettu suunnilleen sama API, sinun piti vain yhdistää ne).

Tällä hetkellä en ole vielä onnistunut jakamaan koodia yhdeksi WASM:lla käännetyksi ja Emterpreterillä tulkituksi, joten lohkolaitteet eivät vielä toimi (katso seuraavassa sarjassa, kuten sanotaan...). Eli loppujen lopuksi pitäisi saada jotain tämän hassun kerrostetun jutun kaltaista:

  • tulkittu lohko I/O. No, odotitko todella emuloitua NVMe:tä alkuperäisellä suorituskyvyllä? 🙂
  • staattisesti käännetty QEMU-pääkoodi (kääntäjä, muut emuloidut laitteet jne.)
  • dynaamisesti käännetty vieraskoodi WASM:iin

QEMU-lähteiden ominaisuudet

Kuten luultavasti jo arvasitkin, vierasarkkitehtuurien emulointikoodi ja isäntäkoneohjeiden generointikoodi on erotettu QEMU:ssa. Itse asiassa se on jopa hieman hankalampi:

  • on vierasarkkitehtuuria
  • on kiihdyttimiä, nimittäin KVM laitteiston virtualisointiin Linuxissa (toistensa kanssa yhteensopiville vieras- ja isäntäjärjestelmille), TCG JIT-koodin luomiseen missä tahansa. QEMU 2.9:stä alkaen tuki HAXM-laitteiston virtualisointistandardille Windowsissa ilmestyi (yksityiskohdat)
  • jos käytetään TCG:tä eikä laitteistovirtualisointia, siinä on erillinen koodinluontituki jokaiselle isäntäarkkitehtuurille sekä yleiselle tulkille
  • ... ja kaiken tämän ympärillä - emuloidut oheislaitteet, käyttöliittymä, siirto, nauhoitustoisto jne.

Tiesitkö muuten: QEMU voi emuloida koko tietokoneen lisäksi myös prosessorin erillistä käyttäjäprosessia varten isäntäytimessä, jota esimerkiksi AFL-fuzzer käyttää binääriinstrumentointiin. Ehkä joku haluaisi siirtää tämän QEMU:n toimintatavan JS:ään? 😉

Kuten useimmat pitkäaikaiset ilmaiset ohjelmistot, QEMU on rakennettu puhelun kautta configure и make. Oletetaan, että päätät lisätä jotain: TCG-taustajärjestelmän, säikeen toteutuksen tai jotain muuta. Älä kiirehdi iloitsemaan/kauhistumaan (alleviivaa tarvittaessa) mahdollisuudesta kommunikoida Autoconfin kanssa – itse asiassa, configure QEMU on ilmeisesti itse kirjoitettu, eikä sitä ole luotu mistään.

WebAssembly

Joten mikä tämä WebAssembly (alias WASM) on? Tämä korvaa Asm.js:n, eikä enää teeskentele olevansa kelvollista JavaScript-koodia. Päinvastoin, se on puhtaasti binäärinen ja optimoitu, eikä edes yksinkertaisesti kokonaisluvun kirjoittaminen siihen ole kovin yksinkertaista: kompaktin vuoksi se tallennetaan muotoon LEB128.

Olet ehkä kuullut Asm.js:n uudelleensilmukka-algoritmista – tämä on "korkean tason" vuonohjauskäskyjen (eli jos-niin-muu-silmukoiden jne.) palautus, jota varten JS-moottorit on suunniteltu. matalan tason LLVM IR, lähempänä prosessorin suorittamaa konekoodia. Luonnollisesti QEMU:n väliesitys on lähempänä toista. Näyttäisi siltä, ​​että tässä se on, tavukoodi, piinauksen loppu... Ja sitten on lohkot, jos-niin-muut ja silmukat!..

Ja tämä on toinen syy miksi Binaryen on hyödyllinen: se voi luonnollisesti hyväksyä korkean tason lohkoja lähellä sitä, mitä WASM:iin tallennettaisiin. Mutta se voi myös tuottaa koodia peruslohkojen ja niiden välisten siirtymien kaaviosta. No, olen jo sanonut, että se piilottaa WebAssembly-tallennusmuodon kätevän C/C++ API:n taakse.

TCG (Tiny Code Generator)

TCG oli alunperin C-kääntäjän taustaohjelma. Sitten se ei ilmeisesti kestänyt kilpailua GCC:n kanssa, mutta lopulta se löysi paikkansa QEMU:ssa isäntäalustan koodin generointimekanismina. On myös TCG-tausta, joka luo abstraktin tavukoodin, jonka tulkki suorittaa välittömästi, mutta päätin välttää sen käyttöä tällä kertaa. Kuitenkin se, että QEMU:ssa on jo mahdollista mahdollistaa siirtyminen generoituun TB:hen toiminnon kautta tcg_qemu_tb_exec, se osoittautui minulle erittäin hyödylliseksi.

Jos haluat lisätä uuden TCG-taustajärjestelmän QEMU:hun, sinun on luotava alihakemisto tcg/<имя архитектуры> (tässä tapauksessa, tcg/binaryen), ja se sisältää kaksi tiedostoa: tcg-target.h и tcg-target.inc.c и määrätä kyse on kaikesta configure. Sinne voi laittaa muita tiedostoja, mutta kuten näiden kahden nimestä voi päätellä, ne molemmat sisällytetään johonkin: toinen tavalliseksi otsikkotiedostoksi (se sisältyy tcg/tcg.h, ja se on jo muissa hakemistojen tiedostoissa tcg, accel eikä vain), toinen - vain koodinpätkänä tcg/tcg.c, mutta sillä on pääsy staattisiin toimintoihinsa.

Päätin käyttää liikaa aikaa yksityiskohtaisiin tutkimuksiin sen toiminnasta, joten yksinkertaisesti kopioin näiden kahden tiedoston "luurankot" toisesta taustasovelluksesta ja osoitin tämän rehellisesti lisenssiotsikossa.

tiedosto tcg-target.h sisältää pääasiassa lomakkeen asetuksia #define-s:

  • kuinka monta rekisteriä ja leveyttä kohdearkkitehtuurissa on (meitä on niin monta kuin haluamme, niin monta kuin haluamme - kysymys on enemmän siitä, mitä selain generoi tehokkaammaksi koodiksi "täysin kohde" -arkkitehtuurissa ...)
  • isäntäohjeiden kohdistus: x86:ssa ja jopa TCI:ssä käskyjä ei kohdisteta ollenkaan, mutta en aion laittaa koodipuskuriin ollenkaan ohjeita, vaan osoittimia Binaryen-kirjastorakenteisiin, joten sanon: 4 tavua
  • mitä valinnaisia ​​ohjeita taustaohjelma voi luoda - sisällytämme kaikki Binaryenista löytämämme käskyt, anna kiihdytin jakaa loput yksinkertaisempiin itse
  • Mikä on taustajärjestelmän pyytämän TLB-välimuistin likimääräinen koko. Tosiasia on, että QEMU:ssa kaikki on vakavaa: vaikka on aputoimintoja, jotka suorittavat lataus/tallennus huomioiden vieras-MMU:n (missä olisimme nyt ilman sitä?), ne tallentavat käännösvälimuistinsa rakenteen muodossa, joiden käsittely on kätevää upottaa suoraan lähetyslohkoihin. Kysymys kuuluu, mikä offset tässä rakenteessa on tehokkaimmin prosessoitavissa pienellä ja nopealla komentosarjalla?
  • täällä voit muokata yhden tai kahden varatun rekisterin tarkoitusta, mahdollistaa TB:n kutsumisen funktion kautta ja valinnaisesti kuvata pari pientä inline- toimii kuten flush_icache_range (mutta tämä ei ole meidän tapaus)

tiedosto tcg-target.inc.c, on tietysti yleensä paljon suurempi kooltaan ja sisältää useita pakollisia toimintoja:

  • alustus, mukaan lukien rajoitukset sille, mitkä käskyt voivat toimia millekin operandille. Kopioin räikeästi toisesta taustaohjelmasta
  • toiminto, joka ottaa yhden sisäisen tavukoodikäskyn
  • Voit myös laittaa aputoimintoja tähän, ja voit myös käyttää staattisia toimintoja tcg/tcg.c

Itselleni valitsin seuraavan strategian: seuraavan käännöslohkon ensimmäisiin sanoiin kirjoitin neljä osoitinta: aloitusmerkki (tiety arvo lähellä 0xFFFFFFFF, joka määritti TB:n nykyisen tilan), kontekstin, luodun moduulin ja maagisen numeron virheenkorjausta varten. Aluksi merkki laitettiin sisään 0xFFFFFFFF - nMissä n - pieni positiivinen luku, ja joka kerta kun se suoritettiin tulkin kautta, se kasvoi 1:llä. Kun se saavutti 0xFFFFFFFE, käännös tapahtui, moduuli tallennettiin funktiotaulukkoon, tuotiin pieneen "käynnistimeen", johon suoritus meni tcg_qemu_tb_exec, ja moduuli poistettiin QEMU-muistista.

Parafraasin klassikoita, "Crutch, kuinka paljon tässä soundissa kietoutuu progerin sydämelle...". Muisti kuitenkin vuotaa jostain. Lisäksi se oli QEMU:n hallinnoima muisti! Minulla oli koodi, joka kirjoitettaessa seuraavaa ohjetta (no, eli osoitinta) poisti sen, jonka linkki oli tässä paikassa aiemmin, mutta tämä ei auttanut. Itse asiassa yksinkertaisimmassa tapauksessa QEMU varaa muistia käynnistyksen yhteydessä ja kirjoittaa luodun koodin sinne. Kun puskuri loppuu, koodi heitetään ulos ja seuraavaa aletaan kirjoittaa tilalle.

Tutkittuani koodia tajusin, että taikanumeron temppu antoi minulle mahdollisuuden olla epäonnistumatta kasatuhossa vapauttamalla jotain vialla alustamattomasta puskurista ensimmäisellä kerralla. Mutta kuka kirjoittaa puskurin uudelleen ohittamaan toimintoni myöhemmin? Kuten Emscripten-kehittäjät neuvovat, kun törmäsin ongelmaan, siirsin tuloksena olevan koodin takaisin alkuperäiseen sovellukseen, asetin siihen Mozilla Record-Replayn... Yleisesti ottaen tajusin lopulta yksinkertaisen asian: jokaiselle lohkolle a struct TranslationBlock sen kuvauksen kanssa. Arvaa missä... Aivan, juuri ennen lohkoa puskurissa. Tajusin tämän, päätin lopettaa kainalosauvojen käytön (ainakin osan), ja heitin vain maagisen numeron ulos ja siirsin loput sanat struct TranslationBlock, luo yksitellen linkitetyn luettelon, jota voidaan nopeasti selata, kun käännösvälimuisti nollataan, ja vapauttaa muistia.

Jotkut kainalosauvat jäävät: esimerkiksi merkittyjä osoittimia koodipuskurissa - jotkut niistä ovat yksinkertaisesti BinaryenExpressionRef, eli he tarkastelevat lausekkeita, jotka täytyy lineaarisesti laittaa generoituun peruslohkoon, osa on ehto siirtymiselle BB:iden välillä, osa on minne mennä. No, Relooperille on jo valmiita lohkoja, jotka on yhdistettävä olosuhteiden mukaan. Niiden erottamiseksi käytetään oletusta, että ne ovat kaikki tasattu vähintään neljän tavun verran, joten voit turvallisesti käyttää vähiten merkitseviä kahta bittiä etiketissä, sinun tarvitsee vain muistaa poistaa se tarvittaessa. Muuten, tällaisia ​​merkintöjä käytetään jo QEMU:ssa osoittamaan syytä TCG-silmukasta poistumiseen.

Binaryenin käyttö

WebAssemblyn moduulit sisältävät toimintoja, joista jokainen sisältää rungon, joka on lauseke. Lausekkeet ovat unaarisia ja binäärisiä operaatioita, lohkoja, jotka koostuvat muiden lausekkeiden luetteloista, ohjausvuosta jne. Kuten jo sanoin, ohjausvirtaus on järjestetty juuri korkean tason haaroihin, silmukoihin, toimintokutsuihin jne. Argumentteja funktioille ei välitetä pinossa, vaan eksplisiittisesti, aivan kuten JS:ssä. On myös globaaleja muuttujia, mutta en ole käyttänyt niitä, joten en kerro niistä.

Funktioissa on myös paikallisia muuttujia, jotka on numeroitu nollasta, tyyppiä: int32 / int64 / float / double. Tässä tapauksessa ensimmäiset n paikallista muuttujaa ovat funktiolle välitettyjä argumentteja. Huomaa, että vaikka kaikki tässä ei ole täysin matalatasoista ohjausvirran kannalta, kokonaisluvut eivät silti sisällä "allekirjoitettu/allekirjoittamaton" -attribuuttia: kuinka numero käyttäytyy, riippuu operaatiokoodista.

Yleisesti ottaen Binaryen tarjoaa yksinkertainen C-API: luot moduulin, hänessä luo lausekkeita - unary, binary, lohkot muista lausekkeista, ohjausvirta jne. Sitten luot funktion, jonka runko-osa on lauseke. Jos sinulla, kuten minulla, on matalan tason siirtymäkaavio, reloooper-komponentti auttaa sinua. Ymmärtääkseni on mahdollista käyttää suoritusvirran korkean tason ohjausta lohkossa, kunhan se ei ylitä lohkon rajoja - eli on mahdollista tehdä sisäinen nopea polku / hidas polku haarautuu sisäänrakennetun TLB-välimuistin käsittelykoodin sisällä, mutta ei häiritse "ulkoista" ohjausvirtaa . Kun vapautat reloooperin, sen lohkot vapautetaan; kun vapautat moduulin, sille varatut lausekkeet, funktiot jne. katoavat. areena.

Jos kuitenkin haluat tulkita koodia lennossa ilman turhaa tulkin ilmentymän luomista ja poistamista, voi olla järkevää laittaa tämä logiikka C++-tiedostoon ja hallita sieltä suoraan koko kirjaston C++ API:ta ohittaen valmiin tehty kääreitä.

Joten tarvitsemasi koodin luomiseen

// настроить глобальные параметры (можно поменять потом)
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);

... jos unohdin jotain, anteeksi, tämä vain edustaa mittakaavaa, ja yksityiskohdat ovat asiakirjoissa.

Ja nyt alkaa crack-fex-pex, jotakuinkin näin:

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

Jotta QEMU:n ja JS:n maailmat voitaisiin jotenkin yhdistää ja samalla saada käännetyt funktiot nopeasti käyttöön, luotiin taulukko (funktiotaulukko kantorakettiin tuotavaksi), ja luodut funktiot sijoitettiin sinne. Indeksin nopeaa laskemista varten käytettiin alun perin nollasanan käännöslohkon indeksiä sellaisenaan, mutta sitten tällä kaavalla laskettu indeksi alkoi yksinkertaisesti mahtua kenttään struct TranslationBlock.

muuten, esittely (tällä hetkellä hämärällä lisenssillä) toimii hyvin vain Firefoxissa. Chromen kehittäjät olivat jotenkin ei ole valmis siihen, että joku haluaisi luoda yli tuhat WebAssembly-moduulin esiintymää, joten he yksinkertaisesti varasivat gigatavun virtuaalista osoitetilaa kullekin...

Tässä kaikki tältä erää. Ehkä tulee toinen artikkeli, jos jotakuta kiinnostaa. Nimittäin ainakin on jäljellä vain saada lohkolaitteet toimimaan. Voi olla myös järkevää tehdä WebAssembly-moduulien kokoaminen asynkroniseksi, kuten JS-maailmassa on tapana, koska vielä on tulkki, joka voi tehdä kaiken tämän, kunnes alkuperäinen moduuli on valmis.

Lopuksi arvoitus: olet kääntänyt binaarin 32-bittiselle arkkitehtuurille, mutta koodi nousee muistioperaatioiden kautta Binaryenista, jostain pinosta tai jostain muualta 2-bittisen osoiteavaruuden ylemmästä 32 Gt:sta. Ongelmana on, että Binaryenin näkökulmasta tämä käyttää liian suurta tuloksena olevaa osoitetta. Kuinka kiertää tämä?

Adminin tavalla

En päätynyt testaamaan tätä, mutta ensimmäinen ajatukseni oli "Mitä jos asensin 32-bittisen Linuxin?" Tällöin osoiteavaruuden yläosa on ytimen käytössä. Ainoa kysymys on, kuinka paljon varataan: 1 vai 2 Gb.

Ohjelmoijan tavalla (vaihtoehto harjoittajille)

Puhalletaan kupla osoiteavaruuden yläosaan. En itse ymmärrä miksi se toimii - siellä jo täytyy olla pino. Mutta "olemme harjoittajia: kaikki toimii meillä, mutta kukaan ei tiedä miksi..."

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

... se on totta, että se ei ole yhteensopiva Valgrindin kanssa, mutta onneksi Valgrind itse työntää todella tehokkaasti kaikki pois sieltä :)

Ehkä joku selittää paremmin, miten tämä koodini toimii...

Lähde: will.com

Lisää kommentti