QEMU.js: nu seriös och med WASM

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 kommentar 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 Binaryen som en qemu-backend i sammanhanget, "Han gjorde nĂ„got sĂ„dant, han kanske sĂ€ger nĂ„got." Vi pratade om att anvĂ€nda Emscriptens relaterade bibliotek Binaryen 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Ă„ binĂ€rt format 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 ett brev 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 det kommer att fungera ganska exakt. 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Ä: Asynkronisera О Emperpreter. 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 (detaljerna)
  • 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 LEB128.

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 var ursprungligen 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 Đž föreskriva 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 som flush_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 enkel C-API: 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, demo (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

Köp pĂ„litlig hosting för webbplatser med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster