QEMU.js: nun serioza kaj kun WASM

Iam mi decidis por amuzo pruvi la inversigeblecon de la procezo kaj lernu kiel generi JavaScript (pli precize, Asm.js) el maŝinkodo. QEMU estis elektita por la eksperimento, kaj iom da tempo poste artikolo estis skribita pri Habr. En la komentoj oni konsilis al mi refari la projekton en WebAssembly, kaj eĉ forlasi min preskaŭ finita Mi iel ne volis la projekton... La laboro daŭris, sed tre malrapide, kaj nun, lastatempe en tiu artikolo aperis komento pri la temo "Do kiel ĉio finiĝis?" Responde al mia detala respondo, mi aŭdis "Ĉi tio sonas kiel artikolo." Nu, se vi povas, estos artikolo. Eble iu trovos ĝin utila. De ĝi la leganto lernos kelkajn faktojn pri la dezajno de QEMU-kodgeneraciaj backends, same kiel kiel verki Ĝust-en-Tempo-kompililon por TTT-aplikaĵo.

taskoj

Ĉar mi jam lernis kiel "iel" porti QEMU al JavaScript, ĉi-foje oni decidis fari ĝin saĝe kaj ne ripeti malnovajn erarojn.

Eraro numero unu: branĉo de punkta liberigo

Mia unua eraro estis forkigi mian version de la kontraŭflua versio 2.4.1. Tiam ŝajnis al mi bona ideo: se punktoliberigo ekzistas, tiam ĝi verŝajne estas pli stabila ol simpla 2.4, kaj eĉ pli la branĉo. master. Kaj ĉar mi planis aldoni sufiĉe da miaj propraj cimoj, mi tute ne bezonis aliulojn. Verŝajne tiel rezultiĝis. Sed jen la afero: QEMU ne staras, kaj iam ili eĉ anoncis optimumigon de la generita kodo je 10 procentoj. "Jes, nun mi frostos," mi pensis kaj rompiĝis. Ĉi tie ni devas fari digresion: pro la unufadena naturo de QEMU.js kaj la fakto ke la origina QEMU ne implicas la foreston de plurfadenado (tio estas, la kapablo samtempe funkciigi plurajn senrilatajn kodvojojn, kaj ne nur "uzi ĉiujn kernojn") estas kritika por ĝi, la ĉefaj funkcioj de fadenoj mi devis "elŝalti ĝin" por povi voki de ekstere. Tio kreis kelkajn naturajn problemojn dum la fuzio. Tamen, la fakto ke iuj el la ŝanĝoj de la branĉo master, kun kiuj mi provis kunfandi mian kodon, estis ankaŭ ĉerizo elektitaj en la punktoeldono (kaj tial en mia branĉo) ankaŭ verŝajne ne estus aldoninta oportunon.

Ĝenerale, mi decidis, ke ankoraŭ havas sencon forĵeti la prototipon, malmunti ĝin por partoj kaj konstrui novan version de nulo bazita sur io pli freŝa kaj nun de master.

Eraro numero du: TLP-metodaro

Esence, ĉi tio ne estas eraro, ĝenerale, ĝi estas nur trajto krei projekton en kondiĉoj de kompleta miskompreno kaj "kien kaj kiel moviĝi?" kaj ĝenerale "ĉu ni atingos tien?" En ĉi tiuj kondiĉoj mallerta programado estis pravigita elekto, sed, nature, mi ne volis ripeti ĝin senbezone. Ĉi-foje mi volis fari tion saĝe: atomcommits, konsciaj kodŝanĝoj (kaj ne “kunligi hazardajn signojn ĝis ĝi kompilas (kun avertoj)”, kiel Linus Torvalds iam diris pri iu, laŭ Vikicitaro), ktp.

Eraro numero tri: eniri la akvon sen koni la vadejon

Mi ankoraŭ ne tute forigis ĉi tion, sed nun mi decidis tute ne sekvi la vojon de plej malgranda rezistado, kaj fari ĝin "kiel plenkreskulo", nome, skribi mian TCG-backend de nulo, por ne devi diri poste: "Jes, ĉi tio estas kompreneble, malrapide, sed mi ne povas kontroli ĉion - tiel estas skribita TCI..." Krome, ĉi tio komence ŝajnis evidenta solvo, ekde tiam Mi generas binaran kodon. Kiel ili diras, “Kant kolektiĝisу, sed ne tiu”: la kodo estas, kompreneble, duuma, sed kontrolo ne povas esti simple transdonita al ĝi - ĝi devas esti eksplicite puŝita en la retumilon por kompilo, rezultigante certan objekton el la JS-mondo, kiu ankoraŭ bezonas estu savita ie. Tamen, sur normalaj RISC-arkitekturoj, laŭ mia kompreno, tipa situacio estas la bezono eksplicite restarigi la instrukaĵkaŝmemoron por regenerita kodo - se ĉi tio ne estas tio, kion ni bezonas, tiam, ĉiukaze, ĝi estas proksima. Krome, de mia lasta provo, mi eksciis, ke kontrolo ne ŝajnas esti translokita al la mezo de la tradukbloko, do ni ne vere bezonas bajtokodon interpretitan de ia ofseto, kaj ni simple povas generi ĝin el la funkcio sur TB. .

Ili venis kaj piedbatis

Kvankam mi komencis reverki la kodon jam en julio, magia piedbato ŝteliris nerimarkite: kutime leteroj de GitHub alvenas kiel sciigoj pri respondoj al Problemoj kaj Pull-petoj, sed ĉi tie, subite mencio en fadeno Binaryen kiel qemu backend en la kunteksto, "Li faris ion tian, eble li diros ion." Ni parolis pri uzado de la rilata biblioteko de Emscripten Binaryen krei WASM JIT. Nu, mi diris, ke vi havas Apache 2.0-licencon tie, kaj QEMU entute estas distribuita sub GPLv2, kaj ili ne estas tre kongruaj. Subite montriĝis, ke permesilo povas esti ripari ĝin iel (Mi ne scias: eble ŝanĝu ĝin, eble duoblan licencon, eble ion alian...). Ĉi tio kompreneble ĝojigis min, ĉar tiam mi jam atente rigardis binara formato WebAssembly, kaj mi estis iel malĝoja kaj nekomprenebla. Ekzistis ankaŭ biblioteko kiu vorus la bazajn blokojn kun la transira grafeo, produktos la bajtkodon, kaj eĉ rulus ĝin en la interpretisto mem, se necese.

Tiam estis pli letero en la dissendolisto de QEMU, sed ĉi tio temas pli pri la demando, "Kiu tamen bezonas ĝin?" Kaj ĝi estas subite, montriĝis, ke ĝi estas necesa. Minimume, vi povas kunigi la jenajn uzeblecojn, se ĝi funkcias pli-malpli rapide:

  • lanĉi ion edukan sen ia ajn instalado
  • virtualigo en iOS, kie, laŭ onidiroj, la nura aplikaĵo, kiu havas la rajton kodigi surflue, estas JS-motoro (ĉu tio estas vera?)
  • pruvo de mini-OS - unu-disketo, enkonstruita, ĉia firmvaro, ktp...

Retumilo Runtime Trajtoj

Kiel mi jam diris, QEMU estas ligita al multfadenado, sed la retumilo ne havas ĝin. Nu, tio estas, ne... Komence ĝi tute ne ekzistis, poste aperis WebWorkers - laŭ mia kompreno, tio estas multfadenado bazita sur mesaĝo-pasado. sen komunaj variabloj. Nature, tio kreas signifajn problemojn dum portado de ekzistanta kodo bazita sur la komuna memormodelo. Tiam, sub publika premo, ĝi estis efektivigita sub la nomo SharedArrayBuffers. Ĝi estis iom post iom enkondukita, oni festis ĝian lanĉon en malsamaj retumiloj, poste oni festis la Novan Jaron, kaj poste Meltdown... Post kio oni alvenis al la konkludo, ke malglata aŭ kruda la tempomezurado, sed helpe de komuna memoro kaj fadeno pliigante la nombrilo, estas tute egale ĝi funkcios sufiĉe precize. Do ni malebligis multfadenadon kun komuna memoro. Ŝajnas, ke ili poste reŝaltis ĝin, sed, kiel evidentiĝis de la unua eksperimento, ekzistas vivo sen ĝi, kaj se jes, ni provos fari ĝin sen fidi je multfadenado.

La dua trajto estas la malebleco de malaltnivelaj manipuladoj kun la stako: oni ne povas simple preni, konservi la nunan kuntekston kaj ŝanĝi al nova kun nova stako. La voka stako estas administrita de la JS virtuala maŝino. Ŝajnus, kio estas la problemo, ĉar ni ankoraŭ decidis administri la antaŭajn fluojn tute permane? La fakto estas, ke bloko I/O en QEMU estas efektivigita per korutinoj, kaj ĉi tie estas kie malaltnivelaj stakmanipuladoj utilus. Feliĉe, Emscipten jam enhavas mekanismon por nesinkronaj operacioj, eĉ du: Sensincigi и Emperpreter. La unua funkcias per grava ŝvelaĵo en la generita JavaScript-kodo kaj ne plu estas subtenata. La dua estas la nuna "ĝusta maniero" kaj funkcias per bajtkoda generacio por la indiĝena interpretisto. Ĝi funkcias, kompreneble, malrapide, sed ĝi ne ŝvelas la kodon. Vere, subteno por korutinoj por ĉi tiu mekanismo devis esti kontribuita sendepende (jam estis korutinoj skribitaj por Asyncify kaj estis efektivigo de proksimume la sama API por Emterpreter, vi nur bezonis konekti ilin).

Nuntempe mi ankoraŭ ne sukcesis dividi la kodon en unu kompilitan en WASM kaj interpretitan per Emterpreter, do blokaj aparatoj ankoraŭ ne funkcias (vidu en la sekva serio, kiel oni diras...). Tio estas, finfine vi devus akiri ion kiel ĉi tiu amuza tavoligita afero:

  • interpretita bloko I/O. Nu, ĉu vi vere atendis emulitan NVMe kun denaska rendimento? 🙂
  • statike kompilita ĉefa QEMU-kodo (tradukisto, aliaj kopiitaj aparatoj, ktp.)
  • dinamike kompilita gastokodo en WASM

Trajtoj de QEMU-fontoj

Kiel vi verŝajne jam divenis, la kodo por kopii gastajn arkitekturojn kaj la kodon por generi maŝinajn instrukciojn estas apartigitaj en QEMU. Fakte, ĝi estas eĉ iom pli malfacila:

  • estas gastaj arkitekturoj
  • estas akceliloj, nome, KVM por aparatara virtualigo sur Linukso (por gasto kaj gastigaj sistemoj kongruaj unu kun la alia), TCG por JIT-kodgenerado ie ajn. Komencante kun QEMU 2.9, aperis subteno por la normo de virtualigo de aparataro HAXM en Vindozo (la detaloj)
  • se TCG estas uzita kaj ne hardvarvirtualigo, tiam ĝi havas apartan kodgeneracian subtenon por ĉiu gastiga arkitekturo, same kiel por la universala interpretisto.
  • ... kaj ĉirkaŭ ĉio ĉi - kopiitaj ekstercentraj, uzantinterfaco, migrado, rekord-reludado, ktp.

Cetere, ĉu vi sciis: QEMU povas kopii ne nur la tutan komputilon, sed ankaŭ la procesoron por aparta uzantprocezo en la gastiga kerno, kiu estas uzata, ekzemple, de la AFL fuzzer por binara instrumentado. Eble iu ŝatus porti ĉi tiun operacimanieron de QEMU al JS? 😉

Kiel la plej multaj longdaŭraj liberaj programoj, QEMU estas konstruita per la alvoko configure и make. Ni diru, ke vi decidas aldoni ion: TCG-backend, fadenefektivigo, io alia. Ne rapidu esti feliĉa/terurigita (substreku laŭeble) pro la perspektivo komuniki kun Autoconf - fakte, configure QEMU-oj estas ŝajne mem-verkitaj kaj ne estas generitaj de io ajn.

RetejoAsembleo

Do kio estas ĉi tiu afero nomata WebAssembly (alinome WASM)? Ĉi tio estas anstataŭaĵo por Asm.js, ne plu ŝajnigante esti valida JavaScript-kodo. Male, ĝi estas pure duuma kaj optimumigita, kaj eĉ simple skribi entjeron en ĝi ne estas tre simpla: por kompakteco, ĝi estas stokita en la formato. LEB128.

Vi eble aŭdis pri la relooping algoritmo por Asm.js - ĉi tio estas la restarigo de "altnivelaj" flukontrolaj instrukcioj (tio estas, se-tiam-alie, bukloj, ktp.), por kiuj JS-motoroj estas dezajnitaj, de la malaltnivela LLVM IR, pli proksime al la maŝinkodo efektivigita de la procesoro. Nature, la meza reprezento de QEMU estas pli proksima al la dua. Ŝajnus, ke jen ĝi estas, bajtokodo, la fino de la turmento... Kaj tiam estas blokoj, se-tiam-alie kaj bukloj!..

Kaj ĉi tio estas alia kialo, kial Binaryen estas utila: ĝi nature povas akcepti altnivelajn blokojn proksime al tio, kio estus konservita en WASM. Sed ĝi ankaŭ povas produkti kodon el grafikaĵo de bazaj blokoj kaj transiroj inter ili. Nu, mi jam diris, ke ĝi kaŝas la stokadformaton WebAssembly malantaŭ la oportuna C/C++ API.

TCG (Malgranda Koda Generatoro)

GCT estis origine backend por la kompililo C. Tiam, ŝajne, ĝi ne povis elteni la konkuradon kun GCC, sed finfine ĝi trovis sian lokon en QEMU kiel kodgenera mekanismo por la gastiga platformo. Ekzistas ankaŭ TCG-backend kiu generas iun abstraktan bajtkodon, kiu estas tuj ekzekutita de la interpretisto, sed mi decidis eviti uzi ĝin ĉi-foje. Tamen, la fakto, ke en QEMU jam eblas ebligi la transiron al la generita TB per la funkcio tcg_qemu_tb_exec, ĝi montriĝis tre utila por mi.

Por aldoni novan TCG-backend al QEMU, vi devas krei subdosierujon tcg/<имя архитектуры> (tiuokaze, tcg/binaryen), kaj ĝi enhavas du dosierojn: tcg-target.h и tcg-target.inc.c и preskribi temas pri ĉio configure. Vi povas meti aliajn dosierojn tie, sed, kiel vi povas supozi laŭ la nomoj de ĉi tiuj du, ili ambaŭ estos inkluzivitaj ie: unu kiel regula kapdosiero (ĝi estas inkluzivita en tcg/tcg.h, kaj tiu jam estas en aliaj dosieroj en la dosierujoj tcg, accel kaj ne nur), la alia - nur kiel kodpeceto enen tcg/tcg.c, sed ĝi havas aliron al siaj senmovaj funkcioj.

Decidante, ke mi pasigos tro da tempo por detalaj esploroj pri kiel ĝi funkcias, mi simple kopiis la "skeletojn" de ĉi tiuj du dosieroj de alia backend efektivigo, honeste indikante tion en la licenca kaplinio.

dosiero tcg-target.h enhavas ĉefe agordojn en la formo #define-s:

  • kiom da registroj kaj kia larĝo estas sur la cela arkitekturo (ni havas tiom da kiom ni volas, kiom da ni volas - la demando estas pli pri tio, kio estos generita en pli efikan kodon de la retumilo sur la "tute cela" arkitekturo). ...)
  • vicigo de gastigaj instrukcioj: ĉe x86, kaj eĉ en TCI, instrukcioj tute ne estas vicigitaj, sed mi enmetos en la kodbufron tute ne instrukciojn, sed montrilojn al Binaryen-bibliotekaj strukturoj, do mi diros: 4 bajtoj
  • kiajn laŭvolajn instrukciojn la backend povas generi - ni inkluzivas ĉion, kion ni trovas en Binaryen, lasu la akcelilon dividi la reston en pli simplajn mem
  • Kio estas la proksimuma grandeco de la TLB-kaŝmemoro petita de la backend. Fakto estas, ke en QEMU ĉio estas serioza: kvankam ekzistas helpaj funkcioj, kiuj plenumas ŝarĝon/stoki konsiderante la gaston MMU (kie ni estus nun sen ĝi?), ili konservas sian tradukkaŝmemoron en formo de strukturo, la kies prilaborado estas oportuna enigi rekte en elsendajn blokojn. La demando estas, kia ofseto en ĉi tiu strukturo estas plej efike prilaborita per malgranda kaj rapida sinsekvo de komandoj?
  • ĉi tie vi povas ĝustigi la celon de unu aŭ du rezervitaj registroj, ebligi voki TB per funkcio kaj laŭvole priskribi kelkajn malgrandajn inline-funkcioj kiel flush_icache_range (sed ĉi tio ne estas nia kazo)

dosiero tcg-target.inc.c, kompreneble, estas kutime multe pli granda en grandeco kaj enhavas plurajn devigajn funkciojn:

  • inicialigo, inkluzive de limigoj pri kiuj instrukcioj povas funkcii per kiuj operaciantoj. Evidente kopiite de mi de alia backend
  • funkcio kiu prenas unu internan bajtokodan instrukcion
  • Vi ankaŭ povas meti helpajn funkciojn ĉi tie, kaj vi ankaŭ povas uzi statikajn funkciojn de tcg/tcg.c

Mi elektis por mi la jenan strategion: en la unuaj vortoj de la sekva tradukbloko, mi notis kvar indikojn: startmarko (certa valoro en la ĉirkaŭaĵo). 0xFFFFFFFF, kiu determinis la nunan staton de la TB), kuntekston, generitan modulon kaj magian nombron por senararigado. Komence la marko estis metita enen 0xFFFFFFFF - nkie n - malgranda pozitiva nombro, kaj ĉiufoje kiam ĝi estis ekzekutita per la interpretisto ĝi pligrandiĝis je 1. Kiam ĝi atingis 0xFFFFFFFE, kompilo okazis, la modulo estis konservita en la funkciotabelo, importita en malgrandan "lanĉilon", en kiu ekzekuto iris de tcg_qemu_tb_exec, kaj la modulo estis forigita de QEMU-memoro.

Por parafrazi la klasikaĵojn, "Lambastonon, kiom estas interplektita en ĉi tiu sono por la koro de la proger...". Tamen la memoro likis ie. Plie, ĝi estis memoro administrita de QEMU! Mi havis kodon kiu, skribinte la sekvan instrukcion (nu, tio estas, montrilon), forigis tiun, kies ligilo estis en ĉi tiu loko pli frue, sed tio ne helpis. Efektive, en la plej simpla kazo, QEMU asignas memoron ĉe ekfunkciigo kaj skribas la generitan kodon tie. Kiam la bufro finiĝas, la kodo estas elĵetita kaj la sekva komencas esti skribita en sia loko.

Post studado de la kodo, mi rimarkis, ke la lertaĵo kun la magia nombro permesis al mi ne malsukcesi pri amasdetruo liberigante ion malĝustan sur nekomencigita bufro ĉe la unua paŝo. Sed kiu reverkas la bufron por preteriri mian funkcion poste? Kiel konsilas la programistoj de Emscripten, kiam mi renkontis problemon, mi portis la rezultan kodon reen al la denaska aplikaĵo, starigis sur ĝi Mozilla Record-Replay... Ĝenerale, finfine mi konstatis simplan aferon: por ĉiu bloko, a struct TranslationBlock kun ĝia priskribo. Divenu kie... Ĝuste, ĝuste antaŭ la bloko ĝuste en la bufro. Rimarkinte tion, mi decidis ĉesi uzi lambastonojn (almenaŭ kelkajn), kaj simple forĵetis la magian nombron, kaj transdonis la ceterajn vortojn al struct TranslationBlock, kreante unuope ligitan liston kiu povas esti rapide trapasita kiam la tradukkaŝmemoro estas rekomencigita, kaj liberigas memoron.

Kelkaj lambastonoj restas: ekzemple markitaj montriloj en la kodbufro - kelkaj el ili estas simple BinaryenExpressionRef, tio estas, ili rigardas la esprimojn kiuj devas esti lineare metitaj en la generitan bazan blokon, parto estas la kondiĉo por transiro inter BBoj, parto estas kien iri. Nu, jam estas pretaj blokoj por Relooper, kiuj devas esti konektitaj laŭ la kondiĉoj. Por distingi ilin, oni uzas la supozon, ke ili ĉiuj estas vicigitaj per almenaŭ kvar bajtoj, do vi povas sekure uzi la malplej signifajn du bitojn por la etikedo, vi nur devas memori forigi ĝin se necese. Cetere, tiaj etikedoj jam estas uzataj en QEMU por indiki la kialon por eliri la TCG-buklon.

Uzante Binaryen

Moduloj en WebAssembly enhavas funkciojn, ĉiu el kiuj enhavas korpon, kiu estas esprimo. Esprimoj estas unuraj kaj binaraj operacioj, blokoj konsistantaj el listoj de aliaj esprimoj, kontrolfluo ktp. Kiel mi jam diris, kontrolfluo ĉi tie estas organizita ĝuste kiel altnivelaj branĉoj, bukloj, funkciovokoj ktp. Argumentoj al funkcioj ne estas pasigitaj sur la stakon, sed eksplicite, same kiel en JS. Ekzistas ankaŭ tutmondaj variabloj, sed mi ne uzis ilin, do mi ne rakontos al vi pri ili.

Funkcioj ankaŭ havas lokajn variablojn, numeritajn de nulo, de tipo: int32 / int64 / float / double. En ĉi tiu kazo, la unuaj n lokaj variabloj estas la argumentoj pasitaj al la funkcio. Bonvolu noti, ke kvankam ĉio ĉi tie ne estas tute malaltnivela laŭ kontrolfluo, entjeroj ankoraŭ ne portas la "subskribitan/sensignitan" atributon: kiel la nombro kondutas dependas de la operacia kodo.

Ĝenerale parolante, Binaryen provizas simpla C-API: vi kreas modulon, en li krei esprimojn - unaria, duuma, blokoj de aliaj esprimoj, kontroli fluon, ktp. Tiam vi kreas funkcion kun esprimo kiel ĝia korpo. Se vi, kiel mi, havas malaltnivelan transiran grafikaĵon, la relooper-komponento helpos vin. Laŭ mia kompreno, eblas uzi altnivelan kontrolon de la ekzekutfluo en bloko, kondiĉe ke ĝi ne preterpasas la limojn de la bloko - tio estas, eblas fari internan rapidan vojon / malrapidan. vojo disbranĉiĝanta ene de la enkonstruita TLB-kaŝmemorkodo prilaborado, sed ne malhelpi la "eksteran" kontrolfluon. Kiam vi liberigas relooper, ĝiaj blokoj estas liberigitaj; kiam vi liberigas modulon, la esprimoj, funkcioj, ktp asignitaj al ĝi malaperas areno.

Tamen, se vi volas interpreti kodon sur la flugo sen nenecesa kreado kaj forigo de interpretisto-instanco, eble havas sencon meti ĉi tiun logikon en C++-dosieron, kaj de tie rekte administri la tutan C++ API de la biblioteko, preterirante preta- faris envolvaĵojn.

Do por generi la kodon, kiun vi bezonas

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

... se mi forgesis ion, pardonu, ĉi tio estas nur por reprezenti la skalon, kaj la detaloj estas en la dokumentado.

Kaj nun komenciĝas la krak-fex-pex, io kiel ĉi tio:

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

Por iel konekti la mondojn de QEMU kaj JS kaj samtempe rapide aliri la kompilitajn funkciojn, oni kreis tabelon (tabelo de funkcioj por importi en la lanĉilon), kaj la generitaj funkcioj estis metitaj tie. Por rapide kalkuli la indekson, la indekso de la nulvorta tradukbloko estis komence uzata kiel ĝi, sed tiam la indekso kalkulita per ĉi tiu formulo komencis simple kongrui en la kampo en struct TranslationBlock.

Por iu, demo (nuntempe kun malklara permesilo) nur bone funkcias en Firefox. Chrome programistoj estis iel ne preta al la fakto, ke iu volus krei pli ol mil okazojn de WebAssembly-moduloj, do ili simple asignis gigabajton da virtuala adresspaco por ĉiu...

Tio estas ĉio por nun. Eble estos alia artikolo se iu interesiĝas. Nome restas almenaŭ nur igi blokajn aparatojn funkcii. Eble ankaŭ havas sencon fari la kompilon de WebAssembly-moduloj nesinkrona, kiel kutimas en la JS-mondo, ĉar ankoraŭ ekzistas interpretisto, kiu povas fari ĉion ĉi ĝis la denaska modulo estas preta.

Fine enigmo: vi kompilis binaron sur 32-bita arkitekturo, sed la kodo, per memoroperacioj, grimpas de Binaryen, ie sur la stako, aŭ ie aliloke en la supraj 2 GB de la 32-bita adresspaco. La problemo estas, ke el la vidpunkto de Binaryen tio aliras tro grandan rezultan adreson. Kiel ĉirkaŭiri ĉi tion?

Laŭ la maniero de administranto

Mi ne finis provi ĉi tion, sed mia unua penso estis "Kio se mi instalus 32-bitan Linukso?" Tiam la supra parto de la adresspaco estos okupita de la kerno. La sola demando estas kiom estos okupita: 1 aŭ 2 Gb.

Laŭ la maniero de programisto (opcio por praktikistoj)

Ni krevu vezikon ĉe la supro de la adresspaco. Mi mem ne komprenas kial ĝi funkcias - tie jam devas esti stako. Sed "ni estas praktikantoj: ĉio funkcias por ni, sed neniu scias kial..."

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

... estas vero, ke ĝi ne kongruas kun Valgrind, sed, feliĉe, Valgrind mem tre efike forpuŝas ĉiujn el tie :)

Eble iu donos pli bonan klarigon pri kiel funkcias ĉi tiu mia kodo...

fonto: www.habr.com

Aldoni komenton