1 puan yazan GN⁺ 5 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Zig master branch'ine LLVM backend'inde ABI dışı tamsayı işleme iyileştirmeleri ve yeni @bitCast semantiği eklendi; böylece optimizasyon sorunları ve dil davranışındaki tutarsızlıklar birlikte ele alındı
  • u4, i13, u40 gibi keyfi bit genişliğine sahip tamsayılar, SSA değerlerinde bit-int olarak ele alınırken belleğe yazılırken ABI boyutlu tamsayılara genişletilecek şekilde değiştirildi
  • Eski @bitCast, bellek baytlarını yeniden yorumlamaya daha yakındı; yeni tanım ise türün mantıksal bit dizisini temel alarak endian bağımlılığını azaltıyor
  • Değişiklik LLVM ve C backend'leri ile comptime yürütmesine kadar genişletildi; standart kütüphane, derleyici ve compiler_rt içindeki ilgili kullanımlar da birlikte gözden geçirildi
  • LLVM'nin kaçırdığı optimizasyonlar geri kazanılırken Zig derleyicisinin kendisinde yaklaşık %5 performans artışı gözlemlendi; 0.17.0'da bazı çalışma zamanı performans iyileştirmeleri bekleniyor

LLVM backend'inde keyfi bit genişliğine sahip tamsayı işleme değişikliği

  • Zig, daha önce u4, i13, u40 gibi keyfi bit genişliğine sahip tamsayı türlerini LLVM IR'nin bit-int türleri olan i4, i13, i40'a doğrudan lowering ediyordu
  • Bu yaklaşım, LLVM'nin bellek gösterimi semantiği nedeniyle optimize edicide gereksiz kısıtlar oluşturuyordu ve Clang bu tür LLVM IR üretmediği için LLVM içindeki bu yollar da yeterince test edilmiyordu
  • Son birkaç yılda gerçekten kaçırılan optimizasyonlar ve miscompilation örnekleri gözlemlendi
  • Yeni yaklaşım, SSA değerleri üzerinde işlem yaparken bit-int türlerini koruyor; ancak belleğe yazarken i8, i16, i32 gibi ABI boyutlu türlere zero-extend veya sign-extend uyguluyor
  • Bu lowering, Clang'in C'deki _BitInt(N) için kullandığı lowering ile uyumlu; dolayısıyla LLVM içinde daha iyi desteklenen bir yol olması bekleniyor

Eski @bitCast'in sınırları

  • Eski @bitCast, kavramsal olarak şu davranışa daha yakındı
    • İşlenen değerin pointer'ını almak
    • Bu pointer'ı hedef türün pointer'ına cast etmek
    • O pointer'dan değeri load etmek
  • Yani eski tanım, türün mantıksal yapısından çok bellekteki baytların yeniden yorumlanmasına yakındı
  • Zamanla gerçek davranış bu tanımdan uzaklaştı ve çoğu hedefte @sizeOf(u24) değeri @sizeOf([3]u8) değerinden büyük olmasına rağmen [3]u8'i u24'e @bitCast etmek yine de mümkün oldu
  • LLVM backend'i yeterince spesifik tanımlanmamış @bitCast semantiğini uyguluyordu; tamsayı türlerinin belleğe yazılma biçimi değişince derleyici test paketinde Illegal Behavior ve çökme durumları ortaya çıktı
  • LLVM backend'ine eski davranışı taklit eden ek mantık eklemek yerine, yeni @bitCast tanımını genel olarak uygulama yönü seçildi

Yeni @bitCast semantiği

  • Yeni semantik, 2024'te sunulup kabul edilen dil önerisi #19755 temel alınarak oluşturuldu
  • Bu semantik zaten self-hosted x86_64 backend'inde uygulanmıştı; bu değişiklikle birlikte LLVM ve C backend'lerine ve comptime yürütmesine de genişletildi
  • Yeni @bitCast, bellek baytlarına göre değil, türü mantıksal olarak temsil eden bit sırasına göre çalışıyor
    • u5, least-significant bit'ten most-significant bit'e doğru 5 mantıksal bitten oluşur
    • [2]u5, ilk elemanın 5 bitinin ardından ikinci elemanın 5 bitinin geldiği 10 mantıksal bitten oluşur
  • u8'i aynı boyuttaki i8'e dönüştürmek gibi basit tamsayılar arası dönüşümlerde bitler aynen korunur ve en üst bit işaret biti olarak yorumlanır
  • Tamsayı türleri ile packed struct veya packed union arasındaki @bitCast semantiği de korunuyor

Dizi ve vektörlerde değişen davranış

  • Yeni semantiğin eskisinden ayrıldığı nokta, diziler ve vektörler gibi aggregate türlerin işin içine girdiği durumlar
  • Örneğin [2]u8'i u16'ya @bitCast ettiğinizde eski semantikte sonuç hedefin endian düzenine göre değişiyordu
    • big-endian hedeflerde ilk dizi elemanı üst 8 bit oluyordu
    • little-endian hedeflerde ilk dizi elemanı alt 8 bit oluyordu
  • Yeni semantik yalnızca mantıksal bit gösterimini dikkate aldığı için endian'dan bağımsız; tüm hedeflerde ilk dizi elemanı alt 8 bit oluyor
  • Genel olarak, little-endian hedeflerdeki eski davranışa daha yakın
  • [2]u3@Vector(3, u2)'ye dönüştürmek gibi alışılmadık dönüşümler de mümkün
    • Dizinin mantıksal bitleri birleştirildikten sonra 2 bitlik birimler halinde okunup vektör elemanları oluşturuluyor
    • Bir tamsayıyı @Vector(n, u1)'e @bitCast ederek tek tek bitlerden oluşan bir vektöre ayırmak için de kullanılabiliyor

Birlikte uygulanan öneriler ve geçiş süreci

  • Bu çalışma sırasında @bitCast ile ilgili kabul edilmiş küçük öneriler de hayata geçirildi
    • Pointer vektörleriyle @bitCast yasağı: #18936
    • enum için @bitCast izni: #35602'nin bir kısmı
  • Yeni semantik, eski semantikten anlamlı biçimde farklı olduğu için standart kütüphane, derleyici ve compiler_rt gibi destek kütüphanelerindeki @bitCast kullanımları gözden geçirildi
  • İlgili PR codeberg.org/ziglang/zig/pulls/35711; master'a merge edilmesiyle birlikte çeşitli issue'lar da kapatıldı
  • Değişen semantik ve önerilen geçiş adımları, Zig 0.17.0 release notes içinde özetlenecek

0.17.0'da beklenen performans etkisi

  • Başlangıçtaki hedef olan LLVM backend'inde ABI dışı tamsayı lowering değişikliği, kaçırılan optimizasyonları geri kazanmada başarılı oldu
  • İlgili sonuçlar demonstrably successful bağlantısında görülebilir
  • Zig derleyicisinin kendisi dahili olarak keyfi bit genişliğine sahip tamsayıları çok sık kullanmasa da, daha iyi optimizasyonlar sayesinde yaklaşık %5 performans artışı gösterdi
  • 0.17.0'da bazı kodlarda küçük çalışma zamanı performans iyileştirmeleri görülebilir

1 yorum

 
GN⁺ 5 시간 전
Lobste.rs yorumları
  • Yazıda sözü edilen mantıksal bit gösteriminin endian’dan bağımsız olduğu söyleniyor, ancak gerçek açıklama büyük endian bit sırasını ya da bayt sırasını desteklemeyen bariz bir küçük endian yaklaşımı gibi görünüyor

    • Burada endian’dan bağımsız denmekle, davranışın küçük endian/büyük endian mimariler arasında değişmediğinin kastedildiği anlaşılıyor
  • 25 Haziran 2026 tarihli yeni geliştirme günlüğü; yeni @bitCast semantiğinin ve LLVM arka uç iyileştirmelerinin yakın tarihli bir pull request’e birleştirildiğini anlatıyor

  • İlginç, ama nadiren test edilen büyük endian hedeflerde aşağıdaki gibi yazılmış kodların birden bozulabileceğini düşündürüyor
    Zig dışı sözde kodla yazarsak:

    if target_is_little_endian {  
        my_int = @bitCast(my_array);  
    } else {  
        my_int = @bitCast([my_array[1], my_array[0]]);  
    }  
    
    • Ben de bunu düşündüm, ama sonuçta kaçınılmaz bir değişikliği ertelemek sorunu sadece büyütür diye bakıyorum
      Aslında büyük bir sorun olmayacak gibi; Zig deposundaki binlerce @bitCast içinden bu değişiklikten etkilenenlerin 100’den çok daha az olduğunu sanıyorum
      Açıkçası çoğu Zig kullanıcısının dizi/vektör ile skaler arasındaki dönüşümlerde @bitCast’in nasıl davrandığını tam olarak bildiğini de düşünmüyorum. Önceden yalnızca yazarın sisteminde test edilip sadece küçük endian’da çalışan kodların, artık her yerde çalışır hale gelmesi de çok olası
  • Eski bir C programcısı olarak, C’deki bit field’ların mimariler arasında taşınabilir davranmadığı için pek popüler olmadığını hatırlıyorum
    Yeni Zig @bitCast semantiği, farklı mimarilerde de aynı sonucu veren taşınabilir soyut semantik sunduğu için tam da ihtiyaç duyulan yön bu bence
    Son dönemde kendi dilimde bit field ve bit cast tasarladığım için, kodumun nasıl davranması gerektiğini netleştirmek adına Zig tasarım ve uygulama belgelerine daha ayrıntılı bakmayı düşünüyorum

    • Zig’in C bit field’larına başlıca alternatifi muhtemelen packed struct ve packed union; ikisi de yeni @bitCast tanımıyla iyi uyum sağlayacak şekilde tanımlanmış
      packed struct, alanların bitlerini “temel tamsayı”ya yerleştiren bir yaklaşım. Örneğin alanlar bool, u6, i9 ve temel tamsayı u16 ise, u16’nın en düşük anlamlı biti bool, sonraki 6 biti u6, kalan 9 biti i9 olur. Yani Zig’in packed struct’ı, bir dizi shift ve maske işleminin üzerine konmuş sözdizimsel şekerlemeye oldukça yakın
      packed union da bir temel tamsayıya sahiptir, ancak tüm alanların temel tamsayıyla tam olarak aynı sayıda bit kullanması gerekir. Bu yüzden bir alana yazıp başka bir alandan okuma davranışı, yeni semantikteki @bitCast ile neredeyse aynıdır. Ancak packed union/packed struct alanları dizi ya da vektör tipi alamaz
      Kişisel olarak bu araçların “bit ile ilgili yapıları” ifade etmeye gayet uygun olduğunu düşünüyorum. Birden fazla değeri packed struct ile bit düzeyinde paketleyip C bit field’ları gibi kullanabilirsiniz; bit işlemleri üstünde sözdizimsel şekerleme olduğu için, C’de tip güvenli olmayan makro yığınlarıyla çözülen bit flag’ler de temiz biçimde ifade edilebilir
      Örneğin RWX erişim flag’leri C’de ACCESS_READ, ACCESS_WRITE, ACCESS_EXEC makroları ve uint8_t API’siyle alınabilirken, Zig’de Access = packed struct(u8) ile read, write, exec, reserved alanları tanımlanıp API’de Access alınabilir
      packed struct ve packed union kullanarak epey garip bit yerleşimlerini de ifade edebilirsiniz. Mach-O nesne formatının sembol tablosu girdisinde, tarihsel nedenlerden kaynaklanıyor gibi görünen tuhaf bir n_type alanı var; bunu packed union(u8) içinde bits: packed struct(u8) ve stab: enum(u8) biçiminde modellemek mümkün
      Bu n_type değerini işlerken elle shift ya da maskeleme gerekmez. n_type.bits.is_stab != 0 kontrol edilir; doğruysa n_type.stab üzerinden switch yapılır, değilse n_type.bits içindeki diğer alanlara bakılır. Tersine, .{ .stab = .gsym } ya da .{ .bits = .{ .ext = false, .type = .undf, .pext = false, .is_stab = 0 } } gibi değerler de oluşturulabilir
      Asıl yazının konusundan farklı bir dil özelliğine biraz uzadı, ama yeni dil tasarımında referans alacak bir şey arıyorsanız Zig’in packed struct ve packed union özelliklerini doğrudan denemenizi öneririm. Basit ama epey iyi araçlar olduklarını düşünüyorum