İçeriğe geç
academia.sh

Ders 06 / 12

WebAssembly Hedefi

Aynı kaynağın koştuğu hedefe göre farklı bir gövdeye ihtiyaç duyması: standart kütüphanenin bir parçasına (dosya sistemi) dayanan bir gövde ile hiç dayanmayan bir gövde koşullu derlemeyle ayrılır, ikisi de yerel hedefte koşturulur ve aynı sonucu verir.

Yayın Güncelleme

Bu derste

  • Koşullu derlemenin, hedefe göre değişen bir kararı kaynakta nasıl ayırdığını yerel hedefte koşturarak gösterir
  • Dosya sistemine dayanan bir gövde ile dayanmayan bir gövdenin aynı sonucu ürettiğini karşılaştırarak kanıtlar
  • Kaynağın taşınabilir görünmesinin taşınabilir olduğu anlamına gelmediğini, hedef bağımlılığının kaynaktan okunması gerektiğini açıklar
İçindekiler

Önceki ders, sınırın iki dil arasında olduğu durumu ölçtü: bildirim doğruysa, hangi işlemcide ya da işletim sisteminde koştuğunun önemi yoktu. Bu ders aynı bağımsızlığı tersine çeviriyor. Burada dil aynı kalıyor — kaynağın tamamı Rust — ama koştuğu hedef değişiyor ve bazı gövdeler bu değişikliğe kayıtsız kalamıyor. Bir tarayıcıda koşan bir hedef, bir dosya sistemine, bir işletim sistemi iş parçacığına ya da bir saate her zaman erişemez; bu ders o erişimin kaynakta nasıl görünür kılındığını ölçüyor.

Önceki dersteki sınır bir bildirimin doğruluğuna, buradaki sınır ise hedefin sunduğu ortamın kendisine bağlı; ikisi de derleyicinin doğrudan sınayamadığı bir varsayıma dayanıyor. Bu kısıt Rust’a özgü değil, tarayıcının kendi güvenlik modelinden geliyor. Bir tarayıcı sekmesi, çalıştırdığı kodu diskten, ağdan ve işletim sisteminden yalıtılmış bir alanda tutar; sayfanın kendi başına bir dosya açması, bir iş parçacığı oluşturması ya da bir soket kurması, yalıtımın amacına aykırı düşer. Bu alana derlenen bir program, o alanın sunduğu olanaklarla sınırlıdır — ve bu olanaklar, aynı programın bir sunucuda ya da bir masaüstünde koşan sürümünün sahip olduklarından daha dardır. Rust’ın standart kütüphanesi bu farkı gizlemez: dosya sistemi, iş parçacığı ve zaman gibi modüllerin bir kısmı, bu yalıtılmış hedefte ya hiç yoktur ya da farklı davranır.

Bu derste yalnız yerel hedef derlenir. Başka bir hedef için gerçek bir derleme yapılmıyor; hedefe göre değişen karar, koşullu derleme ile yerel hedefte gösteriliyor — kaynaktaki iki gövdeden hangisinin bu makinede seçildiği koşturularak görülüyor, öteki gövde ise hiç derlenmeden kaynakta duruyor. Bu seçim çalışma zamanında yapılmaz: cfg tarafından yönetilen karar, program çalışmaya başlamadan önce, derleme sırasında verilir. Aynı ikili dosyanın içinde iki gövdeden birini seçip çalışma anında diğerine geçmek mümkün değildir — hangi hedef için derlendiyse, o hedefin gövdesi ikili dosyaya girer, öteki hiç girmez.

Hedefe Göre Ayrılan Gövde

#[cfg(...)] bir çalışma zamanı dallanması değildir; bir if ifadesiyle karıştırılmamalı. if çalışma anında bir koşulu sınar ve iki dalın da ikili dosyada yer aldığı, yalnız birinin çalıştırıldığı bir yapıdır. cfg ise derleme anında karar verir: koşulu sağlamayan öğe, kaynak ağacından hiç kesilmiş gibi, derlemenin geri kalanına hiç girmez. Bu farkın sonucu, aşağıdaki koşumda görülüyor.

