Lihat juga versi sebelumnya dari jawaban ini pada pertanyaan rotasi lainnya dengan beberapa detail lebih lanjut tentang apa yang diproduksi asm gcc / clang untuk x86.
Cara paling ramah kompiler untuk mengekspresikan rotate dalam C dan C ++ yang menghindari Perilaku Tidak Terdefinisi tampaknya adalah implementasi John Regehr . Saya telah menyesuaikannya untuk memutar dengan lebar tipe (menggunakan tipe lebar tetap seperti uint32_t).
#include <stdint.h> // for uint32_t
#include <limits.h> // for CHAR_BIT
#include <assert.h>
static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);
c &= mask;
return (n<<c) | (n>>( (-c)&mask ));
}
static inline uint32_t rotr32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1);
c &= mask;
return (n>>c) | (n<<( (-c)&mask ));
}
Berfungsi untuk semua jenis bilangan bulat unsigned, tidak hanya uint32_t, jadi Anda dapat membuat versi untuk ukuran lain.
Lihat juga versi template C ++ 11 dengan banyak pemeriksaan keamanan (termasuk static_assertbahwa lebar tipe adalah pangkat 2) , yang tidak terjadi pada beberapa DSP 24-bit atau mainframe 36-bit, misalnya.
Saya akan merekomendasikan hanya menggunakan template sebagai back-end untuk pembungkus dengan nama yang menyertakan lebar putar secara eksplisit. Aturan promosi integer berarti bahwa rotl_template(u16 & 0x11UL, 7)akan melakukan rotasi 32 atau 64-bit, bukan 16 (bergantung pada lebarnya unsigned long). Even uint16_t & uint16_tdipromosikan signed intoleh aturan integer-promotion C ++, kecuali pada platform intyang tidak lebih luas dari uint16_t.
Pada x86 , versi ini sejajar dengan saturol r32, cl (atau rol r32, imm8) dengan kompiler yang melakukannya, karena kompilator tahu bahwa instruksi rotate dan shift x86 menutupi jumlah shift dengan cara yang sama seperti yang dilakukan sumber C.
Dukungan kompiler untuk idiom penghindaran UB ini pada x86, untuk uint32_t xdan unsigned int nuntuk perubahan jumlah variabel:
- dentang: dikenali untuk putaran hitung variabel sejak dentang3.5, beberapa shift + atau insns sebelum itu.
- gcc: dikenali untuk putaran hitung variabel sejak gcc4.9 , beberapa shift + atau insns sebelumnya. gcc5 dan yang lebih baru juga mengoptimalkan cabang dan mask di versi wikipedia, hanya dengan menggunakan instruksi
roratau roluntuk jumlah variabel.
- icc: didukung untuk putaran hitung variabel sejak ICC13 atau sebelumnya . Penggunaan rotasi hitungan konstan
shld edi,edi,7yang lebih lambat dan membutuhkan lebih banyak byte daripada rol edi,7pada beberapa CPU (terutama AMD, tetapi juga beberapa Intel), ketika BMI2 tidak tersedia untuk rorx eax,edi,25menyimpan MOV.
- MSVC: x86-64 CL19: Hanya dikenali untuk putaran hitungan konstan. (Idiom wikipedia dikenali, tetapi cabang dan AND tidak dioptimalkan). Gunakan
_rotl/ _rotrintrinsics dari <intrin.h>pada x86 (termasuk x86-64).
gcc untuk ARM menggunakan and r1, r1, #31untuk berputar variabel-hitung, tapi masih melakukan rotate yang sebenarnya dengan instruksi tunggal : ror r0, r0, r1. Jadi gcc tidak menyadari bahwa jumlah rotasi pada dasarnya bersifat modular. Seperti yang dikatakan dokumen ARM, “ROR dengan panjang shift n,, lebih dari 32 sama dengan ROR dengan panjang shift n-32” . Saya pikir gcc menjadi bingung di sini karena pergeseran kiri / kanan pada ARM memenuhi hitungan, jadi pergeseran sebesar 32 atau lebih akan menghapus register. (Tidak seperti x86, di mana shift mask hitungannya sama dengan rotates). Ia mungkin memutuskan bahwa ia membutuhkan instruksi AND sebelum mengenali idiom rotate, karena cara kerja non-circular shift pada target tersebut.
Kompiler x86 saat ini masih menggunakan instruksi tambahan untuk menutupi jumlah variabel untuk putaran 8 dan 16-bit, mungkin karena alasan yang sama mereka tidak menghindari AND pada ARM. Ini adalah pengoptimalan yang terlewat, karena performa tidak bergantung pada jumlah rotasi pada CPU x86-64 mana pun. (Penyembunyian hitungan diperkenalkan dengan 286 untuk alasan kinerja karena menangani pergeseran secara berulang, bukan dengan latensi konstan seperti CPU modern.)
BTW, lebih memilih rotate-right untuk variabel-count rotates, untuk menghindari compiler yang 32-nmenerapkan rotasi kiri pada arsitektur seperti ARM dan MIPS yang hanya menyediakan rotate-right. (Ini mengoptimalkan dengan penghitungan konstanta waktu kompilasi.)
Fun Fakta: ARM tidak benar-benar memiliki dedicated pergeseran / petunjuk rotate, itu hanya MOV dengan sumber operan melalui laras-shifter dalam modus ROR : mov r0, r0, ror r1. Jadi rotate bisa dilipat menjadi operand sumber register untuk instruksi EOR atau sesuatu.
Pastikan Anda menggunakan tipe unsigned untuk ndan nilai kembaliannya, jika tidak maka tidak akan berputar . (gcc untuk target x86 melakukan pergeseran kanan aritmatika, menggeser salinan bit-tanda daripada nol, yang mengarah ke masalah ketika Anda ORmenggeser dua nilai bersama-sama. Pergeseran kanan bilangan bulat bertanda negatif adalah perilaku yang ditentukan implementasi di C.)
Juga, pastikan hitungan shift adalah tipe unsigned , karena (-n)&31dengan tipe yang ditandatangani bisa menjadi pelengkap atau tanda / besaran, dan tidak sama dengan 2 ^ n modular yang Anda dapatkan dengan komplemen unsigned atau two. (Lihat komentar di posting blog Regehr). unsigned intbekerja dengan baik pada setiap kompiler yang saya lihat, untuk setiap lebarnya x. Beberapa tipe lain benar-benar mengalahkan pengenalan idiom untuk beberapa kompiler, jadi jangan hanya menggunakan tipe yang sama seperti x.
Beberapa kompiler menyediakan intrinsik untuk rotasi , yang jauh lebih baik daripada inline-asm jika versi portabel tidak menghasilkan kode yang baik pada kompilator yang Anda targetkan. Tidak ada intrinsik lintas platform untuk kompiler apa pun yang saya ketahui. Ini adalah beberapa opsi x86:
- Dokumen Intel yang
<immintrin.h>menyediakan _rotldan _rotl64intrinsik , dan sama untuk shift kanan. MSVC membutuhkan <intrin.h>, sedangkan gcc membutuhkan <x86intrin.h>. An #ifdefmenangani gcc vs. icc, tetapi clang tampaknya tidak menyediakannya di mana pun, kecuali dalam mode kompatibilitas MSVC dengan-fms-extensions -fms-compatibility -fms-compatibility-version=17.00 . Dan asm yang dipancarkannya menyebalkan (masking ekstra dan CMOV).
- MSVC:
_rotr8dan_rotr16 .
- gcc dan icc (bukan clang):
<x86intrin.h>juga menyediakan __rolb/ __rorbuntuk putar 8-bit kiri / kanan, __rolw/ __rorw(16-bit), __rold/ __rord(32-bit), __rolq/ __rorq(64-bit, hanya ditentukan untuk target 64-bit). Untuk rotasi sempit, implementasi menggunakan __builtin_ia32_rolhiatau ...qi, tetapi rotasi 32 dan 64-bit ditentukan menggunakan shift / atau (tanpa perlindungan terhadap UB, karena kode di ia32intrin.hhanya harus bekerja di gcc untuk x86). GNU C tampaknya tidak memiliki __builtin_rotatefungsi lintas platform seperti yang dilakukannya __builtin_popcount(yang berkembang menjadi apa pun yang optimal pada platform target, meskipun itu bukan satu instruksi). Sebagian besar waktu Anda mendapatkan kode yang bagus dari pengenalan idiom.
#if defined(__x86_64__) || defined(__i386__)
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h> // Not just <immintrin.h> for compilers other than icc
#endif
uint32_t rotl32_x86_intrinsic(rotwidth_t x, unsigned n) {
return _rotl(x, n);
}
#endif
Agaknya beberapa kompiler non-x86 memiliki intrinsik juga, tapi jangan memperluas jawaban wiki-komunitas ini untuk memasukkan semuanya. (Mungkin lakukan itu dalam jawaban yang ada tentang intrinsik ).
(Versi lama dari jawaban ini menyarankan asm sebaris khusus MSVC (yang hanya berfungsi untuk kode 32bit x86), atau http://www.devx.com/tips/Tip/14043 untuk versi C. Komentar tersebut membalasnya .)
Asm sebaris mengalahkan banyak pengoptimalan , terutama gaya MSVC karena memaksa masukan untuk disimpan / dimuat ulang . Sebuah GNU C inline-asm rotate yang ditulis dengan hati-hati akan memungkinkan penghitungan menjadi operan langsung untuk penghitungan shift konstan waktu kompilasi, tetapi masih tidak dapat mengoptimalkan sepenuhnya jika nilai yang akan digeser juga merupakan konstanta waktu kompilasi setelah sebaris. https://gcc.gnu.org/wiki/DontUseInlineAsm .