QEMU.js: nou ernstig en met WASM

Eens op 'n tyd het ek besluit vir pret bewys die omkeerbaarheid van die proses en leer hoe om JavaScript (meer presies, Asm.js) vanaf masjienkode te genereer. QEMU is vir die eksperiment gekies, en 'n ruk later is 'n artikel oor Habr. In die kommentaar is ek aangeraai om die projek in WebAssembly te herskep, en selfs self op te hou amper klaar Ek wou op een of ander manier nie die projek hê nie ... Die werk was aan die gang, maar baie stadig, en nou, onlangs in daardie artikel verskyn kommentaar oor die onderwerp "So hoe het dit alles geëindig?" In reaksie op my gedetailleerde antwoord het ek gehoor "Dit klink soos 'n artikel." Wel, as jy kan, sal daar 'n artikel wees. Miskien sal iemand dit nuttig vind. Daaruit sal die leser 'n paar feite leer oor die ontwerp van QEMU-kodegenerering-agtergronde, asook hoe om 'n Just-in-Time-samesteller vir 'n webtoepassing te skryf.

take

Aangesien ek reeds geleer het hoe om QEMU na JavaScript te "porteer", is hierdie keer besluit om dit wys te doen en nie ou foute te herhaal nie.

Fout nommer een: vertak vanaf puntvrystelling

My eerste fout was om my weergawe van die stroomop weergawe 2.4.1 af te vurk. Toe het dit vir my 'n goeie idee gelyk: as puntvrystelling bestaan, dan is dit waarskynlik meer stabiel as eenvoudige 2.4, en selfs meer so die tak master. En aangesien ek beplan het om 'n redelike hoeveelheid van my eie goggas by te voeg, het ek glad nie iemand anders nodig gehad nie. Dis seker hoe dit uitgedraai het. Maar hier is die ding: QEMU staan ​​nie stil nie, en op 'n stadium het hulle selfs die optimalisering van die gegenereerde kode met 10 persent aangekondig."Ja, nou gaan ek vries," het ek gedink en gebreek. Hier moet ons 'n afwyking maak: as gevolg van die enkel-draad-aard van QEMU.js en die feit dat die oorspronklike QEMU nie die afwesigheid van multi-threading impliseer nie (dit wil sê die vermoë om gelyktydig verskeie onverwante kodepaaie te bedryf, en nie net “gebruik alle pitte”) is krities daarvoor, die hooffunksies van drade wat ek moes “uitdraai” om van buite af te kon bel. Dit het 'n paar natuurlike probleme tydens die samesmelting geskep. Maar die feit dat sommige van die veranderinge van die tak master, waarmee ek probeer het om my kode saam te voeg, is ook cherry picked in die puntvrystelling (en dus in my tak) sou ook waarskynlik nie gerief bygevoeg het nie.

Oor die algemeen het ek besluit dat dit steeds sin maak om die prototipe uit te gooi, dit uitmekaar te haal vir onderdele en 'n nuwe weergawe van nuuts af te bou gebaseer op iets varser en nou van master.

Fout nommer twee: TLP-metodologie

In wese is dit nie 'n fout nie; in die algemeen is dit net 'n kenmerk van die skep van 'n projek in toestande van volledige misverstand van beide "waarheen en hoe om te beweeg?" en in die algemeen "sal ons daar kom?" In hierdie toestande lomp programmering was 'n geregverdigde opsie, maar ek wou dit natuurlik nie onnodig herhaal nie. Hierdie keer wou ek dit wys doen: atomiese commits, bewuste kodeveranderings (en nie “toevallige karakters saamvoeg totdat dit saamstel (met waarskuwings)”, soos Linus Torvalds eenkeer oor iemand gesê het, volgens Wikiquote), ens.

Fout nommer drie: om in die water te klim sonder om die drif te ken

