QEMU.js: одоо ноцтой бөгөөд WASM-тай

Нэг удаа би зугаацахаар шийдсэн үйл явцын эргэлт буцалтгүй байдлыг нотлох мөн машины кодоос JavaScript (илүү нарийвчлалтай Asm.js) үүсгэх талаар сурах. Туршилтанд QEMU сонгогдсон бөгөөд хэсэг хугацааны дараа Хабр дээр нийтлэл бичсэн. Сэтгэгдлүүд дээр надад төслийг WebAssembly дээр дахин хийх, тэр ч байтугай өөрийгөө орхихыг зөвлөсөн. бараг дууссан Би ямар нэгэн байдлаар төслийг хүсээгүй ... Ажил үргэлжилж байсан, гэхдээ маш удаан, одоо, саяхан тэр нийтлэлд гарч ирэв. тайлбар "Тэгвэл энэ бүхэн хэрхэн дууссан бэ?" сэдвээр Миний дэлгэрэнгүй хариултын хариуд би "Энэ нийтлэл шиг сонсогдож байна." За тэгээд чадаж байвал нийтлэл гарна. Магадгүй хэн нэгэнд хэрэг болох байх. Уншигч эндээс QEMU код үүсгэх backend-ийн дизайн, вэб програмд ​​зориулж Just-in-Time хөрвүүлэгчийг хэрхэн бичих талаар зарим баримтуудыг мэдэж авах болно.

үүрэг

Би аль хэдийн QEMU-г JavaScript руу "ямар нэгэн байдлаар" порт хийх талаар сурсан байсан тул энэ удаад үүнийг ухаалгаар хийж, хуучин алдаагаа давтахгүй байхаар шийдсэн.

Алдаа дугаар нэг: цэгийн хувилбараас салбар

Миний анхны алдаа бол өөрийн хувилбарыг 2.4.1-ийн дээд хувилбараас салгасан явдал юм. Дараа нь надад сайн санаа санагдсан: хэрэв цэгийн хувилбар байгаа бол энэ нь энгийн 2.4-ээс илүү тогтвортой байх болно, тэр ч байтугай салбар master. Би өөрийнхөө алдаануудыг нэлээд хэмжээгээр нэмэхээр төлөвлөж байсан болохоор өөр хэн нэгнийх нь огт хэрэггүй байсан. Ингэж байж магадгүй. Гэхдээ энд нэг зүйл байна: QEMU зогсохгүй, хэзээ нэгэн цагт тэд үүсгэсэн кодыг 10 хувиар оновчлохыг зарласан. "Тийм ээ, би одоо хөлдөх болно" гэж би бодоод эвдэрсэн. QEMU.js-ийн нэг урсгалтай шинж чанар, анхны QEMU нь олон урсгал байхгүй гэсэн үг биш (өөрөөр хэлбэл хэд хэдэн хамааралгүй кодын замыг нэгэн зэрэг ажиллуулах чадвар, ба зөвхөн "бүх цөмийг ашиглах" биш) нь чухал ач холбогдолтой, утаснуудын үндсэн функцууд нь би гаднаас залгахын тулд "үүнийг эргүүлэх" хэрэгтэй болсон. Энэ нь нэгдэх явцад байгалийн зарим бэрхшээлийг бий болгосон. Гэсэн хэдий ч салбараас зарим өөрчлөлтүүд гарсан master, Би өөрийн кодыг нэгтгэх гэж оролдсон бөгөөд интоорыг цэгийн хувилбарт (тиймээс миний салбараас) сонгосон нь бас тав тухтай байдлыг нэмэгдүүлэхгүй байх байсан.

Ерөнхийдөө би загвараа хаяж, эд ангиудыг нь задалж, шинэ, шинэ зүйл дээр үндэслэн шинэ хувилбарыг эхнээс нь бүтээх нь утга учиртай гэж шийдсэн. master.

Хоёр дахь алдаа: TLP аргачлал

