- Kode yang hanya mengikuti standar ISO C itu jarang, dan codebase C di dunia nyata bergantung pada ekstensi nonstandar untuk menambah fitur serta mengatasi celah pada tiap kompiler dan pustaka
- Kompiler C yang berguna harus bisa memproses header sistem seperti
<stdio.h>, tetapi glibc membuat hambatan melalui ekstensi GNU dan asumsi seperti __attribute__((packed)) dan #include_next
- Logika byteswapping di SDL dapat memilih inline assembly jika ada makro ISA, sehingga kompiler selain GCC·clang pun bisa dituntut mendukung ekstensi gaya GCC
- Penanganan
extern inline di OpenBSD dan Gnulib membuat kompatibilitas semantik inline menjadi rumit karena perbedaan makna antara C99 dan GCC, percabangan per platform, serta kondisi _FORTIFY_SOURCE
- Kompiler C kecil harus memilih antara patch upstream, patch downstream, menyiapkan guard khusus, atau meniru kompatibilitas GCC, dan perluasan makro pengujian fitur tampak sebagai arah yang lebih baik
Hambatan pertama yang dibuat header glibc
- Untuk menjadi kompiler C yang berguna, ia harus mampu memproses dan mengurai header pustaka C sistem, dan jika tidak bisa menangani
<stdio.h>, akan sulit bahkan untuk meloloskan hello world
- Di lingkungan GNU/Linux, hambatan ini berujung pada glibc
- glibc menentukan ekstensi yang didukung dengan memeriksa makro yang telah didefinisikan kompiler dalam sys/cdefs.h, yang secara tidak langsung disertakan oleh hampir semua header libc
- Ekstensi yang tidak didukung ditangani dengan menghapus definisi terkait, tetapi logika kompatibilitas ini sendiri pada praktiknya bisa rusak
-
struct epoll_event dan __attribute__((packed))
struct epoll_event di sys/epoll.h Linux adalah packed struct yang menggunakan GNU __attribute__((packed))
- Atribut ini mengubah layout struktur pada 64-bit, sehingga jika diabaikan maka ABI akan rusak
- Tidak cukup jika kompiler hanya mengimplementasikan
__attribute__((packed))
- Di
sys/cdefs.h, ada kode yang mendefinisikan __attribute__(xyz) sebagai makro kosong jika kompiler bukan GCC, clang, atau tcc
- Akibatnya, kompiler lain bisa kehilangan atribut packed di header glibc meskipun sebenarnya mendukung atribut tersebut
- Karena header
epoll khusus Linux, ada juga argumen bahwa standar portabilitas C tidak mudah diterapkan mentah-mentah di sini
-
limits.h dan #include_next
- Beberapa header C seperti
stddef.h, stdint.h, limits.h, dan float.h dibutuhkan bahkan dalam implementasi freestanding, sehingga harus disediakan oleh kompiler
- POSIX menuntut
limits.h mendefinisikan konstanta khusus POSIX selain konstanta C standar, sehingga dibutuhkan limits.h khusus platform di atas limits.h milik kompiler
<limits.h> milik glibc langsung mendefinisikan nilai limits.h ANSI jika bukan GNU C, dan di lingkungan GCC mengambil header kompiler dengan #include_next <limits.h>
- Struktur ini mengasumsikan bahwa builtin
limits.h khusus GCC mendefinisikan makro tertentu, dan juga bergantung pada ekstensi #include_next
- clang juga harus mengakali struktur ini
Deteksi fitur SDL dan masalah inline assembly
- Fungsi byteswapping di
SDL_endian.h memakai builtin kompiler atau inline assembly jika memungkinkan, dan sebagai jalan terakhir diganti dengan implementasi operasi bit biasa
- Logika deteksinya kira-kira berjalan dalam urutan berikut
- Jika GCC atau clang dan ada
__has_builtin(__builtin_bswapX), gunakan builtin
- Jika MSVC 8.0 atau lebih baru, gunakan intrinsic MSVC
#pragma
- Jika makro spesifik ISA seperti
__x86_64__ terdefinisi, gunakan inline assembly
- Selain itu, gunakan implementasi operasi bit biasa
- Urutan ini menjadi masalah jika kompiler yang bukan GCC atau clang secara masuk akal mendefinisikan predefined macro spesifik ISA
- Sekalipun kompiler tersebut menyediakan builtin bswap dan operator khusus
__has_builtin, secara logika ia bisa tetap mencoba memakai inline assembly gaya GCC
- Hasilnya, strukturnya menjadi seperti mengharapkan kompiler yang tidak dikenal juga mendukung inline assembly gaya GCC
OpenBSD libc dan kebingungan extern inline
- Beberapa header OpenBSD memuat definisi fungsi inline yang dapat digunakan kompiler secara opsional saat optimisasi
- Fungsi-fungsi ini didefinisikan melalui makro
__only_inline, dan jika kompiler tidak benar-benar melakukan inline, harus ada pengganti berupa simbol eksternal
- Artinya, dibutuhkan fungsi inline dengan extern linkage
-
Perbedaan makna inline di C99 dan GCC
inline memang ditetapkan di C99, tetapi perilaku standarnya bertabrakan dengan perilaku GCC nonstandar sebelum C99
- Definisi inline di dalam header harus memakai
extern inline bersama badan fungsi, dan dalam kasus ini tidak akan mengeluarkan exported function yang nyata
- Di translation unit, deklarasi untuk mengekspor definisi fungsi harus hanya memakai
inline
- Makna
inline juga berbeda antara C++ dan C
- Perbedaan ini dibahas rinci dalam tulisan Youtao Guo
-
__only_inline milik OpenBSD
- OpenBSD bergantung pada semantik inline GCC
- Untuk menutupi perbedaan versi GCC, makro
__only_inline di sys/cdefs.h menetapkan semantik inline gnu89 lama pada GCC modern dengan __attribute__ eksplisit
- Pada kompiler non-GNU,
__only_inline didefinisikan sebagai linkage static
- Akibatnya, fungsi bisa dideklarasikan dan didefinisikan dengan linkage yang saling bertentangan lalu rusak
-
Solusi _ANSI_LIBRARY
- OpenBSD menghormati makro
_ANSI_LIBRARY
- Jika makro ini didefinisikan, penggunaan definisi
__only_inline yang bermasalah di header standar seperti signal.h diabaikan sepenuhnya
- Versi yang dioptimalkan memang tidak didapat, tetapi setidaknya proses build tetap berjalan
-
Kode kompatibilitas extern inline milik Gnulib
- Kode kompatibilitas
extern inline di Gnulib juga muncul saat membangun Guile dan nano
- extern-inline.m4 memuat percabangan kondisi yang rumit untuk menangani implementasi sudut semantik C ini yang aneh dan mudah rusak
- Kondisinya mencerminkan perbedaan lingkungan seperti Apple, DragonFly, FreeBSD, GCC, clang, PCC, HP cc, PGI, SunPro C,
_FORTIFY_SOURCE, __GNUC_STDC_INLINE__, dan __GNUC_GNU_INLINE__
Asumsi clang dalam bionic Android
- bionic adalah libc milik Android, dan header-nya jauh lebih kuat mengasumsikan clang dibanding GCC
- Header bionic banyak memakai ekstensi khusus clang seperti
_Nonnull dan _Null_unspecified untuk nullability checks
- Makro seperti ini tidak sulit dihilangkan dengan
#define lewat flag baris perintah
- Masalah ini terlihat saat memakai ponsel Android sebagai lingkungan pengembangan aarch64 native melalui Termux
_Null_unspecified juga disebut __BIONIC_COMPLICATED_NULLNESS, dan definisi terkait ada di sys/cdefs.h milik bionic
Pilihan yang dihadapi kompiler C kecil
- Kode yang hanya mengikuti standar ISO C jarang ditemukan di dunia nyata, dan banyak codebase C bergantung pada perilaku nonstandar serta ekstensi bahasa
- Ketergantungan ini muncul bukan hanya untuk fitur tambahan, tetapi juga ketika mengatasi bug dan celah yang berbeda-beda pada tiap kompiler dan pustaka
- Codebase yang ingin mendukung banyak lingkungan bergantung pada pemeriksaan preprocessor dan guard, tetapi pendekatan ini mudah rusak dan sulit dikelola
- Saat membuat kompiler C seperti antcc, masalah kompatibilitas seperti ini terus berulang
- Jika banyak proyek open source bergantung pada ekstensi dan perilaku nonstandar spesifik kompiler bahkan untuk hal yang tidak esensial, beban respons untuk kompiler alternatif menjadi makin besar
- Pada saat yang sama, juga sulit menuntut semua pengembang untuk menguji kode C mereka di banyak kompiler, termasuk kompiler kecil yang kurang dikenal
- Portabilitas C sendiri sudah cukup sulit
- Dari sudut pandang penulis kompiler, ada empat pilihan yang mungkin
- Mencoba mengirim patch inkompatibilitas ke upstream
- Menjadi cukup terkenal sehingga pengembang menambahkan pemeriksaan
#ifdef khusus dan pengujian dasar
- Menanganinya di downstream, lalu mendistribusikan patch atau patch terpisah
- Berpura-pura sebagai versi GCC tertentu dan mengimplementasikan ekstensi yang relevan
- Patch upstream tampak seperti pertarungan yang sulit dimenangkan, dan patch downstream adalah cara termudah
- Untuk mendukung banyak codebase dengan kebingungan seminimal mungkin bagi pengguna dan pengembang, meniru kompatibilitas GCC terdengar realistis, tetapi beban implementasinya besar
- clang mendefinisikan
__GNUC__=4, __GNUC_MINOR__=2, dan __GNUC_PATCHLEVEL__=1 untuk mengklaim kompatibilitas dengan GCC 4.2.1
- Kini clang hampir menjadi target dukungan tersendiri, tetapi upaya besar tetap diperlukan sampai kernel Linux bisa dikompilasi dengan clang, termasuk patch di kedua proyek
Makro GCC dan masalah mengejar ketertinggalan
- Pendekatan berpura-pura sebagai GCC juga punya masalah
- Banyak codebase hanya memeriksa
#ifdef __GNUC__ dan bisa memakai ekstensi GCC baru tanpa memeriksa versinya
- Dalam kasus seperti ini, kompiler alternatif harus terus mengejar ketertinggalan
- Ini juga menjadi salah satu alasan mengapa clang, meskipun mendukung ekstensi GNU yang lebih baru daripada 4.2.1, tidak menaikkan nilai makro
__GNUC__
- Latar belakang terkait dibahas dalam diskusi LLVM tentang menaikkan minor version
__GNUC__
Arah yang lebih baik dan kondisi saat ini
- Idealnya, makro pengujian fitur harus dipakai lebih luas daripada guard khusus kompiler dan pemeriksaan versi
- Makro pengujian fitur yang berguna antara lain
__has_builtin, __has_feature, dan __has_attribute
- Pendekatan seperti makro standar
__STDC_NO_VLA__ juga bisa dipakai lebih luas
- Saat ini di dunia *NIX, baik atau buruk, duopoli semu GCC/clang adalah kondisi dasarnya
- Pengembangan kompiler C kecil yang independen juga terus berlanjut
1 komentar
Komentar Lobste.rs
(penulis kompiler kefir) Masalah
__attribute__di<sys/cdefs.h>menurut pengalaman termasuk salah satu yang paling merepotkan. Ini merusak epoll, struktur packed yang umum, constructor, dan visibilitas simbol, sehingga kefir sampai harus dibundel bersama header monkeypatch berikutMemang tidak ideal, tetapi mungkin ini cara yang paling realistis, dan dalam praktiknya ini memungkinkan penghapusan sebagian besar patch kustom dari test suite eksternal
Jenis kegagalan lain adalah kode alternatif yang bug. Beberapa proyek mencoba mendeteksi kompiler agar bisa menyesuaikan perilakunya, tetapi karena pengujian pada kompiler alternatif kurang, kode fallback sering penuh bug atau tidak terawat dengan baik. Dari sudut pandang pembuat kompiler, ini jauh lebih menjengkelkan daripada langsung gagal dengan status “kompiler tidak didukung”. Misalnya, karena harus men-debug sendiri miskompilasi aneh seperti ketidaksesuaian lebar typedef integer antara program dan pustaka yang sudah dikompilasi sebelumnya
$TERMtidak disetel kexterm-256coloruntuk berpura-pura menjadi xterm, banyak hal jadi rusakSaya benar-benar tidak tahu harus menyelesaikannya bagaimana. Pada akhirnya apakah proyek kita memang harus cukup luas dipakai dan cukup terkenal dulu ya. Mudah sekali!
Saya rasa saya juga pernah beberapa kali mengalami miskompilasi aneh yang muncul karena fallback pendeteksian kompiler tidak dikelola dengan baik, dan itu memang sangat menyebalkan
Saya terutama mengembangkan cproc di linux-musl, jadi saya tidak tahu bahwa glibc menonaktifkan
__attribute__untuk kompiler lain, tetapi ini benar-benar situasi yang cukup buruk. Komentarnya mengatakan penggunaan attribute aman meski diabaikan, tetapi itu tidak mempertimbangkan bahwa sebagian besar kode aplikasi secara tidak langsung mengikutsertakansys/cdefs.hdan bisa memakai attribute yang tidak boleh diabaikanSelain
packed,aligneddanconstructorjuga sering dipakaiSaya penasaran apakah ini sudah dilaporkan di issue tracker suatu tempat. Sebagian besar penggunaan attribute di dalam cdefs.h tampaknya sudah dilindungi dengan
__glibc_has_attribute, jadi saya juga penasaran apa sebenarnya yang dicapai oleh penonaktifan__attribute__secara menyeluruh, dan apakah itu bisa dihapusFitur yang dipakai header libc tetapi tidak punya cara yang baik bagi kompiler untuk menyatakan dukungannya juga menjadi masalah. Ini fitur yang tidak terekspos lewat cara seperti
__has_attributeatau__has_builtin; contoh yang terlintas adalah label__asm__. NetBSD memakainya untuk mengganti nama simbol, dan jika tidak ada__GNUC__atau__PCC__, ia mengeluarkan#error. Namun saya juga tidak tahu apa yang sebaiknya diusulkan selain membiarkannya saja dicoba lalu gagal bila memang tidak didukungSaya juga pernah mengalami masalah terkait
__builtin_va_list. Ada libc yang tanpa__GNUC__mendefinisikanva_listsebagaivoid *, atau bahkan memberikan definisi yang saling bertentangan. Ini juga tidak bisa diuji dengan__has_builtin. Mungkin__has_builtin(__builtin_va_arg)bisa jadi pengujian yang cukup baik, tetapi saya tidak yakin bagaimana membuat ini diperbaiki di macOS__attribute__di/usr/include/sysdan/usr/include/bits, dan ada banyak penggunaan yang tidak dilindungi. Kebanyakan berupa__format__,__aligned__,__noreturn__, jadi ini juga perlu diperbaikiSecara umum glibc tampaknya tidak memprioritaskan kompatibilitas dengan kompiler non-GCC, jadi saya tidak yakin patch seperti itu akan diterima. Setelah upgrade sistem di awal tahun ini, glibc menambahkan penggunaan
__SIZE_TYPE__tanpa pelindung di header Linux, sehingga kompiler saya tidak bisa mengompilasi beberapa proyek. Saya sudah melaporkannya, tetapi sampai sekarang belum diperbaiki, dan akhirnya saya menambahkan makro pradefinisi bergaya__X_TYPE__agar sesuai dengan GCCUntuk masalah label
__asm__, saya juga tidak terpikir solusi yang bagus. Namun jika penggantian nama asm memang 100% diperlukan agar bisa berfungsi, mungkin lebih baik cukup dicoba saja dan dibiarkan gagal ketimbang melakukan pengecekan kompiler__builtin_va_listini cukup serius. Saya sempat mengira__has_builtin(__builtin_va_list)akan berfungsi, tetapi ternyata tidak