QEMU.js: indi ciddi və WASM ilə

Bir vaxtlar əylənmək qərarına gəldim prosesin tərsinə çevrilməsini sübut edin və maşın kodundan JavaScript (daha doğrusu, Asm.js) yaratmağı öyrənin. Təcrübə üçün QEMU seçildi və bir müddət sonra Habr haqqında məqalə yazılıb. Şərhlərdə mənə layihəni WebAssembly-də yenidən düzəltməyi və hətta özümü tərk etməyi məsləhət gördük. demək olar ki, bitdi Mən birtəhər layihəni istəmirdim... İş gedirdi, amma çox yavaş-yavaş və indi, bu yaxınlarda o məqalədə çıxdı şərh "Bəs hər şey necə bitdi?" mövzusunda Ətraflı cavabıma cavab olaraq “Bu, məqalə kimi səslənir” eşitdim. Yaxşı, bacarsan, məqalə olacaq. Bəlkə kimsə bunu faydalı tapacaq. Buradan oxucu QEMU kod generasiya arxa planlarının dizaynı, həmçinin veb tətbiqi üçün Just-in-Time kompilyatorunu necə yazmaq barədə bəzi faktları öyrənəcək.

vəzifələri

QEMU-nu JavaScript-ə “birtəhər” portla necə bağlamağı artıq öyrəndiyim üçün bu dəfə bunu ağıllı şəkildə etmək və köhnə səhvləri təkrarlamamaq qərarına gəldim.

Bir nömrəli səhv: buraxılış nöqtəsindən filial

İlk səhvim versiyamı 2.4.1-in yuxarı versiyasından ayırmaq oldu. Sonra mənə yaxşı bir fikir gəldi: əgər nöqtə buraxılışı varsa, o, yəqin ki, sadə 2.4-dən daha sabitdir və daha çox filial master. Öz səhvlərimin kifayət qədər miqdarını əlavə etməyi planlaşdırdığım üçün başqa heç kimə ehtiyacım yox idi. Yəqin ki, belə çıxdı. Ancaq burada bir şey var: QEMU yerində dayanmır və müəyyən bir nöqtədə hətta yaradılan kodun 10 faiz optimallaşdırılmasını elan etdilər.“Bəli, indi donacağam” deyə düşündüm və sındım. Burada bir təxribat etmək lazımdır: QEMU.js-in tək yivli təbiətinə və orijinal QEMU-nun çox iş parçacığının olmamasını (yəni, bir-biri ilə əlaqəli olmayan bir neçə kod yolunu eyni vaxtda idarə etmək imkanı və təkcə “bütün ləpələrdən istifadə et” deyil) onun üçün vacibdir, xaricdən zəng edə bilmək üçün iplərin əsas funksiyalarını “çıxarmalı idim”. Bu, birləşmə zamanı bəzi təbii problemlər yaratdı. Ancaq budaqlardan bəzilərinin dəyişməsi faktı masterKodumu birləşdirməyə çalışdığım , həmçinin nöqtə buraxılışında (və buna görə də filialımda) albalı seçildi, yəqin ki, rahatlıq əlavə etməyəcəkdi.

Ümumiyyətlə, qərara gəldim ki, prototipi atmaq, hissələr üçün sökmək və daha təzə və indi bir şeyə əsaslanaraq sıfırdan yeni bir versiya qurmaq məntiqlidir. master.

İkinci səhv: TLP metodologiyası

Əslində, bu, səhv deyil, ümumiyyətlə, həm "hara və necə köçməli?", həm də ümumiyyətlə "ora çatacağıq?" Bu şərtlərdə yöndəmsiz proqramlaşdırma haqlı variant idi, amma təbii ki, mən bunu lazımsız yerə təkrarlamaq istəmədim. Bu dəfə mən bunu ağıllı şəkildə etmək istədim: atom öhdəliyi, şüurlu kod dəyişiklikləri (və bir dəfə Linus Torvaldsın Wikiquote-ə görə kimsə haqqında dediyi kimi “təsadüfi simvolları tərtib edənə qədər (xəbərdarlıqla) birləşdirmək deyil) və s.

Üç nömrəli səhv: keçidi bilmədən suya girmək

