QEMU.js: қазір маңызды және WASM-мен

Бір кездері мен көңіл көтеру үшін шешім қабылдадым процестің қайтымдылығын дәлелдеу және машиналық кодтан JavaScript (дәлірек айтқанда, Asm.js) жасау жолын үйреніңіз. Эксперимент үшін QEMU таңдалды, біраз уақыттан кейін Хабр туралы мақала жазылды. Түсініктемелерде маған жобаны WebAssembly-де қайта құруға кеңес берді, тіпті өзімді тастадым. аяқталуға жақын Мен әйтеуір жобаны қаламадым... Жұмыс жүріп жатты, бірақ өте баяу, ал қазір, жақында сол мақалада пайда болды. Пікір «Сонымен бәрі қалай аяқталды?» тақырыбында Менің егжей-тегжейлі жауабыма жауап ретінде мен «Бұл мақалаға ұқсайды» деп естідім. Жарайды, мүмкін болса, мақала болады. Мүмкін біреу оны пайдалы деп табады. Одан оқырман QEMU кодын генерациялау серверлерінің дизайны туралы кейбір фактілерді, сондай-ақ веб-қосымша үшін Just-in-Time компиляторын қалай жазу керектігін біледі.

міндеттері

Мен QEMU-ді JavaScript-ке қалай «қандай да бір жолмен» порттауды үйренгендіктен, бұл жолы оны ақылмен жасау және ескі қателерді қайталамау туралы шешім қабылдадым.

№XNUMX қате: босату нүктесінен тармақ

Менің бірінші қателігім өз нұсқамды 2.4.1 ағынды нұсқасынан ажырату болды. Содан кейін маған жақсы идея болып көрінді: егер нүктелік босату бар болса, онда ол қарапайым 2.4-ке қарағанда тұрақтырақ болуы мүмкін және одан да көп филиал master. Мен өз қателерімнің жеткілікті мөлшерін қосуды жоспарлағандықтан, маған басқа біреудің қателері мүлдем қажет болмады. Солай болған шығар. Бірақ міне, бір нәрсе: QEMU бір орында тұрмайды, тіпті бір сәтте олар генерацияланған кодты 10 пайызға оңтайландыруды жариялады: «Иә, енді мен қатып қаламын», - деп ойладым және бұзылдым. Мұнда біз шегініс жасауымыз керек: QEMU.js бір ағынды сипатына байланысты және түпнұсқа QEMU көп ағынның жоқтығын білдірмейді (яғни, бір уақытта бірнеше байланысты емес код жолдарын басқару мүмкіндігі және «барлық ядроларды пайдалану» ғана емес) ол үшін маңызды, ағындардың негізгі функциялары мен сырттан қоңырау шалу үшін «оны шығаруға» тура келді. Бұл біріктіру кезінде кейбір табиғи қиындықтар туғызды. Дегенмен, филиалдан кейбір өзгерістер фактісі master, оның көмегімен мен кодты біріктіруге тырыстым, сонымен қатар нүктелік шығарылымда (сондықтан менің филиалымда) шие таңдалды, сонымен қатар ыңғайлылық қосылмауы мүмкін.

Тұтастай алғанда, мен прототипті лақтырып тастау, оны бөліктерге бөлшектеу және жаңарақ нәрсеге негізделген жаңа нұсқаны нөлден бастаудың мағынасы бар деп шештім. master.

Екінші қате: TLP әдістемесі

Негізінде, бұл қате емес, тұтастай алғанда, бұл «қайда және қалай көшу керек?» және жалпы «біз сонда жете аламыз ба?» дегенді толық түсінбеу жағдайында жобаны құрудың ерекшелігі. Бұл жағдайларда ыңғайсыз бағдарламалау ақталған нұсқа болды, бірақ, әрине, мен оны қажетсіз қайталағым келмеді. Бұл жолы мен мұны ақылмен істегім келді: атомдық міндеттемелер, саналы кодты өзгерту (және Викицитата бойынша Линус Торвальдс бір кездері біреу туралы айтқандай, «кездейсоқ таңбаларды құрастырғанға дейін (ескертулермен) біріктіру» емес) және т.б.

Үшінші қате: өткелді білмей суға түсу