Ek het nog nie heeltemal hiervan ontslae geraak nie, maar nou het ek besluit om glad nie die pad van die minste weerstand te volg nie, en om dit "as 'n volwassene" te doen, naamlik om my TCG-backend van nuuts af te skryf, om nie om later te moet sê: "Ja, dit is natuurlik stadig, maar ek kan nie alles beheer nie - dis hoe TCI geskryf word ..." Boonop het dit aanvanklik na 'n ooglopende oplossing gelyk, aangesien Ek genereer binêre kode. Soos hulle sê, “Gent het bymekaargekomу, maar nie daardie een nie”: die kode is natuurlik binêr, maar beheer kan nie bloot daarna oorgedra word nie - dit moet eksplisiet in die blaaier ingedruk word vir samestelling, wat lei tot 'n sekere objek uit die JS-wêreld, wat nog steeds moet iewers gered word. Op normale RISC-argitekture, sover ek verstaan, is 'n tipiese situasie egter die behoefte om die instruksiekas vir hergenereerde kode uitdruklik terug te stel - as dit nie is wat ons nodig het nie, dan is dit in elk geval naby. Daarbenewens, uit my laaste poging, het ek geleer dat beheer blykbaar nie na die middel van die vertaalblok oorgedra word nie, dus het ons nie regtig greepkode nodig wat vanaf enige offset geïnterpreteer word nie, en ons kan dit eenvoudig vanaf die funksie op TB genereer .

Hulle het gekom en geskop

Alhoewel ek die kode in Julie begin herskryf het, het 'n magiese skop ongemerk opgesluip: gewoonlik kom briewe van GitHub as kennisgewings oor antwoorde op Kwessies en Trek-versoeke, maar hier, skielik noem in draad Binaryen as 'n qemu backend in die konteks, "Hy het so iets gedoen, miskien sal hy iets sê." Ons het gepraat oor die gebruik van Emscripten se verwante biblioteek Binaryen WASM JIT te skep. Wel, ek het gesê dat jy 'n Apache 2.0-lisensie daar het, en QEMU as 'n geheel word onder GPLv2 versprei, en hulle is nie baie versoenbaar nie. Skielik het dit geblyk dat 'n lisensie kan wees maak dit op een of ander manier reg (Ek weet nie: dalk verander dit, dalk dubbele lisensiëring, dalk iets anders...). Dit het my natuurlik gelukkig gemaak, want ek het toe al mooi na gekyk binêre formaat WebAssembly, en ek was op een of ander manier hartseer en onverstaanbaar. Daar was ook 'n biblioteek wat die basiese blokke met die oorgangsgrafiek sou verslind, die greepkode sou produseer en dit selfs in die tolk self sou laat loop, indien nodig.

Toe was daar meer 'n brief op die QEMU-poslys, maar dit gaan meer oor die vraag: "Wie het dit in elk geval nodig?" En dit is skielik, dit het geblyk dit was nodig. Jy kan ten minste die volgende gebruiksmoontlikhede bymekaarskraap, as dit min of meer vinnig werk:

  • iets opvoedkundig bekendstel sonder enige installasie
  • virtualisasie op iOS, waar, volgens gerugte, die enigste toepassing wat die reg het om kodegenerering in die lug 'n JS-enjin is (is dit waar?)
  • demonstrasie van mini-OS - enkel-floppy, ingeboude, alle soorte firmware, ens ...

Blaaier Runtime Kenmerke

Soos ek reeds gesê het, is QEMU gekoppel aan multithreading, maar die blaaier het dit nie. Wel, dit wil sê, nee... Aanvanklik het dit glad nie bestaan ​​nie, toe verskyn WebWorkers - sover ek verstaan, is dit multithreading gebaseer op boodskapversending sonder gedeelde veranderlikes. Dit skep natuurlik aansienlike probleme wanneer bestaande kode op grond van die gedeelde geheue-model oorgedra word. Toe, onder openbare druk, is dit ook onder die naam geïmplementeer SharedArrayBuffers. Dit is geleidelik bekendgestel, hulle het die bekendstelling daarvan in verskillende blaaiers gevier, dan het hulle die Nuwejaar gevier, en toe Meltdown... Waarna hulle tot die gevolgtrekking gekom het dat die tydmeting grof of grof maak, maar met behulp van gedeelde geheue en 'n draad wat die toonbank verhoog, dit is alles dieselfde dit sal redelik akkuraat uitwerk. Dus het ons multithreading met gedeelde geheue gedeaktiveer. Dit blyk dat hulle dit later weer aangeskakel het, maar soos dit duidelik geword het uit die eerste eksperiment, is daar lewe daarsonder, en indien wel, sal ons probeer om dit te doen sonder om op multithreading staat te maak.