Mən hələ də bundan tam qurtulmamışam, amma indi qətiyyən ən az müqavimət yolu ilə getməməyə və bunu “böyüklər kimi” etməyə qərar verdim, yəni TCG backend-i sıfırdan yazın ki, belə olmasın. sonra demək lazımdır ki, "Bəli, bu, əlbəttə ki, yavaş-yavaşdır, amma mən hər şeyi idarə edə bilmirəm - TCI belə yazılır ..." Üstəlik, bu, əvvəlcə açıq bir həll kimi görünürdü, çünki İkili kod yaradıram. Necə deyərlər, “Gent toplandıу, lakin o biri deyil”: kod, əlbəttə ki, ikilikdir, lakin idarəetmə sadəcə ona ötürülə bilməz - tərtib etmək üçün açıq şəkildə brauzerə itələnməlidir, nəticədə JS dünyasından müəyyən bir obyekt yaranır ki, bu da hələ də tələb olunur. bir yerdə xilas olmaq. Bununla belə, normal RISC arxitekturalarında, başa düşdüyüm qədər, tipik bir vəziyyət, bərpa edilmiş kod üçün təlimat önbelleğini açıq şəkildə sıfırlamaq ehtiyacıdır - əgər bu bizə lazım deyilsə, hər halda, yaxındır. Bundan əlavə, son cəhdimdən öyrəndim ki, idarəetmə tərcümə blokunun ortasına köçürülmür, ona görə də hər hansı bir ofsetdən şərh olunan bayt koduna ehtiyacımız yoxdur və biz onu sadəcə olaraq TB-dəki funksiyadan yarada bilərik. .

Gəlib təpik vurdular

İyul ayında kodu yenidən yazmağa başlasam da, sehrli zərbə diqqətdən kənarda qaldı: adətən GitHub-dan məktublar Problemlər və Pull sorğularına cavablar haqqında bildirişlər kimi gəlir, lakin burada, birdən mövzuda qeyd edin Binaryen qemu backend kimi kontekstdə, "O, belə bir şey etdi, bəlkə bir şey deyər." Biz Emscriptenin əlaqəli kitabxanasından istifadə etməkdən danışırdıq Binaryen WASM JIT yaratmaq. Yaxşı, dedim ki, sizin orada Apache 2.0 lisenziyanız var və QEMU bütövlükdə GPLv2 altında paylanır və onlar çox uyğun deyil. Birdən məlum oldu ki, lisenziya ola bilər birtəhər düzəldin (Bilmirəm: bəlkə dəyişdirin, bəlkə ikili lisenziyalaşdırma, bəlkə başqa bir şey...). Bu, əlbəttə ki, məni sevindirdi, çünki o vaxta qədər mən artıq diqqətlə baxmışdım ikili format WebAssembly və mən bir növ kədərli və anlaşılmaz idim. Keçid qrafiki ilə əsas blokları yeyən, bayt kodunu istehsal edən və hətta lazım gələrsə, onu tərcüməçinin özündə işlədən bir kitabxana da var idi.

Sonra daha çox oldu məktub QEMU poçt siyahısındadır, lakin bu daha çox “Bu kimə lazımdır?” sualına aiddir. Və belədir birdən, lazım olduğu ortaya çıxdı. Ən azı, az və ya çox tez işləyirsə, aşağıdakı istifadə imkanlarını birləşdirə bilərsiniz:

  • heç bir quraşdırma olmadan maarifləndirici bir şey işə salmaq
  • iOS-da virtuallaşdırma, burada şayiələrə görə, tez kod yaratmaq hüququ olan yeganə proqram JS mühərrikidir (bu doğrudurmu?)
  • mini-OS-nin nümayişi - tək disket, quraşdırılmış, bütün növ proqram təminatı və s...

Brauzerin işləmə müddəti xüsusiyyətləri

Artıq dediyim kimi, QEMU multithreading ilə bağlıdır, lakin brauzerdə bu yoxdur. Yaxşı, yəni, yox... Əvvəlcə ümumiyyətlə yox idi, sonra WebWorkers ortaya çıxdı - başa düşdüyüm qədər, bu, mesaj ötürülməsinə əsaslanan çoxillikdir. paylaşılan dəyişənlər olmadan. Təbii ki, bu, paylaşılan yaddaş modeli əsasında mövcud kodu daşıyarkən əhəmiyyətli problemlər yaradır. Daha sonra ictimai təzyiqlə adı altında da həyata keçirildi SharedArrayBuffers. Tədricən təqdim olundu, onlar müxtəlif brauzerlərdə onun buraxılışını qeyd etdilər, sonra Yeni ili qeyd etdilər, sonra isə Meltdown... Bundan sonra onlar vaxt ölçmənin qaba və ya qaba olduğu qənaətinə gəldilər, lakin ortaq yaddaşın köməyi ilə və bir sayğac artıran mövzu, hamısı eynidir olduqca dəqiq işləyəcək. Beləliklə, paylaşılan yaddaşla çox iş parçacığını deaktiv etdik. Deyəsən, sonradan onu yenidən işə saldılar, lakin ilk təcrübədən məlum oldu ki, onsuz da həyat var və əgər belədirsə, biz bunu çoxilliklərə etibar etmədən etməyə çalışacağıq.

İkinci xüsusiyyət, yığınla aşağı səviyyəli manipulyasiyaların qeyri-mümkünlüyüdür: sadəcə götürə, mövcud konteksti saxlaya və yeni bir yığınla yenisinə keçə bilməzsiniz. Zəng yığını JS virtual maşını tərəfindən idarə olunur. Görünür, problem nədir, çünki biz əvvəlki axınları tamamilə əl ilə idarə etmək qərarına gəldik? Fakt budur ki, QEMU-da blok I/O koroutinlər vasitəsilə həyata keçirilir və burada aşağı səviyyəli yığın manipulyasiyaları lazımlı olacaq. Xoşbəxtlikdən, Emscipten artıq asinxron əməliyyatlar üçün bir mexanizm ehtiva edir, hətta ikisi: Asinfikasiya edin и Tərcüməçi. Birincisi yaradılan JavaScript kodunda əhəmiyyətli şişkinlik vasitəsilə işləyir və artıq dəstəklənmir. İkincisi, hazırkı "düzgün yol"dur və yerli tərcüməçi üçün bayt kodu generasiyası vasitəsilə işləyir. Bu, əlbəttə ki, yavaş-yavaş işləyir, lakin kodu şişirtmir. Doğrudur, bu mexanizm üçün koroutinlərə dəstək müstəqil şəkildə verilməli idi (Asyncify üçün artıq yazılmış koroutinlər var idi və Emterpreter üçün təxminən eyni API tətbiqi var idi, sadəcə onları birləşdirmək lazım idi).

Hal-hazırda mən hələ kodu WASM-də tərtib edilmiş və Emterpreter istifadə edərək şərh edilən birinə bölməyi bacarmamışam, ona görə də blok cihazları hələ işləmir (necə deyərlər, növbəti seriyaya baxın...). Yəni, sonda bu gülməli laylı şey kimi bir şey almalısınız:

  • şərh edilmiş blok I/O. Yaxşı, həqiqətən NVMe-nin doğma performansla təqlid edilməsini gözləyirdinizmi? 🙂
  • statik olaraq tərtib edilmiş əsas QEMU kodu (tərcüməçi, digər emulyasiya edilmiş cihazlar və s.)
  • WASM-də dinamik şəkildə tərtib edilmiş qonaq kodu

QEMU mənbələrinin xüsusiyyətləri

Yəqin ki, artıq təxmin etdiyiniz kimi, qonaq arxitekturasını təqlid etmək üçün kod və host maşın təlimatlarını yaratmaq üçün kod QEMU-da ayrılmışdır. Əslində, bu, bir az daha hiyləgərdir:

  • qonaq memarlıqları var
  • yoxdur sürətləndiricilər, yəni Linux-da hardware virtualizasiyası üçün KVM (bir-birinə uyğun gələn qonaq və host sistemləri üçün), hər yerdə JIT kodu yaratmaq üçün TCG. QEMU 2.9 ilə başlayaraq, Windows-da HAXM hardware virtualizasiya standartı üçün dəstək ortaya çıxdı (detalları)
  • Əgər hardware virtualizasiyası deyil, TCG istifadə olunursa, o zaman hər bir host arxitekturası, eləcə də universal tərcüməçi üçün ayrıca kod yaratma dəstəyinə malikdir.
  • ... və bütün bunlar ətrafında - təqlid edilmiş periferiyalar, istifadəçi interfeysi, miqrasiya, qeyd-təkrar və s.

