QEMU.js: nu pÄ allvar och med WASM

För lĂ€nge sedan beslöt jag mig för att, pĂ„ skĂ€mt, bevisa reversibiliteten av processen och lĂ€ra mig att generera JavaScript (eller snarare, Asm.js) frĂ„n maskinkod. För experimentet valdes QEMU, och en tid senare skrevs en artikel pĂ„ Habr. I kommentarerna rĂ„dde man mig att konvertera projektet till WebAssembly, men att överge ett nĂ€stan fĂ€rdigt projekt kĂ€ndes inte sĂ„ lockande... Arbetet gick framĂ„t, men det var otroligt lĂ„ngsamt, och för inte sĂ„ lĂ€nge sedan dök det upp en kommentar pĂ„ den artikel kommentar om ”SĂ„ hur blev det?” Till mitt utförliga svar fick jag höra “Det hĂ€r förtjĂ€nar en artikel”. Ja, om det förtjĂ€nar, sĂ„ blir det en artikel. Kanske blir den anvĂ€ndbar för nĂ„gon. HĂ€r kommer lĂ€saren att lĂ€ra sig fakta om strukturen av backend-kodgenereringen i QEMU, samt hur man skriver en Just-in-Time kompilator för webbaserade applikationer.

Uppgifter

Eftersom jag redan hade lĂ€rt mig att ”snarare brutalt” portera QEMU till JavaScript, sĂ„ bestĂ€mde jag mig den hĂ€r gĂ„ngen för att göra det ordentligt och inte upprepa gamla misstag.

Fel nummer ett: avvika frÄn point release

Mitt första misstag var att avvika min version frĂ„n upstream-versionen 2.4.1. DĂ„ verkade det vara en bra idĂ©: om en point release finns, sĂ„ Ă€r den förmodligen stabilare Ă€n bara 2.4, och Ă€nnu mer Ă€n grenen master. Eftersom jag planerade att lĂ€gga till en hel del av mina egna buggar, sĂ„ behövde jag verkligen inga andra. SĂ„ blev det nog ocksĂ„. Men hĂ€r Ă€r problemet: QEMU stĂ„r inte stilla, och vid nĂ„got tillfĂ€lle annonserade de faktiskt en optimering av den genererade koden med cirka 10%. “Aha, nu kan jag slĂ„ ihop” tĂ€nkte jag och blev besviken. HĂ€r mĂ„ste jag göra en avstickare: pĂ„ grund av den enkla trĂ„dkonstruktionen i QEMU.js och det faktum att den ursprungliga QEMU inte tillĂ„ter avsaknad av parallellism (dvs för den Ă€r det kritiskt med möjligheten att flera orelaterade code paths kan köras samtidigt, inte bara att masteranvĂ€nda alla kĂ€rnor”), sĂ„ tvingades jag att ”vĂ€nda” huvudfunktionerna för trĂ„darna för att möjliggöra samtal utifrĂ„n. Detta skapade vissa naturliga problem nĂ€r jag skulle slĂ„ ihop dem. Dock skulle det faktum att en del av Ă€ndringarna i grenen

, som jag försökte slÄ ihop min kod med, Àven hade cherry pickats in i point release (och alltsÄ ocksÄ i min gren) antagligen inte ha gjort saken enklare. master.

Sammanfattningsvis beslöt jag att det fortfarande var meningsfullt att kassera prototypen, dissekera den i delar och bygga en ny version frÄn grunden baserat pÄ nÄgot nyare och nu redan av

I grund och botten Ă€r detta inte ett misstag — det Ă€r helt enkelt en aspekt av att skapa ett projekt under förhĂ„llanden av fullstĂ€ndig oklarhet kring "vart vi ska och hur?" och överhuvudtaget "kommer vi ens dit?". Under dessa förhĂ„llanden slarvprogrammering var ett acceptabelt alternativ, men naturligtvis ville jag inte upprepa det utan behov. Denna gĂ„ng ville jag göra det pĂ„ rĂ€tt sĂ€tt: atomĂ€ra commits, medvetna kodĂ€ndringar (inte "trĂ„da slumpmĂ€ssiga tecken tills det kompilerar (med varningar)," som Linus Torvalds en gĂ„ng sa om nĂ„gon, om man fĂ„r tro Vikiquote) etc.

