Zig, semantik @bitCast baru dan peningkatan backend LLVM
(ziglang.org)- Pada branch master Zig, peningkatan penanganan integer non-ABI di backend LLVM dan semantik
@bitCastbaru telah digabungkan, merapikan sekaligus masalah optimisasi dan ketidaksesuaian perilaku bahasa - Integer lebar bit arbitrer seperti
u4,i13,u40kini diperlakukan sebagai bit-int pada nilai SSA, tetapi saat disimpan ke memori akan diperluas ke integer berukuran ABI @bitCastlama 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, dancompiler_rtikut 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,u40langsung ke tipe bit-int LLVM IR sepertii4,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,
@bitCastlama 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),@bitCastdari[3]u8keu24tetap diizinkan - Backend LLVM mengimplementasikan semantik
@bitCastyang 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
@bitCastbaru 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 @bitCastbaru bekerja berdasarkan urutan bit yang secara logis merepresentasikan tipe, bukan byte memoriu5tersusun dari 5 bit logis dari least-significant bit hingga most-significant bit[2]u5tersusun dari 10 bit logis, yaitu 5 bit elemen pertama diikuti 5 bit elemen kedua
- Untuk konversi sederhana antar-integer, seperti mengubah
u8menjadii8dengan ukuran yang sama, bit tetap dipertahankan apa adanya, dan bit tertinggi ditafsirkan sebagai bit tanda - Semantik
@bitCastantara tipe integer danpacked structataupacked unionjuga 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,
@bitCastdari[2]u8keu16menghasilkan 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]u3menjadi@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
@bitCastke@Vector(n, u1)
Proposal terkait dan migrasi
- Dalam pekerjaan kali ini, proposal kecil yang berkaitan dengan
@bitCastjuga ikut diimplementasikan - Karena semantik baru berbeda secara bermakna dari semantik lama, penggunaan
@bitCastdi pustaka standar, compiler, dan pustaka pendukung seperticompiler_rtikut 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
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
Dalam log pengembangan baru tertanggal 25 Juni 2026, disebutkan bahwa semantik
@bitCastbaru dan peningkatan backend LLVM telah digabungkan ke pull request terbaruMenarik, 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:
Sebenarnya sepertinya bukan masalah besar; dari ribuan
@bitCastdi repositori Zig, yang terdampak perubahan ini tampaknya jauh lebih sedikit dari 100Sejujurnya saya juga tidak yakin kebanyakan pengguna Zig benar-benar tahu persis bagaimana
@bitCastbekerja 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 sajaSebagai mantan programmer C, saya ingat bit field di C kurang disukai karena perilakunya tidak portabel antar arsitektur
Semantik
@bitCastZig yang baru tampak seperti semantik abstrak yang portabel dan menghasilkan keluaran yang sama di arsitektur berbeda, jadi menurut saya ini memang arah yang dibutuhkanSaya 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
packed structdanpacked union, dan keduanya didefinisikan agar cocok dengan definisi@bitCastyang barupacked structbekerja dengan mengisi bit-bit field ke dalam “integer dasar”. Misalnya, jika field-nya adalahbool,u6, dani9, lalu integer dasarnyau16, maka bit paling rendah dariu16menjadibool, 6 bit berikutnya menjadiu6, dan 9 bit sisanya menjadii9. Dengan kata lain, packed struct di Zig kurang lebih adalah gula sintaks di atas beberapa shift dan maskpacked unionjuga 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@bitCastdalam semantik baru. Hanya saja field padapacked union/packed structtidak bisa bertipe array atau vektorSecara 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 structdan 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 rapiMisalnya, flag akses RWX di C bisa diterima lewat makro
ACCESS_READ,ACCESS_WRITE,ACCESS_EXECdan APIuint8_t, tetapi di Zig Anda bisa mendefinisikanAccess = packed struct(u8)dengan fieldread,write,exec, danreserved, lalu API menerimaAccessDengan
packed structdanpacked union, susunan bit yang cukup aneh pun bisa direpresentasikan. Entri tabel simbol pada format objek Mach-O memiliki fieldn_typeyang tampak ganjil karena alasan historis, dan ini bisa dimodelkan sebagaibits: packed struct(u8)danstab: enum(u8)di dalampacked union(u8)Saat menangani nilai
n_typeini, tidak perlu melakukan shift atau masking manual. Cukup cekn_type.bits.is_stab != 0, lalu jika benar lakukanswitchpadan_type.stab; jika tidak, lihat field lain padan_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 structdanpacked uniondi Zig. Menurut saya, alatnya sederhana tetapi cukup bagus