1 poin oleh GN⁺ 5 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • Pada branch master Zig, peningkatan penanganan integer non-ABI di backend LLVM dan semantik @bitCast baru telah digabungkan, merapikan sekaligus masalah optimisasi dan ketidaksesuaian perilaku bahasa
  • Integer lebar bit arbitrer seperti u4, i13, u40 kini diperlakukan sebagai bit-int pada nilai SSA, tetapi saat disimpan ke memori akan diperluas ke integer berukuran ABI
  • @bitCast lama lebih dekat ke reinterpretasi byte memori, tetapi definisi baru menafsirkannya berdasarkan susunan bit logis dari tipe sehingga mengurangi ketergantungan pada endian
  • Perubahan ini juga diperluas ke backend LLVM·C dan eksekusi comptime, serta penggunaan terkait di pustaka standar, compiler, dan compiler_rt ikut ditinjau
  • Dengan optimisasi LLVM yang sebelumnya terlewat kini aktif kembali, sekitar peningkatan performa 5% teramati pada compiler Zig sendiri, dan beberapa peningkatan performa runtime dapat diharapkan di 0.17.0

Perubahan penanganan integer lebar bit arbitrer di backend LLVM

  • Sebelumnya Zig menurunkan tipe integer lebar bit arbitrer seperti u4, i13, u40 langsung ke tipe bit-int LLVM IR seperti i4, i13, i40
  • Pendekatan ini membuat semantik representasi memori LLVM menimbulkan batasan yang tidak perlu pada optimizer, dan karena Clang tidak menghasilkan LLVM IR seperti ini, jalur internal LLVM tersebut juga tidak cukup teruji
  • Selama beberapa tahun terakhir, benar-benar teramati kasus optimisasi yang terlewat dan miscompilation
  • Pendekatan baru tetap memakai tipe bit-int untuk manipulasi nilai SSA, tetapi saat menyimpan ke memori nilainya di-zero-extend atau sign-extend ke tipe berukuran ABI seperti i8, i16, i32
  • Lowering ini selaras dengan cara Clang me-lower _BitInt(N) di C, sehingga diharapkan mengikuti jalur yang lebih didukung di LLVM

Keterbatasan @bitCast lama

  • Secara konseptual, @bitCast lama kurang lebih mendekati perilaku berikut
    • mengambil pointer ke nilai operand
    • me-cast pointer tersebut menjadi pointer tipe tujuan
    • memuat nilai dari pointer itu
  • Artinya definisi lama lebih dekat ke reinterpretasi byte dalam memori daripada struktur logis tipe
  • Seiring waktu, perilaku aktual menyimpang dari definisi ini, dan meskipun @sizeOf(u24) pada kebanyakan target lebih besar daripada @sizeOf([3]u8), @bitCast dari [3]u8 ke u24 tetap diizinkan
  • Backend LLVM mengimplementasikan semantik @bitCast yang belum terspesifikasi dengan cukup baik, dan ketika cara penyimpanan tipe integer ke memori diubah, muncul Illegal Behavior dan crash dalam test suite compiler
  • Alih-alih menambahkan logika di backend LLVM untuk meniru perilaku lama, dipilih arah untuk mengimplementasikan definisi @bitCast baru secara menyeluruh

Semantik @bitCast baru

  • Semantik baru ini didasarkan pada proposal bahasa yang diajukan dan diterima pada 2024, #19755
  • Semantik ini sebelumnya sudah diimplementasikan di backend self-hosted x86_64, dan dengan perubahan kali ini diperluas ke backend LLVM·C serta eksekusi comptime
  • @bitCast baru bekerja berdasarkan urutan bit yang secara logis merepresentasikan tipe, bukan byte memori
    • u5 tersusun dari 5 bit logis dari least-significant bit hingga most-significant bit
    • [2]u5 tersusun dari 10 bit logis, yaitu 5 bit elemen pertama diikuti 5 bit elemen kedua
  • Untuk konversi sederhana antar-integer, seperti mengubah u8 menjadi i8 dengan ukuran yang sama, bit tetap dipertahankan apa adanya, dan bit tertinggi ditafsirkan sebagai bit tanda
  • Semantik @bitCast antara tipe integer dan packed struct atau packed union juga tetap dipertahankan