Misstag nummer tre: att ge sig ut i vattnet utan att veta botten

Detta har jag fortfarande inte helt blivit av med, men nu har jag beslutat att inte ta den absolut enklaste vĂ€gen, utan att göra "som vuxna gör", nĂ€mligen skriva min egen TCG backend frĂ„n grunden, sĂ„ att jag sedan inte behöver sĂ€ga "Ja, det Ă€r klart, det Ă€r lĂ„ngsamt, men jag kan ju inte kontrollera allt — TCI Ă€r sĂ„ skrivet...". Dessutom verkade detta frĂ„n början vara en uppenbar lösning, eftersom jag genererar binĂ€rkod. Som sagt, "SammanstĂ€lld Gentför, men inte den rĂ€tta": koden Ă€r givetvis binĂ€r, men kontrollen kan inte överföras bara sĂ„ dĂ€r — den mĂ„ste uttryckligen stoppas in i webblĂ€saren för kompilering, vilket resulterar i ett objekt frĂ„n JS-vĂ€rlden, som ocksĂ„ mĂ„ste sparas nĂ„gonstans. Å andra sidan, pĂ„ normala RISC-arkitekturer, sĂ„vitt jag förstĂ„r, Ă€r en typisk situation att det Ă€r nödvĂ€ndigt att explicit Ă„terstĂ€lla instruktionscachen för omgenererad kod — om det inte Ă€r vad vi behöver, sĂ„ Ă€r det Ă„tminstone nĂ€ra. Dessutom lĂ€rde jag mig frĂ„n mina tidigare försök att kontrollen inte verkar överföras till mitten av översĂ€ttningsblocket, sĂ„ byte-kod, som tolkas frĂ„n nĂ„got förskjutning, Ă€r inte sĂ€rskilt anvĂ€ndbar, och man kan helt enkelt generera efter en funktion pĂ„ TB.

Kom och sparkade

Även om jag började skriva om koden redan i juli, sĂ„ kom den magiska sparken osynligt: vanligtvis kommer e-post frĂ„n GitHub som aviseringar om svar pĂ„ frĂ„gor och pull-förfrĂ„gningar, men hĂ€r, plötsligt var det nĂ€mnd i trĂ„den Binaryen som en QEMU backend i sammanhanget, "Hör, han höll pĂ„ med nĂ„got liknande, kanske han kan sĂ€ga nĂ„got". Det handlade om att anvĂ€nda en beslĂ€ktad bibliotek till Emscripten Binaryen för att skapa WASM JIT. SĂ„ jag sa att ni har licensen Apache 2.0 och QEMU som helhet distribueras under GPLv2, och de Ă€r inte sĂ€rskilt kompatibla. Plötsligt visade det sig att licensen kan pĂ„ nĂ„got sĂ€tt justeras (jag vet inte: kanske byta, kanske dubbellicensiering, kanske nĂ„got annat
). Det gladde mig förstĂ„s, eftersom jag vid det laget hade tittat pĂ„ binĂ€rt format WebAssembly, och jag kĂ€nde mig lite ledsen och förvirrad. HĂ€r fanns det en bibliotek som bĂ„de kunde smĂ€lta grundlĂ€ggande byggblock med en övergĂ„ngsgraf, generera bytekod och till och med köra den i en tolk, om det behövdes.

Det fanns Àven ett brev pÄ QEMU:s mail-lista, men det handlar snarare om frÄgan, "Vem behöver det ens?". Men det plötsligt, visade sig vara nödvÀndigt. Minst sagt kan man skrapa ihop sÄdana anvÀndningsmöjligheter, om det fungerar hyfsat snabbt:

  • att köra nĂ„got lĂ€rande helt utan installation
  • virtualisering pĂ„ iOS, dĂ€r det enligt ryktet enda programmet som har rĂ€tt att generera kod i farten — Ă€r JS-motorn (och Ă€r det verkligen sĂ„?)
  • demonstration av mini-OS — en-diskett, inbyggda, olika firmware osv


