Qemu.js le taic JIT: faodaidh tu fhathast am mince a thionndadh air ais

O chionn beagan bhliadhnaichean Fabrice Bellard air a sgrìobhadh le jslinux na emuladair PC sgrìobhte ann an JavaScript. Às deidh sin bha co-dhiù barrachd ann Mas-fhìor x86.. Ach bha iad uile, cho fad ‘s as aithne dhomh, nan eadar-mhìnearan, agus bha Qemu, air a sgrìobhadh mòran na bu thràithe leis an aon Fabrice Bellard, agus, is dòcha, emuladair ùr-nodha fèin-spèis sam bith, a’ cleachdadh cruinneachadh JIT de chòd aoighean a-steach do chòd siostam aoigheachd. Bha e coltach riumsa gu robh an t-àm ann a’ ghnìomh eile a chuir an gnìomh a thaobh an fhear a dh’ fhuasglas brobhsairean: cruinneachadh JIT de chòd inneal a-steach do JavaScript, airson an robh e coltach gu robh e cho reusanta port Qemu. Bhiodh e coltach, carson Qemu, tha emuladairean nas sìmplidh agus nas fhasa a chleachdadh - an aon VirtualBox, mar eisimpleir - air an stàladh agus ag obair. Ach tha grunn fheartan inntinneach aig Qemu

  • stòr fosgailte
  • comas a bhith ag obair gun kernel driver
  • comas a bhith ag obair ann am modh eadar-theangair
  • taic airson àireamh mhòr de ailtireachd aoigheachd is aoighean

A thaobh an treas puing, is urrainn dhomh a-nis a mhìneachadh, gu dearbh, ann am modh TCI, nach e an stiùireadh inneal aoighean iad fhèin a tha air a mhìneachadh, ach am bytecode a gheibhear bhuapa, ach chan eil seo ag atharrachadh brìgh - gus togail agus ruith Qemu air ailtireachd ùr, ma tha thu fortanach, tha compiler A C gu leòr - faodar gineadair còd a sgrìobhadh a chuir dheth.

Agus a-nis, às deidh dà bhliadhna de bhith ag obair gu socair le còd stòr Qemu anns an ùine shaor agam, nochd prototype obrach, anns am faod thu ruith mar-thà, mar eisimpleir, Kolibri OS.

Dè th' ann an Emscripten

An-diugh, tha mòran de luchd-cruinneachaidh air nochdadh, agus is e JavaScript an toradh deireannach. Bha cuid, mar Type Script, an dùil an toiseach a bhith mar an dòigh as fheàrr air sgrìobhadh airson an lìon. Aig an aon àm, tha Emscripten na dhòigh air còd C no C ++ a tha ann mu thràth a ghabhail agus a chuir ri chèile ann an cruth a ghabhas leughadh le brabhsair. Air adhart an duilleag seo Tha sinn air iomadh port de phrògraman ainmeil a chruinneachadh: an seoMar eisimpleir, faodaidh tu coimhead air PyPy - leis an t-slighe, tha iad ag ràdh gu bheil JIT aca mu thràth. Gu dearbh, chan urrainn a h-uile prògram a bhith air a chur ri chèile gu sìmplidh agus air a ruith ann am brabhsair - tha àireamh ann feartan, a dh’ fheumas tu cur suas, ge-tà, leis gu bheil an sgrìobhadh air an aon duilleag ag ràdh “Faodar Emscripten a chleachdadh gus cha mhòr gin sam bith a chur ri chèile. so-ghiùlain Còd C/C++ gu JavaScript". Is e sin, tha grunn obrachaidhean ann a tha nan giùlan neo-mhìnichte a rèir na h-ìre, ach mar as trice ag obair air x86 - mar eisimpleir, ruigsinneachd neo-ainmichte air caochladairean, a tha sa chumantas toirmisgte air cuid de dh’ ailtireachd. San fharsaingeachd , Is e prògram tar-àrd-ùrlar a th’ ann an Qemu agus , bha mi airson a chreidsinn, agus chan eil mòran giùlan neo-mhìnichte ann mu thràth - gabh e agus cuir ri chèile, an uairsin tinker beagan le JIT - agus tha thu deiseil! cùis...

A ’chiad oidhirp

San fharsaingeachd, chan e mise a’ chiad neach a thàinig a-steach don bheachd a bhith a’ giùlain Qemu gu JavaScript. Chaidh ceist fhaighneachd air fòram ReactOS an robh seo comasach le bhith a’ cleachdadh Emscripten. Fiù ‘s na bu thràithe, bha fathannan ann gun do rinn Fabrice Bellard seo gu pearsanta, ach bha sinn a’ bruidhinn mu dheidhinn jslinux, a tha, cho fad ‘s as aithne dhomh, dìreach oidhirp air coileanadh gu leòr a choileanadh le làimh ann an JS, agus chaidh a sgrìobhadh bhon toiseach. Nas fhaide air adhart, chaidh Virtual x86 a sgrìobhadh - chaidh stòran neo-ghluasadach a phostadh air a shon, agus, mar a chaidh a ràdh, bha barrachd “fìrinneachd” den aithris ga dhèanamh comasach SeaBIOS a chleachdadh mar firmware. A bharrachd air an sin, bha co-dhiù aon oidhirp air Qemu a phortadh a’ cleachdadh Emscripten – dh’fheuch mi ri seo a dhèanamh socaid, ach bha leasachadh, cho fad 's a tha mi tuigsinn, reòta.