Aşağıdaki koşum bir sensör kaydını okuyan iki gövde tanımlıyor. Biri dosya sisteminden okuyor; öteki, tarayıcı hedefinde derlenecek bir gövdenin yerini tutuyor ve bu makinede hiç derlenmiyor. Hangi gövdenin derleneceğine #[cfg(...)] özniteliği karar veriyor.

DS11. Sensör kaydı dört satırlık bir metin: 12, 7, 5, 21 — toplamı 45.

// wasm1.rs — hedefe gore ayrilan govde, yerel hedefte secilen dal
use std::fs;

#[cfg(not(target_family = "wasm"))]
fn kayitlari_oku() -> String {
    // Dosya sistemine dayanan govde: yalnizca dosya sistemi olan hedeflerde derlenir.
    fs::read_to_string("sensor.txt").expect("dosya okunamadi")
}

#[cfg(target_family = "wasm")]
fn kayitlari_oku() -> String {
    // Bu govde tarayici hedefinde derlenir; yerel hedefte hic derlenmez,
    // bu yuzden burada tanimlanmamis bir cagri tasimasi hicbir sey bozmaz.
    govde_wasm_hedefinde_baska_bir_kaynaktan_okur()
}

fn toplam(metin: &str) -> i32 {
    metin.lines().filter_map(|s| s.trim().parse::<i32>().ok()).sum()
}

fn main() {
    fs::write("sensor.txt", "12\n7\n5\n21\n").expect("dosya yazilamadi");
    let icerik = kayitlari_oku();
    println!("dosya sistemine dayanan govde toplami: {}", toplam(&icerik));
}
dosya sistemine dayanan govde toplami: 45

Kaynakta iki kayitlari_oku tanımı var, ama derlenen program yalnız birini içeriyor. #[cfg(not(target_family = "wasm"))] işaretli gövde, bu makine wasm ailesinden olmadığı için derlemeye girdi; #[cfg(target_family = "wasm")] işaretli gövde ise hiç derlenmedi — içindeki çağrı hiçbir yerde tanımlı olmadığı hâlde derleyici bunu fark etmedi, çünkü o gövdeyi hiç görmedi. DS12. Bu, cfg ile dışarıda bırakılan bir gövdenin söz dizimi geçerli olduğu sürece hiçbir semantik denetimden geçmediğini gösteriyor; “derlenmeyen” ile “yanlış” aynı şey değil, derlenmeyen gövde hakkında derleyici hiçbir yargı vermiyor.

Bu, önceki dersteki yabancı işlev bildirimine benziyor görünse de ayrı bir güvence taşıyor. extern "C" bildirimi, derleyicinin hiç görmediği ama gerçekten var olan ve bağlanması gereken bir gövdeyi tarif ediyordu; bağlayıcı o adı bulamazsa derleme çöküyordu. #[cfg(target_family = "wasm")] ile işaretli gövde ise bu makinede hiç aranmıyor — içinde geçen govde_wasm_hedefinde_baska_bir_kaynaktan_okur adının bu programın hiçbir yerinde tanımlı olmaması önemli değil, çünkü o ad hiçbir zaman bağlayıcıya ulaşmıyor. Yabancı fonksiyon arayüzünde devredilen güvence bir bildirimin doğruluğuydu; burada devredilen hiçbir şey yok, çünkü o gövde bu derlemenin bir parçası bile değil.

kayitlari_oku içindeki .expect(...) çağrısı da bilinçli bir seçim: dosya, aynı main işlevi içinde bir satır önce yazıldığı için okuma başarısız olmayacağı biliniyor, ama Rust bunu derleme zamanında bilemiyor — read_to_string her zaman bir Result döndürür ve bu değer ele alınmak zorunda. .expect(...) bu değeri açıp, başarısızlık durumunda panikle durmayı seçiyor; bu ders panik tetiklemediği için bu yol yalnızca başarı durumunda izleniyor.

