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 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 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 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Ă„ 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 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 . 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Ä: och . 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 ()
- 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 .
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 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 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 somflush_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 : 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, (Ă€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