Mar sin, bhiodh e coltach, seo na stòran, seo Emscripten - gabh e agus cuir ri chèile. Ach tha leabharlannan ann cuideachd air a bheil Qemu an urra, agus leabharlannan air a bheil na leabharlannan sin an urra, msaa, agus tha aon dhiubh libffi, a tha glib an urra. Bha fathannan air an eadar-lìon gun robh fear anns a’ chruinneachadh mhòr de phuirt leabharlannan airson Emscripten, ach bha e car duilich a chreidsinn: an toiseach, cha robh e an dùil a bhith na neach-cruinneachaidh ùr, san dàrna àite, bha e ro ìosal aig ìre a leabharlann dìreach airson a thogail, agus a chur ri chèile gu JS. Agus chan e dìreach ceist mu chuir a-steach co-chruinneachaidh a th’ ann - is dòcha, ma thionndaidheas tu e, airson cuid de ghnàthasan gairm faodaidh tu na h-argamaidean riatanach a ghineadh air a’ chruach agus an gnìomh a ghairm às an aonais. Ach is e rud duilich a th’ ann an Emscripten: gus am bi an còd a chaidh a chruthachadh a ’coimhead eòlach air inneal-optimachaidh einnsean JS, thathas a’ cleachdadh cuid de chleasan. Gu sònraichte, tha an ath-chuairteachadh ris an canar - gineadair còd a ’cleachdadh an LLVM IR a fhuaireadh le beagan stiùiridhean gluasaid eas-chruthach a’ feuchainn ri ifs so-chreidsinneach, lùban, msaa ath-chruthachadh. Uill, ciamar a tha na h-argamaidean air an toirt seachad don ghnìomh? Gu nàdarra, mar argamaidean gu gnìomhan JS, is e sin, ma ghabhas e dèanamh, chan ann tron ​​chruach.

Aig an toiseach bha beachd ann dìreach sgrìobhadh airson libffi le JS agus deuchainnean àbhaisteach a ruith, ach aig a’ cheann thall bha mi troimh-chèile mu mar a dhèanadh mi na faidhlichean cinn agam gus an obraicheadh ​​​​iad leis a’ chòd a th’ ann - dè as urrainn dhomh a dhèanamh, mar a chanas iad, "A bheil na gnìomhan cho iom-fhillte "A bheil sinn cho gòrach?" B’ fheudar dhomh libffi a phortadh gu ailtireachd eile, mar sin a bhruidhinn - gu fortanach, tha an dà mhacra aig Emscripten airson co-chruinneachadh in-loidhne (ann an Javascript, seadh - uill, ge bith dè an ailtireachd, mar sin an assembler), agus an comas còd a ruith air a chruthachadh air an itealan. San fharsaingeachd, às deidh dhomh a bhith a’ tinkering le criomagan libffi a bha an urra ri àrd-ùrlar airson ùine, fhuair mi còd a ghabhas ullachadh agus ruith mi air a’ chiad deuchainn air an tàinig mi tarsainn. Gu mo iongnadh, bha an deuchainn soirbheachail. Air mo shàrachadh le mo shòlas - gun fealla-dhà, dh ’obraich e bhon chiad fhoillseachadh - chaidh mi, gun a bhith a’ creidsinn mo shùilean fhathast, a choimhead air a ’chòd a thàinig às a-rithist, gus measadh càite an tèid mi a chladhach. An seo chaidh mi às ùr airson an dàrna turas - b’ e an aon rud a rinn mo ghnìomh ffi_call - thug seo cunntas air gairm soirbheachail. Cha robh call ann fhèin. Mar sin chuir mi a’ chiad iarrtas tarraing-a-mach agam, a cheartaich mearachd san deuchainn a tha soilleir do oileanach Olympiad sam bith - cha bu chòir àireamhan fìor a choimeas mar a == b agus eadhon ciamar a - b < EPS - feumaidh tu cuideachd cuimhneachadh air a’ mhodal, air neo bidh 0 gu math co-ionann ri 1/3... San fharsaingeachd, thàinig mi suas le port sònraichte de libffi, a thèid seachad air na deuchainnean as sìmplidh, agus leis a bheil glib air a chur ri chèile - cho-dhùin mi gum biodh feum air, cuiridh mi ris nas fhaide air adhart. A’ coimhead air adhart, canaidh mi, mar a thionndaidh e a-mach, nach robh an compiler eadhon a’ toirt a-steach gnìomh libffi anns a’ chòd mu dheireadh.

Ach, mar a thuirt mi mu thràth, tha cuid de chuingealachaidhean ann, agus am measg cleachdadh an-asgaidh de dhiofar ghiùlan neo-mhìnichte, tha feart nas mì-thlachdmhor air a bhith falaichte - chan eil JavaScript le dealbhadh a ’toirt taic do multithreading le cuimhne co-roinnte. Ann am prionnsapal, mar as trice is e deagh bheachd a chanar ris an seo, ach chan ann airson còd giùlain aig a bheil ailtireachd ceangailte ri snàithleanan C. San fharsaingeachd, tha Firefox a’ feuchainn ri taic a thoirt do luchd-obrach co-roinnte, agus tha buileachadh pthread aig Emscripten dhaibh, ach cha robh mi airson a bhith an urra ris. B’ fheudar dhomh ioma-snàthainn a thoirt a-mach gu slaodach bhon chòd Qemu - is e sin, faighinn a-mach càite a bheil na snàithleanan a’ ruith, corp na lùb a tha a’ ruith san t-snàthainn seo a ghluasad gu gnìomh air leth, agus na gnìomhan sin a ghairm aon às deidh aon bhon phrìomh lùb.

An dàrna oidhirp

Aig àm air choreigin, dh'fhàs e soilleir gu robh an duilgheadas ann fhathast, agus nach toireadh sin math sam bith a bhith a’ putadh crutches timcheall a’ chòd. Co-dhùnadh: feumaidh sinn dòigh air choireigin siostamachadh a dhèanamh air a’ phròiseas airson crutches a chur ris. Mar sin, chaidh dreach 2.4.1, a bha ùr aig an àm sin, a thogail (chan e 2.5.0, oir, cò aig a tha fios, bidh biastagan anns an dreach ùr nach deach a ghlacadh fhathast, agus tha gu leòr de na mialan agam fhèin agam. ), agus b’ e a’ chiad rud ath-sgrìobhadh gu sàbhailte thread-posix.c. Uill, is e sin, cho sàbhailte: ma dh'fheuch cuideigin ri gnìomhachd a dhèanamh a lean gu bacadh, chaidh an gnìomh a ghairm sa bhad abort() - gu dearbh, cha do dh'fhuasgladh seo a h-uile duilgheadas aig an aon àm, ach co-dhiù bha e dòigh air choireigin nas tlachdmhoire na bhith a 'faighinn dàta neo-chunbhalach gu sàmhach.

San fharsaingeachd, tha roghainnean Emscripten gu math cuideachail ann a bhith a’ gluasad còd gu JS -s ASSERTIONS=1 -s SAFE_HEAP=1 - bidh iad a’ glacadh cuid de sheòrsan giùlan neo-mhìnichte, leithid fiosan gu seòladh neo-ainmichte (nach eil idir co-chòrdail ris a’ chòd airson arrays clò-sgrìobhte mar HEAP32[addr >> 2] = 1) no gairm gnìomh leis an àireamh ceàrr de argamaidean.

Air an t-slighe, tha mearachdan co-thaobhadh nan cùis fa leth. Mar a thuirt mi mu thràth, tha backend mìneachaidh “crìonadh” aig Qemu airson gineadh còd TCI (eadar-theangair còd beag), agus airson Qemu a thogail agus a ruith air ailtireachd ùr, ma tha thu fortanach, tha compiler C gu leòr. "ma tha thu fortanach". Bha mi mì-fhortanach, agus thionndaidh e a-mach gu bheil TCI a’ cleachdadh ruigsinneachd neo-ainmichte nuair a bhios e a’ parsadh a chòd byte. Is e sin, air a h-uile seòrsa ARM agus ailtireachd eile le ruigsinneachd riatanach leveled, bidh Qemu a’ cur ri chèile leis gu bheil backend àbhaisteach TCG aca a ghineas còd dùthchasach, ach is e ceist eile a th’ ann an obraich TCI orra. Ach, mar a thàinig e a-mach, bha sgrìobhainnean TCI gu soilleir a’ nochdadh rudeigin coltach ris. Mar thoradh air an sin, chaidh gairmean gnìomh airson leughadh neo-ainmichte a chur ris a’ chòd, a chaidh a lorg ann am pàirt eile de Qemu.

sgrios mòr

Mar thoradh air an sin, chaidh ruigsinneachd neo-ainmichte air TCI a cheartachadh, chaidh prìomh lùb a chruthachadh ris an canar am pròiseasar, RCU agus cuid de rudan beaga eile. Agus mar sin bidh mi a’ cur air bhog Qemu leis an roghainn -d exec,in_asm,out_asm, a tha a 'ciallachadh gum feum thu a ràdh dè na blocaichean còd a thathar a' cur an gnìomh, agus cuideachd aig àm a 'chraolaidh gus sgrìobhadh dè an còd aoigheachd a bh' ann, dè an còd aoigheachd a thàinig gu bhith (anns a 'chùis seo, bytecode). Bidh e a’ tòiseachadh, a’ cur grunn bhlocaichean eadar-theangachaidh an gnìomh, a’ sgrìobhadh an teachdaireachd deasbaid a dh’ fhàg mi gun tòisich RCU a-nis agus... a’ tuiteam abort() taobh a-staigh gnìomh free(). Le bhith a 'tinkering leis a' ghnìomh free() Chaidh againn air faighinn a-mach gun robh sgudal ann am bann-cinn a’ bhloca tiùrr, a tha na laighe anns na h-ochd bytes ron chuimhne a chaidh a shònrachadh, an àite meud a’ bhloca no rudeigin coltach ris.

A 'sgrios a' charn - cho bòidheach ... Ann an leithid de chùis, tha leigheas feumail - bho (ma ghabhas e dèanamh) na h-aon stòran, cruinnich binary dùthchasach agus ruith e fo Valgrind. Às deidh beagan ùine, bha am binary deiseil. Bidh mi ga chuir air bhog leis na h-aon roghainnean - bidh e a’ tuiteam eadhon aig àm tòiseachaidh, mus ruig e gu bàs. Tha e mì-thlachdmhor, gu dearbh - a rèir choltais, cha robh na stòran dìreach mar an ceudna, rud nach eil na iongnadh, oir tha rèiteachadh a ’lorg roghainnean beagan eadar-dhealaichte, ach tha Valgrind agam - an toiseach cuiridh mi am biast seo air dòigh, agus an uairsin, ma tha mi fortanach , nochdaidh am fear tùsail. Tha mi a’ ruith an aon rud fo Valgrind... Y-y-y, y-y, uh-uh, thòisich e, chaidh e tro thòiseachadh gu h-àbhaisteach agus ghluais e air adhart seachad air a’ bhiast thùsail gun aon rabhadh mu ruigsinneachd cuimhne ceàrr, gun luaidh air easan. Cha do dh'ullaich beatha, mar a chanas iad, mi airson seo - bidh prògram tubaist a 'stad nuair a thèid a chuir air bhog fo Walgrind. Is e dìomhaireachd a bh’ ann an rud a bh’ ann. Is e mo bheachd-sa, aon uair faisg air an stiùireadh gnàthach às deidh tubaist aig àm tòiseachaidh, sheall gdb obair memset-a le puing dligheach a’ cleachdadh an dàrna cuid mmx, or xmm clàran, an uairsin is dòcha gur e mearachd co-thaobhadh de sheòrsa air choreigin a bh’ ann, ged a tha e fhathast duilich a chreidsinn.

Ceart gu leòr, chan eil e coltach gu bheil Valgrind a’ cuideachadh an seo. Agus an seo thòisich an rud as tàmailteach - tha e coltach gu bheil a h-uile dad eadhon a ’tòiseachadh, ach a’ tuiteam airson adhbharan gu tur neo-aithnichte air sgàth tachartas a dh ’fhaodadh a bhith air tachairt o chionn milleanan de stiùiridhean. Airson ùine mhòr, cha robh e eadhon soilleir ciamar a dhèiligeas tu ris. Aig a’ cheann thall, bha agam ri suidhe sìos agus deasbaid fhathast. Sheall clò-bhualadh na chaidh ath-sgrìobhadh leis a’ cheann nach robh e coltach ri àireamh, ach seòrsa de dhàta binary. Agus, feuch agus feuch, chaidh an sreang binary seo a lorg anns an fhaidhle BIOS - is e sin, a-nis bha e comasach a ràdh le misneachd reusanta gur e tar-shruth bufair a bh ’ann, agus tha e eadhon soilleir gun deach a sgrìobhadh chun bhufair seo. Uill, an uairsin rudeigin mar seo - ann an Emscripten, gu fortanach, chan eil àite seòlaidh air thuaiream, chan eil tuill ann nas motha, gus an urrainn dhut sgrìobhadh an àiteigin ann am meadhan a’ chòd gus dàta a chuir a-mach le puing bhon fhoillseachadh mu dheireadh, thoir sùil air an dàta, thoir sùil air a’ phuing, agus, mura h-eil e air atharrachadh, faigh biadh airson smaoineachadh. Fìor, bheir e mionaid no dhà airson ceangal a dhèanamh às deidh atharrachadh sam bith, ach dè as urrainn dhut a dhèanamh? Mar thoradh air an sin, chaidh loidhne shònraichte a lorg a bha a 'dèanamh lethbhreac den BIOS bhon bhufair sealach gu cuimhne an aoigh - agus, gu dearbh, cha robh àite gu leòr anns a' bhufair. Le bhith a’ lorg tùs an t-seòlaidh bufair neònach sin thàinig gnìomh qemu_anon_ram_alloc ann am faidhle oslib-posix.c - an loidsig a bha seo: uaireannan faodaidh e a bhith feumail an seòladh a cho-thaobhadh ri duilleag mhòr de 2 MB ann am meud, airson seo iarraidh sinn mmap an toiseach beagan a bharrachd, agus an uairsin tillidh sinn an còrr le cuideachadh munmap. Agus mura h-eil feum air a leithid de cho-thaobhadh, seallaidh sinn an toradh an àite 2 MB getpagesize() - mmap bheir e seachad seòladh co-thaobhach fhathast... Mar sin ann an Emscripten mmap dìreach gairmean malloc, ach gu dearbh chan eil e co-thaobhadh ris an duilleag. San fharsaingeachd, chaidh biast a chuir sàrachadh orm airson mìos no dhà a cheartachadh le atharrachadh ann an dhà sreathan.

Feartan gnìomhan gairm

Agus a-nis tha am pròiseasar a ’cunntadh rudeigin, chan eil Qemu a’ tuiteam, ach chan eil an scrion a ’tionndadh air, agus bidh am pròiseasar gu sgiobalta a’ dol a-steach do lùban, a ’breithneachadh leis an toradh -d exec,in_asm,out_asm. Tha beachd-bharail air nochdadh: chan eil briseadh timer (no, san fharsaingeachd, a h-uile briseadh) a’ ruighinn. Agus gu dearbh, ma bheir thu air falbh na brisidhean bhon cho-chruinneachadh dùthchasach, a dh ’obraich airson adhbhar air choireigin, gheibh thu dealbh coltach ris. Ach cha b 'e seo am freagairt idir: sheall coimeas de na comharran a chaidh a thoirt a-mach leis an roghainn gu h-àrd gu robh na slighean cur gu bàs a' dol eadar-dhealaichte gu math tràth. An seo feumar a ràdh gu bheil coimeas eadar na chaidh a chlàradh a ’cleachdadh an lannsair emrun chan e pròiseas gu tur meacanaigeach a th’ ann an toradh debugging le toradh a’ cho-chruinneachaidh dhùthchasach. Chan eil fios agam gu cinnteach ciamar a tha prògram a tha a’ ruith ann am brobhsair a’ ceangal ris emrun, ach tha coltas gu bheil cuid de loidhnichean san toradh air an ath-eagrachadh, agus mar sin chan eil an eadar-dhealachadh anns an diff fhathast na adhbhar airson gabhail ris gu bheil na slighean air a dhol eadar-dhealaichte. San fharsaingeachd, dh'fhàs e soilleir gu bheil a rèir an stiùiridh ljmpl tha gluasad gu diofar sheòlaidhean ann, agus tha am bytecode a chaidh a chruthachadh gu bunaiteach eadar-dhealaichte: tha stiùireadh ann an aon airson gnìomh neach-cuideachaidh a ghairm, chan eil am fear eile. An dèidh googling an stiùireadh agus a 'sgrùdadh a' chòd a tha ag eadar-theangachadh an stiùireadh seo, dh'fhàs e soilleir, an toiseach, dìreach roimhe anns a 'chlàr cr0 chaidh clàradh a dhèanamh - cuideachd a’ cleachdadh neach-cuideachaidh - a thionndaidh am pròiseasar gu modh dìonta, agus san dàrna àite, nach do thionndaidh an tionndadh js gu modh dìon. Ach is e an fhìrinn gur e feart eile de Emscripten gu bheil e deònach gabhail ri còd leithid cur an gnìomh stiùiridhean call ann an TCI, a bhios comharradh gnìomh sam bith a’ leantainn ann an seòrsa long long f(int arg0, .. int arg9) - feumar gnìomhan a ghairm leis an àireamh cheart de argamaidean. Ma thèid an riaghailt seo a bhriseadh, a rèir nan roghainnean deasbaid, tuitidh am prògram (rud a tha math) no gairmidh e an gnìomh ceàrr idir (a bhios duilich a dheasbad). Tha an treas roghainn ann cuideachd - comas a thoirt do bhith a’ gineadh pasgain a chuireas ris / a bheir air falbh argamaidean, ach gu h-iomlan bidh na pasgain sin a’ gabhail tòrr àite, a dh’ aindeoin ‘s gu dearbh nach fheum mi ach beagan a bharrachd air ceud pasgain. Tha seo na aonar gu math brònach, ach bha duilgheadas nas miosa ann: anns a 'chòd a chaidh a chruthachadh de na gnìomhan còmhdaich, chaidh na h-argamaidean a thionndadh agus a thionndadh, ach uaireannan cha deach an gnìomh leis na h-argamaidean a chaidh a chruthachadh a ghairm - uill, dìreach mar a bha e. mo bhuileachadh libffi. Is e sin, cha deach cuid de luchd-cuideachaidh a chuir gu bàs.

Gu fortanach, tha liostaichean luchd-cuideachaidh aig Qemu a ghabhas leughadh le inneal ann an cruth faidhle cinn mar

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

Tha iad air an cleachdadh gu math èibhinn: an toiseach, tha macros air an ath-mhìneachadh anns an dòigh as annasaiche DEF_HELPER_n, agus an uairsin tionndaidh air helper.h. Chun na h-ìre gu bheil am macro air a leudachadh gu bhith na neach-tòiseachaidh structar agus cromag, agus an uairsin tha sreath air a mhìneachadh, agus an àite eileamaidean - #include <helper.h> Mar thoradh air an sin, bha cothrom agam mu dheireadh feuchainn air an leabharlann aig an obair piparsadh, agus chaidh sgriobt a sgrìobhadh a tha a’ gineadh dìreach na còmhdaichean sin airson dìreach na gnìomhan air a bheil feum orra.

Agus mar sin, às deidh sin bha coltas gu robh am pròiseasar ag obair. Tha e coltach gur ann air sgàth nach deach an sgrion a thòiseachadh a-riamh, ged a bha e comasach dha memtest86+ ruith anns a’ cho-chruinneachadh dùthchasach. An seo feumar soilleireachadh gu bheil còd I/O bloc Qemu sgrìobhte ann an coroutines. Tha a bhuileachadh gu math duilich fhèin aig Emscripten, ach bha feum air taic a thoirt dha ann an còd Qemu fhathast, agus faodaidh tu am pròiseasar a dheasbad a-nis: tha Qemu a’ toirt taic do roghainnean -kernel, -initrd, -append, leis an urrainn dhut Linux a thòiseachadh no, mar eisimpleir, memtest86 +, gun a bhith a’ cleachdadh innealan bloca idir. Ach seo an duilgheadas: anns a’ cho-chruinneachadh dhùthchasach chitheadh ​​​​duine toradh kernel Linux chun consol leis an roghainn -nographic, agus gun toradh bhon bhrobhsair chun cheann-uidhe bhon deach a chuir air bhog emrun, cha tàinig. Is e sin, chan eil e soilleir: chan eil am pròiseasar ag obair no chan eil toradh grafaigean ag obair. Agus an uairsin thachair e rium feitheamh beagan. Thionndaidh e a-mach “chan eil am pròiseasar a’ cadal, ach dìreach a ’brùthadh gu slaodach,” agus às deidh timcheall air còig mionaidean thilg an kernel dòrlach de theachdaireachdan air a ’chonsól agus lean i a’ crochadh. Dh'fhàs e soilleir gu bheil am pròiseasar, san fharsaingeachd, ag obair, agus feumaidh sinn a dhol a-steach don chòd airson a bhith ag obair le SDL2. Gu mì-fhortanach, chan eil fios agam ciamar a chleachdas mi an leabharlann seo, agus mar sin ann an cuid de dh'àiteachan bha agam ri bhith ag obair air thuaiream. Aig àm air choreigin, nochd an loidhne co-shìnte0 air an sgrion air cùl gorm, a mhol cuid de bheachdan. Aig a ’cheann thall, thionndaidh e a-mach gur e an duilgheadas a bh’ ann gu bheil Qemu a ’fosgladh grunn uinneagan brìgheil ann an aon uinneag fiosaigeach, eadar an urrainn dhut tionndadh le bhith a’ cleachdadh Ctrl-Alt-n: bidh e ag obair anns an togail dhùthchasach, ach chan ann ann an Emscripten. An dèidh faighinn cuidhteas uinneagan neo-riatanach a 'cleachdadh roghainnean -monitor none -parallel none -serial none agus stiùireadh airson an scrion gu lèir ath-tharraing gu làidir air gach frèam, dh’ obraich a h-uile dad gu h-obann.

Coroutines

Mar sin, bidh atharrais sa bhrobhsair ag obair, ach chan urrainn dhut rud sam bith inntinneach a ruith ann an aon-fhillte, oir chan eil bloc I / O ann - feumaidh tu taic airson coroutines a chuir an gnìomh. Tha grunn backendine coroutine aig Qemu mu thràth, ach air sgàth nàdar JavaScript agus gineadair còd Emscripten, chan urrainn dhut dìreach tòiseachadh air stacan a dhèanamh. Bhiodh e coltach gu bheil “a h-uile càil air falbh, tha am plàstair ga thoirt air falbh,” ach tha luchd-leasachaidh Emscripten air aire a thoirt don h-uile càil mu thràth. Tha seo air a chuir an gnìomh gu math èibhinn: canaidh sinn gairm gnìomh mar seo amharasach emscripten_sleep agus grunn eile a’ cleachdadh an uidheamachd Asyncify, a bharrachd air fiosan stiùiridh agus fiosan gu gnìomh sam bith far am faodadh aon den dà chùis roimhe tachairt nas fhaide sìos a’ chruach. Agus a-nis, ro gach gairm amharasach, taghaidh sinn co-theacs async, agus dìreach às deidh a’ ghairm, nì sinn sgrùdadh a bheil gairm asyncronach air tachairt, agus ma tha, sàbhailidh sinn a h-uile caochladair ionadail anns a ’cho-theacsa async seo, comharraich dè an gnìomh gus smachd a ghluasad gu nuair a dh’ fheumas sinn cumail oirnn a’ cur gu bàs, agus an gnìomh làithreach fhàgail. Seo far a bheil cothrom ann sgrùdadh a dhèanamh air a’ bhuaidh a' caitheamh - airson feumalachdan cur an gnìomh còd leantainneach às deidh dha tilleadh bho ghairm asyncronach, bidh an neach-cruinneachaidh a’ gineadh “stubs” den ghnìomh a’ tòiseachadh às deidh gairm amharasach - mar seo: ma tha n fiosan amharasach ann, thèid an gnìomh a leudachadh an àiteigin n/2 amannan - tha seo fhathast, mura h-eil Cumaibh cuimhne, às deidh gach gairm a dh’ fhaodadh a bhith asyncronach, feumaidh tu cuid de chaochladairean ionadail a shàbhaladh ris a ’ghnìomh thùsail. Às deidh sin, bha agam eadhon ri sgriobt sìmplidh a sgrìobhadh ann am Python, a tha, stèidhichte air seata sònraichte de ghnìomhan a tha gu sònraichte cus feum agus a rèir coltais “nach leig le asynchrony a dhol troimhe fhèin” (is e sin, brosnachadh stac agus a h-uile dad a thuirt mi nach eil. obair annta), a’ comharrachadh fiosan tro chomharran anns am bu chòir don neach-cruinneachaidh na gnìomhan sin a leigeil seachad gus nach bi na gnìomhan sin air am meas mar asyncronach. Agus an uairsin tha faidhlichean JS fo 60 MB gu soilleir ro mhòr - canaidh sinn co-dhiù 30. Ged, aon uair ‘s gu robh mi a’ stèidheachadh sgriobt cruinneachaidh, agus gun fhiosta thilg mi a-mach na roghainnean ceangail, am measg sin bha -O3. Bidh mi a 'ruith a' chòd a chaidh a chruthachadh, agus bidh Chromium ag ithe suas cuimhne agus a 'tuiteam. Thug mi sùil an uairsin gun fhiosta air na bha e a’ feuchainn ri luchdachadh sìos... Uill, dè as urrainn dhomh a ràdh, bhithinn air reothadh cuideachd nan deach iarraidh orm sgrùdadh smaoineachail a dhèanamh air Javascript 500+ MB agus a bharrachadh.

Gu mì-fhortanach, cha robh na sgrùdaidhean ann an còd leabharlainn taic Asyncify gu tur càirdeil leotha longjmp-s a thathas a’ cleachdadh anns a ’chòd pròiseasar brìgheil, ach às deidh bad beag a chuireas às do na sgrùdaidhean sin agus ag ath-nuadhachadh cho-theacsan gu làidir mar gum biodh a h-uile dad gu math, dh’ obraich an còd. Agus an uairsin thòisich rud neònach: uaireannan chaidh sgrùdaidhean anns a’ chòd sioncronaidh a bhrosnachadh - an aon fheadhainn a thuiteas air a’ chòd ma bu chòir, a rèir loidsig gnìomhachaidh, a bhacadh - dh’ fheuch cuideigin ri grèim fhaighinn air mutex a chaidh a ghlacadh mar-thà. Gu fortanach, cha b’ e duilgheadas loidsigeach a bha seo anns a’ chòd sreathach - bha mi dìreach a’ cleachdadh a’ phrìomh ghnìomh prìomh lùb a thug Emscripten seachad, ach uaireannan bhiodh an gairm asyncronach a’ sgaoileadh a’ chruach gu tur, agus aig an àm sin dh’ fhàilnich e. setTimeout bhon phrìomh lùb - mar sin, chaidh an còd a-steach don phrìomh lùb lùb gun a bhith a 'fàgail an tionndadh roimhe. Ath-sgrìobh air lùb gun chrìoch agus emscripten_sleep, agus sguir na duilgheadasan le mutexes. Tha an còd eadhon nas loidsigeach - às deidh a h-uile càil, gu dearbh, chan eil còd agam a bhios ag ullachadh an ath fhrèam beothalachd - bidh am pròiseasar dìreach a ’tomhas rudeigin agus tha an scrion air ùrachadh bho àm gu àm. Ach, cha do stad na duilgheadasan an sin: uaireannan bhiodh cur gu bàs Qemu dìreach a’ tighinn gu crìch gu sàmhach gun eisgeachdan no mearachdan sam bith. Aig an àm sin thug mi suas e, ach, a’ coimhead air adhart, canaidh mi gur e seo an duilgheadas: chan eil an còd coroutine, gu dearbh, a’ cleachdadh setTimeout (no co-dhiù chan ann cho tric sa dh’ fhaodadh tu smaoineachadh): gnìomh emscripten_yield dìreach suidhich a’ bhratach gairm asyncronach. Is e a’ phuing gu lèir sin emscripten_coroutine_next chan e gnìomh asyncronach a th’ ann: air an taobh a-staigh bidh e a’ sgrùdadh a’ bhratach, ga ath-shuidheachadh agus a’ gluasad smachd gu far a bheil feum air. Is e sin, tha adhartachadh a’ chruach a’ tighinn gu crìch an sin. B ’e an duilgheadas a bh’ ann mar thoradh air cleachdadh às deidh-saor, a nochd nuair a chaidh an amar coroutine a chiorramachadh leis nach do rinn mi leth-bhreac de loidhne chòd cudromach bhon backend coroutine a th ’ann, an gnìomh qemu_in_coroutine air ais fìor nuair a bu chòir dha a bhith air tilleadh ceàrr. Dh'adhbhraich seo gairm emscripten_yield, os cionn nach robh duine air a' chruaich emscripten_coroutine_next, dh'fhosgail an stac chun a 'mhullaich, ach chan eil setTimeout, mar a thuirt mi cheana, nach deach a thaisbeanadh.

Gineadh còd javascript

Agus an seo, gu dearbh, tha an gealladh “a’ tionndadh a ’mhion-fheòil air ais.” Chan eil idir. Gu dearbh, ma ruitheas sinn Qemu sa bhrobhsair, agus Node.js ann, an uairsin, gu nàdarra, às deidh gineadh còd ann an Qemu gheibh sinn JavaScript gu tur ceàrr. Ach fhathast, seòrsa de chruth-atharrachadh air ais.

An toiseach, beagan mu mar a tha Qemu ag obair. Feuch an toir thu mathanas dhomh anns a’ bhad: chan e leasaiche proifeasanta Qemu a th ’annam agus is dòcha gu bheil mo cho-dhùnaidhean mearachdach ann an cuid de dh’ àiteachan. Mar a chanas iad, “chan fheum beachd an oileanach a bhith aig an aon àm ri beachd an tidseir, axiomatics Peano agus mothachadh cumanta.” Tha àireamh sònraichte de ailtireachd aoighean le taic aig Qemu agus airson gach fear tha eòlaire mar target-i386. Nuair a bhios tu a’ togail, faodaidh tu taic a shònrachadh airson grunn ailtireachd aoighean, ach bidh an toradh dìreach grunn binaries. Bidh an còd gus taic a thoirt don ailtireachd aoighean, an uair sin, a’ gineadh cuid de ghnìomhachdan Qemu a-staigh, a tha an TCG (Tiny Code Generator) mu thràth a’ tionndadh gu còd inneal airson an ailtireachd aoigheachd. Mar a chaidh a ràdh anns an fhaidhle readme a tha suidhichte anns an eòlaire tcg, bha seo an toiseach mar phàirt de cho-chruinneachadh C cunbhalach, a chaidh atharrachadh a-rithist airson JIT. Mar sin, mar eisimpleir, chan e ailtireachd aoighean a th’ ann an ailtireachd targaid a thaobh na sgrìobhainn seo tuilleadh, ach ailtireachd aoigheachd. Aig àm air choreigin, nochd pàirt eile - Tiny Code Interpreter (TCI), a bu chòir còd a chuir an gnìomh (cha mhòr an aon obair a-staigh) às aonais gineadair còd airson ailtireachd aoigheachd sònraichte. Gu dearbh, mar a tha na sgrìobhainnean aige ag ràdh, is dòcha nach bi an eadar-theangair seo an-còmhnaidh a’ coileanadh cho math ri gineadair còd JIT, chan ann a-mhàin gu cainneachdail a thaobh astar, ach cuideachd gu càileachdail. Ged nach eil mi cinnteach gu bheil an tuairisgeul aige gu tur buntainneach.

An toiseach dh’ fheuch mi ri backend TCG làn-chuimseach a dhèanamh, ach gu sgiobalta fhuair mi troimh-chèile anns a’ chòd stòr agus tuairisgeul nach robh gu tur soilleir air an stiùireadh bytecode, agus mar sin chuir mi romham an eadar-theangair TCI a phasgadh. Thug seo grunn bhuannachdan:

  • nuair a chuireas tu gineadair còd an gnìomh, dh’ fhaodadh tu coimhead chan ann air an tuairisgeul air an stiùireadh, ach air còd an eadar-mhìneachaidh
  • faodaidh tu gnìomhan a ghineadh chan ann airson a h-uile bloc eadar-theangachaidh a choinnicheas tu, ach, mar eisimpleir, dìreach às deidh a’ cheudamh coileanadh
  • ma dh’ atharraicheas an còd a chaidh a ghineadh (agus tha coltas gu bheil seo comasach, a’ breithneachadh a rèir nan gnìomhan le ainmean anns a bheil am facal paiste), feumaidh mi an còd JS a chaidh a chruthachadh a dhì-dhligheachadh, ach co-dhiù bidh rudeigin agam airson ath-nuadhachadh bhuaithe.

A thaobh an treas puing, chan eil mi cinnteach gu bheil e comasach a bhith a’ gleusadh às deidh don chòd a bhith air a chuir gu bàs airson a’ chiad uair, ach tha a’ chiad dà phuing gu leòr.

An toiseach, chaidh an còd a chruthachadh ann an cruth suidse mòr aig seòladh an stiùiridh bytecode tùsail, ach an uairsin, a ’cuimhneachadh air an artaigil mu Emscripten, optimization de JS gineadh agus ath-chuairteachadh, chuir mi romham barrachd còd daonna a ghineadh, gu sònraichte leis gu bheil e empirigeach e. thionndaidh e a-mach gur e toiseach tòiseachaidh an aon àite inntrigidh a-steach don bhloc eadar-theangachaidh. Cha bu luaithe a chaidh a ràdh na chaidh a dhèanamh, às deidh greis bha gineadair còd againn a chruthaich còd le ifs (ged nach robh lùban ann). Ach droch fhortan, thuit e, a’ toirt seachad teachdaireachd gu robh an stiùireadh fada ceàrr. A bharrachd air an sin, b 'e an stiùireadh mu dheireadh aig an ìre ath-chuairteachaidh seo brcond. Ceart gu leòr, cuiridh mi an aon sgrùdadh ri ginealach an stiùiridh seo ro agus às deidh a ’ghairm ath-chuairteach agus ... cha deach aon dhiubh a chuir gu bàs, ach às deidh an tionndadh dearbhte dh’ fhàilnich iad fhathast. Aig a ’cheann thall, às deidh dhomh sgrùdadh a dhèanamh air a’ chòd a chaidh a chruthachadh, thuig mi, às deidh an tionndadh, gu bheil am puing don stiùireadh gnàthach air ath-luchdachadh bhon chruach agus is dòcha gu bheil e air a sgrìobhadh thairis leis a ’chòd JavaScript a chaidh a chruthachadh. Agus mar sin thionndaidh e a-mach. Cha do dh'adhbhraich àrdachadh bufair bho aon megabyte gu deich dad, agus dh'fhàs e soilleir gu robh an gineadair còd a 'ruith ann an cearcallan. B’ fheudar dhuinn dèanamh cinnteach nach deach sinn seachad air crìochan an TB a th’ ann an-dràsta, agus nan dèanadh sinn sin, cuir a-mach seòladh an ath TB le soidhne as lugha gus am b’ urrainn dhuinn cumail oirnn a’ cur gu bàs. A bharrachd air an sin, tha seo a’ fuasgladh na duilgheadas “dè na gnìomhan a ghineadh a bu chòir a bhith neo-dhligheach ma tha am pìos bytecode seo air atharrachadh?” — chan fheum ach a’ ghnìomh a fhreagras ris a’ bhloc eadar-theangachaidh seo a bhith neo-dhligheach. Co-dhiù, ged a rinn mi dì-bhugachadh air a h-uile càil ann an Chromium (leis gu bheil mi a’ cleachdadh Firefox agus gu bheil e nas fhasa dhomh brobhsair air leth a chleachdadh airson deuchainnean), chuidich Firefox mi le bhith a’ ceartachadh neo-fhreagarrachd leis an inbhe asm.js, às deidh sin thòisich an còd ag obair nas luaithe ann an Chromium.

Eisimpleir de chòd a chaidh a chruthachadh

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

co-dhùnadh

Mar sin, chan eil an obair deiseil fhathast, ach tha mi sgìth de bhith a’ toirt an togail fad-ùine seo gu foirfeachd gu dìomhair. Mar sin, chuir mi romham na tha agam fhoillseachadh an-dràsta. Tha an còd rud beag eagallach ann an àiteachan, oir is e deuchainn a tha seo, agus chan eil e soilleir ro-làimh dè a dh'fheumar a dhèanamh. Is dòcha, an uairsin is fhiach geallaidhean atamach àbhaisteach a chuir a-mach a bharrachd air dreach nas ùire de Qemu. Anns an eadar-ama, tha snàithlean anns an Gita ann an cruth blog: airson gach “ìre” a chaidh seachad co-dhiù dòigh air choireigin, chaidh aithris mhionaideach ann an Ruisis a chur ris. Gu fìrinneach, tha an artaigil seo gu ìre mhòr na ath-aithris air a ’cho-dhùnadh git log.

Faodaidh tu feuchainn air uile an seo (Thoir an aire air trafaig).

Dè tha ag obair mar-thà:

  • Pròiseasaran brìgheil x86 a’ ruith
  • Tha prototype obrach ann de ghineadair còd JIT bho chòd inneal gu JavaScript
  • Tha teamplaid ann airson ailtireachd aoighean 32-bit eile a chuir ri chèile: an-dràsta faodaidh tu Linux a mheas airson ailtireachd MIPS a ’reothadh sa bhrobhsair aig an ìre luchdachadh

Dè eile as urrainn dhut a dhèanamh

  • Luathaich emulation. Fiù ‘s ann am modh JIT tha e coltach gu bheil e a’ ruith nas slaodaiche na Virtual x86 (ach is dòcha gu bheil Qemu slàn ann le tòrr bathar-cruaidh is ailtirean aithris)
  • Gus eadar-aghaidh àbhaisteach a dhèanamh - gu fìrinneach, chan e leasaiche lìn math a th’ annam, agus mar sin airson a-nis tha mi air an t-slige àbhaisteach Emscripten ath-dhèanamh cho math ‘s as urrainn dhomh
  • Feuch ri gnìomhan Qemu nas iom-fhillte a chuir air bhog - lìonrachadh, imrich VM, msaa.
  • ÙRACHADH: feumaidh tu na beagan leasachaidhean agus aithrisean bug agad a chuir a-steach gu Emscripten shuas an abhainn, mar a rinn luchd-giùlain Qemu agus pròiseactan eile roimhe. Taing dhaibh airson a bhith comasach air na chuir iad ri Emscripten gu h-obann a chleachdadh mar phàirt den obair agam.

Source: www.habr.com

Cuir beachd ann