QEMU.js: orain serio eta WASMrekin

Bazen behin ondo pasatzea erabaki nuen prozesuaren itzulgarritasuna frogatzea eta ikasi JavaScript nola sortzen (zehatzago, Asm.js) makina-kodetik. QEMU aukeratu zuten esperimenturako, eta denbora pixka bat geroago Habr-i buruzko artikulu bat idatzi zen. Iruzkinetan WebAssembly-n proiektua berregiteko gomendatu zidaten, eta baita nire buruari uztea ere ia amaituta Nolabait ez nuen proiektua nahi... Lana aurrera zihoan, baina oso poliki, eta orain, duela gutxi artikulu horretan agertu zen duzu "Beraz, nola amaitu zen dena?" Nire erantzun zehatzari erantzunez, "Artikulu bat dirudi" entzun nuen. Tira, ahal baduzu, artikulu bat egongo da. Agian norbaitek erabilgarria irudituko dio. Bertatik irakurleak QEMU kodea sortzeko backend-en diseinuari buruzko datu batzuk ikasiko ditu, baita web aplikazio baterako Just-in-Time konpilatzailea nola idatzi ere.

zereginak

Jadanik QEMU JavaScript-era "nolabait" nola eramaten ikasi nuenez, oraingoan zentzuz egitea erabaki zen eta akats zaharrak ez errepikatzea.

Errore zenbaki bat: adarkatu puntu askapenetik

Nire lehenengo akatsa 2.4.1 upstream bertsiotik nire bertsioa bifurkatzea izan zen. Orduan ideia ona iruditu zitzaidan: puntu-oharra badago, seguruenik 2.4 sinplea baino egonkorragoa da, eta are gehiago adarra. master. Eta nire akatsen kopuru dezente gehitzeko asmoa nuenez, ez nuen inoren beharrik. Halaxe atera zen ziurrenik. Baina hona hemen gauza: QEMU ez dago geldirik, eta uneren batean sortutako kodearen optimizazioa ehuneko 10ean ere iragarri zuten. "Bai, orain izoztu egingo naiz", pentsatu nuen eta hautsi egin nuen. Hemen digresio bat egin behar dugu: QEMU.js-en hari bakarreko izaera dela eta eta jatorrizko QEMUk ez duela esan nahi multi-threading (hau da, aldi berean, zerikusirik ez duten hainbat kode-bide funtzionatzeko gaitasuna, eta "erabildu nukleo guztiak") ez ezik, funtsezkoa da horretarako, kanpotik deitu ahal izateko "atera" behar nituen harien funtzio nagusiak. Horrek arazo natural batzuk sortu zituen fusioan. Hala ere, izan ere, adar batetik aldaketa batzuk master, zeinekin nire kodea batzen saiatu nintzen, puntu-oharra (eta, beraz, nire adarrean), gainera, ziurrenik ez lukete erosotasunik gehituko.

Oro har, erabaki nuen oraindik zentzuzkoa dela prototipoa botatzea, piezak desmuntatzea eta bertsio berri bat hutsetik eraikitzea, zerbait freskoago batean oinarrituta eta orain. master.

Bigarren akatsa: TLP metodologia

Funtsean, hau ez da akats bat, orokorrean, proiektu bat sortzearen ezaugarri bat da, bai "nora eta nola mugitu?" eta, oro har, "lortuko al gara?" Baldintza hauetan programazio traketsa aukera justifikatua zen, baina, jakina, ez nuen alferrik errepikatu nahi. Oraingoan zentzuz egin nahi nuen: konpromezu atomikoak, kode kontzienteen aldaketak (eta ez β€œausazko karaktereak elkarrekin lotzea (abisuekin) konpilatu arte”, behin Linus Torvaldsek norbaiti buruz esan zuen bezala, Wikiquoten arabera), etab.

Hirugarren akatsa: gudea ezagutu gabe uretara sartzea