Die tweede kenmerk is die onmoontlikheid van lae-vlak manipulasies met die stapel: jy kan nie net die huidige konteks neem, stoor en oorskakel na 'n nuwe een met 'n nuwe stapel nie. Die oproepstapel word deur die JS virtuele masjien bestuur. Dit wil voorkom, wat is die probleem, aangesien ons steeds besluit het om eersgenoemde vloei heeltemal handmatig te bestuur? Die feit is dat blok I/O in QEMU geïmplementeer word deur middel van koroutines, en dit is waar lae-vlak stapel manipulasies handig te pas sal kom. Gelukkig bevat Emscipten reeds 'n meganisme vir asinchroniese bewerkings, selfs twee: Asinifiseer и Emperpreter. Die eerste een werk deur 'n aansienlike opblaas in die gegenereerde JavaScript-kode en word nie meer ondersteun nie. Die tweede is die huidige "korrekte manier" en werk deur bytecode generering vir die inheemse tolk. Dit werk natuurlik stadig, maar dit blaas nie die kode op nie. True, ondersteuning vir koroutines vir hierdie meganisme moes onafhanklik bygedra word (daar was reeds koroutines geskryf vir Asyncify en daar was 'n implementering van ongeveer dieselfde API vir Emterpreter, jy moes dit net verbind).

Op die oomblik het ek nog nie daarin geslaag om die kode te verdeel in een wat in WASM saamgestel is en met Emterpreter geïnterpreteer is nie, so bloktoestelle werk nog nie (sien in die volgende reeks, soos hulle sê...). Dit wil sê, op die ou end behoort jy iets soos hierdie snaakse gelaagde ding te kry:

  • geïnterpreteer blok I/O. Wel, het jy regtig nagevolgde NVMe met inheemse prestasie verwag? 🙂
  • staties saamgestelde hoof-QEMU-kode (vertaler, ander nagebootste toestelle, ens.)
  • dinamies saamgestelde gaskode in WASM

Kenmerke van QEMU-bronne

Soos u waarskynlik reeds geraai het, is die kode vir die nabootsing van gasargitekture en die kode vir die generering van gasheermasjien-instruksies in QEMU geskei. Trouens, dit is selfs 'n bietjie moeiliker:

  • daar is gaste-argitekture
  • daar is versnellers, naamlik KVM vir hardeware-virtualisering op Linux (vir gas- en gasheerstelsels wat met mekaar versoenbaar is), TCG vir JIT-kodegenerering op enige plek. Begin met QEMU 2.9, het ondersteuning vir die HAXM hardeware virtualiseringstandaard op Windows verskyn (die besonderhede)
  • as TCG gebruik word en nie hardeware-virtualisering nie, dan het dit aparte kodegenerering-ondersteuning vir elke gasheerargitektuur, sowel as vir die universele tolk
  • ... en rondom dit alles - nagebootsde randapparatuur, gebruikerskoppelvlak, migrasie, rekord-herhaling, ens.

Terloops, het jy geweet: QEMU kan nie net die hele rekenaar naboots nie, maar ook die verwerker vir 'n aparte gebruikersproses in die gasheerkern, wat byvoorbeeld deur die AFL-fuzzer vir binêre instrumentasie gebruik word. Miskien wil iemand hierdie werkswyse van QEMU na JS oordra? 😉

Soos die meeste jarelange gratis sagteware, word QEMU deur die oproep gebou configure и make. Kom ons sê jy besluit om iets by te voeg: 'n TCG-agterkant, draadimplementering, iets anders. Moenie haastig wees om gelukkig/afgrys te wees (onderstreep soos toepaslik) oor die vooruitsig om met Autoconf te kommunikeer nie - trouens, configure QEMU's is blykbaar self geskryf en word uit niks gegenereer nie.

WebAssembly

So, wat is hierdie ding genaamd WebAssembly (ook bekend as WASM)? Dit is 'n plaasvervanger vir Asm.js, wat nie meer voorgee dat dit geldige JavaScript-kode is nie. Inteendeel, dit is suiwer binêr en geoptimaliseer, en selfs om 'n heelgetal daarin te skryf is nie baie eenvoudig nie: vir kompaktheid word dit in die formaat gestoor LEB128.

Jy het dalk gehoor van die herloop-algoritme vir Asm.js - dit is die herstel van "hoëvlak" vloeibeheerinstruksies (dit wil sê as-dan-anders, lusse, ens.), waarvoor JS-enjins ontwerp is, vanaf die laevlak LLVM IR, nader aan die masjienkode wat deur die verwerker uitgevoer word. Natuurlik is die intermediêre verteenwoordiging van QEMU nader aan die tweede. Dit wil voorkom asof hier is, bytecode, die einde van die pyniging... En dan is daar blokke, as-dan-anders en lusse!

En dit is nog 'n rede waarom Binaryen nuttig is: dit kan natuurlik hoëvlakblokke aanvaar naby aan wat in WASM gestoor sou word. Maar dit kan ook kode produseer uit 'n grafiek van basiese blokke en oorgange tussen hulle. Wel, ek het reeds gesê dat dit die WebAssembly-bergingformaat agter die gerieflike C/C++ API versteek.

TCG (Tiny Code Generator)

GCT oorspronklik was backend vir die C-samesteller Toe, blykbaar, kon dit nie die kompetisie met GCC weerstaan ​​nie, maar dit het uiteindelik sy plek in QEMU gevind as 'n kodegenereringsmeganisme vir die gasheerplatform. Daar is ook 'n TCG backend wat 'n paar abstrakte greepkode genereer, wat onmiddellik deur die tolk uitgevoer word, maar ek het besluit om dit hierdie keer te vermy. Die feit dat dit egter reeds in QEMU moontlik is om die oorgang na die gegenereerde TB deur die funksie moontlik te maak tcg_qemu_tb_exec, dit blyk baie nuttig vir my te wees.

Om 'n nuwe TCG backend by QEMU te voeg, moet jy 'n subgids skep tcg/<имя архитектуры> (in hierdie geval, tcg/binaryen), en dit bevat twee lêers: tcg-target.h и tcg-target.inc.c и voorskryf Dit gaan alles oor configure. Jy kan ander lêers daar plaas, maar, soos jy uit die name van hierdie twee kan raai, sal hulle albei iewers ingesluit word: een as 'n gewone koplêer (dit is ingesluit in tcg/tcg.h, en daardie een is reeds in ander lêers in die gidse tcg, accel en nie net nie), die ander - slegs as 'n kodebrokkie in tcg/tcg.c, maar dit het toegang tot sy statiese funksies.

Toe ek besluit het dat ek te veel tyd sou spandeer aan gedetailleerde ondersoeke van hoe dit werk, het ek eenvoudig die "geraamtes" van hierdie twee lêers van 'n ander backend-implementering gekopieer, en dit eerlik in die lisensie-opskrif aangedui.

lêer tcg-target.h bevat hoofsaaklik instellings in die vorm #define-s:

  • hoeveel registers en watter breedte is daar op die teikenargitektuur (ons het soveel as wat ons wil hê, soveel as wat ons wil hê - die vraag is meer oor wat in meer doeltreffende kode gegenereer sal word deur die blaaier op die "heeltemal teiken" argitektuur ...)
  • belyning van gasheerinstruksies: op x86, en selfs in TCI, is instruksies glad nie belyn nie, maar ek gaan glad nie instruksies in die kodebuffer plaas nie, maar wysers na Binaryen-biblioteekstrukture, so ek sal sê: 4 grepe
  • watter opsionele instruksies die backend kan genereer - ons sluit alles in wat ons in Binaryen vind, laat die versneller die res in eenvoudiger self opbreek
  • Wat is die benaderde grootte van die TLB-kas wat deur die agterkant aangevra word. Die feit is dat in QEMU alles ernstig is: alhoewel daar helperfunksies is wat laai/stoor uitvoer met inagneming van die gas MMU (waar sou ons nou daarsonder wees?), stoor hulle hul vertaalkas in die vorm van 'n struktuur, die verwerking wat gerieflik is om direk in uitsaaiblokke in te sluit. Die vraag is, watter kompensasie in hierdie struktuur word die doeltreffendste verwerk deur 'n klein en vinnige reeks opdragte?
  • hier kan jy die doel van een of twee gereserveerde registers aanpas, die oproep van TB deur 'n funksie moontlik maak en opsioneel 'n paar klein inline-funksies soos flush_icache_range (maar dit is nie ons geval nie)

lêer tcg-target.inc.c, natuurlik, is gewoonlik baie groter in grootte en bevat verskeie verpligte funksies:

  • inisialisering, insluitend beperkings op watter instruksies op watter operande kan werk. Blatant deur my gekopieer vanaf 'n ander agterkant
  • funksie wat een interne greepkode-instruksie neem
  • Jy kan ook hulpfunksies hier plaas, en jy kan ook statiese funksies van gebruik tcg/tcg.c

Vir myself het ek die volgende strategie gekies: in die eerste woorde van die volgende vertaalblok het ek vier wenke neergeskryf: 'n beginpunt ('n sekere waarde in die nabyheid 0xFFFFFFFF, wat die huidige toestand van die TB bepaal het), konteks, gegenereerde module en towernommer vir ontfouting. Aanvanklik is die merk ingesit 0xFFFFFFFF - nWaar n - 'n klein positiewe getal, en elke keer as dit deur die tolk uitgevoer is, het dit met 1 toegeneem. Toe dit bereik 0xFFFFFFFE, samestelling plaasgevind het, is die module gestoor in die funksietabel, ingevoer in 'n klein "lanseerder", waarin die uitvoering vanaf tcg_qemu_tb_exec, en die module is uit QEMU-geheue verwyder.