Yeri gəlmişkən, bilirdinizmi: QEMU təkcə bütün kompüteri deyil, həm də host nüvəsindəki ayrıca istifadəçi prosesi üçün prosessoru təqlid edə bilər, məsələn, ikili alətlər üçün AFL fuzzer tərəfindən istifadə olunur. Bəlkə kimsə QEMU-nun bu iş rejimini JS-ə köçürmək istərdi? 😉

Çoxdankı pulsuz proqram təminatı kimi, QEMU zəng vasitəsilə qurulur configure и make. Deyək ki, bir şey əlavə etmək qərarına gəldiniz: bir TCG backend, mövzu tətbiqi, başqa bir şey. Autoconf ilə ünsiyyət qurmaq perspektivində sevinməyə/dəhşətə (lazım olduqda altını çəkin) tələsməyin - əslində, configure QEMU-nun özü yazılmışdır və heç bir şeydən yaranmır.

WebAssembly

Beləliklə, WebAssembly (aka WASM) adlanan bu şey nədir? Bu, artıq etibarlı JavaScript kodu kimi görünməyən Asm.js-in əvəzidir. Əksinə, o, sırf ikili və optimallaşdırılmışdır və hətta sadəcə ona tam ədəd yazmaq çox sadə deyil: yığcamlıq üçün formatda saxlanılır. LEB128.

Asm.js üçün təkrar dövriyyə alqoritmi haqqında eşitmiş ola bilərsiniz - bu, JS mühərrikləri üçün nəzərdə tutulmuş "yüksək səviyyəli" axına nəzarət təlimatlarının (yəni, əgər-onsuz da, döngələr və s.) bərpasıdır. aşağı səviyyəli LLVM IR, prosessor tərəfindən yerinə yetirilən maşın koduna daha yaxındır. Təbii ki, QEMU-nun ara təmsilçiliyi ikinciyə daha yaxındır. Deyəsən, budur, bayt kodu, əzabın sonu... Və sonra bloklar, əgər-onda-else və döngələr var!..

Bu Binaryen-in faydalı olmasının başqa bir səbəbidir: o, təbii olaraq WASM-də saxlanacaqlara yaxın yüksək səviyyəli blokları qəbul edə bilər. Lakin o, həm də əsas blokların və onlar arasında keçidlərin qrafikindən kod yarada bilər. Yaxşı, mən artıq dedim ki, o, WebAssembly saxlama formatını rahat C/C++ API arxasında gizlədir.

TCG (Tiny Code Generator)

GTC əslən idi C kompilyatoru üçün backend.Sonra, görünür, GCC ilə rəqabətə tab gətirə bilmədi, lakin sonda QEMU-da host platforması üçün kod yaratmaq mexanizmi kimi öz yerini tapdı. Tərcüməçi tərəfindən dərhal yerinə yetirilən bəzi mücərrəd bayt kodu yaradan TCG backend də var, lakin mən bu dəfə ondan istifadə etməmək qərarına gəldim. Bununla belə, QEMU-da funksiya vasitəsilə yaradılan vərəmə keçidi təmin etmək artıq mümkündür tcg_qemu_tb_exec, mənim üçün çox faydalı oldu.

QEMU-ya yeni TCG backend əlavə etmək üçün siz alt kataloq yaratmalısınız tcg/<имя архитектуры> (bu halda, tcg/binaryen) və iki fayldan ibarətdir: tcg-target.h и tcg-target.inc.c и yazmaq hər şey haqqındadır configure. Oraya başqa faylları qoya bilərsiniz, lakin bu ikisinin adından təxmin edə bildiyiniz kimi, onların hər ikisi harasa daxil ediləcək: biri adi başlıq faylı kimi (bu, daxil edilir tcg/tcg.h, və o biri artıq kataloqlardakı digər fayllardadır tcg, accel və yalnız), digəri - yalnız kod parçası olaraq tcg/tcg.c, lakin onun statik funksiyalarına çıxışı var.

