QEMU.js: азыр олуттуу жана WASM менен

Бир жолу мен көңүл ачууну чечтим процесстин кайтарымдуулугун далилдейт жана машина кодунан JavaScript (тагыраак айтканда, Asm.js) түзүүнү үйрөнүңүз. Эксперимент үчүн QEMU тандалган жана бир нече убакыт өткөндөн кийин Habr боюнча макала жазылган. Комментарийлерде мага долбоорду WebAssemblyде кайра жасоону, ал тургай өзүмдү таштап салууну сунушташты. дээрлик бүттү Мен эмнегедир долбоорду каалаган жокмун ... Иш жүрүп жаткан, бирок абдан жай, азыр, жакында эле ошол макалада пайда болду түшүндүрмө темасында "Ошондо баары кантип бүттү?" Менин толук жообума жооп берип жатып, мен "Бул макалага окшош экен" деп уктум. Мейли, эгер мүмкүн болсо, макала болот. Балким, кимдир бирөө аны пайдалуу деп табат. Андан окурман QEMU кодун генерациялоо серверлеринин дизайны жөнүндө кээ бир фактыларды, ошондой эле веб-тиркеме үчүн Just-in-Time компиляторун кантип жазууну үйрөнөт.

милдеттери

Мен буга чейин QEMU-ну JavaScript'ке кантип "кандайдыр бир жол менен" порттоштурууну үйрөнгөндүктөн, бул жолу аны акылдуулук менен жасоону жана эски каталарды кайталабоону чечтим.

Ката саны биринчи: чекит чыгаруудан бутак

Менин биринчи катачылыгым версиямды 2.4.1 версиясынан өйдө бөлүп алуу болду. Ошондо мага жакшы идея болуп көрүндү: эгер чекиттик релиз бар болсо, анда ал жөнөкөй 2.4 ге караганда туруктуураак, андан да көбүрөөк филиал master. Мен өзүмдүн каталарымдын адилеттүү суммасын кошууну пландаштыргандыктан, мага башка эч кимдин кереги жок болчу. Ушундай болуп калса керек. Бирок, бул жерде бир нерсе: QEMU бир жерде тура бербейт, жана кайсы бир учурда алар түзүлгөн кодду 10 пайызга оптималдаштырууну жарыялашты."Ооба, азыр мен тоңуп калам" деп ойлоп, сынып калдым. Бул жерде биз чегинүү жасообуз керек: QEMU.js бир жиптүү табиятынан жана баштапкы QEMU көп агымдын жоктугун билдирбейт (башкача айтканда, бир эле учурда бир нече байланышпаган код жолдорун иштетүү мүмкүнчүлүгү жана жөн гана "бардык ядролорду колдонуу" эмес) ал үчүн абдан маанилүү, жиптердин негизги функцияларын сырттан чакыра алуу үчүн мен аны "чыгарууга" туура келди. Бул биригүү учурунда кээ бир табигый көйгөйлөрдү жараткан. Бирок, филиалдан кээ бир өзгөрүүлөр болуп жаткандыгы master, мен өзүмдүн кодумду бириктирүүгө аракет кылган, алча релизинде (демек, менин филиалымда) тандалган, балким, ыңгайлуулуктарды кошмок эмес.

Жалпысынан алганда, мен прототибин ыргытып, аны бөлүктөргө бөлүп, нөлдөн баштап жаңы версиясын куруунун мааниси бар деп чечтим. master.

Экинчи ката: TLP методологиясы

Негизи, бул жаңылыштык эмес, жалпысынан алганда, бул жөн гана “кайда жана кантип көчүү керек?” жана жалпысынан “биз ал жакка жетебизби?” деген толук түшүнбөстүктүн шартында долбоорду түзүүнүн өзгөчөлүгү. Бул шарттарда олдоксон программалоо негиздүү вариант болчу, бирок, албетте, мен аны ашыкча кайталагым келген эмес. Бул жолу мен муну акылмандык менен жасагым келди: атомдук тапшырмалар, аң-сезимдүү кодду өзгөртүү (жана Викицитатка ылайык, Линус Торвалдс бир жолу кимдир бирөө жөнүндө айткандай, "кокустук белгилерди чогултмайынча (эскертүү менен) бириктирүү" эмес) ж.б.