Om die klassieke te parafraseer, "Crutch, how much is intertwined in this sound for the proger's heart...". Die geheue het egter iewers uitgelek. Boonop was dit geheue wat deur QEMU bestuur word! Ek het 'n kode gehad wat, toe ek die volgende instruksie geskryf het (wel, dit is 'n wyser), die een uitgevee het wie se skakel vroeër op hierdie plek was, maar dit het nie gehelp nie. Eintlik, in die eenvoudigste geval, ken QEMU geheue toe by opstart en skryf die gegenereerde kode daar. Wanneer die buffer opraak, word die kode uitgegooi en die volgende een begin in sy plek geskryf word.

Nadat ek die kode bestudeer het, het ek besef dat die truuk met die towernommer my in staat gestel het om nie op hoopvernietiging te misluk deur iets verkeerd op 'n ongeinitialiseerde buffer op die eerste pas vry te maak nie. Maar wie herskryf die buffer om my funksie later te omseil? Soos die Emscripten-ontwikkelaars adviseer, toe ek 'n probleem ondervind het, het ek die gevolglike kode teruggeplaas na die oorspronklike toepassing, Mozilla Record-Replay daarop gestel ... Oor die algemeen het ek uiteindelik 'n eenvoudige ding besef: vir elke blok, a struct TranslationBlock met sy beskrywing. Raai waar... Dis reg, net voor die blok reg in die buffer. Toe ek dit besef, het ek besluit om op te hou om krukke te gebruik (ten minste sommige), en het eenvoudig die towernommer uitgegooi en die oorblywende woorde oorgeplaas na struct TranslationBlock, skep 'n enkelgekoppelde lys wat vinnig deurkruis kan word wanneer die vertaalkas teruggestel word, en maak geheue vry.

Sommige krukke bly oor: byvoorbeeld gemerkte wysers in die kodebuffer - sommige van hulle is eenvoudig BinaryenExpressionRef, dit wil sê, hulle kyk na die uitdrukkings wat lineêr in die gegenereerde basiese blok geplaas moet word, deel is die voorwaarde vir oorgang tussen BB's, deel is waarheen om te gaan. Wel, daar is reeds voorbereide blokke vir Relooper wat volgens die voorwaardes gekoppel moet word. Om hulle te onderskei, word die aanname gebruik dat hulle almal in lyn is met ten minste vier grepe, sodat jy veilig die minste betekenisvolle twee bisse vir die etiket kan gebruik, jy moet net onthou om dit te verwyder indien nodig. Terloops, sulke etikette word reeds in QEMU gebruik om die rede vir die uitgang van die TCG-lus aan te dui.

Gebruik Binaryen

Modules in WebAssembly bevat funksies, wat elkeen 'n liggaam bevat, wat 'n uitdrukking is. Uitdrukkings is unêre en binêre bewerkings, blokke wat bestaan ​​uit lyste van ander uitdrukkings, beheervloei, ens. Soos ek reeds gesê het, is beheervloei hier presies georganiseer as hoëvlaktakke, lusse, funksie-oproepe, ens. Argumente vir funksies word nie op die stapel deurgegee nie, maar eksplisiet, net soos in JS. Daar is ook globale veranderlikes, maar ek het dit nie gebruik nie, so ek sal jou nie daarvan vertel nie.

Funksies het ook plaaslike veranderlikes, genommer vanaf nul, van tipe: int32 / int64 / float / double. In hierdie geval is die eerste n plaaslike veranderlikes die argumente wat na die funksie oorgedra word. Neem asseblief kennis dat alhoewel alles hier nie heeltemal lae-vlak is in terme van beheervloei nie, heelgetalle steeds nie die "getekende/ongeteken"-kenmerk dra nie: hoe die getal optree hang af van die bewerkingskode.