Bunun necə işlədiyinə dair təfərrüatlı araşdırmalara çox vaxt sərf edəcəyimə qərar verərək, bu iki faylın "skeletlərini" başqa bir backend tətbiqindən kopyaladım və bunu lisenziya başlığında vicdanla qeyd etdim.

Файл tcg-target.h formada əsasən parametrləri ehtiva edir #define-s:

  • hədəf arxitekturasında neçə registr və hansı genişlik var (bizdə istədiyimiz qədər, istədiyimiz qədər var - sual daha çox "tamamilə hədəf" arxitekturasında brauzer tərəfindən daha səmərəli koda nəyin yaradılacağı ilə bağlıdır ...)
  • host təlimatlarının uyğunlaşdırılması: x86-da və hətta TCI-də təlimatlar ümumiyyətlə uyğunlaşdırılmır, amma kod buferinə ümumiyyətlə təlimatlar deyil, Binaryen kitabxana strukturlarına göstəricilər qoyacağam, ona görə deyəcəyəm: 4 bayt
  • Backend hansı isteğe bağlı təlimatları yarada bilər - Binaryen-də tapdığımız hər şeyi daxil edirik, sürətləndirici qalanları daha sadə olanlara ayırsın.
  • Backend tərəfindən tələb olunan TLB keşinin təxmini ölçüsü nədir. Fakt budur ki, QEMU-da hər şey ciddidir: qonaq MMU-nu nəzərə alaraq yükləmə/saxlama funksiyalarını yerinə yetirən köməkçi funksiyalar olsa da (onsuz biz indi harda olardıq?), onlar öz tərcümə keşini struktur şəklində saxlayırlar. emalı birbaşa yayım bloklarına daxil etmək üçün əlverişlidir. Sual budur ki, bu strukturda hansı ofset kiçik və sürətli əmrlər ardıcıllığı ilə ən səmərəli şəkildə işlənir?
  • burada bir və ya iki qorunan registrlərin məqsədini dəyişə, funksiya vasitəsilə TB-yə zəng etməyə imkan verə və isteğe bağlı olaraq bir neçə kiçik registr təsvir edə bilərsiniz. inlinekimi funksiyaları yerinə yetirir flush_icache_range (amma bu bizim vəziyyətimiz deyil)

Файл tcg-target.inc.c, əlbəttə ki, ölçüsü adətən daha böyükdür və bir neçə məcburi funksiyanı ehtiva edir:

  • inisializasiya, o cümlədən hansı təlimatların hansı operandlar üzərində işləyə biləcəyinə dair məhdudiyyətlər. Açıqca mənim tərəfimdən başqa bir arxa hissədən kopyalandı
  • bir daxili bayt kodu təlimatını alan funksiya
  • Burada köməkçi funksiyaları da qoya bilərsiniz və statik funksiyalardan da istifadə edə bilərsiniz tcg/tcg.c

Özüm üçün aşağıdakı strategiyanı seçdim: növbəti tərcümə blokunun ilk sözlərində dörd göstəricini yazdım: başlanğıc işarəsi (yaxınlıqda müəyyən bir dəyər 0xFFFFFFFF, TB-nin cari vəziyyətini təyin edən), kontekst, yaradılan modul və sazlama üçün sehrli nömrə. Əvvəlcə işarə qoyuldu 0xFFFFFFFF - nHara n - kiçik müsbət rəqəmdir və hər dəfə tərcüməçi vasitəsilə yerinə yetirildikdə 1 artdı. 0xFFFFFFFE, kompilyasiya baş verdi, modul funksional cədvəldə saxlandı, icranın getdiyi kiçik bir "başlatıcıya" idxal edildi. tcg_qemu_tb_exec, və modul QEMU yaddaşından çıxarıldı.

Klassikləri təfsir etmək üçün, "Crutch, progerin ürəyi üçün bu səsdə nə qədər iç-içə...". Ancaq yaddaş harasa sızırdı. Üstəlik, QEMU tərəfindən idarə olunan yaddaş idi! Mənim kodum var idi ki, növbəti təlimatı (yaxşı, yəni göstərici) yazarkən əvvəllər linki bu yerdə olanı sildim, amma bu kömək etmədi. Əslində, ən sadə halda, QEMU başlanğıcda yaddaş ayırır və yaradılan kodu orada yazır. Bufer bitdikdə kod atılır və onun yerinə növbətisi yazılmağa başlayır.