Мен одан әлі толық арылған жоқпын, бірақ енді мен ең аз қарсылық жолын ұстанбай, оны «ересек адам ретінде» жасауды шештім, атап айтқанда, TCG серверін нөлден бастап жазамын. кейінірек айту керек: «Иә, бұл, әрине, баяу, бірақ мен бәрін басқара алмаймын - TCI осылай жазылған ...» Оның үстіне, бұл бастапқыда айқын шешім сияқты көрінді, өйткені Мен екілік кодты жасаймын. Олар айтқандай, «Гент жиналдыу, бірақ ол емес»: код, әрине, екілік, бірақ басқаруды оған жай ғана беру мүмкін емес - оны құрастыру үшін браузерге анық итеру керек, нәтижесінде JS әлемінен белгілі бір нысан пайда болады, ол әлі де қажет бір жерде сақталады. Дегенмен, қалыпты RISC архитектураларында, менің түсінуімше, әдеттегі жағдай қалпына келтірілген код үшін нұсқаулық кэшін нақты қалпына келтіру қажеттілігі болып табылады - егер бұл бізге қажет болмаса, онда, кез келген жағдайда, жақын. Сонымен қатар, менің соңғы әрекетімнен мен басқару аударма блогының ортасына берілмейтінін білдім, сондықтан бізге кез келген офсеттен интерпретацияланған байткод қажет емес және біз оны жай ғана ТБ функциясынан жасай аламыз. .

Олар келіп, тепті

Мен кодты шілдеде қайта жаза бастағаныма қарамастан, сиқырлы соққы байқалмай қалды: әдетте GitHub хаттары Мәселелер мен тарту сұрауларына жауаптар туралы хабарландырулар ретінде келеді, бірақ мұнда, кенеттен жіпте атап өту Binaryen qemu сервері ретінде контексте: «Ол осындай нәрсе жасады, мүмкін ол бірдеңе айтатын шығар». Біз Эмскриптеннің сәйкес кітапханасын пайдалану туралы сөйлестік Бинарьен WASM JIT жасау үшін. Мен сізде Apache 2.0 лицензиясы бар екенін айттым, ал QEMU тұтастай алғанда GPLv2 бойынша таратылады және олар өте үйлесімді емес. Кенеттен лицензия болуы мүмкін екені белгілі болды оны қандай да бір жолмен түзетіңіз (Мен білмеймін: мүмкін оны өзгерту, мүмкін қос лицензиялау, мүмкін басқа нәрсе ...). Бұл, әрине, мені қуантты, өйткені ол уақытқа дейін мен мұқият қарап үлгердім екілік пішім WebAssembly, мен қандай да бір қайғылы және түсініксіз болдым. Сондай-ақ, ауысу графигі бар негізгі блоктарды жейтін, байт-кодты шығаратын және қажет болса, оны аудармашының өзінде іске қосатын кітапхана болды.

Содан кейін көп болды хат QEMU тарату тізімінде, бірақ бұл «ол кімге керек?» Деген сұраққа көбірек қатысты. Және солай кенеттен, қажет болып шықты. Кем дегенде, егер ол жылдам немесе азырақ жұмыс істейтін болса, келесі пайдалану мүмкіндіктерін біріктіруге болады:

  • мүлде орнатусыз білім беретін нәрсені іске қосу
  • iOS жүйесінде виртуализация, мұнда қауесеттерге сәйкес, кодты жылдам құруға құқығы бар жалғыз қолданба JS қозғалтқышы болып табылады (бұл рас па?)
  • мини-ОЖ демонстрациясы - бір иілгіш, кірістірілген, микробағдарламаның барлық түрлері және т.б.

Браузердің жұмыс уақыты мүмкіндіктері

Жоғарыда айтқанымдай, QEMU көп ағынмен байланысты, бірақ браузерде ол жоқ. Ал, яғни, жоқ... Бастапқыда ол мүлдем болған жоқ, содан кейін WebWorkers пайда болды - менің түсінуімше, бұл хабарды жіберуге негізделген көп ағындық. ортақ айнымалыларсыз. Әрине, бұл ортақ жад үлгісіне негізделген бар кодты тасымалдау кезінде маңызды проблемаларды тудырады. Одан кейін жұртшылықтың қысымымен ол да атпен жүзеге асырылды SharedArrayBuffers. Ол бірте-бірте енгізілді, олар әртүрлі браузерлерде оның іске қосылуын атап өтті, содан кейін олар Жаңа жылды тойлады, содан кейін Meltdown... Осыдан кейін олар уақытты дөрекі немесе өрескел өлшеу деген қорытындыға келді, бірақ ортақ жады және есептегішті арттыратын жіп, бәрі бірдей ол өте дәл жұмыс істейді. Осылайша, біз ортақ жадымен көп ағынды өшірдік. Олар кейінірек оны қайта қосқан сияқты, бірақ бірінші эксперименттен белгілі болғандай, онсыз өмір бар, ал егер солай болса, біз оны көп ағынға сенбей-ақ жасауға тырысамыз.

Екінші мүмкіндік - стекке төмен деңгейлі манипуляциялардың мүмкін еместігі: сіз жай ғана алып, ағымдағы контекстті сақтай алмайсыз және жаңа стекпен жаңасына ауыса алмайсыз. Қоңыраулар стегін JS виртуалды машинасы басқарады. Біз бұрынғы ағындарды толығымен қолмен басқаруды шешкендіктен, мәселе неде деп ойлаймыз? Шындығында, QEMU жүйесіндегі енгізу-шығару блогы корутиндер арқылы жүзеге асырылады және бұл жерде төменгі деңгейлі стек манипуляциялары пайдалы болады. Бақытымызға орай, Emscipten қазірдің өзінде асинхронды операцияларға арналған механизмді қамтиды, тіпті екеуі: Синхронизация и Аудармашы. Біріншісі жасалынған JavaScript кодындағы айтарлықтай толқу арқылы жұмыс істейді және бұдан былай қолдау көрсетілмейді. Екіншісі - ағымдағы «дұрыс жол» және жергілікті аудармашы үшін байт-кодты генерациялау арқылы жұмыс істейді. Бұл, әрине, баяу жұмыс істейді, бірақ ол кодты толтырмайды. Рас, бұл механизм үшін корутиндерге қолдау көрсетуге дербес үлес қосу керек болды (Asyncify үшін қазірдің өзінде жазылған корутиндер болды және Emterpreter үшін шамамен бірдей API іске асырылуы болды, сізге оларды қосу керек болды).

Қазіргі уақытта мен кодты WASM-де құрастырылған және Emterpreter көмегімен түсіндірілетін кодқа әлі бөле алмадым, сондықтан блоктау құрылғылары әлі жұмыс істемейді (келесі сериядан қараңыз, олар айтқандай...). Яғни, соңында сіз осындай күлкілі қабатты нәрсе алуыңыз керек:

  • интерпретацияланған блок енгізу/шығару. Сіз шынымен де NVMe өнімділігімен эмуляцияланған деп күттіңіз бе? 🙂
  • статикалық түрде құрастырылған негізгі QEMU коды (аудармашы, басқа эмуляцияланған құрылғылар және т.б.)
  • WASM ішіне динамикалық түрде құрастырылған қонақ коды

QEMU көздерінің ерекшеліктері

Сіз әлдеқашан болжағаныңыздай, қонақ архитектурасын эмуляциялау коды және хост машинасының нұсқауларын генерациялау коды QEMU ішінде бөлінген. Шындығында, бұл одан да қиынырақ:

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

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

Көптеген бұрыннан келе жатқан тегін бағдарламалық құрал сияқты, QEMU қоңырау арқылы құрастырылады configure и make. Сіз бірдеңе қосуды шештіңіз делік: TCG сервері, ағынды іске асыру, тағы бір нәрсе. Autoconf-пен байланысу мүмкіндігіне қуануға/қорқынышқа асықпаңыз (қажет болса астын сызыңыз) - шын мәнінде, configure QEMU өздігінен жазылған және ештеңеден жасалмаған сияқты.

webassembly

Сонымен, WebAssembly (WASM) деп аталатын бұл не? Бұл Asm.js үшін ауыстыру, енді жарамды JavaScript коды болып көрінбейді. Керісінше, ол таза екілік және оңтайландырылған, тіпті оған бүтін санды жазу да оңай емес: ықшамдық үшін ол форматта сақталады. LEB128.

