En gÄng i tiden bestÀmde jag mig för skojs skull bevisa processens reversibilitet och lÀr dig hur du genererar JavaScript (mer exakt, Asm.js) frÄn maskinkod. QEMU valdes för experimentet, och en tid senare skrevs en artikel om Habr. I kommentarerna fick jag rÄdet att göra om projektet i WebAssembly och till och med sluta sjÀlv nÀstan fÀrdig Jag ville pÄ nÄgot sÀtt inte ha projektet... Arbetet pÄgick, men vÀldigt lÄngsamt, och nu, nyligen dök det upp i den artikeln pÄ Àmnet "SÄ hur slutade det hela?" Som svar pÄ mitt detaljerade svar hörde jag "Det hÀr lÄter som en artikel." Tja, om du kan, kommer det att finnas en artikel. Kanske nÄgon kommer att ha nytta av det. FrÄn den kommer lÀsaren att lÀra sig lite fakta om designen av QEMU-kodgenereringsbackends, samt hur man skriver en Just-in-Time-kompilator för en webbapplikation.
uppgifter
Eftersom jag redan hade lÀrt mig hur man "pÄ nÄgot sÀtt" porterar QEMU till JavaScript, beslutades den hÀr gÄngen att göra det klokt och inte upprepa gamla misstag.
Fel nummer ett: förgrena sig frÄn punktslÀpp
Mitt första misstag var att dela min version frÄn uppströmsversionen 2.4.1. DÄ tyckte jag att det var en bra idé: om punktutgivning finns, sÄ Àr den förmodligen mer stabil Àn enkel 2.4, och Ànnu mer sÄ grenen master. Och eftersom jag planerade att lÀgga till en hel del av mina egna buggar, behövde jag inte nÄgon annans alls. Det var nog sÄ det blev. Men hÀr Àr grejen: QEMU stÄr inte stilla, och vid nÄgot tillfÀlle tillkÀnnagav de till och med optimering av den genererade koden med 10 procent. "Ja, nu ska jag frysa", tÀnkte jag och bröt ihop. HÀr mÄste vi göra en avvikelse: pÄ grund av QEMU.js entrÄdiga natur och det faktum att den ursprungliga QEMU inte innebÀr frÄnvaron av multi-threading (det vill sÀga möjligheten att samtidigt anvÀnda flera orelaterade kodvÀgar, och inte bara "anvÀnd alla kÀrnor") Àr avgörande för det, huvudfunktionerna i trÄdar var jag tvungen att "vÀnda ut" för att kunna anropa utifrÄn. Detta skapade en del naturliga problem under sammanslagningen. Men det faktum att nÄgra av förÀndringarna frÄn grenen master, som jag försökte slÄ ihop min kod med, var ocksÄ körsbÀrsplockade i punktutgÄvan (och dÀrför i min gren) ocksÄ förmodligen inte skulle ha lagt till bekvÀmlighet.
I allmÀnhet bestÀmde jag mig för att det fortfarande Àr vettigt att kasta ut prototypen, plocka isÀr den för delar och bygga en ny version frÄn grunden baserad pÄ nÄgot frÀschare och nu frÄn master.
Misstag nummer tvÄ: TLP-metodik
I grund och botten Àr detta inte ett misstag, i allmÀnhet Àr det bara en funktion för att skapa ett projekt under förhÄllanden av fullstÀndigt missförstÄnd av bÄde "vart och hur man ska flytta?" och i allmÀnhet "kommer vi dit?" Under dessa förhÄllanden klumpig programmering var ett motiverat alternativ, men jag ville naturligtvis inte upprepa det i onödan. Den hÀr gÄngen ville jag göra det klokt: atomic commits, medvetna kodÀndringar (och inte "strÀnga ihop slumpmÀssiga tecken tills det kompileras (med varningar)", som Linus Torvalds en gÄng sa om nÄgon, enligt Wikiquote), etc.
Misstag nummer tre: att komma i vattnet utan att kÀnna till vadstÀllet
Jag har fortfarande inte helt blivit av med detta, men nu har jag bestĂ€mt mig för att inte följa minsta motstĂ„ndets vĂ€g alls, och att göra det "som vuxen", nĂ€mligen skriva min TCG-backend frĂ„n grunden, för att inte att behöva sĂ€ga senare, "Ja, det hĂ€r gĂ„r naturligtvis lĂ„ngsamt, men jag kan inte kontrollera allt - det Ă€r sĂ„ TCI skrivs..." Dessutom verkade detta initialt vara en sjĂ€lvklar lösning, eftersom Jag genererar binĂ€r kod. Som de sĂ€ger, "Gent samladesŃ, men inte denâ: koden Ă€r naturligtvis binĂ€r, men kontrollen kan inte bara överföras till den - den mĂ„ste explicit tryckas in i webblĂ€saren för kompilering, vilket resulterar i ett visst objekt frĂ„n JS-vĂ€rlden, som fortfarande mĂ„ste sparas nĂ„gonstans. Men pĂ„ normala RISC-arkitekturer, sĂ„ vitt jag förstĂ„r, Ă€r en typisk situation behovet av att explicit Ă„terstĂ€lla instruktionscachen för regenererad kod - om detta inte Ă€r vad vi behöver, sĂ„ Ă€r det i alla fall nĂ€ra. Dessutom, frĂ„n mitt senaste försök, lĂ€rde jag mig att kontrollen inte verkar överföras till mitten av översĂ€ttningsblocket, sĂ„ vi behöver egentligen inte bytekod tolkad frĂ„n nĂ„gon offset, och vi kan helt enkelt generera den frĂ„n funktionen pĂ„ TB .
De kom och sparkade
Ăven om jag började skriva om koden redan i juli, smög en magisk kick fram obemĂ€rkt: vanligtvis kommer brev frĂ„n GitHub som meddelanden om svar pĂ„ problem och Pull-förfrĂ„gningar, men hĂ€r, plötsligt nĂ€mna i trĂ„den i sammanhanget, "Han gjorde nĂ„got sĂ„dant, han kanske sĂ€ger nĂ„got." Vi pratade om att anvĂ€nda Emscriptens relaterade bibliotek för att skapa WASM JIT. Jo, jag sa att du har en Apache 2.0-licens dĂ€r, och QEMU som helhet distribueras under GPLv2, och de Ă€r inte sĂ€rskilt kompatibla. Plötsligt visade det sig att en licens kan vara fixa det pĂ„ nĂ„got sĂ€tt (Jag vet inte: kanske Ă€ndra det, kanske dubbla licenser, kanske nĂ„got annat...). Detta gjorde mig sĂ„klart glad, för vid det laget hade jag redan tittat noga pĂ„ WebAssembly, och jag var pĂ„ nĂ„got sĂ€tt ledsen och oförstĂ„ende. Det fanns ocksĂ„ ett bibliotek som skulle sluka de grundlĂ€ggande blocken med övergĂ„ngsgrafen, producera bytekoden och till och med köra den i sjĂ€lva tolken, om det skulle behövas.
Sedan var det mer pÄ QEMU:s sÀndlista, men det hÀr handlar mer om frÄgan "Vem behöver det egentligen?" Och det Àr plötsligt, det visade sig att det var nödvÀndigt. Som ett minimum kan du skrapa ihop sÄdana anvÀndningsmöjligheter om det fungerar mer eller mindre snabbt:
- lanserar nÄgot pedagogiskt utan nÄgon installation alls
- virtualisering pÄ iOS, dÀr, enligt rykten, den enda applikationen som har rÀtt till kodgenerering i farten Àr en JS-motor (stÀmmer detta?)
- demonstration av mini-OS - en diskett, inbyggd, alla typer av firmware, etc...
WebblÀsarens körtidsfunktioner
Som jag redan sa Àr QEMU knuten till multithreading, men webblÀsaren har det inte. Tja, det vill sÀga nej... Först fanns det inte alls, sedan dök WebWorkers upp - sÄ vitt jag förstÄr Àr detta multitrÄdning baserat pÄ meddelandeförmedling utan delade variabler. Naturligtvis skapar detta betydande problem vid portering av befintlig kod baserad pÄ modellen med delat minne. Sedan, under pÄtryckningar frÄn allmÀnheten, genomfördes det ocksÄ under namnet SharedArrayBuffers. Det introducerades gradvis, man firade lanseringen i olika webblÀsare, sedan firade man nyÄr, och sedan Meltdown... Varefter man kom fram till att grova eller grova tidsmÀtningen, men med hjÀlp av delat minne och en trÄd som ökar rÀknaren, det Àr likadant . SÄ vi inaktiverade multithreading med delat minne. Det verkar som att de senare slog pÄ det igen, men som det blev klart frÄn det första experimentet finns det liv utan det, och i sÄ fall kommer vi att försöka göra det utan att förlita oss pÄ multithreading.
Den andra funktionen Àr omöjligheten av lÄgnivÄmanipulationer med stacken: du kan inte bara ta, spara det aktuella sammanhanget och byta till en ny med en ny stack. Anropsstacken hanteras av den virtuella JS-maskinen. Det verkar, vad Àr problemet, eftersom vi ÀndÄ bestÀmde oss för att hantera de tidigare flödena helt manuellt? Faktum Àr att block I/O i QEMU implementeras genom coroutines, och det Àr hÀr lÄgnivÄstackmanipulationer skulle komma till nytta. Lyckligtvis innehÄller Emscipten redan en mekanism för asynkrona operationer, till och med tvÄ: О . Den första fungerar genom betydande uppsvÀllning i den genererade JavaScript-koden och stöds inte lÀngre. Det andra Àr det nuvarande "korrekta sÀttet" och fungerar genom bytekodgenerering för den ursprungliga tolken. Det fungerar, naturligtvis, lÄngsamt, men det svÀller inte koden. Det Àr sant att stöd för koroutiner för denna mekanism mÄste bidra oberoende (det fanns redan koroutiner skrivna för Asyncify och det fanns en implementering av ungefÀr samma API för Emterpreter, du behövde bara koppla dem).
För tillfÀllet har jag Ànnu inte lyckats dela upp koden till en kompilerad i WASM och tolkad med Emterpreter, sÄ blockenheter fungerar inte Àn (se i nÀsta serie, som man sÀger...). Det vill sÀga, i slutÀndan borde du fÄ nÄgot i stil med denna roliga skiktade sak:
- tolkat block I/O. Tja, förvĂ€ntade du dig verkligen emulerat NVMe med inbyggd prestanda? đ
- statiskt kompilerad QEMU-huvudkod (översÀttare, andra emulerade enheter, etc.)
- dynamiskt kompilerad gÀstkod till WASM
Funktioner hos QEMU-kÀllor
Som du förmodligen redan gissat Àr koden för att emulera gÀstarkitekturer och koden för att generera vÀrdmaskininstruktioner separerade i QEMU. Faktum Àr att det Àr Ànnu lite knepigare:
- det finns gÀstarkitekturer
- finns acceleratorer, nÀmligen KVM för hÄrdvaruvirtualisering pÄ Linux (för kompatibla gÀst- och vÀrdsystem), TCG för JIT-kodgenerering var som helst. FrÄn och med QEMU 2.9 dök stöd för HAXM-hÄrdvaruvirtualiseringsstandarden upp pÄ Windows ()
- om TCG anvÀnds och inte hÄrdvaruvirtualisering, har den separat kodgenereringsstöd för varje vÀrdarkitektur, sÄvÀl som för den universella tolken
- ... och runt allt detta - emulerad kringutrustning, anvÀndargrÀnssnitt, migrering, inspelningsrepris, etc.
Visste du förresten: QEMU kan emulera inte bara hela datorn utan Ă€ven processorn för en separat anvĂ€ndarprocess i vĂ€rdkĂ€rnan, som till exempel anvĂ€nds av AFL fuzzer för binĂ€r instrumentering. Kanske nĂ„gon skulle vilja överföra detta funktionssĂ€tt för QEMU till JS? đ
Som de flesta lÄngvariga gratisprogram, byggs QEMU genom samtalet configure О make. LÄt oss sÀga att du bestÀmmer dig för att lÀgga till nÄgot: en TCG-backend, trÄdimplementering, nÄgot annat. Skynda dig inte att vara glad/förskrÀckt (understryka vid behov) vid möjligheten att kommunicera med Autoconf - faktiskt, configure QEMU:s Àr tydligen sjÀlvskrivna och genereras inte frÄn nÄgonting.
WebAssembly
SÄ vad heter den hÀr saken WebAssembly (alias WASM)? Detta Àr en ersÀttning för Asm.js och utger sig inte lÀngre för att vara giltig JavaScript-kod. TvÀrtom, det Àr rent binÀrt och optimerat, och till och med bara att skriva ett heltal i det Àr inte sÀrskilt enkelt: för kompakthet lagras det i formatet .
Du kanske har hört talas om relooping-algoritmen för Asm.js - det hÀr Àr ÄterstÀllningen av "high-level" exekveringsflödeskontrollinstruktioner (det vill sÀga om-dÄ-annat, loopar, etc.), för vilka JS-motorer Àr designade, frÄn lÄgnivÄ LLVM IR, nÀrmare maskinkoden som exekveras av processorn. Naturligtvis Àr den mellanliggande representationen av QEMU nÀrmare den andra. Det verkar som att hÀr Àr det, bytecode, slutet pÄ plÄgan... Och sÄ finns det block, if-then-else och loops!
Och detta Àr ytterligare en anledning till varför Binaryen Àr anvÀndbar: den kan naturligtvis acceptera högnivÄblock nÀra det som skulle lagras i WASM. Men den kan ocksÄ producera kod frÄn en graf av grundlÀggande block och övergÄngar mellan dem. Tja, jag har redan sagt att det döljer WebAssembly-lagringsformatet bakom det bekvÀma C/C++ API.
TCG (Tiny Code Generator)
TCG backend för C-kompilatorn. Sedan kunde den tydligen inte stÄ emot konkurrensen med GCC, men till slut hittade den sin plats i QEMU som en kodgenereringsmekanism för vÀrdplattformen. Det finns ocksÄ en TCG-backend som genererar en del abstrakt bytekod, som omedelbart exekveras av tolken, men jag bestÀmde mig för att undvika att anvÀnda den denna gÄng. Det faktum att det i QEMU redan Àr möjligt att möjliggöra övergÄngen till den genererade TB genom funktionen tcg_qemu_tb_exec, det visade sig vara vÀldigt anvÀndbart för mig.
För att lĂ€gga till en ny TCG-backend till QEMU mĂ„ste du skapa en underkatalog tcg/<ĐžĐŒŃ Đ°ŃŃ
ĐžŃĐ”ĐșŃŃŃŃ> (I detta fall, tcg/binaryen), och den innehĂ„ller tvĂ„ filer: tcg-target.h Đž tcg-target.inc.c Đž allt handlar om configure. Du kan lĂ€gga andra filer dĂ€r, men, som du kan gissa frĂ„n namnen pĂ„ dessa tvĂ„, kommer de bĂ„da att inkluderas nĂ„gonstans: en som en vanlig rubrikfil (den ingĂ„r i tcg/tcg.h, och den finns redan i andra filer i katalogerna tcg, accel och inte bara), den andra - bara som ett kodavsnitt i tcg/tcg.c, men den har tillgĂ„ng till dess statiska funktioner.
NÀr jag bestÀmde mig för att jag skulle spendera för mycket tid pÄ detaljerade undersökningar av hur det fungerar, kopierade jag helt enkelt "skeletten" av dessa tvÄ filer frÄn en annan backend-implementering, och indikerade Àrligt detta i licenshuvudet.
fil tcg-target.h innehÄller frÀmst instÀllningar i formulÀret #define-s:
- hur mÄnga register och vilken bredd finns det pÄ mÄlarkitekturen (vi har sÄ mÄnga vi vill, sÄ mÄnga vi vill - frÄgan Àr mer om vad som kommer att genereras till effektivare kod av webblÀsaren pÄ arkitekturen "helt mÄl" ...)
- justering av vÀrdinstruktioner: pÄ x86, och Àven i TCI, Àr instruktioner inte justerade alls, men jag ska inte lÀgga in instruktioner alls i kodbufferten, utan pekare till Binaryen-biblioteksstrukturer, sÄ jag sÀger: 4 bytes
- vilka valfria instruktioner backend kan generera - vi inkluderar allt vi hittar i Binaryen, lÄt acceleratorn dela upp resten i enklare sjÀlv
- Vad Ă€r den ungefĂ€rliga storleken pĂ„ TLB-cachen som begĂ€rs av backend. Faktum Ă€r att i QEMU Ă€r allt allvarligt: ââĂ€ven om det finns hjĂ€lpfunktioner som utför laddning/lagring med hĂ€nsyn till gĂ€st-MMU (var skulle vi vara utan den nu?), sparar de sin översĂ€ttningscache i form av en struktur, bearbetning som Ă€r bekvĂ€m att bĂ€dda in direkt i sĂ€ndningsblock. FrĂ„gan Ă€r vilken offset i denna struktur som bearbetas mest effektivt av en liten och snabb sekvens av kommandon?
- hÀr kan du justera syftet med ett eller tvÄ reserverade register, aktivera att ringa TB genom en funktion och eventuellt beskriva ett par smÄ
inline-funktioner somflush_icache_range(men detta Àr inte vÄrt fall)
fil tcg-target.inc.c, naturligtvis, Àr vanligtvis mycket större i storlek och innehÄller flera obligatoriska funktioner:
- initiering, inklusive begrÀnsningar för vilka instruktioner som kan fungera pÄ vilka operander. Uppenbart kopierat av mig frÄn en annan backend
- funktion som tar en intern bytekodinstruktion
- Du kan ocksÄ lÀgga in hjÀlpfunktioner hÀr, och du kan Àven anvÀnda statiska funktioner frÄn
tcg/tcg.c
För mig sjÀlv valde jag följande strategi: i de första orden i nÀsta översÀttningsblock skrev jag ner fyra pekare: ett startmÀrke (ett visst vÀrde i nÀrheten 0xFFFFFFFF, som bestÀmde det aktuella tillstÄndet för TB), kontext, genererad modul och magiskt nummer för felsökning. Först placerades mÀrket in 0xFFFFFFFF - nvar n - ett litet positivt tal, och varje gÄng det utfördes via tolken ökade det med 1. NÀr det nÄdde 0xFFFFFFFE, kompilering Àgde rum, modulen sparades i funktionstabellen, importerades till en liten "startprogram", till vilken exekveringen gick frÄn tcg_qemu_tb_exec, och modulen togs bort frÄn QEMU-minnet.
För att parafrasera klassikerna, "Crutch, how much is intertwined in this sound for the proger's heart...". Men minnet lÀckte nÄgonstans. Dessutom var det minne som hanterades av QEMU! Jag hade en kod som nÀr jag skrev nÀsta instruktion (nÄja, det vill sÀga en pekare) raderade den vars lÀnk fanns pÄ den hÀr platsen tidigare, men det hjÀlpte inte. Faktiskt, i det enklaste fallet, allokerar QEMU minne vid start och skriver den genererade koden dÀr. NÀr bufferten tar slut kastas koden ut och nÀsta börjar skrivas pÄ dess plats.
Efter att ha studerat koden insÄg jag att tricket med det magiska numret tillÀt mig att inte misslyckas med högförstörelse genom att frigöra nÄgot fel pÄ en oinitierad buffert vid första passet. Men vem skriver om bufferten för att kringgÄ min funktion senare? Som Emscripten-utvecklarna rekommenderar, nÀr jag stötte pÄ ett problem, portade jag den resulterande koden tillbaka till den ursprungliga applikationen, satte in Mozilla Record-Replay pÄ den... I allmÀnhet insÄg jag till slut en enkel sak: för varje block, a struct TranslationBlock med dess beskrivning. Gissa var... Just det, precis innan blocket precis i bufferten. NÀr jag insÄg detta bestÀmde jag mig för att sluta anvÀnda kryckor (Ätminstone nÄgra), och kastade helt enkelt ut det magiska numret och överförde de ÄterstÄende orden till struct TranslationBlock, skapa en enkellÀnkad lista som snabbt kan passeras nÀr översÀttningscachen ÄterstÀlls, och frigöra minne.
NÄgra kryckor finns kvar: till exempel markerade pekare i kodbufferten - nÄgra av dem Àr helt enkelt BinaryenExpressionRef, det vill sÀga, de tittar pÄ de uttryck som mÄste lÀggas linjÀrt in i det genererade grundblocket, del Àr villkoret för övergÄng mellan BB, del Àr vart man ska gÄ. Jo, det finns redan förberedda block för Relooper som behöver kopplas in enligt förutsÀttningarna. För att sÀrskilja dem anvÀnds antagandet att de alla Àr justerade med minst fyra byte, sÄ du kan sÀkert anvÀnda de minst signifikanta tvÄ bitarna för etiketten, du behöver bara komma ihÄg att ta bort den om det behövs. Förresten, sÄdana etiketter anvÀnds redan i QEMU för att indikera anledningen till att lÀmna TCG-slingan.
AnvÀnder Binaryen
Moduler i WebAssembly innehÄller funktioner som var och en innehÄller en body, vilket Àr ett uttryck. Uttryck Àr unÀra och binÀra operationer, block som bestÄr av listor med andra uttryck, kontrollflöde, etc. Som jag redan har sagt Àr kontrollflödet hÀr organiserat precis som högnivÄgrenar, loopar, funktionsanrop, etc. Argument till funktioner skickas inte pÄ stacken, utan explicit, precis som i JS. Det finns ocksÄ globala variabler, men jag har inte anvÀnt dem, sÄ jag kommer inte att berÀtta om dem.
Funktioner har ocksÄ lokala variabler, numrerade frÄn noll, av typen: int32 / int64 / float / double. I det hÀr fallet Àr de första n lokala variablerna de argument som skickas till funktionen. Observera att Àven om allt hÀr inte Àr helt pÄ lÄg nivÄ nÀr det gÀller kontrollflöde, har heltal fortfarande inte attributet "signed/unsigned": hur numret beter sig beror pÄ operationskoden.
Generellt sett tillhandahÄller Binaryen : du skapar en modul, i honom skapa uttryck - unÀra, binÀra, block frÄn andra uttryck, kontrollflöde, etc. Sedan skapar man en funktion med ett uttryck som sin kropp. Om du, som jag, har en övergÄngsgraf pÄ lÄg nivÄ, kommer relooper-komponenten att hjÀlpa dig. SÄvitt jag förstÄr Àr det möjligt att anvÀnda högnivÄstyrning av exekveringsflödet i ett block, sÄ lÀnge det inte gÄr utanför blockets grÀnser - det vill sÀga det Àr möjligt att göra intern snabb vÀg / lÄngsam sökvÀg som förgrenas inuti den inbyggda TLB-cachebearbetningskoden, men inte för att störa det "externa" kontrollflödet . NÀr du frigör en relooper frigörs dess block, nÀr du frigör en modul försvinner uttrycken, funktionerna etc. som tilldelats den. arena.
Men om du vill tolka kod i farten utan att skapa och ta bort en tolkinstans i onödan, kan det vara vettigt att lÀgga in denna logik i en C++-fil, och dÀrifrÄn direkt hantera hela C++-API:et i biblioteket, utan att klara- gjort omslag.
SÄ för att generera koden du behöver
// ĐœĐ°ŃŃŃĐŸĐžŃŃ ĐłĐ»ĐŸĐ±Đ°Đ»ŃĐœŃĐ” паŃĐ°ĐŒĐ”ŃŃŃ (ĐŒĐŸĐ¶ĐœĐŸ ĐżĐŸĐŒĐ”ĐœŃŃŃ ĐżĐŸŃĐŸĐŒ)
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);... om jag har glömt nÄgot, förlÄt, detta Àr bara för att representera skalan, och detaljerna finns i dokumentationen.
Och nu börjar crack-fex-pex, ungefÀr sÄ hÀr:
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);För att pÄ nÄgot sÀtt koppla ihop QEMU och JS vÀrldar och samtidigt snabbt komma Ät de kompilerade funktionerna skapades en array (en funktionstabell för import till startprogrammet), och de genererade funktionerna placerades dÀr. För att snabbt berÀkna indexet anvÀndes indexet för nollordsöversÀttningsblocket initialt som det, men sedan började indexet som berÀknades med denna formel helt enkelt passa in i fÀltet i struct TranslationBlock.
Förresten, (för nÀrvarande med skum licens) fungerar bara bra i Firefox. Chrome-utvecklare var pÄ nÄgot sÀtt inte redo till det faktum att nÄgon skulle vilja skapa mer Àn tusen instanser av WebAssembly-moduler, sÄ de tilldelade helt enkelt en gigabyte virtuellt adressutrymme för varje...
Det var allt tills vidare. Kanske kommer det en annan artikel om nÄgon Àr intresserad. Det finns nÀmligen kvar Ätminstone endast fÄ blockenheter att fungera. Det kan ocksÄ vara vettigt att göra kompileringen av WebAssembly-moduler asynkron, som Àr brukligt i JS-vÀrlden, eftersom det fortfarande finns en tolk som kan göra allt detta tills den inbyggda modulen Àr klar.
Till sist en gÄta: du har kompilerat en binÀr pÄ en 32-bitars arkitektur, men koden, genom minnesoperationer, klÀttrar frÄn Binaryen, nÄgonstans pÄ stacken, eller nÄgon annanstans i de övre 2 GB av 32-bitars adressutrymmet. Problemet Àr att frÄn Binaryens synvinkel Àr detta att fÄ tillgÄng till en för stor resulterande adress. Hur kommer man runt detta?
PÄ administratörens sÀtt
Jag testade det inte till slut, men min första tanke var: "Vad hÀnder om jag installerar 32-bitars?" Linux"DÄ kommer den övre delen av adressutrymmet att upptas av kÀrnan. Den enda frÄgan Àr hur mycket som kommer att upptas: 1 eller 2 GB."
PÄ ett programmerings sÀtt (alternativ för utövare)
LÄt oss blÄsa en bubbla lÀngst upp i adressutrymmet. SjÀlv förstÄr jag inte varför det fungerar - dÀr redan det mÄste finnas en stack. Men "vi Àr utövare: allt fungerar för oss, men ingen vet varför..."
// 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));
}... det Àr sant att det inte Àr kompatibelt med Valgrind, men lyckligtvis driver Valgrind sjÀlv vÀldigt effektivt ut alla dÀrifrÄn :)
Kanske kan nÄgon ge en bÀttre förklaring av hur denna min kod fungerar...
KĂ€lla: will.com
