För lÀnge sedan bestÀmde jag mig för att bevisa omvÀndbarheten av processen och lÀra mig generera JavaScript (eller snarare Asm.js) frÄn maskinkod. QEMU valdes för experimentet, och efter ett tag skrev jag en artikel pÄ Habr. I kommentarerna fick jag rÄdet att göra om projektet till WebAssembly, men att ge upp ett nÀstan klart projekt kÀndes inte riktigt lockande... Arbetet fortsatte, men det gick mycket trögt, och nyligen dök det upp en kommentar i den artikeln om "SÄ hur gick det till slut?". PÄ min utförliga svar hörde jag "Det hÀr Àr vÀrt en artikel". SÄ, om det Àr vÀrt det, blir det en artikel. Kanske nÄgon kan ha nytta av det. LÀsaren kommer att lÀra sig vissa fakta om strukturen av backend-kodgenereringen i QEMU, samt hur man skriver en Just-in-Time-kompilator för webapplikationer.
Uppgifter
Eftersom jag redan hade lÀrt mig att "nÄgon typ av" portera QEMU till JavaScript, beslutades det denna gÄng att göra det pÄ ett klokt sÀtt och inte upprepa gamla misstag.
Fel nummer ett: avvika frÄn point release
Mitt första misstag var att skapa en avknoppning av min version frÄn upstream-version 2.4.1. DÄ verkade det vara en bra idé: om det finns en point release, sÄ Àr den förmodligen stabilare Àn den enkla 2.4, och Àn mindre Àn grenen. master. Och eftersom jag planerade att lÀgga till en hel del egna buggar, sÄ behövde jag definitivt inte andras. SÄ blev det nog Àven. Men hÀr Àr problemet: QEMU stÄr inte stilla, och vid nÄgot tillfÀlle meddelade de till och med en optimering av den genererade koden med cirka 10 procent. "Aha, nu kommer jag att integrera," tÀnkte jag och blev besviken. HÀr behöver vi göra en avstickare: med hÀnsyn till det enstaka karaktÀren av QEMU.js och det faktum att den ursprungliga QEMU inte förutsÀtter avsaknaden av flertrÄdighet (dvs. det Àr kritiskt för den att kunna hantera flera oberoende kodvÀgar samtidigt, snarare Àn att bara "anvÀnda alla kÀrnor"), var tvungna att "vrida" huvudfunktionerna för trÄdar för att kunna anropas frÄn utsidan. Detta skapade vissa naturliga problem vid sammanslagningen. Men faktum Àr att vissa Àndringar frÄn grenen master, frÄn vilken jag försökte slÄ samman min kod, ocksÄ plockades ut i point release (och dÀrmed i min gren), vilket med stor sÀkerhet inte skulle ha lagt till nÄgot bekvÀmlighet.
I allmÀnhet beslutade jag att det ÀndÄ var meningsfullt att slÀppa prototypen, plocka isÀr den och bygga en ny version frÄn grunden baserad pÄ nÄgot nyare och nu frÄn master.
Fel nummer tvÄ: TLP-metodologi
I grund och botten Àr det egentligen inte ett misstag, mer en egenskap hos projektets skapande under förhÄllanden av fullstÀndigt okunnande om "vart och hur vi ska gÄ?" och överhuvudtaget "kommer vi att klara det?". Under dessa omstÀndigheter slarvigt programmerande var en rimlig vÀg, men jag ville naturligtvis inte göra det igen utan behov. Denna gÄng ville jag göra det ordentligt: atomÀra commits, medvetna kodÀndringar (och inte "stringa slumpmÀssiga tecken tills det kompilerar (med varningar)", som Linus Torvalds en gÄng sa om nÄgon, om man fÄr tro Viki-citat) osv.
Fel nummer tre: att kliva i vattnet utan att kÀnna till strömmen
FrÄn detta har jag fortfarande inte helt befriat mig, men nu har jag bestÀmt mig för att inte gÄ den allra lÀttaste vÀgen, utan göra det "vuxet", nÀmligen skriva min egen TCG-backend frÄn grunden, sÄ att jag inte i efterhand ska behöva sÀga "Ja, det gÄr lÄngsamt, men jag kan ju inte kontrollera allt - TCI Àr sÄ skrivet...". Dessutom verkade detta frÄn början som en sjÀlvklar lösning, eftersom jag faktiskt genererar binÀrkod. Som man sÀger, "Samla Gent, men inte den rÀtta": koden Àr naturligtvis binÀr, men Ät gÄngen styrningen inte bara kan överföras - den mÄste uttryckligen skickas in i webblÀsaren för kompilering, vilket resulterar i ett objekt frÄn JS-vÀrlden som fortfarande mÄste sparas nÄgonstans. à andra sidan, pÄ normala RISC-arkitekturer, sÄvitt jag förstÄr, Àr det typiskt nödvÀndigt att uttryckligen rensa instruktionens cache för den regenererade koden - om det inte Àr exakt vad vi behöver, Àr det Ätminstone nÀra. Dessutom lÀrde jag mig av mitt förra försök att styrningen pÄ mitten av översÀttningsblocket pÄ nÄgot sÀtt inte överförs, dÀrför behövs inte bytekoden som kan tolkas frÄn nÄgot förskjutning, vi kan enkelt generera enligt funktionen pÄ TB.förVi kom och sparkade
Ăven om jag började skriva om koden redan i juli, sĂ„ kom den magiska knuffen plötsligt: vanligtvis kommer meddelanden frĂ„n GitHub som aviseringar om svar pĂ„ frĂ„gor och Pull requests, men hĂ€r,
omnĂ€mnandet i trĂ„den plötsligt Binaryen som en QEMU-backend Binaryen pĂ„ nĂ„got sĂ€tt justeras ĐșаĐș-ŃĐŸ ĐżĐŸĐżŃаĐČĐžŃŃ (Jag vet inte: kanske byta, kanske dubbellicensiering, kanske nĂ„got annatâŠ). Det gladde mig verkligen, eftersom jag redan vid flera tillfĂ€llen hade tittat pĂ„ WebAssembly, och jag kĂ€nde mig lite ledsen och förvirrad över det. HĂ€r fanns dock ett bibliotek som bĂ„de kunde hantera grundlĂ€ggande block och övergĂ„ngsgrafen, utföra bytekod och till och med köra det i en tolk om det skulle behövas.
Sedan kom det Ànnu ett till mailinglistan QEMU, men det Àr mer en frÄga om, 'Vem behöver det egentligen?'. Och det visade sig plötsligt, att det faktiskt var behövligt. à tminstone kan man samla pÄ sig sÄdana anvÀndningsmöjligheter, ifall det fungerar hyfsat snabbt:
- att köra nÄgot utbildande utan installation
- virtualisering pĂ„ iOS, dĂ€r det enligt rykten Ă€r den enda applikationen som har rĂ€tt att generera kod i farten â det Ă€r JS-motorn (Ă€r det verkligen sĂ„?)
- demonstration av en mini-OS â en diskettsystem, inbyggda system, olika firmware, osvâŠ
SÀrskildheter hos webblÀsarmiljön
Som jag redan nĂ€mnt Ă€r QEMU kopplat till flerkörning, och i webblĂ€saren finns inte det. NĂ„vĂ€l, det fanns egentligen inte... Först fanns det inte alls, sedan kom WebWorkers â sĂ„ vitt jag förstĂ„r, Ă€r detta flerkörning baserat pĂ„ meddelandeöverföring utan gemensamt Ă€ndrade variabler. Naturligtvis skapar det betydande problem vid portering av befintlig kod som baseras pĂ„ en delad minnesmodell. Sedan, under press frĂ„n allmĂ€nheten, implementerades det ocksĂ„ under namnet SharedArrayBuffers. Det infördes gradvis, firades nĂ€r det lanserades i olika webblĂ€sare, firades nyĂ„r och sedan kom Meltdown... Efter det kom man fram till att oavsett hur man mĂ€tte tiden, med hjĂ€lp av delat minne och en trĂ„d som inkrementerade en rĂ€knare, blev det Ă€ndĂ„ . SĂ„ flerkörning med delat minne stĂ€ngdes av. Det verkade som om det senare aktiverades igen, men som det blev klart frĂ„n det första experimentet, finns det liv Ă€ven utan det, och om sĂ„ Ă€r fallet, sĂ„ ska vi försöka göra det, utan att förlita oss pĂ„ flerkörning.
En andra funktion Àr att det inte gÄr att manipulera stacken pÄ lÄg nivÄ: man kan inte bara spara det aktuella sammanhanget och byta till ett nytt med en ny stack. Anropsstacken hanteras av JS virtuella maskinen. Vad Àr problemet, egentligen, nÀr vi ju har beslutat att hantera före detta trÄdar helt manuellt? Problemet Àr att blockin- och utmatning i QEMU implementeras genom korutiner, och hÀr skulle vi verkligen behöva lÄgnivÄmanipulationer av stacken. Lyckligtvis innehÄller Emscripten redan ett mekanism för asynkrona operationer, faktiskt tvÄ: och . Den första fungerar genom att avsevÀrt öka storleken pÄ 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. Den fungerar, förstÄs, lÄngsamt, men den ökar inte storleken pÄ koden. Förresten, stödet för korutiner för denna mekanism fick vi bidra till sjÀlva (det fanns redan korutiner skrivna för Asyncify och en implementation av i stort sett samma API för Emterpreter, vi behövde bara koppla dem).
Jag har för nĂ€rvarande Ă€nnu inte delat koden i WASM-kompilerad och tolkad med Emterpreter, sĂ„ blockenheter fungerar Ă€nnu inte (hĂ„ll ögonen öppna för nĂ€sta avsnitt, som man sĂ€gerâŠ). SĂ„ i slutĂ€ndan ska det resultera i nĂ„got sĂ„ hĂ€r roligt lager-pĂ„-lager:
- tolkad blockin- och utmatning. VadĂ„, förvĂ€ntade du dig verkligen emulerad NVMe med inhemsk prestanda? đ
- statisk kompilerad huvudkod för QEMU (översÀttare, andra emulerade enheter osv.)
- dynamiskt kompilerad WASM-gÀstande kod
Funktioner i QEMU:s kÀllkod
Som du kanske redan har gissat, Àr koden för emulering av gÀstande arkitekturer och koden för generering av vÀrdmaskininstruktioner i QEMU delad. I sjÀlva verket Àr det faktiskt lite mer komplicerat Àn sÄ:
- 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 det stöd för standarden för hÄrdvaruvirtualisering HAXM pÄ Windows ()
- om TCG anvÀnds istÀllet för hÄrdvaruvirtualisering, sÄ har det sÀrskilt stöd för kodgenerering för varje vÀrdarkitektur samt för en universell tolk.
- ⊠och runt allt detta â emulerad kringutrustning, anvĂ€ndargrĂ€nssnitt, migrering, record-replay, etc.
Förresten, visste du att: QEMU kan emulera inte bara hela datorer utan Ă€ven processorer för enskilda anvĂ€ndarprocesser i vĂ€rd-kĂ€rnan, vilket anvĂ€nds av exempelvis fuzzern AFL för att instrumentera binĂ€rer. Kanske nĂ„gon vill portera detta QEMU-arbetslĂ€ge till JS? đ
Precis som de flesta vĂ€l etablerade fria programvaror, kompileras QEMU genom ett anrop. konfigurera och makeAnta att du har beslutat att lĂ€gga till nĂ„got: TCG-backend, implementering av trĂ„dar, nĂ„got annat. Skynda dig inte att glĂ€djas / förskrĂ€ckas (markera som lĂ€mpligt) över perspektivet av att samarbeta med Autoconf â i sjĂ€lva verket, konfigurera har QEMU, till synes, sin egen kod och genereras inte frĂ„n nĂ„got.
WebAssembly
Vad Àr dÄ WebAssembly (Àven kallat WASM)? Det Àr en ersÀttning för Asm.js, som nu inte lÀngre utger sig för att vara giltig JavaScript-kod. TvÀrtom, det Àr strikt binÀrt och optimerat, och Àven att skriva ett heltal till det Àr inte sÀrskilt enkelt: det lagras för kompaktitet i formatet .
Kanske har du hört talas om relooping-algoritmen för Asm.js â det handlar om att Ă„terstĂ€lla "högnivĂ„" kontrollinstruktioner för flödet av exekvering (det vill sĂ€ga if-then-else, loopar, etc.), vilket JavaScript-motorer Ă€r anpassade för, frĂ„n lĂ„gnivĂ„ LLVM IR, som ligger nĂ€rmare den maskinkod som utförs av processorn. Naturligtvis Ă€r det intermediala representationsformatet för QEMU nĂ€rmare det andra. Det skulle verka, hĂ€r Ă€r bytekoden, slutet pĂ„ plĂ„gan⊠Och hĂ€r dyker block, if-then-else och loopar upp!
Och detta Àr Ànnu en anledning till varför Binaryen Àr anvÀndbart: det kan naturligtvis acceptera högnivÄblock som liknar det som kommer att sparas i WASM. Men det kan ocksÄ generera kod frÄn grafen av grundlÀggande block och övergÄngar mellan dem. Jag har redan nÀmnt att det döljer formatet för lagring av WebAssembly bakom ett bekvÀmt C/C++ API.
TCG (Tiny Code Generator)
TCG backend för C-kompilatorn. Senare klarade den uppenbarligen inte konkurrensen med GCC, men hittade slutligen 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 omedelbart exekverar, men jag valde att inte anvĂ€nda den den hĂ€r gĂ„ngen. Ă
andra sidan var det en fördel för mig att QEMU redan har möjlighet att aktivera övergÄngen till den genererade TB via funktionen tcg_qemu_tb_exec, vilket visade sig vara mycket anvÀndbart.
För att lÀgga till en ny TCG-backend i QEMU mÄste du skapa en underkatalog tcg/ i det hÀr fallet, tcg/binaryen), och i den mÄste det finnas tvÄ filer: tcg-target.h och tcg-target.inc.c och allt detta i konfigurera. Det gÄr ocksÄ att lÀgga andra filer dÀr, men som man kan lista ut av namnen pÄ dessa tvÄ kommer de bÄda att inkluderas nÄgonstans: en som en vanlig headerfil (den inkluderas i tcg/tcg.h, och den andra i andra filer i katalogerna tcg, accel och Àven andra), den andra endast som kodsnutt i tcg/tcg.c, men den har tillgÄng till dess statiska funktioner.
Efter att ha beslutat att jag skulle lÀgga 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-implementation och angav Àrligt detta i licenshuvudet.
Fil tcg-target.h innehÄller huvudsakligen instÀllningar i form av #define-er:
- hur mĂ„nga register och vilken bredd som finns pĂ„ den mĂ„larharkitekturen (i vĂ„rt fall â sĂ„ mĂ„nga vi önskar â frĂ„gan handlar mer om vad som kommer att genereras i effektivare kod av webblĂ€saren pĂ„ den "helt mĂ„lade" arkitekturen...)
- justering av vÀrdinstruktioner: pÄ x86 och Àven i TCI Àr instruktionerna överhuvudtaget inte justerade, jag planerar att lÀgga i kodbufferten inte instruktionerna direkt, utan pekare pÄ strukturerna i Binaryen-biblioteket, sÄ jag sÀger: 4 byte
- vilka valfria instruktioner backendet kan generera â vi inkluderar allt vi kan hitta i Binaryen, resten fĂ„r acceleratorn dela upp i enklare sjĂ€lv
- vilken ungefÀrlig storlek TLB-cachen begÀr backend. Tanken Àr att i QEMU Àr allting pÄ allvar: Àven om det finns hjÀlpfunktioner som utför load/store med hÀnsyn till gÀstens MMU (och vem kan klara sig utan det idag?), sÄ sparar de sin översÀttningscache i form av en struktur vars bearbetning Àr lÀtt att integrera direkt i översÀttningsblocken. FrÄgan Àr alltsÄ vilket offset i denna struktur som effektivt kan hanteras av en liten och snabb sekvens av instruktioner.
- hÀr kan man justera syftet med ett par reserverade register, aktivera anrop till TB genom en funktion och valfritt beskriva ett par mindre.
inline-funktioner somflush_icache_range(men det Àr inte vÄr situation)
Fil tcg-target.inc.c, naturligtvis Àr det vanligtvis mycket större i storlek och innehÄller flera obligatoriska funktioner:
- initialisering, som bland annat anger begrÀnsningar för vilka instruktioner med vilka operander som fÄr utföras. Nefariously kopierat av mig frÄn en annan backend.
- en funktion som tar en intern bytekod-instruktion
- hÀr kan man ocksÄ placera hjÀlpfunktioner, och man kan Àven anvÀnda statiska funktioner frÄn
tcg/tcg.c
För mig valde jag följande strategi: i de första orden i nĂ€sta översĂ€ttningsblock registrerade jag fyra pekare: startetikett (ett visst vĂ€rde i nĂ€rheten av 0xFFFFFFFF, vid vilken den aktuella statusen för TB bestĂ€mdes), kontext, genererad modul och magic number för avbuggning. Först stĂ€lldes etiketten in pĂ„ 0xFFFFFFFF - n, dĂ€r n â ett litet positivt tal, och vid varje körning via tolken ökades det med 1. NĂ€r det nĂ„dde 0xFFFFFFFE, intrĂ€ffade kompileringen, modulen sparades i en funktions-tabell, importerad i en liten "starter" dĂ€r utförandet gick ivĂ€g ifrĂ„n tcg_qemu_tb_exec, och modulen togs bort frĂ„n QEMU:s minne.
Omformulerande av klassikern, "Krycka, hur mycket av detta ljud har flĂ€tats samman till programmerarens hjĂ€rta...". ĂndĂ„ lĂ€ckte minnet bort. Det var minne som hanterades av QEMU! Jag hade en kod som vid skrivning av nĂ€sta instruktion (det vill sĂ€ga, pekaren) tog bort den som refererades pĂ„ denna plats tidigare, men det hjĂ€lpte inte. Egentligen allokera QEMU vid start minne och skriver genererad kod dit. NĂ€r bufferten tar slut tas koden bort och nĂ€sta börjar skrivas pĂ„ dess plats.
Efter att ha granskat koden insÄg jag att fixa med magic number gjorde att vi slipper problemet med en krasch av heapen, nÀr vi frigör nÄgot felaktigt frÄn en icke-initialiserad buffer vid första genomgÄngen. Men vem skriver tillbaka bufferten utanför min funktion senare? Som Emscripten-utvecklarna rekommenderar, nÀr jag stötte pÄ problemet, portade jag den resulterande koden tillbaka till en inhemsk applikation och körde den med Mozilla Record-Replay... Sammanfattningsvis insÄg jag en enkel sak: för varje block allokeras 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 fixar (Ätminstone vissa), och kastade helt enkelt bort magic number, och flyttade de ÄterstÄende orden till struct TranslationBlock, genom att skapa en enkel lÀnkad lista som kan genomgÄs snabbt vid ÄterstÀllning av översÀttningscachen, och frigöra minnet.
Vissa fixar kvarstod: till exempel Ă€r markerade pekare i kodbufferten â en del av dem Ă€r helt enkelt BinaryenExpressionRef, vilket innebĂ€r att de pekar pĂ„ uttryck som behöver placeras linjĂ€rt i den genererade basblocken, en del â villkor för övergĂ„ng mellan BB, en del â vart man ska gĂ„. Och det finns redan förberedda block för Relooper som mĂ„ste kopplas ihop enligt villkoren. För att sĂ€rskilja dem anvĂ€nds antagandet att alla Ă€r justerade Ă„tminstone pĂ„ fyra byte, sĂ„ vi kan tryggt anvĂ€nda de lĂ€gre tvĂ„ bitarna för etiketten, vi mĂ„ste bara komma ihĂ„g att ta bort den vid behov. För övrigt anvĂ€nds sĂ„dana etiketter redan i QEMU för att ange orsaken till att man lĂ€mnar TCG-loopen.
AnvÀndning av Binaryen
Moduler i WebAssembly innehÄller funktioner, dÀr varje innehÄller ett 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 osv. Som jag redan nÀmnt organiseras kontrollflödet hÀr verkligen i form av hög-nivÄ förgreningar, loopar, funktionsanrop osv. Argument till funktioner överförs inte pÄ stacken, utan tydligt, precis som i JS. Det finns ocksÄ globala variabler, men jag anvÀnde dem inte, sÄ jag kommer inte att berÀtta om dem.
Funktioner har ocksĂ„ numrerade lokala variabler som börjar pĂ„ noll och har typerna: int32 / int64 / float / double. Observera att de första n lokala variablerna Ă€r argument som skickas till funktionen. Ăven om detta inte Ă€r helt lĂ„gnivĂ„ i termer av kontrollflöde, bĂ€r heltalen fortfarande ingen indikation av "tecken/utan tecken"; hur ett tal beter sig beror pĂ„ operationens kod.
Generellt sett erbjuder Binaryen : du skapar en modul, i vilken du skapar uttryck â unĂ€ra, binĂ€ra, block av andra uttryck, kontrollflöde osv. Sedan skapar du en funktion, dĂ€r kroppen mĂ„ste ange ett uttryck. Om du, precis som jag, har en lĂ„gnivĂ„ graf över övergĂ„ngar â kommer komponenten relooper att hjĂ€lpa dig. SĂ„vitt jag förstĂ„r kan du anvĂ€nda högnivĂ„ kontrollflöde i en block sĂ„ lĂ€nge det inte gĂ„r utanför blocket â det vill sĂ€ga att göra intern förgrening fast path / slow path i den inbyggda TLB-cachehanteringen gĂ„r bra, men att blanda sig i det "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 uttryck, funktioner osv. som tilldelats dess arena..
Men om du vill tolka koden i realtid utan onödig skapelse och borttagning av instanser av tolken, kan det vara meningsfullt att flytta denna logik till en C++-fil och styra hela C++ API:et för biblioteket direkt, förbi de fÀrdiga omslagen.
SÄ för att generera kod behöver du
// ĐœĐ°ŃŃŃĐŸĐžŃŃ ĐłĐ»ĐŸĐ±Đ°Đ»ŃĐœŃĐ” паŃĐ°ĐŒĐ”ŃŃŃ (ĐŒĐŸĐ¶ĐœĐŸ ĐżĐŸĐŒĐ”ĐœŃŃŃ ĐżĐŸŃĐŸĐŒ)
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 glömde nĂ„got â ber om ursĂ€kt, detta Ă€r bara för att ge en kĂ€nsla för 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 nu har du redan en instans!
}, buf, sz);För att pÄ nÄgot sÀtt koppla samman QEMU-vÀrlden och JS, och snabbt komma Ät kompilerade funktioner, skapades en array (funktions-tabell för import i startprogrammet), dÀr de genererade funktionerna placerades. För att snabbt berÀkna indexet anvÀndes ursprungligen indexet för det första ordet i översÀttningsblocket, men sedan blev indexet, berÀknat med en sÄdan formel, helt enkelt inmatat i fÀltet. struct TranslationBlock.
Förresten, (Ă€ven om det har en grumlig licens) fungerar bara normalt i Firefox. Utvecklarna av Chrome var inte riktigt redo för att nĂ„gon skulle vilja skapa mer Ă€n tusen instanser av WebAssembly-moduler, sĂ„ de allokerade helt enkelt ett gigabyte virtuellt adressutrymme för varjeâŠ
Detta Àr allt för nu. Kanske kommer det att bli en till artikel om nÄgon Àr intresserad. à terstÄr helt klart minst enbart att fÄ blocken att fungera. Det kan ocksÄ vara vÀrt att göra kompileringen av WebAssembly-moduler asynkron, som vanligt i JS-vÀrlden, eftersom det redan finns en tolk som kan utföra allt medan den inhemska modulen inte Àr klar.
Som avslutning en gÄta: Du har samlat en binÀrfil pÄ en 32-bitars arkitektur, men koden med minnesoperationer gÄr ut frÄn Binaryen, nÄgonstans pÄ stacken eller nÄgon annanstans i de övre 2 GB av det 32-bitars adressutrymmet. Problemet Àr att det i Binaryens ögon Àr en ÄtgÀrd mot en för stor resulterande adress. Hur kan man kringgÄ detta?
Ur adminsperspektiv
Jag testade detta inte till slut, men den första tanken var "Vad hÀnder om jag installerar 32-bitars Linux?" DÄ blir den övre delen av adressutrymmet upptagen av kÀrnan. FrÄgan Àr bara hur mycket som kommer att vara upptaget: 1 eller 2 GB.
Ur programmerarperspektiv (praktisk variant)
Vi blĂ„ser upp en bubbla i den övre delen av adressutrymmet. Jag förstĂ„r inte sjĂ€lv varför det fungerar â det borde ju finnas en stack dĂ€r. Men "vi praktiker: allt fungerar, men ingen vet varförâŠ". redan ⊠kompatibelt med Valgrind, men lyckligtvis driver Valgrind mycket effektivt bort alla 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 ger nĂ„gon en bĂ€ttre förklaring till hur den hĂ€r koden fungerarâŠ
Qemu.js med JIT-stöd: köttfÀrs kan fortfarande vÀndas tillbaka
KĂ€lla: habr.com
