QEMU.js: nüüd tõsine ja WASM-iga

Kunagi ammu otsustasin nalja pärast tõestada protsessi pöörduvust ja õppige masinkoodist JavaScripti (täpsemalt Asm.js) genereerima. Katse jaoks valiti QEMU ja mõni aeg hiljem kirjutati artikkel Habrist. Kommentaarides soovitati mul projekt WebAssemblys ümber teha ja isegi loobuda peaaegu valmis Ma millegipärast ei tahtnud seda projekti... Töö käis, kuid väga aeglaselt ja nüüd, hiljuti ilmus see artikkel kommentaar teemal "Kuidas see kõik lõppes?" Vastuseks minu üksikasjalikule vastusele kuulsin "See kõlab nagu artikkel." Noh, kui saate, siis tuleb artikkel. Ehk leiab keegi sellest kasu. Sellest saab lugeja teada mõningaid fakte QEMU koodi genereerimise taustaprogrammide disaini kohta, aga ka seda, kuidas kirjutada veebirakenduse jaoks just-in-Time kompilaatorit.

ülesanded

Kuna olin juba õppinud QEMU “kuidagi” portimist JavaScripti, siis seekord otsustati seda teha targalt ja mitte korrata vanu vigu.

Viga number üks: hargneb punkti vabastamisest

Minu esimene viga oli eraldada oma versioon ülesvoolu versioonist 2.4.1. Siis tundus mulle hea mõte: kui point release on olemas, siis on see ilmselt stabiilsem kui lihtne 2.4 ja veel enam haru master. Ja kuna plaanisin lisada üsna palju oma vigu, ei vajanud ma üldse kellegi teise oma. Küllap see nii välja kukkus. Aga siin on asi: QEMU ei seisa paigal ja mingil hetkel teatasid nad isegi genereeritud koodi optimeerimisest 10 protsendi võrra. "Jah, nüüd ma külmun," mõtlesin ja murdusin. Siinkohal peame tegema kõrvalepõike: QEMU.js-i ühelõimelise olemuse ja asjaolu tõttu, et algne QEMU ei tähenda mitmelõime puudumist (st võimalust kasutada samaaegselt mitut mitteseotud kooditeed, ja mitte ainult "kasuta kõiki tuumasid") on selle jaoks kriitilise tähtsusega, vaid lõimede põhifunktsioonid pidin väljastpoolt helistamiseks "välja keerama". See tekitas ühinemise käigus loomulikke probleeme. Kuid asjaolu, et mõned muudatused filiaalist master, millega proovisin oma koodi liita, olid ka punktiväljaandes (ja seega ka minu harus) valitud, poleks samuti ilmselt mugavust lisanud.

Üldiselt otsustasin, et prototüüp on siiski mõttekas välja visata, osadeks lahti võtta ja nullist uus versioon ehitada millegi värskema põhjal ja nüüd alates. master.

Viga number kaks: TLP metoodika

Sisuliselt pole see viga, üldiselt on see lihtsalt projekti loomise funktsioon nii "kuhu ja kuidas liikuda?" ja üldiselt "kas me jõuame?" Nendes tingimustes kohmakas programmeerimine oli õigustatud variant, kuid loomulikult ei tahtnud ma seda asjatult korrata. Seekord tahtsin seda teha targalt: aatomi commit’id, teadlikud koodimuutused (ja mitte “suvaliste märkide kokku tõmbamine, kuni see kompileerub (koos hoiatustega)”, nagu Linus Torvalds kunagi Wikitsitaadi järgi kellegi kohta ütles) jne.

Viga number kolm: vette sattumine fordi teadmata