Funktioner i webblÀsarens körmiljö

Som jag redan har nĂ€mnt, QEMU Ă€r kopplad till flerkörning, men det finns ingen i webblĂ€saren. AlltsĂ„, det fanns först inga
 sedan dök WebWorkers upp — sĂ„vitt jag förstĂ„r, Ă€r det flerkörning baserad pĂ„ meddelandeöverföring utan delade variabler. Naturligtvis skapar detta betydande problem vid portering av befintlig kod, baserad pĂ„ shared memory-modellen. Senare, under press frĂ„n allmĂ€nheten, implementerades den under namnet SharedArrayBuffers. Den blev gradvis introducerad, firades i olika webblĂ€sare, sedan firades nyĂ„ret, och sedan Meltdown
 Efter vilket det kom fram att oavsett hur mycket man finjusterade tidsmĂ€tningen, sĂ„ skulle man fortfarande fĂ„ en ganska exakt. SĂ„ de stĂ€ngde av flerkörning med delad minne. Det verkar som om de aktiverade det igen, men som det blev klart frĂ„n det första experimentet, Ă€r livet Ă€ndĂ„ möjligt utan det, sĂ„ vi försöker göra det utan att förlita oss pĂ„ flerkörning.

Den andra funktionen Àr att det inte gÄr att göra lÄgnivÄmanipulationer med stacken: du kan inte bara ta, spara nuvarande kontext och vÀxla till en ny med en ny stack. Anropsstacken hanteras av JS virtuella maskin. Vad Àr dÄ problemet, eftersom vi ÀndÄ har beslutat oss för att hantera de tidigare trÄdarna helt manuellt? Problemet Àr att block I/O i QEMU Àr implementerat via korutiner, och hÀr skulle vi verkligen behöva lÄgnivÄmanipulationer av stacken. Lyckligtvis innehÄller Emscripten redan en mekanism för asynkrona operationer, till och med tvÄ: Asyncify och Emterpreter. Den första fungerar genom en betydande uppblÄsning av den genererade JavaScript-koden och stöds inte lÀngre. Den andra Àr den nuvarande "rÀtta metoden" och fungerar genom att generera bytekod för sin egen tolk. Det fungerar, sÄklart, lÄngsamt, men svÀller inte koden. Det mÄste nÀmnas att stödet för korutiner för denna mekanism fick jag bidra till sjÀlv (det fanns redan korutiner skrivna för Asyncify och en implementation av ungefÀr samma API för Emterpreter, det krÀvdes bara att koppla dem).

Just nu har jag Ànnu inte hunnit separera koden i den som kan kompileras till WASM och den som tolkas med Emterpreter, sÄ blockenheten fungerar fortfarande inte (se i nÀsta avsnitt, som man sÀger
). SÄ, i slutÀndan borde det bli nÄgot sÄ roligt som:

  • tolkad block I/O. Vad trodde du, förvĂ€ntade du dig verkligen emulerat NVMe med inhemsk prestanda? 🙂
  • statisk kompilerad huvudkod för QEMU (översĂ€ttare, andra emulerade enheter osv.)
  • dynamiskt kompilerad gĂ€stkod till WASM

Egenskaper hos QEMU-kÀllkoder

Som du förmodligen redan gissat Àr koden för emulering av gÀstande arkitekturer och koden för att generera vÀrdmaskininstruktioner i QEMU separerade. Faktum Àr att det Àr Ànnu lite mer komplicerat:

  • det finns gĂ€stande arkitekturer
  • det finns acceleratorer, nĂ€mligen KVM för hĂ„rdvaruvirtualisering pĂ„ Linux (för kompatibla gĂ€stande och vĂ€rdsystem), TCG för JIT-kodgenerering var som helst. FrĂ„n och med QEMU 2.9 finns stöd för standarden för hĂ„rdvaruvirtualisering HAXM pĂ„ Windows (detaljer)
  • Om TCG anvĂ€nds istĂ€llet för hĂ„rdvaruvirtualisering, har det sĂ€rskilt stöd för kodgenerering för varje vĂ€rdarkitektur, sĂ„vĂ€l som för en universell tolk.
  • 
 och runt detta finns emulerad periferiutrustning, anvĂ€ndargrĂ€nssnitt, migrering, inspelning-Ă„terspelning, etc.

