2 poin oleh GN⁺ 2024-08-05 | 1 komentar | Bagikan ke WhatsApp
  • Sifat waktu konstan (constant-time) yang penting dalam kode kriptografi bisa rusak hanya oleh optimisasi kompiler, sehingga sedang dilakukan eksperimen menambahkan patch peringatan di dalam LLVM untuk mencari pola berbahaya
  • “Optimisasi” kompiler memang bisa membuat sebagian benchmark lebih cepat, tetapi jalur inti yang sesungguhnya sering bergantung pada intrinsics dan assembly, sementara biaya bug yang lahir dari optimisasi terus menumpuk secara terpisah
  • Pada Juni 2024, Antoon Purnal mengonfirmasi bahwa kode referensi Kyber pada Clang 15 ke atas, dengan sebagian opsi optimisasi tertentu, berubah menjadi percabangan kondisional berbasis nilai rahasia yang dapat membuka jalan bagi serangan timing
  • TIMECOP 2 memeriksa hasil kompilasi yang dideklarasikan waktu konstan di dalam SUPERCOP, tetapi ada keterbatasan pada perintah yang didukung Valgrind dan pada aliran data yang benar-benar muncul saat pengujian dijalankan
  • Respons praktis di lapangan adalah memakai fungsi seperti crypto_{int,uint}{8,16,32,64}.h agar kompiler tidak melihat hasil 1-bit sebagai bool, atau berpindah ke assembly yang sudah diverifikasi, bahasa yang berorientasi keamanan, atau kompiler khusus

Kekosongan tanggung jawab yang diciptakan “optimisasi” kompiler

  • Dalam riwayat perubahan LLVM dan GCC modern, “optimisasi”, pengujian “optimisasi”, perbaikan pengujian, dan perbaikan bug “optimisasi” terus bermunculan
  • Walaupun kode bekerja baik sebelum dikompilasi, jika perilakunya berubah setelah perubahan kompiler, dalam banyak kasus tanggung jawabnya justru dibebankan kepada programmer yang dianggap menyentuh “undefined behavior”
  • “Language standards” seperti ini dibuat oleh para penulis kompiler, dan hasilnya adalah struktur di mana perubahan dari kelompok kecil pembuat kompiler membebankan tanggung jawab yang lebih besar kepada kode milik jutaan programmer
  • Sebagai contoh kode kriptografi, pada berbagai benchmark CPU, implementasi avx2 dari kyber768 sekitar 4 kali lebih cepat daripada kode portabel yang dikompilasi dengan kompiler “optimisasi”

Batas pengukuran kinerja optimisasi

  • Pada 2000, Todd A. Proebsting dalam Proebsting's Law menyatakan bahwa “kemajuan kompiler menggandakan daya komputasi setiap 18 tahun”, dan menyimpulkan kontribusi optimisasi kompiler hanya bersifat pinggiran
  • Arseny Kapoulkine dalam benchmark tahun 2022 merangkum bahwa LLVM 11 membutuhkan waktu 2 kali lebih lama untuk kompilasi teroptimisasi dibanding LLVM 2.7, sementara kode hasil eksekusinya umumnya hanya 10~20% lebih cepat
  • Kedua pembahasan itu sama-sama meleset dari pengukuran kinerja yang benar-benar dirasakan pengguna
    • Hotspot tempat kinerja terkonsentrasi sering berisi intrinsics dan assembly
    • FFmpeg memiliki 160.000 baris assembly berdasarkan file .asm dan .S
    • Semakin banyak data yang diproses komputer dan jaringan, semakin besar pula porsi waktu CPU nyata yang tertumpu pada hotspot semacam ini
  • Biaya keamanan juga tumbuh terpisah dari diskusi optimisasi
    • Deloitte melaporkan anggaran keamanan TI pada 2023 sebesar 0,5% dari pendapatan perusahaan
    • Jika dilihat bersama angka total pendapatan perusahaan global pada 2022 yang melebihi 48 triliun dolar, skala keseluruhannya bisa mencapai ratusan miliar dolar
    • Namun ada catatan bahwa angka 0,5% Deloitte bisa jadi hanya rata-rata sederhana per perusahaan, dan tidak semua perusahaan menjawab survei

Kebocoran timing dan kasus Kyber

  • Masalah keamanan yang dibuat kompiler “optimisasi” bukan hanya bug tradisional, tetapi juga kebocoran timing di mana informasi rahasia bocor lewat waktu eksekusi
  • Makalah EuroS&P 2018 oleh Laurent Simon, David Chisnall, dan Ross Anderson memperingatkan bahwa upgrade kompiler dapat diam-diam membuka kanal timing pada kode yang sebelumnya aman
  • Contoh yang ditekankan dalam makalah 2018 adalah kode yang memilih salah satu dari dua nilai menggunakan bool, dan bool memicu kompiler membuat lompatan kondisional
    • Dalam implementasi kriptografi, ada praktik menghindari hal ini dengan membuang bool dari kode penting dan menyediakan fungsi pembanding waktu konstan tersendiri
    • OpenSSL dikutip mendeklarasikan 37 fungsi untuk tujuan ini
  • Kasus 2015 pada curve25519-donna dan MSVC 2015 dijelaskan sebagai kesalahpahaman dalam artikel ini
    • Yang sebenarnya terjadi, saat dikompilasi untuk x86 32-bit, operasi int64 diubah menjadi panggilan ke library int64 32-bit Microsoft, llmul.asm
    • Kebocoran timing muncul dari percabangan bergantung data di llmul.asm, dan library ini juga dinilai harus masuk dalam konsep yang wajar tentang kode sumber
  • Pada Juni 2024, Antoon Purnal mengonfirmasi bahwa kode referensi Kyber dapat membuka peluang serangan timing pada Clang 15 ke atas dengan opsi optimisasi tertentu
    • Bentuk masalahnya adalah (-((x>>j)&1))&y, yaitu perhitungan yang menghasilkan y jika bit ke-j dari x aktif, dan 0 jika tidak
    • Clang mengubah bit tersebut menjadi bool lewat instruksi bit-test, lalu menghasilkan percabangan kondisional berdasarkan bool itu
    • Di dalam LLVM, “optimisasi” ini ditangani oleh combineShiftAnd1ToBitTest di lib/CodeGen/SelectionDAG/DAGCombiner.cpp
    • Fungsi ini ditambahkan oleh Sanjay Patel pada September 2019 dan kemudian diubah oleh beberapa orang lain
  • Ada juga contoh pelanggaran batas serupa di GCC
    • Patch GCC dari ARM pada November 2021 mengubah (-x)>>31 menjadi -(x>0)
    • Pada April 2024, peringatan mengenai hal ini muncul

TIMECOP dan pemeriksaan waktu konstan

  • TIMECOP 2 terintegrasi ke dalam framework pengujian kriptografi SUPERCOP dan secara otomatis memeriksa percabangan kondisional yang diturunkan dari nilai rahasia pada kode hasil kompilasi yang dideklarasikan waktu konstan
  • Objek pemeriksaan tidak hanya percabangan kondisional, tetapi juga indeks array yang diturunkan dari nilai rahasia
    • Makalah KyberSlash juga menjelaskan patch yang memeriksa pembagian yang diturunkan dari nilai rahasia
  • TIMECOP 1 adalah alat yang dibuat Moritz Neikes dengan memodifikasi SUPERCOP, dan mengotomatisasi pendekatan ctgrind dari Adam Langley
  • TIMECOP 2 memperluas metode sebelumnya dalam beberapa hal
    • Menandai keluaran RNG secara otomatis sebagai nilai rahasia
    • Mendukung “declassification”
    • Mendukung penetapan “public inputs”
    • Berjalan di banyak core
  • TIMECOP memiliki keterbatasan yang jelas
    • Hanya bisa menangani instruksi yang didukung Valgrind, sehingga akan berhenti pada instruksi seperti AMD XOP
    • Hanya memeriksa aliran data yang benar-benar terlihat saat pengujian dijalankan
  • Pengembangan alat pemeriksa perilaku waktu konstan masih terus berjalan, dan daftar alat terkait tersedia di ct-tools
  • Pemeriksaan yang setara dengan TIMECOP telah masuk ke test suite libmceliece, dan bisa menyebar ke library lain