Үндсэндээ энэ нь алдаа биш, ерөнхийдөө "хаана, яаж нүүх вэ?", ерөнхийдөө "бид тийшээ очих уу?" гэсэн ойлголтыг бүрэн ойлгоогүй нөхцөлд төсөл зохиох онцлог шинж юм. Эдгээр нөхцөлд болхи програмчлал Энэ нь үндэслэлтэй сонголт байсан, гэхдээ би үүнийг шаардлагагүйгээр давтахыг хүсээгүй. Энэ удаад би үүнийг ухаалгаар хийхийг хүссэн: атомын үйлдлүүд, ухамсартай кодын өөрчлөлтүүд (мөн Линус Торвалдсын нэгэнтээ Wikiquote-д бичсэнээр хэн нэгний тухай хэлсэнчлэн "санамсаргүй тэмдэгтүүдийг эмхэтгэх хүртлээ (анхааралтай) хамтад нь оруулахгүй" гэх мэт).

Гурав дахь алдаа: гарцыг мэдэхгүй усанд орох

Би үүнээс бүрэн ангижраагүй байгаа ч одоо би хамгийн бага эсэргүүцэлтэй замыг дагахгүй байхаар шийдсэн бөгөөд үүнийг "насанд хүрсэн хүн шиг" хийхээр шийдсэн. Дараа нь "Тийм ээ, энэ нь мэдээжийн хэрэг, аажмаар, гэхдээ би бүгдийг хянаж чадахгүй - TCI ингэж бичигдсэн ..." гэж хэлэх хэрэгтэй. Түүгээр ч барахгүй энэ нь эхэндээ тодорхой шийдэл мэт санагдаж байсан Би хоёртын код үүсгэдэг. Тэдний хэлснээр "Гент цуглараву, гэхдээ тэр нь биш": код нь мэдээжийн хэрэг хоёртын хувилбар боловч хяналтыг түүн рүү шилжүүлэх боломжгүй - үүнийг хөрвүүлэхийн тулд хөтөч рүү шууд оруулах ёстой бөгөөд үүний үр дүнд JS ертөнцөөс тодорхой объект гарч ирнэ. хаа нэгтээ аврагдах болно. Гэсэн хэдий ч, миний ойлгож байгаагаар ердийн RISC архитектурын хувьд ердийн нөхцөл байдал бол шинэчилсэн кодын зааврын кэшийг тодорхой дахин тохируулах шаардлагатай байдаг - хэрэв энэ нь бидэнд хэрэгтэй зүйл биш бол ямар ч тохиолдолд энэ нь ойрхон байна. Нэмж дурдахад, миний сүүлчийн оролдлогоос би хяналт орчуулгын блокийн дунд шилждэггүй юм шиг ойлгосон, тиймээс бидэнд байт кодыг ямар ч офсетээс тайлбарлах шаардлагагүй бөгөөд бид үүнийг зүгээр л TB дээрх функцээс үүсгэж болно. .

Тэд ирээд өшиглөсөн

Хэдийгээр би XNUMX-р сард кодыг дахин бичиж эхэлсэн ч үл анзаарагдам шидэт цохилт гарч ирэв: ихэвчлэн GitHub-аас захидал Асуудал болон Татан авах хүсэлтийн хариуны тухай мэдэгдэл хэлбэрээр ирдэг, гэхдээ энд, гэнэт л сэдвээр дурдъя Binaryen нь qemu backend байдлаар "Тэр ийм зүйл хийсэн, магадгүй тэр ямар нэг юм хэлэх байх." Бид Emscripten-ийн холбогдох номын санг ашиглах тухай ярьж байсан Бинарен WASM JIT үүсгэх. Би чамайг Apache 2.0 лицензтэй гэж хэлсэн бөгөөд QEMU нь бүхэлдээ GPLv2-ийн дагуу тархсан бөгөөд тэдгээр нь тийм ч нийцэхгүй байна. Гэнэт лиценз байж болох юм байна ямар нэгэн байдлаар засах (Би мэдэхгүй байна: магадгүй үүнийг өөрчлөх, магадгүй давхар лиценз, магадгүй өөр зүйл ...). Энэ нь мэдээжийн хэрэг намайг аз жаргалтай болгосон, учир нь тэр үед би аль хэдийн анхааралтай ажигласан байсан хоёртын формат WebAssembly, би ямар нэгэн байдлаар гунигтай, ойлгомжгүй байсан. Мөн шилжилтийн график бүхий үндсэн блокуудыг залгиж, байт кодыг гаргаж, шаардлагатай бол орчуулагч дээр ажиллуулдаг номын сан байсан.

