1 puan yazan GN⁺ 2025-07-10 | 1 yorum | WhatsApp'ta paylaş
  • Rust derleyicisinin işaretçi takma ad garantilerini optimizasyonlarda kullanabilmesi için, unsafe kodun kuralları nerede ihlal ettiğinin açıkça tanımlanması gerekir
  • Mevcut Stacked Borrows bu ölçütü sundu, ancak gerçek dünyadaki unsafe Rust kodunda yaygın olan kalıpları ve borrow checker'ın güncel özelliklerini yeterince kapsayamıyor
  • Tree Borrows, Stacked Borrows'un temel yapısını yığından ağaca çevirerek daha fazla geçerli kalıbı ifade etmeyi mümkün kılıyor
  • En yaygın kullanılan 30.000 Rust crate'i üzerinde yapılan değerlendirmede, Stacked Borrows'a kıyasla reddedilen test vakaları %54 daha az
  • Rocq ispatıyla, mevcut optimizasyonların çoğu korunurken read-read yeniden sıralama gibi yeni optimizasyonların da mümkün olduğu doğrulandı

unsafe Rust'ta gerekli takma ad kuralları

  • Rust, sahiplik temelli tür sistemi ile bellek güvenliği ve veri yarışı önleme gibi güçlü garantiler sunar
  • Ancak unsafe kod alanında güvenlik otomatik olarak garanti edilmez; programcının uyması gereken ayrı kurallar gerekir
  • Derleyici, tür sisteminin garantilerini, özellikle de işaretçi takma ad (aliasing) ile ilgili bilgileri kullanarak fonksiyon içi optimizasyonları güçlendirmeye çalışır
  • Hatalı yazılmış unsafe kod bu optimizasyonları bozabileceği için, hangi kodun “badly behaved” sayılacağını açıkça belirleyen bir ölçüt önemlidir
  • Mevcut çalışma olan Stacked Borrows bu ölçütü tanımladı, ancak bazı sınırlamaları var
    • Gerçek dünyadaki unsafe Rust kodunda yaygın olan çeşitli kalıpları reddeder
    • Rust borrow checker'a yakın dönemde eklenen gelişmiş özellikleri yansıtamaz

Tree Borrows'un yaklaşımı ve değerlendirme sonuçları

  • Tree Borrows, Stacked Borrows'un merkezî yapısı olan yığını ağaçla değiştirerek tanımlanır
  • Bu yapısal değişiklik, mevcut modelin sınırlamalarını gevşetir
    • En yaygın kullanılan 30.000 Rust crate'i üzerinde yapılan değerlendirmede, Stacked Borrows'a göre reddedilen test vakaları %54 azaldı
  • Rocq ispatıyla optimizasyonla ilgili özellikler de doğrulandı
    • Stacked Borrows'un izin verdiği optimizasyonların çoğu korunuyor
    • Önemli yeni bir optimizasyon olan read-read reorderings de mümkün oluyor
  • Tree Borrows, PLDI'25 Distinguished Paper Award ödülünü aldı
  • İlgili kaynaklar

1 yorum

 
GN⁺ 2025-07-10
Hacker News yorumları
  • Ralf Jung’un son yazısı ek bağlam sağlıyor: https://www.ralfj.de/blog/2025/07/07/tree-borrows-paper.html
    Bonus olarak, Ralf Jung grubunun Rust lehçesiyle Rust’ın yürütme semantiğini çalıştırılabilir biçimde hassas şekilde belirtmeyi amaçlayan yakın tarihli bir sunumu da var: https://youtube.com/watch?v=yoeuW_dSe0o
  • “Derleyicinin, işaretçi aliasing ile ilgili tip sistemi garantilerinden yararlanarak güçlü fonksiyon içi optimizasyonların önünü açmak istediği” ifadesinin gerçekte ne kadar doğru olduğundan şüpheliyim
    Torvalds uzun zamandır C’nin katı aliasing kurallarının faydadan çok zarar getirdiğini savunuyor ve kulağa ikna edici geliyor. Örnek burada: https://lore.kernel.org/all/CAHk-=wgq1DvgNVoodk7JKc6BuU1m9Un... Bu konuyla ilgileniyorsanız tüm thread de okunmaya değer
    Rust’ın temelde farklı olup olmadığı, sınırlı deneyimime göre öyle görünmüyor. En azından işin içine unsafe girince durum daha da böyle
    • C’nin katı aliasing kurallarının berbat olduğuna katılıyorum, ama Rust için önerilen kurallar çok farklı
      Derleyici için daha yararlı, programcı içinse daha az külfetli olduklarını düşünüyorum. Ayrıca dilin içinde gerçekten bir kaçış yolu da var: raw pointer kullanmak. Kodu denetleyecek araçlar da var
      Sonuçta dil tasarımındaki her şey gibi bu da bir ödünleşim ve Rust’ta bu tür optimizasyonlar için yeni bir sweet spot bulunmuş olabilir. Doğru olup olmadığını zaman gösterecek
    • Rust’ın aliasing kuralları C’den epey farklı
      C’de restrict diye nükleer bomba gibi bir araç var; deneyimime göre clang ve gcc’de yalnızca fonksiyon argümanlarına eklendiğinde bir etkisi oluyordu. Tip tabanlı alias analizi genel olarak kullanması zor; int64_t tipinin sonsuz kopyasını da yapamazsınız, muhtemelen yapmak da istemezsiniz. Başka bir tipe yeniden yorumlamak için memcpyyi zorunlu kılması da can sıkıcı
      Buna karşılık Rust referansları ömür, kapsam ve değiştirilebilirlik açısından ince ayrıntılı sınırlarla çevrili ve “fiziksel” tipin kendisini pek umursamıyor. Bu yüzden aynı belleği &mut i32/&i32 ile &mut i64/&i64 olarak yeniden yorumlayıp aralarında geçiş yapmak da mümkün. Unsafe soyutlama aynı anda çakışan &mut referansları vermediği ya da tek bir &mutı çakışmayan birden fazla &muta böldüğü sürece, sıradan güvenli Rust okuma-yazmalarıyla yarım değerleri veya birden çok değeri okuyup yazabilirsiniz
    • Linus’un derleyiciler hakkında söylediklerini bir miktar süzgeçten geçirmek gerekir. O işletim sistemi çekirdeği yazıyor, derleyici yazan biri değil; ikisi oldukça farklı alanlar
      Alias analizi bugünlerde iyi performans elde etmek için çok önemli. Ancak en büyük kazanımların en basit sezgisel kurallardan geldiğini de unutmamak gerekir. Örneğin aynı SSA değerini pointer olarak kullanan iki load’ın mutlaka birbirine alias olması gibi
      LLVM açısından BasicAA bu rolü üstlenir. “Bir nesnenin allocation noktasını izleyebiliyorsan alias sorgusunu kesin olarak çöz, yoksa bilmiyorum de” yaklaşımına yakın basit sezgisel kurallar kümesidir
      Asıl soru, temel ve bariz kontrollerin ötesine geçen alias analizinin değeridir. Alias sorgularının artık önemsiz biçimde çözülemediği aşamaya gelindiğinde, sonuçla yapılabilecek şeyler de genelde büyük ölçüde azalır ve çoğunlukla kod taşıma risklerini bulmak düzeyine iner. Kazanç çok daha küçüktür
      Yapmak istediğim deneylerden biri, teorik olarak kusursuz bir alias analizinin sağlayacağı toplam hız artışını ölçmek. Tahminim, Linux çekirdeği gibi HPC dışı kodlarda bile bunun yaklaşık %20 olacağı yönünde
      [1] Buna, yüksek kaliteli alias analizi olmadan denenmeyecek veri yerleşimi dönüşümleri gibi kahramanca optimizasyonlar dahil değil. Gerçekte böyle bir alias analizinin olmadığını zaten bildiğimiz için o tür optimizasyonlar da denenmeyecek; beklenen hız artışına katmaya değmez diye düşünüyorum
    • C’nin katı aliasing’i ile Rust’ın aliasing’i, ikisi de aliasing’i ele alsa da birbirinden farklı şeyler. Rust oldukça açık biçimde C yaklaşımını benimsemedi
      C’de aliasing yalnızca tipe dayanır; bu yüzden diğer adı da tip tabanlı alias analizi ya da TBAA’dır
    • Daha kapsamlı bir analiz görmek isterdim, ama basit bir ölçüt, derleyicide alias bilgisini LLVM’e aktaran tüm kısımları çıkarıp performansın ne olduğuna bakmaktır
      noaliasın çalışma süresi açısından yaklaşık %5 performans artışına katkı sağladığını iddia eden bir şey buldum, ama kaynağın çok eski olduğu kesin
      https://github.com/rust-lang/rust/issues/54878#issuecomment-...
  • Bahsi geçen Stacked Borrows hakkında 2020 ve 2018’de de thread’ler vardı
    https://news.ycombinator.com/item?id=22281205
    https://news.ycombinator.com/item?id=17715399
  • PLDI sunumunu da izleyebilirsiniz: https://www.youtube.com/watch?v=CJi_Fcs4bak
  • Makaledeki Örnek 4’te belirli Rust kodunun reddedildiği iddiasını kendim denedim; kararlı derleyici sürümünde öyle görünmüyor
    &muttan *mut i32 oluşturup write(x) yerine *x = 10 yaparsanız örtük iki aşamalı borrow kullanmadığınız için derleyicinin bunu reddetmesi gerekir gibi açıklanmıştı, ama gerçekte kabul ediyor
    • Stacked Borrows, Miri’nin çalışma zamanı modelidir. Miri’de çalıştırırsanız *x = 10; sürümünde hata raporlanır, write(x); sürümünde ise raporlanmaz
      Hata “Undefined Behavior: attempting a write access using [...] but that tag does not exist in the borrow stack for this location” biçimindedir

rustc’nin kendisinin ikisini de reddetmesi için bir neden yok. Çünkü y bir *mut; derleme zamanı tip sistemi açısından x olan &mut ile arasında bir ödünç alma/ömür ilişkisi yok.

  • Makale, mevcut ödünç alma denetçisi uygulamasını değil, önerilen Tree Borrows modelindeki davranışı açıklıyor.
    Mevcut ödünç alma denetçisi daha kısıtlayıcı bir analiz kullanıyor ve ham işaretçiler ile değiştirilebilir referanslar arasındaki bu belirli çatışmayı tespit edemiyor.
  • Harika bir çalışma. Birkaç yıl önce Nevin’in web sitesinde Tree Borrows belirtimini okuduğumu ve oldukça çetrefilli bir sorunu zarif biçimde çözme şekline çok etkilendiğimi hatırlıyorum.
    Kendi deneyimimde [1] [2] örneklerinde de Stacked Borrows’a göre yasa dışı olan ama makul kodlara izin veriyordu.
    [1] https://github.com/Voultapher/sort-research-rs/blob/main/wri... Miri sütunu
    [2] https://github.com/rust-lang/rust/blob/6b3ae3f6e45a33c2d95fa...
  • İlgilenenler için Miri uygulaması burada: https://github.com/rust-lang/miri/tree/master/src/borrow_tra...
  • Rust’ın ya da gelecekteki programlama dillerinin derleme hızı, çalışma hızı, algoritma esnekliği gibi özellikleri farklı olan birden fazla ödünç alma denetçisi uygulamasına izin verecek ve projelerin seçim yapmasını sağlayacak şekilde evrilip evrilmeyeceğini merak ediyorum.
    • Rust zaten ödünç alma denetçisi uygulamasını değiştirmeyi destekliyor.
      Kapsam tabanlı ödünç alma denetçisinden sözcüksel olmayan ömür ödünç alma denetçisine geçildi; bir sonraki deneysel Polonius uygulaması da seçenek olarak var. Ancak yeni uygulama üretime hazır olduğunda eski uygulama bırakılıyor. Çünkü seçmek için bir neden yok.
      Ödünç alma denetimi hızlıdır ve yeni uygulama daha fazla doğru programı katı biçimde kabul eder.
      Ayrıca çalışma zamanı denetimi maliyetini ödeyerek daha fazla esneklik sağlayan Rc ve RefCell tipleri de var.
    • Zaten birden fazla yaklaşım var. Rust’ın kullandığı afin tipler, doğrusal tipler, etkiler, bağımlı tipler, biçimsel kanıtlar gibi yöntemler.
      Hepsinin uygulama, performans ve geliştirici deneyimi açısından farklı maliyetleri ve yetenekleri var.
      Ayrıca Rust dışındaki çoğu şeyin gerçekte hedeflediği, otomatik kaynak yönetiminin üretkenliği. Yöntem ne olursa olsun otomatik kaynak yönetimi kullanıp, yalnızca performans açısından kritik yollarda yukarıdaki tip sistemlerinden birini birleştirmek şeklinde.
    • Aslında istenen şey muhtemelen altta yatan ayrım mantığıdır. Fonksiyon önkoşullarını hassas biçimde belirtmek, fonksiyon ara koşullarını kanıtlamak ve iyileştiricinin bu “yardımcı teoremleri” alıp belirtilen değişmezlerin izin verdiği sınıra kadar özgürce optimizasyon yapması gibi bir yapı.
      Bu bağlamda “Rust”, “insanların genelde istediği değişmezler” ve “bu olağan değişmezleri varsayan ama ne daha fazlasını ne de daha azını yapan optimizasyonlar kümesi”nden ibaret görülebilir.
    • Rust’ın ödünç alma denetçisinin derleme zamanı maliyeti oldukça küçüktür ve kod üretimini hiç etkilemez.
      Derleme zamanının çoğu trait çözümleme, monomorfizasyon, LLVM optimizasyon geçişleri ve bağlamaya gider.
    • Anladığım kadarıyla ödünç alma denetçisinde yalnızca yanlış negatifler var, yanlış pozitifler yok değil mi?
      Belki aptalca bir soru olabilir ama birden fazla uygulamayı paralel iş parçacıklarında çalıştırıp ilk olumlu sonuç verenin kazanmasını sağlayamaz mıyız diye merak ediyorum.
  • Makalede unsafe kodun aynı değişken için birden çok değiştirilebilir referansı işaretçi olarak birlikte var edebileceği söyleniyor; bu tanımsız davranış değil mi?
    İşaretçi kullanarak aynı değişken için birden çok değiştirilebilir referansın aynı anda var olmasını sağlamak tanımsız davranıştır. Makalenin niyetini yanlış anlamadıysam böyle görünüyor.
    • Bu çalışmanın özü, tanımsız davranışın kesin sınırlarını belirlemek.
      Yukarıdaki kod Rust derleyicisi tarafından kabul ediliyor ama kuralları çiğniyor. Sorun hangi kuralları çiğnediği.
      Özünde ödünç alma denetçisinin kabul ettiği şey yasaldır; unsafe ise yasa dışı veya tanımsız davranış olan şeyleri de ifade edebilir. Ayrıca ödünç alma denetçisinin doğrulayabildiğinden daha geniş, ama yine de yasal ve tanımlı davranış olan bir kurallar kümesi vardır.
      Bu araştırmanın amacı o kurallar kümesini hassas biçimde belirtmek. Büyük çerçeve “yazılabilir işaretçiler alias olmamalı” fikrine yakın; ama iç işaretçiler, yineleyici geçersizleştirme, kötü işaretçi üretmenin mi yoksa onu kullanmanın mı sorun olduğu gibi ayrıntılar çok zor.
      Önceki Stacked Borrows makalesi daha basitti ama daha kısıtlayıcıydı; bu yüzden gerçek dünyadaki unsafe kodlar kurallardan sık sık geçemiyordu. Tree Borrows daha geniştir, daha çok koda izin verir ve yine de kanıtlanabilir biçimde güvenlidir.
    • Doğru, ama mesele tam olarak hangi kuralın ihlal edildiği. Bunun tanımsız davranış olduğunu söyleyen kesin tanım nedir?
      Tree Borrows tam da böyle bir tanım öneriyor.
      Burada “kod bunu yapabilir” demek, “bu kodu yazabilir, derleyebilir ve çalıştırabilirsiniz; Tree Borrows gibi bir şey yoksa bu kodda sorun olduğunu iddia etmek için dayanağınız yoktur” anlamına geliyor.
      Böyle bir kodun tanımsız davranış olduğunu söylememiz gerektiğini, yani Tree Borrows gibi bir şeye ihtiyaç olduğunu zaten kabul etmiş oluyorsunuz. Makalenin bu bölümü, neden böyle bir şeye ihtiyaç duyulduğunu savunan kısım.
    • Buradaki “yapabilir” ifadesini yanlış anlamış gibisiniz. unsafe kodda gerçekten bunu yapabilirsiniz. Ve doğru şekilde, bu tanımsız davranıştır.
      https://play.rust-lang.org/?version=stable&mode=debug&editio...
    • Devam eden paragrafın başlangıcına bakınca niyet en net hâliyle görülüyor.
      Rust derleyicisi geliştiricilerinin alias optimizasyonlarını desteklemek istediği açık olduğundan, yukarıdaki gibi karşı örnekleri değerlendirme dışı “bırakmanın” bir yoluna ihtiyaç olduğundan bahsediyor.
    • Bence kilit nokta tam da bu. Birden çok değiştirilebilir referansa izin vermeme gibi kısıtları ihlal etmek çok kolay.

unsafe, Rust’ın ömür analiziyle kodun geçerliliğini kanıtlamanın zor olduğu durumlar içindir; ancak bundan çok daha fazlasını yapmak için kötüye kullanılabilir

  • Yazarlardan biri olan Neven Villani’nin, 2010 Fields Medal ödüllü Cédric Villani’nin oğlu olduğunu şimdi öğrendim. Elma ağacından uzağa düşmez sözü tam yerine oturuyor