Үчүнчү ката: өтмөктү билбей сууга түшүү

Мен мындан дагы эле толугу менен арыла элекмин, бирок азыр мен эч кандай каршылык көрсөтүү жолуна түшпөй, муну “чоң адам катары” жасоону чечтим, тактап айтканда, TCG бэкендимди нөлдөн баштап жазып, андай болбошу үчүн. кийинчерээк: "Ооба, бул, албетте, акырындык менен, бирок мен баарын көзөмөлдөй албайм - TCI ушундай жазылган ..." Анын үстүнө, бул башында ачык-айкын чечим болуп көрүнгөн, анткени Мен экилик кодду чыгарам. Алар айткандай, «Гент чогулдуу, бирок ал эмес": код, албетте, бинардык, бирок башкарууну ага жөн эле өткөрүп берүү мүмкүн эмес - аны компиляциялоо үчүн браузерге ачык түртүп салуу керек, натыйжада JS дүйнөсүнөн белгилүү бир объект пайда болот. бир жерде куткарылсын. Бирок, кадимки RISC архитектурасында, мен түшүнүшүм боюнча, типтүү жагдай - бул регенерацияланган код үчүн нускама кэшин ачык түрдө баштапкы абалга келтирүү зарылчылыгы - эгерде бул бизге керек болбосо, анда, кандай болгон күндө да, жакын. Кошумчалай кетсек, акыркы аракетимден мен башкаруу котормо блогунун ортосуна өткөрүлбөй турганын билдим, андыктан бизге кандайдыр бир офсеттен интерпретацияланган байт коддун кереги жок жана биз аны жөн гана ТБ функциясынан түзө алабыз. .

Алар келип тепкилеп жатышты

Июль айында кодду кайра жаза баштасам да, сыйкырдуу сокку байкалбай калды: адатта GitHub каттары Маселелер жана Тартуу сурамдарына жооптор жөнүндө эскертмелер катары келет, бирок бул жерде, күтүлбөгөн жерден темада эскертуу Binaryen qemu backend катары контекстте, "Ал ушундай бир нерсе кылды, балким, ал бир нерсе деп айтат." Биз Emscriptenдин тиешелүү китепканасын колдонуу жөнүндө сүйлөшүп жатканбыз Binaryen WASM JIT түзүү. Ооба, мен ал жерде сизде Apache 2.0 лицензиясы бар экенин айттым, жана QEMU жалпысынан GPLv2 боюнча таратылат жана алар анча шайкеш келбейт. Күтүлбөгөн жерден лицензия болушу мүмкүн экени белгилүү болду аны кандайдыр бир жол менен оңдоо (Билбейм: балким, аны өзгөртүү, балким, кош лицензиялоо, балким, башка нерсе...). Бул, албетте, мени кубандырды, анткени ал убакка чейин мен жакшылап карап койгон элем бинардык формат WebAssembly, мен кандайдыр бир кайгылуу жана түшүнүксүз болдум. Өткөөл графиги бар негизги блокторду жалмап, байткодду чыгара турган, керек болсо аны котормочунун өзүндө иштете турган китепкана да бар болчу.

Анан дагы көп болду кат QEMU почта тизмесинде, бирок бул суроого көбүрөөк байланыштуу: "Кимге баары бир керек?" Жана бул күтүлбөгөн жерден, керек болуп чыкты. Жок дегенде, сиз аздыр-көптүр тез иштесе, төмөнкү колдонуу мүмкүнчүлүктөрүн кырып салсаңыз болот:

  • эч кандай орнотуусуз билим берүүчү нерсени ишке киргизүү
  • iOS'то виртуалдаштыруу, мында, имиштерге ылайык, кодду тез арада жаратуу укугуна ээ болгон жалгыз тиркеме JS кыймылдаткычы (бул чынбы?)
  • мини-OS көрсөтүү - бир дискеттүү, орнотулган, микропрограмманын бардык түрлөрү, ж.б.у.с.

Браузердин иштөө убактысынын өзгөчөлүктөрү