Förresten, visste du att: QEMU kan emulera inte bara hela datorer, utan Ă€ven en processor för separata anvĂ€ndarprocesser i vĂ€rdskĂ€rnan, vilket anvĂ€nds av exempelvis fuzzern AFL för instrumentering av binĂ€rer. Kanske finns det nĂ„gon som vill porta detta lĂ€ge av QEMU till JS? 😉

Precis som de flesta gamla fria program byggs QEMU genom anrop. konfigurera och make. Anta att du vill lĂ€gga till nĂ„got: TCG-backend, implementering av trĂ„dar, nĂ„got annat. Tveka inte att glĂ€djas/vara skrĂ€md (utfyllnad behövs) av utsikten att interagera med Autoconf — faktiskt Ă€r det konfigurera i QEMU uppenbarligen skrivet för hand och genereras av ingenting.

WebAssembly

Vad Àr dÄ WebAssembly (Àven kÀnt som WASM)? Det Àr en ersÀttning för Asm.js som numera inte lÄtsas vara giltig JavaScript-kod. TvÀrtom Àr det strikt binÀrt och optimerat, och att bara skriva ett heltal i det Àr inte helt enkelt: det lagras för kompakt i formatet LEB128.

Kanske har du hört talas om relooping-algoritmen för Asm.js — det handlar om att Ă„terstĂ€lla "högnivĂ„" kontrollflödesinstruktioner (det vill sĂ€ga if-then-else, loopar, etc.) som JavaScript-motorer Ă€r optimerade för, frĂ„n lĂ„gnivĂ„ LLVM IR som ligger nĂ€rmare maskinkod som utförs av processorn. Naturligtvis Ă€r QEMUs mellanliggande representation nĂ€rmare det andra. Det verkar som om vi har bytekoden och slutet pĂ„ plĂ„gan
 och hĂ€r kommer blocken, if-then-else och loopar
!

Och detta Àr ytterligare en anledning till att Binaryen Àr anvÀndbart: det kan naturligtvis ta emot högnivÄblock, nÀra det som kommer att sparas i WASM. Men det kan ocksÄ avge kod frÄn grafen av grundlÀggande block och övergÄngar mellan dem. Jag nÀmnde redan vad den döljer bakom det bekvÀma C/C++ API:et för lagringsformatet för WebAssembly.

TCG (Tiny Code Generator)

TCG blev ursprungligen backend för C-kompilatorn. Sedan tÄlde det tydligen inte konkurrensen med GCC, men hittade till slut sin plats i QEMU som en kodgenereringsmekanism för vÀrdplattformen. Det finns ocksÄ en TCG-backend som genererar en slags abstrakt bytekod, som tolkaren genast exekverar, men jag beslutade att avstÄ frÄn dess anvÀndning den hÀr gÄngen. Det faktum att det i QEMU redan finns möjlighet att gÄ över till den genererade TB via funktionen tcg_qemu_tb_exec, visade sig vara mycket anvÀndbart för mig.

För att lĂ€gga till en ny TCG-backend i QEMU mĂ„ste man skapa en underkatalog tcg/ (i det hĂ€r fallet, tcg/binaryen), och i den ska det finnas tvĂ„ filer: tcg-target.h och tcg-target.inc.c och skriver allt detta i konfigurera. Man kan ocksĂ„ lĂ€gga dit andra filer, men som man kan gissa frĂ„n namnen pĂ„ dessa tvĂ„, kommer de bĂ„da att inkluderas nĂ„gonstans: den ena som en vanlig headerfil (den inkluderas i tcg/tcg.h, och den andra i andra filer i katalogerna tcg, accel och inte bara), den andra — endast som en kodsnutt i tcg/tcg.c, men den har tillgĂ„ng till dess statiska funktioner.

Jag insÄg att jag skulle spendera för mycket tid pÄ detaljerad forskning om hur det Àr ordnat, sÄ jag kopierade helt enkelt "skeletterna" av dessa tvÄ filer frÄn en annan backend-implementation och angav Àrligt detta i licensens rubrik.