Perubahan perilaku pada array·vektor

  • Titik utama perbedaan semantik baru dibanding yang lama muncul ketika tipe agregat seperti array dan vektor terlibat
  • Sebagai contoh, @bitCast dari [2]u8 ke u16 menghasilkan nilai berbeda menurut endian target dalam semantik lama
    • pada target big-endian, elemen array pertama menjadi 8 bit atas
    • pada target little-endian, elemen array pertama menjadi 8 bit bawah
  • Semantik baru hanya mempertimbangkan representasi bit logis sehingga independen terhadap endian, dan di semua target elemen array pertama menjadi 8 bit bawah
  • Secara umum, ini lebih dekat ke perilaku lama pada target little-endian
  • Konversi tidak lazim seperti [2]u3 menjadi @Vector(3, u2) juga dimungkinkan
    • bit logis array digabung terlebih dahulu, lalu dibaca per 2 bit untuk membentuk elemen vektor
    • ini juga bisa dipakai untuk memecah integer menjadi vektor bit individual dengan @bitCast ke @Vector(n, u1)

Proposal terkait dan migrasi

  • Dalam pekerjaan kali ini, proposal kecil yang berkaitan dengan @bitCast juga ikut diimplementasikan
    • pelarangan @bitCast dengan vektor pointer: #18936
    • izin @bitCast terhadap enum: bagian dari #35602
  • Karena semantik baru berbeda secara bermakna dari semantik lama, penggunaan @bitCast di pustaka standar, compiler, dan pustaka pendukung seperti compiler_rt ikut diperiksa
  • PR terkait adalah codeberg.org/ziglang/zig/pulls/35711, dan saat digabung ke master beberapa issue juga ikut ditutup
  • Semantik yang berubah dan prosedur migrasi yang direkomendasikan akan dirangkum dalam catatan rilis Zig 0.17.0

Dampak performa yang diharapkan di 0.17.0

  • Perubahan lowering integer non-ABI di backend LLVM, yang merupakan tujuan awal, berhasil menghidupkan kembali optimisasi yang sebelumnya terlewat
  • Hasil terkait dapat dilihat di demonstrably successful
  • Compiler Zig sendiri tidak banyak memakai integer lebar bit arbitrer secara internal, tetapi berkat optimisasi yang lebih baik tetap menunjukkan sekitar peningkatan performa 5%
  • Di 0.17.0, beberapa kode juga dapat memperoleh sedikit peningkatan performa runtime

1 komentar

 
GN⁺ 5 jam lalu
Pendapat di Lobste.rs
  • Tulisan itu menyebut representasi bit logis bersifat independen terhadap endianness, tetapi penjelasan nyatanya tampak seperti pendekatan little-endian yang jelas tidak mendukung urutan bit atau urutan byte big-endian

    • Di sini, maksud independen terhadap endianness tampaknya adalah perilakunya tidak berubah antara arsitektur little-endian dan big-endian
  • Dalam log pengembangan baru tertanggal 25 Juni 2026, disebutkan bahwa semantik @bitCast baru dan peningkatan backend LLVM telah digabungkan ke pull request terbaru

  • Menarik, tetapi saya jadi bertanya-tanya apakah pada target big-endian yang jarang diuji, kode yang ditulis seperti di bawah ini bisa tiba-tiba rusak
    Jika ditulis sebagai pseudocode non-Zig:

    if target_is_little_endian {  
        my_int = @bitCast(my_array);  
    } else {  
        my_int = @bitCast([my_array[1], my_array[0]]);  
    }  
    
    • Saya juga sempat memikirkan hal itu, tetapi menurut saya menunda perubahan yang pada akhirnya tak terhindarkan hanya akan memperbesar masalah
      Sebenarnya sepertinya bukan masalah besar; dari ribuan @bitCast di repositori Zig, yang terdampak perubahan ini tampaknya jauh lebih sedikit dari 100
      Sejujurnya saya juga tidak yakin kebanyakan pengguna Zig benar-benar tahu persis bagaimana @bitCast bekerja saat mengonversi antara array/vektor dan skalar. Sebelumnya, banyak kode mungkin hanya diuji di sistem penulisnya dan karenanya hanya berjalan di little-endian, sedangkan sekarang justru bisa berjalan di mana saja
  • Sebagai mantan programmer C, saya ingat bit field di C kurang disukai karena perilakunya tidak portabel antar arsitektur
    Semantik @bitCast Zig yang baru tampak seperti semantik abstrak yang portabel dan menghasilkan keluaran yang sama di arsitektur berbeda, jadi menurut saya ini memang arah yang dibutuhkan
    Saya sedang merancang bit field dan bit cast di bahasa saya sendiri akhir-akhir ini, jadi saya berniat melihat dokumen desain dan implementasi Zig lebih saksama untuk memperjelas bagaimana kode saya seharusnya berperilaku

    • Alternatif utama Zig untuk bit field C mungkin adalah packed struct dan packed union, dan keduanya didefinisikan agar cocok dengan definisi @bitCast yang baru
      packed struct bekerja dengan mengisi bit-bit field ke dalam “integer dasar”. Misalnya, jika field-nya adalah bool, u6, dan i9, lalu integer dasarnya u16, maka bit paling rendah dari u16 menjadi bool, 6 bit berikutnya menjadi u6, dan 9 bit sisanya menjadi i9. Dengan kata lain, packed struct di Zig kurang lebih adalah gula sintaks di atas beberapa shift dan mask
      packed union juga memiliki integer dasar, tetapi semua field harus memakai jumlah bit yang persis sama dengan integer dasar tersebut. Jadi, menulis ke satu field lalu membaca dari field lain hampir identik dengan @bitCast dalam semantik baru. Hanya saja field pada packed union/packed struct tidak bisa bertipe array atau vektor
      Secara pribadi, saya rasa alat-alat ini sangat cocok untuk merepresentasikan “struktur yang berkaitan dengan bit”. Anda bisa melakukan bit packing beberapa nilai ke dalam packed struct dan memakainya seperti bit field C, dan karena ini adalah gula sintaks di atas operasi bit, bit flag yang di C biasanya ditangani dengan tumpukan makro yang tidak type-safe juga bisa diekspresikan dengan rapi
      Misalnya, flag akses RWX di C bisa diterima lewat makro ACCESS_READ, ACCESS_WRITE, ACCESS_EXEC dan API uint8_t, tetapi di Zig Anda bisa mendefinisikan Access = packed struct(u8) dengan field read, write, exec, dan reserved, lalu API menerima Access
      Dengan packed struct dan packed union, susunan bit yang cukup aneh pun bisa direpresentasikan. Entri tabel simbol pada format objek Mach-O memiliki field n_type yang tampak ganjil karena alasan historis, dan ini bisa dimodelkan sebagai bits: packed struct(u8) dan stab: enum(u8) di dalam packed union(u8)
      Saat menangani nilai n_type ini, tidak perlu melakukan shift atau masking manual. Cukup cek n_type.bits.is_stab != 0, lalu jika benar lakukan switch pada n_type.stab; jika tidak, lihat field lain pada n_type.bits. Sebaliknya, Anda juga bisa membuat nilai seperti .{ .stab = .gsym } atau .{ .bits = .{ .ext = false, .type = .undf, .pext = false, .is_stab = 0 } }
      Ini jadi agak panjang dan membahas fitur bahasa yang berbeda dari topik tulisan aslinya, tetapi jika Anda mencari sesuatu yang bisa dijadikan referensi untuk desain bahasa baru, ada baiknya mencoba sendiri packed struct dan packed union di Zig. Menurut saya, alatnya sederhana tetapi cukup bagus