Мен буга чейин айткандай, QEMU multithreading менен байланышкан, бирок браузерде ал жок. Ооба, башкача айтканда, жок... Башында ал такыр болгон эмес, андан кийин WebWorkers пайда болду - менин түшүнүшүм боюнча, бул билдирүүнүн өтүүсүнө негизделген көп агым бөлүшүлгөн өзгөрмөлөрсүз. Албетте, бул жалпы эс тутум моделинин негизинде учурдагы кодду көчүрүүдө олуттуу көйгөйлөрдү жаратат. Анан коомчулуктун басымы менен ат менен ишке ашырылган SharedArrayBuffers. Ал акырындык менен киргизилип, анын ишке киришин ар кандай браузерлерде майрамдашты, андан кийин алар Жаңы жылды майрамдашты, анан Meltdown... Андан кийин алар убакытты одоно же орой өлчөө деген тыянакка келишти, бирок жалпы эс тутумдун жардамы менен эсептегичти көбөйтүүчү жип, баары бирдей ал абдан так иштейт. Ошентип, биз жалпы эс тутум менен көп агымды өчүрүп койдук. Кийинчерээк алар аны кайра күйгүзүштү окшойт, бирок, биринчи эксперименттен көрүнүп тургандай, ансыз да жашоо бар, эгер ошондой болсо, биз муну multithreading'ге таянбастан жасоого аракет кылабыз.

Экинчи өзгөчөлүк - стек менен төмөнкү деңгээлдеги манипуляциялардын мүмкүн эместиги: сиз жөн эле алып, учурдагы контекстти сактап, жаңы стек менен жаңысына өтө албайсыз. Чалуу стек JS виртуалдык машинасы тарабынан башкарылат. Мурунку агымдарды толугу менен кол менен башкарууну чечкендиктен, көйгөй эмнеде экени көрүнүп турат? Чындыгында, QEMUдагы I/O блоктору корутиндер аркылуу ишке ашырылат жана бул жерде төмөнкү деңгээлдеги стек манипуляциялары жардамга келет. Бактыга жараша, Emscipten мурунтан эле асинхрондук операциялар үчүн механизмди камтыйт, атүгүл эки: Asyncify и Emterpreter. Биринчиси түзүлгөн JavaScript кодундагы олуттуу шишик аркылуу иштейт жана мындан ары колдоого алынбайт. Экинчиси - учурдагы "туура жол" жана жергиликтүү котормочу үчүн байткодду түзүү аркылуу иштейт. Бул, албетте, жай иштейт, бирок ал кодду толтурбайт. Ырас, бул механизм үчүн корутиндерди колдоо өз алдынча кошулушу керек болчу (Asyncify үчүн мурунтан эле корутиндер жазылган жана Emterpreter үчүн болжол менен ошол эле API ишке ашырылган, сиз аларды жөн гана туташтырышыңыз керек болчу).

Учурда мен кодду WASMде түзүлгөн жана Emterpreter аркылуу чечмелеген кодду бөлүүгө жетише элекмин, андыктан блоктордун түзүлүштөрү азырынча иштебейт (кийинки серияларды караңыз, алар айткандай...). Башкача айтканда, аягында сиз бул күлкүлүү катмарлуу нерсеге ээ болушуңуз керек:

  • интерпретацияланган блок I/O. Ооба, сиз чындап эле NVMe үлгүсүн жергиликтүү аткарууну күттүңүз беле? 🙂
  • статикалык түрдө түзүлгөн негизги QEMU коду (котормочу, башка эмуляцияланган түзүлүштөр ж.б.)
  • динамикалык түрдө конок кодун WASMге түздү

QEMU булактарынын өзгөчөлүктөрү

Сиз ойлогондой, конок архитектурасын эмуляциялоо коду жана хост машинасынын нускамаларын түзүү коду QEMUде бөлүнгөн. Чынында, бул дагы бир аз татаалыраак:

  • конок архитектурасы бар
  • болуп саналат тездеткичтер, тактап айтканда, Linux боюнча аппараттык виртуалдаштыруу үчүн KVM (бири-бирине шайкеш келген конок жана хост системалары үчүн), каалаган жерде JIT кодун түзүү үчүн TCG. QEMU 2.9 менен баштап, Windowsта HAXM аппараттык виртуалдаштыруу стандартын колдоо пайда болду (маалымат)
  • эгерде TCG аппараттык виртуалдаштыруунун ордуна колдонулса, анда ал ар бир хост архитектурасы үчүн, ошондой эле универсалдуу котормочу үчүн өзүнчө код түзүү колдоосуна ээ.
  • ... жана мунун бардыгынын тегерегинде - эмуляцияланган перифериялык түзүлүштөр, колдонуучу интерфейси, миграция, жазуу-кайталоо ж.б.