Fil tcg-target.h innehÄller huvudsakligen instÀllningar i form av #define-er:

  • hur mĂ„nga register och vilken bredd som finns pĂ„ mĂ„larkitekturen (hos oss — sĂ„ mĂ„nga vi vill ha — frĂ„gan Ă€r mer vad som kommer att genereras i mer effektiv kod av webblĂ€saren för "verkligt mĂ„l"-arkitektur
)
  • justering av vĂ€rd instruktioner: pĂ„ x86, och Ă€ven i TCI, justeras instruktioner överhuvudtaget inte, jag planerar att lĂ€gga i kodbufferten, och inte instruktioner alls, utan pekare pĂ„ strukturer frĂ„n biblioteket Binaryen, sĂ„ jag sĂ€ger: 4 byte
  • vilka valfria instruktioner backend kan generera — vi inkluderar allt vi hittar i Binaryen, resten lĂ„ter vi accelereraren bryta ner till enklare sjĂ€lv.
  • vilken ungefĂ€rlig storlek har TLB-cachen som begĂ€rs av backend. Saken Ă€r den att i QEMU Ă€r allt pĂ„ riktigt: Ă€ven om det finns hjĂ€lpfunktioner som genomför load/store med hĂ€nsyn till gĂ€stens MMU (och hur skulle det kunna vara utan det nu för tiden?), sĂ„ sparar de sin översĂ€ttningscache i form av en struktur, vars behandling Ă€r bekvĂ€m att integrera direkt i översĂ€ttningsblocken. FrĂ„gan Ă€r vilket offset i denna struktur som Ă€r mest effektivt bearbetat av en liten och snabb sekvens av kommandon.
  • hĂ€r kan man Ă€ven justera syftet för en eller tvĂ„ reserverade register, aktivera anrop av TB genom funktionen och eventuellt beskriva ett par smĂ„. inline-funktioner som flush_icache_range (men det Ă€r inte vĂ„rt fall).

Fil tcg-target.inc.c, naturligtvis, Àr vanligtvis mycket större i storlek och innehÄller flera obligatoriska funktioner:

  • initialisering, som ocksĂ„ anger begrĂ€nsningar för vilken instruktion som kan arbeta med vilka operander. Jag har frĂ€ckt kopierat detta frĂ„n en annan backend.
  • funktion som tar en instruktion av intern bytekod.
  • hĂ€r kan Ă€ven hjĂ€lpande funktioner placeras, och hĂ€r kan statiska funktioner anvĂ€ndas frĂ„n. tcg/tcg.c

För mig valde jag följande strategi: i de första orden av det nya översĂ€ttningsblocket skrev jag fyra pekare: en mĂ€rkning av början (ett vĂ€rde i nĂ€rheten av 0xFFFFFFFF, efter vilken det aktuella tillstĂ„ndet av TB bestĂ€mdes), kontext, den genererade modulen och ett magic number för debugning. Först stĂ€lldes mĂ€rkningen in pĂ„ 0xFFFFFFFF - n, dĂ€r n — ett litet positivt tal, och vid varje utförande genom tolken ökades det med 1. NĂ€r det nĂ„dde 0xFFFFFFFE, skedde kompilering, modulen sparades i en funktionstabell, som importerades i en liten „startare”, dit utförandet gick frĂ„n tcg_qemu_tb_exec, och modulen togs bort frĂ„n minnet i QEMU.

StĂ€llande pĂ„ sin spets, "Stödrulle, sĂ„ mycket som detta ljud har flĂ€tat samman för programmerarens hjĂ€rta...". ÄndĂ„, minnet försvann nĂ„gonstans. Dessutom var det minne som hanterades av QEMU! Jag hade en kod som vid skrivning av nĂ€sta instruktion (dvs. pekaren) tog bort den som tidigare refererades till pĂ„ denna plats, men det hjĂ€lpte inte. Egentligen, i det enklaste fallet, allokerar QEMU minne vid start och skriver den genererade koden dit. NĂ€r bufferten tar slut, kastas koden, och nĂ€sta börjar skrivas pĂ„ dess plats.

