- C/C++ kodunu Emscripten ile WebAssembly'ye portlayarak tarayıcıda çalışan bir web uygulaması oluşturma süreci, gerçek bir Rubik’s Cube çözücü örneği üzerinden ayrıntılı biçimde anlatılıyor
- Hello World'den çoklu iş parçacığına, callback'lere, kalıcı depolamaya, modülerleştirmeye kadar tarayıcı/WebAssembly ortamında karşılaşılan somut zorluklar ve çözüm yöntemleri adım adım ele alınıyor
- JavaScript asenkron başlatma, fonksiyon dışa aktarma, Web Worker ve Spectre sorunları, IDBFS ile IndexedDB kalıcı kayıt gibi gerçek dünya sorun giderme konularına odaklanılıyor
- Emscripten'ın soyutlamalarının gerçekte sık sık 'sızdıran soyutlamalar' (leaky abstractions) olduğunun altı tekrar tekrar çiziliyor; web platformunun sınırlarını ve iç yapısını bilmenin gerekliliği vurgulanıyor
- Frontend tarafında asgari düzeyde JavaScript/HTML bilgisiyle karmaşık bir C kod tabanını web'e taşıma deneyimi üzerinden, mevcut C/C++ kütüphanelerini web'e aktarmak isteyen geliştiricilere somut yardım ve püf noktaları sunan deneyim temelli bir rehber niteliği taşıyor
Giriş
- Kısa süre önce Rubik’s Cube optimal çözüm algoritmasını bir web uygulaması olarak hayata geçiren bir proje üzerinde çalışıldı
- C ile geliştirilen Rubik’s Cube optimal çözücüsünün Emscripten ile derlenip WebAssembly olarak web tarayıcısında çalıştırılma süreci kayda geçiriliyor
- WebAssembly kullanmanın ana nedeni, JavaScript'e kıyasla neredeyse native'e yakın performansı web ortamında elde edebilmek
- Bu yazı klasik bir web geliştirme öğreticisi değil; mevcut C/C++ kodunu web'e taşımak isteyen geliştiriciler için bir 'acı yolculuğu'
- Çok fazla web geliştirme deneyimi olmasa da HTML, JavaScript'in temel yapısını ve tarayıcı geliştirici araçlarını kullanmayı bilenler takip edebilir
Ortam Kurulumu
- Tüm örnek kodlar git deposunda ve github'da bulunabilir
- Emscripten kurulumu gerekli (kurulum için resmi siteye bakılabilir), web sunucusu olarak darkhttpd veya Python
http.server kullanılabilir
- Öğreticideki kod örnekleri Linux ve UNIX türevlerinde test edildi. Windows kullanıcıları için WSL (Windows Subsystem for Linux) öneriliyor
Hello World
- C kodundaki Hello World,
emcc -o index.html hello.c komutuyla derlendiğinde index.html (web sayfası), index.wasm (WebAssembly bytecode'u), index.js (JavaScript glue code) olmak üzere üç dosya üretilir
- Tarayıcıda veya Node.js'te çalışabilir; her ortamın farklı kullanım şekilleri vardır
- Yalnızca
.wasm üretmek için -sSTANDALONE_WASM seçeneği kullanılabilir
- Emscripten'da sadece
.wasm üretmek mümkün olsa da çoğu durumda JavaScript glue code zorunludur
Ara Bölüm I: WebAssembly nedir?
- WebAssembly (WASM), web tarayıcısı içindeki yüksek performanslı bir sanal makinede çalışan düşük seviyeli bir dildir
- WASM, 2017'den bu yana tüm büyük tarayıcılar tarafından destekleniyor
- Emscripten başlangıçta C/C++ kodunu asm.js adlı bir JavaScript alt kümesine dönüştürüyordu, ancak WASM'ın gelişiyle buna geçildi
- Metinsel gösterimi de bulunur ve yığın tabanlı bir yapıya sahiptir. Yakın zamana kadar yalnızca 32 bit mimariyi desteklediği için 4 GB'tan fazla bellek kullanamıyordu, ancak WASM64 tarayıcılara kademeli olarak geliyor
Kütüphane Derleme
- C fonksiyonu multiply()'ı WASM olarak derleyip JavaScript'ten çağıran temel bir örnek işleniyor
- Varsayılan derlemede Emscripten, fonksiyon adlarının başına alt çizgi (_) ekler (ör.
_multiply)
- Fonksiyonları dışa açmak için -sEXPORTED_FUNCTIONS seçeneğinin belirtilmesi gerekir
- Kütüphane yüklenirken başlatma işlemi asenkron olduğu için onRuntimeInitialized veya
await gibi asenkron işlem yönetimi gerekir
- Uygulama kodları depodaki
01_library klasöründe yer alıyor
Ara Bölüm II: JavaScript ve DOM
- JavaScript'ten HTML bileşenlerine erişmek ve onları değiştirmek için Document Object Model (DOM) kullanılmalıdır
- Event listener (
addEventListener) ve yerleşik operatörler/fonksiyonlar ile dinamik arayüzler oluşturulabilir
- Örnek için giriş alanı, buton ve sonuç gösterimi içeren temel HTML/JavaScript bağlantı yapısı açıklanıyor
- Script'leri ayırma/birleştirme için pratik yöntemler ve sorunlar da (ör.
defer kullanımı, DOM öğelerinin yüklenme sırası) anlatılıyor
Kütüphaneyi Modülerleştirme ve Yükleme
- WASM kütüphanesini birden fazla kez dahil etmek veya hem Node.js hem web tarafında yeniden kullanmak için MODULARIZE ve EXPORT_NAME seçenekleriyle modül biçiminde derlemek mümkün
- Node.js uyumluluğu için
.mjs (ES6 modülü) uzantısı öneriliyor
- Hem web'de hem Node tarafında
import MyLibrary from ... biçiminde modül kullanımı yapılabilir
Çoklu İş Parçacığı
- WebAssembly'de performansı artırmak için pthreads tabanlı çok iş parçacıklı kod portlanabilir
- Bir fonksiyon içinde çok sayıda iş parçacığı oluşturularak hesaplama işleri paralel yürütülür (ör. asal sayı sayımı)
- Derleme sırasında -pthread ve -sPTHREAD_POOL_SIZE= seçenekleri gerekir
- Gerçek tarayıcı ortamında Cross-Origin-Opener-Policy: same-origin ve Cross-Origin-Embedder-Policy: require-corp gibi HTTP başlıklarının eklenmesi gerekir
- Tüm örnekler depodaki
03_threads klasöründe görülebilir
Ara Bölüm III: Web Workers ve Spectre
- Emscripten çoklu iş parçacığı, Web Workers ile gerçekleştirilir (Web Workers ayrı süreçlerdir ve mesaj tabanlı iletişim yapısı kullanır)
- Paylaşımlı bellek (SharedArrayBuffer) kullanımında güvenlik kaynaklı kısıtlamalar vardır
- 2018'deki Spectre açığından sonra cross-origin isolation gereksinimi ve ilgili başlıklar zorunlu hale geldi
Ana İş Parçacığını Bloke Etmeye Dikkat
- Uzun süren işler tarayıcının ana UI iş parçacığını bloke ederse kullanıcı deneyimi ciddi biçimde kötüleşir
- Bunu önlemek için web worker kullanılır: UI/girdi işlemleri ile hesaplama işlemleri net biçimde ayrılır
- postMessage ve onmessage ile ana iş parçacığı ile worker arasında olay tabanlı iletişim kurulur
- Web worker içinde Emscripten-WASM modülü yüklenerek yalnızca asenkron hesaplama işleri yürütülür
Callback Fonksiyonları
- C fonksiyonuna parametre olarak fonksiyon işaretçisi (callback) aktarılırken JavaScript'teki fonksiyon nesneleriyle otomatik bağlanma mümkün değildir
- Emscripten'ın sağladığı addFunction(), UTF8ToString() gibi araçlar kullanılmalı; derleme sırasında -sEXPORTED_RUNTIME_METHODS ve -sALLOW_TABLE_GROWTH seçenekleri eklenmelidir
- Callback'ler güvenli çalışması için mutlaka ana iş parçacığında çağrılmalıdır (web worker içinde erişilemez)
Kalıcı Depolama
- Kullanıcının tarayıcısında verileri kalıcı biçimde saklamak için Emscripten'ın IDBFS (IndexedDB tabanlı dosya sistemi) çözümü kullanılır
- Derleme sırasında --lidbfs.js bayrağı ve --pre-js gibi seçeneklerle başlangıç ayarları gerekir
- C kodunda dosya G/Ç fonksiyonları (
fopen, fread, fwrite) aynen kullanılabilir; ancak verinin gerçekten yansıtılması/eşitlenmesi için JavaScript tarafında açık mount ve senkronizasyon işlemleri zorunludur
- Tarayıcının sandbox/güvenlik politikaları nedeniyle yerel dosya sistemine doğrudan erişim yalnızca Node.js'te mümkündür; tarayıcıda güvenli kalıcı veri saklama için IDBFS gibi backend'ler kullanılmalıdır
Sonuç
- Bu öğreticinin tamamı sayesinde karmaşık native C/C++ kodunu, yalnızca asgari düzeyde JavaScript ve HTML ile güvenli ve performans kaybı olmadan tarayıcı üzerinde çalıştırmanın pratik yolları ayrıntılı biçimde öğrenilebilir
- Gerçek üretim ortamında çoklu iş parçacığı, callback'ler, asenkron işlemler, depolama entegrasyonu gibi tüm temel alanlardaki zorluklar ve çözümler deneyimlenebilir; ilgili ayarlar ve tarayıcı kısıtları gibi güncel eğilimler de görülebilir
- Sunulan Git deposu örnekleri referans alınarak bunlar kendi projelerine uygulanıp genişletilebilir
1 yorum
Hacker News görüşleri
.jsuzantısını.mjsolarak değiştirdiğini özellikle belirtmesini isterdim; aslında hangi uzantıyı kullanırsanız kullanın bir noktada sorunlara çarpmanız gerçeğine dair çok hissedilir bir empati var. Dojo’dan CommonJS, AMD, ESM, webpack, esbuild, rollup ve daha pek çok modül sistemini kullanmış biri olarak buna %100 katılıyorum.versionssekmesinden son bir ayda en çok indirilen sürümü seçiyorum, çünkü onun son CommonJS sürümü olma ihtimali yüksek. ESM’in daha gelişmiş bir modül sistemi olduğu açık, ama TC39’un bunu neredeyse bilinçli biçimde CommonJS ile uyumsuz hale getirmiş olması (top-level awaitgibi) gerçekten anlaşılmaz geliyor.import mapsde geldiğine göre, ileride ne tür yeni ve “eğlenceli” sorunlar çıkacağını merak ediyorum.Functionnesnesinin çalışma anında herhangi bir JS kodunu derleyebildiğini öğrendim; bu yüzdenimportbile kullanamadığım ortamımda bir tür can simidi gibi çok faydalı oldu. JS ekosisteminde çok gerekli olmayabilir ama benim için büyük yardım sağlıyor.bun.shkullanması gerektiği söyleniyor..esm.jsde kullanılamaz mı?varanahtar sözcüğü yerineletya daconstkullanılması öneriliyor.varhâlâ çalışıyor ama günümüz JS geliştiricilerinin çoğu linter ilevarkullanımını yasaklıyor.varyalnızca fonksiyon kapsamını desteklediği için, çoğu başka dil geliştiricisinin er ya da geç kafa karışıklığı yaşadığı bir nokta. Yerel uygulama portlama sorunlarına örnek olarak, derleme zamanında Ctrl-C ve Ctrl-V kopyala/yapıştırını sabit kodlayıp Linux ve Windows’ta çalışan ama Mac’te çalışmayan bir durumdan söz ediliyor. Web’de isecopy,pasteolaylarını algılayarak ele almak gerekiyor; Unity gibi çerçevelerde de sabit kodlanmış tuşlar yüzünden Mac’te kopyala-yapıştırın çalışmadığını gördüm. Çoğu oyunda gerekmez, ama kopyala-yapıştır gerektiren bir özelliği web’e taşıdığınızda mutlaka sorun oluyor.mutexveyarwlockgibi senkronizasyon ilkel araçlarıyla değerin kendisini bağlamlar arasında (ör.v8 isolates) aktarılabilir hale getirmek yerine, pratikte neredeyse işe yaramayanSharedArrayBuffer’ın getirilmiş olması üzücü. İş parçacıkları arası senkronizasyon sonuçta RPC katmanı üzerinden thunking ve veri kopyalama yapmaya dönüyor. Şirketimizdeki üretim uygulaması 70~100GB RAM kullanan devasa bir uygulama (ben yapmadan önce de öyleydi); bu yüzden yerel kod tabanlı olarak bellek sayfalarını ve özel veri yapılarını doğrudan yönetip serileştirme/geri çözmeyi en aza indiren tuhaf bir çözüm buluyoruz. V8’de dizge kodlamasıutf16olduğu için yerel katmanda JS değerleriyle uğraşmak pahalı.emscripten/C++ ile derleme yapıyor; buna WebGPU/gölgelendiriciler ve WebAudio da eklendi, yani daha da zorlu bir yolculuk bekleniyor.APPLE,MSVC,EMSCRIPTENhedefleriyle ilgili bir yoruma rastladım ve tam bugün HN’de yine Emscripten’dan söz edildiğini görünce artık ciddi biçimde zaman ayırıp derinlemesine incelemem gerektiğine karar verdim.emscripten, C kodundaki sqlite API çağrılarını tarayıcıdaki sqlite veritabanına köprüleyebilirse bu çok ideal olurdu; daha fazla araştırmaya değer.httpdverelaydayarlarını daha iyi hale getirmek ya da tamamen ayrı bir alana taşımak da düşünülüyor.