Kodu öyrəndikdən sonra başa düşdüm ki, sehrli nömrə ilə hiylə mənə ilk keçiddə işə salınmamış buferdə səhv bir şey çıxararaq yığın məhvində uğursuz olmamağa imkan verdi. Bəs sonradan mənim funksiyamdan yan keçmək üçün buferi kim yenidən yazır? Emscripten tərtibatçılarının məsləhət gördüyü kimi, problemlə üzləşəndə ​​mən yaranan kodu yenidən yerli proqrama köçürdüm, onun üzərinə Mozilla Record-Replay-ı təyin etdim... Ümumiyyətlə, sonda sadə bir şeyi başa düşdüm: hər blok üçün, a struct TranslationBlock təsviri ilə. Harada olduğunu təxmin et... Bu, tampondakı blokdan dərhal əvvəl. Bunu dərk edərək, qoltuqağacı istifadə etməyi dayandırmaq qərarına gəldim (ən azı bəziləri) və sadəcə sehrli nömrəni atdım və qalan sözləri köçürdüm. struct TranslationBlock, tərcümə keşi sıfırlandıqda tez keçilə bilən tək əlaqəli siyahı yaratmaq və yaddaşı boşaltmaq.

Bəzi qoltuqaltılar qalır: məsələn, kod buferində işarələnmiş göstəricilər - bəziləri sadəcə olaraq BinaryenExpressionRef, yəni yaradılan əsas bloka xətti olaraq qoyulması lazım olan ifadələrə baxırlar, hissə BB-lər arasında keçid üçün şərtdir, hissə hara getmək lazımdır. Yaxşı, şərtlərə uyğun olaraq bağlanmalı olan Relooper üçün artıq hazırlanmış bloklar var. Onları ayırd etmək üçün, onların hamısının ən azı dörd baytla uyğunlaşdırıldığı fərziyyəsindən istifadə olunur, beləliklə, etiket üçün ən az əhəmiyyətli iki bitdən etibarlı şəkildə istifadə edə bilərsiniz, sadəcə zəruri hallarda onu silməyi unutmayın. Yeri gəlmişkən, bu cür etiketlər artıq QEMU-da TCG döngəsindən çıxma səbəbini göstərmək üçün istifadə olunur.

Binaryen istifadə edərək

WebAssembly-dəki modullar funksiyaları ehtiva edir, onların hər birində ifadə olan gövdə var. İfadələr birlik və binar əməliyyatlar, digər ifadələrin siyahılarından ibarət bloklar, idarəetmə axını və s. Artıq dediyim kimi, burada nəzarət axını yüksək səviyyəli filiallar, döngələr, funksiya çağırışları və s. Funksiyaların arqumentləri stekdə deyil, JS-də olduğu kimi açıq şəkildə ötürülür. Qlobal dəyişənlər də var, amma mən onlardan istifadə etməmişəm, ona görə də onlar haqqında sizə danışmayacağam.

Funksiyalar həmçinin sıfırdan nömrələnmiş yerli dəyişənlərə malikdir: int32 / int64 / float / double. Bu halda ilk n yerli dəyişən funksiyaya ötürülən arqumentlərdir. Nəzərə alın ki, burada hər şey nəzarət axını baxımından tamamilə aşağı səviyyədə olmasa da, tam ədədlər hələ də “imzalı/imzasız” atributunu daşımır: nömrənin necə davranması əməliyyat kodundan asılıdır.

