QEMU.js: no serieus en mei WASM

Eartiids besleat ik foar de wille bewize de reversibiliteit fan it proses en learje hoe't jo JavaSkript generearje (krekter as Asm.js) fan masinekoade. QEMU waard keazen foar it eksperimint, en in skoft letter waard in artikel skreaun op Habr. Yn 'e opmerkingen waard ik advisearre om it projekt yn WebAssembly opnij te meitsjen, en sels mysels te stopjen hast klear Ik woe it projekt op ien of oare manier net ... It wurk gie troch, mar hiel stadich, en no, koartlyn yn dat artikel ferskynde комментарий oer it ûnderwerp "Dus hoe is it allegear einige?" As antwurd op myn detaillearre antwurd hearde ik "Dit klinkt as in artikel." No, as jo kinne, sil d'r in artikel wêze. Miskien sil immen it nuttich fine. Dêrút sil de lêzer wat feiten leare oer it ûntwerp fan QEMU-koadegeneraasje-backends, lykas hoe't jo in Just-in-Time-kompiler skriuwe foar in webapplikaasje.

taken

Om't ik al leard hie hoe't ik QEMU "op ien of oare manier" koe portearje nei JavaScript, waard dizze kear besletten om it ferstannich te dwaan en âlde flaters net te werheljen.

Flater nûmer ien: branch fan punt release

Myn earste flater wie om myn ferzje te forkjen fan 'e streamopferzje 2.4.1. Doe like it my in goed idee: as punt frijlitting bestiet, dan is it wierskynlik stabiler as ienfâldige 2.4, en noch mear de branch master. En om't ik fan plan wie om in flinke hoemannichte fan myn eigen bugs ta te foegjen, hie ik gjinien oars nedich. Sa is it wierskynlik wurden. Mar hjir is it ding: QEMU stiet net stil, en op in stuit hawwe se sels oankundige optimisaasje fan 'e generearre koade troch 10 prosint. "Ja, no sil ik frieze," tocht ik en bruts ôf. Hjir moatte wy in ôfwiking meitsje: troch de single-threaded aard fan QEMU.js en it feit dat de orizjinele QEMU net ymplisearret it ûntbrekken fan multi-threading (dat is, de mooglikheid om tagelyk ferskate net-relatearre koadepaden te operearjen, en net allinnich "gebrûk alle kernels") is kritysk foar it, de wichtichste funksjes fan triedden ik moast "draaie it út" te kinnen belje fan bûten. Dit soarge foar wat natuerlike problemen by de fúzje. Lykwols, it feit dat guon fan 'e feroarings út' e branch master, wêrmei't ik besocht te fusearjen myn koade, waarden ek cherry plukt yn 'e punt release (en dus yn myn branch) ek wierskynlik soe net hawwe tafoege gemak.

Yn 't algemien besleat ik dat it noch logysk is om it prototype út te smiten, it te disassemble foar dielen en in nije ferzje fanôf it begjin te bouwen basearre op wat frisser en no fan master.

Flater nûmer twa: TLP-metoade

Yn essinsje is dit gjin flater, yn 't algemien is it gewoan in eigenskip fan it meitsjen fan in projekt yn betingsten fan folslein misferstân fan sawol "wêr en hoe te ferpleatsen?" en yn it algemien "sille wy dêr komme?" Yn dizze betingsten ûnhandige programmearring wie in rjochtfeardige opsje, mar, fansels, woe ik it net ûnnedich werhelje. Dizze kear woe ik it ferstannich dwaan: atomyske commits, bewuste koadeferoaringen (en net "willekeurige tekens byinoar stringje oant it kompilearret (mei warskôgings)", sa't Linus Torvalds ienris sei oer ien, neffens Wikiquote), ensfh.

Flater nûmer trije: yn it wetter gean sûnder de feart te witten

Ik bin dit noch net hielendal kwyt, mar no haw ik besletten om it paad fan minste wjerstân hielendal net te folgjen, en it "as folwoeksene" te dwaan, nammentlik myn TCG-backend fanôf it begjin te skriuwen, om net om letter te sizzen: "Ja, dit is fansels stadich, mar ik kin net alles kontrolearje - sa is TCI skreaun ..." Boppedat, dit like ynearsten as in fanselssprekkend oplossing, sûnt Ik generearje binêre koade. Sa't se sizze, "Gint sammeleу, mar net dat ien": de koade is fansels binêr, mar kontrôle kin der net gewoan nei oerdroegen wurde - it moat eksplisyt yn 'e blêder drukke wurde foar kompilaasje, wat resulteart yn in bepaald objekt út' e JS-wrâld, dat noch moat earne bewarre wurde. Op normale RISC-arsjitektueren, foar safier't ik begryp, is in typyske situaasje lykwols de needsaak om de ynstruksjecache foar regenerearre koade eksplisyt te resetten - as dit net is wat wy nedich binne, dan is it yn alle gefallen tichtby. Derneist learde ik fan myn lêste besykjen dat kontrôle net liket te wêzen oerbrocht nei it midden fan it oersettingsblok, dus wy hoege net echt bytekoade ynterpretearre út elke offset, en wy kinne it gewoan generearje fan 'e funksje op TB .

Se kamen en skopten

Hoewol ik yn july begon mei it herskriuwen fan 'e koade, krûpte in magyske kick ûngemurken op: normaal komme brieven fan GitHub as notifikaasjes oer antwurden op problemen en Pull-oanfragen, mar hjir, ynienen neame yn thread Binaryen as in qemu backend yn 'e kontekst, "Hy die sokssawat, miskien sil hy wat sizze." Wy hiene it oer it brûken fan Emscripten's relatearre bibleteek Binaryen om WASM JIT te meitsjen. No, ik sei dat jo hawwe in Apache 2.0 lisinsje dêr, en QEMU as gehiel wurdt ferdield ûnder GPLv2, en se binne net hiel kompatibel. Ynienen die bliken dat in fergunning kin reparearje it op ien of oare manier (Ik wit it net: miskien feroarje, miskien dûbele lisinsje, miskien wat oars ...). Dit makke my fansels bliid, want doe hie ik al goed nei sjoen binêre opmaak WebAssembly, en ik wie ien of oare manier tryst en ûnbegryplik. D'r wie ek in bibleteek dy't de basisblokken mei de oergongsgrafyk opslokte, de bytekoade produsearje, en sels útfiere soe yn 'e tolk sels, as it nedich wie.

Doe wie der mear in brief op 'e QEMU-mailinglist, mar dit giet mear oer de fraach, "Wa hat it dochs nedich?" En it is ynienen, it die bliken dat it nedich wie. Jo kinne op syn minst de folgjende gebrûksmooglikheden byinoar skrape, as it min of mear fluch wurket:

  • iets edukatyfs lansearje sûnder ienige ynstallaasje
  • virtualisaasje op iOS, wêr't, neffens geroften, de ienige applikaasje dy't it rjocht hat op koade generaasje op 'e flecht is in JS-motor (is dit wier?)
  • demonstraasje fan mini-OS - single-floppy, ynboude, alle soarten firmware, ensfh ...

Browser Runtime Features

Lykas ik al sei, is QEMU bûn oan multithreading, mar de browser hat it net. No, dat wol, nee... Earst bestie it hielendal net, doe ferskynden WebWorkers - foar safier't ik begryp is dit multithreading basearre op it trochjaan fan berjochten sûnder dielde fariabelen. Natuerlik soarget dit foar wichtige problemen by it portearjen fan besteande koade basearre op it dielde ûnthâldmodel. Doe waard it ûnder druk fan it publyk ek útfierd ûnder de namme SharedArrayBuffers. It waard stadichoan yntrodusearre, se fierden har lansearring yn ferskate browsers, dan fierden se it Nije Jier, en dan Meltdown ... wêrnei't se kamen ta de konklúzje dat grof of grof de tiid mjitting, mar mei help fan dielde ûnthâld en in thread incrementing de teller, it is allegear itselde it sil aardich krekt útwurkje. Sa hawwe wy multithreading útskeakele mei dielde ûnthâld. It liket derop dat se it letter wer oansette, mar, sa't it dúdlik waard út it earste eksperimint, is d'r libben sûnder, en as dat sa is, sille wy besykje it te dwaan sûnder te fertrouwen op multithreading.

De twadde funksje is de ûnmooglikheid fan manipulaasjes op leech nivo mei de stapel: jo kinne net gewoan nimme, de hjoeddeistige kontekst bewarje en oerskeakelje nei in nije mei in nije stapel. De opropstapel wurdt beheard troch de JS firtuele masine. It soe lykje, wat is it probleem, om't wy noch besletten om de eardere streamen folslein mei de hân te behearjen? It feit is dat blok I / O yn QEMU wurdt ymplementearre fia coroutines, en dit is wêr't leech-nivo stack manipulaasjes soe komme fan pas. Gelokkich befettet Emscipten al in meganisme foar asynchrone operaasjes, sels twa: Asyncify и Emterpreter. De earste wurket troch signifikante bloat yn 'e generearre JavaScript-koade en wurdt net langer stipe. De twadde is de hjoeddeiske "korrekte manier" en wurket troch bytecode generaasje foar de native tolk. It wurket, fansels, stadich, mar it net bloat de koade. Wier, stipe foar koroutines foar dit meganisme moast selsstannich bydroegen wurde (d'r wiene al koroutines skreaun foar Asyncify en d'r wie in ymplemintaasje fan sawat deselde API foar Emterpreter, jo moasten se gewoan ferbine).

Op it stuit bin ik noch net slagge om de koade te splitsen yn ien kompilearre yn WASM en ynterpretearre mei Emterpreter, dus blokapparaten wurkje noch net (sjoch yn 'e folgjende searje, sa't se sizze ...). Dat is, op it lêst moatte jo wat krije as dit grappige gelaagde ding:

  • ynterpretearre blok I / O. No, hiene jo echt ferwachte NVMe mei native prestaasjes? 🙂
  • statysk kompilearre haad QEMU-koade (oersetter, oare emulearre apparaten, ensfh.)
  • dynamysk kompilearre gast koade yn WASM

Skaaimerken fan QEMU boarnen

Lykas jo wierskynlik al riede, binne de koade foar it emulearjen fan gastarsjitektuer en de koade foar it generearjen fan hostmasjine-ynstruksjes skieden yn QEMU. Yn feite is it noch in bytsje lestiger:

  • der binne gast arsjitektuer
  • is accelerators, nammentlik KVM foar hardware-virtualisaasje op Linux (foar gast- en hostsystemen kompatibel mei elkoar), TCG foar generaasje fan JIT-koade oeral. Begjin mei QEMU 2.9 ferskynde stipe foar de HAXM-hardware-virtualisaasjestandert op Windows (de details)
  • as TCG wurdt brûkt en net hardware-virtualisaasje, dan hat it aparte koade-generaasje-stipe foar elke host-arsjitektuer, lykas foar de universele tolk
  • ... en om dit alles hinne - emulearre perifeare apparaten, brûkersynterface, migraasje, opnij opnij opnimme, ensfh.

Trouwens, wisten jo: QEMU kin emulate net allinnich de hiele kompjûter, mar ek de prosessor foar in apart brûker proses yn de host kernel, dat wurdt brûkt, bygelyks, troch de AFL fuzzer foar binêre ynstrumintaasje. Miskien wol immen dizze modus fan wurking fan QEMU nei JS oerdrage? 😉

Lykas de measte langsteande frije software, wurdt QEMU boud troch de oprop configure и make. Litte wy sizze dat jo beslute om wat ta te foegjen: in TCG-backend, thread-ymplemintaasje, wat oars. Haast net om lokkich / ôfgryslik te wêzen (ûnderstreekje as passend) by it perspektyf fan kommunikaasje mei Autoconf - yn feite, configure QEMU's binne blykber sels skreaun en wurdt net generearre út neat.

WebAssembly

Dus wat is dit ding neamd WebAssembly (aka WASM)? Dit is in ferfanging foar Asm.js, net langer pretend as in jildige JavaScript-koade. Krektoarsom, it is suver binêr en optimalisearre, en sels gewoan it skriuwen fan in hiel getal yn it is net hiel ienfâldich: foar kompaktheid wurdt it opslein yn it formaat LEB128.

Jo hawwe miskien heard oer it relooping-algoritme foar Asm.js - dit is de restauraasje fan "high-level" flow control ynstruksjes (dat is, as-dan-oars, loops, ensfh.), wêrfoar JS-motoren binne ûntwurpen, fan de low-level LLVM IR, tichter by de masine koade útfierd troch de prosessor. Natuerlik is de tuskenlizzende fertsjintwurdiging fan QEMU tichter by de twadde. It soe lykje dat hjir it is, bytecode, it ein fan 'e pine ... En dan binne der blokken, as-dan-oars en loops! ..

En dit is in oare reden wêrom Binaryen nuttich is: it kin natuerlik blokken op hege nivo akseptearje tichtby wat soe wurde opslein yn WASM. Mar it kin ek produsearje koade út in grafyk fan basis blokken en transysjes tusken harren. No, ik haw al sein dat it it WebAssembly-opslachformaat ferberget efter de handige C / C ++ API.

TCG (Tiny Code Generator)

GTC wie oarspronklik backend foar de C-kompilator. Dan, blykber, koe it net tsjin de konkurrinsje mei GCC, mar op it lêst fûn it syn plak yn QEMU as in koade generaasje meganisme foar de host platfoarm. D'r is ek in TCG-backend dy't wat abstrakte bytekoade genereart, dy't daliks wurdt útfierd troch de tolk, mar ik besleat om dizze kear te foarkommen dat it gebrûk makket. It feit lykwols dat it yn QEMU al mooglik is om de oergong nei de generearre TB yn te skeakeljen fia de funksje tcg_qemu_tb_exec, it die bliken hiel brûkber foar my.

Om in nije TCG-backend ta te foegjen oan QEMU, moatte jo in submap oanmeitsje tcg/<имя архитектуры> (yn dit gefal, tcg/binaryen), en it befettet twa triemmen: tcg-target.h и tcg-target.inc.c и foarskriuwe it hat alles te krijen mei configure. Jo kinne dêr oare bestannen pleatse, mar, lykas jo kinne riede út de nammen fan dizze twa, sille se beide earne opnommen wurde: ien as in gewoane kopteksttriem (it is opnommen yn tcg/tcg.h, en dy is al yn oare triemmen yn de mappen tcg, accel en net allinich), de oare - allinich as koadefragment yn tcg/tcg.c, mar it hat tagong ta syn statyske funksjes.

Besluten dat ik tefolle tiid soe besteegje oan detaillearre ûndersiken fan hoe't it wurket, kopiearre ik gewoan de "skeletten" fan dizze twa bestannen fan in oare ymplemintaasje fan 'e backend, earlik oanjûn yn' e lisinsjekop.

file tcg-target.h befettet benammen ynstellings yn 'e foarm #define-s:

  • hoefolle registers en hokker breedte binne der op 'e doel-arsjitektuer (wy hawwe safolle as wy wolle, safolle as wy wolle - de fraach is mear oer wat sil wurde generearre yn effisjinter koade troch de browser op 'e "folslein doel" arsjitektuer ...)
  • alignment fan host ynstruksjes: op x86, en sels yn TCI, ynstruksjes binne net ôfstimd op alle, mar ik sil sette yn de koade buffer gjin ynstruksjes op alle, mar oanwizers nei Binaryen bibleteek struktueren, dus ik sil sizze: 4 bytes
  • hokker opsjonele ynstruksjes de backend kin generearje - wy befetsje alles wat wy fine yn Binaryen, lit de accelerator de rest yn ienfâldiger sels brekke
  • Wat is de ûngefear grutte fan 'e TLB-cache oanfrege troch de efterkant. It feit is dat yn QEMU alles serieus is: hoewol d'r helpfunksjes binne dy't load/store útfiere mei rekkening mei de gast MMU (wêr soene wy ​​no sûnder wêze?), se bewarje har oersettingscache yn 'e foarm fan in struktuer, de ferwurking wêrfan handich is om direkt yn útstjoerblokken yn te foegjen. De fraach is, hokker offset yn dizze struktuer wurdt meast effisjint ferwurke troch in lytse en flugge folchoarder fan kommando's?
  • hjir kinne jo it doel fan ien of twa reservearre registers oanpasse, TB oproppe fia in funksje ynskeakelje en opsjoneel in pear lytse beskriuwe inline-funksjes lykas flush_icache_range (mar dit is net ús gefal)

file tcg-target.inc.c, fansels, is normaal folle grutter yn grutte en befettet ferskate ferplichte funksjes:

  • inisjalisaasje, ynklusyf beheiningen op hokker ynstruksjes kinne operearje op hokker operanden. Blatant kopiearre troch my fan in oare backend
  • funksje dy't ien ynterne bytekoade ynstruksje nimt
  • Jo kinne ek sette auxiliary funksjes hjir, En jo kinne ek brûke statyske funksjes fan tcg/tcg.c

Foar mysels haw ik de folgjende strategy keazen: yn de earste wurden fan it folgjende oersetblok haw ik fjouwer oanwizers opskreaun: in startteken (in bepaalde wearde yn de buert 0xFFFFFFFF, dy't de aktuele steat fan 'e TB bepaalde), kontekst, generearre module, en magysk nûmer foar debuggen. Earst waard it mark yn pleatst 0xFFFFFFFF - nwêr n - in lyts posityf nûmer, en elke kear dat it waard útfierd troch de tolk tanommen mei 1. Doe't it berikt 0xFFFFFFFE, kompilaasje fûn plak, de module waard bewarre yn 'e funksjetabel, ymportearre yn in lyts "launcher", wêryn útfiering gie fanút tcg_qemu_tb_exec, en de module waard fuortsmiten út QEMU ûnthâld.

Om de klassiken te parafrasearjen, "Crutch, hoefolle is yn dit lûd ferweefd foar it hert fan 'e proger ...". It ûnthâld lekt lykwols earne. Boppedat wie it ûnthâld beheard troch QEMU! Ik hie in koade dy't, by it skriuwen fan 'e folgjende ynstruksje (goed, dat is, in oanwizer), dejinge dy't de keppeling wie op dit plak earder wiske, mar dit holp net. Eins, yn it ienfâldichste gefal, allocates QEMU ûnthâld by it opstarten en skriuwt dêr de oanmakke koade. As de buffer rint út, de koade wurdt smiten út en de folgjende begjint te skreaun yn syn plak.

Nei't ik de koade studearre, realisearre ik dat de trúk mei it magyske getal my koe net mislearje op heap ferneatiging troch wat ferkeard te befrijen op in uninitialisearre buffer op 'e earste pas. Mar wa herskriuwt de buffer om myn funksje letter te omgean? As de Emscripten-ûntwikkelders advisearje, doe't ik in probleem rûn, haw ik de resultearjende koade werombrocht nei de native applikaasje, set Mozilla Record-Replay derop ... Yn 't algemien realisearre ik op it lêst in ienfâldich ding: foar elk blok, in struct TranslationBlock mei syn beskriuwing. Guess wêr... Dat is krekt, krekt foar it blok rjocht yn de buffer. Doe't ik dit realisearre, besleat ik op te hâlden mei it brûken fan krukken (op syn minst guon), en smiet it magyske getal gewoan út en stjoerde de oerbleaune wurden oer nei struct TranslationBlock, it oanmeitsjen fan in inkeld keppele list dy't fluch trochtocht wurde kin as de oersettingscache weromset wurdt, en ûnthâld frijmeitsje.

Guon krukken bliuwe: bygelyks markearre oanwizers yn 'e koadebuffer - guon fan har binne gewoan BinaryenExpressionRef, dat is, se sjogge nei de útdrukkingen dy't lineêr moatte wurde set yn 'e generearre basisblok, diel is de betingst foar oergong tusken BB's, diel is wêr't te gean. No ja, der binne al klearmakke blokken foar Relooper dy't neffens de betingsten ferbûn wurde moatte. Om se te ûnderskieden, wurdt de oanname brûkt dat se allegear op syn minst fjouwer bytes binne ôfstimd, sadat jo de minste wichtige twa bits feilich kinne brûke foar it label, jo moatte gewoan ûnthâlde om it te ferwiderjen as it nedich is. Trouwens, sokke labels wurde al brûkt yn QEMU om de reden oan te jaan foar it ferlitten fan 'e TCG-loop.

Binaryen brûke

Modules yn WebAssembly befetsje funksjes, elk fan dat befettet in lichem, dat is in útdrukking. Ekspresjes binne unêre en binêre operaasjes, blokken besteande út listen mei oare útdrukkingen, kontrôlestream, ensfh. Lykas ik al sei, wurdt kontrôlestream hjir krekt organisearre as tûken op hege nivo, loops, funksjeoproppen, ensfh. Arguminten foar funksjes wurde net trochjûn op 'e steapel, mar eksplisyt, krekt as yn JS. Der binne ek globale fariabelen, mar ik haw net brûkt se, dus ik sil net fertelle jo oer harren.

Funksjes hawwe ek lokale fariabelen, nûmere fan nul, fan type: int32 / int64 / float / dûbel. Yn dit gefal binne de earste n lokale fariabelen de arguminten dy't trochjûn wurde oan de funksje. Tink derom dat hoewol alles hjir net folslein leech is yn termen fan kontrôlestream, heule getallen noch altyd net it "ûndertekene / net ûndertekene" attribút drage: hoe't it nûmer gedraacht hinget ôf fan 'e operaasjekoade.

Yn 't algemien biedt Binaryen ienfâldige C-API: jo meitsje in module, yn hy útdrukkingen oanmeitsje - unary, binêr, blokken fan oare útdrukkingen, kontrôlestream, ensfh. Dan meitsje jo in funksje mei in útdrukking as syn lichem. As jo, lykas ik, in oergongsgraf op leech nivo hawwe, sil de relooper-komponint jo helpe. Sa fier as ik begryp, is it mooglik om te brûken hege-nivo kontrôle fan de útfiering flow yn in blok, sa lang as it giet net bûten de grinzen fan it blok - dat is, it is mooglik om te meitsjen ynterne fluch paad / stadich paad fertakking binnen de ynboude TLB-cache-ferwurkingskoade, mar net om te bemuoien mei de "eksterne" kontrôlestream. As jo ​​in relooper frijmeitsje, wurde de blokken frijmakke, as jo in module frijmeitsje, ferdwine de útdrukkingen, funksjes, ensfh. arena.

As jo ​​​​lykwols koade op 'e flecht wolle ynterpretearje sûnder ûnnedige oanmeitsjen en wiskjen fan in tolkeksimplaar, kin it sin wêze om dizze logika yn in C++-bestân te setten, en fanôf dêrút direkt de hiele C++ API fan 'e bibleteek te behearjen, troch te gean mei klear- wrappers makke.

Dus om de koade te generearjen dy't jo nedich binne

// настроить глобальные параметры (можно поменять потом)
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 ik wat fergetten, sorry, dit is gewoan om de skaal te fertsjintwurdigjen, en de details binne yn 'e dokumintaasje.

En no begjint de crack-fex-pex, sa'n ding:

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 de wrâlden fan QEMU en JS op ien of oare manier te ferbinen en tagelyk fluch tagong te krijen ta de kompilearre funksjes, waard in array makke (in tabel mei funksjes foar ymportearjen yn 'e launcher), en de generearre funksjes waarden dêr pleatst. Om de yndeks fluch te berekkenjen, waard de yndeks fan it nulwurdoersettingsblok yn earste ynstânsje brûkt as it, mar doe begon de yndeks berekkene mei dizze formule gewoan yn it fjild te passen yn struct TranslationBlock.

By de manier, demo (op it stuit mei in tsjustere lisinsje) wurket allinnich goed yn Firefox. Chrome-ûntwikkelders wiene ien of oare manier net klear oan it feit dat immen mear dan tûzen eksimplaren fan WebAssembly-modules oanmeitsje soe, sadat se gewoan in gigabyte oan firtuele adresromte foar elke ...

Dat is alles foar no. Miskien komt der in oar artikel as immen ynteressearre is. Der bliuwt nammentlik alteast oer allinnich meitsje blok apparaten wurkje. It kin ek sin wêze om de kompilaasje fan WebAssembly-modules asyngroan te meitsjen, lykas gewoanlik yn 'e JS-wrâld, om't der noch in tolk is dy't dit alles dwaan kin oant de native module klear is.

Einliks in riedsel: jo hawwe kompilearre in binêre op in 32-bit arsjitektuer, mar de koade, troch ûnthâld operaasjes, klimt út Binaryen, earne op 'e steapel, of earne oars yn' e boppeste 2 GB fan de 32-bit adres romte. It probleem is dat út it eachpunt fan Binaryen dit tagong hat ta in te grut resultearend adres. Hoe te krijen om dit?

Op admin's manier

Ik haw dit net op it lêst testen, mar myn earste gedachte wie "Wat as ik 32-bit Linux ynstalleare?" Dan sil it boppeste diel fan 'e adresromte beset wurde troch de kernel. De ienige fraach is hoefolle sil wurde beset: 1 of 2 Gb.

Op in programmeur's manier (opsje foar praktiken)

Litte wy in bubbel blaze oan 'e boppekant fan' e adresromte. Ik sels begryp net wêrom it wurket - dêr al der moat in steapel wêze. Mar "wy binne praktiken: alles wurket foar ús, mar gjinien wit wêrom ..."

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

... it is wier dat it net kompatibel is mei Valgrind, mar, gelokkich, triuwt Valgrind sels heul effektyf elkenien derút :)

Miskien sil immen in bettere útlis jaan oer hoe't dizze koade fan my wurket ...

Boarne: www.habr.com

Add a comment