Oraindik ez naiz guztiz kendu hau, baina orain erresistentzia txikienaren bidetik ez jarraitzea erabaki dut, eta "hazien erara" egitea, hots, nire TCG backend hutsetik idaztea, ez izateko. geroago esan behar: "Bai, hau da, noski, poliki-poliki, baina ezin dut dena kontrolatu - horrela idatzita dago TCI..." Gainera, hasiera batean konponbide bistako bat zirudien, geroztik Kode bitarra sortzen dut. Diotenez, β€œGante bildu zenΡƒ, baina ez hori": kodea, noski, bitarra da, baina kontrola ezin zaio transferitu besterik gabe; berariaz nabigatzailean sartu behar da konpilaziorako, JS munduko objektu jakin bat sortuz, oraindik ere behar duena. nonbait gorde. Hala ere, RISC arkitektura arruntetan, ulertzen dudanez, egoera tipikoa da birsortutako kodearen instrukzio-cachea esplizituki berrezarri beharra - hau ez bada behar duguna, orduan, edonola ere, hurbil dago. Gainera, nire azken saiakeratik, kontrola ez omen dela itzulpen-blokearen erdialdera transferitzen ikasi nuen, beraz, ez dugu behar inolako desplazamendutik interpretatutako bytecoderik, eta TBko funtziotik sor dezakegu besterik gabe. .

Etorri eta ostikoka eman zuten

Uztailean kodea berridazten hasi nintzen arren, ostiko magiko bat sortu zen oharkabean: normalean GitHub-en gutunak Issues eta Pull eskaeren erantzunei buruzko jakinarazpen gisa iristen dira, baina hemen, bat-batean harian aipatu Binaryen qemu backend gisa testuinguruan, "Horrelako zerbait egin zuen, agian zerbait esango du". Emscripten-en erlazionatutako liburutegia erabiltzeaz ari ginen Binaryen WASM JIT sortzeko. Tira, esan nuen Apache 2.0 lizentzia daukazula han, eta QEMU oro har GPLv2 pean banatzen da, eta ez dira oso bateragarriak. Bat-batean, lizentzia bat izan daiteke konpondu nolabait (Ez dakit: agian aldatu, agian lizentzia bikoitza, agian beste zerbait...). Horrek, noski, poztu ninduen, ordurako jada ondo begiratua bainuen formatu bitarra WebAssembly, eta nolabait triste eta ulergaitza nintzen. Trantsizio grafikoarekin oinarrizko blokeak irensten zituen liburutegi bat ere bazegoen, bytecodea ekoizten zuena eta interpretatzailean bertan exekutatu ere, behar izanez gero.

Gero gehiago izan zen gutun bat QEMU posta-zerrendan, baina hau galderari buruz gehiago da: "Nork behar du hala ere?" Eta hala da bat-batean, beharrezkoa zela ikusi zen. Gutxienez, erabilera-aukera hauek bildu ditzakezu, azkarrago edo gutxiago funtzionatzen badu:

  • zerbait hezitzailea abiaraziz batere instalaziorik gabe
  • birtualizazioa iOS-en, non, zurrumurruen arabera, berehala kode sortzeko eskubidea duen aplikazio bakarra JS motorra den (egia al da?)
  • mini-OS-aren erakustaldia - diskete bakarrekoa, integratua, mota guztietako firmwareak, etab...

Arakatzailearen Runtime Ezaugarriak

Esan bezala, QEMU multithreading-ari lotuta dago, baina arakatzaileak ez du. Tira, hau da, ez... Hasieran ez zen batere existitzen, gero WebWorkers agertu zen - nik ulertzen dudanez, hau mezuak pasatzean oinarritutako multithreading da. aldagai partekaturik gabe. Jakina, horrek arazo handiak sortzen ditu memoria partekatuaren ereduan oinarrituta dagoen kodea portatzerakoan. Gero, publikoaren presiopean, izenpean ere ezarri zen SharedArrayBuffers. Pixkanaka sartzen joan zen, nabigatzaile ezberdinetan kaleratzea ospatu zuten, gero Urte Berria ospatu zuten, eta gero Meltdown... Horren ostean, denboraren neurketa lodia edo larria dela ondorioztatu zuten, baina memoria partekatuaren laguntzaz eta haria kontagailua handituz, berdin da nahiko zehaztasunez funtzionatuko du. Beraz, memoria partekatuarekin multithreading desgaitu genuen. Badirudi beranduago piztu zutela berriro, baina, lehen esperimentutik argi geratu zenez, hori gabe bizitza dago, eta hala bada, multithreading-ean fidatu gabe egiten saiatuko gara.