Ümumiyyətlə, Binaryen təmin edir sadə C-API: modul yaradırsınız, onda ifadələr yaratmaq - birlik, binar, digər ifadələrdən bloklar, idarəetmə axını və s. Sonra gövdəsi kimi ifadə olan bir funksiya yaradırsınız. Əgər siz də mənim kimi aşağı səviyyəli keçid qrafikiniz varsa, relooper komponenti sizə kömək edəcək. Anladığım qədər, blokun hüdudlarından kənara çıxmamaq şərti ilə blokda icra axınına yüksək səviyyəli nəzarətdən istifadə etmək mümkündür - yəni daxili sürətli yol / yavaş etmək mümkündür. daxili TLB keş emal kodu daxilində budaqlanan yol, lakin "xarici" idarəetmə axınına müdaxilə etməmək üçün. Yenidən işlədici azad etdikdə onun blokları boşalır, modulu azad etdikdə ona ayrılmış ifadələr, funksiyalar və s. yox olur. arena.

Bununla belə, tərcüməçi instansiyasını lazımsız yaratmadan və silmədən kodu tez şərh etmək istəyirsinizsə, bu məntiqi C++ faylına yerləşdirmək və oradan da hazır proqramlardan yan keçərək kitabxananın bütün C++ API-sini birbaşa idarə etmək məntiqli ola bilər. sarğılar düzəltdi.

Beləliklə, sizə lazım olan kodu yaratmaq üçün

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

... nəyisə unutmuşamsa, bağışlayın, bu sadəcə miqyası göstərmək üçündür və təfərrüatlar sənədlərdə var.

İndi crack-fex-pex başlayır, belə bir şey:

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 və JS aləmlərini bir növ birləşdirmək və eyni zamanda tərtib edilmiş funksiyalara tez daxil olmaq üçün massiv yaradıldı (başlatıcıya idxal üçün funksiyalar cədvəli) və yaradılan funksiyalar orada yerləşdirildi. İndeksi tez hesablamaq üçün əvvəlcə sıfır sözdən ibarət tərcümə blokunun indeksi onun kimi istifadə edildi, lakin sonra bu düsturla hesablanan indeks sadəcə olaraq sahəyə uyğunlaşmağa başladı. struct TranslationBlock.

Yeri gəlmişkən, demo (hazırda qaranlıq lisenziya ilə) yalnız Firefox-da yaxşı işləyir. Chrome tərtibatçıları idi nədənsə hazır deyil Kiminsə WebAssembly modullarının mindən çox nümunəsini yaratmaq istəməsinə görə, onlar sadəcə olaraq hər biri üçün bir gigabayt virtual ünvan sahəsi ayırdılar...

Hələlik bu qədər. Yəqin ki, kimsə maraqlansa, başqa məqalə də olacaq. Yəni ən azı qalır yalnız blok cihazlarının işləməsini təmin edin. JS dünyasında adət olduğu kimi, WebAssembly modullarının tərtibini asinxron etmək də məntiqli ola bilər, çünki yerli modul hazır olana qədər bütün bunları edə biləcək tərcüməçi hələ də var.

Nəhayət bir tapmaca: siz 32-bitlik arxitekturada binar tərtib etmisiniz, lakin kod yaddaş əməliyyatları vasitəsilə Binaryen-dən, yığının bir yerində və ya 2-bit ünvan sahəsinin yuxarı 32 GB-da başqa bir yerə qalxır. Problem ondadır ki, Binaryen nöqteyi-nəzərindən bu, çox böyük bir nəticə ünvanı əldə etməkdir. Bunun ətrafından necə çıxmaq olar?

Admin kimi

Bunu sınaqdan keçirmədim, amma ilk fikrim “32 bitlik Linux quraşdırsam nə olacaq?” oldu. Sonra ünvan sahəsinin yuxarı hissəsi nüvə tərəfindən tutulacaq. Yeganə sual nə qədər işğal ediləcəyidir: 1 və ya 2 Gb.

Proqramçı üsulu ilə (praktikilər üçün seçim)

Ünvan sahəsinin yuxarı hissəsində bir qabarcıq üfürək. Mən özüm bunun niyə işlədiyini başa düşmürəm - orada artıq bir yığın olmalıdır. Ancaq "biz praktikantıq: hər şey bizim üçün işləyir, amma heç kim niyə bilmir ..."

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

... doğrudur, Valgrind ilə uyğun deyil, amma xoşbəxtlikdən Valgrind özü çox təsirli şəkildə hamını oradan itələyir :)

Bəlkə kimsə mənim bu kodumun necə işlədiyini daha yaxşı izah edəcək...

Mənbə: www.habr.com

Добавить комментарий