Siehe auch eine frühere Version dieser Antwort zu einer anderen Rotationsfrage mit einigen weiteren Details darüber, was asm gcc / clang für x86 produziert.
Die compilerfreundlichste Art, eine Rotation in C und C ++ auszudrücken, die undefiniertes Verhalten vermeidet, scheint die Implementierung von John Regehr zu sein . Ich habe es so angepasst, dass es sich um die Breite des Typs dreht (unter Verwendung von Typen mit fester Breite wie 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 ));
}
Funktioniert für jeden vorzeichenlosen Integer-Typ, nicht nur uint32_t
, sodass Sie Versionen für andere Größen erstellen können.
Siehe auch eine C ++ 11-Vorlagenversion mit vielen Sicherheitsüberprüfungen (einschließlich der Tatsache, static_assert
dass die Typbreite eine Potenz von 2 ist) , was beispielsweise bei einigen 24-Bit-DSPs oder 36-Bit-Mainframes nicht der Fall ist.
Ich würde empfehlen, die Vorlage nur als Back-End für Wrapper mit Namen zu verwenden, die die Drehbreite explizit enthalten. Integer-Promotion-Regeln bedeuten, dass rotl_template(u16 & 0x11UL, 7)
eine 32- oder 64-Bit-Drehung durchgeführt wird, nicht 16 (abhängig von der Breite von unsigned long
). Even uint16_t & uint16_t
wird signed int
durch die Ganzzahl-Heraufstufungsregeln von C ++ hochgestuft, außer auf Plattformen, auf denen int
nicht breiter als ist uint16_t
.
Unter x86 wird diese Version in eine einzelnerol r32, cl
(oder rol r32, imm8
) Version mit Compilern eingefügt, die sie bearbeiten, da der Compiler weiß, dass x86-Anweisungen zum Drehen und Verschieben die Anzahl der Verschiebungen genauso maskieren wie die C-Quelle.
Compiler-Unterstützung für diese UB-vermeidende Redewendung auf x86 für uint32_t x
und unsigned int n
für Verschiebungen mit variabler Anzahl:
- clang: erkannt für variable Anzahl dreht sich seit clang3.5, mehrere Verschiebungen + oder insns davor.
- gcc: erkannt für variable Anzahl dreht sich seit gcc4.9 , mehrere Verschiebungen + oder insns davor. gcc5 und später optimieren Sie den Zweig und die Maske auch in der Wikipedia-Version, indem Sie nur eine
ror
oder- rol
Anweisung für die Anzahl der Variablen verwenden.
- icc: unterstützt für Rotationen mit variabler Anzahl seit ICC13 oder früher . Die konstante Anzahl dreht die Verwendung,
shld edi,edi,7
was langsamer ist und mehr Bytes benötigt als rol edi,7
bei einigen CPUs (insbesondere AMD, aber auch bei einigen Intel), wenn BMI2 nicht rorx eax,edi,25
zum Speichern eines MOV verfügbar ist .
- MSVC: x86-64 CL19: Nur für Rotationen mit konstanter Anzahl erkannt. (Die Wikipedia-Sprache wird erkannt, aber der Zweig und AND werden nicht optimiert). Verwenden Sie die
_rotl
/ _rotr
intrinsics von <intrin.h>
x86 (einschließlich x86-64).
gcc for ARM verwendet eine and r1, r1, #31
Rotation für variable Anzahl, führt jedoch die eigentliche Rotation mit einer einzigen Anweisung aus : ror r0, r0, r1
. Gcc erkennt also nicht, dass Rotationszählungen von Natur aus modular sind. In den ARM-Dokumenten heißt es: "ROR mit Schichtlänge n
, mehr als 32 sind gleich ROR mit Schichtlänge n-32
" . Ich denke, gcc wird hier verwirrt, weil Links- / Rechtsverschiebungen auf ARM die Anzahl sättigen, sodass eine Verschiebung um 32 oder mehr das Register löscht. (Im Gegensatz zu x86, bei dem Verschiebungen die Anzahl genauso maskieren wie beim Drehen). Es entscheidet wahrscheinlich, dass es eine UND-Anweisung benötigt, bevor es die Rotationssprache erkennt, da nicht kreisförmige Verschiebungen auf dieses Ziel wirken.
Aktuelle x86-Compiler verwenden immer noch einen zusätzlichen Befehl, um eine Variablenanzahl für 8- und 16-Bit-Rotationen zu maskieren, wahrscheinlich aus dem gleichen Grund, aus dem sie das UND auf ARM nicht vermeiden. Dies ist eine verpasste Optimierung, da die Leistung nicht von der Anzahl der Rotationen auf einer x86-64-CPU abhängt. (Die Maskierung von Zählungen wurde aus Leistungsgründen mit 286 eingeführt, da Verschiebungen iterativ und nicht wie bei modernen CPUs mit konstanter Latenz behandelt wurden.)
Übrigens, bevorzugen Sie Rotation nach rechts für Rotationen mit variabler Anzahl, um zu vermeiden, dass der Compiler 32-n
bei Architekturen wie ARM und MIPS, die nur eine Rotation nach rechts bereitstellen, eine Rotation nach links implementiert. (Dies optimiert die Anzahl der Kompilierungszeitkonstanten.)
Unterhaltsame Tatsache: ARM verfügt nicht über spezielle Shift / Rotate-Anweisungen, sondern nur über MOV, wobei der Quelloperand im ROR-Modus durch den Barrel-Shifter läuft : mov r0, r0, ror r1
. So kann eine Drehung für einen EOR-Befehl oder etwas in einen Registerquellenoperanden gefaltet werden.
Stellen Sie sicher, dass Sie vorzeichenlose Typen für n
und den Rückgabewert verwenden, da dies sonst keine Drehung ist . (gcc für x86-Ziele führt arithmetische Rechtsverschiebungen durch, wobei Kopien des Vorzeichenbits anstelle von Nullen OR
verschoben werden , was zu einem Problem führt, wenn Sie die beiden Werte zusammen verschieben. Rechtsverschiebungen von Ganzzahlen mit negativem Vorzeichen sind ein implementierungsdefiniertes Verhalten in C.)
Auch stellen Sie sicher , die Verschiebungszahl ein Typ ohne Vorzeichen ist , weil (-n)&31
mit einem signierten Typ könnte Einerkomplement oder Zeichen / Größe, und nicht das gleiche wie die modularen 2 ^ n Sie mit unsigned oder Zweier-Komplement zu bekommen sein. (Siehe Kommentare zu Regehrs Blogbeitrag). unsigned int
funktioniert gut auf jedem Compiler, den ich mir angesehen habe, für jede Breite von x
. Einige andere Typen besiegen tatsächlich die Redewendungserkennung für einige Compiler. Verwenden Sie also nicht einfach den gleichen Typ wie x
.
Einige Compiler bieten Intrinsics für Rotationen , was weitaus besser ist als Inline-Asm, wenn die tragbare Version keinen guten Code auf dem Compiler generiert, auf den Sie abzielen. Es gibt keine plattformübergreifenden Eigenschaften für Compiler, die mir bekannt sind. Dies sind einige der x86-Optionen:
- Intel-Dokumente,
<immintrin.h>
die _rotl
und _rotl64
intrinsics bereitstellen , und dasselbe für die Rechtsverschiebung. MSVC erfordert <intrin.h>
, während gcc erfordern <x86intrin.h>
. A #ifdef
kümmert sich um gcc vs. icc, aber clang scheint sie nirgendwo zu bieten, außer im MSVC-Kompatibilitätsmodus mit-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
. Und der Asm, den es für sie ausstrahlt, ist zum Kotzen (zusätzliche Maskierung und ein CMOV).
- MSVC:
_rotr8
und_rotr16
.
- gcc und icc (nicht klirrend):
<x86intrin.h>
Bietet auch __rolb
/ __rorb
für 8-Bit-Drehung nach links / rechts, __rolw
/ __rorw
(16-Bit), __rold
/ __rord
(32-Bit), __rolq
/ __rorq
(64-Bit, nur für 64-Bit-Ziele definiert). Für enge Drehungen verwendet die Implementierung __builtin_ia32_rolhi
oder ...qi
, aber die 32- und 64-Bit-Drehungen werden mit shift / oder definiert (ohne Schutz gegen UB, da der Code in ia32intrin.h
nur für x86 auf gcc funktionieren muss). GNU C scheint keine plattformübergreifenden __builtin_rotate
Funktionen zu haben, wie es funktioniert __builtin_popcount
(was sich auf das ausdehnt, was auf der Zielplattform optimal ist, auch wenn es sich nicht um eine einzelne Anweisung handelt). Meistens erhalten Sie guten Code durch die Erkennung von Redewendungen.
// 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
Vermutlich haben auch einige Nicht-x86-Compiler Eigenschaften, aber erweitern wir diese Community-Wiki-Antwort nicht, um sie alle einzuschließen. (Vielleicht tun Sie das in der vorhandenen Antwort über Intrinsics ).
(Die alte Version dieser Antwort schlug MSVC-spezifischen Inline-Asm vor (der nur für 32-Bit-x86-Code funktioniert) oder http://www.devx.com/tips/Tip/14043 für eine C-Version. Die Kommentare antworten darauf .)
Inline-ASM besiegt viele Optimierungen , insbesondere im MSVC-Stil, da Eingaben gespeichert / neu geladen werden müssen . Eine sorgfältig geschriebene GNU C-Inline-Asm-Drehung würde es dem Zähler ermöglichen, ein sofortiger Operand für Verschiebungszählungen zur Kompilierungszeitkonstante zu sein, aber es könnte immer noch nicht vollständig optimiert werden, wenn der zu verschiebende Wert auch eine Kompilierungszeitkonstante ist nach dem Inlining. https://gcc.gnu.org/wiki/DontUseInlineAsm .