Дараа нь илүү их байсан захидал QEMU захидлын жагсаалтад байгаа боловч энэ нь "Ямар ч байсан энэ хэнд хэрэгтэй вэ?" Гэсэн асуултын тухай юм. Тэгээд ч тийм гэнэт л, энэ нь зайлшгүй шаардлагатай болсон. Наад зах нь, хэрэв энэ нь илүү хурдан ажиллах юм бол та дараах хэрэглээний боломжуудыг хамтад нь хусах боломжтой.

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

Хөтчийн ажиллах цагийн онцлогууд

Би аль хэдийн хэлсэнчлэн QEMU нь олон урсгалтай холбоотой боловч хөтөч дээр энэ нь байдаггүй. Тийм ээ, үгүй ​​... Эхлээд энэ нь огт байхгүй байсан, дараа нь WebWorkers гарч ирэв - миний ойлгож байгаагаар энэ нь мессеж дамжуулахад үндэслэсэн олон урсгал юм. хуваалцсан хувьсагчгүй. Мэдээжийн хэрэг, энэ нь хуваалцсан санах ойн загвар дээр суурилсан кодыг шилжүүлэхэд ихээхэн бэрхшээл учруулдаг. Тэгээд олон нийтийн шахалтаар мөн л нэрийн дор хэрэгжсэн SharedArrayBuffers. Үүнийг аажмаар нэвтрүүлж, янз бүрийн хөтөч дээр нээлтээ тэмдэглэж, дараа нь тэд шинэ жилээ тэмдэглэж, дараа нь Meltdown... Үүний дараа тэд цаг хугацааны хэмжилтийг бүдүүлэг эсвэл бүдүүлэг гэсэн дүгнэлтэд хүрсэн, гэхдээ хуваалцсан санах ойн тусламжтайгаар тоолуурыг нэмэгдүүлэх утас, энэ нь бүгд адилхан энэ нь нэлээд нарийвчлалтай ажиллах болно. Тиймээс бид хуваалцсан санах ойтой олон урсгалыг идэвхгүй болгосон. Хожим нь тэд үүнийг буцааж асаасан бололтой, гэхдээ эхний туршилтаас харахад түүнгүйгээр амьдрал байгаа бөгөөд хэрэв тийм бол бид үүнийг олон урсгалт найдлагагүйгээр хийхийг хичээх болно.

Хоёрдахь онцлог нь стектэй бага түвшний заль мэх хийх боломжгүй юм: та зүгээр л авч, одоогийн контекстийг хадгалж, шинэ стекээр шинийг сольж чадахгүй. Дуудлагын стекийг JS виртуал машин удирддаг. Бид өмнөх урсгалыг бүрэн гараар удирдахаар шийдсэн хэвээр байгаа тул ямар асуудал байгаа юм шиг санагдаж байна уу? Баримт нь QEMU дахь блок I/O нь корутинуудаар хэрэгждэг бөгөөд энд доод түвшний стекийн залруулга хэрэг болох юм. Аз болоход, Emscipten нь асинхрон үйлдлийн механизмыг аль хэдийн агуулж байгаа бөгөөд бүр хоёр нь: Асинхрончлох и Орчуулагч. Эхнийх нь үүсгэсэн JavaScript кодыг их хэмжээгээр дүүргэх замаар ажилладаг бөгөөд цаашид дэмжигдэхгүй. Хоёр дахь нь одоогийн "зөв арга" бөгөөд эх орчуулагчийн хувьд байт код үүсгэх замаар ажилладаг. Энэ нь мэдээжийн хэрэг аажмаар ажилладаг боловч кодыг бүдгэрүүлдэггүй. Энэ механизмд зориулсан корутинуудын дэмжлэгийг бие даан оруулах шаардлагатай байсан нь үнэн (Ayncify-д зориулагдсан корутинууд аль хэдийн бичигдсэн байсан бөгөөд Emterpreter-д ойролцоогоор ижил API-ийн хэрэгжилт байсан, та тэдгээрийг холбоход л хангалттай байсан).

