Qemu.js bi piştgiriya JIT re: hûn hîn jî dikarin mince paşde bizivirînin

Çend sal berê Fabrice Bellard ji hêla jslinux ve hatî nivîsandin emulatorek PC-yê ye ku bi JavaScript-ê hatî nivîsandin. Piştî wê bi kêmanî zêdetir bû X86 virtual. Lê ew hemî, bi qasî ku ez dizanim, wergêr bûn, dema ku Qemu, ku pir berê ji hêla heman Fabrice Bellard ve hatî nivîsandin, û, belkî, her emulatorek nûjen a xwe-hurmetkar, berhevkirina koda mêvanê JIT-ê di koda pergala mêvandar de bikar tîne. Ji min re xuya bû ku ew dem bû ku ez peywira berevajî li gorî ya ku gerok çareser dikin bicîh bikim: berhevkirina koda makîneyê JIT di JavaScriptê de, ji bo ku portkirina Qemu herî mentiqî bû. Wusa dixuye ku çima Qemu, emulatorên hêsantir û bikarhêner-heval hene - heman VirtualBox, mînakî - hatî saz kirin û kar dike. Lê Qemu çend taybetmendiyên balkêş hene

  • çavkaniya vekirî
  • şiyana xebatê bêyî ajokerek kernelê
  • şiyana ku di moda wergêr de bixebite
  • piştgirî ji bo hejmareke mezin ji mîmariya mêvandar û mêvan

Di derbarê xala sêyemîn de, ez naha dikarim rave bikim ku bi rastî, di moda TCI de, ew ne talîmatên makîneya mêvan bixwe ne ku têne şîrove kirin, lê bytekoda ku ji wan hatî wergirtin, lê ev eslê xwe naguhezîne - da ku were çêkirin û xebitandin. Qemu li ser mîmariyek nû, heke hûn bextewar bin, Berhevkarek C bes e - nivîsandina jeneratorek kodê dikare were paşxistin.

Û naha, piştî du salan ku di wextê min ê vala de bi bêhnfirehiya koda çavkaniyê ya Qemu, prototîpek xebitandinê xuya bû, ku tê de hûn dikarin berê, mînakî, Kolibri OS-ê bimeşînin.

Emscripten çi ye

Naha, gelek berhevkar derketine, ku encama dawiya wan JavaScript e. Hin, mîna Type Script, bi eslê xwe armanc bû ku bibe awayê çêtirîn nivîsandina ji bo tevnê. Di heman demê de, Emscripten rêyek e ku meriv koda C an C++ ya heyî bigire û wê di formek gerok-xwendine de berhev bike. Li vê rûpelê Me gelek benderên bernameyên naskirî berhev kirine: virMînakî, hûn dikarin li PyPy binêrin - bi awayê, ew îdîa dikin ku jixwe JIT heye. Bi rastî, ne her bername dikare bi hêsanî were berhev kirin û di gerokek de were xebitandin - hejmarek hene taybetmendiyên, ya ku divê hûn pê ragirin, lêbelê, ji ber ku li ser heman rûpelê nivîsa "Emscripten dikare ji bo berhevkirina hema hema her tiştî were bikar anîn. cîtêgûherr Koda C/C++ ji JavaScriptê re". Ango, çend operasyon hene ku li gorî standardê tevgerên nediyar in, lê bi gelemperî li ser x86 dixebitin - mînakî, gihandina nelihevkirî ya guhêrbaran, ku bi gelemperî li ser hin mîmaran qedexe ye. Bi gelemperî , Qemu bernameyek cross-platformê ye û, min dixwest ez bawer bikim, û ew jixwe gelek tevgerên nediyar di xwe de nagire - wê bigire û berhev bike, dûv re hinekî bi JIT-ê bişoxilîne - û hûn qediyan! Lê ne ew e doz...

Pêşî biceribînin

Bi gelemperî, ez ne kesê yekem im ku bi ramana veguhestina Qemu ji JavaScript re tê. Li ser forumê ReactOS pirsek hate pirsîn gelo ev bi karanîna Emscripten gengaz bû. Berê jî, gotegot hebûn ku Fabrice Bellard ev bi kesane kir, lê me li ser jslinux-ê dipeyivî, ku, bi qasî ku ez dizanim, tenê hewldanek e ku meriv bi destan performansa têr di JS-ê de bi dest bixe, û ji sifirê ve hatî nivîsandin. Dûv re, Virtual x86 hate nivîsandin - jêderkên bêserûber ji bo wê hatin şandin, û, wekî ku hate gotin, "realîzma" mezintir a emûlasyonê ev gengaz kir ku SeaBIOS wekî firmware bikar bîne. Wekî din, bi kêmî ve yek hewildanek hebû ku Qemu bi karanîna Emscripten-ê veguhezîne - min hewl da ku wiya bikim socketpair, lê pêşkeftin, bi qasî ku ez fam dikim, cemidandibû.

Ji ber vê yekê, wusa dixuye, li vir çavkanî ne, li vir Emscripten e - wê bigirin û berhev bikin. Lê pirtûkxaneyên ku Qemu pê ve girêdayî ne û pirtûkxaneyên ku ew pirtûkxane pê ve girêdayî ne û hwd jî hene û yek ji wan jî ev e. libffi, ku glib pê ve girêdayî ye. Li ser Înternetê gotegot hebûn ku di berhevoka mezin a portxaneyên pirtûkxaneyên Emscripten de yek heye, lê bi rengekî dijwar bû ku bawer bikira: yekem, ne armanc bû ku ew bibe berhevkarek nû, ya duyemîn jî, ew pir nizm bû. pirtûkxaneyê ku tenê hilde, û berhev bike JS. Û ew ne tenê mijarek têkelên meclîsê ye - dibe ku, heke hûn wê bizivirînin, ji bo hin peymanên bangkirinê hûn dikarin argumanên pêwîst li ser stikê biafirînin û bêyî wan fonksiyonê bang bikin. Lê Emscripten tiştek xapînok e: ji bo ku koda çêkirî ji optimîzatora motora geroka JS-ê re nas xuya bike, hin hîle têne bikar anîn. Bi taybetî, bi navê relooping - jeneratorek kodê ku LLVM IR-ya wergirtî bi hin rêwerzên veguheztinê yên razber bikar tîne, hewl dide ku heke, lûf û hwd maqûl ji nû ve biafirîne. Baş e, argûman çawa derbasî fonksiyonê dibin? Bi xwezayî, wekî argumanên fonksiyonên JS-ê, yanî heke gengaz be, ne bi stikê re ye.

Di destpêkê de ramanek hebû ku meriv bi JS-ê re li şûna libffi binivîsim û ceribandinên standard bimeşînim, lê di dawiyê de ez tevlihev bûm ka meriv çawa pelên sernavê xwe çêdikim da ku ew bi koda heyî re bixebitin - ez dikarim çi bikim, wek ku ew dibêjin, "Ma kar ew qas tevlihev in "Ma em ew qas bêaqil in?" Min neçar ma ku libffi li mîmariyek din veguhezînim, da ku biaxivim - bi bextewarî, Emscripten hem makroyên ji bo kombûna xêzkirî (di Javascript de, erê - baş e, mîmarî çi dibe bila bibe, ji ber vê yekê komker be), hem jî şiyana xebitandina koda ku di firînê de hatî çêkirin heye. Bi gelemperî, piştî ku hindek dem bi perçeyên libffi yên girêdayî platformê ve mijûl bûm, min hin kodek berhevkar stend û di ceribandina yekem a ku ez pê re rû bi rû hatim de xebitîm. Bi şaşwaziya min, test serkeftî bû. Ji şehrezayiya xwe matmayî mam - ne henek e, ew ji destpêkirina yekem de xebitî - ez, hîn jî ji çavên xwe bawer nakim, ez çûm ku dîsa li koda encamê binihêrim, da ku binirxînim ku ez li ku derê dinê bikolim. Li vir ez ji bo cara duyemîn gêj bûm - tenê tiştê ku fonksiyona min kir ev bû ffi_call - ev bangek serketî ragihand. Ji xwe gazî nebû. Ji ber vê yekê min daxwaza xweya yekem a kişandinê şand, ku di testê de xeletiyek ku ji her xwendekarek Olîmpiyadê re diyar e rast kir - divê hejmarên rastîn wekî hev neyê berhev kirin a == b û heta çawa a - b < EPS - hûn jî hewce ne ku modulê bi bîr bînin, wekî din 0 dê bibe pir wekî 1/3... Bi gelemperî, min hin portek libffi peyda kir, ku ceribandinên herî hêsan derbas dike, û bi kîjan glib re ye berhev kirin - Min biryar da ku ew ê hewce be, ez ê paşê lê zêde bikim. Li pêş çavan, ez ê bibêjim ku, wekî ku derket holê, berhevkar fonksiyona libffi jî di koda paşîn de nexistiye.

Lê, wekî ku min berê jî got, hin tixûb hene, û di nav karanîna belaş a behreyên cihêreng ên nediyar de, taybetmendiyek ne xweştir veşartiye - JavaScript ji hêla sêwiranê ve bi bîranîna hevbeş piştgirî nade multithreading. Di prensîbê de, ev bi gelemperî dikare wekî ramanek baş jî were gotin, lê ne ji bo barkirina koda ku mîmariya wê bi mijarên C ve girêdayî ye. Bi gelemperî, Firefox bi piştgirîkirina xebatkarên hevbeş diceribîne, û Emscripten ji bo wan pêkanîna pthread heye, lê min nexwest ku ez pê ve girêdayî bim. Diviya bû ku min hêdî hêdî multithreading ji koda Qemu derxînim - ango, bibînim ku ew li ku derê dimeşin, laşê lûleya ku di vê mijarê de diqewime veguhezînim fonksiyonek cûda, û fonksiyonên weha yek bi yek ji lûleya sereke bang bikim.

Hewldana duyemîn

Di hin xalan de, eşkere bû ku pirsgirêk hîn jî li wir e, û ku bi rengekî bêkêmasî li dora kodê pelixandin dê negihîje xêrê. Encam: pêdivî ye ku em bi rengekî prosesa lêzêdekirina kêzikan bi rêkûpêk bikin. Ji ber vê yekê, guhertoya 2.4.1, ku wê demê teze bû, hate girtin (ne 2.5.0, ji ber ku, kî dizane, dê di guhertoya nû de xeletiyên ku hîn nehatine girtin hebin, û ez têra xwe xeletiyên xwe hene. ), û yekem tiştê ku ew bi ewlehî ji nû ve binivîsin bû thread-posix.c. Welê, ew qas ewledar e: heke kesek hewl da ku operasyonek ku rê li ber astengkirinê bigire, fonksiyonê tavilê hate gazî kirin abort() - bê guman, vê yekê bi yekcarî hemî pirsgirêkan çareser nekir, lê bi kêmanî ew ji wergirtina daneyên nehevgirtî bi bêdengî xweştir bû.

Bi gelemperî, vebijarkên Emscripten di barkirina koda JS de pir arîkar in -s ASSERTIONS=1 -s SAFE_HEAP=1 - ew hin celeb tevgerên nediyar digirin, wek bang li navnîşanek nelihevkirî (ya ku bi kodê re ji bo rêzikên tîpkirî yên mîna HEAP32[addr >> 2] = 1) an gazîkirina fonksiyonek bi hejmarek xelet a argumanan.

Bi awayê, xeletiyên lihevkirinê mijarek cûda ne. Wekî ku min berê jî got, Qemu ji bo hilberîna kodê TCI (wergêrê koda piçûk) paşnavek şîrovekarek "dejenere" heye, û ji bo avakirina Qemu li ser mîmariyek nû û bi rêve birin, heke hûn bextewar bin, berhevkarek C bes e. Peyvên sereke "eger tu bextewar bî". Ez bêbext bûm, û derket holê ku TCI dema ku bytekoda xwe pars dike gihandina nelihev bikar tîne. Ango, li ser hemî cûrbecûr ARM û mîmariyên din ên bi gihîştina pêdivî bi astê re, Qemu berhev dike ji ber ku wan piştgiriyek normal TCG heye ku koda xwemalî çêdike, lê gelo TCI dê li ser wan bixebite pirsek din e. Lêbelê, wekî ku derket holê, belgeya TCI bi eşkere tiştek bi vî rengî destnîşan kir. Wekî encamek, bangên fonksiyonê yên ji bo xwendina nelihevkirî li kodê hatin zêdekirin, ku li beşek din a Qemu hatin dîtin.

Heap hilweşandina

Wekî encamek, gihîştina nelihevkirî ya TCI-ê hate rast kirin, xelekek sereke hate afirandin ku di encamê de pêvajo, RCU û hin tiştên piçûk ên din tê gotin. Û ji ber vê yekê ez Qemu bi vebijarkê dest pê dikim -d exec,in_asm,out_asm, ku tê vê wateyê ku hûn hewce ne ku bibêjin ka kîjan blokên kodê têne darve kirin, û hem jî di dema weşanê de binivîsin ka koda mêvan çi bû, koda mêvandar bû çi (di vê rewşê de, bytecode). Ew dest pê dike, çend blokên wergerê pêk tîne, peyama xeletkirinê ya ku min hiştiye dinivîse ku RCU dê nuha dest pê bike û ... têk diçe abort() di hundurê fonksiyonek de free(). Bi tinekirina fonksiyonê free() Me karî fêhm bikin ku di serê bloka hep de, ya ku di heşt baytên pêşiya bîranîna veqetandî de ye, li şûna mezinahiya blokê an tiştek wusa, zibil hebû.

Hilweşîna girikê - çiqas xweş... Di rewşek wusa de, dermanek bikêr heye - ji heman çavkaniyan (heke gengaz be), binaryek xwemalî berhev bikin û di bin Valgrind de bimeşînin. Piştî demekê, binary amade bû. Ez wê bi heman vebijarkan dest pê dikim - ew di dema destpêkirinê de jî têk diçe, berî ku bi rastî bigihîje darvekirinê. Ew ne xweş e, bê guman - xuya ye, çavkanî tam ne yek bûn, ku ne ecêb e, ji ber ku mîhengê vebijarkên hinekî cûda peyda kir, lê min Valgrind heye - pêşî ez ê vê xeletiyê rast bikim, û dûv re, ger ez bextewar im , ya orîjînal dê xuya bibe. Ez heman tiştî di bin Valgrind de dimeşînim... Y-y-y, y-y-y, uh-uh, ew dest pê kir, di destpêkê de bi normalî derbas bû û berê xwe da xeletiya orîjînal bêyî ku yek hişyariyek li ser gihîştina bîranîna nerast, nebêje li ser ketinê. Jiyan, wekî ku ew dibêjin, ez ji bo vê yekê amade nekirim - bernameyek têkçûyî gava ku di bin Walgrind de hate destpêkirin têk diçe. Ew çi bû sir e. Hîpoteza min ev e ku carekê li dora fermana heyî piştî qezayek di dema destpêkirinê de, gdb kar nîşan da memset-a bi nîşandereke derbasdar bi kar an mmx, an xmm qeyd dike, wê hingê dibe ku ew celebek xeletiyek hevrêziyê bû, her çend hîn jî dijwar e ku meriv bawer bike.

Okay, Valgrind li vir alîkar nabîne. Û li vir tiştê herî nefret dest pê kir - her tişt xuya dike ku dest pê dike, lê ji ber sedemek bêkêmasî nenas ji ber bûyerek ku dikaribû bi mîlyonan rêwerzan berê biqewime têk diçe. Demek dirêj jî ne diyar bû ku meriv çawa nêzîk bibe. Di dawiyê de, ez hîn jî neçar bûm ku rûnim û debug bikim. Çapkirina tiştê ku sernivîs pê re ji nû ve hatî nivîsandin destnîşan kir ku ew ne wekî hejmarek xuya dike, lê ji ber vê yekê celebek daneya binary. Va ye, va ye, ev rêzika binaryê di pelê BIOS-ê de hate dîtin - ango, naha gengaz bû ku bi pêbaweriyek maqûl were gotin ku ew zêdebûnek tampon bû, û tewra diyar e ku ew li ser vê tamponê hatî nivîsandin. Welê, wê hingê tiştek bi vî rengî - di Emscripten de, bextewar, bêserûberkirina cîhê navnîşanê tune, tê de qul jî tune, ji ber vê yekê hûn dikarin li cîhek nîvê kodê binivîsin da ku ji destpêka paşîn ve daneyan bi nîşanker derxînin. li daneyan binihêrin, li nîşanderê binihêrin, û heke ew nehatibe guhertin, xwarinê ji bo ramanê bistînin. Rast e, piştî her guheztinê girêdanek du hûrdeman digire, lê hûn dikarin çi bikin? Wekî encamek, rêzek taybetî hate dîtin ku BIOS-ê ji tampona demkî ber bi bîranîna mêvan ve kopî kir - û, bi rastî, di tamponê de cîhek têr tune. Dîtina çavkaniya wê navnîşana tamponê ya xerîb bi fonksiyonek encam da qemu_anon_ram_alloc di dosyayê de oslib-posix.c - mantiqa li wir ev bû: carinan ew dikare kêrhatî be ku navnîşan li ser rûpelek mezin a bi mezinahiya 2 MB-ê ve girêbide, ji bo vê yekê em ê bipirsin mmap pêşî hinekî din, û paşê em ê zêde bi alîkariyê vegerînin munmap. Û heke lihevkirinek wusa ne hewce ye, wê hingê em ê li şûna 2 MB encamê destnîşan bikin getpagesize() - mmap ew ê dîsa jî navnîşanek lihevkirî bide... Ji ber vê yekê di Emscripten de mmap tenê bang dike malloc, lê bê guman ew li ser rûpelê li hev nake. Bi gelemperî, xeletiyek ku çend mehan ez xemgîn kirim bi guherînek ve hate rast kirin drav xetên.

Taybetmendiyên fonksiyonên bangkirinê

Û naha pêvajo tiştek dihejmêre, Qemu têk naçe, lê ekran venagere, û pêvajo zû diçe nav lûleyan, li gorî derketinê dadbar dike. -d exec,in_asm,out_asm. Hîpotezek derketiye holê: qutkirinên demjimêr (an, bi gelemperî, hemî qutkirin) nayên. Û bi rastî, heke hûn astengiyên ji meclîsa xwemalî, ku ji ber hin sedeman xebitîn, vekin, hûn wêneyek wusa distînin. Lê ev qet ne bersiv bû: berawirdkirina şopên ku bi vebijarka jorîn re hatine derxistin destnîşan kir ku rêgezên darvekirinê pir zû ji hev cuda bûne. Li vir divê were gotin ku berhevoka tiştê ku bi karanîna destan hatî tomar kirin emrun debugging encam bi derana meclîsa xwecihaxêv ne pêvajoyeke bi temamî mekanîk e. Ez bi rastî nizanim bernameyek ku di gerokek de dixebite bi çi ve girêdayî ye emrun, lê hin xêzên di hilberanê de ji nû ve têne rêz kirin, ji ber vê yekê cûdahiya cûdahî hîn ne sedemek e ku meriv texmîn bike ku rêgez ji hev cihê bûne. Bi gelemperî, eşkere bû ku li gorî rêwerzan ljmpl veguheztinek li navnîşanên cihêreng heye, û bytekoda ku hatî çêkirin bi bingehîn cûda ye: yek rêwerzek ji bo gazîkirina fonksiyonek alîkar heye, ya din na. Piştî googlêkirina talîmatan û xwendina koda ku van rêwerzan werdigerîne, eşkere bû ku, pêşî, yekser berî wê di qeydê de cr0 tomarek hate çêkirin - di heman demê de bi karanîna arîkarek jî - ku pêvajo veguhezand moda parastî, û ya duyemîn jî, ku guhertoya js qet neguherî moda parastî. Lê rastî ev e ku taybetmendiyek din a Emscripten nerazîbûna wê ye ku kodê wekî pêkanîna talîmatan tehemûl bike. call di TCI de, ku her nîşanek fonksiyonê di celebê de encam dide long long f(int arg0, .. int arg9) - Divê fonksiyon bi hejmara rast a argumanan re bêne gazî kirin. Ger ev rêgez were binpêkirin, li gorî mîhengên xeletkirinê ve girêdayî, bername dê an têkbiçe (ku baş e) an jî bi tevahî fonksiyona xelet bang bike (ya ku dê xeletiyek xemgîn be). Di heman demê de vebijarkek sêyemîn jî heye - nifşa pêçanên ku argumanan lê zêde dikin / jê dikin çalak bikin, lê bi tevahî van pêçan gelek cîh digirin, tevî vê yekê ku di rastiyê de ez tenê ji sed dorpêçan hewce dikim. Ev bi tena serê xwe pir xemgîn e, lê derket holê ku pirsgirêkek cidîtir heye: di koda hilberandî ya fonksiyonên wrapper de, argûman hatin veguheztin û veguheztin, lê carinan fonksiyona bi argumanên çêkirî nehat gazî kirin - baş e, mîna ku di pêkanîna libffi min. Yanî hin alîkar bi hêsanî nehatin îdamkirin.

Xweşbextane, Qemu di forma pelê sernavê de navnîşên arîkar ên ku ji makîneyê têne xwendin hene

DEF_HELPER_0(lock, void)
DEF_HELPER_0(unlock, void)
DEF_HELPER_3(write_eflags, void, env, tl, i32)

Ew pir xweş têne bikar anîn: yekem, makro bi awayê herî ecêb ji nû ve têne destnîşankirin DEF_HELPER_n, û paşê vedike helper.h. Heya ku makro di nav destpêkek avahiyek û komê de tê berfireh kirin, û dûv re rêzek tê destnîşankirin, û li şûna hêmanan - #include <helper.h> Wekî encamek, min di dawiyê de şansek hebû ku ez pirtûkxaneyê li ser xebatê biceribînim pyparsing, û skrîptek hate nivîsandin ku tam wan pêçanan ji bo fonksiyonên ku ji bo wan hewce ne diafirîne.

Û ji ber vê yekê, piştî wê pêvajoyê xuya bû ku kar dike. Wusa dixuye ku ji ber ku ekran çu carî nehat destpêkirin, her çend memtest86+ dikaribû di meclîsa xwecî de bixebite. Li vir pêdivî ye ku were zelal kirin ku koda I/O ya bloka Qemu bi tîpan tê nivîsandin. Emscripten pêkanîna xweya pir dijwar heye, lê dîsa jî pêdivî ye ku ew di koda Qemu de were piştgirî kirin, û hûn dikarin nuha pêvajoyê debug bikin: Qemu vebijarkan piştgirî dike -kernel, -initrd, -append, bi ku hûn dikarin Linux-ê an, mînakî, memtest86+-ê, bêyî ku qet amûrên blokê bikar bînin, boot bikin. Lê pirsgirêk li vir ev e: di meclîsa xwemalî de meriv dikare bi vebijarkê derana kernel Linux-ê li konsolê bibîne -nographic, û tu encamek ji gerokê heya termînalê ji cihê ku lê hatî destpêkirin tune emrun, nehat. Ango ne diyar e: pêvajo nexebite an jî derketina grafîkê nexebite. Û paşê hat bîra min ku ez hinekî bisekinim. Derket holê ku "prosesor ne xew e, lê bi tenê hêdî hêdî dibiriqe", û piştî pênc hûrdeman kernel komek peyam avêt ser konsolê û daliqandin berdewam kir. Eşkere bû ku pêvajo, bi gelemperî, dixebite, û pêdivî ye ku em kodê ji bo xebata bi SDL2 re bikolin. Mixabin, ez nizanim meriv vê pirtûkxaneyê çawa bikar tîne, ji ber vê yekê li hin deveran neçar ma ku ez bi rasthatinî tevbigerim. Di hin xalan de, xeta paralel0 li ser dîmenderek şîn dibiriqe, ku hin ramanan pêşniyar dike. Di dawiyê de, derket holê ku pirsgirêk ev bû ku Qemu di yek pencereyek laşî de gelek pencereyên virtual vedike, di navbera wan de hûn dikarin bi karanîna Ctrl-Alt-n veguherînin: ew di avakirina xwemalî de dixebite, lê ne di Emscripten de. Piştî ku ji pencereyên nehewce xilas bibin bi karanîna vebijarkan -monitor none -parallel none -serial none û talîmatên ku bi zorê li ser her çarçoveyek tevahî ekranê ji nû ve xêz bikin, her tişt ji nişkê ve xebitî.

Coroutines

Ji ber vê yekê, emulation di gerokê de dixebite, lê hûn nekarin tiştek yek-floppyek balkêş tê de bimeşînin, ji ber ku bloka I/O tune - hûn hewce ne ku piştgirî ji bo korutînan bicîh bikin. Qemu jixwe gelek paşnavên kortînî hene, lê ji ber cewherê JavaScript û çêkerê kodê Emscripten, hûn nekarin tenê dest bi kolandina stûnan bikin. Wusa dixuye ku "her tişt çûye, çîp tê rakirin", lê pêşdebirên Emscripten berê xwe dane her tiştî. Ev pir bikêrhatî tê pêkanîn: em bangek fonksiyonek bi vî rengî gumanbar bi nav bikin emscripten_sleep û çend kesên din ku mekanîzmaya Asyncify bikar tînin, û her weha bangên nîşanker û bang li her fonksiyonê dikin ku yek ji her du rewşên berê dibe ku di binê stikê de çêbibe. Û naha, berî her bangek gumanbar, em ê çarçoveyek asynchron hilbijêrin, û tavilê piştî bangê, em ê kontrol bikin ka bangek asynchron çê bûye, û heke hebe, em ê hemî guhêrbarên herêmî di vê çarçoweya asynchron de hilînin, destnîşan bikin ka kîjan fonksiyonê ji bo veguheztina kontrolê dema ku em hewce ne ku darvekirinê bidomînin, û ji fonksiyona heyî derkevin. Li vir cîhê lêkolîna bandorê heye xerakirin - ji bo hewcedariyên berdewamkirina înfaza kodê piştî vegera ji bangek asynkron, berhevkar "stûkên" fonksiyonê ku piştî bangek gumanbar dest pê dike diafirîne - bi vî rengî: heke n bangên gumanbar hebin, wê hingê fonksiyon dê li cîhek n/2 were berfireh kirin car - ev hîn jî ye, heke na Ji bîr mekin ku piştî her bangek potansiyel asynkron, hûn hewce ne ku hilanîna hin guherbarên herêmî li fonksiyona orjînal zêde bikin. Dûv re, min tewra neçar kir ku di Python de skrîptek hêsan binivîsim, ku, li ser bingeha komek fonksiyonên taybetî yên zêde-bikaranîn ên ku qaşo "nahêlin ku asynkronî di nav xwe de derbas bibe" (ango, pêşkeftina stûyê û her tiştê ku min tenê diyar kir, ne di wan de bixebitin), bangên bi nîşankeran destnîşan dike ku tê de fonksiyonên divê ji hêla berhevker ve werin paşguh kirin da ku ev fonksiyon asynkron neyên hesibandin. Û paşê pelên JS yên di bin 60 MB-ê de eşkere pir zêde ne - em bibêjin bi kêmanî 30. Her çend, carekê min skrîptek meclîsê saz dikir, û bi xeletî vebijarkên girêdanê avêtin, ku di nav wan de bû -O3. Ez koda çêkirî dimeşînim, û Chromium bîranînê dixwe û têk diçe. Dûv re min bi xeletî li tiştê ku wî dixwest dakêşîne nihêrî... Belê, ez çi bibêjim, ger ji min were xwestin ku ez bi ramanî li Javascriptek 500+ MB bixwînim û xweşbîn bikim, ez ê çi bibêjim.

Mixabin, kontrolên di koda pirtûkxaneya piştevaniya Asyncify de bi tevahî ne dostane bûn longjmp-yên ku di koda pêvajoya virtual de têne bikar anîn, lê piştî pişkek piçûk a ku van kontrolan neçalak dike û bi zorê şert û mercên wekî ku her tişt baş e sererast dike, kod xebitî. Û dûv re tiştek ecêb dest pê kir: carinan kontrolên koda hevdengkirinê dihatin dest pê kirin - heman ên ku kodê dişkînin ger, li gorî mantiqa darvekirinê, ew were asteng kirin - kesek hewl da ku mutexek jixwe hatî girtin bigire. Xwezî, ev derket holê ku di koda serialîzekirî de ne pirsgirêkek mentiqî ye - min bi tenê fonksiyona lûleya sereke ya standard ya ku ji hêla Emscripten ve hatî peyda kirin bikar tîne, lê carinan banga asynchronous bi tevahî stikê vedike, û di wê gavê de ew ê têk biçe. setTimeout ji lûleya sereke - bi vî rengî, kod bêyî ku ji dubarekirina berê derkeve, ket nav dubareya lûleya sereke. Li ser xelekek bêdawî ji nû ve nivîsand û emscripten_sleep, û pirsgirêkên mutexes rawestandin. Kod hîna maqûltir bûye - her tiştî, bi rastî, kodek min tune ku çarçoweya anîmasyona din amade dike - pêvajo tenê tiştek hesab dike û ekran bi periyodîk tê nûve kirin. Lêbelê, pirsgirêk li wir nesekinîn: carinan darvekirina Qemu bi tenê bê îstîsna an xeletî bi bêdengî bi dawî bû. Wê gavê min dev ji wê berda, lê, li pêş çavan, ez ê bibêjim ku pirsgirêk ev bû: koda korûtîn, bi rastî, nayê bikar anîn. setTimeout (an bi kêmanî ne bi qasî ku hûn difikirin): fonksiyon emscripten_yield bi tenê ala banga asînkron saz dike. Hemû xal ew e emscripten_coroutine_next ne fonksiyonek asynkron e: di hundurê xwe de ew ala kontrol dike, ji nû ve vediguhezîne û kontrolê vediguhezîne cîhê ku hewce dike. Ango teşwîqkirina stûyê li wir diqede. Pirsgirêk ev bû ku ji ber karanîna-piştî-belaş, ya ku dema ku hewza korutînê hate neçalak kirin xuya bû ji ber vê yekê ku min rêzek girîng a kodê ji paşîna korûnê ya heyî kopî nekir, fonksiyonê. qemu_in_coroutine rast vegeriya dema ku di rastiyê de divê ew derew vegere. Ev bû sedema bangekê emscripten_yield, ku li jora wê tu kes li ser stûyê tune bû emscripten_coroutine_next, stûn heta jor vebû, lê na setTimeout, wekî min berê jî got, nehat pêşandan.

Çêkirina koda JavaScriptê

Û li vir, bi rastî, soza "vegerandina goştê hûrkirî" ye. Ne rast. Bê guman, heke em Qemu di gerokê de, û Node.js tê de bimeşînin, wê hingê, bi xwezayî, piştî afirandina kodê li Qemu em ê JavaScript-ê bi tevahî xelet bistînin. Lê dîsa jî, celebek veguherînek berevajî.

Pêşîn, hinekî li ser ka Qemu çawa dixebite. Ji kerema xwe min tavilê bibore: Ez ne pêşdebirek Qemu profesyonel im û dibe ku encamên min li hin deveran xelet bin. Wekî ku ew dibêjin, "raya xwendekar ne hewce ye ku bi ramana mamoste, axiomatîk û hişmendiya hevpar a Peano re li hev be." Qemu xwedan hejmarek mîmariya mêvanên piştgirî hene û ji bo her yekê pelrêçek mîna heye target-i386. Dema ku hûn ava dikin, hûn dikarin ji bo çend mîmarên mêvan piştgirî diyar bikin, lê encam dê tenê çend binar be. Koda ku piştgirî dide mîmariya mêvan, di encamê de, hin operasyonên Qemu yên hundurîn çêdike, ku TCG (Generatora Kodê ya Biçûk) jixwe ji bo mîmariya mêvandar vediguhere koda makîneyê. Wekî ku di pelê readme de ku di pelrêça tcg-ê de hatî destnîşan kirin, ev bi eslê xwe beşek ji berhevkarek C-ya birêkûpêk bû, ku paşê ji bo JIT-ê hate adapte kirin. Ji ber vê yekê, wek nimûne, mîmariya armanc di çarçoveya vê belgeyê de êdî ne mîmariya mêvan e, lê mîmariya mêvandar e. Di deverekê de, hêmanek din xuya bû - Tiny Code Interpreter (TCI), ku di nebûna jeneratorek kodê de ji bo mîmariya mêvandarek taybetî divê kodê (hema hema heman operasyonên hundurîn) bicîh bîne. Bi rastî, wekî ku belgeya wê diyar dike, dibe ku ev wergêr her gav ne wekî hilberînerek kodê JIT-ê, ne tenê ji hêla lezê ve, lê di heman demê de ji hêla kalîteyê ve jî baş tevbigere. Her çend ez ne bawer im ku şiroveya wî bi tevahî têkildar be.