Efter att ha studerat koden insĂ„g jag att lösningen med magic number förhindrade krasch vid minneshanteringen, genom att frigöra nĂ„got oavsett pĂ„ en oinitialiserad buffert vid första genomgĂ„ngen. Men vem skriver om bufferten utan att anvĂ€nda min funktion senare? Som Emscripten-utvecklarna föreslĂ„r, nĂ€r jag stötte pĂ„ problemet, portade jag den resulterande koden tillbaka till en inhemsk applikation och anvĂ€nde Mozilla Record-Replay... Sammanfattningsvis kom jag fram till en enkel insikt: varje block tilldelas struct TranslationBlock med dess beskrivning. Gissa var
 RĂ€tt, direkt före blocket i bufferten. NĂ€r jag insĂ„g detta bestĂ€mde jag mig för att sluta med nödlösningar (Ă„tminstone nĂ„gra), och kastade helt enkelt bort magic number, och flyttade de Ă„terstĂ„ende orden till struct TranslationBlock, genom att skapa en enkelt lĂ€nkad lista, som kan genomsökas snabbt vid rensning av översĂ€ttningscache, och frigöra minnet.

Vissa nödlösningar kvarstod: exempelvis de markerade pekarna i kodbufferten — en del av dem Ă€r helt enkelt BinaryenExpressionRef, det vill sĂ€ga de pekar pĂ„ uttrycken som mĂ„ste placeras linjĂ€rt i den genererade grundblock, en del — villkoret för övergĂ„ng mellan block, och en del — vart man ska övergĂ„. Dessutom finns det redan förberedda block för Relooper som behöver kopplas samman enligt villkor. För att sĂ€rskilja dem anvĂ€nds antagandet att alla Ă€r justerade Ă„tminstone till fyra byte, sĂ„ man kan lugnt anvĂ€nda de tvĂ„ lĂ€gsta bitarna för etiketten, man fĂ„r bara inte glömma att ta bort den vid behov. För övrigt anvĂ€nds sĂ„dana etiketter redan i QEMU för att ange orsaken till uttrĂ€det frĂ„n TCG-cykeln.

AnvÀndning av Binaryen

Moduler i WebAssembly innehÄller funktioner, dÀr varje funktion har en kropp som utgör ett uttryck. Uttryck Àr unÀra och binÀra operationer, block som bestÄr av listor av andra uttryck, kontrollflöde etc. Som jag redan nÀmnt organiseras kontrollflödet hÀr specifikt som hög nivÄ förgreningar, loopar, funktionsanrop etc. Argumenten till funktionerna överförs inte pÄ stacken utan uttryckligen, precis som i JS. Det finns Àven globala variabler, men jag har inte anvÀnt dem, sÄ jag kommer inte att berÀtta om dem.

Funktioner har ocksÄ nollindexerade lokala variabler av typerna: int32 / int64 / float / double. De första n lokala variablerna Àr argument som skickas till funktionen. Observera att Àven om det hÀr inte Àr helt lÄgnivÄ nÀr det kommer till kontrollflöde, sÄ bÀr heltal fortfarande inte pÄ egenskapen "tecken/utan tecken": hur ett tal beter sig beror pÄ operationskoden.

Överlag erbjuder Binaryen ett enkelt C-API: du skapar ett modul, i vilket du skapar uttryck – unĂ€ra, binĂ€ra, block av andra uttryck, kontrollflöde, osv. Sedan skapar du en funktion dĂ€r du anger uttrycket som kropp. Om du, som jag, har en lĂ„gnivĂ„ övergĂ„ngsgraf – kommer komponenten relooper att hjĂ€lpa dig. SĂ„vitt jag vet, kan du anvĂ€nda hög-nivĂ„ kontroll av exekveringsflödet i blocket sĂ„ lĂ€nge det inte gĂ„r utanför blocket – det vill sĂ€ga att göra intern förgrening fast path / slow path inom den inbyggda TLB-cachebehandlingskoden Ă€r möjligt, men att ingripe i den "yttre" kontrollflödet Ă€r inte tillĂ„tet. NĂ€r du frigör relooper, frigörs dess block, nĂ€r du frigör modulen, försvinner uttrycken, funktionerna osv. som skapats pĂ„ dess arena..