Ma pole sellest ikka veel päris lahti saanud, aga nüüd otsustasin mitte minna kergema vastupanu teed ja teha seda “täiskasvanuna” ehk kirjutada oma TCG taustaprogramm nullist, et mitte. pean hiljem ütlema: "Jah, see on muidugi aeglaselt, aga ma ei saa kõike kontrollida - nii on TCI kirjutatud..." Pealegi tundus see alguses ilmselge lahendusena, kuna Ma genereerin binaarkoodi. Nagu öeldakse: "Ghent kogunesу, kuid mitte see”: kood on loomulikult binaarne, kuid juhtimist ei saa lihtsalt sellele üle kanda – see tuleb kompileerimiseks otse brauserisse lükata, mille tulemuseks on teatud objekt JS maailmast, mis vajab siiski kuhugi päästetud olla. Tavaliste RISC-arhitektuuride puhul, niipalju kui ma aru saan, on tüüpiline olukord aga vajadus uuesti genereeritud koodi käsuvahemälu selgesõnaliselt lähtestada - kui see pole see, mida me vajame, on see igal juhul lähedal. Lisaks sain viimasest katsest teada, et juhtimine ei paista olevat tõlkeploki keskele üle kantud, nii et me ei vaja baitkoodi, mida tõlgendatakse ühestki nihkest ja saame selle lihtsalt genereerida TB funktsioonist. .

Nad tulid ja lõid jalaga

Kuigi hakkasin koodi ümber kirjutama juba juulis, hiilis märkamatult kohale maagiline löök: tavaliselt saabuvad GitHubist kirjad teatena probleemidele ja tõmbetaotlustele vastamise kohta, kuid siin äkki maini lõimes Binaryen qemu taustaprogrammina kontekstis: "Ta tegi midagi sellist, võib-olla ta ütleb midagi." Rääkisime Emscripteni seotud raamatukogu kasutamisest Binaryen WASM JITi loomiseks. Noh, ma ütlesin, et teil on seal Apache 2.0 litsents ja QEMU tervikuna levitatakse GPLv2 all ja need pole eriti ühilduvad. Järsku selgus, et luba võib olla paranda see kuidagi ära (Ma ei tea: võib-olla muuta seda, võib-olla topeltlitsentsi, võib-olla midagi muud...). See muidugi rõõmustas mind, sest olin selleks ajaks juba lähedalt vaadanud binaarne vorming WebAssembly ja ma olin kuidagi kurb ja arusaamatu. Seal oli ka teek, mis neelas põhiplokid koos üleminekugraafikuga, toodab baitkoodi ja käivitas selle vajadusel isegi interpretaatoris.

Siis oli rohkem kirja QEMU meililistis, kuid see puudutab pigem küsimust "Kellele seda ikkagi vaja on?" Ja ongi äkki, selgus, et see oli vajalik. Vähemalt saate selliseid kasutusvõimalusi kokku kraapida, kui see töötab enam-vähem kiiresti:

  • millegi hariva käivitamine ilma installita
  • virtualiseerimine iOS-is, kus kuulujuttude kohaselt on ainus rakendus, millel on õigus lennult koodi genereerida, JS-mootor (kas see on tõsi?)
  • mini-OS-i demonstratsioon - ühe disketiga, sisseehitatud, igasugune püsivara jne...

Brauseri käitusaja funktsioonid

Nagu ma juba ütlesin, on QEMU seotud mitme lõimega, kuid brauseril seda pole. Noh, see tähendab, ei... Alguses polnud seda üldse olemas, siis ilmusid WebWorkers - minu arusaamist mööda on see sõnumi edastamisel põhinev mitmelõimeline ilma jagatud muutujateta. Loomulikult tekitab see olulisi probleeme olemasoleva koodi teisaldamisel jagatud mälu mudeli alusel. Siis hakati avalikkuse survel ka nime all ellu viima SharedArrayBuffers. See võeti järk-järgult kasutusele, tähistati selle käivitamist erinevates brauserites, siis tähistati uut aastat ja siis Meltdowni... Pärast seda jõuti järeldusele, et ajamõõtmine on jäme või jäme, kuid ühismälu ja niit suurendades loendurit, kõik on sama see tuleb päris täpselt välja. Nii et keelasime ühismäluga mitmelõimestamise. Tundub, et nad lülitasid selle hiljem uuesti sisse, kuid nagu esimesest katsest selgus, on elu ka ilma selleta ja kui jah, siis proovime seda teha ilma mitmelõimelisusele lootmata.

Teine omadus on virnaga madala taseme manipuleerimise võimatus: te ei saa lihtsalt võtta, salvestada praegust konteksti ja lülituda uuele uuele virnaga. Kõnede pinu haldab JS-i virtuaalmasin. Näib, milles probleem, kuna otsustasime siiski endisi vooge täielikult käsitsi hallata? Fakt on see, et QEMU-s rakendatakse ploki I/O-d korutiinide kaudu ja siin tuleks kasuks madala tasemega pinu manipuleerimine. Õnneks sisaldab Emscipten juba asünkroonsete toimingute mehhanismi, isegi kahte: Asünkroonimine и Interpreter. Esimene toimib loodud JavaScripti koodi märkimisväärse paisumise kaudu ja seda enam ei toetata. Teine on praegune "õige viis" ja töötab baitkoodi genereerimise kaudu natiivse tõlgi jaoks. See töötab loomulikult aeglaselt, kuid see ei aja koodi üle. Tõsi, selle mehhanismi korutiinide tugi tuli panustada iseseisvalt (Asyncify jaoks olid juba kirjutatud korutiinid ja Emterpreteri jaoks oli umbes sama API juurutus, vaja oli need lihtsalt ühendada).

Hetkel pole mul veel õnnestunud WASM-is kompileeritud ja Emterpreteri abil tõlgendatud koodi tükeldada, seega plokkseadmed veel ei tööta (vaata järgmises seerias, nagu öeldakse...). See tähendab, et lõpuks peaksite saama sellise naljaka kihilise asja:

  • tõlgendatud plokk I/O. Noh, kas te tõesti eeldasite, et emuleeritud NVMe on natiivse jõudlusega? 🙂
  • staatiliselt kompileeritud peamine QEMU kood (tõlkija, muud emuleeritud seadmed jne)
  • dünaamiliselt kompileeritud külaliskood WASM-i

QEMU allikate omadused

Nagu ilmselt juba arvasite, on QEMU-s külalisarhitektuuride emuleerimise kood ja hostmasina juhiste genereerimise kood eraldatud. Tegelikult on see isegi veidi keerulisem:

  • on külalisarhitektuure
  • on kiirendid, nimelt KVM riistvara virtualiseerimiseks Linuxis (üksteisega ühilduvate külaliste ja hostsüsteemide jaoks), TCG JIT-koodi genereerimiseks kõikjal. Alates QEMU 2.9-st ilmus Windowsi riistvara virtualiseerimisstandardi HAXM tugi (Detailid)
  • kui kasutatakse TCG-d, mitte riistvara virtualiseerimist, on sellel eraldi koodi genereerimise tugi nii iga hosti arhitektuuri kui ka universaalse tõlgi jaoks
  • ... ja kõige selle ümber – emuleeritud välisseadmed, kasutajaliides, migratsioon, salvestuse taasesitus jne.

Muide, kas teadsite: QEMU suudab emuleerida mitte ainult kogu arvutit, vaid ka protsessorit eraldi kasutajaprotsessi jaoks hosti tuumas, mida kasutab näiteks AFL-fuzzer binaarseks mõõtmiseks. Võib-olla soovib keegi selle QEMU töörežiimi portida JS-i? 😉

Nagu enamik pikaajalist tasuta tarkvara, on ka QEMU loodud kõne kaudu configure и make. Oletame, et otsustate midagi lisada: TCG taustaprogrammi, lõime juurutamist või midagi muud. Ärge kiirustage Autoconfiga suhtlemise üle õnnelikud/hirmunud (kriipsutage vajadusel alla) – tegelikult configure QEMU on ilmselt enda kirjutatud ja ei ole loodud millestki.

WebAssembly

Mis on see asi, mida nimetatakse WebAssemblyks (teise nimega WASM)? See on Asm.js-i asendus, mis ei pretendeeri enam kehtiva JavaScripti koodina. Vastupidi, see on puhtalt binaarne ja optimeeritud ning isegi lihtsalt täisarvu kirjutamine sellesse pole väga lihtne: kompaktsuse huvides salvestatakse see vormingus LEB128.

Võib-olla olete kuulnud Asm.js-i relooping-algoritmist – see on "kõrgetasemeliste" voojuhtimiskäskude (st kui-siis-muidu tsüklite jne) taastamine, mille jaoks on loodud JS-i mootorid, alates madala taseme LLVM IR, mis on lähemal protsessori käivitatavale masinkoodile. Loomulikult on QEMU vahepealne esitus teisele lähemal. Näib, et siin see on, baitkood, piina lõpp... Ja siis on plokid, kui-siis-muu ja silmused!..