Di destpêkê de min hewl da ku ez piştgiriyek TCG-ya bêkêmasî çêkim, lê zû di koda çavkaniyê û ravekirinek ne bi tevahî zelal a rêwerzên bytecode de tevlihev bûm, ji ber vê yekê min biryar da ku wergêra TCI-ê pêça. Vê yekê çend avantaj dan:

  • dema ku hûn jeneratorek kodê bicîh dikin, hûn dikarin ne li danasîna talîmatan, lê li koda werger binêrin
  • hûn dikarin fonksiyonan ne ji bo her bloka wergera ku tê dîtin, lê, wekî mînak, tenê piştî darvekirina sedemîn biafirînin.
  • heke koda hatî hilberandin biguhezîne (û ev dixuye ku mimkun e, li gorî fonksiyonên bi navên ku peyva patchê vedihewînin dadbar bikin), ez ê hewce bikim ku koda JS-ya hatî hilberandin betal bikim, lê bi kêmanî ez ê tiştek hebe ku wê ji nû ve çêbikim.

Di derbarê xala sêyemîn de, ez ne bawer im ku piştî ku kod ji bo cara yekem hatî darve kirin, patchkirin gengaz e, lê du xalên yekem bes in.

Di destpêkê de, kod di forma veguhezek mezin de li navnîşana rêwerznameya bytecode ya orîjînal hate hilberandin, lê dûv re, ku gotara li ser Emscripten, xweşbînkirina JS-ya hatî hilberandin û veguheztina bibîranîn, min biryar da ku ez bêtir kodek mirovî biafirînim, nemaze ji ber ku ew bi ezmûnî derket holê ku yekane xala têketina bloka wergerê Destpêka wê ye. Dûv re negot, piştî demekê me jeneratorek kodê hebû ku kodek bi if-an çêdikir (her çend bê loop). Lê şansê xirab, ew têk çû, peyamek da ku rêwerzên hin dirêjahiya nerast bûn. Wekî din, talîmata dawî ya di vê asta vegerê de bû brcond. Baş e, ez ê li nifşê vê rêwerzê berî û piştî bangewaziya vegerê kontrolek heman rengî lê zêde bikim û... yek ji wan nehat darvekirin, lê piştî veguheztina piştrastkirinê ew dîsa jî têk çûn. Di dawiyê de, piştî xwendina koda hilberandî, min fêm kir ku piştî veguheztinê, nîşana fermana heyî ji stikê ji nû ve tê barkirin û dibe ku ji hêla koda JavaScript-a hatî çêkirin ve were nivîsandin. Û wisa derket holê. Zêdekirina tamponê ji yek megabyte ber bi deh ve rê neda tiştek, û eşkere bû ku jeneratorê kodê di nav doran de dimeşe. Diviyabû ku em kontrol bikin ku em ji sînorên TBya heyî derneketine, û heke me kir, wê hingê navnîşana TB-ya din bi nîşana minusê derxînin da ku em karibin darvekirinê bidomînin. Wekî din, ev pirsgirêk çareser dike "heke ev perçeya bytekodê guherî divê kîjan fonksiyonên hatî hilberandin were betal kirin?" - tenê fonksiyona ku bi vê bloka wergerê re têkildar e pêdivî ye ku were betal kirin. Bi awayê, her çend min her tişt di Chromiumê de xelet kir (ji ber ku ez Firefox bikar tînim û ji min re hêsantir e ku ez gerokek cihêreng ji bo ceribandinan bikar bînim), Firefox ji min re bû alîkar ku nelihevhatinên bi standarda asm.js re rast bikim, piştî ku kodê di nav de zûtir dest bi xebatê kir. Chromium.

Mînaka koda hatî çêkirin

Compiling 0x15b46d0:
CompiledTB[0x015b46d0] = function(stdlib, ffi, heap) {
"use asm";
var HEAP8 = new stdlib.Int8Array(heap);
var HEAP16 = new stdlib.Int16Array(heap);
var HEAP32 = new stdlib.Int32Array(heap);
var HEAPU8 = new stdlib.Uint8Array(heap);
var HEAPU16 = new stdlib.Uint16Array(heap);
var HEAPU32 = new stdlib.Uint32Array(heap);

var dynCall_iiiiiiiiiii = ffi.dynCall_iiiiiiiiiii;
var getTempRet0 = ffi.getTempRet0;
var badAlignment = ffi.badAlignment;
var _i64Add = ffi._i64Add;
var _i64Subtract = ffi._i64Subtract;
var Math_imul = ffi.Math_imul;
var _mul_unsigned_long_long = ffi._mul_unsigned_long_long;
var execute_if_compiled = ffi.execute_if_compiled;
var getThrew = ffi.getThrew;
var abort = ffi.abort;
var qemu_ld_ub = ffi.qemu_ld_ub;
var qemu_ld_leuw = ffi.qemu_ld_leuw;
var qemu_ld_leul = ffi.qemu_ld_leul;
var qemu_ld_beuw = ffi.qemu_ld_beuw;
var qemu_ld_beul = ffi.qemu_ld_beul;
var qemu_ld_beq = ffi.qemu_ld_beq;
var qemu_ld_leq = ffi.qemu_ld_leq;
var qemu_st_b = ffi.qemu_st_b;
var qemu_st_lew = ffi.qemu_st_lew;
var qemu_st_lel = ffi.qemu_st_lel;
var qemu_st_bew = ffi.qemu_st_bew;
var qemu_st_bel = ffi.qemu_st_bel;
var qemu_st_leq = ffi.qemu_st_leq;
var qemu_st_beq = ffi.qemu_st_beq;

function tb_fun(tb_ptr, env, sp_value, depth) {
  tb_ptr = tb_ptr|0;
  env = env|0;
  sp_value = sp_value|0;
  depth = depth|0;
  var u0 = 0, u1 = 0, u2 = 0, u3 = 0, result = 0;
  var r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0;
  var r10 = 0, r11 = 0, r12 = 0, r13 = 0, r14 = 0, r15 = 0, r16 = 0, r17 = 0, r18 = 0, r19 = 0;
  var r20 = 0, r21 = 0, r22 = 0, r23 = 0, r24 = 0, r25 = 0, r26 = 0, r27 = 0, r28 = 0, r29 = 0;
  var r30 = 0, r31 = 0, r41 = 0, r42 = 0, r43 = 0, r44 = 0;
    r14 = env|0;
    r15 = sp_value|0;
  START: do {
    r0 = HEAPU32[((r14 + (-4))|0) >> 2] | 0;
    r42 = 0;
    result = ((r0|0) != (r42|0))|0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445321] = r14;
    if(result|0) {
    HEAPU32[1445322] = r15;
    return 0x0345bf93|0;
    }
    r0 = HEAPU32[((r14 + (16))|0) >> 2] | 0;
    r42 = 8;
    r0 = ((r0|0) - (r42|0))|0;
    HEAPU32[(r14 + (16)) >> 2] = r0;
    r1 = 8;
    HEAPU32[(r14 + (44)) >> 2] = r1;
    r1 = r0|0;
    HEAPU32[(r14 + (40)) >> 2] = r1;
    r42 = 4;
    r0 = ((r0|0) + (r42|0))|0;
    r2 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    HEAPU32[1445309] = r2;
    HEAPU32[1445321] = r14;
    HEAPU32[1445322] = r15;
    qemu_st_lel(env|0, r0|0, r2|0, 34, 22759218);
if(getThrew() | 0) abort();
    r0 = 3241038392;
    HEAPU32[1445307] = r0;
    r0 = qemu_ld_leul(env|0, r0|0, 34, 22759233)|0;
if(getThrew() | 0) abort();
    HEAPU32[(r14 + (24)) >> 2] = r0;
    r1 = HEAPU32[((r14 + (12))|0) >> 2] | 0;
    r2 = HEAPU32[((r14 + (40))|0) >> 2] | 0;
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    HEAPU32[1445309] = r2;
    qemu_st_lel(env|0, r2|0, r1|0, 34, 22759265);
if(getThrew() | 0) abort();
    r0 = HEAPU32[((r14 + (24))|0) >> 2] | 0;
    HEAPU32[(r14 + (40)) >> 2] = r0;
    r1 = 24;
    HEAPU32[(r14 + (52)) >> 2] = r1;
    r42 = 0;
    result = ((r0|0) == (r42|0))|0;
    if(result|0) {
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    }
    HEAPU32[1445307] = r0;
    HEAPU32[1445308] = r1;
    return execute_if_compiled(22759392|0, env|0, sp_value|0, depth|0) | 0;
    return execute_if_compiled(23164080|0, env|0, sp_value|0, depth|0) | 0;
    break;
  } while(1); abort(); return 0|0;
}
return {tb_fun: tb_fun};
}(window, CompilerFFI, Module.buffer)["tb_fun"]

encamê

Ji ber vê yekê, kar hîn jî neqediyaye, lê ez westiyam ku bi dizî vê avakirina dirêj-dem-dirêj bigihînim kamilbûnê. Ji ber vê yekê, min biryar da ku tiştê ku ji bo niha heye biweşînim. Kod li cîhan piçek tirsnak e, ji ber ku ev ceribandinek e, û ji berê de ne diyar e ka çi divê were kirin. Belkî, wê hingê hêja ye ku li ser hin guhertoyên nûjen ên Qemu, peymanên atomî yên normal werin derxistin. Di vê navberê de, di forma blogê de di Gita de mijarek heye: ji bo her "asta" ku bi kêmanî bi rengek derbas bûye, şîroveyek berfireh bi rûsî hatiye zêdekirin. Bi rastî, ev gotar bi rêjeyek mezin vebêja encamê ye git log.

Hûn dikarin hemî biceribînin vir (ji trafîkê haydar bin).

Ya ku jixwe dixebite:

  • x86 virtual processor dixebite
  • Ji koda makîneyê heya JavaScriptê prototîpek xebatê ya hilberînerek koda JIT-ê heye
  • Şablonek ji bo berhevkirina mîmarên mêvanên din ên 32-bit heye: niha hûn dikarin Linux-ê ji bo mîmariya MIPS-ê ku di qonaxa barkirinê de di gerokê de dicemidîne heyranê xwe bikin.

Wekî din hûn dikarin çi bikin

  • Emûlasyonê lez bikin. Tewra di moda JIT-ê de xuya dike ku ji Virtual x86 hêdîtir dimeşe (lê potansiyel heye Qemuyek tevahî ku bi gelek hardware û mîmariya emûlkirî heye)
  • Ji bo çêkirina têkiliyek normal - bi eşkere, ez ne pêşdebirek malperê baş im, ji ber vê yekê heya niha min şêlê standard Emscripten bi qasî ku ji destê min tê ji nû ve çêkiriye
  • Biceribînin ku fonksiyonên Qemu tevlihevtir bidin destpêkirin - torê, koçkirina VM, hwd.
  • UPD: hûn ê hewce bikin ku çend pêşkeftin û raporên xeletiyên xwe ji Emscripten jorîn re bişînin, wekî ku dergevanên berê yên Qemu û projeyên din kirin. Spas ji wan re ku karîbûn bi nepenî tevkariya xwe ya ji bo Emscripten wekî beşek ji peywira min bikar bînin.

Source: www.habr.com

Add a comment