Men om du vill tolka koden under körning utan onödigt mÄnga skapande och radering av instanser av tolkningsmotorn, kan det vara vÀrt att flytta logiken till en C++-fil och direkt styra hela C++ API:et i biblioteket, utan att gÄ genom fÀrdiga wrappers.

SÄledes, för att generera kod, behövs

// ĐœĐ°ŃŃ‚Ń€ĐŸĐžŃ‚ŃŒ ĐłĐ»ĐŸĐ±Đ°Đ»ŃŒĐœŃ‹Đ” ĐżĐ°Ń€Đ°ĐŒĐ”Ń‚Ń€Ń‹ (ĐŒĐŸĐ¶ĐœĐŸ ĐżĐŸĐŒĐ”ĐœŃŃ‚ŃŒ ĐżĐŸŃ‚ĐŸĐŒ)
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, det hĂ€r Ă€r bara för att ge en uppfattning om skalan, detaljerna finns i dokumentationen.

Och nu börjar kruxet, 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,
          // ...
      }
  );
  // och hÀr har du redan en instans!
}, buf, sz);

För att pÄ nÄgot sÀtt koppla ihop QEMU-vÀrlden med JS och samtidigt snabbt komma Ät de kompilerade funktionerna skapades en array (funktionsmatris för import i startprogrammet), och dit lades de genererade funktionerna. För att snabbt berÀkna index anvÀndes initialt indexet för det första ordet i translation block, men senare blev indexet, berÀknat enligt en sÄdan formel, helt enkelt inskrivet i fÀltet. struct TranslationBlock.

Förresten, demoversioner (Ă€n sĂ„ lĂ€nge med en oklar licens) fungerar bara riktigt bra i Firefox. Chrome-utvecklarna var inte riktigt redo för att nĂ„gon skulle vilja skapa mer Ă€n tusen instanser av WebAssembly-moduler, sĂ„ de tilldelade helt enkelt ett gigabyte virtuellt adresseringsutrymme för varje


Detta Àr allt för nu. Det kan bli ytterligare en artikel om det finns intresse för den. NÀmligen, minst bara att fÄ blockenheter att fungera. Det kan Àven vara vÀrt att göra kompileringen av WebAssembly-moduler asynkron, som det Àr brukligt i JS-vÀrlden, eftersom det ÀndÄ finns en tolk som kan utföra allt detta medan den inhemska modulen förbereds.

Avslutningsvis en gÄta: du har samlat en binÀrfil pÄ en 32-bitarsarkitektur, men koden gÄr genom minnesoperationer av nÄgon anledning ut ur Binaryen, nÄgonstans till stacken eller nÄgon annanstans i de övre 2 GB av det 32-bitars adresseringsutrymmet. Problemet Àr att detta med Binaryens ögon innebÀr en Ätkomst till en för stor resulterande adress. Hur kan detta lösas?

I admin-mening

Jag testade aldrig detta i slutÀndan, men första tanken var "Vad hÀnder om jag installerar 32-bitars Linux?" DÄ kommer den övre delen av adressutrymmet att upptas av kÀrnan. FrÄgan Àr bara hur mycket som kommer att tas: 1 eller 2 Gb.

I programmerarmening (val för praktiker)

Vi ska piffa upp den övre delen av adressutrymmet. Jag förstĂ„r inte riktigt varför det fungerar — det borde ju finnas en stack dĂ€r. Men "vi praktiker: allt fungerar, men ingen vet varför
". redan 
med Valgrind, dock inte kompatibelt, men lyckligtvis trycker Valgrind ut alla vĂ€ldigt effektivt dĂ€rifrĂ„n 🙂

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

Kanske kan nĂ„gon ge en bĂ€ttre förklaring pĂ„ hur min kod fungerar


Qemu.js med stöd för JIT: du kan fortfarande vrida köttfÀrsen bakÄt

KĂ€lla: habr.com

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