Pendekatan penulisan ulang waktu konstan

  • Setelah menemukan potongan kode berdurasi variabel, dibutuhkan cara untuk menulis ulang menjadi waktu konstan tanpa bug
  • Presentasi Juli 2024 memperkenalkan sebagian fungsi waktu konstan yang disediakan oleh libmceliece dan SUPERCOP
    • Nama filenya crypto_{int,uint}{8,16,32,64}.h
    • File-file ini bisa disalin dan dipakai di proyek lain
  • Fungsi contoh crypto_uint32_bitmod_mask(x,j) memiliki efek yang sama seperti -((x>>(j&31))&1), tetapi membuat kompiler tidak bisa melihat hasil 1-bit
  • Contoh yang lebih kompleks adalah crypto_uint32_max(x,y)
  • Makalah 2018 membahas tweak untuk menambahkan fungsi waktu konstan __builtin_ct_choose(bool cond, x, y) ke Clang/LLVM
    • Makalah itu secara keliru mengusulkan bahwa satu fungsi ini sudah cukup
    • Fungsi ini mungkin suatu hari masuk ke kompiler, tetapi bisa butuh waktu lama sebelum proyek dapat mengandalkannya
    • Cara implementasinya dinilai tampak lebih rapuh daripada crypto_{int,uint}{8,16,32,64}.h

Cara menghindari masalah sejak awal

  • Jika pengujian sebelum distribusi library hasil kompilasi berhasil menangkap kebocoran timing yang diperkenalkan kompiler, versi kompiler sebelumnya bisa tetap dipakai untuk distribusi selama kode sedang ditulis ulang
    • Pendekatan ini adalah respons sementara untuk terus menjaga pengguna tetap aman
  • Salah satu solusi adalah mendistribusikan library dalam bentuk assembly
    • Presentasi RWC 2024 Adoption of high-assurance and highly performant cryptographic algorithms at AWS memperkenalkan perangkat lunak X25519 cepat yang telah dibuktikan menghitung X25519 dengan benar untuk semua input
    • Implementasinya ditulis dalam assembly untuk 2 versi CPU Intel/AMD 64-bit dan 2 versi CPU ARM 64-bit
    • Pernyataan kebenarannya adalah teorema tentang kode mesin yang benar-benar dijalankan pengguna, dan pembuktiannya diverifikasi dengan prover teorema HOL Light
  • Namun, untuk perangkat lunak kriptografi yang belum mencapai tingkat ini, masalah sulitnya audit assembly tetap ada
  • Juga sedang dieksplorasi cara cepat menyuntikkan “vaksin” pencegah kebocoran timing ke kode yang ditulis dalam C, C++, dan sebagainya