Asm.js үшін қайта құру алгоритмі туралы естіген боларсыз – бұл JS қозғалтқыштары әзірленген «жоғары деңгейлі» ағынды басқару нұсқауларын (яғни, егер-онда-басқа, циклдар және т.б.) қалпына келтіру. төменгі деңгейлі LLVM IR, процессор орындайтын машина кодына жақынырақ. Әрине, QEMU аралық өкілдігі екіншісіне жақын. Міне, байт-код, азаптың соңы... Сосын блоктар, if-then-else және циклдар бар!..

Бұл Binaryen пайдалы болуының тағы бір себебі: ол WASM-де сақталатынға жақын жоғары деңгейлі блоктарды табиғи түрде қабылдай алады. Бірақ ол негізгі блоктар мен олардың арасындағы ауысулардың графигінен кодты да жасай алады. Мен оның WebAssembly сақтау пішімін ыңғайлы C/C++ API артына жасыратынын айттым.

TCG (Tiny Code Generator)

TCG бастапқыда болды С компиляторына арналған бэкенд.Сосын, ол 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, яғни олар түзілген негізгі блокқа сызықты түрде қою керек өрнектерді қарайды, бөлігі - ББ-лар арасындағы ауысу шарты, бөлігі - қайда бару керек. Ал, Relooper үшін дайындалған блоктар бар, олар шарттарға сәйкес қосылуы керек. Оларды ажырату үшін олардың барлығы кемінде төрт байтқа тураланған деген болжам пайдаланылады, сондықтан жапсырма үшін ең аз маңызды екі битті қауіпсіз пайдалануға болады, қажет болған жағдайда оны алып тастауды есте сақтау қажет. Айтпақшы, мұндай белгілер QEMU-де TCG циклінен шығу себебін көрсету үшін бұрыннан бар.

Binaryen пайдалану

WebAssembly модульдерінде функциялар бар, олардың әрқайсысында өрнек болып табылатын дене бар. Өрнектер - бірарлы және екілік операциялар, басқа өрнектер тізімдерінен тұратын блоктар, басқару ағыны және т.б. Жоғарыда айтқанымдай, мұнда басқару ағыны дәл жоғары деңгейлі тармақтар, циклдар, функциялық шақырулар және т.б. ретінде ұйымдастырылған. Функциялардың аргументтері стекке берілмейді, бірақ JS сияқты анық. Жаһандық айнымалылар да бар, бірақ мен оларды пайдаланбадым, сондықтан олар туралы айтпаймын.

Функцияларда нөлден бастап нөмірленген, типті жергілікті айнымалылар бар: int32 / int64 / float / double. Бұл жағдайда бірінші n жергілікті айнымалылар функцияға берілген аргументтер болып табылады. Мұнда барлығы басқару ағыны тұрғысынан мүлдем төмен деңгейде болмаса да, бүтін сандар әлі де «қол қойылған/қолтаңбасыз» атрибутын тасымалдамайтынын ескеріңіз: санның әрекеті операция кодына байланысты.

Жалпы айтқанда, Binaryen қамтамасыз етеді қарапайым C-API: модуль жасайсыз, Онда өрнектерді құру - бірлік, екілік, басқа өрнектерден блоктар, басқару ағыны және т.б. Содан кейін оның денесі ретінде өрнек бар функция жасайсыз. Егер сізде мен сияқты төмен деңгейлі ауысу графигі болса, сізге қайта өңдеуші компонент көмектеседі. Менің түсінуімше, блоктың шекарасынан шықпаса, блокта орындау ағынын жоғары деңгейлі басқаруды қолдануға болады - яғни ішкі жылдам жолды / баяу жасауға болады. кірістірілген 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-пен үйлесімді емес екені рас, бірақ, бақытымызға орай, Валгриндтің өзі барлығын ол жерден өте тиімді түрде итеріп жібереді :)

Мүмкін біреу менің кодымның қалай жұмыс істейтінін жақсырақ түсіндіреді ...

Ақпарат көзі: www.habr.com

пікір қалдыру