Одоогоор би кодыг WASM-д эмхэтгэсэн, Emterpreter ашиглан тайлбарласан код болгон хувааж амжаагүй байгаа тул блок төхөөрөмж хараахан ажиллахгүй байна (тэдний хэлснээр дараагийн цувралаас үзнэ үү ...). Өөрөөр хэлбэл, эцэст нь та ийм инээдтэй давхраатай зүйлийг авах хэрэгтэй.

  • тайлбарласан блок I/O. За, та жинхэнэ гүйцэтгэлтэй NVMe-г дуурайсан гэж үнэхээр хүлээж байсан уу? 🙂
  • статик байдлаар эмхэтгэсэн үндсэн QEMU код (орчуулагч, бусад дуурайлган төхөөрөмж гэх мэт)
  • WASM руу динамикаар эмхэтгэсэн зочны код

QEMU эх сурвалжуудын онцлог

Та аль хэдийн таамаглаж байсанчлан зочны архитектурыг дуурайх код болон хост машины зааварчилгаа үүсгэх кодыг QEMU-д тусгаарласан болно. Үнэн хэрэгтээ энэ нь бүр ч илүү төвөгтэй юм:

  • зочин архитектурууд байдаг
  • байна хурдасгуурууд, тухайлбал, Линукс дээрх техник хангамжийн виртуалчлалын 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-д хадгалагдахтай ойролцоо өндөр түвшний блокуудыг хүлээн авах боломжтой. Гэхдээ энэ нь үндсэн блокууд болон тэдгээрийн хоорондох шилжилтийн графикаас код гаргаж болно. Энэ нь тохиромжтой C/C++ API-ийн ард WebAssembly хадгалах форматыг нуудаг гэж би аль хэдийн хэлсэн.

TCG (жижиг код үүсгэгч)

GTC анх байсан Си хөрвүүлэгчийн backend. Дараа нь энэ нь GCC-тэй хийсэн өрсөлдөөнийг даван туулж чадаагүй бололтой, гэхдээ эцэст нь QEMU-д хост платформд зориулсан код үүсгэх механизм болгон байр сууриа олсон. Мөн зарим хийсвэр байт код үүсгэдэг TCG backend байдаг бөгөөд үүнийг орчуулагч шууд гүйцэтгэдэг, гэхдээ би энэ удаад үүнийг ашиглахгүй байхаар шийдсэн. Гэсэн хэдий ч QEMU-д функцээр дамжуулан үүсгэсэн сүрьеэ рүү шилжих боломжтой болсон tcg_qemu_tb_exec, энэ нь надад маш хэрэгтэй болсон.

QEMU-д шинэ TCG backend нэмэхийн тулд та дэд лавлах үүсгэх хэрэгтэй 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, өөрөөр хэлбэл үүсгэсэн үндсэн блок руу шугаман байдлаар оруулах шаардлагатай илэрхийллүүдийг хардаг, хэсэг нь BB-ийн хооронд шилжих нөхцөл, хэсэг нь хаашаа явах вэ. За, Relooper-д зориулж аль хэдийн бэлтгэсэн блокууд байгаа бөгөөд тэдгээрийг нөхцөлийн дагуу холбох шаардлагатай. Тэдгээрийг ялгахын тулд тэдгээрийг дор хаяж дөрвөн байтаар зэрэгцүүлсэн гэсэн таамаглалыг ашигладаг тул шошгонд хамгийн бага ач холбогдолтой хоёр битийг аюулгүйгээр ашиглах боломжтой тул шаардлагатай бол үүнийг арилгахаа санах хэрэгтэй. Дашрамд хэлэхэд, ийм шошго нь QEMU-д TCG гогцооноос гарах шалтгааныг зааж өгөхөд аль хэдийн ашиглагдаж байна.

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 битийн Линукс суулгавал яах вэ?" Дараа нь хаягийн зайны дээд хэсгийг цөм эзэлнэ. Ганц асуулт бол 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 өөрөө хүн бүрийг тэндээс маш үр дүнтэй түлхэж өгдөг :)

Магадгүй хэн нэгэн миний энэ код хэрхэн ажилладаг талаар илүү сайн тайлбар өгөх байх ...

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх