23 puan yazan GN⁺ 2025-06-08 | 1 yorum | WhatsApp'ta paylaş
  • 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

 
GN⁺ 2025-06-08
Hacker News görüşleri
  • .js uzantısını .mjs olarak 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.
    • CommonJS’den ESM’ye geçiş, adeta Python 2’den Python 3’e geçiş kadar büyük bir dönüşümdü; ama beklentiye kıyasla getirisi az, zahmeti ise daha fazla gibi hissettirdi. Artık birçok kütüphane yalnızca ESM destekliyor; bu yüzden bugünlerde npm’de versions sekmesinden 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 await gibi) gerçekten anlaşılmaz geliyor.
    • JS’te modül tarihinin resmen travmaya yakın olduğunu düşünüyorum; şimdi tarayıcıya import maps de geldiğine göre, ileride ne tür yeni ve “eğlenceli” sorunlar çıkacağını merak ediyorum.
    • Kısa süre önce Function nesnesinin çalışma anında herhangi bir JS kodunu derleyebildiğini öğrendim; bu yüzden import bile 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.
    • O yüzden herkesin bun.sh kullanması gerektiği söyleniyor.
    • .esm.js de kullanılamaz mı?
  • Bu yazıda uzun vadede sorun çıkarabilecek kısımlardan bahsetmek gerekirse, var anahtar sözcüğü yerine let ya da const kullanılması öneriliyor. var hâlâ çalışıyor ama günümüz JS geliştiricilerinin çoğu linter ile var kullanımını yasaklıyor. var yalnı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 ise copy, paste olayları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.
  • Web/NodeJS’de çok iş parçacıklılıktan gerçekten nefret ettiğine dair bir yakınma var. mutex veya rwlock gibi senkronizasyon ilkel araçlarıyla değerin kendisini bağlamlar arasında (ör. v8 isolates) aktarılabilir hale getirmek yerine, pratikte neredeyse işe yaramayan SharedArrayBuffer’ı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ı utf16 olduğu için yerel katmanda JS değerleriyle uğraşmak pahalı.
    • 100GB RAM kullanan bu uygulamanın gerçekten bir web uygulaması olması gerekip gerekmediğini merak ediyorum; sanki C# gibi bir dille yazılmış bir iç araç olması daha mantıklı olurmuş gibi geliyor.
  • Bu ekosistem o kadar kaotik ki, “mazoşist” denmesi bile neredeyse daha normal geliyor.
    • Zaten kaosun içkin olduğunu da söyleyebiliriz.
  • Yazının kendisi iyi yazılmış; üstelik böylesine zor ve karmaşık bir yolla başlamayı seçmesi şaşırtıcı. Proje kurulumu en zor kısım. Hemen güvenlik/header sorunlarına çarpmasını takdir ediyorum, ama bazen beklenen sorun CORS oluyor. Bizim şirket de emscripten/C++ ile derleme yapıyor; buna WebGPU/gölgelendiriciler ve WebAudio da eklendi, yani daha da zorlu bir yolculuk bekleniyor.
  • Eskiden tarayıcıda kod derlemenin “yavaş olacağını” belirsiz şekilde düşünürdüm, ama OP bunun böyle olmadığını iyi açıklıyor. Emscripten projesi de “LLVM, Emscripten, Binaryen ve WebAssembly birleşimi sayesinde çıktının küçük olduğunu ve neredeyse yerel hızda çalıştığını” vurguluyor (emscripten.org).
    • Bugün benim için tam bir “sarı otobüs sendromu” günü. Geçen haftaya kadar Emscripten’ı bilmiyordum; ama projeye SDL eklerken CMake içinde APPLE, MSVC, EMSCRIPTEN hedefleriyle 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.
    • “Neredeyse yerel hız” ifadesi oldukça öznel görünüyor; dokümanlarda bunun gerçekten ne kadar hızlı olduğuna dair sayısal veri bulamadım.
  • Yazı faydalıydı; ben de C ile yazılmış bir derleyiciyi WebAssembly’ye derleyip bir web playground’a dönüştürmek istiyorum. Bu arada modern tarayıcılar JavaScript üzerinden SQLite kullanabiliyor; bunun wasm’de de mümkün olup olmadığını merak ediyorum. Eğer 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.
  • SSL için neden 48 numaralı portun kullanıldığı merak ediliyor; özel bir sebebi var mı diye soruluyor.
    • Yanıt olarak, portun H48 adından esinlenilerek rastgele seçildiği söyleniyor. Bu web uygulaması ek HTTP başlıkları gerektirdiği için, sitenin tamamını etkilemeden uygulamak adına basitçe farklı bir port kullanılmış. Ayrıca https://h48.tronto.net adresine de yönlendiriliyor; ileride OpenBSD’nin httpd ve relayd ayarlarını daha iyi hale getirmek ya da tamamen ayrı bir alana taşımak da düşünülüyor.