Yukarıdaki koşum target_family = "wasm" yazımını seçiyor; dilin tanıdığı öznitelik adlarından biri de target_arch = "wasm32"dir ve ikisi aynı şeyi sormaz. target_family, bir hedefin ait olduğu geniş aileyi (wasm ailesi, unix ailesi gibi) sorar; target_arch ise işlemci mimarisinin adını (wasm32, x86_64 gibi) sorar. Bu derste aile sorulmuştu, çünkü ölçülen ayrım mimariye değil, hedefin sunduğu ortam sınıfına dayanıyordu — dosya sistemi bulunmayan bir hedef, tek bir mimariyle sınırlı değildir. Hangi öznitelik adının seçildiği kaynaktan okunabilir bir karardır ve gövdenin hangi soruya göre ayrıldığını söyler.

Dosya Sistemine Dayanmayan Karşılık

Bir gövdenin cfg ile ayrılması, o gövdenin varlığını sürdürmesi gerektiği anlamına gelmez. Aynı sensör kaydı, dosya sistemine hiç dokunmadan da elde edilebilir — bu ders için değer kaynağın içine gömülü. Bu iki seçenek arasındaki fark bir uygulama ayrıntısı değildir: ilki verinin çalışma zamanında bir kaynaktan geldiğini varsayar, ikincisi verinin derleme zamanında zaten bilindiğini varsayar. Hangi varsayımın geçerli olduğu, çözülen problemin kendisine bağlıdır — sensör kaydı gerçekten değişen bir veriyse dosya (ya da onun yerini tutan başka bir kaynak) gerekir; sabit bir yapılandırma değeriyse gömülü sabit yeterlidir.

// wasm2.rs — dosya sistemine hic dayanmayan, hedeften bagimsiz govde
const GOMULU_KAYITLAR: &str = "12\n7\n5\n21\n";

fn toplam(metin: &str) -> i32 {
    metin.lines().filter_map(|s| s.trim().parse::<i32>().ok()).sum()
}

fn main() {
    let toplam_deger = toplam(GOMULU_KAYITLAR);
    println!("gomulu govde toplami: {}", toplam_deger);
}
gomulu govde toplami: 45

İki gövde de aynı sonucu (45) verdi, ama farklı yollardan. Dosya sistemine dayanan gövdenin çalışması için ortamın bir dosya sistemi sunması gerekiyordu — bu makinede sunuluyor, ama bir tarayıcı hedefinde bu varsayım genellikle geçerli değildir, çünkü tarayıcı sayfası doğrudan bir dosya sistemine erişemez. Gömülü gövde ise hiçbir dış kaynağa bağlı değil; verisi kaynağın kendisinde, derleme anında sabitleniyor. DS13. Aynı sonucu üreten bu iki yazım, hedef bağımsız bir görevi (dört sayıyı toplamak) hedefe bağlı bir kaynaktan (dosya) ve hedeften bağımsız bir kaynaktan (gömülü sabit) besleyerek gösteriyor.

Verinin kaynağa gömülmesi burada const bildirimiyle yapıldı; değer derleme sırasında ikili dosyanın bir parçası olur, çalışma zamanında hiçbir dış kaynak istemez. Bu yazım yalnızca tarayıcı hedefiyle sınırlı bir çözüm değildir — bir dosya sisteminin hiç bulunmadığı, yalnızca sabit bir bellek düzeninin var olduğu ortamlar için de aynı yaklaşım kullanılır. Sıradaki ders bu ortamı doğrudan ele alacak; oradaki kısıt tarayıcının yalıtılmış alanının değil, standart kütüphanenin kendisinin yokluğudur.

İkinci Bir Ayrım: İş Parçacığı

Dosya sistemi tek örnek değil. Aynı kalıp, birden çok iş parçacığı açma yeteneği için de geçerli — bir tarayıcı sayfasının çalıştırdığı kod, işletim sisteminin sıradan iş parçacığı oluşturma çağrısına her zaman erişemez. Bir iş parçacığı açmak, işletim sisteminden yeni bir yürütme birimi ayırmasını ve bu birimi zamanlamasını ister; bu, dosya açmaktan farklı bir hizmet ama aynı sınıftan bir istek — ikisi de tek başına kaynağın içinde yürütülemez, ortamın bir şey sağlamasını bekler. Aşağıdaki koşum aynı cfg düzenini bir kez daha kuruyor: yerel hedefte derlenen gövde bir iş parçacığı açıp sonucunu topluyor, wasm ailesi için yazılmış gövde ise yine bu makinede hiç derlenmiyor.