Bigarren ezaugarria pilarekin maila baxuko manipulazioak ezin izatea da: ezin duzu uneko testuingurua hartu, gorde eta pila berri batekin berri batera aldatu. Deien pila JS makina birtualak kudeatzen du. Badirudi, zein da arazoa, lehengo fluxuak guztiz eskuz kudeatzea erabaki genuen eta? Izan ere, bloke I/O QEMU-n koroutinen bidez inplementatzen da, eta hemen behe-mailako pila manipulazioak ondo etorriko lirateke. Zorionez, Emscipten-ek eragiketa asinkronoetarako mekanismo bat dauka dagoeneko, baita bi ere: Asintzikatu ΠΈ Enterpretea. Lehenengoak sortzen den JavaScript kodean bloat nabarmenaren bidez funtzionatzen du eta jada ez da onartzen. Bigarrena egungo "modu zuzena" da eta jatorrizko interpretearentzat bytecode sortzearen bidez funtzionatzen du. Poliki-poliki funtzionatzen du, noski, baina ez du kodea puzten. Egia da, mekanismo honen korutinen laguntza modu independentean lagundu behar zen (asyncify-rako idatzitako koroutinak zeuden jada eta gutxi gorabehera Emterpreter-erako API beraren inplementazioa zegoen, konektatu besterik ez zenituen behar).

Momentuz, oraindik ez dut lortu kodea WASM-en konpilatu eta Emterpreter erabiliz interpretatutako batean zatitzea, beraz, bloke-gailuek oraindik ez dute funtzionatzen (ikus hurrengo seriean, esaten den bezala...). Hau da, azkenean geruza dibertigarri honen antzeko zerbait lortu beharko zenuke:

  • interpretatutako bloke I/O. Beno, benetan espero al zenuen NVMe emulazioa jatorrizko errendimenduarekin? πŸ™‚
  • QEMU kode nagusia (itzultzailea, emulatutako beste gailu batzuk, etab.)
  • dinamikoki konpilatu gonbidatu kodea WASM-n

QEMU iturrien ezaugarriak

Ziurrenik dagoeneko asmatu duzun bezala, gonbidatuen arkitekturak emulatzeko kodea eta ostalari-makinaren argibideak sortzeko kodea bereizita daude QEMUn. Izan ere, apur bat zailagoa da:

  • arkitektura gonbidatuak daude
  • dago azeleragailuak, hots, Linux-en hardware birtualizaziorako KVM (elkarrekin bateragarriak diren sistema gonbidatuetarako eta ostalarietarako), TCG edozein lekutan JIT kodea sortzeko. QEMU 2.9-tik hasita, Windows-en HAXM hardware birtualizazio estandarraren laguntza agertu zen (xehetasunak)
  • TCG erabiltzen bada eta ez hardware birtualizazioa, orduan ostalari-arkitektura bakoitzerako kodea sortzeko laguntza bereizia du, baita interprete unibertsalarentzat ere.
  • ... eta honen guztiaren inguruan - periferiko emulatuak, erabiltzailearen interfazea, migrazioa, grabaketa-erreprodukzioa, etab.

Bide batez, ba al zenekien: QEMU-k ordenagailu osoa ez ezik, prozesadorea ere emulatu dezake ostalariaren nukleoan, adibidez, AFL fuzzer-ek tresneria bitarrako erabiltzen duen erabiltzaile-prozesu bereizi baterako. Agian norbaitek QEMUren funtzionamendu modu hau JSra eraman nahiko luke? πŸ˜‰

Aspaldiko software libre gehiena bezala, QEMU deialdiaren bidez eraikitzen da configure ΠΈ make. Demagun zerbait gehitzea erabakitzen duzula: TCG backend bat, haria inplementatzea, beste zerbait. Ez zaitez presarik pozik egoteko/izututa egoteko (azpimarratu behar den moduan) Autoconf-ekin komunikatzeko aukerarekin - hain zuzen ere, configure QEMU-a itxuraz berez idatzia da eta ez da ezertatik sortzen.

WebAssembly

Beraz, zer da WebAssembly (WASM) deitzen den gauza hau? Hau Asm.js-en ordezkoa da, jada ez du baliozko JavaScript kodea duen itxurarik. Aitzitik, bitarra eta optimizatua da, eta zenbaki oso bat idaztea ere ez da oso erraza: trinkotasuna lortzeko, formatuan gordetzen da. LEB128.

Baliteke Asm.js-ren birlooping algoritmoari buruz entzun izana - hau da "goi mailako" fluxu-kontroleko jarraibideen (hau da, if-then-else, begiztak, etab.) berreskuratzea da, horretarako JS motorrak diseinatuta daude. maila baxuko LLVM IR, prozesadoreak exekutatutako makina-kodetik gertuago. Jakina, QEMUren tarteko ordezkaritza bigarrenetik gertuago dago. Badirudi hemen dagoela, bytecode, oinazearen amaiera... Eta gero blokeak, if-then-else eta begiztak!...

Eta hau da Binaryen erabilgarria den beste arrazoi bat: modu naturalean onar ditzake WASM-en gordeko litzatekeenaren inguruko maila altuko blokeak. Baina oinarrizko blokeen eta haien arteko trantsizioen grafiko batetik kodea ere sor dezake. Beno, dagoeneko esan dut WebAssembly biltegiratze formatua ezkutatzen duela C/C++ API erosoaren atzean.

TCG (Tiny Code Generator)

gaztelaniaz zen jatorriz backend C konpiladorearentzat. Orduan, itxuraz, ezin izan zuen GCC-rekin lehiatu, baina azkenean QEMUn bere lekua aurkitu zuen ostalari plataformarako kodea sortzeko mekanismo gisa. Bytecode abstraktu batzuk sortzen dituen TCG backend bat ere badago, interpreteak berehala exekutatzen duena, baina oraingoan erabiltzea saihestea erabaki nuen. Hala ere, QEMUn jada posible dela funtzioaren bidez sortutako TBrako trantsizioa gaitzea tcg_qemu_tb_exec, oso erabilgarria iruditu zait.

QEMU-ra TCG backend berri bat gehitzeko, azpidirektorio bat sortu behar duzu tcg/<имя Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹> (kasu honetan, tcg/binaryen), eta bi fitxategi ditu: tcg-target.h ΠΈ tcg-target.inc.c ΠΈ erregistratu dena da configure. Bertan beste fitxategi batzuk jar ditzakezu, baina, bi hauen izenetatik asmatzen duzun bezala, biak sartuko dira nonbait: bat goiburuko fitxategi arrunt gisa (n sartzen da. tcg/tcg.h, eta hori jada direktorioetako beste fitxategi batzuetan dago tcg, accel eta ez bakarrik), bestea - kode zati gisa soilik tcg/tcg.c, baina bere funtzio estatikoetarako sarbidea du.

Funtzionamenduari buruzko ikerketa zehatzetan denbora gehiegi emango nuela erabakita, bi fitxategi horien "eskeletoak" beste backend inplementazio batetik kopiatu besterik ez nuen egin, lizentziaren goiburuan zintzotasunez adieraziz.

fitxategia tcg-target.h inprimakiko ezarpenak ditu nagusiki #define-s:

  • zenbat erregistro eta zer zabalera dauden xede-arkitekturan (nahi adina ditugu, nahi adina - galdera gehiago da arakatzaileak "erabat xede" arkitekturan kode eraginkorragoan sortuko duenari buruz? ...)
  • Ostalari-argibideen lerrokatzea: x86-n, eta baita TCI-n ere, instrukzioak ez daude batere lerrokatuta, baina kode-bufferean jarriko ditut instrukzioak ez, Binaryen liburutegi-egituretarako erakusleak baizik, beraz, esango dut: 4 byteak
  • backend-ak zer aukerako jarraibide sor ditzakeen - Binaryen-en aurkitzen dugun guztia sartzen dugu, azeleragailuak gainerakoa sinpleagoetan zatitzen dugu.
  • Zein da backend-ak eskatutako TLB cachearen gutxi gorabeherako tamaina. Kontua da QEMUn dena larria dela: gonbidatutako MMUa kontuan hartuta karga/biltegiratzea egiten duten laguntzaile funtzioak dauden arren (non egongo ginateke orain gabe?), beren itzulpen cachea egitura baten moduan gordetzen dute, horren prozesatzea komenigarria da zuzenean emisio-blokeetan txertatzeko. Galdera da, egitura honetan zein desplazamendu prozesatzen da modu eraginkorrenean komando-sekuentzia txiki eta azkar batek?
  • hemen erreserbatutako erregistro baten edo biren helburua moldatu dezakezu, TB deitzea funtzio baten bidez gaitu eta aukeran pare bat deskriba ditzakezu. inline- bezalako funtzioak flush_icache_range (baina hau ez da gure kasua)

fitxategia tcg-target.inc.c, noski, tamainaz askoz handiagoa izan ohi da eta derrigorrezko hainbat funtzio ditu:

  • hasieratzea, argibide zein eragigaitan funtziona dezaketen murrizketak barne. Nik garbiki kopiatu dut beste backend batetik
  • barne bytecode instrukzio bat hartzen duen funtzioa
  • Funtzio laguntzaileak ere jar ditzakezu hemen, eta funtzio estatikoak ere erabil ditzakezu tcg/tcg.c

Niretzat estrategia hau aukeratu nuen: hurrengo itzulpen blokeko lehen hitzetan lau erakusle idatzi nituen: hasierako marka (inguruko balio jakin bat 0xFFFFFFFF, TBren egungo egoera zehazten zuena), testuingurua, sortutako modulua eta arazketarako zenbaki magikoa. Hasieran marka jarri zen 0xFFFFFFFF - nNon n - zenbaki positibo txiki bat, eta interpretearen bidez exekutatzen zen bakoitzean 1 handitzen zen. Iritsi zenean 0xFFFFFFFE, konpilazioa egin zen, modulua funtzioen taulan gorde zen, "abiarazle" txiki batera inportatu zen, eta exekuzioa hasi zen. tcg_qemu_tb_exec, eta modulua QEMU memoriatik kendu zen.

Klasikoak parafraseatuz, β€œMakulu, zenbat uztartzen den soinu honetan progerren bihotzerako...”. Hala ere, oroimena nonbait isurtzen ari zen. Gainera, QEMUk kudeatutako memoria zen! Kode bat nuen, hurrengo instrukzioa idazterakoan (beno, hau da, erakuslea), lehen esteka leku honetan zegoena ezabatzen zuena, baina honek ez zuen lagundu. Egia esan, kasurik errazenean, QEMUk memoria esleitzen du abiaraztean eta sortutako kodea bertan idazten du. Buffera agortzen denean, kodea bota egiten da eta hurrengoa bere lekuan idazten hasten da.

Kodea aztertu ondoren, konturatu nintzen zenbaki magikoaren trikimailuak aukera ematen zidala piloaren suntsipenean huts egin ezean, hasieratu gabeko buffer batean oker zerbait askatuz lehen pasean. Baina nork berridazten du buffer-a gero nire funtzioa saihesteko? Emscripten-eko garatzaileek aholkatzen dutenez, arazoren bat topatzen nuenean, ondoriozko kodea jatorrizko aplikaziora eraman nuen berriro, Mozilla Record-Replay ezarri nuen... Oro har, azkenean gauza sinple batez konturatu nintzen: bloke bakoitzeko, a struct TranslationBlock bere deskribapenarekin. Asmatu non... Hori bai, bufferean dagoen blokea baino lehen. Horretaz konturatuta, makuluak erabiltzeari uztea erabaki nuen (batzuk behintzat), eta zenbaki magikoa bota, eta gainerako hitzak transferitu. struct TranslationBlock, itzulpen-cachea berrezartzen denean azkar zeharkatu daitekeen zerrenda bana sortuz, eta memoria askatu.

Makulu batzuk geratzen dira: adibidez, markatutako erakusleak kode-buferrean - horietako batzuk besterik ez dira BinaryenExpressionRef, hau da, sortutako oinarrizko blokean linealki jarri behar diren esamoldeak aztertzen dituzte, zati bat BBen arteko trantsiziorako baldintza da, zati bat nora joan. Bada, dagoeneko prest daude Relooper-erako blokeak, baldintzen arabera konektatu behar direnak. Horiek bereizteko, denak gutxienez lau bytez lerrokatuta daudela suposatzen da, beraz, etiketarako bi bit esanguratsuenak segurtasunez erabil ditzakezu, behar izanez gero kendu behar duzula gogoratu besterik ez duzu egin behar. Bide batez, QEMUn dagoeneko erabiltzen dira horrelako etiketak TCG begiztatik irteteko arrazoia adierazteko.

Binaryen erabiliz

WebAssembly-ko moduluek funtzioak dituzte, eta horietako bakoitzak gorputz bat dauka, hau da, adierazpen bat. Adierazpenak eragiketa unarioak eta bitarrak dira, beste adierazpen batzuen zerrendez osatutako blokeak, kontrol-fluxua, etab. Esan bezala, hemen kontrol-fluxua goi-mailako adar, begizta, funtzio-dei eta abar gisa antolatzen da. Funtzioen argudioak ez dira pilara pasatzen, esplizituki baizik, JSn bezala. Aldagai globalak ere badaude, baina ez ditut erabili, beraz, ez dizut horien berri emango.

Funtzioek ere aldagai lokalak dituzte, zerotik zenbakituta, motakoak: int32 / int64 / float / double. Kasu honetan, lehen n aldagai lokalak funtzioari emandako argumentuak dira. Kontuan izan hemen dena kontrol-fluxuari dagokionez guztiz maila baxua ez den arren, osokoek oraindik ez dutela "sinatua/sinatutakoa" atributua: zenbakiak nola jokatzen duen eragiketa-kodearen araberakoa da.

