QEMU.js: artık ciddi ve WASM ile birlikte

Bir zamanlar eğlenmeye karar verdim sürecin tersine çevrilebilirliğini kanıtlamak ve makine kodundan JavaScript'in (daha doğrusu Asm.js) nasıl oluşturulacağını öğrenin. Deney için QEMU seçildi ve bir süre sonra Habr hakkında bir makale yazıldı. Yorumlarda projeyi WebAssembly'de yeniden yapmam ve hatta kendimden ayrılmam önerildi neredeyse bitti Bir şekilde projeyi istemedim... Çalışmalar devam ediyordu ama çok yavaştı ve şimdi, yakın zamanda o makalede ortaya çıktı yorum “Peki her şey nasıl bitti?” konulu Ayrıntılı cevabıma yanıt olarak "Bu bir makaleye benziyor" ifadesini duydum. Eğer yapabilirsen, bir makale olacak. Belki birileri bunu faydalı bulacaktır. Okuyucu bu kitaptan QEMU kod oluşturma arka uçlarının tasarımının yanı sıra bir web uygulaması için Tam Zamanında derleyicinin nasıl yazılacağı hakkında bazı gerçekleri öğrenecektir.

görevler

QEMU'yu JavaScript'e "bir şekilde" nasıl aktaracağımı zaten öğrendiğim için, bu sefer bunu akıllıca yapmaya ve eski hataları tekrarlamamaya karar verdim.

Birinci hata: nokta sürümünden itibaren dallanma

İlk hatam, sürümümü yukarı akış sürümü 2.4.1'den ayırmaktı. O zaman bana iyi bir fikir gibi geldi: eğer nokta sürümü mevcutsa, o zaman muhtemelen basit 2.4'ten daha kararlıdır ve hatta şube daha da kararlıdır. master. Ve kendi hatalarımdan oldukça fazlasını eklemeyi planladığımdan, başkasının hatalarına hiç ihtiyacım olmadı. Muhtemelen böyle sonuçlandı. Ama olay şu: QEMU yerinde durmuyor ve hatta bir noktada oluşturulan kodun yüzde 10 oranında optimize edildiğini bile duyurdular. "Evet, şimdi donacağım" diye düşündüm ve yıkıldım. Burada bir konu dışına çıkmamız gerekiyor: QEMU.js'nin tek iş parçacıklı doğası nedeniyle ve orijinal QEMU'nun çoklu iş parçacığının (yani, ilgisiz birkaç kod yolunu aynı anda çalıştırma yeteneği) bulunmadığı anlamına gelmemesi ve sadece "tüm çekirdekleri kullan") onun için kritik değil, dışarıdan arayabilmek için iş parçacıklarının ana işlevlerini "çıkarmam" gerekiyordu. Bu durum birleşme sırasında bazı doğal sorunlar yarattı. Ancak şubeden bazı değişikliklerin olduğu gerçeği masterKodumu birleştirmeye çalıştığım .

Genel olarak, prototipi atmanın, parçalara ayırmanın ve daha taze bir şeye dayanarak sıfırdan yeni bir versiyon oluşturmanın hala mantıklı olduğuna karar verdim. master.

İkinci hata: TLP metodolojisi

Özünde, bu bir hata değil, genel olarak hem "nereye ve nasıl taşınacağız?" Hem de genel olarak "oraya varacak mıyız?" Konularının tamamen yanlış anlaşılması koşullarında bir proje yaratmanın bir özelliğidir. Bu koşullarda beceriksiz programlama haklı bir seçenekti ama doğal olarak gereksiz yere tekrarlamak istemedim. Bu sefer bunu akıllıca yapmak istedim: atomik taahhütler, bilinçli kod değişiklikleri (ve Wikiquote'ye göre Linus Torvalds'ın birisi hakkında söylediği gibi "derlenene kadar (uyarılarla) rastgele karakterleri bir araya getirmek" değil) vb.

Üçüncü hata: Geçidi bilmeden suya girmek

Hala bundan tamamen kurtulamadım, ancak artık en az direnç yolunu izlememeye ve bunu "bir yetişkin olarak" yapmaya, yani TCG arka ucumu sıfırdan yazmaya karar verdim. daha sonra şunu söylemek zorunda kalacağım: "Evet, elbette yavaş yavaş ama her şeyi kontrol edemiyorum - TCI böyle yazılıyor..." Üstelik bu başlangıçta bariz bir çözüm gibi görünüyordu, çünkü İkili kod üretiyorum. Dedikleri gibi, “Ghent toplandıу, ama o değil”: kod elbette ikili, ancak kontrol ona basitçe aktarılamaz - derleme için açıkça tarayıcıya itilmesi gerekir, bu da JS dünyasından belirli bir nesnenin ortaya çıkmasına neden olur ve bunun hala gerekli olması gerekir. bir yere saklanmak. Bununla birlikte, normal RISC mimarilerinde, anladığım kadarıyla, tipik bir durum, yeniden oluşturulan kod için talimat önbelleğini açıkça sıfırlama ihtiyacıdır - ihtiyacımız olan bu değilse, o zaman her durumda yakındır. Ek olarak, son denememden, kontrolün çeviri bloğunun ortasına aktarılmadığını, dolayısıyla herhangi bir ofsetten yorumlanan bayt koduna gerçekten ihtiyacımız olmadığını ve bunu TB'deki fonksiyondan kolayca oluşturabildiğimizi öğrendim. .

Gelip tekme attılar

Kodu Temmuz ayında yeniden yazmaya başlamama rağmen, fark edilmeden sihirli bir vuruş geldi: GitHub'dan gelen mektuplar genellikle Sorunlara ve Çekme isteklerine verilen yanıtlarla ilgili bildirimler olarak gelir, ancak burada, aniden başlıkta bahsetmek Qemu arka ucu olarak Binaryen bağlamında “O öyle bir şey yaptı, belki bir şey söyler.” Emscripten'in ilgili kütüphanesini kullanmaktan bahsediyorduk İkili WASM JIT'i oluşturmak için. Orada Apache 2.0 lisansınızın olduğunu ve QEMU'nun bir bütün olarak GPLv2 altında dağıtıldığını ve pek uyumlu olmadıklarını söyledim. Aniden bir lisansın olabileceği ortaya çıktı bir şekilde düzelt (Bilmiyorum: belki değiştirebiliriz, belki çift lisanslama, belki başka bir şey...). Bu elbette beni mutlu etti çünkü o zamana kadar zaten yakından bakmıştım. ikili format WebAssembly ve ben bir şekilde üzgün ve anlaşılmazdım. Ayrıca geçiş grafiğine sahip temel blokları tüketen, bayt kodunu üreten ve hatta gerekirse onu yorumlayıcıda çalıştıran bir kütüphane de vardı.

Sonra daha fazlası vardı письмо QEMU posta listesinde, ancak bu daha çok "Buna kimin ihtiyacı var?" sorusuyla ilgili. Ve budur anidengerekli olduğu ortaya çıktı. Az ya da çok hızlı çalışıyorsa, en azından aşağıdaki kullanım olanaklarını bir araya getirebilirsiniz:

  • hiçbir kurulum yapmadan eğitici bir şey başlatmak
  • iOS'ta sanallaştırma, söylentilere göre anında kod oluşturma hakkına sahip tek uygulamanın bir JS motoru olduğu (bu doğru mu?)
  • mini işletim sisteminin gösterimi - tek disket, yerleşik, her türlü ürün yazılımı vb.

Tarayıcı Çalışma Zamanı Özellikleri

Daha önce de söylediğim gibi, QEMU çoklu iş parçacığına bağlı, ancak tarayıcıda bu yok. Yani hayır... İlk başta hiç yoktu, sonra WebWorkers ortaya çıktı - anladığım kadarıyla bu, mesaj aktarımına dayalı çoklu iş parçacığıdır paylaşılan değişkenler olmadan. Doğal olarak bu durum, paylaşılan bellek modeline göre mevcut kodun taşınmasında önemli sorunlar yaratmaktadır. Daha sonra kamuoyunun baskısıyla bu da adı altında hayata geçirildi. SharedArrayBuffers. Yavaş yavaş tanıtıldı, lansmanı farklı tarayıcılarda kutlandı, ardından Yeni Yıl kutlandı ve ardından Meltdown... Ardından zaman ölçümünün kaba mı yoksa kaba mı olduğu sonucuna vardılar, ancak ortak hafıza ve iş parçacığı sayacı arttırıyor, hepsi aynı oldukça doğru bir şekilde işe yarayacak. Bu nedenle, paylaşılan bellekle çoklu iş parçacığını devre dışı bıraktık. Görünüşe göre onu daha sonra tekrar açtılar, ancak ilk deneyde açıkça görüldüğü gibi, onsuz bir yaşam var ve eğer öyleyse, bunu çoklu iş parçacığına güvenmeden yapmaya çalışacağız.

İkinci özellik, yığınla düşük seviyeli manipülasyonların imkansızlığıdır: mevcut bağlamı basitçe alıp kaydedemez ve yeni bir yığınla yenisine geçemezsiniz. Çağrı yığını JS sanal makinesi tarafından yönetilir. Görünüşe göre sorun nedir, çünkü eski akışları hala tamamen manuel olarak yönetmeye karar verdik? Gerçek şu ki, QEMU'daki blok G/Ç, eşyordamlar aracılığıyla uygulanır ve düşük seviyeli yığın manipülasyonlarının kullanışlı olacağı yer burasıdır. Neyse ki, Emscipten zaten eşzamansız işlemler için bir mekanizma içeriyor, hatta iki tane: Eşzamansızlaştırma и Tercüman. İlki, oluşturulan JavaScript kodunda önemli miktarda şişkinlik oluşmasına neden oluyor ve artık desteklenmiyor. İkincisi mevcut "doğru yoldur" ve yerel yorumlayıcı için bayt kodu oluşturma yoluyla çalışır. Elbette yavaş çalışır, ancak kodu şişirmez. Doğru, bu mekanizma için eşyordam desteğinin bağımsız olarak sağlanması gerekiyordu (Asyncify için yazılmış eşyordamlar zaten vardı ve Emterpreter için yaklaşık olarak aynı API'nin bir uygulaması vardı, yalnızca bunları bağlamanız gerekiyordu).

Şu anda kodu henüz WASM'de derlenen ve Emterpreter kullanılarak yorumlanan bir koda bölmeyi başaramadım, bu nedenle blok cihazları henüz çalışmıyor (dedikleri gibi bir sonraki seriye bakın...). Yani, sonunda şu komik, katmanlı şeye benzer bir şey elde etmelisiniz:

  • yorumlanmış blok G/Ç. Peki, gerçekten yerel performansa sahip taklit NVMe'yi mi bekliyordunuz? 🙂
  • statik olarak derlenmiş ana QEMU kodu (çevirmen, diğer öykünülmüş cihazlar vb.)
  • Konuk kodunu dinamik olarak WASM'ye derledi

QEMU kaynaklarının özellikleri

Muhtemelen zaten tahmin ettiğiniz gibi, konuk mimarileri taklit etmeye yönelik kod ve ana makine talimatlarını oluşturmaya yönelik kod, QEMU'da ayrılmıştır. Aslında durum biraz daha çetrefilli:

  • misafir mimariler var
  • var hızlandırıcılaryani Linux'ta donanım sanallaştırması için KVM (birbirleriyle uyumlu konuk ve ana bilgisayar sistemleri için), her yerde JIT kodu üretimi için TCG. QEMU 2.9'dan itibaren Windows'ta HAXM donanım sanallaştırma standardı desteği ortaya çıktı (Ayrıntılar)
  • donanım sanallaştırması yerine TCG kullanılıyorsa, evrensel yorumlayıcının yanı sıra her ana bilgisayar mimarisi için ayrı kod oluşturma desteğine sahiptir.
  • ... ve tüm bunların etrafında - öykünülmüş çevre birimleri, kullanıcı arayüzü, geçiş, kayıt tekrarı vb.

Bu arada şunu biliyor muydunuz: QEMU yalnızca bilgisayarın tamamını değil, aynı zamanda ana bilgisayar çekirdeğindeki ayrı bir kullanıcı işlemi için işlemciyi de taklit edebilir; bu, örneğin ikili enstrümantasyon için AFL fuzzer tarafından kullanılır. Belki birisi QEMU'nun bu çalışma modunu JS'ye taşımak ister? 😉

Uzun süredir devam eden ücretsiz yazılımların çoğu gibi, QEMU da çağrı yoluyla oluşturulmuştur. configure и make. Diyelim ki bir şey eklemeye karar verdiniz: bir TCG arka ucu, iş parçacığı uygulaması veya başka bir şey. Autoconf ile iletişim kurma ihtimali karşısında mutlu olmak/dehşete düşmek için acele etmeyin (uygun şekilde altını çizin) - aslında, configure QEMU'lar görünüşe göre kendi kendine yazılmıştır ve hiçbir şeyden üretilmemiştir.

WebAssembly

Peki WebAssembly (diğer adıyla WASM) adı verilen şey nedir? Bu, artık geçerli bir JavaScript kodu gibi görünmeyen Asm.js'nin yerine geçmiştir. Aksine, tamamen ikili ve optimize edilmiştir ve içine bir tamsayı yazmak bile çok basit değildir: kompaktlık için şu formatta saklanır: LEB128.

Asm.js için yeniden döngü algoritmasını duymuş olabilirsiniz - bu, JS motorlarının tasarlandığı "yüksek seviyeli" akış kontrol talimatlarının (yani, if-then-else, döngüler vb.) geri yüklenmesidir. düşük seviyeli LLVM IR, işlemci tarafından yürütülen makine koduna daha yakındır. Doğal olarak QEMU'nun ara temsili ikinciye daha yakındır. Öyle görünüyor ki, bayt kodu, işkencenin sonu... Ve sonra bloklar, if-then-else ve döngüler var!..

Binaryen'in faydalı olmasının bir başka nedeni de budur: WASM'de depolanacak olana yakın yüksek seviyeli blokları doğal olarak kabul edebilir. Ancak aynı zamanda temel bloklardan ve bunlar arasındaki geçişlerden oluşan bir grafikten de kod üretebilir. WebAssembly depolama formatını kullanışlı C/C++ API'sinin arkasına gizlediğini zaten söylemiştim.

TCG (Küçük Kod Oluşturucu)

TCG aslen C derleyicisi için arka uç Daha sonra görünüşe göre GCC ile rekabete dayanamadı ama sonunda ana platform için kod oluşturma mekanizması olarak QEMU'da yerini buldu. Ayrıca, yorumlayıcı tarafından hemen yürütülen bazı soyut bayt kodları üreten bir TCG arka ucu da var, ancak bu sefer onu kullanmaktan kaçınmaya karar verdim. Ancak QEMU'da, oluşturulan TB'ye geçişin işlev aracılığıyla etkinleştirilmesinin zaten mümkün olduğu gerçeği tcg_qemu_tb_execbenim için çok faydalı olduğu ortaya çıktı.

QEMU'ya yeni bir TCG arka ucu eklemek için bir alt dizin oluşturmanız gerekir tcg/<имя архитектуры> (bu durumda, tcg/binaryen) ve iki dosya içerir: tcg-target.h и tcg-target.inc.c и kayıt her şey bununla ilgili configure. Oraya başka dosyalar da koyabilirsiniz, ancak bu ikisinin adından da tahmin edebileceğiniz gibi her ikisi de bir yere dahil edilecektir: biri normal başlık dosyası olarak (içinde bulunur) tcg/tcg.h, ve bu zaten dizinlerdeki diğer dosyalarda bulunuyor tcg, accel ve sadece değil), diğeri - yalnızca bir kod pasajı olarak tcg/tcg.c, ancak statik işlevlerine erişimi vardır.

Nasıl çalıştığına dair ayrıntılı araştırmalara çok fazla zaman harcayacağıma karar vererek, bu iki dosyanın "iskeletlerini" başka bir arka uç uygulamasından kopyaladım ve bunu lisans başlığında dürüstçe belirttim.

Dosya tcg-target.h esas olarak formdaki ayarları içerir #define-S:

  • Hedef mimaride kaç kayıt var ve hangi genişlik var (istediğimiz kadar kayıt var, istediğimiz kadar var - soru daha çok "tamamen hedef" mimaride tarayıcı tarafından neyin daha verimli koda dönüştürüleceği ile ilgilidir ...)
  • ana bilgisayar talimatlarının hizalanması: x86'da ve hatta TCI'da talimatlar hiç hizalanmamıştır, ancak kod arabelleğine talimatlar değil, Binaryen kütüphane yapılarına işaretler koyacağım, bu yüzden şunu söyleyeceğim: 4 bayt
  • arka uç hangi isteğe bağlı talimatları üretebilir - Binaryen'de bulduğumuz her şeyi dahil ediyoruz, bırakın hızlandırıcı geri kalanını daha basit olanlara ayırsın
  • Arka uç tarafından talep edilen TLB önbelleğinin yaklaşık boyutu nedir? Gerçek şu ki, QEMU'da her şey ciddidir: konuk MMU'yu dikkate alarak yükleme/depolama gerçekleştiren yardımcı işlevler olmasına rağmen (şimdi o olmasaydı nerede olurduk?), çeviri önbelleklerini bir yapı biçiminde kaydederler, işlenmesinin doğrudan yayın bloklarına yerleştirilmesi uygundur. Soru şudur: Bu yapıdaki hangi denge, küçük ve hızlı bir komut dizisi tarafından en verimli şekilde işlenir?
  • burada bir veya iki ayrılmış kaydın amacını ayarlayabilir, bir işlev aracılığıyla TB'nin çağrılmasını etkinleştirebilir ve isteğe bağlı olarak birkaç küçük kaydı tanımlayabilirsiniz. inline-gibi işlevler flush_icache_range (ama bu bizim durumumuz değil)

Dosya tcg-target.inc.celbette boyut olarak çok daha büyüktür ve birkaç zorunlu işlevi içerir:

  • Hangi talimatların hangi işlenenler üzerinde çalışabileceğine ilişkin kısıtlamalar da dahil olmak üzere başlatma. Başka bir arka uçtan açıkça benim tarafımdan kopyalandı
  • bir dahili bayt kodu talimatı alan işlev
  • Ayrıca buraya yardımcı fonksiyonları da koyabilirsiniz, ayrıca statik fonksiyonları da kullanabilirsiniz. tcg/tcg.c

Kendim için şu stratejiyi seçtim: bir sonraki çeviri bloğunun ilk kelimelerinde dört işaretçi yazdım: bir başlangıç ​​​​işareti (yakınlarda belirli bir değer) 0xFFFFFFFF, TB'nin mevcut durumunu belirleyen), bağlamı, oluşturulan modülü ve hata ayıklama için sihirli sayıyı belirler. İlk başta işaret yerleştirildi 0xFFFFFFFF - nNerede n - küçük bir pozitif sayı ve yorumlayıcı aracılığıyla her çalıştırıldığında 1 arttı. 0xFFFFFFFE, derleme gerçekleşti, modül fonksiyon tablosuna kaydedildi, yürütmenin yapıldığı küçük bir "başlatıcıya" aktarıldı tcg_qemu_tb_execve modül QEMU belleğinden kaldırıldı.

Klasiklerden alıntı yapacak olursak, “Koltuk değneği, bu ses, proger'ın yüreğine ne kadar çok şey katıyor…”. Ancak hafıza bir yerde sızıntı yapıyordu. Üstelik QEMU tarafından yönetilen bir bellekti! Bir sonraki talimatı (yani bir işaretçiyi) yazarken, daha önce bağlantısı bu yerde olan talimatı silen bir kodum vardı, ancak bu işe yaramadı. Aslında en basit durumda, QEMU başlangıçta belleği ayırır ve oluşturulan kodu oraya yazar. Tampon bittiğinde kod atılır ve yerine bir sonraki yazılmaya başlanır.

Kodu inceledikten sonra, sihirli sayı hilesinin, ilk geçişte başlatılmamış arabellekteki yanlış bir şeyi serbest bırakarak yığın imhasında başarısız olmama izin verdiğini fark ettim. Peki daha sonra işlevimi atlamak için arabelleği kim yeniden yazar? Emscripten geliştiricilerinin tavsiye ettiği gibi, bir sorunla karşılaştığımda ortaya çıkan kodu yerel uygulamaya geri aktardım, Mozilla Record-Replay'i ayarladım... Genel olarak, sonunda basit bir şeyin farkına vardım: her blok için, A struct TranslationBlock açıklamasıyla birlikte. Bil bakalım nerede... Aynen öyle, tam tampondaki bloktan hemen önce. Bunun farkına vararak koltuk değneği kullanmayı (en azından bir kısmını) bırakmaya karar verdim ve sihirli sayıyı attım ve kalan kelimeleri struct TranslationBlock, çeviri önbelleği sıfırlandığında hızlı bir şekilde geçilebilen tek bağlantılı bir liste oluşturur ve belleği boşaltır.

Bazı destek noktaları kaldı: örneğin, kod arabelleğindeki işaretli işaretçiler; bazıları yalnızca BinaryenExpressionRefyani oluşturulan temel bloğa doğrusal olarak yerleştirilmesi gereken ifadelere bakarlar, bir kısmı BB'ler arasındaki geçişin koşulu, bir kısmı nereye gidileceğidir. Relooper için şartlara göre bağlanması gereken hazırlanmış bloklar zaten var. Bunları ayırt etmek için hepsinin en az dört bayt ile hizalandığı varsayımı kullanılır, böylece etiket için en az anlamlı iki biti güvenle kullanabilirsiniz, sadece gerekirse onu kaldırmayı hatırlamanız gerekir. Bu arada, bu tür etiketler QEMU'da TCG döngüsünden çıkma nedenini belirtmek için zaten kullanılıyor.

Binaryen'i kullanma

WebAssembly'deki modüller, her biri bir ifade olan bir gövde içeren işlevler içerir. İfadeler tekli ve ikili işlemler, diğer ifadelerin listelerinden oluşan bloklar, kontrol akışı vb.'dir. Daha önce de söylediğim gibi, buradaki kontrol akışı tam olarak üst düzey dallar, döngüler, işlev çağrıları vb. şeklinde düzenlenmiştir. İşlevlere ilişkin argümanlar yığına aktarılmaz, ancak tıpkı JS'de olduğu gibi açıkça aktarılır. Global değişkenler de var ama onları kullanmadım o yüzden size bunları anlatmayacağım.

İşlevlerin ayrıca sıfırdan numaralandırılmış yerel değişkenleri vardır: int32 / int64 / float / double. Bu durumda ilk n yerel değişken, işleve iletilen argümanlardır. Buradaki her şeyin kontrol akışı açısından tamamen düşük seviyeli olmamasına rağmen, tamsayıların hala "imzalı/imzasız" özelliğini taşımadığını lütfen unutmayın: sayının nasıl davranacağı işlem koduna bağlıdır.

Genel anlamda Binaryen şunları sağlar: basit C-API: bir modül yaratırsınız, içinde ifadeler oluşturun - tekli, ikili, diğer ifadelerden bloklar, akışı kontrol edin vb. Daha sonra gövdesi bir ifade olan bir fonksiyon yaratırsınız. Eğer siz de benim gibi düşük seviyeli bir geçiş grafiğine sahipseniz, relooper bileşeni size yardımcı olacaktır. Anladığım kadarıyla, bloğun sınırlarını aşmadığı sürece bir bloktaki yürütme akışının üst düzey kontrolünü kullanmak mümkündür - yani dahili hızlı yol/yavaş yapmak mümkündür Yol, yerleşik TLB önbellek işleme kodunun içinde dallanır, ancak "harici" kontrol akışına müdahale etmez. Bir relooper'ı serbest bıraktığınızda blokları serbest kalır; bir modülü serbest bıraktığınızda ona tahsis edilen ifadeler, işlevler vb. kaybolur. arena.

Bununla birlikte, gereksiz bir yorumlayıcı örneği oluşturup silmeden kodu anında yorumlamak istiyorsanız, bu mantığı bir C++ dosyasına koymak ve buradan kütüphanenin tüm C++ API'sini, hazır komutları atlayarak doğrudan yönetmek mantıklı olabilir. sarmalayıcılar yaptı.

Yani ihtiyacınız olan kodu oluşturmak için

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

... eğer bir şeyi unuttuysam kusura bakmayın, bu sadece ölçeği temsil etmek içindir ve ayrıntılar belgelerde mevcuttur.

Ve şimdi crack-fex-pex başlıyor, şöyle 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 ve JS dünyalarını bir şekilde birbirine bağlamak ve aynı zamanda derlenmiş işlevlere hızlı bir şekilde erişmek için bir dizi oluşturuldu (başlatıcıya içe aktarılacak işlevler tablosu) ve oluşturulan işlevler buraya yerleştirildi. Endeksi hızlı bir şekilde hesaplamak için, sıfır kelime çeviri bloğunun endeksi başlangıçta olduğu gibi kullanıldı, ancak daha sonra bu formül kullanılarak hesaplanan endeks, içindeki alana kolayca sığmaya başladı. struct TranslationBlock.

Bu arada, gösteri (şu anda belirsiz bir lisansa sahip) yalnızca Firefox'ta düzgün çalışır. Chrome geliştiricileri bir şekilde hazır değil birisinin WebAssembly modüllerinin binden fazla örneğini oluşturmak isteyebileceği gerçeğine göre, her biri için bir gigabaytlık sanal adres alanı tahsis ettiler...

Şimdilik bu kadar. Belki ilgilenen varsa başka bir makale olacaktır. Yani en azından kalır sadece Blok cihazlarının çalışmasını sağlayın. Yerel modül hazır olana kadar tüm bunları yapabilecek bir yorumlayıcı hala mevcut olduğundan, JS dünyasında alışılageldiği gibi WebAssembly modüllerinin derlenmesini eşzamansız hale getirmek de mantıklı olabilir.

Sonunda bir bilmece: 32 bit mimaride bir ikili dosya derlediniz, ancak kod, bellek işlemleri yoluyla Binaryen'den, yığının herhangi bir yerinden veya 2 bit adres alanının üst 32 GB'sindeki başka bir yerden tırmanıyor. Sorun Binaryen'in bakış açısına göre bunun çok büyük bir sonuç adresine erişmesidir. Bunu nasıl aşabiliriz?

Yöneticinin yolunda

Bunu test etmedim ama ilk düşüncem şu oldu: "Ya 32-bit Linux kurarsam?" Daha sonra adres alanının üst kısmı çekirdek tarafından doldurulacaktır. Tek soru ne kadar işgal edileceğidir: 1 veya 2 Gb.

Bir programcının tarzında (uygulayıcılar için seçenek)

Adres alanının üst kısmına bir baloncuk üfleyelim. Ben bunun neden işe yaradığını anlamıyorum - orada zaten bir yığın olmalı. Ama "bizler uygulayıcıyız: her şey bizim için çalışıyor, ama kimse nedenini bilmiyor..."

// 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 ile uyumlu olmadığı doğru, ama neyse ki Valgrind'in kendisi çok etkili bir şekilde herkesi oradan uzaklaştırıyor :)

Belki birileri bu kodumun nasıl çalıştığına dair daha iyi bir açıklama yapabilir ...

Kaynak: habr.com

Yorum ekle