Oor die algemeen bied Binaryen eenvoudige C-API: jy skep 'n module, in hom skep uitdrukkings - unêr, binêr, blokke van ander uitdrukkings, beheervloei, ens. Dan skep jy 'n funksie met 'n uitdrukking as sy liggaam. As jy, soos ek, 'n laevlak-oorgangsgrafiek het, sal die herlooper-komponent jou help. Sover ek verstaan, is dit moontlik om hoëvlakbeheer van die uitvoeringsvloei in 'n blok te gebruik, solank dit nie verder gaan as die grense van die blok nie - dit wil sê, dit is moontlik om interne vinnige pad / stadig te maak pad wat binne die ingeboude TLB-kasverwerkingskode vertak, maar nie om met die "eksterne" beheervloei in te meng nie. Wanneer jy 'n herlooper bevry, word sy blokke vrygestel, wanneer jy 'n module vrystel, verdwyn die uitdrukkings, funksies, ens. arena.

As jy egter kode vinnig wil interpreteer sonder onnodige skepping en uitvee van 'n tolkinstansie, kan dit sin maak om hierdie logika in 'n C++-lêer te plaas, en van daar af direk die hele C++ API van die biblioteek te bestuur, deur gereed- omhulsels gemaak.

So om die kode te genereer wat jy nodig het

// настроить глобальные параметры (можно поменять потом)
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);

... as ek iets vergeet het, jammer, dit is net om die skaal voor te stel, en die besonderhede is in die dokumentasie.

En nou begin die crack-fex-pex, iets soos hierdie:

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

Om op een of ander manier die wêrelde van QEMU en JS te verbind en terselfdertyd vinnig toegang tot die saamgestelde funksies te verkry, is 'n skikking geskep ('n tabel van funksies vir invoer in die lanseerder), en die gegenereerde funksies is daar geplaas. Om die indeks vinnig te bereken, is die indeks van die nulwoordvertalingsblok aanvanklik as dit gebruik, maar toe het die indeks wat met hierdie formule bereken is, eenvoudig in die veld begin pas in struct TranslationBlock.

By the way, demo (tans met 'n troebel lisensie) werk net goed in Firefox. Chrome-ontwikkelaars was een of ander manier nie gereed nie aan die feit dat iemand meer as 'n duisend gevalle van WebAssembly-modules sou wou skep, sodat hulle eenvoudig 'n gigagreep virtuele adresspasie vir elke...

Dit is al vir nou. Miskien sal daar nog 'n artikel wees as iemand belangstel. Daar bly naamlik ten minste oor enigste laat blok toestelle werk. Dit kan ook sin maak om die samestelling van WebAssemble-modules asinchroon te maak, soos gebruiklik in die JS-wêreld, aangesien daar nog 'n tolk is wat dit alles kan doen totdat die inheemse module gereed is.

Uiteindelik 'n raaisel: jy het 'n binêre op 'n 32-bis argitektuur saamgestel, maar die kode, deur geheue bewerkings, klim vanaf Binaryen, iewers op die stapel, of iewers anders in die boonste 2 GB van die 32-bis adresruimte. Die probleem is dat dit vanuit Binaryen se oogpunt toegang tot 'n te groot resulterende adres verkry. Hoe om dit te omseil?

Op admin se manier

Ek het dit nie uiteindelik getoets nie, maar my eerste gedagte was "Wat as ek 32-bis Linux geïnstalleer het?" Dan sal die boonste deel van die adresspasie deur die kern beset word. Die enigste vraag is hoeveel beset gaan word: 1 of 2 Gb.

Op 'n programmeerder se manier (opsie vir praktisyns)

Kom ons blaas 'n borrel aan die bokant van die adresspasie. Ek verstaan ​​self nie hoekom dit werk nie - daar reeds daar moet 'n stapel wees. Maar “ons is praktisyns: alles werk vir ons, maar niemand weet hoekom nie...”

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

... dit is waar dat dit nie met Valgrind versoenbaar is nie, maar gelukkig stoot Valgrind self almal baie effektief daar uit :)

Miskien sal iemand 'n beter verduideliking gee van hoe hierdie kode van my werk...

Bron: will.com

Voeg 'n opmerking