Айтмакчы, сиз билесизби: QEMU бүт компьютерди гана эмес, ошондой эле, мисалы, бинардык инструменттер үчүн AFL fuzzer тарабынан колдонулган хост ядросунда өзүнчө колдонуучу процесси үчүн процессорду эмуляциялай алат. Балким, кимдир бирөө QEMU бул режимин JSге өткөргүсү келет? 😉

Көпчүлүк бекер программалык камсыздоо сыяктуу, QEMU чалуу аркылуу курулган configure и make. Сиз бир нерсени кошууну чечти дейли: TCG сервери, жипти ишке ашыруу, башка нерсе. Автоконф. configure QEMU, сыягы, өз алдынча жазылган жана эч нерседен жаралбайт.

веб ассамблеясы

Ошентип, бул WebAssembly (aka WASM) деген эмне? Бул Asm.js үчүн алмаштыруу, мындан ары жарактуу JavaScript коду болуп көрүнбөйт. Тескерисинче, ал таза бинардык жана оптималдаштырылган, ал тургай ага бүтүн санды жазуу да оңой эмес: компакттуулук үчүн ал форматта сакталат. LEB128.

Asm.js үчүн кайра иштетүү алгоритми жөнүндө уккан болушуңуз мүмкүн - бул JS кыймылдаткычтары иштелип чыккан "жогорку деңгээлдеги" агымды башкаруу инструкцияларын (б.а. болсо, анда-башкача, циклдер ж.б.) калыбына келтирүү. төмөнкү деңгээлдеги LLVM IR, процессор тарабынан аткарылган машина кодуна жакыныраак. Албетте, QEMUнун ортодогу өкүлчүлүгү экинчиге жакыныраак. Мына, байткод, азаптын аягы... Анан блоктор, if-then-else жана циклдер бар!..

Жана бул Binaryenдин пайдалуу болушунун дагы бир себеби: ал табигый түрдө WASMде сактала турган нерсеге жакын жогорку деңгээлдеги блокторду кабыл алат. Бирок ал ошондой эле негизги блоктордун жана алардын ортосундагы өткөөлдөрдүн графигинен код чыгара алат. Ооба, мен буга чейин айттым, ал WebAssembly сактагыч форматын ыңгайлуу C/C++ API артына жашырат.

TCG (Tiny Code Generator)

ТКГ башында болгон С компилятору үчүн бэкэнд.Андан кийин, сыягы, ал GCC менен атаандаштыкка туруштук бере алган жок, бирок акыры QEMUде хост платформасы үчүн кодду түзүү механизми катары өз ордун тапты. Котормочу дароо аткара турган абстракттуу байткодду жараткан TCG сервери да бар, бирок мен бул жолу аны колдонуудан качууну чечтим. Бирок, QEMUде бул функция аркылуу түзүлгөн кургак учукка өтүүнү камсыз кылуу мүмкүн экендиги tcg_qemu_tb_exec, бул мен үчүн абдан пайдалуу болуп чыкты.

QEMUге жаңы TCG серверин кошуу үчүн, сиз подкаталог түзүшүңүз керек tcg/<имя архитектуры> (Бул учурда, tcg/binaryen) жана ал эки файлды камтыйт: tcg-target.h и tcg-target.inc.c и жазуу Мунун баары жөнүндө configure. Сиз ал жерге башка файлдарды кое аласыз, бирок бул экөөнүн аттары боюнча болжолдогонуңуздай, экөө тең бир жерде камтылат: бири кадимки баш файл катары (ал tcg/tcg.h, жана бул каталогдордогу башка файлдарда мурунтан эле бар tcg, accel жана гана эмес), башкасы - код үзүндүсү катары гана tcg/tcg.c, бирок анын статикалык функцияларына кирүү мүмкүнчүлүгү бар.

