Див. Також попередню версію цієї відповіді на інше запитання про обертання з деякими деталями про те, що створює asm gcc / clang для x86.
Найбільш зручним для компілятора способом вираження обертання в C та C ++, який дозволяє уникнути невизначеної поведінки, є реалізація Джона Регера . Я адаптував його для обертання на ширину типу (використовуючи типи фіксованої ширини, як 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 ));
}
Роботи для будь-якого цілого числа без знака типу, а не просто uint32_t
, щоб ви могли зробити версії для інших розмірів.
Див. Також версію шаблону C ++ 11 з безліччю перевірок безпеки (включаючи, static_assert
що ширина типу дорівнює 2) , що не стосується, наприклад, деяких 24-розрядних ЦСП або 36-розрядних мейнфреймів.
Я б рекомендував використовувати шаблон лише як фонове для обгортки з іменами, які явно включають ширину обертання. Правила цілочисельного просування означають, що rotl_template(u16 & 0x11UL, 7)
обертання буде здійснюватися на 32 або 64 біти, а не на 16 (залежно від ширини unsigned long
). Навіть uint16_t & uint16_t
просувається до signed int
правил цілочисельного просування C ++, за винятком платформ, де int
не ширше ніж uint16_t
.
У x86 ця версія вбудовується до одногоrol r32, cl
(або rol r32, imm8
) із компіляторами, які її переглядають, оскільки компілятор знає, що інструкції обертання x86 і зсуву x86 маскують підрахунок зсувів так само, як це робить джерело C.
Підтримка компілятора для цієї ідіоми, що уникає UB, на x86, для uint32_t x
і unsigned int n
для змін з рахунком змінних:
- clang: розпізнається для змінної кількості обертається з clang3.5, кілька змін + або insns до цього.
- gcc: розпізнано для змінної кількості обертається з gcc4.9 , кілька змін + або insns до цього. gcc5 та пізніших версій оптимізують гілку та маску у версії wikipedia, використовуючи лише інструкцію
ror
або rol
для вказівки на кількість змінних.
- icc: підтримується для обертання змінної кількості, починаючи з ICC13 або раніше . Постійний підрахунок обертає використання,
shld edi,edi,7
яке відбувається повільніше і займає більше байтів, ніж rol edi,7
на деяких центральних процесорах (особливо AMD, але також деяких Intel), коли BMI2 недоступний для rorx eax,edi,25
збереження MOV.
- MSVC: x86-64 CL19: Розпізнається лише для обертання з постійним відліком. (Ідіома Вікіпедії розпізнана, але гілка та І не оптимізовані). Використовуйте
_rotl
/ _rotr
intrinsics з <intrin.h>
x86 (включаючи x86-64).
GCC для ARM використовує and r1, r1, #31
для змінного кількості обертається, але по- , як і раніше робить фактичні обертатися з однією командою : ror r0, r0, r1
. Отже, gcc не усвідомлює, що відліки обертання за своєю суттю модульні. Як сказано в документах ARM, "ROR із довжиною зсуву n
, більше 32 - це те саме, що ROR із довжиною зсуву n-32
" . Я думаю, gcc тут плутається, тому що зсуви вліво / вправо на ARM насичують рахунок, тому зміщення на 32 або більше очистить реєстр. (На відміну від x86, де зрушення маскують відлік так само, як і обертання). Ймовірно, він вирішує, що йому потрібна інструкція І, перш ніж розпізнати ідіому обертання, через те, як некруглі зміни працюють на цю ціль.
Поточні компілятори x86 все ще використовують додаткову інструкцію, щоб замаскувати кількість змінних для 8 та 16-бітового обертання, ймовірно, з тієї ж причини, що вони не уникають І на ARM. Це пропущена оптимізація, оскільки продуктивність не залежить від кількості обертань на будь-якому процесорі x86-64. (Маскування підрахунків було введено з 286 з міркувань продуктивності, оскільки воно обробляло зміщення ітеративно, а не з постійною затримкою, як сучасні процесори.)
До речі, віддайте перевагу повороту вправо для поворотів із змінним відліком, щоб уникнути змушення компілятора 32-n
реалізувати поворот ліворуч на таких архітектурах, як ARM та MIPS, які забезпечують лише поворот вправо. (Це оптимізує з урахуванням констант часу підрахунку.)
Кумедний факт: ARM не дійсно має спеціальний зрушення / ротацію інструкції, це просто MOV з джерелом операнда відбувається через ствол перевертня в режимі ROR : mov r0, r0, ror r1
. Тож обертання може скластися в операнд із джерелом реєстру для інструкції EOR або чогось іншого.
Переконайтеся, що використовуєте непідписані типи для n
і поверненого значення, інакше воно не буде обертатися . (gcc для цілей x86 виконує арифметичні зрушення вправо, зсуваючи копії біта знака, а не нулі, що призводить до проблеми, коли ви OR
обидва значення зміщуєте разом. Правильні зсуви від’ємних знакових цілих чисел - це поведінка, визначена реалізацією в C.)
Крім того, переконайтеся, що кількість змін не є типом без підпису , оскільки (-n)&31
з підписаним типом це може бути доповнення або знак / величина, а не те саме, що модульний 2 ^ n, який ви отримуєте з доповненням без підпису або двох. (Див. Коментарі до повідомлення блогу Регера). unsigned int
добре працює на кожному компіляторі, який я розглядав, для кожної ширини x
. Деякі інші типи насправді перемагають розпізнавання ідіом для деяких компіляторів, тому не використовуйте просто той самий тип, що і x
.
Деякі компілятори забезпечують внутрішні функції для обертання , що набагато краще, ніж inline-asm, якщо портативна версія не створює хорошого коду на компіляторі, на який ви націлюєтеся. Для будь-яких компіляторів, яких я знаю, не існує внутрішніх характеристик між платформами. Ось деякі з варіантів x86:
- Документи Intel, що
<immintrin.h>
надає _rotl
і _rotl64
внутрішні характеристики , і те саме для зрушення вправо. MSVC вимагає <intrin.h>
, тоді як gcc вимагає <x86intrin.h>
. An #ifdef
опікується gcc проти icc, але, здається, clang-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
їх ніде не надає, крім режиму сумісності MSVC з . І промінь, який він видає для них, відстій (додаткове маскування та CMOV).
- MSVC:
_rotr8
і_rotr16
.
- gcc та icc (не clang):
<x86intrin.h>
також передбачає __rolb
/ __rorb
для 8-бітового повороту вліво / вправо, __rolw
/ __rorw
(16-біт), __rold
/ __rord
(32-біт), __rolq
/ __rorq
(64-біт, визначено лише для 64-бітних цілей). Для вузьких обертів реалізація використовує __builtin_ia32_rolhi
або ...qi
, але 32 та 64-розрядні повороти визначаються за допомогою shift / або (без захисту від UB, оскільки код у ia32intrin.h
повинен працювати лише на gcc для x86). Здається, GNU C не має жодних крос-платформних __builtin_rotate
функцій, як це робиться __builtin_popcount
(яка розширюється до оптимального на цільовій платформі, навіть якщо це не одна інструкція). Найчастіше ви отримуєте хороший код із розпізнавання ідіом.
// 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
Імовірно, деякі компілятори, що не належать до x86, також мають властивості, але давайте не будемо розширювати цю відповідь спільноти-wiki, щоб включити їх усіх. (Можливо, зробіть це в існуючій відповіді про внутрішні характеристики ).
(Стара версія цієї відповіді пропонувала вбудований asm для MSVC (який працює лише для 32-бітного коду x86) або http://www.devx.com/tips/Tip/14043 для версії C. Коментарі відповідають на це .)
Inline asm перемагає багато оптимізацій , особливо в стилі MSVC, оскільки змушує входи зберігатися / перезавантажуватися . Ретельно написане обертання вбудованого asm GNU C дозволило б підрахуванню бути безпосереднім операндом для підрахунку зсуву константи часу компіляції, але він все одно не міг повністю оптимізувати, якщо значення, яке потрібно зсунути, також є константою часу компіляції після вкладання. https://gcc.gnu.org/wiki/DontUseInlineAsm .