DS14. Hesaplanan değer 6 * 7, yani 42; iş parçacığının döndürdüğü değer ana gövdeye join çağrısıyla toplanıyor.

// wasm3.rs — is parcasina dayanan govde, yerel hedefte secilen dal
use std::thread;

#[cfg(not(target_family = "wasm"))]
fn deger_uret() -> i32 {
    // Is parcasi acan govde: coklu is parcasi destegi olmayan hedeflerde derlenmez.
    let is_parcasi = thread::spawn(|| 6 * 7);
    is_parcasi.join().expect("is parcasi cokmus olamaz")
}

#[cfg(target_family = "wasm")]
fn deger_uret() -> i32 {
    wasm_hedefinde_ayri_bir_yontemle_hesaplanir()
}

fn main() {
    println!("is parcasina dayanan govde: {}", deger_uret());
}
is parcasina dayanan govde: 42

Bu koşumda da fark aynı yerde: thread::spawn çağrısı ayrı bir iş parçacığı açılmasını işletim sisteminden ister ve bu istek her hedefte karşılanamaz. #[cfg(not(target_family = "wasm"))] etiketi bu bağımlılığı üstlenen gövdeyi işaretliyor; aynı hesabı doğrudan, hiçbir iş parçacığı açmadan yapan bir gövde de yazılabilirdi (6 * 7 tek satırda hesaplanır) ve o gövde her hedefte aynı şekilde çalışırdı. Buradaki nokta önceki ölçümle aynı: hedefe bağlı olan, hesabın kendisi değil, hesabı nasıl yürüttüğüdür.

Bu iki örnek (dosya, iş parçacığı) arasında bir de fark var: dosya sisteminin yokluğu genellikle bir hata değeriyle kendini gösterir — çağrı bir Result döndürür ve başarısız olursa bu değer üzerinden anlaşılır. İş parçacığı açma yeteneğinin durumu hedefe göre değişir; bazı hedeflerde işlev hiç bulunmaz ve kaynak o işlevi kullanan haliyle derlenmez, bazı hedeflerde ise farklı bir kısıtla (örneğin tek bir iş parçacığıyla sınırlı olarak) çalışabilir. İki durumda da ortak olan, bu farkın kaynakta bir yerde görünür olması gerektiğidir — ya bir cfg etiketiyle, ya da dönen değerin ele alınma biçimiyle.

Sınırlayıcı Ölçüm

Yukarıdaki iki koşum da bu makinede aynı sonucu verdi — ama bu, dosya sistemine dayanan gövdenin her hedefte çalışacağı anlamına gelmez. Kaynağın kendisi, hangi kısmının hedefe bağlı olduğunu zaten söylüyor: #[cfg(not(target_family = "wasm"))] etiketi, altındaki gövdenin wasm ailesi dışındaki hedeflerde derleneceğini, #[cfg(target_family = "wasm")] etiketi ise karşıt gövdenin yalnız wasm hedefinde derleneceğini beyan ediyor. Bu bilgiye ulaşmak için o hedefte bir koşum çalıştırmaya gerek yok; etiketin kendisi, koşuma bakmadan okunabilen bir bildirim.

Bunun tersi de doğru: cfg etiketi taşımayan bir gövde, otomatik olarak her hedefte çalışacağı anlamına gelmez. toplam işlevi bu derste hiçbir cfg taşımıyor ve iki gövde tarafından da paylaşılıyor — çünkü yalnız metin ayrıştırma ve toplama yapıyor, dış bir kaynağa dokunmuyor. Ama bir işlev cfg taşımadığı hâlde içinde iş parçacığı açan, gerçek zamanı okuyan ya da bir soket bağlantısı kuran bir çağrı taşısaydı, bu bağımlılık kaynaktan görünmezdi — derleyici böyle bir çağrıyı reddetmez, yalnızca o hedefte derlerken ya derleme hatası verir ya da çalışma zamanında beklenmeyen bir sonuç üretir. Kaynağın taşınabilir görünmesi, taşınabilir olduğunu göstermez; hangi varsayımın hedefe bağlı olduğu ancak kaynaktaki cfg etiketlerinden ve kullanılan standart kütüphane parçalarından okunabilir, bir koşumdan değil.

