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_assert
bahwa 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_t
dipromosikan signed int
oleh aturan integer-promotion C ++, kecuali pada platform int
yang 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 x
dan unsigned int n
untuk 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
ror
atau rol
untuk jumlah variabel.
- icc: didukung untuk putaran hitung variabel sejak ICC13 atau sebelumnya . Penggunaan rotasi hitungan konstan
shld edi,edi,7
yang lebih lambat dan membutuhkan lebih banyak byte daripada rol edi,7
pada beberapa CPU (terutama AMD, tetapi juga beberapa Intel), ketika BMI2 tidak tersedia untuk rorx eax,edi,25
menyimpan MOV.
- MSVC: x86-64 CL19: Hanya dikenali untuk putaran hitungan konstan. (Idiom wikipedia dikenali, tetapi cabang dan AND tidak dioptimalkan). Gunakan
_rotl
/ _rotr
intrinsics dari <intrin.h>
pada x86 (termasuk x86-64).
gcc untuk ARM menggunakan and r1, r1, #31
untuk 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-n
menerapkan 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 n
dan 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 OR
menggeser 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)&31
dengan 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 int
bekerja 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 _rotl
dan _rotl64
intrinsik , dan sama untuk shift kanan. MSVC membutuhkan <intrin.h>
, sedangkan gcc membutuhkan <x86intrin.h>
. An #ifdef
menangani 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:
_rotr8
dan_rotr16
.
- gcc dan icc (bukan clang):
<x86intrin.h>
juga menyediakan __rolb
/ __rorb
untuk 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_rolhi
atau ...qi
, tetapi rotasi 32 dan 64-bit ditentukan menggunakan shift / atau (tanpa perlindungan terhadap UB, karena kode di ia32intrin.h
hanya harus bekerja di gcc untuk x86). GNU C tampaknya tidak memiliki __builtin_rotate
fungsi 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 .