Анын кантип иштээрин деталдуу иликтөөгө өтө көп убакыт коротом деп чечип, мен бул эки файлдын "скелеттерин" башка сервердик ишке ашыруудан көчүрүп алдым, муну лицензиянын аталышында чынчылдык менен көрсөттү.

билэ tcg-target.h формада негизинен орнотууларды камтыйт #define-s:

  • максаттуу архитектурада канча регистр жана кандай туурасы бар (бизде канча кааласак, ошончо көп - суроо "толугу менен максаттуу" архитектурада браузер тарабынан натыйжалуураак кодго эмнелер түзүлө тургандыгы жөнүндө. ...)
  • хост инструкцияларын тегиздөө: x86да, жада калса TCIде инструкциялар такыр тегизделген эмес, бирок мен код буферине инструкцияларды эмес, Binaryen китепканасынын структураларына көрсөткүчтөрдү киргизем, ошондуктан мен айтам: 4 байт
  • Backend кандай кошумча инструкцияларды түзө алат - биз Binaryenде тапкан нерселердин бардыгын камтабыз, акселератор калганын жөнөкөйлөргө бөлсүн
  • Backend сураган TLB кэшинин болжолдуу өлчөмү кандай. Чындыгында QEMUде баары олуттуу: конок MMUну эске алуу менен жүктөө/дүкөндү аткарган жардамчы функциялар бар болсо да (ансыз биз азыр кайда болмокпуз?), алар өздөрүнүн котормо кэшин структура түрүндө сакташат. аны иштетүү түз берүү блокторуна киргизүүгө ыңгайлуу. Суроо туулат, бул структурада кайсы офсет командалардын кичинекей жана тез ырааттуулугу менен эң натыйжалуу иштетилет?
  • бул жерде сиз бир же эки резервдик регистрдин максатын өзгөртө аласыз, бир функция аркылуу ТБ чакырууну иштетип, бир нече кичинекей inline- сыяктуу функцияларды аткарат flush_icache_range (бирок бул биздин иш эмес)

билэ tcg-target.inc.c, албетте, көлөмү боюнча, адатта, бир топ чоңураак жана бир нече милдеттүү функцияларды камтыйт:

  • инициализация, анын ичинде инструкциялар кайсы операнддарда иштей ала турган чектөөлөр. Ачык түрдө мен тарабынан башка бэкэндден көчүрүлгөн
  • бир ички байт-код инструкциясын алган функция
  • Бул жерге кошумча функцияларды да койсоңуз болот, ошондой эле статикалык функцияларды да колдоно аласыз tcg/tcg.c

Мен өзүм үчүн төмөнкү стратегияны тандадым: кийинки котормо блогунун биринчи сөздөрүндө мен төрт көрсөткүчтү жаздым: баштоо белгиси (жакын жердеги белгилүү бир маани 0xFFFFFFFF, ал кургак учуктун учурдагы абалын аныктаган), контекст, түзүлгөн модуль жана мүчүлүштүктөрдү оңдоо үчүн сыйкырдуу номер. Алгач белги коюлган 0xFFFFFFFF - nкайда n - кичинекей оң сан, ал котормочу аркылуу аткарылган сайын 1ге көбөйдү. Ал жеткенде 0xFFFFFFFE, компиляция болуп өттү, модул функционалдык таблицада сакталып, кичинекей "ишке киргизгичке" импорттолду, анын аткарылышы tcg_qemu_tb_exec, жана модул QEMU эс тутумунан алынып салынды.

Классиктерди кайталап айтсак, "балдак, прогердин жүрөгү үчүн бул үн менен канчалык чырмалышкан...". Бирок, эси бир жерде агып жатты. Анын үстүнө, бул эстутум QEMU тарабынан башкарылчу! Менде коду бар болчу, ал кийинки нускаманы жазып жатканда (жакшы, башкача айтканда, көрсөткүч), шилтемеси мурда ушул жерде болгон, бирок бул жардам берген жок. Чынында, эң жөнөкөй учурда, QEMU ишке киргизүүдө эстутумду бөлүштүрөт жана түзүлгөн кодду ошол жерге жазат. Буфер бүткөндө код ыргытылат жана анын ордуна кийинкиси жазыла баштайт.

Кодду изилдеп чыккандан кийин, мен сыйкырдуу сан менен трюк биринчи өтүүдө инициализацияланбаган буферде туура эмес нерсени бошотуп, үймөктү жок кылууда ийгиликке жетпөөгө мүмкүндүк бергенин түшүндүм. Бирок кийинчерээк менин функциямды айланып өтүү үчүн буферди ким кайра жазат? Emscripten иштеп чыгуучулары кеңешкендей, мен көйгөйгө туш болгондо, мен пайда болгон кодду жергиликтүү тиркемеге көчүрдүм, ага Mozilla Record-Replay орноттум... Жалпысынан, мен жөнөкөй нерсени түшүндүм: ар бир блок үчүн, а struct TranslationBlock анын сүрөттөлүшү менен. Каякты тапса болот... Туура, буфердеги блоктун алдында. Муну түшүнүп, мен балдактарды колдонууну таштоону чечтим (жок дегенде кээ бирлери), сыйкырдуу номерди ыргытып жибердим да, калган сөздөрдү struct TranslationBlock, котормо кэши баштапкы абалга келтирилгенде тез өтүүгө мүмкүн болгон жалгыз шилтемеленген тизмени түзүү жана эстутумду бошотуу.

Кээ бир балдактар ​​калат: мисалы, код буферинде белгиленген көрсөткүчтөр - алардын айрымдары жөн эле BinaryenExpressionRef, башкача айтканда, алар түзүлүүчү негизги блокко сызыктуу салынышы керек болгон туюнтмаларды карашат, бөлүк - BBs ортосунда өтүү шарты, бөлүгү кайда баруу керек. Ооба, Relooper үчүн буга чейин даярдалган блоктор бар, аларды шарттарга ылайык туташтыруу керек. Аларды айырмалоо үчүн, алардын бардыгы жок дегенде төрт байт менен тегизделген деген божомол колдонулат, андыктан сиз энбелги үчүн эң аз маанилүү эки битти коопсуз колдоно аласыз, керек болсо аны алып салууну унутпаңыз. Айтмакчы, мындай энбелгилер TCG циклинен чыгуунун себебин көрсөтүү үчүн QEMUде мурунтан эле колдонулат.

Binaryen колдонуу

WebAssembly'деги модулдар функцияларды камтыйт, алардын ар бири туюнтма болгон денени камтыйт. Туундулар - унардык жана бинардык операциялар, башка туюнтмалардын тизмегинен турган блоктор, башкаруу агымы ж.б. Мен буга чейин айткандай, бул жерде башкаруу агымы так жогорку деңгээлдеги бутактар, циклдер, функциялык чакыруулар ж. Функцияларга аргументтер стекке берилбейт, бирок JSдегидей ачык-айкын. Глобалдык өзгөрмөлөр да бар, бирок мен аларды колдонгон эмесмин, андыктан алар жөнүндө айтпай эле коёюн.

Функцияларда ошондой эле нөлдөн баштап номерленген локалдык өзгөрмөлөр бар: int32 / int64 / float / double. Бул учурда, биринчи n жергиликтүү өзгөрмөлөр функцияга берилген аргументтер болуп саналат. Көңүл буруңуз, бул жерде бардыгы башкаруу агымы жагынан толугу менен төмөн деңгээлде болбосо да, бүтүн сандар дагы эле “кол коюлган/кол коюлбаган” атрибутун алып жүрбөйт: сандын иштеши операция кодунан көз каранды.

Жалпысынан алганда, Binaryen камсыз кылат жөнөкөй C-API: сиз модулду түзөсүз, анда туюнтмаларды түзүү - унардык, бинардык, башка туюнтмалардын блоктору, башкаруу агымы ж.б. Андан кийин анын денеси катары туюнтма менен функция түзөсүз. Эгерде сизде, мага окшоп, төмөнкү деңгээлдеги өтүү графиги болсо, анда relooper компоненти сизге жардам берет. Мен түшүнгөндөй, блоктун чегинен чыкпаса, блокто аткаруу агымын жогорку деңгээлде башкарууну колдонууга болот - башкача айтканда, ички тез жолду / жай жасоого болот. орнотулган TLB кэш иштетүү кодунун ичинде бутакталган жол, бирок "тышкы" башкаруу агымына тоскоол болбошу үчүн. Кайталоочуну бошоткондо, анын блоктору бошотулат, модулду бошоткондо, ага бөлүнгөн туюнтмалар, функциялар ж.б. арена.

Бирок, эгер сиз котормочунун инстанциясын ашыкча түзбөстөн жана жок кылбастан, кодун тез арада чечмелегиңиз келсе, бул логиканы C++ файлына салып, ошол жерден китепкананын бүт C++ API'син түздөн-түз башкарып, даяр- орогучтарды жасаган.

Ошентип, сизге керектүү кодду түзүү үчүн

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

... эгер мен бир нерсени унутуп калсам, кечиресиз, бул жөн гана масштабды көрсөтүү үчүн, ал эми маалымат документтерде.

Эми crack-fex-pex башталат, мындай нерсе:

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

QEMU жана JS ааламдарын кандайдыр бир жол менен туташтыруу жана ошол эле учурда компиляцияланган функцияларга тез жетүү үчүн массив түзүлдү (ишке киргизгичке импорттоо үчүн функциялардын таблицасы) жана түзүлгөн функциялар ал жерге жайгаштырылган. Индексти тез эсептөө үчүн алгач нөлдүк котормо блогунун индекси колдонулган, бирок андан кийин бул формула менен эсептелген индекс жөн эле талаага туура келе баштаган. struct TranslationBlock.

Айтмакчы, демо (учурда күңүрт лицензия менен) Firefox менен гана жакшы иштейт. Chrome иштеп чыгуучулары болгон негедир даяр эмес кимдир бирөө WebAssembly модулдарынын миңден ашык инстанцияларын түзүүнү каалайт, ошондуктан алар жөн гана ар бирине бир гигабайт виртуалдык дарек мейкиндигин бөлүп коюшту...

Азырынча баары ушул. Балким, кимдир бирөө кызыкдар болсо, дагы бир макала болот. Тактап айтканда, жок дегенде калды так блоктук түзүлүштөрдү иштетүү. JS дүйнөсүндө көнүмүш болуп калгандай, WebAssembly модулдарынын компиляциясын асинхрондуу кылуу да акылга сыярлык болушу мүмкүн, анткени жергиликтүү модул даяр болмоюнча мунун баарын жасай турган котормочу дагы эле бар.

Акыры табышмак: сиз 32 биттик архитектурада бинардык түздүңүз, бирок код эстутум операциялары аркылуу Binaryenден, стектин бир жеринен же 2 биттик дарек мейкиндигинин 32 ГБ жогорку жагындагы башка жерден көтөрүлөт. Көйгөй, Binaryenдин көз карашы боюнча, бул өтө чоң жыйынтык дарекке жетүү болуп саналат. Муну кантип айланып өтүүгө болот?

Админ жолу менен

Мен муну сынап бүтө элекмин, бирок менин биринчи оюм: "Эгер мен 32-бит Linux орнотсом эмне болот?" Андан кийин дарек мейкиндигинин жогорку бөлүгүн ядро ​​ээлейт. Бир гана суроо канча ээлейт: 1 же 2 Гб.

Программисттин жолу менен (практиктер үчүн вариант)

Дарек мейкиндигинин жогору жагында көбүктү үйлөйлү. Мен өзүм түшүнбөйм, эмне үчүн иштейт - ошол жерде буга чейин стек болушу керек. Бирок "биз практиктербиз: бардыгы биз үчүн иштейт, бирок эмне үчүн экенин эч ким билбейт ..."

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

... бул Valgrind менен шайкеш келбегени чын, бирок, бактыга жараша, Valgrind өзү бардыгын ал жерден абдан натыйжалуу түртөт :)

Балким, кимдир бирөө менин бул кодумдун кандай иштээрин жакшыраак түшүндүрүп берет ...

Source: www.habr.com

Комментарий кошуу