Bu derste ölçülen iki örnek (dosya sistemi, iş parçacığı) tükenmez bir liste değil, aynı sınırın iki yüzü. Gerçek zamanın okunması da benzer bir sınıra girer: bir hedefin sistem saatine erişimi olmayabilir ya da bu erişim farklı bir yolla sağlanabilir. Üç örneğin ortak yanı, hepsinin işletim sisteminden ya da donanımdan bir hizmet istemesidir — dosya sistemi bir depolama hizmeti, iş parçacığı bir zamanlama hizmeti, saat bir donanım sayacı hizmeti ister. Yalıtılmış bir hedef bu hizmetlerin bir kısmını hiç sunmaz ya da farklı bir arayüzden sunar; kaynak, hangi işlevin hangi hizmeti istediğini cfg etiketleriyle ya da kullanılan modülün kendisiyle işaretlediği ölçüde okunabilir kalır. Üçü de bu derste koşturulmuyor; yalnızca dosya sistemi ve iş parçacığı örnekleri koşturuldu, saat örneği aynı sınıfa ait olduğu için yalnızca anılıyor.

Bu okunabilirlik, gerçek çok hedefli kaynakların (birden çok işletim sistemi ve mimaride derlenmesi beklenen kütüphanelerin) neden cfg etiketleriyle dolu göründüğünün de nedenidir; bu ders boyunca kurulan iki örnek, o kaynaklardaki düzenlerin küçük ölçekli bir modelinden ibaret. Bir kaynağı bir hedefe taşımadan önce güvenilir bir yöntem, kaynaktaki cfg etiketlerini ve dış hizmet isteyen çağrıları taramaktır — bu derste yapılan tam olarak budur, yalnızca tek bir hedef çiftinde (yerel hedef ile wasm ailesi) ve tek bir gövde üzerinde.

Özet

  • Hedefe göre değişen bir karar, #[cfg(...)] öznitelikleriyle kaynakta iki ayrı gövdeye bölünebilir; yerel hedefte yalnız biri derlenir, öteki hiç derlenmeden kaynakta durur — cfg bir if gibi çalışma anında dallanmaz, derleme anında karar verir
  • Tarayıcı hedefi için gerçek bir derleme yapılmadı; hedefe bağlı karar kaynak üzerinden modellendi
  • Dosya sistemine dayanan gövde ile dayanmayan gövde aynı sensör toplamını (45) verdi; fark sonuçta değil, hangi ortam varsayımına dayandıklarında
  • Bir tarayıcı hedefinin dosya sistemi gibi bazı ortam olanaklarını sunmaması, bu hedefin verdiği güvencenin dilin kendisinden değil hedeften geldiğini gösterir — sınıf dilin dışında
  • Dosya sistemi ile iş parçacığı açma aynı sınırın iki ayrı örneği: ikisi de işletim sisteminden bir hizmet ister ve bu hizmet her hedefte aynı biçimde sunulmaz
  • Kaynağın taşınabilir görünmesi taşınabilir olduğu anlamına gelmez; hedef bağımlılığı cfg etiketlerinden ve kullanılan standart kütüphane parçalarından, koşumdan değil, kaynaktan okunur

Sonraki Adım

Bu derste sınır, aynı dilin iki hedefi arasındaydı; fark, ortamın hangi olanakları sunduğuydu — dosya sistemi de iş parçacığı da işletim sisteminin var olduğu varsayımına dayanıyordu, yalnızca o varsayımın karşılanıp karşılanmadığı hedeften hedefe değişiyordu. Sıradaki ders bu sınırı bir uca taşıyor: standart kütüphanenin hiç bulunmadığı, işletim sisteminin kendisinin olmadığı bir ortam. Orada gömülü sabit gibi hedeften bağımsız bir yazım bile, artık bellek ayırmanın kendisinin var olup olmadığı sorusuyla karşılaşacak — bu derste “hedefe bağlı” olan yalnız birkaç işlevdi, orada “hedefe bağlı” olan, programın bütün gövdesinin nasıl yazılacağını belirleyen bir kısıttır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat