Vedi anche una versione precedente di questa risposta su un'altra domanda a rotazione con alcuni dettagli in più su ciò che asm gcc / clang produce per x86.
Il modo più adatto al compilatore per esprimere una rotazione in C e C ++ che evita qualsiasi comportamento indefinito sembra essere l'implementazione di John Regehr . L'ho adattato per ruotare in base alla larghezza del tipo (usando tipi a larghezza fissa come uint32_t
).
#include <stdint.h> // for uint32_t
#include <limits.h> // for CHAR_BIT
// #define NDEBUG
#include <assert.h>
static inline uint32_t rotl32 (uint32_t n, unsigned int c)
{
const unsigned int mask = (CHAR_BIT*sizeof(n) - 1); // assumes width is a power of 2.
// assert ( (c<=mask) &&"rotate by type width or more");
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);
// assert ( (c<=mask) &&"rotate by type width or more");
c &= mask;
return (n>>c) | (n<<( (-c)&mask ));
}
Funziona per qualsiasi tipo di numero intero senza segno, non solo uint32_t
, quindi puoi creare versioni per altre dimensioni.
Vedi anche una versione del modello C ++ 11 con molti controlli di sicurezza (incluso il fatto static_assert
che la larghezza del tipo è una potenza di 2) , il che non è il caso di alcuni DSP a 24 bit o mainframe a 36 bit, per esempio.
Consiglierei di utilizzare il modello solo come back-end per wrapper con nomi che includono esplicitamente la larghezza di rotazione. Le regole di promozione di numeri interi significano che rotl_template(u16 & 0x11UL, 7)
eseguirà una rotazione a 32 o 64 bit, non a 16 (a seconda della larghezza di unsigned long
). Even uint16_t & uint16_t
è promosso signed int
dalle regole di promozione dei numeri interi di C ++, tranne sulle piattaforme in cui int
non è più largo di uint16_t
.
Su x86 , questa versione è in linea con un singolorol r32, cl
(o rol r32, imm8
) con compilatori che la supportano, perché il compilatore sa che le istruzioni x86 rotate e shift mascherano lo shift-count allo stesso modo del sorgente C.
Supporto del compilatore per questo idioma che evita UB su x86, per uint32_t x
e unsigned int n
per turni di conteggio variabili:
- clang: riconosciuto per le rotazioni di conteggio variabili da clang3.5, più turni + o insns prima.
- gcc: riconosciuto per le rotazioni di conteggio variabili da gcc4.9 , più turni + o insns prima. gcc5 e versioni successive ottimizzano il ramo e la maschera anche nella versione di wikipedia, usando solo un'istruzione
ror
o rol
per i conteggi delle variabili.
- icc: supportato per le rotazioni di conteggio variabili da ICC13 o precedenti . Il conteggio costante ruota l'uso
shld edi,edi,7
che è più lento e richiede più byte rispetto rol edi,7
ad alcune CPU (specialmente AMD, ma anche alcune Intel), quando BMI2 non è disponibile per rorx eax,edi,25
salvare un MOV.
- MSVC: x86-64 CL19: riconosciuto solo per le rotazioni con conteggio costante. (L'idioma di wikipedia è riconosciuto, ma il ramo e AND non sono ottimizzati). Usa
_rotl
/ _rotr
intrinsics da <intrin.h>
x86 (incluso x86-64).
gcc per ARM utilizza una and r1, r1, #31
per ruota variabile count, ma ancora fa ruotare il reale con una sola istruzione : ror r0, r0, r1
. Quindi gcc non si rende conto che i conteggi delle rotazioni sono intrinsecamente modulari. Come dicono i documenti ARM, "ROR con lunghezza turno n
, più di 32 è uguale a ROR con lunghezza turno n-32
" . Penso che gcc venga confuso qui perché gli spostamenti sinistra / destra su ARM saturano il conteggio, quindi uno spostamento di 32 o più cancellerà il registro. (A differenza di x86, dove i turni mascherano il conteggio allo stesso modo delle rotazioni). Probabilmente decide di aver bisogno di un'istruzione AND prima di riconoscere l'idioma di rotazione, a causa di come funzionano gli spostamenti non circolari su quel target.
Gli attuali compilatori x86 usano ancora un'istruzione extra per mascherare un conteggio di variabili per rotazioni a 8 e 16 bit, probabilmente per lo stesso motivo per cui non evitano l'AND su ARM. Questa è un'ottimizzazione mancata, perché le prestazioni non dipendono dal conteggio delle rotazioni su qualsiasi CPU x86-64. (Il mascheramento dei conteggi è stato introdotto con 286 per motivi di prestazioni perché ha gestito i turni in modo iterativo, non con latenza costante come le CPU moderne.)
A proposito, preferisci la rotazione a destra per le rotazioni a conteggio variabile, per evitare che il compilatore 32-n
esegua una rotazione a sinistra su architetture come ARM e MIPS che forniscono solo una rotazione a destra. (Questo ottimizza i conteggi delle costanti del tempo di compilazione.)
Curiosità: ARM non ha davvero spostamento dedicato / operazioni di rotazione, è solo MOV con la fonte operando passare attraverso la canna-shifter in modalità ROR : mov r0, r0, ror r1
. Quindi una rotazione può trasformarsi in un operando sorgente di registro per un'istruzione EOR o qualcosa del genere.
Assicurati di utilizzare tipi senza segno per n
e il valore restituito, altrimenti non sarà una rotazione . (gcc per i target x86 esegue spostamenti aritmetici a destra, spostandosi in copie del bit di segno anziché in zero, causando un problema quando si OR
spostano i due valori insieme. Gli spostamenti a destra di interi con segno negativo è un comportamento definito dall'implementazione in C.)
Inoltre, assicurati che il conteggio dello spostamento sia un tipo senza segno , perché (-n)&31
con un tipo con segno potrebbe essere il complemento o il segno / grandezza di uno, e non lo stesso del 2 ^ n modulare che ottieni con il complemento a due o senza segno. (Vedi i commenti sul post del blog di Regehr). unsigned int
funziona bene su ogni compilatore che ho visto, per ogni larghezza di x
. Alcuni altri tipi effettivamente sconfiggono il riconoscimento del linguaggio per alcuni compilatori, quindi non usare solo lo stesso tipo di x
.
Alcuni compilatori forniscono elementi intrinseci per le rotazioni , che è di gran lunga migliore di inline-asm se la versione portatile non genera un buon codice sul compilatore che stai prendendo di mira. Non ci sono elementi intrinseci multipiattaforma per nessun compilatore che io conosca. Queste sono alcune delle opzioni x86:
- Documenti Intel che
<immintrin.h>
forniscono _rotl
e _rotl64
intrinseci , e lo stesso per il giusto turno. MSVC richiede <intrin.h>
, mentre gcc richiede <x86intrin.h>
. An si #ifdef
prende cura di gcc contro icc, ma clang non sembra fornirli da nessuna parte, tranne che in modalità di compatibilità MSVC con-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. E l'asm che emette per loro fa schifo (mascheramento extra e un CMOV).
- MSVC:
_rotr8
e_rotr16
.
- gcc e icc (non clang):
<x86intrin.h>
fornisce anche __rolb
/ __rorb
per la rotazione a sinistra / destra a 8 bit, __rolw
/ __rorw
(16-bit), __rold
/ __rord
(32-bit), __rolq
/ __rorq
(64-bit, definito solo per target a 64-bit). Per le rotazioni strette, l'implementazione usa __builtin_ia32_rolhi
o ...qi
, ma le rotazioni a 32 e 64 bit sono definite usando shift / o (senza protezione contro UB, perché il codice in ia32intrin.h
deve funzionare solo su gcc per x86). GNU C sembra non avere alcuna __builtin_rotate
funzione multipiattaforma come fa per __builtin_popcount
(che si espande a tutto ciò che è ottimale sulla piattaforma di destinazione, anche se non è una singola istruzione). Il più delle volte ottieni un buon codice dal riconoscimento del linguaggio.
// For real use, probably use a rotate intrinsic for MSVC, or this idiom for other compilers. This pattern of #ifdefs may be helpful
#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 __builtin_ia32_rorhi(x, 7); // 16-bit rotate, GNU C
return _rotl(x, n); // gcc, icc, msvc. Intel-defined.
//return __rold(x, n); // gcc, icc.
// can't find anything for clang
}
#endif
Presumibilmente anche alcuni compilatori non x86 hanno elementi intrinseci, ma non espandiamo questa risposta wiki della comunità per includerli tutti. (Forse fallo nella risposta esistente sugli intrinseci ).
(La vecchia versione di questa risposta suggeriva asm inline specifico per MSVC (che funziona solo per codice x86 a 32 bit), o http://www.devx.com/tips/Tip/14043 per una versione C. I commenti stanno rispondendo a questo .)
Inline asm sconfigge molte ottimizzazioni , soprattutto in stile MSVC perché forza l'archiviazione / ricarica degli input . Una rotazione inline-asm GNU C scritta con cura consentirebbe al conteggio di essere un operando immediato per i conteggi di spostamento costanti del tempo di compilazione, ma non potrebbe comunque essere ottimizzato completamente se il valore da spostare è anche una costante del tempo di compilazione dopo l'inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm .