Qemu.js gyda chefnogaeth JIT: gallwch ddal i droi'r briwgig yn ôl

Ychydig flynyddoedd yn ôl Fabrice Bellard ysgrifennwyd gan jslinux yn efelychydd PC a ysgrifennwyd yn JavaScript. Ar ôl hynny roedd o leiaf mwy Rhith x86. Ond roedd pob un ohonynt, cyn belled ag y gwn, yn ddehonglwyr, tra bod Qemu, a ysgrifennwyd yn llawer cynharach gan yr un Fabrice Bellard, ac, yn ôl pob tebyg, unrhyw efelychydd modern hunan-barch, yn defnyddio casgliad JIT o god gwestai i mewn i god system gwesteiwr. Roedd yn ymddangos i mi ei bod yn bryd gweithredu'r dasg i'r gwrthwyneb mewn perthynas â'r un y mae porwyr yn ei datrys: JIT yn casglu cod peiriant i JavaScript, yr oedd yn ymddangos yn fwyaf rhesymegol i borthladd Qemu ar ei gyfer. Mae'n ymddangos, pam Qemu, mae yna efelychwyr symlach a hawdd eu defnyddio - yr un VirtualBox, er enghraifft - wedi'u gosod ac yn gweithio. Ond mae gan Qemu sawl nodwedd ddiddorol

  • ffynhonnell agor
  • y gallu i weithio heb yrrwr cnewyllyn
  • y gallu i weithio yn y modd cyfieithydd
  • cefnogaeth i nifer fawr o bensaernïaeth gwesteiwr a gwestai

O ran y trydydd pwynt, gallaf nawr egluro, mewn gwirionedd, yn y modd TCI, nad y cyfarwyddiadau peiriant gwestai eu hunain sy'n cael eu dehongli, ond y bytecode a geir oddi wrthynt, ond nid yw hyn yn newid y hanfod - er mwyn adeiladu a rhedeg Qemu ar bensaernïaeth newydd, os ydych chi'n ffodus, mae casglwr C yn ddigon - gellir gohirio ysgrifennu generadur cod.

Ac yn awr, ar ôl dwy flynedd o tincian hamddenol gyda chod ffynhonnell Qemu yn fy amser rhydd, ymddangosodd prototeip gweithiol, y gallwch chi redeg ynddo eisoes, er enghraifft, Kolibri OS.

Beth yw Emscripten

Y dyddiau hyn, mae llawer o gasglwyr wedi ymddangos, a'r canlyniad terfynol yw JavaScript. Yn wreiddiol, bwriad rhai, fel Type Script, oedd y ffordd orau o ysgrifennu ar gyfer y we. Ar yr un pryd, mae Emscripten yn ffordd o gymryd cod C neu C ++ sy'n bodoli eisoes a'i lunio ar ffurf y gellir ei darllen gan borwr. Ar Mae'r dudalen hon Rydym wedi casglu llawer o borthladdoedd o raglenni adnabyddus: ymaEr enghraifft, gallwch edrych ar PyPy - gyda llaw, maen nhw'n honni bod ganddyn nhw JIT yn barod. Mewn gwirionedd, ni ellir llunio pob rhaglen a'i rhedeg mewn porwr - mae yna nifer Nodweddion, y mae'n rhaid i chi ei oddef, fodd bynnag, gan fod yr arysgrif ar yr un dudalen yn dweud “Gellir defnyddio Emscripten i lunio bron unrhyw cludadwy cod C/C++ i JavaScript". Hynny yw, mae yna nifer o weithrediadau sy'n ymddygiad anniffiniedig yn unol â'r safon, ond fel arfer yn gweithio ar x86 - er enghraifft, mynediad heb ei alinio i newidynnau, sy'n cael ei wahardd yn gyffredinol ar rai pensaernïaeth. Yn gyffredinol , Mae Qemu yn rhaglen draws-lwyfan ac , roeddwn i eisiau credu, ac nid yw'n cynnwys llawer o ymddygiad anniffiniedig yn barod - cymerwch hi a'i llunio, yna tincer ychydig gyda JIT - ac rydych chi wedi gorffen! achos...

Ceisiwch yn gyntaf

Yn gyffredinol, nid fi yw'r person cyntaf i feddwl am y syniad o drosglwyddo Qemu i JavaScript. Gofynnwyd cwestiwn i fforwm ReactOS a oedd hyn yn bosibl gan ddefnyddio Emscripten. Hyd yn oed yn gynharach, roedd sibrydion bod Fabrice Bellard wedi gwneud hyn yn bersonol, ond yr oeddem yn sôn am jslinux, sydd, hyd y gwn i, yn ymgais i gyflawni perfformiad digonol â llaw yn JS, ac fe'i hysgrifennwyd o'r dechrau. Yn ddiweddarach, ysgrifennwyd Virtual x86 - postiwyd ffynonellau heb eu rhwystro ar ei gyfer, ac, fel y dywedwyd, roedd mwy o “realaeth” yr efelychiad yn ei gwneud hi'n bosibl defnyddio SeaBIOS fel cadarnwedd. Yn ogystal, bu o leiaf un ymgais i borthladd Qemu gan ddefnyddio Emscripten - ceisiais wneud hyn pâr soced, ond yr oedd dadblygiad, hyd y deallaf, wedi ei rewi.