Eksperimen patch clang-vs-clang

  • Kesamaan antara x&1 dan x>>31 adalah hasilnya hanya punya dua kemungkinan
    • x&1 adalah 0 atau 1
    • x>>31 untuk uint32 adalah 0 atau 1
    • x>>31 untuk int32 adalah 0 atau -1
  • Bentuk seperti ini memudahkan penulis “optimisasi” kompiler untuk memasukkan hasil 1-bit ke dalam bool
  • Ada rekomendasi untuk selalu mengompilasi dengan -fwrapv agar GCC dan Clang mengasumsikan aritmetika komplemen dua
  • Memindai source untuk &1, 1&, >>31, dan sebagainya memang menghasilkan banyak contoh, tetapi pemindaian juga dilakukan dengan memasukkan patch langsung ke “optimizer” LLVM
  • Patch itu dimulai dari commit LLVM 68df06a0b2998765cb0a41353fcf0919bbf57ddb, mencari &1 dan >>31, lalu mengeluarkan peringatan berikut
    • please take this away before clang does something bad
  • Contoh perintah kompilasinya adalah clang -Rpass-analysis=clang-vs-clang -O -c x.c
  • Fungsi uji yang dipakai adalah sebagai berikut
int sra31(int x)
    {
      x >>= 31;
      return x;
    }
  • Tidak mengejutkan jika peringatan yang sama muncul berulang kali
    • Kompiler terus mencoba menerapkan “optimisasi” sampai tidak ada lagi kemajuan
  • Keluaran clang-vs-clang membedakan signed dan unsigned pada shift
    • Perbedaan ini penting untuk penulisan ulang manual atau otomatis berbasis crypto_{int,uint}{8,16,32,64}.h
    • Salah satu cara mengotomatisasi transformasi source adalah clang-tidy
  • Kode yang terlewat oleh #ifdef atau dihapus sebelum tahap “optimisasi” ini tidak akan memunculkan peringatan clang-vs-clang

Hasil eksekusi SUPERCOP dan kasus yang ditemukan

  • SUPERCOP 20240716 dijalankan di dual EPYC 7742 dengan ./data-do-biglittle
    • Overclocking dinonaktifkan
    • Daftar kompiler SUPERCOP disesuaikan agar memakai clang-vs-clang dengan menambahkan -Rpass-analysis=clang-vs-clang ke baris clang di okcompilers/{c,cpp}
  • Hasilnya siap setelah 3 jam
    • Keluaran Clang berjumlah 675.752 baris
    • Ukuran aslinya 210.786.494 byte
    • Hasil kompresinya adalah 20240803-fromclang.txt.gz sebesar 3.595.199 byte
  • Keluaran itu mengandung banyak noise dari percabangan source berbasis data publik yang di dalam Clang menghasilkan &1
  • Contoh yang jelas layak diubah lebih awal adalah sebagai berikut
a0 += (a0>>15)&106;
  • Contoh yang perlu upaya parsing C bila dicari dengan pemindaian source sederhana adalah sebagai berikut
    • Makro ONE8 didefinisikan sebagai ((uint8_t)1)
*pk2^=(((* pk_cp)>>ir)&ONE8)<<jr;
  • Contoh yang lebih sulit lagi muncul dalam makro berbasis intrinsic AVX2
    • signmask_x16(x) didefinisikan sebagai _mm256_srai_epi16((x),15)
    • Ini menggeser ke kanan 15 bit setiap potongan signed 16-bit di dalam vektor 256-bit
mask = signmask_x16(sub_x16(x,const_x16((q+1)/2)));
  • Kasus AVX2 ini belum menjadi prioritas tinggi
    • Agar operasi vektor berubah menjadi percabangan kondisional, kode harus dikompilasi dengan AVX-512, dan kompiler harus mengambil keputusan aneh dengan mengubah bool tervektorisasi menjadi percabangan kondisional bool serial
    • TIMECOP memakai Valgrind, dan Valgrind tidak mendukung AVX-512
    • Untuk saat ini, kompilasi AVX-512 tidak direkomendasikan

int128 dan arah respons yang lebih luas

  • Temuan paling menarik adalah kasus di mana shift kanan 64-bit pada int128 memicu peringatan >>
  • Implementasi int128 secara internal bisa memakai shift kanan 63 bit untuk mengetahui tanda dari word 64-bit bagian atas
  • Jika Clang menambahkan dukungan seperti GCC untuk mengubah shift kanan 63 bit menjadi bool, lalu menjadi percabangan kondisional, banyak kode int128 bisa mendadak menjadi waktu variabel
    • Dalam kasus ini, situasinya mirip dengan yang diklaim judul makalah 2015, tetapi kali ini benar-benar bisa terjadi walaupun source tidak memiliki bool
  • Perlindungan termudah di tingkat source adalah menghindari implementasi int128 bawaan kompiler dan memakai fungsi crypto_int128
    • Tidak seperti int128 milik GCC dan Clang, crypto_int128 dapat bekerja juga pada platform 32-bit yang kecil
  • Gagasan menambahkan tipe data rahasia ke GCC dan Clang tampak bagus, tetapi dari struktur kedua kompiler itu sendiri belum terlihat cara membuatnya kokoh
  • Harapan lebih besar diletakkan pada kompiler yang sejak awal dirancang untuk keamanan
    • Ada kompiler berfokus keamanan yang membutuhkan bahasa input baru seperti FaCT dan Jasmin yang sedang aktif dikembangkan
    • Ada kekhawatiran soal waktu penulisan ulang kode, tetapi melihat cara kompiler saat ini menangani kode lama, tindakan dalam bentuk apa pun tampaknya memang diperlukan

1 komentar

 
GN⁺ 2024-08-05
Pendapat di Hacker News
  • Tidak tepat menyebutnya bug compiler ketika kode yang memiliki perilaku tak terdefinisi tidak berjalan sesuai keinginan
    Ini mirip seperti menjalankan dd dengan argumen yang salah lalu menghapus data, kemudian mengatakan dd punya bug

    • Sepertinya penulis di sini mencampuradukkan perilaku yang ditentukan implementasi dengan perilaku tak terdefinisi. Sebagian besar contoh dalam tulisan itu adalah kode yang valid, dan masalah sebenarnya adalah optimasi compiler yang mengubah aritmetika operasi bit menjadi percabangan, sehingga memungkinkan serangan timing pada kode kriptografi
      Sulit menyebut source code atau compiler sebagai bug; lebih tepat melihatnya sebagai standar C yang, menurut kriteria penulis, terlalu kurang terspesifikasi sehingga menimbulkan bug keamanan pada sebagian target
      Pada akhirnya para penyusun standar C tidak bisa mendefinisikan sampai perilaku hardware, hanya semantik bahasa, jadi bidang kriptografi mau tidak mau harus bergelut dengan bug yang bersumber dari hardware
    • Masalahnya adalah perilaku tak terdefinisi di C dan C++ jumlahnya tidak masuk akal banyak, dan sangat sulit menghindari semuanya
      Salah satu kelebihan Rust adalah membatasi potensi perilaku tak terdefinisi ke dalam blok unsafe. Meski begitu, walaupun Rust telah mendefinisikan banyak hal yang di C merupakan perilaku tak terdefinisi, begitu masuk ke kode unsafe, sangat mudah secara tidak sengaja menginjak perilaku tak terdefinisi yang subtil
    • Hanya ada dua model perilaku tak terdefinisi yang berguna bagi pengguna compiler: menolak kompilasi jika itu ide buruk, atau melakukan sesuatu yang masuk akal dan stabil
      Model ketiga, yaitu gagal diam-diam sambil menghasilkan kode yang tidak dapat diprediksi, hanya berguna bagi penulis compiler. Bersembunyi di balik spesifikasi tidak memberi manfaat bagi pengguna nyata
    • Tulisan Russ Cox C and C++ Prioritize Performance over Correctness membahas topik ini dengan baik: https://research.swtch.com/ub
    • Bantahan itu lebih mirip menyerang straw man. Intinya adalah para penulis compiler sendiri yang memutuskan apa itu perilaku tak terdefinisi, dan mendefinisikan standar agar mendapat lebih banyak ruang untuk optimasi
      Optimasi itu merusak kode yang sebelumnya berjalan baik. Para penulis compiler bisa saja memprioritaskan kompatibilitas mundur, tetapi mereka tidak melakukannya
      Selain itu, optimasi semacam ini juga tidak secara berarti meningkatkan performa kode nyata, jadi mereka perlu membantah argumen bahwa kompromi yang merusak kode itu tidak sepadan
  • Saya menyukai Bernstein, tetapi kadang ia salah arah dan menjadi terlalu ekstrem, dan tulisan ini adalah contoh yang baik. Di akhir tulisan, ia sendiri juga setengah mengakuinya
    Bagian besar tulisan itu adalah poin sampingan tentang seberapa bagus keuntungan optimasi, dan meski ada datanya, penilaiannya akan bergantung pada use case
    Keluhan utamanya adalah compiler C tidak mempertimbangkan semantik yang tidak bisa diekspresikan oleh bahasa tersebut, dan itu bukan hal yang mengejutkan
    Di bagian akhir ia mengatakan, “pakailah bahasa yang bisa mengekspresikan semantik yang dibutuhkan,” dan seluruh tulisan itu sebenarnya bisa digantikan oleh satu kalimat tersebut

    • Poin pentingnya adalah pihak yang mendefinisikan semantik C dan C++ memasukkan terlalu banyak perilaku ke dalam keranjang “perilaku tak terdefinisi”
      Banyak di antaranya landasannya meragukan, dan membuat penulisan program yang benar menjadi lebih sulit
    • Bagian bahwa keuntungan optimasi bergantung pada use case adalah konteks yang berguna, dan cukup membuka mata
    • Di sini DJB kurang meyakinkan. Banyak terlihat pandangan religius elitis yang tidak berdasar
  • C dan C++ tidak cocok untuk menulis algoritma yang memiliki jaminan waktu konstan
    Di standar hampir tidak ada konsep real-time, dan compiler juga tidak menyediakan jaminan tambahan lewat ekstensi
    Namun menyalahkan pengembang compiler untuk hal ini adalah arah yang keliru

    • Jika ingin membuat kode mesin yang selalu beroperasi dalam waktu konstan tanpa bergantung pada percabangan, harus memakai bahasa yang bisa mengekspresikan hal itu. C tidak mendukungnya
    • Saya penasaran bahasa apa yang cocok untuk menulis algoritma dengan jaminan waktu konstan
  • Pada CPU Intel, clang ataupun yang lain tidak bisa menghasilkan kode yang benar di mode pengguna. Karena sejak awal tidak ada kode yang benar
    https://www.intel.com/content/www/us/en/developer/articles/t...
    Jika melihat DOITM di dokumen tersebut, library kriptografi di user space sama sekali tidak mungkin menyetel bit yang diperlukan

    • Kode mode pengguna juga bisa dijalankan dalam mode yang benar. Hanya saja ia tidak bisa menyalakan dan mematikan toggle untuk mode itu secara langsung
      Setelah dinyalakan, mode itu berjalan baik bahkan di user space, jadi misalnya bisa dibuat sebagai flag per proses yang diaktifkan lewat system call prctl, lalu scheduler menyesuaikan MSR saat perpindahan task
    • Tidak bisakah melakukan system call ke kernel untuk menyetel flag, lalu kembali ke mode pengguna dalam keadaan itu?
  • Kalimat “setiap kali memungkinkan, para penulis compiler menolak bertanggung jawab atas bug yang mereka buat” saja sudah menunjukkan kasus yang jarang terjadi, ketika kredibilitas sebuah tulisan blog runtuh secepat ini
    Kalau mengikuti tautannya pun, itu hanya hal C yang sangat mendasar: perilaku tak terdefinisi bukan berarti menghasilkan “nilai sembarang”

    • Sepertinya mereka menyebut hal yang berbeda sebagai “bug”. Satu pihak berbicara tentang bug pada kode sumber, sementara pihak lain berbicara tentang bug pada program yang dihasilkan
      Meski ada perilaku tak terdefinisi, kode sumbernya memang bug, tetapi program yang dihasilkan sering kali tetap benar. Ketika belakangan penulis compiler memasukkan optimisasi baru dan, berdasarkan perilaku tak terdefinisi itu, menghasilkan program yang bermasalah, perdebatan soal tanggung jawab pun dimulai
      Bagian yang enggan diakui adalah bahwa tanggung jawab terhadap pengguna terbagi di semua pihak. Jika baterai terbakar hanya karena sebuah aplikasi CRUD melakukan dereference NULL, orang waras tidak akan hanya menyalahkan penulis aplikasi karena lupa memeriksa NULL
      Compiler, sistem operasi, dan vendor perangkat keras juga harus bertanggung jawab atas produk yang dirancang secara tidak bertanggung jawab; ini tidak selesai hanya dengan mengatakan “perilaku tak terdefinisi” menurut standar ISO. Semua anggota rantai pasok berbagi tanggung jawab untuk mengantisipasi bagaimana produk dapat disalahgunakan dan menanganinya secara wajar
    • Saya rasa penulis sangat memahami apa itu perilaku tak terdefinisi. Ia hanya melihat keseluruhan sistem secara kritis
      Perilaku tak terdefinisi ada untuk memberikan nilai. Bahasa bisa dibuat tanpanya, tetapi alasan ia tetap ada adalah portabilitas dan fleksibilitas yang diberikan kepada penulis compiler
      Inti tulisannya adalah apakah fleksibilitas itu memang bernilai jika dibandingkan dengan sulitnya menulis program tanpa perilaku tak terdefinisi
      Menurut penulis, uang yang hilang karena bug tampaknya lebih besar daripada uang yang dihemat lewat bytecode yang lebih cepat, dan karena penulis compiler punya pengaruh besar dalam menentukan isi standar bahasa, kemauan untuk memperbaiki hal ini lemah
  • Sebagai catatan, clang memiliki atribut clang::optnone untuk mematikan semua optimisasi per fungsi, dan GCC memiliki atribut gnu::optimize yang bagus, yang bisa menambah atau menghapus optimisasi berdasarkan nama, atau menetapkan level optimisasi terlepas dari flag compiler
    gnu::optimize(0) mirip dengan flag clang itu. clang juga memiliki clang::no_builtins, khusus untuk mematikan optimisasi memcpy dan memset

  • Saya cukup memahami tujuan yang diinginkan orang-orang kriptografi, misalnya evaluasi waktu konstan dan penyembunyian nilai rahasia
    Namun compiler serbaguna tidak memikirkan hal seperti itu hampir sepanjang waktu, jadi tampaknya sulit menjadi lebih dari sekadar hack yang umumnya bekerja
    Kalau ingin serius, sepertinya perlu compiler khusus sendiri, atau harus terus memakai assembly

  • Suatu saat nanti kita mungkin akan menganggap masa sekarang sebagai masa lalu yang buruk, dan sudah beranjak dari C ke bahasa dengan perilaku tak terdefinisi yang jauh lebih sedikit
    Di C, terlalu mudah menulis ekspresi yang bisa dikompilasi, tetapi niatnya sama sekali tidak bisa dipahami compiler
    Misalnya di Python, kita bisa menulis kode seperti result = [something(value) for value in set_object]. Karena objek set tidak memiliki urutan, jelas bahwa urutan pemrosesan item dan urutan hasil tidak penting, dan ini membuka banyak peluang optimasi di tingkat bahasa tanpa compiler harus menebak niat penulis
    Kode serupa dalam bahasa lain dengan data immutable melangkah lebih jauh: karena something(value1) tidak dapat memengaruhi something(value2), ia bisa dijalankan paralel, baik dengan thread maupun proses
    Sebagian besar optimasi compiler C adalah melihat pola kode lalu mencari cara mempercepat hal yang mungkin dimaksud penulis. Dibanding bahasa-bahasa modern, C kurang mampu mengekspresikan niat sehingga compiler memang punya kebebasan untuk menebak, tetapi untuk menghasilkan performa yang decent, inferensi semacam itu harus dilakukan
    Meski begitu, ini mungkin juga berkah terselubung, seperti ketika teleskop Hubble membutuhkan kacamata. Untuk mengatasi keterbatasan, teknik-teknik hebat dibuat, dan setelah masalahnya diperbaiki, teknik-teknik itu menghasilkan performa jauh lebih tinggi daripada perkiraan awal. Jika optimasi compiler C diterapkan ke bahasa non-C, mungkin ia akan bekerja seperti kekuatan super

    • Kekurangan contoh Python itu adalah meski urutannya tidak dispesifikasikan, orang bisa saja bergantung pada properti tertentu, dan jika optimizer mengubah urutannya, kodenya bisa rusak
      Pada dasarnya ini mirip perilaku tak terdefinisi, tetapi alih-alih langsung menjadi masalah keamanan, ia bisa muncul sebagai hasil yang salah. Tentu saja hasil yang salah nantinya bisa berujung pada masalah keamanan
      Berbeda dari perilaku tak terdefinisi, praktis mustahil membuat “sanitizer” yang memeriksa apakah kode bekerja untuk semua kemungkinan urutan set
      gcc dan clang punya banyak hint level rendah yang sering tidak ada di bahasa lain. Ada __builtin_expect/__builtin_unpredictable, __builtin_unreachable/__builtin_assume, #pragma clang loop vectorize(assume_safety)/#pragma GCC ivdep, pragma untuk mematikan loop unrolling atau vektorisasi, atau memilih nilai tertentu, dan sebagainya
      Menurut saya yang paling besar hilang adalah optimization barrier yang secara eksplisit mencegah compiler membuat inferensi berdasarkan asal-usul nilai. __asm__ sampai batas tertentu bisa, tetapi punya efek samping yang tidak diinginkan dan memerlukan nama jenis register yang spesifik platform
      Potensi optimasi tingkat tinggi berbasis niat juga jelas ada. Misalnya, memesan ruang array list sebelum melakukan push sebanyak n kali dalam loop, menggabungkan lookup hashmap yang melakukan contains→get→put dengan kunci yang sama, atau secara lokal menginferensi perilaku alokasi global untuk menghilangkan objek dan alokasi
    • Secara teori masuk akal, tetapi belum ada yang membuktikan bahwa itu lebih cepat daripada C dalam praktik
      C cukup dekat dengan hardware nyata sehingga programmer bisa langsung mengatakan apa yang harus dilakukan, jadi compiler tidak perlu menebak niat programmer
    • Memang ada ruang untuk optimasi berbasis semantik, tetapi dari pengamatan, optimasi seperti itu umumnya berada di sekitar alokasi memori
      Bahasa yang mengimplementasikan optimasi memori semacam itu umumnya sejenis Java, dan sejak awal sudah ada pre-pessimization yang agresif, sehingga muncul motivasi untuk melakukan optimasi tersebut. Namun bahkan optimasi itu pun tidak mampu menutup kerugiannya
      Intinya, C memang kurang bagus, tetapi sisi lain lebih buruk
  • Jika tidak suka semantik C, jangan marah kepada engineer compiler; pakailah bahasa pemrograman lain

    • Sejujurnya, saya tidak tahu apakah djb bisa tahan dengan apa pun selain qhasm miliknya sendiri. Bahkan Zig sekalipun. Penilaian kali ini tidak terlalu mengejutkan darinya
  • Tulisan yang menyegarkan karena menyampaikan sudut pandang yang jarang terdengar. Layak dibaca bersama: https://gavinhoward.com/2023/08/the-scourge-of-00ub/