Ja see on veel üks põhjus, miks Binaryen on kasulik: see võib loomulikult vastu võtta kõrgetasemelisi plokke, mis on lähedased WASM-i talletatule. Kuid see võib luua koodi ka põhiplokkide ja nendevaheliste üleminekute graafikust. Noh, ma olen juba öelnud, et see peidab WebAssembly salvestusvormingu mugava C/C++ API taha.

TCG (Tiny Code Generator)

TCG oli algselt C-kompilaatori taustaprogramm. Siis ilmselt ei pidanud see GCC-ga konkurentsile vastu, kuid lõpuks leidis see oma koha QEMU-s hostplatvormi koodi genereerimise mehhanismina. Samuti on olemas TCG taustaprogramm, mis genereerib mingi abstraktse baitkoodi, mille tõlk kohe käivitab, kuid otsustasin seekord selle kasutamist vältida. Küll aga see, et QEMU-s on juba võimalik funktsiooni kaudu võimaldada üleminek genereeritud TB-le tcg_qemu_tb_exec, osutus see minu jaoks väga kasulikuks.

Uue TCG taustaprogrammi lisamiseks QEMU-sse peate looma alamkataloogi tcg/<имя архитектуры> (sel juhul, tcg/binaryen) ja see sisaldab kahte faili: tcg-target.h и tcg-target.inc.c и ette kirjutada see kõik käib configure. Sinna saab panna ka muid faile, aga nagu nende kahe nimest arvata võib, lisatakse need mõlemad kuhugi: üks tavalise päisefailina (see sisaldub tcg/tcg.h, ja see on juba teistes kataloogide failides tcg, accel ja mitte ainult), teine ​​- ainult koodilõiguna sisse tcg/tcg.c, kuid sellel on juurdepääs oma staatilistele funktsioonidele.

Otsustades, et kulutan liiga palju aega selle toimimise üksikasjalikule uurimisele, kopeerisin lihtsalt nende kahe faili "skeletid" teisest taustarakendusest, näidates seda ausalt litsentsi päises.

fail tcg-target.h sisaldab peamiselt vormi sätteid #define-s:

  • mitu registrit ja kui laius on sihtarhitektuuril (meil on nii palju kui tahame, nii palju kui tahame - küsimus on pigem selles, mida brauser "täielikult sihtmärgi" arhitektuuril tõhusamaks koodiks genereerib ...)
  • hostijuhiste joondamine: x86-s ja isegi TCI-s ei ole juhised üldse joondatud, kuid ma ei pane koodipuhvri mitte juhiseid, vaid viiteid Binaryeni teegi struktuuridele, nii et ma ütlen: 4 baiti
  • milliseid valikulisi käske saab taustaprogramm genereerida - lisame kõik, mida Binaryenis leiame, laseme kiirendil jagada ülejäänud lihtsamateks
  • Mis on taustaprogrammi taotletud TLB vahemälu ligikaudne suurus. Fakt on see, et QEMU-s on kõik tõsine: kuigi on olemas abifunktsioonid, mis laadivad / salvestavad, võttes arvesse külalis-MMU-d (kus me nüüd oleksime ilma selleta?), salvestavad nad oma tõlkevahemälu struktuuri kujul, mille töötlemist on mugav otse leviplokkidesse manustada. Küsimus on selles, millist nihet selles struktuuris töödeldakse kõige tõhusamalt väikese ja kiire käsujada abil?
  • siin saate muuta ühe või kahe reserveeritud registri eesmärki, lubada funktsiooni kaudu TB kutsumist ja soovi korral kirjeldada paari väikest inline- toimib nagu flush_icache_range (aga see pole meie juhtum)

fail tcg-target.inc.cmuidugi on tavaliselt palju suurem ja sisaldab mitmeid kohustuslikke funktsioone:

  • initsialiseerimine, sealhulgas piirangud selle kohta, millised käsud võivad millistel operandidel töötada. Minu poolt räigelt kopeeritud teisest taustaprogrammist
  • funktsioon, mis võtab ühe sisemise baitkoodi käsu
  • Siia saab panna ka abifunktsioone, samuti saab kasutada staatilisi funktsioone alates tcg/tcg.c

Enda jaoks valisin järgmise strateegia: järgmise tõlkeploki esimestesse sõnadesse panin kirja neli viidet: algusmärk (teatud väärtus läheduses 0xFFFFFFFF, mis määras TB praeguse oleku), konteksti, loodud mooduli ja maagilise numbri silumiseks. Algul pandi märk sisse 0xFFFFFFFF - nKus n - väike positiivne arv ja iga kord, kui see tõlgi kaudu täideti, suurenes see 1 võrra. Kui see jõudis 0xFFFFFFFE, kompileerimine toimus, moodul salvestati funktsioonitabelisse, imporditi väikesesse “käivitajasse”, kuhu täitmine läks tcg_qemu_tb_execja moodul eemaldati QEMU mälust.

Klassikuid parafraseerides: “Kark, kui palju on selles helis põimunud proger’i südame jaoks...”. Mälestus aga lekkis kuskilt. Pealegi oli see mälu, mida haldas QEMU! Mul oli kood, mis järgmise juhise (noh, see tähendab osuti) kirjutamisel kustutas selle, mille link oli varem selles kohas, kuid see ei aidanud. Tegelikult eraldab QEMU kõige lihtsamal juhul käivitamisel mälu ja kirjutab sinna genereeritud koodi. Kui puhver saab otsa, visatakse kood välja ja selle asemele hakatakse kirjutama järgmist.

Pärast koodi uurimist mõistsin, et maagilise numbriga tehtud trikk võimaldas mul mitte ebaõnnestuda hunniku hävitamisel, vabastades esimesel läbimisel initsialiseerimata puhvris midagi valesti. Aga kes kirjutab puhvri ümber, et hiljem minu funktsioonist mööda minna? Nagu Emscripteni arendajad soovitavad, teisaldasin probleemi ilmnemisel saadud koodi tagasi natiivsesse rakendusse, määrasin sellele Mozilla Record-Replay... Üldiselt sain lõpuks aru lihtsast asjast: iga ploki puhul a struct TranslationBlock koos selle kirjeldusega. Arva ära, kus... Just, vahetult enne plokki otse puhvris. Sellest aru saades otsustasin karkude kasutamisest loobuda (vähemalt mõned) ja viskasin maagilise numbri lihtsalt välja ja edastasin ülejäänud sõnad struct TranslationBlock, luues üksikult lingitud loendi, mida saab tõlke vahemälu lähtestamisel kiiresti läbida ja mis vabastab mälu.

Mõned kargud jäävad alles: näiteks märgistatud osutid koodipuhvris - mõned neist on lihtsalt BinaryenExpressionRef, see tähendab, et nad vaatavad avaldisi, mis tuleb genereeritud põhiplokki lineaarselt panna, osa on BB-de vahelise ülemineku tingimus, osa on see, kuhu minna. Noh, Relooperi jaoks on juba ette valmistatud plokid, mis tuleb vastavalt tingimustele ühendada. Nende eristamiseks kasutatakse eeldust, et need kõik on joondatud vähemalt nelja baiti võrra, nii et võid julgelt kasutada sildi jaoks kaht kõige vähemtähtsamat bitti, vaid tuleb meeles pidada, et see vajadusel eemaldatakse. Muide, selliseid silte kasutatakse juba QEMU-s, et näidata TCG-ahelast väljumise põhjust.

Binaryeni kasutamine

WebAssembly moodulid sisaldavad funktsioone, millest igaüks sisaldab keha, mis on avaldis. Avaldised on ühe- ja kahendtehted, teiste avaldiste loenditest koosnevad plokid, juhtvoog jne. Nagu ma juba ütlesin, on siin juhtimisvoog korraldatud täpselt kõrgetasemeliste harude, silmuste, funktsioonikutsete jne kujul. Funktsioonide argumente ei edastata virna, vaid otse, nagu JS-is. On ka globaalseid muutujaid, kuid ma pole neid kasutanud, nii et ma ei räägi teile neist.

Funktsioonidel on ka lokaalsed muutujad, mis on nummerdatud nullist, tüübiga int32 / int64 / float / double. Sel juhul on funktsioonile edastatavad argumendid esimesed n kohalikku muutujat. Pange tähele, et kuigi kõik siin ei ole juhtimisvoo osas täiesti madala tasemega, ei kanna täisarvud ikkagi atribuuti "signed/unsigned": numbri käitumine sõltub operatsiooni koodist.

Üldiselt pakub Binaryen lihtne C-API: loote mooduli, temas avaldiste loomine – unaarne, binaarne, teistest avaldistest pärinevad plokid, juhtvoog jne. Seejärel loote funktsiooni, mille kehaks on avaldis. Kui teil, nagu minul, on madala tasemega üleminekugraafik, aitab teid relooperi komponent. Niipalju kui ma aru saan, on võimalik kasutada täitmisvoo kõrgetasemelist juhtimist plokis seni, kuni see ei välju ploki piiridest - see tähendab, et on võimalik muuta sisemine kiire tee / aeglane tee hargneb sisseehitatud TLB vahemälu töötlemiskoodi sees, kuid mitte segama välist juhtimisvoogu. Kui vabastate relooperi, vabastatakse selle plokid, mooduli vabastamisel kaovad sellele eraldatud avaldised, funktsioonid jne. areenil.

Kui aga soovite koodi tõlgendada käigu pealt ilma tarbetu tõlgi eksemplari loomise ja kustutamiseta, võib olla mõttekas panna see loogika C++ faili ja sealt otse hallata kogu teegi C++ API-t, minnes valmis valmistatud ümbrised.

Nii et vajaliku koodi genereerimiseks

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

... kui ma midagi unustasin, vabandust, see on lihtsalt skaala esindamiseks ja üksikasjad on dokumentatsioonis.

Ja nüüd algab crack-fex-pex, umbes selline:

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

QEMU ja JS maailmade kuidagi ühendamiseks ja samal ajal kompileeritud funktsioonidele kiireks ligipääsuks loodi massiiv (käivitusseadmesse importimise funktsioonide tabel), kuhu paigutati genereeritud funktsioonid. Indeksi kiireks arvutamiseks kasutati algselt nullsõna tõlkeploki indeksit sellisena, kuid siis hakkas selle valemiga arvutatud indeks lihtsalt väljale mahtuma. struct TranslationBlock.

Muide, demo (praegu häguse litsentsiga) töötab hästi ainult Firefoxis. Chrome'i arendajad olid kuidagi pole valmis tõsiasjale, et keegi soovib luua rohkem kui tuhat WebAssembly mooduli eksemplari, nii et nad eraldasid igale gigabaidile virtuaalset aadressiruumi...

Praeguseks kõik. Võib-olla tuleb mõni teine ​​artikkel, kui kedagi huvitab. Nimelt jääb vähemalt ainult panna plokkseadmed tööle. Samuti võib olla mõttekas muuta WebAssembly moodulite koostamine asünkroonseks, nagu JS-maailmas kombeks, kuna ikka on olemas tõlk, mis suudab seda kõike teha seni, kuni algmoodul on valmis.

Lõpuks üks mõistatus: olete koostanud binaarfaili 32-bitise arhitektuuriga, kuid kood tõuseb mäluoperatsioonide kaudu Binaryenist, kuskilt pinust või mujalt 2-bitise aadressiruumi ülemisest 32 GB-st. Probleem on selles, et Binaryeni seisukohast on see juurdepääs liiga suurele tulemuseks olevale aadressile. Kuidas sellest mööda saada?

Administraatori viisil

Ma ei jõudnud seda testida, kuid mu esimene mõte oli: "Mis siis, kui installiksin 32-bitise Linuxi?" Seejärel hõivab aadressiruumi ülemise osa kernel. Ainus küsimus on selles, kui palju on hõivatud: 1 või 2 Gb.

Programmeerija moodi (praktikute valik)

Puhume aadressiruumi ülaosas mulli. Ma ise ei saa aru, miks see töötab - seal juba seal peab olema virn. Aga "me oleme praktikud: kõik töötab meie jaoks, kuid keegi ei tea, miks..."

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

... tõsi küll, et Valgrindiga ei ühildu, aga õnneks tõrjub Valgrind ise väga tõhusalt kõik sealt minema :)

Äkki keegi seletab paremini, kuidas see minu kood töötab...

Allikas: www.habr.com

Lisa kommentaar