3 puan yazan GN⁺ 2023-07-15 | 3 yorum | WhatsApp'ta paylaş
  • Cosmopolitan Libc ile mevcut C yazılımlarının daha fazlasını derleyebilmek için, derleme sistemini kandırmanın ötesine geçilmesi ve yaklaşık 2.000 satırlık bir GCC yamasıyla derleyicinin sorunlu kodu otomatik dönüştürmesi gerekti
  • Engel, SIGTERM, EINVAL gibi sistem değerlerinin derleme zamanı sabiti olmaması ve bu yüzden C’nin switch case etiketleri ile static/const struct başlatma kurallarına takılmasıydı
  • sed, Python regex’leri, GCC eklentileri ve önişlemci makrolarını yakalama gibi yollar denendi; ancak case etiketi hatası ayrıştırma sırasında oluştuğu için bunu yalnızca eklentiyle engellemek zordu
  • Son yama -fportcosmo bayrağıyla etkinleştiriliyor; GCC hata vermeden hemen önce switch ifadelerini if/goto yapısına çeviriyor ve yapı başlatmalarını çalışma zamanında değer doldurarak tamamlıyor
  • Anonim yapılar, enum, bazı const int kullanımları ve başlatma ifadeleri içindeki dizi indeksleri gibi sınırlamalar kalsa da, bash, curl, git, ninja, gcc gibi yazılımları kaynak kodu değiştirmeden derlemenin yolu açıldı

GCC yamasının hedefi

  • Cosmopolitan Libc ile Actually Portable Executables oluşturmayı kolaylaştırmak için yaklaşık 2.000 satırlık bir gcc yaması yazıldı
  • Yama uygulandıktan sonra ./configure veya cmake derleme sistemleriyle şu yazılımlar derlenebiliyor
    • bash
    • curl
    • git
    • ninja
    • gcc’nin kendisi
  • Derlenen çalıştırılabilir dosyaların Linux, FreeBSD, MacOS, OpenBSD, NetBSD ve Windows’ta da çalışabilmesi gerekiyor
    • Windows’ta çalıştırmanın doğrudan test edilmediği notu düşülüyor
  • GitHub Actions ile derlenen ikili dosyalar superconfigure releases üzerinden indirilebiliyor

Cosmopolitan Libc portlamasında takılınan nokta

  • Cosmopolitan Libc 2021’de redbean web sunucusuyla dikkat çektikten sonra, mevcut C yazılımlarının bu libc üzerinde ne kadar iyi çalıştığı önemli bir soru haline geldi
  • Lua, Wren, Janet, quickjs gibi dil runtime’larını portlama sürecinde aynı kalıp tekrarlandı
  • SIGTERM, EINVAL gibi sistem sabitleri switch deyimindeki case etiketlerinde kullanıldığında C’nin derleme kurallarıyla çakışıyor
  • C standardı, case etiketlerinin derleme zamanı sabiti olmasını şart koşuyor
    • Cosmopolitan Libc’de sistem arayüzüyle ilgili C önişlemci makrolarının sembolik olması gerekiyordu
    • Bunun sonucunda switch(errno) { case EINVAL: ... } gibi yaygın kodlar derlenmeyebiliyordu

switch ifadelerini if ve goto ile değiştirmek

  • switch(errno) içindeki case EINVAL, case ENOSYS gibi etiketler derleme zamanı sabiti değilse GCC case label does not reduce to an integer constant hatası veriyor
  • Yama, switch deyimlerini otomatik olarak şu yapıya dönüştürüyor
    • Her case değeri için if (errno == EINVAL) goto caselabel_EINVAL; biçiminde bir dal ekliyor
    • default etiketine giden bir goto ekliyor
    • Özgün case bloklarını etiketler ve goto endofthis_switch ile yeniden düzenliyor
  • Bu yöntem en zarif çözüm olmasa da, birçok kod tabanında görülen örnekleri işleyebildi
  • ifdef ve fallthrough iç içe geçmiş switch deyimlerini de ele almak gerektiğinden, goto tabanlı dönüşüm basit bir seçenekti

struct başlatmaları da aynı kısıta takılıyor

  • static veya const struct başlatmalarında da derleme zamanı sabiti sorunu ortaya çıkıyor
  • EINVAL derleme zamanı sabiti değilse aşağıdaki başlatmaların bir kısmı C’de geçerli olmuyor
    • Fonksiyon içindeki normal yerel struct başlatması mümkün
    • const struct
    • static struct
    • Global const struct
    • Global static struct
  • Gerçek bir örnek olarak CPython’ın faulthandler modülü var
  • Çözüm, yapıyı sahte değerlerle başlatıp kullanılmadan önce doğru değerleri dolduran kod eklemek

Metin değiştirme ve GCC eklentilerinin sınırları

  • Başta dönüşümü otomatikleştirmek için sed shell script’i denenmiş, ardından Python script’i ve regex’lerle fallthrough durumları da işlenmeye çalışılmıştı
  • Ancak C önişlemcisi ve ifdef nedeniyle metin değiştirme kusursuz çalışmakta zorlandı
    • Çünkü derleme sırasında hangi ifdef’in etkin olacağını önceden bilmek zor
  • Sonraki deneme, GCC eklentisi kullanarak AST dönüşümü yapmaktı
    • GCC eklentileri -fplugin=your-plugin.so ile yüklenebiliyor
    • PLUGIN_PRE_GENERICIZE, PLUGIN_FINISH_PARSE_FUNCTION, PLUGIN_FINISH_DECL gibi olaylarda AST’ye erişilebiliyor
    • debug_tree ile AST yazdırılabiliyor, walk_tree_without_duplicates ile AST üzerinde dolaşılabiliyor
  • Sorun, case etiketi doğrulamasının GCC ayrıştırması sırasında yapılmasıydı
    • Eklenti, ayrıştırma bittikten sonra AST’ye erişiyor
    • Hatalı case SIGTERM: etiketi AST’de kalmıyor
    • GCC zaten hata vermiş olduğu için eklenti tarafında düzeltmek zorlaşıyor

Önişlemci makrolarını yakalama deneyi

  • Yalnızca eklentiyle ayrıştırma öncesi hatayı engellemek zor olduğundan, C önişlemcisiyle etkileşime giren bir yöntem denendi
  • GCC eklenti başlıklarındaki cpp_reader yapısı üzerinden makrolar #define, #undef edildiğinde veya kullanıldığında callback bağlanabiliyordu
  • Cosmopolitan Libc’nin sistem değeri makro yapısından yararlanılarak geçici sabitler eklendi
    • Örnek: extern const int SIGTERM;
    • Örnek: #define SIGTERM SYMBOLIC(SIGTERM)
    • Eklenti için static const int __tmpcosmo_SIGTERM = ... gibi geçici değerler tutuldu
  • Eklenti, SYMBOLIC(SIGTERM) kullanımını yakalayıp geçici değerle değiştiriyor; ardından PLUGIN_PRE_GENERICIZE aşamasında AST’yi tekrar doğru VAR_DECL haline çeviriyordu
  • Bu makro hack’i switch ve struct başlatma hatalarını aşmayı başardı
  • Bu yöntemle en azından CPython 3.11 derlenebilir hale geldi

Eklenti yerine GCC’nin kendisini düzeltme nedeni

  • Justine Tunney ile yapılan görüşmeden sonra, sorunun çözülebileceği anlaşılınca GCC kod tabanını doğrudan değiştiren daha basit bir yaklaşım önerildi
  • Eklenti ve makro hack’i gereksiz çok iş çıkarıyor; kenar durumları ve çökmeleri ele almak karmaşıklaşıyordu
  • Örneğin yalnızca case ENOSYS dönüştürülmesi gereken bir durumda bile, makro ikamesi nedeniyle ENOSYS + 1 veya printf("ENOSYS = %d", ENOSYS) gibi normal kullanımları da eklentinin işlemesi gerekiyordu
  • GCC yaması yaklaşımında ayrı bir -fplugin yerine özellik -fportcosmo bayrağıyla açılıyor
  • GCC, case is not constant hatasını vermeden hemen önce flag_portcosmo’nun etkin olup olmadığını kontrol ediyor; etkinse gerekli ikameleri yapıyor
  • Mevcut eklentideki AST dönüşüm kodu, GCC içinde diğer eklenti callback’leri çağrılmadan önce çalışacak şekilde taşındı
  • Bu değişiklikle makroyla ilgili hack kodları kaldırılabildi

Gerçek derlemeler ve yamanın genişletilmesi

  • 5 Haziran 2023 itibarıyla yamalanmış GCC, önceki eklenti test vakalarının tamamını geçti ve Cosmopolitan Libc monorepo’suna yeni bir binary eklendi
  • Daha fazla kod derlendikçe düzeltmeler ve iyileştirmeler sürdü
  • lua derlemesi basitleşti; g++ içinde case constant hatasının oluştuğu yer bulunarak ninja da derlenebilir hale geldi
  • Python 3.11’e ncurses bağlama sürecinde yeni bir sorun bulundu
    • ncurses içinde DATA2(PARODD | PARENB, PARODD) gibi, yapı başlatma değerlerinde ikili ifadeler içeren kodlar vardı
    • Mevcut yama basit sabit başlatmaları işliyordu ama PARODD | PARENB gibi ifadeleri işleyemiyordu
  • Daha sonra yama, C’deki case etiketleri ve yapı başlatma öğeleri rastgele ifadeler olabilecek şekilde genişletildi
    • C standardına göre case etiketleri hâlâ derleme zamanı sabiti olmalı
    • Yamalanmış derleyici bu tür kullanımlarda uyarı veriyor ama derlemeye devam ediyor
    • C++ tarafında g++11’in constexpr işlemesiyle uyuşmadığı için rastgele ifade ikamesinin kapsamı dışında bırakıldı
  • Bu değişiklikten sonra ncurses sorunsuz derlendi

Kalan kısıtlar ve pratik sonuç

  • Yama kusursuz değil
    • Bazı anonim yapıları işleyemiyor
    • enum ve const int için bazı durumları işleyemiyor
    • Başlatma ifadeleri içinde SIGTERM’in dizi indeksi olarak kullanıldığı durumları işleyemiyor
    • Tuhaf static başlatmalar veya SIGTERM’in static const int8_t içine konduğu durumlarda nadir derleyici çökmeleri kalabilir
  • Yine de belirgin karşı örneklerin çoğu ortadan kaldırıldı ve birçok popüler yazılım sorunsuz derleniyor
  • Daha sıkı bir test ortamı, ek iyileştirme noktalarını ortaya çıkarabilir
  • İstenirse switch deyimleri ve struct başlatma ifadeleri elle de düzeltilebilir; ancak çoğu durumda derleyici bunu otomatik ele alabiliyor
  • C yazılımı statik olarak derlenebiliyorsa ve özellikle musl ile de derleniyorsa, Cosmopolitan Libc ile de derlenebilme olasılığı var

3 yorum

 
ryuheechul 2023-07-15

Artık neo, mesajları kod bloğuna sarma formatına mı geçiyor? Yatay kaydırma yapmak gerekmesi ve kırmızı tonlardaki metin renginin biraz hayal kırıklığı yaratması üzücü...

 
xguru 2023-07-16

Ah, bunun kod bloğuna alınması bir bug hüzün

 
GN⁺ 2023-07-15
Hacker News yorumları
  • Bu yazıyı ben yazdım; başlık “Patching GCC to build Actually Portable Executables” olmalı. Çünkü Cosmopolitan Libc’ye ve jart’ın Actually Portable Executable biçimine atıfta bulunuyor.
    GCC yamam sayesinde artık vim, emacs, ninja, bash, git, gcc gibi yazılımlar Cosmopolitan Libc ile, alışılmış autotools/cmake tarzı build sistemleri üzerinden derlenebiliyor. Üretilen yürütülebilir dosyalar Linux, FreeBSD, MacOS, OpenBSD, NetBSD ve Windows’ta çalışabilmeli. Ancak Windows’u henüz test etmedim.
    Bu teknikle derlemeyi denediğim yazılımların listesi burada: https://github.com/ahgamut/superconfigure
    superconfigure betiği, yazılım derlemek için kullanılan normal configure betiğini saran ve --enable-static gibi bayrakları ekleyen bir wrapper’dan ibaret.
    GCC’yi Cosmopolitan Libc ile derlemeyi denemek isterseniz şu depoyu kullanabilirsiniz: https://github.com/ahgamut/musl-cross-make/tree/gccbuild

    • Bu yamanın GCC upstream’e girme ihtimali olup olmadığını merak ediyorum. APE yürütülebilir dosyaları ve cosmopolitan libc çok havalı teknolojiler; kullanımı daha kolay hale gelirse iyi olur.
    • Güzel. Nim gibi transpile edilen dillerin arkasında bunu kullanırken sorun çıkma ihtimali görüyor musunuz, merak ediyorum.
    • busybox derlemeyi de denediniz mi, merak ediyorum.
    • Yakın zamanda lobste.rs’te jart’ın sockpuppet’ı olduğu gerekçesiyle engellendiğinizi gördüm; ancak şimdi gerçekten başka biri olduğunuzu fark ettim.
      HN’de daha iyi muamele görmenizi umarım.
    • Sadece derlenebilmesi o kadar büyük bir başarı değil; daha ilginç olan, gerçekten çalışıp çalışmadığı.
      Örneğin glibc sürümüyle karşılaştırıldığında regresyon olmadığını kanıtlayacak kadar yeterli test yapılıp yapılmadığını merak ediyorum.
  • Go deposunda bununla ilgili bir issue var: https://github.com/golang/go/issues/51900

  • Genel olarak epey havalı, ama bunun teorik arka planını merak ediyorum. Yeni bir işletim sistemi hedefi oluşturulacaksa, mantıken yeni sabitler tanımlamak daha doğru görünüyor.
    Alt işletim sistemlerinin her biri için o işletim sisteminin sabitlerini aynen geçirmek yerine, her işletim sisteminin sabitlerini her yerde aynı olacak şekilde dönüştürmek yeterli olur. Böylece sabitleri dizi indeksi veya case ifadesinde kullanan kod da düzgün çalışır.
    Yazıda anlatılan yaklaşıma göre daha fazla overhead oluşturabilir, ama çok daha kolay ve Cosmopolitan libc’ye yeni program derlemek isteyenler açısından kullanımı da daha basit görünüyor.

    • Yeni sabitler oluşturmak derlemeyi çok daha kolaylaştırır, ancak şahsen çalışma sırasında mevcut sabitler ile yeni sabitler arasında dönüşüm yapmanın overhead’inin fazla büyük olduğunu düşünüyorum.
      Sabitlerin listesine buradan bakabilirsiniz: https://github.com/jart/cosmopolitan/blob/master/libc/sysv/c...
      Bu sabitlerden biri her kullanıldığında, çok sayıda sabiti büyük bir lookup table olarak ikili dosyaya koymak ve programın kontrol etmesi gerektiği her seferinde o tablodan geçmek gerekir. Aşırı yavaş olmayabilir, ama kesinlikle hissedileceğini düşünüyorum.
      Amaç, libc’nin veya port etmek istediğimiz yazılımın kaynak kodunu çok değiştirmeden port etmeyi kolaylaştırmak ve performansı benzer ya da daha iyi olan ikili dosyalar üretmekti. Bu kısıtlar altında, bu GCC yaması süreci basitleştirmenin en iyi yolu gibi göründü.
      SIGHUP’ı dizi indeksi ilklendirmesinde kullanan yeterince çok kod tabanıyla karşılaşırsam, önerdiğiniz yöntemi deneyip ödünleşimi ölçmeyi düşünüyorum. Ya da siz kendiniz deneyip ayrı bir sabit kümesinin daha iyi olup olmadığını bildirebilirsiniz.
  • “Bariz karşı örnekleri ortadan kaldırmak için epey zaman harcadıysanız ve birçok popüler yazılım sorunsuzca derleniyorsa”, başarıyla derlenen yazılımların listesini paylaşmak faydalı olabilir. Belki zaten vardır.

    • vim, emacs, ninja, bash, git, gcc gibi yazılımlar derlenebiliyor. Bu teknikle derlemeyi denediğim liste burada: https://github.com/ahgamut/superconfigure
      superconfigure betiği, yazılım derlemek için kullanılan normal configure betiğini saran ve --enable-static gibi bayrakları ekleyen bir wrapper’dan ibaret.
  • APE’nin pratik bir amacı olup olmadığını merak ediyorum. Birden fazla işletim sisteminde çalışması gereken tek bir çalıştırılabilir dosyayı gerçekten dağıtan bir yer var mı?

    • Logfile Navigator(https://lnav.org) uzak ana makinelerle iletişim kuran bir ajan olarak APE çalıştırılabilir dosyası kullanıyor
      lnav’ın kendisi APE olarak derlenmiyor, ama içine gömülü ajan APE olarak derleniyor. Kullanıcı o ana makinedeki günlükleri okumak istediğinde bu ajan uzaktan gönderiliyor
      Böylece işletim sistemi türünü ayrıca belirleme ya da çalıştırılabilir dosyanın birden fazla sürümünü dahil etme gibi ek adımlara gerek kalmıyor. Konuyla ilgili kısa yazı burada: https://lnav.org/2021/05/03/tailing-remote-files.html
    • Java/Clojure/Kotlin programcıları, .Net programcıları, JS, Python, Ruby, Erlang ve tüm sanal makine tabanlı dillerin programcıları aklıma geliyor
      Statik olarak derlenen programlar her şeyin en altında yer alıyor, ama uygulama düzeyinde azınlıktalar
      APE, “C kadar düşük seviyeli bir AOT dili bir kez derlenip her yerde çalışabiliyorsa sanal makineye kimin ihtiyacı var?” gibi büyük bir soru ortaya atıyor
    • Örneğin Maven deposu gibi aynı id/version kombinasyonuna birden fazla artifact koymanın zahmetli olduğu yerlerde, çalıştırılabilir dosya dağıtımını biraz daha kolaylaştırabilir gibi görünüyor. PyPI’da da benzer bir sorun olabilir
      Üstelik cross-compile yapmak acı verici. Çeşitli BSD’lerin hepsine sahip bir build farm’ı olmayan küçük açık kaynak projeleri, çoğu zaman bu platformlar için ikili dosya hiç dağıtmıyor
      kubectl’in bile Linux için ikili dosyaları var ama BSD için yok. OSX’i BSD saymazsak tabii. Bu yüzden böyle projeler için kolay bir kazanım gibi görünüyor
    • Gelecekteki yan projelerimi bu şekilde yayımlamayı düşünüyorum. Yalnızca istemci taraflı SPA’lar için bile Redbean çalıştırılabilir dosyası derliyorum. Bunların farklı sistemlerde olduğu gibi çalışması fikri şaşırtıcı
    • Platformdan bağımsız, doğrudan çalıştırılabilir bir dosya biçiminin işe yarayacağı bir shader compiler var
      Kullanıcının makinesinde anında derlemek için fazla büyük. Birkaç büyük üçüncü taraf C++ bağımlılığı içeriyor ve donanıma göre derleme 2 dakikadan 20 dakikaya kadar sürüyor
      macOS x86-64+ARM, Linux x86-64, Windows x86-64 üzerinde çalışması gerekiyor. ARM Linux da olsa iyi olur
      Şu anda WASM’i değerlendiriyoruz, ancak kurulu bir WASI runtime’ı gerektiriyor
  • Kafası karışanlar için: “Actually Portable Executables” https://justine.lol/ape.html

    • Bundan bahseden bir yorum ekledim ama henüz değişmedi. Yakında değişeceğini düşünüyorum. Burada @dang’i çağırabilir miyim acaba?
  • switch’i if ile değiştirmek epey kaba bir çözüm. Platformdan bağımsız bir sabit kümesine eşleyip runtime sembolünü bir fonksiyonla dereference ederek düzeltilebilir gibi
    Örneğin switch(CosmoMappErrno(EINVAL)) ve case COSMO_EINVAL: gibi kullanılırsa kodu bir goto yığınına çevirmek gerekmez

    • Ama bu, değiştirilmemiş kodu derlemeye yardımcı olmaz. Derleyici içinde üretilen goto’lar için endişelenmeye gerek yok. Derleyici zaten bunu orada burada yapar
      Elbette derleyicinin böyle bir switch’i tanıyıp if ağacı olarak yeniden yazmak yerine, label’ları her değer için cosmo’ya özel sabitlerle değiştirmesini ve switch girdisini mevcut runtime platform değerinden ilgili cosmo’ya özel sabite eşleyen bir fonksiyon çağrısıyla sarmalamasını kastediyorsanız, o başka. Diğer değerleri de değiştirmeden geçirerek
      Bu aslında daha basit bir derleyici dönüşümü de olabilir. Ancak hangi sabit kümelerinden birinin kullanıldığını tanıma ve switch girdisine uygun eşleme çağrısını uygulama karmaşıklığı ortaya çıkar. Kütüphane tarafında da bu eşlemeleri oluşturmak gerekeceğinden karmaşıklık ister
      Zaten başka bir dönüşüm yöntemiyle çalışan bir uygulama olduğuna göre, yazarın bunu uygulamak isteyip istemeyeceğini bilmiyorum
      Son olarak böyle bir eşleme yaklaşımında yanlış eşleme riski var; negatif errno değerleri kullanan kodlarda da eşlemeden önce girdiyi tersine çevirmek gibi zahmetli durumlar söz konusu
    • Runtime sembolünü bir fonksiyonla dereference etme yaklaşımı ilginç geliyor. Çalışan bir örnek gösterirseniz memnuniyetle denerim
      if-else-goto dizisini sevmemin nedeni, GCC’nin diğer bölümleriyle tam oturmasıydı. Patch’ime[1] bakarsanız bu özelliği eklemek için GCC’nin mevcut kodunda çok az değişiklik yaptığımı görebilirsiniz
      [1]: https://github.com/ahgamut/gcc/tree/portcosmo-11.2
    • Değiştirmenin otomatik yapılıp yapılmadığına kadar önemsemek gerekiyor mu?
  • SIGxxx’i indeks olarak kullanan koda neden bu kadar şaşırıldığını bilmiyorum. Sinyal numaraları iyi tanımlıdır; genelde ardışık oldukları ve 1’den başladıkları bilinir

    • busybox’ı denemeden önce git, bash, gcc, curl gibi birçok codebase’i derlemiştim; böyle bir kod görmemiştim
      Üstelik o hata, debug etmesi uzun süren patch’imdeki derleyici hatasıyla birlikte ortaya çıktığı için akılda kalan bir sürpriz oldu
      Böyle bir kod kalıbının yaygın olduğunu düşünüp düşünmediğinizi merak ediyorum. busybox dışında hangi codebase’lerde olduğunu da bilmek isterim. Çok örnek varsa patch’i bu kalıbı işleyecek şekilde güncellemeye zaman ayırabilirim
  • errno bu kadar büyük bir sorunsa, C++ standart kütüphanesini ve std::error_code’u çalıştırma umudu nedir bilmiyorum
    Orada sihirli makrolar kullanmanın bir yolu yok

    • Sorun o sihirli makro gibi görünüyor. Normalde makrolar sabitlere genişler, ama artık öyle olmadığı için bazı şeyler bozuluyor
      std::error_code bir sabit olmadığı için aynı sorunu yaşamaz gibi görünüyor