QEMU.js: yn awr o ddifrif a chyda WASM

Un tro penderfynais am hwyl profi gwrthdroadwyedd y broses a dysgu sut i gynhyrchu JavaScript (yn fwy manwl gywir, Asm.js) o god peiriant. Dewiswyd QEMU ar gyfer yr arbrawf, a pheth amser yn ddiweddarach ysgrifennwyd erthygl ar Habr. Yn y sylwadau fe'm cynghorwyd i ail-wneud y prosiect yn WebAssembly, a hyd yn oed roi'r gorau iddi fy hun bron wedi gorffen Doeddwn i ddim eisiau'r prosiect rywsut... Roedd y gwaith yn mynd rhagddo, ond yn araf iawn, ac yn awr, yn ddiweddar yn yr erthygl honno ymddangosodd y sylw ar y testun “Felly sut daeth y cyfan i ben?” Mewn ymateb i fy ateb manwl, clywais “Mae hyn yn swnio fel erthygl.” Wel, os gallwch chi, bydd erthygl. Efallai y bydd rhywun yn ei chael yn ddefnyddiol. Oddi arno bydd y darllenydd yn dysgu rhai ffeithiau am ddyluniad backendau cynhyrchu cod QEMU, yn ogystal â sut i ysgrifennu casglwr Mewn Amser ar gyfer rhaglen we.

tasgau

Gan fy mod eisoes wedi dysgu sut i “rywsut” borthladd QEMU i JavaScript, y tro hwn penderfynwyd ei wneud yn ddoeth a pheidio ag ailadrodd hen gamgymeriadau.

Gwall rhif un: cangen o ryddhau pwynt

Fy nghamgymeriad cyntaf oedd fforchio fy fersiwn o'r fersiwn i fyny'r afon 2.4.1. Yna roedd yn ymddangos yn syniad da i mi: os oes rhyddhau pwynt yn bodoli, yna mae'n debyg ei fod yn fwy sefydlog na 2.4 syml, a hyd yn oed yn fwy felly y gangen master. A chan fy mod yn bwriadu ychwanegu cryn dipyn o fygiau fy hun, nid oedd arnaf angen rhai unrhyw un arall o gwbl. Mae'n debyg mai dyna sut y trodd allan. Ond dyma'r peth: nid yw QEMU yn aros yn ei unfan, ac ar ryw adeg fe wnaethon nhw hyd yn oed gyhoeddi optimeiddio'r cod a gynhyrchir gan 10 y cant. “Ie, nawr rydw i'n mynd i rewi,” meddyliais a thorri i lawr. Yma mae angen i ni wneud gwyriad: oherwydd natur un edau QEMU.js a'r ffaith nad yw'r QEMU gwreiddiol yn awgrymu absenoldeb aml-edafu (hynny yw, y gallu i weithredu sawl llwybr cod digyswllt ar yr un pryd, a nid dim ond “defnyddio pob cnewyllyn”) sy'n hanfodol ar ei gyfer, prif swyddogaethau edafedd roedd yn rhaid i mi eu “troi allan” i allu galw o'r tu allan. Creodd hyn rai problemau naturiol yn ystod yr uno. Fodd bynnag, mae'r ffaith bod rhai o'r newidiadau o'r gangen master, Yr wyf yn ceisio i uno fy cod, hefyd yn cael eu dewis yn y datganiad pwynt (ac felly yn fy nghangen) hefyd yn ôl pob tebyg ni fyddai wedi ychwanegu cyfleustra.

Yn gyffredinol, penderfynais ei bod yn dal yn gwneud synnwyr i daflu'r prototeip allan, ei ddadosod ar gyfer rhannau ac adeiladu fersiwn newydd o'r dechrau yn seiliedig ar rywbeth mwy ffres ac yn awr o master.

Camgymeriad rhif dau: methodoleg TLP

Yn y bôn, nid camgymeriad yw hwn, yn gyffredinol, dim ond nodwedd o greu prosiect mewn amodau o gamddealltwriaeth lwyr o “ble a sut i symud?” ac yn gyffredinol “a fyddwn ni'n cyrraedd yno?” Yn yr amodau hyn rhaglennu trwsgl yn opsiwn y gellir ei gyfiawnhau, ond, yn naturiol, nid oeddwn am ei ailadrodd yn ddiangen. Y tro hwn roeddwn i eisiau ei wneud yn ddoeth: ymrwymiadau atomig, newidiadau cod ymwybodol (a pheidio â “llinynu cymeriadau hap at ei gilydd nes ei fod yn llunio (gyda rhybuddion)”, fel y dywedodd Linus Torvalds unwaith am rywun, yn ôl Wikiquote), ac ati.

Camgymeriad rhif tri: mynd i mewn i'r dŵr heb wybod y rhyd

Dwi dal heb gael gwared yn llwyr ar hyn, ond nawr dwi wedi penderfynu peidio dilyn y llwybr o wrthwynebiad lleiaf o gwbl, a'i wneud “fel oedolyn,” sef ysgrifennu fy nghefn TCG o'r dechrau, rhag i mi beidio. i orfod dweud yn ddiweddarach, “Ie, mae hyn wrth gwrs, yn araf, ond ni allaf reoli popeth - dyna sut mae TCI wedi'i ysgrifennu ...” Ar ben hynny, roedd hyn i ddechrau yn ymddangos fel ateb amlwg, ers hynny Rwy'n cynhyrchu cod deuaidd. Fel maen nhw'n dweud, “Gent a gasglwydу, ond nid yr un hwnnw”: mae'r cod, wrth gwrs, yn ddeuaidd, ond ni ellir trosglwyddo rheolaeth iddo yn syml - mae'n rhaid ei wthio'n benodol i'r porwr i'w lunio, gan arwain at wrthrych penodol o'r byd JS, sydd angen ei wneud o hyd. cael eu hachub yn rhywle. Fodd bynnag, ar bensaernïaeth RISC arferol, cyn belled ag y deallaf, sefyllfa nodweddiadol yw'r angen i ailosod y storfa gyfarwyddiadau ar gyfer cod wedi'i adfywio yn benodol - os nad dyma sydd ei angen arnom, yna, beth bynnag, mae'n agos. Yn ogystal, o'm hymgais ddiwethaf, dysgais nad yw'n ymddangos bod rheolaeth yn cael ei drosglwyddo i ganol y bloc cyfieithu, felly nid oes angen dehongli beitcode arnom o unrhyw wrthbwyso, a gallwn ei gynhyrchu'n syml o'r swyddogaeth ar TB. .

Daethant a chicio

Er i mi ddechrau ailysgrifennu'r cod yn ôl ym mis Gorffennaf, daeth cic hud i'r amlwg heb i neb sylwi: fel arfer mae llythyrau gan GitHub yn cyrraedd fel hysbysiadau am ymatebion i geisiadau Materion a Thynnu, ond yma, yn sydyn crybwyll yn edefyn Binaryen fel backend qemu yn y cyd-destun, “Fe wnaeth rywbeth felly, efallai y bydd yn dweud rhywbeth.” Roeddem yn sôn am ddefnyddio llyfrgell gysylltiedig Emscripten Binaryen i greu WASM JIT. Wel, dywedais fod gennych drwydded Apache 2.0 yno, ac mae QEMU yn ei gyfanrwydd yn cael ei ddosbarthu o dan GPLv2, ac nid ydynt yn gydnaws iawn. Yn sydyn mae'n troi allan y gall trwydded fod ei drwsio rhywsut (Dydw i ddim yn gwybod: efallai ei newid, efallai trwyddedu deuol, efallai rhywbeth arall ...). Roedd hyn, wrth gwrs, yn fy ngwneud i'n hapus, oherwydd erbyn hynny roeddwn i eisoes wedi edrych yn fanwl fformat deuaidd WebCynulliad, ac roeddwn i rywsut yn drist ac yn annealladwy. Roedd yna lyfrgell hefyd a fyddai'n difa'r blociau sylfaenol gyda'r graff trawsnewid, yn cynhyrchu'r cod byte, a hyd yn oed yn ei redeg yn y cyfieithydd ei hun, pe bai angen.

Yna roedd mwy llythyr ar restr bostio QEMU, ond mae hyn yn ymwneud yn fwy â’r cwestiwn, “Pwy sydd ei angen beth bynnag?” Ac y mae yn sydyn, trodd allan ei fod yn angenrheidiol. O leiaf, gallwch chi grafu'r posibiliadau defnydd canlynol gyda'i gilydd, os yw'n gweithio fwy neu lai yn gyflym:

  • lansio rhywbeth addysgol heb unrhyw osodiad o gwbl
  • rhithwiroli ar iOS, lle, yn ôl sibrydion, yr unig gymhwysiad sydd â'r hawl i gynhyrchu cod ar y hedfan yw injan JS (a yw hyn yn wir?)
  • arddangosiad o mini-OS - un-llipa, adeiledig, pob math o firmware, ac ati ...

Nodweddion Amser Rhedeg Porwr

Fel y dywedais eisoes, mae QEMU ynghlwm wrth multithreading, ond nid oes gan y porwr ef. Wel, hynny yw, na... Ar y dechrau nid oedd yn bodoli o gwbl, yna ymddangosodd WebWorkers - hyd y deallaf, mae hwn yn aml-edau yn seiliedig ar drosglwyddo neges heb newidynnau a rennir. Yn naturiol, mae hyn yn creu problemau sylweddol wrth drosglwyddo cod presennol yn seiliedig ar y model cof a rennir. Yna, o dan bwysau cyhoeddus, fe'i gweithredwyd hefyd o dan yr enw SharedArrayBuffers. Fe'i cyflwynwyd yn raddol, dathlwyd ei lansiad mewn gwahanol borwyr, yna dathlwyd y Flwyddyn Newydd, ac yna Meltdown... Wedi hynny daethant i'r casgliad mai bras neu fras y mesuriad amser, ond gyda chymorth cof a rennir a edau cynyddran y cownter, mae'r cyfan yr un fath bydd yn gweithio allan yn eithaf cywir. Felly fe wnaethom analluogi multithreading gyda chof a rennir. Mae'n ymddangos iddynt ei droi yn ôl ymlaen yn ddiweddarach, ond, fel y daeth yn amlwg o'r arbrawf cyntaf, mae bywyd hebddo, ac os felly, byddwn yn ceisio ei wneud heb ddibynnu ar amledau.

Yr ail nodwedd yw amhosibilrwydd triniaethau lefel isel gyda'r pentwr: ni allwch gymryd, arbed y cyd-destun presennol a newid i un newydd gyda stac newydd. Mae'r pentwr galwadau yn cael ei reoli gan beiriant rhithwir JS. Mae'n ymddangos, beth yw'r broblem, gan ein bod yn dal i benderfynu rheoli'r llifoedd blaenorol yn gyfan gwbl â llaw? Y ffaith yw bod bloc I/O yn QEMU yn cael ei weithredu trwy goroutines, a dyma lle byddai triniaethau stac lefel isel yn dod yn ddefnyddiol. Yn ffodus, mae Emscipten eisoes yn cynnwys mecanwaith ar gyfer gweithrediadau asyncronaidd, hyd yn oed dau: Cysoni и Ymherawdwr. Mae'r un cyntaf yn gweithio trwy chwythiad sylweddol yn y cod JavaScript a gynhyrchir ac nid yw'n cael ei gefnogi mwyach. Yr ail yw'r "ffordd gywir" gyfredol ac mae'n gweithio trwy gynhyrchu bytecode ar gyfer y cyfieithydd brodorol. Mae'n gweithio, wrth gwrs, yn araf, ond nid yw'n chwyddo'r cod. Yn wir, roedd yn rhaid cyfrannu cefnogaeth ar gyfer coroutines ar gyfer y mecanwaith hwn yn annibynnol (roedd coroutines eisoes wedi'u hysgrifennu ar gyfer Asyncify ac roedd gweithredu tua'r un API ar gyfer Emterpreter, y cyfan oedd angen i chi ei wneud oedd eu cysylltu).

Ar hyn o bryd, nid wyf eto wedi llwyddo i rannu'r cod yn un a luniwyd yn WASM a'i ddehongli gan ddefnyddio Emterpreter, felly nid yw dyfeisiau bloc yn gweithio eto (gweler yn y gyfres nesaf, fel y dywedant...). Hynny yw, yn y diwedd fe ddylech chi gael rhywbeth fel y peth haenog doniol hwn:

  • bloc wedi'i ddehongli I/O. Wel, a oeddech chi wir yn disgwyl NVMe wedi'i efelychu gyda pherfformiad brodorol? 🙂
  • Prif god QEMU a luniwyd yn statig (cyfieithydd, dyfeisiau efelychiedig eraill, ac ati)
  • cod gwestai wedi'i lunio'n ddeinamig yn WASM

Nodweddion ffynonellau QEMU

Fel y mae'n debyg eich bod eisoes wedi dyfalu, mae'r cod ar gyfer efelychu pensaernïaeth gwesteion a'r cod ar gyfer cynhyrchu cyfarwyddiadau peiriannau gwesteiwr wedi'u gwahanu yn QEMU. Mewn gwirionedd, mae hyd yn oed ychydig yn anoddach:

  • mae pensaernïaeth gwadd
  • mae cyflymyddion, sef, KVM ar gyfer rhithwiroli caledwedd ar Linux (ar gyfer systemau gwestai a gwesteiwr sy'n gydnaws â'i gilydd), TCG ar gyfer cynhyrchu cod JIT yn unrhyw le. Gan ddechrau gyda QEMU 2.9, ymddangosodd cefnogaeth ar gyfer safon rhithwiroli caledwedd HAXM ar Windows (y manylion)
  • os defnyddir TCG ac nid rhithwiroli caledwedd, yna mae ganddo gefnogaeth cynhyrchu cod ar wahân ar gyfer pob pensaernïaeth gwesteiwr, yn ogystal ag ar gyfer y cyfieithydd cyffredinol
  • ... ac o gwmpas hyn i gyd - perifferolion efelychiedig, rhyngwyneb defnyddiwr, mudo, ailchwarae recordiau, ac ati.

Gyda llaw, oeddech chi'n gwybod: Gall QEMU efelychu nid yn unig y cyfrifiadur cyfan, ond hefyd y prosesydd ar gyfer proses defnyddiwr ar wahân yn y cnewyllyn gwesteiwr, a ddefnyddir, er enghraifft, gan y fuzzer AFL ar gyfer offeryniaeth ddeuaidd. Efallai yr hoffai rhywun drosglwyddo'r dull gweithredu hwn o QEMU i JS? 😉

Fel y mwyafrif o feddalwedd rhydd hirsefydlog, mae QEMU yn cael ei adeiladu trwy'r alwad configure и make. Gadewch i ni ddweud eich bod yn penderfynu ychwanegu rhywbeth: backend TCG, gweithredu llinyn, rhywbeth arall. Peidiwch â rhuthro i fod yn hapus/arswydus (tanlinellwch fel y bo'n briodol) gyda'r gobaith o gyfathrebu ag Autoconf - mewn gwirionedd, configure Mae'n debyg bod QEMU's yn hunan-ysgrifenedig ac nid yw'n deillio o unrhyw beth.

WebAssembly

Felly beth yw enw'r peth hwn WebAssembly (aka WASM)? Mae hwn yn disodli Asm.js, nad yw bellach yn esgus ei fod yn god JavaScript dilys. I'r gwrthwyneb, mae'n ddeuaidd yn unig ac wedi'i optimeiddio, ac nid yw hyd yn oed ysgrifennu cyfanrif ynddo yn syml iawn: ar gyfer crynoder, mae'n cael ei storio yn y fformat LEB128.

Efallai eich bod wedi clywed am yr algorithm ail-lopio ar gyfer Asm.js - dyma adfer cyfarwyddiadau rheoli llif “lefel uchel” (hynny yw, os-yna-arall, dolenni, ac ati), y mae peiriannau JS wedi'u dylunio ar eu cyfer, o yr LLVM IR lefel isel, yn agosach at y cod peiriant a weithredir gan y prosesydd. Yn naturiol, mae cynrychiolaeth ganolraddol QEMU yn agosach at yr ail. Mae'n ymddangos mai dyma hi, bytecode, diwedd y poenydio... Ac yna mae yna flociau, os-yna-arall a dolenni!..

A dyma reswm arall pam mae Binaryen yn ddefnyddiol: gall yn naturiol dderbyn blociau lefel uchel yn agos at yr hyn a fyddai'n cael ei storio yn WASM. Ond gall hefyd gynhyrchu cod o graff o flociau sylfaenol a thrawsnewidiadau rhyngddynt. Wel, rwyf eisoes wedi dweud ei fod yn cuddio fformat storio WebAssembly y tu ôl i'r API C / C ++ cyfleus.

TCG (Cynhyrchydd Cod Bach)

GTC oedd yn wreiddiol backend ar gyfer y casglwr C. Yna, mae'n debyg, ni allai wrthsefyll y gystadleuaeth gyda GCC, ond yn y diwedd canfu ei le yn QEMU fel mecanwaith cynhyrchu cod ar gyfer y llwyfan cynnal. Mae yna hefyd backend TCG sy'n cynhyrchu rhywfaint o bytecode haniaethol, sy'n cael ei weithredu ar unwaith gan y cyfieithydd, ond penderfynais osgoi ei ddefnyddio y tro hwn. Fodd bynnag, mae'r ffaith ei bod eisoes yn bosibl yn QEMU i alluogi'r newid i'r TB a gynhyrchir trwy'r swyddogaeth tcg_qemu_tb_exec, trodd allan i fod yn ddefnyddiol iawn i mi.

I ychwanegu backend TCG newydd i QEMU, mae angen i chi greu is-gyfeiriadur tcg/<имя архитектуры> (yn yr achos hwn, tcg/binaryen), ac mae'n cynnwys dwy ffeil: tcg-target.h и tcg-target.inc.c и rhagnodi mae'n ymwneud configure. Gallwch roi ffeiliau eraill yno, ond, fel y gallwch ddyfalu o enwau'r ddau hyn, bydd y ddau yn cael eu cynnwys yn rhywle: un fel ffeil pennawd arferol (mae wedi'i gynnwys yn tcg/tcg.h, a bod un eisoes mewn ffeiliau eraill yn y cyfeiriaduron tcg, accel ac nid yn unig), y llall — yn unig fel snippet code i mewn tcg/tcg.c, ond mae ganddo fynediad i'w swyddogaethau statig.

Gan benderfynu y byddwn yn treulio gormod o amser ar ymchwiliadau manwl i sut mae'n gweithio, yn syml, fe wnes i gopïo “sgerbydau” y ddwy ffeil hyn o weithrediad backend arall, gan nodi hyn yn onest ym mhennyn y drwydded.

file tcg-target.h yn cynnwys gosodiadau yn y ffurf yn bennaf #define-s:

  • faint o gofrestrau a pha led sydd ar y bensaernïaeth darged (mae gennym ni gynifer ag y dymunwn, cymaint ag y dymunwn - mae'r cwestiwn yn fwy am yr hyn a fydd yn cael ei gynhyrchu i god mwy effeithlon gan y porwr ar y bensaernïaeth “targed llwyr" ...)
  • alinio cyfarwyddiadau gwesteiwr: ar x86, a hyd yn oed yn TCI, nid yw cyfarwyddiadau wedi'u halinio o gwbl, ond rydw i'n mynd i roi yn y byffer cod nid cyfarwyddiadau o gwbl, ond awgrymiadau i strwythurau llyfrgell Binaryen, felly byddaf yn dweud: 4 beit
  • pa gyfarwyddiadau dewisol y gall y backend eu cynhyrchu - rydym yn cynnwys popeth a ddarganfyddwn yn Binaryen, gadewch i'r cyflymydd dorri'r gweddill yn rhai symlach ei hun
  • Beth yw maint bras y storfa TLB y mae'r backend yn gofyn amdani. Y ffaith yw bod popeth yn QEMU yn ddifrifol: er bod yna swyddogaethau cynorthwyydd sy'n cyflawni llwyth / storfa gan ystyried yr MMU gwadd (ble fydden ni hebddo nawr?), maen nhw'n arbed eu storfa gyfieithu ar ffurf strwythur, y y mae ei brosesu yn gyfleus i'w fewnosod yn uniongyrchol i flociau darlledu. Y cwestiwn yw, pa wrthbwyso yn y strwythur hwn sy'n cael ei brosesu'n fwyaf effeithlon gan ddilyniant bach a chyflym o orchmynion?
  • yma gallwch newid pwrpas un neu ddwy gofrestr neilltuedig, galluogi galw TB trwy swyddogaeth a disgrifio cwpl o rai bach yn ddewisol inline-swyddogaethau fel flush_icache_range (ond nid dyma ein hachos ni)

file tcg-target.inc.c, wrth gwrs, fel arfer yn llawer mwy o ran maint ac yn cynnwys nifer o swyddogaethau gorfodol:

  • cychwyn, gan gynnwys cyfyngiadau ar ba gyfarwyddiadau y gellir gweithredu ar ba weithrediadau. Wedi'i gopïo'n amlwg gennyf i o gefn arall
  • swyddogaeth sy'n cymryd un cyfarwyddyd bytecode mewnol
  • Gallwch hefyd roi swyddogaethau ategol yma, a gallwch hefyd ddefnyddio swyddogaethau statig o tcg/tcg.c

I mi fy hun, dewisais y strategaeth ganlynol: yng ngeiriau cyntaf y bloc cyfieithu nesaf, ysgrifennais bedwar pwynt: marc cychwyn (gwerth penodol yn y cyffiniau 0xFFFFFFFF, a benderfynodd gyflwr presennol y TB), cyd-destun, modiwl a gynhyrchir, a rhif hud ar gyfer dadfygio. Ar y cyntaf gosodwyd y marc i mewn 0xFFFFFFFF - nlle n - nifer fach gadarnhaol, a phob tro y'i gweithredwyd trwy'r cyfieithydd cynyddodd 1. Pan gyrhaeddodd 0xFFFFFFFE, cynhaliwyd y casgliad, arbedwyd y modiwl yn y tabl swyddogaethau, a fewnforiwyd i “lansiwr” bach, ac aeth y gweithredu iddo. tcg_qemu_tb_exec, a chafodd y modiwl ei dynnu o gof QEMU.

I aralleirio’r clasuron, “Crutch, faint sydd wedi’i gydblethu yn y sain hon i galon y rhaglaw...”. Fodd bynnag, roedd y cof yn gollwng yn rhywle. Ar ben hynny, roedd y cof yn cael ei reoli gan QEMU! Roedd gen i god a oedd, wrth ysgrifennu'r cyfarwyddyd nesaf (wel, hynny yw, pwyntydd), wedi dileu'r un yr oedd ei ddolen yn y lle hwn yn gynharach, ond nid oedd hyn yn helpu. Mewn gwirionedd, yn yr achos symlaf, mae QEMU yn dyrannu cof wrth gychwyn ac yn ysgrifennu'r cod a gynhyrchir yno. Pan fydd y byffer yn rhedeg allan, mae'r cod yn cael ei daflu allan ac mae'r un nesaf yn dechrau cael ei ysgrifennu yn ei le.

Ar ôl astudio'r cod, sylweddolais fod y tric gyda'r rhif hud yn fy ngalluogi i beidio â methu â dinistrio'r domen trwy ryddhau rhywbeth o'i le ar glustogfa anghyfarwydd ar y tocyn cyntaf. Ond pwy sy'n ailysgrifennu'r byffer i osgoi fy swyddogaeth yn ddiweddarach? Fel y mae datblygwyr Emscripten yn ei gynghori, pan ges i broblem, fe wnes i borthi'r cod canlyniadol yn ôl i'r cymhwysiad brodorol, gosod Mozilla Record-Replay arno... Yn gyffredinol, yn y diwedd sylweddolais beth syml: ar gyfer pob bloc, a struct TranslationBlock gyda'i ddisgrifiad. Dyfalwch ble... Mae hynny'n iawn, ychydig cyn y bloc yn union yn y byffer. Gan sylweddoli hyn, penderfynais roi'r gorau i ddefnyddio baglau (o leiaf rai), a thaflu'r rhif hud allan, a throsglwyddo'r geiriau sy'n weddill i struct TranslationBlock, creu rhestr sydd wedi'i chysylltu'n unigol y gellir ei chroesi'n gyflym pan gaiff y storfa gyfieithu ei hailosod, a rhyddhau'r cof.

Erys rhai baglau: er enghraifft, awgrymiadau wedi'u marcio yn y byffer cod - mae rhai ohonynt yn syml BinaryenExpressionRef, hynny yw, maent yn edrych ar yr ymadroddion y mae angen eu rhoi'n llinol yn y bloc sylfaenol a gynhyrchir, rhan yw'r cyflwr ar gyfer pontio rhwng BBs, rhan yw ble i fynd. Wel, mae yna flociau parod eisoes ar gyfer Relooper y mae angen eu cysylltu yn ôl yr amodau. Er mwyn eu gwahaniaethu, defnyddir y rhagdybiaeth eu bod i gyd wedi'u halinio gan o leiaf bedwar beit, fel y gallwch chi ddefnyddio'r ddau ddarn lleiaf arwyddocaol ar gyfer y label yn ddiogel, does ond angen i chi gofio ei dynnu os oes angen. Gyda llaw, mae labeli o'r fath eisoes yn cael eu defnyddio yn QEMU i nodi'r rheswm dros adael y ddolen TCG.

Defnyddio Binaryen

Mae modiwlau yn WebAssembly yn cynnwys swyddogaethau, pob un ohonynt yn cynnwys corff, sy'n fynegiant. Gweithrediadau unary a deuaidd yw mynegiadau, blociau sy'n cynnwys rhestrau o ymadroddion eraill, llif rheoli, ac ati. Fel y dywedais eisoes, mae llif rheoli yma wedi'i drefnu'n union fel canghennau lefel uchel, dolenni, galwadau swyddogaeth, ac ati. Nid yw dadleuon i swyddogaethau yn cael eu trosglwyddo ar y pentwr, ond yn benodol, yn union fel yn JS. Mae yna newidynnau byd-eang hefyd, ond nid wyf wedi eu defnyddio, felly ni fyddaf yn dweud wrthych amdanynt.

Mae gan swyddogaethau hefyd newidynnau lleol, wedi'u rhifo o sero, o fath: int32 / int64 / arnofio / dwbl. Yn yr achos hwn, yr n newidyn lleol cyntaf yw'r dadleuon a drosglwyddir i'r swyddogaeth. Sylwch, er nad yw popeth yma yn gwbl lefel isel o ran llif rheoli, nid yw cyfanrifau yn dal i fod â'r nodwedd “wedi'i lofnodi / heb ei lofnodi”: mae sut mae'r rhif yn ymddwyn yn dibynnu ar y cod gweithredu.

A siarad yn gyffredinol, mae Binaryen yn darparu C-API syml: rydych chi'n creu modiwl, ynddo ef creu ymadroddion - unary, deuaidd, blociau o ymadroddion eraill, rheoli llif, ac ati. Yna byddwch yn creu swyddogaeth gyda mynegiant fel ei gorff. Os oes gennych chi, fel fi, graff pontio lefel isel, bydd y gydran relooper yn eich helpu chi. Cyn belled ag y deallaf, mae'n bosibl defnyddio rheolaeth lefel uchel o'r llif gweithredu mewn bloc, cyn belled nad yw'n mynd y tu hwnt i ffiniau'r bloc - hynny yw, mae'n bosibl gwneud llwybr cyflym mewnol / araf. llwybr canghennog y tu mewn i'r cod prosesu storfa TLB adeiledig, ond i beidio ag ymyrryd â'r llif rheoli “allanol”. Pan fyddwch yn rhyddhau relooper, mae ei flociau yn cael eu rhyddhau; pan fyddwch yn rhyddhau modiwl, mae'r ymadroddion, swyddogaethau, ac ati a neilltuwyd iddo yn diflannu arena.

Fodd bynnag, os ydych chi am ddehongli cod ar y hedfan heb greu a dileu enghraifft cyfieithydd yn ddiangen, efallai y byddai'n gwneud synnwyr rhoi'r rhesymeg hon mewn ffeil C++, ac oddi yno rheoli'r API C++ cyfan o'r llyfrgell yn uniongyrchol, gan osgoi parod- deunydd lapio wedi'u gwneud.

Felly i gynhyrchu'r cod sydd ei angen arnoch chi

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

... os anghofiais unrhyw beth, mae'n ddrwg gennyf, dim ond i gynrychioli'r raddfa y mae hyn, ac mae'r manylion yn y ddogfennaeth.

A nawr mae'r crack-fex-pex yn dechrau, rhywbeth fel hyn:

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

Er mwyn cysylltu bydoedd QEMU a JS rywsut ac ar yr un pryd cyrchu'r swyddogaethau a luniwyd yn gyflym, crëwyd amrywiaeth (tabl o swyddogaethau i'w mewnforio i'r lansiwr), a gosodwyd y swyddogaethau a gynhyrchir yno. I gyfrifo'r mynegai yn gyflym, defnyddiwyd mynegai'r bloc cyfieithu sero gair i ddechrau, ond yna dechreuodd y mynegai a gyfrifwyd gan ddefnyddio'r fformiwla hon ffitio i mewn i'r maes yn unig. struct TranslationBlock.

Gyda llaw, demo (gyda thrwydded wallgof ar hyn o bryd) dim ond yn gweithio'n iawn yn Firefox. Roedd datblygwyr Chrome rhywsut ddim yn barod i'r ffaith y byddai rhywun eisiau creu mwy na mil o enghreifftiau o fodiwlau WebAssembly, felly fe wnaethon nhw ddyrannu gigabeit o ofod cyfeiriad rhithwir ar gyfer pob un...

Dyna i gyd am y tro. Efallai y bydd erthygl arall os oes gan unrhyw un ddiddordeb. Sef, mae yna o leiaf yn unig gwneud i ddyfeisiau bloc weithio. Efallai y byddai hefyd yn gwneud synnwyr i wneud y casgliad o fodiwlau WebAssembly yn anghydamserol, fel sy'n arferol ym myd JS, gan fod cyfieithydd ar y pryd a all wneud hyn i gyd nes bod y modiwl brodorol yn barod.

O'r diwedd pos: rydych chi wedi llunio deuaidd ar bensaernïaeth 32-bit, ond mae'r cod, trwy weithrediadau cof, yn dringo o Binaryen, rhywle ar y pentwr, neu rywle arall yn 2 GB uchaf y gofod cyfeiriad 32-bit. Y broblem yw bod hwn, o safbwynt Binaryen, yn cyrchu cyfeiriad canlyniadol rhy fawr. Sut i fynd o gwmpas hyn?

Mewn ffordd weinyddol

Wnes i ddim profi hyn yn y pen draw, ond fy meddwl cyntaf oedd “Beth pe bawn i'n gosod Linux 32-bit?” Yna bydd rhan uchaf y gofod cyfeiriad yn cael ei feddiannu gan y cnewyllyn. Yr unig gwestiwn yw faint fydd yn cael ei feddiannu: 1 neu 2 Gb.

Mewn ffordd rhaglennydd (opsiwn i ymarferwyr)

Gadewch i ni chwythu swigen ar frig y gofod cyfeiriad. Dwi fy hun ddim yn deall pam ei fod yn gweithio - yno eisoes rhaid cael pentwr. Ond “ymarferwyr ydyn ni: mae popeth yn gweithio i ni, ond does neb yn gwybod pam...”

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

... mae'n wir nad yw'n gydnaws â Valgrind, ond, yn ffodus, mae Valgrind ei hun yn effeithiol iawn yn gwthio pawb allan o'r fan honno :)

Efallai y bydd rhywun yn rhoi esboniad gwell o sut mae'r cod hwn o fy un i'n gweithio...

Ffynhonnell: hab.com

Ychwanegu sylw