Felly, mae'n ymddangos, dyma'r ffynonellau, dyma Emscripten - cymerwch ef a lluniwch. Ond mae yna hefyd lyfrgelloedd y mae Qemu yn dibynnu arnynt, a llyfrgelloedd y mae'r llyfrgelloedd hynny'n dibynnu arnynt, ac ati, ac mae un ohonynt yn libffi, pa glib yn dibynnu ar. Roedd sïon ar y Rhyngrwyd fod yna un yn y casgliad mawr o borthladdoedd o lyfrgelloedd ar gyfer Emscripten, ond roedd yn anodd credu rhywsut: yn gyntaf, ni fwriadwyd bod yn gasglwr newydd, yn ail, roedd yn rhy isel ei lefel a llyfrgell i ddim ond codi, a chrynhoi i JS. Ac nid mater o fewnosodiadau cynulliad yn unig ydyw - mae'n debyg, os ydych chi'n ei droelli, ar gyfer rhai confensiynau galw gallwch chi gynhyrchu'r dadleuon angenrheidiol ar y pentwr a galw'r swyddogaeth hebddynt. Ond mae Emscripten yn beth anodd: er mwyn gwneud i'r cod a gynhyrchir edrych yn gyfarwydd i optimeiddiwr injan JS y porwr, defnyddir rhai triciau. Yn benodol, mae'r ail-loopio fel y'i gelwir - mae generadur cod sy'n defnyddio'r LLVM IR a dderbyniwyd gyda rhai cyfarwyddiadau pontio haniaethol yn ceisio ail-greu ifs credadwy, dolenni, ac ati. Wel, sut mae'r dadleuon yn cael eu trosglwyddo i'r swyddogaeth? Yn naturiol, fel dadleuon i swyddogaethau JS, hynny yw, os yn bosibl, nid drwy'r pentwr.

Ar y dechrau roedd syniad yn syml i ysgrifennu yn lle libffi gyda JS a rhedeg profion safonol, ond yn y diwedd roeddwn wedi drysu ynghylch sut i wneud fy ffeiliau pennyn fel y byddent yn gweithio gyda'r cod presennol - beth allaf ei wneud, fel maen nhw'n dweud, "A yw'r tasgau mor gymhleth "A ydym ni mor dwp?" Roedd yn rhaid i mi borthladd libffi i bensaernïaeth arall, felly i siarad - yn ffodus, mae gan Emscripten y ddau facro ar gyfer cydosod mewnol (yn Javascript, ie - wel, beth bynnag yw'r bensaernïaeth, felly'r cydosodwr), a'r gallu i redeg cod a gynhyrchir ar y hedfan. Yn gyffredinol, ar ôl tinkering gyda darnau libffi sy'n dibynnu ar blatfform am beth amser, cefais rywfaint o god y gellir ei lunio a'i redeg ar y prawf cyntaf y deuthum ar ei draws. Er mawr syndod i mi, roedd y prawf yn llwyddiannus. Wedi fy syfrdanu gan fy athrylith - dim jôc, fe weithiodd o'r lansiad cyntaf - yr wyf, yn dal i beidio â chredu fy llygaid, yn mynd i edrych ar y cod canlyniadol eto, i werthuso ble i gloddio nesaf. Dyma fi'n mynd yn wallgof am yr eildro - yr unig beth wnaeth fy swyddogaeth oedd ffi_call - adroddodd hyn alwad lwyddiannus. Nid oedd unrhyw alwad ei hun. Felly anfonais fy nghais tynnu cyntaf, a oedd yn cywiro gwall yn y prawf sy'n amlwg i unrhyw fyfyriwr Olympiad - ni ddylid cymharu rhifau real fel a == b a hyd yn oed sut a - b < EPS - mae angen i chi gofio'r modiwl hefyd, fel arall bydd 0 yn hafal iawn i 1/3... Yn gyffredinol, deuthum i fyny gyda phorthladd penodol o libffi, sy'n pasio'r profion symlaf, a pha glib yw llunio - penderfynais y byddai angen, byddaf yn ei ychwanegu yn nes ymlaen. Wrth edrych ymlaen, byddaf yn dweud, fel y digwyddodd, nad oedd y casglwr hyd yn oed yn cynnwys y swyddogaeth libffi yn y cod terfynol.

Ond, fel y dywedais eisoes, mae rhai cyfyngiadau, ac ymhlith y defnydd rhad ac am ddim o ymddygiad amrywiol heb ei ddiffinio, mae nodwedd fwy annymunol wedi'i chuddio - nid yw JavaScript trwy ddyluniad yn cefnogi aml-threading gyda chof a rennir. Mewn egwyddor, fel arfer gellir galw hyn hyd yn oed yn syniad da, ond nid ar gyfer cod cludo y mae ei bensaernïaeth yn gysylltiedig ag edafedd C. Yn gyffredinol, mae Firefox yn arbrofi gyda chefnogi gweithwyr a rennir, ac mae gan Emscripten weithrediad llinyn ar eu cyfer, ond nid oeddwn am ddibynnu arno. Roedd yn rhaid i mi ddiwreiddio multithreading yn araf o'r cod Qemu - hynny yw, darganfod ble mae'r edafedd yn rhedeg, symud corff y ddolen sy'n rhedeg yn yr edefyn hwn i swyddogaeth ar wahân, a galw swyddogaethau o'r fath fesul un o'r brif ddolen.

Ail gynnig

Ar ryw adeg, daeth yn amlwg bod y broblem yn dal i fod yno, ac na fyddai gwthio baglau ar hap o amgylch y cod yn arwain at unrhyw les. Casgliad: mae angen i ni rywsut systemateiddio'r broses o ychwanegu baglau. Felly, cymerwyd fersiwn 2.4.1, a oedd yn ffres bryd hynny, (nid 2.5.0, oherwydd, pwy a wyr, bydd chwilod yn y fersiwn newydd nad ydynt wedi'u dal eto, ac mae gennyf ddigon o fygiau fy hun ), a'r peth cyntaf oedd ei ail-ysgrifennu yn ddiogel thread-posix.c. Wel, hynny yw, mor ddiogel: pe bai rhywun yn ceisio cyflawni llawdriniaeth sy'n arwain at rwystro, galwyd y swyddogaeth ar unwaith abort() - wrth gwrs, nid oedd hyn yn datrys yr holl broblemau ar unwaith, ond o leiaf roedd yn fwy dymunol rhywsut na derbyn data anghyson yn dawel.

Yn gyffredinol, mae opsiynau Emscripten yn ddefnyddiol iawn wrth drosglwyddo cod i JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - maent yn dal rhai mathau o ymddygiad heb ei ddiffinio, megis galwadau i gyfeiriad heb ei alinio (nad yw'n gyson o gwbl â'r cod ar gyfer araeau wedi'u teipio fel HEAP32[addr >> 2] = 1) neu alw swyddogaeth gyda'r nifer anghywir o ddadleuon.

Gyda llaw, mae gwallau aliniad yn fater ar wahân. Fel y dywedais eisoes, mae gan Qemu gefn deongliadol “dirywiedig” ar gyfer cynhyrchu cod TCI (dehonglydd cod bach), ac i adeiladu a rhedeg Qemu ar bensaernïaeth newydd, os ydych chi'n ffodus, mae casglwr C yn ddigon. "os ydych chi'n lwcus". Roeddwn yn anlwcus, ac fe ddaeth yn amlwg bod TCI yn defnyddio mynediad heb ei alinio wrth ddosrannu ei god beit. Hynny yw, ar bob math o ARM a phensaernïaeth eraill sydd â mynediad wedi'i lefelu o reidrwydd, mae Qemu yn llunio oherwydd bod ganddyn nhw backend TCG arferol sy'n cynhyrchu cod brodorol, ond cwestiwn arall yw a fydd TCI yn gweithio arnynt. Fodd bynnag, fel y digwyddodd, roedd dogfennaeth TCI yn dangos yn glir rywbeth tebyg. O ganlyniad, ychwanegwyd galwadau swyddogaeth am ddarllen heb ei alinio at y cod, a ddarganfuwyd mewn rhan arall o Qemu.

Dinistr tomen

O ganlyniad, cywirwyd mynediad heb ei alinio i TCI, crëwyd prif ddolen a oedd yn ei dro yn galw'r prosesydd, RCU a rhai pethau bach eraill. Ac felly dwi'n lansio Qemu gyda'r opsiwn -d exec,in_asm,out_asm, sy'n golygu bod angen i chi ddweud pa flociau o god sy'n cael eu gweithredu, a hefyd ar adeg darlledu i ysgrifennu beth oedd cod gwestai, pa god gwesteiwr a ddaeth (yn yr achos hwn, bytecode). Mae'n dechrau, yn gweithredu sawl bloc cyfieithu, yn ysgrifennu'r neges dadfygio a adewais y bydd RCU nawr yn dechrau a ... damweiniau abort() tu mewn i swyddogaeth free(). Trwy tincian gyda'r swyddogaeth free() Llwyddom i ddarganfod, ym mhennyn y bloc pentwr, sy'n gorwedd yn yr wyth beit cyn y cof a neilltuwyd, yn lle maint y bloc neu rywbeth tebyg, roedd sbwriel.

Dinistrio'r domen - pa mor giwt ... Mewn achos o'r fath, mae ateb defnyddiol - o (os yn bosibl) o'r un ffynonellau, cydosod deuaidd brodorol a'i redeg o dan Valgrind. Ar ôl peth amser, roedd y deuaidd yn barod. Rwy'n ei lansio gyda'r un opsiynau - mae'n damwain hyd yn oed wrth gychwyn, cyn cyrraedd y dienyddiad mewn gwirionedd. Mae'n annymunol, wrth gwrs - mae'n debyg, nid oedd y ffynonellau yn union yr un fath, nad yw'n syndod, gan fod ffurfweddu wedi canfod opsiynau ychydig yn wahanol, ond mae gen i Valgrind - yn gyntaf byddaf yn trwsio'r byg hwn, ac yna, os byddaf yn lwcus , bydd yr un gwreiddiol yn ymddangos. Rwy'n rhedeg yr un peth o dan Valgrind... Y-y-y, y-y-y, uh-uh, fe ddechreuodd, aeth trwy ymgychwyn fel arfer a symud ymlaen heibio'r byg gwreiddiol heb un rhybudd am fynediad cof anghywir, heb sôn am gwympiadau. Ni wnaeth bywyd, fel maen nhw'n dweud, fy mharatoi ar gyfer hyn - mae rhaglen chwilfriw yn stopio pan gaiff ei lansio o dan Walgrind. Mae'r hyn oedd yn ddirgelwch. Fy rhagdybiaeth yw bod gdb wedi dangos gwaith unwaith yng nghyffiniau'r cyfarwyddyd cyfredol ar ôl damwain wrth gychwyn. memset-a gyda phwyntydd dilys gan ddefnyddio'r naill neu'r llall mmx, neu xmm cofrestri, yna efallai ei fod yn rhyw fath o gamgymeriad alinio, er ei bod yn dal yn anodd credu.

Iawn, nid yw'n ymddangos bod Valgrind yn helpu yma. Ac yma y dechreuodd y peth mwyaf ffiaidd - mae'n ymddangos bod popeth yn dechrau hyd yn oed, ond mae damweiniau am resymau hollol anhysbys oherwydd digwyddiad a allai fod wedi digwydd filiynau o gyfarwyddiadau yn ôl. Am amser hir, nid oedd hyd yn oed yn glir sut i fynd ati. Yn y diwedd, roedd yn rhaid i mi eistedd i lawr a dadfygio o hyd. Roedd argraffu'r hyn yr ailysgrifennwyd y pennawd ag ef yn dangos nad oedd yn edrych fel rhif, ond yn hytrach rhyw fath o ddata deuaidd. Ac wele, canfuwyd y llinyn deuaidd hwn yn y ffeil BIOS - hynny yw, yn awr roedd modd dweud yn rhesymol hyderus mai gorlif byffer ydoedd, ac mae hyd yn oed yn amlwg ei fod wedi'i ysgrifennu at y byffer hwn. Wel, yna rhywbeth fel hyn - yn Emscripten, yn ffodus, nid oes unrhyw hap o'r gofod cyfeiriad, nid oes tyllau ynddo ychwaith, felly gallwch chi ysgrifennu rhywle yng nghanol y cod i allbynnu data fesul pwyntydd o'r lansiad diwethaf, edrychwch ar y data, edrychwch ar y pwyntydd, ac , os nad yw wedi newid, meddyliwch. Gwir, mae'n cymryd ychydig o funudau i gysylltu ar ôl unrhyw newid, ond beth allwch chi ei wneud? O ganlyniad, canfuwyd llinell benodol a oedd yn copïo'r BIOS o'r byffer dros dro i'r cof gwestai - ac, yn wir, nid oedd digon o le yn y byffer. Arweiniodd dod o hyd i ffynhonnell y cyfeiriad byffer rhyfedd hwnnw at swyddogaeth qemu_anon_ram_alloc mewn ffeil oslib-posix.c - y rhesymeg oedd yna: weithiau gall fod yn ddefnyddiol alinio'r cyfeiriad i dudalen enfawr o faint 2 MB, am hyn byddwn yn gofyn mmap yn gyntaf ychydig yn fwy, ac yna byddwn yn dychwelyd y gormodedd gyda chymorth munmap. Ac os nad oes angen aliniad o'r fath, yna byddwn yn nodi'r canlyniad yn lle 2 MB getpagesize() - mmap bydd yn dal i roi cyfeiriad wedi'i alinio... Felly yn Emscripten mmap dim ond galwadau malloc, ond wrth gwrs nid yw'n alinio ar y dudalen. Yn gyffredinol, bug a oedd yn rhwystredig i mi am ychydig fisoedd yn cael ei gywiro gan newid yn i chi llinellau.

Nodweddion swyddogaethau galw

Ac yn awr mae'r prosesydd yn cyfrif rhywbeth, nid yw Qemu yn chwalu, ond nid yw'r sgrin yn troi ymlaen, ac mae'r prosesydd yn mynd i mewn i ddolenni yn gyflym, a barnu yn ôl yr allbwn -d exec,in_asm,out_asm. Mae rhagdybiaeth wedi dod i'r amlwg: nid yw ymyriadau amserydd (neu, yn gyffredinol, pob ymyriad) yn cyrraedd. Ac yn wir, os ydych chi'n dadsgriwio'r ymyriadau o'r cynulliad brodorol, a weithiodd am ryw reswm, fe gewch chi lun tebyg. Ond nid dyma'r ateb o gwbl: roedd cymhariaeth o'r olion a roddwyd gyda'r opsiwn uchod yn dangos bod y llwybrau dienyddio yn amrywio'n gynnar iawn. Yma mae'n rhaid dweud bod cymhariaeth o'r hyn a gofnodwyd gan ddefnyddio'r lansiwr emrun nid yw allbwn dadfygio gydag allbwn y cynulliad brodorol yn broses gwbl fecanyddol. Nid wyf yn gwybod yn union sut mae rhaglen sy'n rhedeg mewn porwr yn cysylltu â hi emrun, ond mae rhai llinellau yn yr allbwn yn troi allan i gael eu haildrefnu, felly nid yw'r gwahaniaeth yn y diff yn rheswm eto i dybio bod y taflwybrau wedi dargyfeirio. Yn gyffredinol, daeth yn amlwg bod yn ôl y cyfarwyddiadau ljmpl mae trosglwyddiad i wahanol gyfeiriadau, ac mae'r cod byte a gynhyrchir yn sylfaenol wahanol: mae un yn cynnwys cyfarwyddyd i alw swyddogaeth cynorthwyydd, nid yw'r llall yn gwneud hynny. Ar ôl googling y cyfarwyddiadau ac astudio'r cod sy'n cyfieithu y cyfarwyddiadau hyn, daeth yn amlwg, yn gyntaf, yn union cyn iddo yn y gofrestr cr0 gwnaed recordiad - hefyd gan ddefnyddio helpwr - a newidiodd y prosesydd i'r modd gwarchodedig, ac yn ail, nad oedd y fersiwn js byth yn newid i'r modd gwarchodedig. Ond y ffaith yw mai nodwedd arall o Emscripten yw ei amharodrwydd i oddef cod megis gweithredu cyfarwyddiadau call yn TCI, y mae unrhyw bwyntydd swyddogaeth yn arwain at fath long long f(int arg0, .. int arg9) - rhaid galw ffwythiannau gyda'r nifer cywir o ddadleuon. Os caiff y rheol hon ei thorri, yn dibynnu ar y gosodiadau dadfygio, bydd y rhaglen naill ai'n chwalu (sy'n dda) neu'n galw'r swyddogaeth anghywir o gwbl (a fydd yn drist i ddadfygio). Mae yna drydydd opsiwn hefyd - galluogi cynhyrchu deunydd lapio sy'n ychwanegu / dileu dadleuon, ond yn gyfan gwbl mae'r deunydd lapio hyn yn cymryd llawer o le, er gwaethaf y ffaith mai dim ond ychydig mwy na chant o ddeunydd lapio sydd ei angen arnaf mewn gwirionedd. Mae hyn ar ei ben ei hun yn drist iawn, ond bu problem fwy difrifol: yn y cod a gynhyrchwyd o'r swyddogaethau lapio, cafodd y dadleuon eu trosi a'u trosi, ond weithiau ni chafodd y swyddogaeth gyda'r dadleuon a gynhyrchwyd ei alw - wel, yn union fel yn fy ngweithrediad libffi. Hynny yw, ni chafodd rhai cynorthwywyr eu dienyddio.

Yn ffodus, mae gan Qemu restrau o gynorthwywyr y gall peiriant eu darllen ar ffurf ffeil pennawd fel

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

Fe'u defnyddir yn eithaf doniol: yn gyntaf, mae macros yn cael eu hailddiffinio yn y ffordd fwyaf rhyfedd DEF_HELPER_n, ac yna'n troi ymlaen helper.h. I'r graddau y mae'r macro yn cael ei ehangu i gychwynnwr strwythur a choma, ac yna arae yn cael ei ddiffinio, ac yn lle elfennau - #include <helper.h> O ganlyniad, cefais gyfle o'r diwedd i roi cynnig ar y llyfrgell yn y gwaith pyparsio, ac ysgrifennwyd sgript sy'n cynhyrchu'r union ddeunydd lapio hynny ar gyfer yr union swyddogaethau y mae eu hangen.

Ac felly, ar ôl hynny roedd yn ymddangos bod y prosesydd yn gweithio. Mae'n ymddangos ei fod oherwydd na chafodd y sgrin ei gychwyn erioed, er bod memtest86+ yn gallu rhedeg yn y cynulliad brodorol. Yma mae angen egluro bod cod I/O bloc Qemu wedi'i ysgrifennu mewn coroutines. Mae gan Emscripten ei weithrediad anodd iawn ei hun, ond roedd angen ei gefnogi o hyd yn y cod Qemu, a gallwch ddadfygio'r prosesydd nawr: mae Qemu yn cefnogi opsiynau -kernel, -initrd, -append, y gallwch chi gychwyn Linux neu, er enghraifft, memtest86 +, heb ddefnyddio dyfeisiau bloc o gwbl. Ond dyma'r broblem: yn y cynulliad brodorol gallai rhywun weld allbwn cnewyllyn Linux i'r consol gyda'r opsiwn -nographic, a dim allbwn o'r porwr i'r derfynell lle cafodd ei lansio emrun, ni ddaeth. Hynny yw, nid yw'n glir: nid yw'r prosesydd yn gweithio neu nid yw'r allbwn graffeg yn gweithio. Ac yna fe ddigwyddodd i mi aros ychydig. Daeth i'r amlwg “nad yw'r prosesydd yn cysgu, ond yn amrantu'n araf yn unig,” ac ar ôl tua phum munud fe daflodd y cnewyllyn griw o negeseuon ar y consol a pharhau i hongian. Daeth yn amlwg bod y prosesydd, yn gyffredinol, yn gweithio, ac mae angen inni gloddio i mewn i'r cod ar gyfer gweithio gyda SDL2. Yn anffodus, dydw i ddim yn gwybod sut i ddefnyddio'r llyfrgell hon, felly mewn rhai mannau roedd yn rhaid i mi actio ar hap. Ar ryw adeg, fflachiodd y llinell parallel0 ar y sgrin ar gefndir glas, a awgrymodd rai meddyliau. Yn y diwedd, daeth i'r amlwg mai'r broblem oedd bod Qemu yn agor sawl ffenestr rithwir mewn un ffenestr ffisegol, y gallwch chi newid rhyngddynt gan ddefnyddio Ctrl-Alt-n: mae'n gweithio yn yr adeilad brodorol, ond nid yn Emscripten. Ar ôl cael gwared ar ffenestri diangen gan ddefnyddio opsiynau -monitor none -parallel none -serial none a chyfarwyddiadau i ail-lunio'r sgrin gyfan yn rymus ar bob ffrâm, gweithiodd popeth yn sydyn.

Coroutines

Felly, mae efelychu yn y porwr yn gweithio, ond ni allwch redeg unrhyw beth hyblyg sengl diddorol ynddo, oherwydd nid oes bloc I / O - mae angen i chi weithredu cefnogaeth ar gyfer coroutines. Mae gan Qemu sawl cefn coroutine eisoes, ond oherwydd natur JavaScript a generadur cod Emscripten, ni allwch ddechrau jyglo staciau. Mae'n ymddangos bod “popeth wedi diflannu, mae'r plastr yn cael ei dynnu,” ond mae datblygwyr Emscripten eisoes wedi gofalu am bopeth. Mae hyn yn cael ei weithredu yn eithaf doniol: gadewch i ni alw galwad swyddogaeth fel hyn amheus emscripten_sleep a sawl un arall gan ddefnyddio'r mecanwaith Asyncify, yn ogystal â galwadau pwyntydd a galwadau i unrhyw swyddogaeth lle gallai un o'r ddau achos blaenorol ddigwydd ymhellach i lawr y pentwr. Ac yn awr, cyn pob galwad amheus, byddwn yn dewis cyd-destun async, ac yn syth ar ôl yr alwad, byddwn yn gwirio a yw galwad asyncronig wedi digwydd, ac os ydyw, byddwn yn arbed yr holl newidynnau lleol yn y cyd-destun async hwn, yn nodi pa swyddogaeth i drosglwyddo rheolaeth i pan fydd angen i ni barhau i weithredu, a gadael y swyddogaeth bresennol. Dyma lle mae lle i astudio'r effaith gwastraffu — ar gyfer anghenion parhau i weithredu cod ar ôl dychwelyd o alwad asyncronig, mae'r casglwr yn cynhyrchu “bonion” o'r swyddogaeth gan ddechrau ar ôl galwad amheus - fel hyn: os oes n galwadau amheus, yna bydd y swyddogaeth yn cael ei ehangu yn rhywle n/2 amseroedd - mae hyn yn dal i fod, os nad Cadwch mewn cof bod angen i chi ychwanegu arbed rhai newidynnau lleol i'r swyddogaeth wreiddiol ar ôl pob galwad anghydamserol posibl. Yn dilyn hynny, roedd yn rhaid i mi hyd yn oed ysgrifennu sgript syml yn Python, sydd, yn seiliedig ar set benodol o swyddogaethau a or-ddefnyddiwyd yn arbennig “yn ôl pob tebyg, ddim yn caniatáu i asynchrony basio trwyddynt eu hunain” (hynny yw, nid yw hyrwyddiad stac a phopeth yr wyf newydd ei ddisgrifio yn gwneud hynny gwaith ynddynt), yn nodi galwadau trwy awgrymiadau y dylai'r casglwr anwybyddu swyddogaethau fel nad yw'r swyddogaethau hyn yn cael eu hystyried yn anghydamserol. Ac yna mae ffeiliau JS o dan 60 MB yn amlwg yn ormod - gadewch i ni ddweud o leiaf 30. Er, ar ôl i mi sefydlu sgript cynulliad, a thaflu'r opsiynau cysylltydd allan yn ddamweiniol, ymhlith y rhain roedd -O3. Rwy'n rhedeg y cod a gynhyrchir, ac mae Chromium yn bwyta cof a damweiniau. Yna edrychais yn ddamweiniol ar yr hyn yr oedd yn ceisio ei lawrlwytho ... Wel, beth allaf ei ddweud, byddwn wedi rhewi hefyd pe bai wedi cael cais i astudio'n feddylgar a gwneud y gorau o Javascript 500+ MB.

Yn anffodus, nid oedd y gwiriadau yng nghod llyfrgell cymorth Asyncify yn gwbl gyfeillgar â nhw longjmp-s sy'n cael eu defnyddio yn y cod prosesydd rhithwir, ond ar ôl darn bach sy'n analluogi'r gwiriadau hyn ac yn adfer cyd-destunau yn rymus fel pe bai popeth yn iawn, gweithiodd y cod. Ac yna dechreuodd peth rhyfedd: weithiau ysgogwyd gwiriadau yn y cod cydamseru - yr un rhai sy'n chwalu'r cod os, yn ôl y rhesymeg gweithredu, y dylid ei rwystro - ceisiodd rhywun fachu mutex a ddaliwyd eisoes. Yn ffodus, nid oedd hyn yn broblem resymegol yn y cod cyfresol - roeddwn yn syml yn defnyddio'r swyddogaeth prif ddolen safonol a ddarperir gan Emscripten, ond weithiau byddai'r alwad asyncronig yn dadlapio'r pentwr yn llwyr, ac ar y foment honno byddai'n methu setTimeout o'r brif ddolen - felly, aeth y cod i mewn i iteriad y brif ddolen heb adael yr iteriad blaenorol. Ailysgrifenu ar ddolen anfeidrol a emscripten_sleep, a daeth y problemau gyda mutexes i ben. Mae'r cod hyd yn oed wedi dod yn fwy rhesymegol - wedi'r cyfan, mewn gwirionedd, nid oes gennyf rywfaint o god sy'n paratoi'r ffrâm animeiddio nesaf - mae'r prosesydd yn cyfrifo rhywbeth ac mae'r sgrin yn cael ei diweddaru o bryd i'w gilydd. Fodd bynnag, ni ddaeth y problemau i ben yno: weithiau byddai gweithredu Qemu yn dod i ben yn dawel heb unrhyw eithriadau na gwallau. Ar y foment honno rhoddais y gorau iddi, ond, wrth edrych ymlaen, fe ddywedaf mai’r broblem oedd hyn: nid yw’r cod coroutine, mewn gwirionedd, yn defnyddio setTimeout (neu o leiaf ddim mor aml ag y gallech feddwl): swyddogaeth emscripten_yield yn syml yn gosod y faner alwad asyncronaidd. Yr holl bwynt yw hynny emscripten_coroutine_next nid yw'n swyddogaeth asyncronaidd: yn fewnol mae'n gwirio'r faner, yn ei hailosod ac yn trosglwyddo rheolaeth i'r man lle mae ei hangen. Hynny yw, mae hyrwyddo'r pentwr yn dod i ben yno. Y broblem oedd, oherwydd defnydd di-ddefnydd, a ymddangosodd pan oedd y pwll coroutine yn anabl oherwydd na wnes i gopïo llinell god bwysig o'r backend coroutine presennol, y swyddogaeth qemu_in_coroutine dychwelyd yn wir pan mewn gwirionedd dylai fod wedi dychwelyd ffug. Arweiniodd hyn at alwad emscripten_yield, uwch ben yr hwn nid oedd neb ar y pentwr emscripten_coroutine_next, y pentwr heb eu plygu i'r brig iawn, ond na setTimeout, fel y dywedais eisoes, heb ei arddangos.

Cynhyrchu cod JavaScript

A dyma, mewn gwirionedd, yr addewid “troi’r briwgig yn ôl.” Ddim mewn gwirionedd. Wrth gwrs, os ydym yn rhedeg Qemu yn y porwr, a Node.js ynddo, yna, yn naturiol, ar ôl cynhyrchu cod yn Qemu byddwn yn cael JavaScript hollol anghywir. Ond o hyd, rhyw fath o drawsnewidiad gwrthdro.

Yn gyntaf, ychydig am sut mae Qemu yn gweithio. Maddeuwch i mi ar unwaith: nid wyf yn ddatblygwr Qemu proffesiynol ac efallai bod fy nghasgliadau'n wallus mewn rhai mannau. Fel y dywedant, “nid oes rhaid i farn y myfyriwr gyd-fynd â barn yr athro, axiomatics Peano a synnwyr cyffredin.” Mae gan Qemu nifer penodol o bensaernïaeth gwesteion a gefnogir ac ar gyfer pob un mae yna gyfeiriadur tebyg target-i386. Wrth adeiladu, gallwch chi nodi cefnogaeth ar gyfer sawl pensaernïaeth gwadd, ond dim ond sawl deuaidd fydd y canlyniad. Mae'r cod i gefnogi'r bensaernïaeth westai, yn ei dro, yn cynhyrchu rhai gweithrediadau Qemu mewnol, y mae'r TCG (Tiny Code Generator) eisoes yn troi'n god peiriant ar gyfer pensaernïaeth y gwesteiwr. Fel y nodwyd yn y ffeil readme a leolir yn y cyfeiriadur tcg, roedd hwn yn wreiddiol yn rhan o gasglwr C rheolaidd, a addaswyd yn ddiweddarach ar gyfer JIT. Felly, er enghraifft, nid pensaernïaeth westai yw pensaernïaeth darged o ran y ddogfen hon bellach, ond pensaernïaeth letyol. Ar ryw adeg, ymddangosodd cydran arall - Dehonglydd Cod Tiny (TCI), a ddylai weithredu cod (bron yr un gweithrediadau mewnol) yn absenoldeb generadur cod ar gyfer pensaernïaeth gwesteiwr penodol. Mewn gwirionedd, fel y dywed ei ddogfennaeth, efallai na fydd y cyfieithydd hwn bob amser yn perfformio cystal â generadur cod JIT, nid yn unig yn feintiol o ran cyflymder, ond hefyd yn ansoddol. Er dwi ddim yn siwr fod ei ddisgrifiad yn gwbwl berthnasol.

Ar y dechrau ceisiais wneud backend TCG llawn, ond fe ddrysais yn gyflym yn y cod ffynhonnell a disgrifiad nad oedd yn hollol glir o'r cyfarwyddiadau cod beit, felly penderfynais lapio'r dehonglydd TCI. Rhoddodd hyn nifer o fanteision:

  • wrth weithredu generadur cod, gallech edrych nid ar y disgrifiad o gyfarwyddiadau, ond ar y cod cyfieithydd
  • gallwch chi gynhyrchu swyddogaethau nid ar gyfer pob bloc cyfieithu y deuir ar ei draws, ond, er enghraifft, dim ond ar ôl y canfed gweithredu
  • os bydd y cod a gynhyrchir yn newid (ac mae'n ymddangos bod hyn yn bosibl, a barnu yn ôl y swyddogaethau ag enwau sy'n cynnwys y gair patch), bydd angen i mi annilysu'r cod JS a gynhyrchir, ond o leiaf bydd gennyf rywbeth i'w adfywio ohono

O ran y trydydd pwynt, nid wyf yn siŵr bod clytio yn bosibl ar ôl i'r cod gael ei weithredu am y tro cyntaf, ond mae'r ddau bwynt cyntaf yn ddigon.

I ddechrau, cynhyrchwyd y cod ar ffurf switsh mawr yng nghyfeiriad y cyfarwyddyd bytecode gwreiddiol, ond yna, gan gofio'r erthygl am Emscripten, optimeiddio JS a gynhyrchir ac ail-lopio, penderfynais gynhyrchu mwy o god dynol, yn enwedig gan ei fod yn empirig. troi allan mai'r unig bwynt mynediad i'r bloc cyfieithu yw ei Start. Cyn dweud na gwneud, ar ôl ychydig roedd gennym gynhyrchydd cod a gynhyrchodd god gydag ifs (er heb ddolenni). Ond anlwc, fe chwalodd, gan roi neges bod y cyfarwyddiadau o ryw hyd anghywir. Ar ben hynny, roedd y cyfarwyddyd olaf ar y lefel ailadrodd hon brcond. Iawn, byddaf yn ychwanegu gwiriad union yr un fath at genhedlaeth y cyfarwyddyd hwn cyn ac ar ôl yr alwad ailadroddus a ... ni chafodd yr un ohonynt ei weithredu, ond ar ôl y switsh haeru maent yn dal i fethu. Yn y diwedd, ar ôl astudio'r cod a gynhyrchir, sylweddolais, ar ôl y switsh, bod y pwyntydd i'r cyfarwyddyd cyfredol yn cael ei ail-lwytho o'r pentwr ac mae'n debyg ei fod wedi'i drosysgrifo gan y cod JavaScript a gynhyrchir. Ac felly y trodd allan. Nid oedd cynyddu'r byffer o un megabeit i ddeg yn arwain at unrhyw beth, a daeth yn amlwg bod y generadur cod yn rhedeg mewn cylchoedd. Roedd yn rhaid inni wirio nad oeddem yn mynd y tu hwnt i ffiniau'r TB presennol, ac os gwnaethom, yna rhoi arwydd minws i gyfeiriad y TB nesaf fel y gallem barhau i'w ddienyddio. Yn ogystal, mae hyn yn datrys y broblem “pa swyddogaethau a gynhyrchir a ddylai gael eu hannilysu os yw’r darn hwn o god beit wedi newid?” — dim ond y swyddogaeth sy'n cyfateb i'r bloc cyfieithu hwn sydd angen ei annilysu. Gyda llaw, er fy mod wedi dadfygio popeth yn Chromium (gan fy mod yn defnyddio Firefox ac mae'n haws i mi ddefnyddio porwr ar wahân ar gyfer arbrofion), mae Firefox wedi fy helpu i gywiro anghydnawsedd â'r safon asm.js, ac wedi hynny dechreuodd y cod weithio'n gyflymach yn Cromiwm.

Enghraifft o god a gynhyrchir

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"]

Casgliad

Felly, nid yw'r gwaith wedi'i gwblhau o hyd, ond rwyf wedi blino ar ddod â'r adeiladwaith hirdymor hwn i berffeithrwydd yn gyfrinachol. Felly, penderfynais gyhoeddi’r hyn sydd gennyf ar hyn o bryd. Mae'r cod ychydig yn frawychus mewn mannau, oherwydd arbrawf yw hwn, ac nid yw'n glir ymlaen llaw beth sydd angen ei wneud. Yn ôl pob tebyg, yna mae'n werth cyhoeddi ymrwymiadau atomig arferol ar ben fersiwn fwy modern o Qemu. Yn y cyfamser, mae edefyn yn y Gita mewn fformat blog: ar gyfer pob “lefel” sydd wedi cael ei basio o leiaf rywsut, mae sylwebaeth fanwl yn Rwsieg wedi'i hychwanegu. Mewn gwirionedd, mae'r erthygl hon i raddau helaeth yn ailadrodd y casgliad git log.

Gallwch chi roi cynnig ar y cyfan yma (gwyliwch rhag traffig).

Beth sydd eisoes yn gweithio:

  • x86 prosesydd rhithwir yn rhedeg
  • Mae yna brototeip gweithredol o gynhyrchydd cod JIT o god peiriant i JavaScript
  • Mae yna dempled ar gyfer cydosod pensaernïaeth gwesteion 32-bit eraill: ar hyn o bryd gallwch chi edmygu Linux am rewi pensaernïaeth MIPS yn y porwr yn y cam llwytho

Beth arall allwch chi ei wneud

  • Cyflymu efelychiad. Hyd yn oed yn y modd JIT mae'n ymddangos ei fod yn rhedeg yn arafach na Virtual x86 (ond mae'n bosibl bod Qemu cyfan gyda llawer o galedwedd a phensaernïaeth efelychiedig)
  • I wneud rhyngwyneb arferol - a dweud y gwir, nid wyf yn ddatblygwr gwe da, felly am y tro rwyf wedi ail-wneud y gragen Emscripten safonol orau y gallaf
  • Ceisiwch lansio swyddogaethau Qemu mwy cymhleth - rhwydweithio, mudo VM, ac ati.
  • DIWEDDARIAD: bydd angen i chi gyflwyno eich ychydig o ddatblygiadau ac adroddiadau bygiau i Emscripten i fyny'r afon, fel y gwnaeth cyn borthorion Qemu a phrosiectau eraill. Diolch iddynt am allu defnyddio eu cyfraniad i Emscripten yn ymhlyg fel rhan o fy nhasg.

Ffynhonnell: hab.com

Ychwanegu sylw