Oro har, Binaryenek eskaintzen du C-API sinplea: modulu bat sortzen duzu, bere baitan sortu esamoldeak - unarioak, bitarrak, beste esamolde batzuen blokeak, kontrolatu fluxua, etab. Ondoren, gorputz gisa adierazpen bat duen funtzio bat sortzen duzu. Zuk, ni bezala, maila baxuko trantsizio grafikoa baduzu, relooper osagaiak lagunduko dizu. Ulertzen dudanez, posible da bloke batean exekuzio-fluxuaren goi-mailako kontrola erabiltzea, betiere blokearen mugetatik haratago ez bada, hau da, barne bide azkarra / motela egitea posible da. TLB cache prozesatzeko kodea barnean adarkatzen da, baina "kanpoko" kontrol-fluxua oztopatzeko. Relooper bat askatzen duzunean, bere blokeak askatzen dira; modulu bat askatzen duzunean, esleitutako esamoldeak, funtzioak, etab. arena.

Hala ere, kodea berehala interpretatu nahi baduzu interprete-instantziarik alferrikako sortu eta ezabatu gabe, logika hau C++ fitxategi batean jartzea zentzuzkoa izan daiteke, eta hortik zuzenean liburutegiko C++ API osoa kudeatzea, prest utziz. bilgarriak egin.

Beraz, behar duzun kodea sortzeko

// Π½Π°ΡΡ‚Ρ€ΠΎΠΈΡ‚ΡŒ Π³Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ (ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠΌΠ΅Π½ΡΡ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠΌ)
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);

... zerbait ahaztu badut, barkatu, hau eskala irudikatzeko besterik ez da, eta xehetasunak dokumentazioan daude.

Eta orain crack-fex-pex hasten da, honelako zerbait:

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 eta JS munduak nolabait konektatzeko eta, aldi berean, konpilatutako funtzioetara azkar sartzeko, array bat sortu zen (abiarazlean inportatzeko funtzioen taula) eta sortutako funtzioak bertan kokatu ziren. Indizea azkar kalkulatzeko, zero hitzen itzulpen-blokearen indizea erabiltzen zen hasieran, baina gero formula honen bidez kalkulatutako indizea eremuan sartzen hasi zen. struct TranslationBlock.

Bide batez, demo (gaur egun lizentzia ilun batekin) Firefox-en bakarrik funtzionatzen du. Chrome garatzaileak ziren nolabait prest ez Norbaitek WebAssembly moduluen mila instantzia baino gehiago sortu nahi izateari, beraz, helbide birtualeko gigabyte bat esleitu besterik ez zuen bakoitzerako...

Hori da guztia oraingoz. Agian, beste artikulu bat egongo da norbait interesatuta badago. Alegia, geratzen da behintzat bakarrik bloke gailuak funtzionatzea. Zentzuzkoa izan liteke WebAssembly moduluen konpilazioa asinkrono bihurtzea ere, JS munduan ohikoa den bezala, oraindik badago hori guztia egin dezakeen interprete bat jatorrizko modulua prest egon arte.

Azkenik asmakizun bat: bitar bat konpilatu duzu 32 biteko arkitektura batean, baina kodea, memoria-eragiketen bidez, Binaryenetik igotzen da, non pila batean, edo 2 biteko helbide-espazioko goiko 32 GB-en beste nonbait. Arazoa da Binaryen-en ikuspuntutik hau ondoriozko helbide handiegia sartzea dela. Nola inguratu hau?

Administratzailearen moduan

Ez nuen hau probatzen amaitu, baina nire lehenengo pentsamendua izan zen: "Eta 32 biteko Linux instalatuko banu?" Ondoren, helbide-espazioaren goiko aldea kernelak okupatuko du. Galdera bakarra da zenbat okupatuko den: 1 edo 2 Gb.

Programatzaile baten moduan (praktikatzaileentzako aukera)

Egin dezagun burbuila bat helbide-espazioaren goiko aldean. Nik neuk ez dut ulertzen zergatik funtzionatzen duen - hor dagoeneko pila bat egon behar da. Baina β€œpraktikanteak gara: denak balio digu, baina inork ez daki zergatik...”

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

... egia da ez dela Valgrind-ekin bateragarria, baina, zorionez, Valgrindek berak oso eraginkortasunez bultzatzen ditu denak hortik :)

Agian norbaitek nire kode honek nola funtzionatzen duen azalduko du...

Iturria: www.habr.com

Gehitu iruzkin berria