X86 के लिए asm gcc / clang का क्या उत्पादन होता है, इस बारे में कुछ और विवरणों के साथ एक अन्य घुमाएँ प्रश्न पर इस उत्तर के पहले संस्करण को भी देखें ।
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
) होगा। यहां तक कि C ++ के पूर्णांक-प्रचार नियमों द्वारा uint16_t & uint16_t
प्रचारित किया signed int
जाता है, उन प्लेटफार्मों को छोड़कर, जहां int
से अधिक व्यापक नहीं है uint16_t
।
X86 पर , यह संस्करण संकलक के साथ एकलrol r32, cl
(या rol r32, imm8
) में इनलाइन करता है, क्योंकि यह संकलक है, क्योंकि संकलक जानता है कि x86 घुमाएँ और शिफ्ट निर्देश सी-सोर्स करता है उसी तरह से शिफ्ट-काउंट को मास्क करें।
86, के लिए पर इस यूबी-परहेज मुहावरा के लिए संकलक समर्थन uint32_t x
और unsigned int n
चर-गिनती बदलाव के लिए:
- क्लैंग: क्लैंग 3.5 के बाद से वेरिएबल-काउंट रोटेट के लिए पहचाना जाता है, इससे पहले कई शिफ्ट + या इंसन्स।
- gcc: gcc4.9 के बाद से वेरिएबल-काउंट रोटेट के लिए पहचाना जाता है , उससे पहले कई शिफ्ट + या इंसन्स। gcc5 और बाद में विकिपीडिया संस्करण में शाखा और मुखौटा को अनुकूलित करें, भी, चर गणना के लिए सिर्फ एक
ror
या rol
निर्देश का उपयोग करके ।
- आईसीसी: ICC13 या इससे पहले के बाद से चर-गिनती घूमता है के लिए समर्थित । लगातार गिनती रोटेट का उपयोग
shld edi,edi,7
करती है जो धीमा है और rol edi,7
कुछ सीपीयू (विशेष रूप से एएमडी, लेकिन कुछ इंटेल) की तुलना में अधिक बाइट्स लेता है , जब बीएमवी 2 एक एमओवी rorx eax,edi,25
को बचाने के लिए उपलब्ध नहीं है ।
- MSVC: x86-64 CL19: केवल निरंतर-गिनती रोटेट के लिए पहचाना जाता है। (विकिपीडिया मुहावरे को मान्यता दी गई है, लेकिन शाखा और इसे दूर नहीं किया गया है)। X86 पर (x86-64 सहित) से
_rotl
/ _rotr
आंतरिक का उपयोग करें <intrin.h>
।
एआरएम के लिए जीसीसी एक का उपयोग करता है and r1, r1, #31
चर गिनती घूमता है के लिए, लेकिन अभी भी एक एकल अनुदेश के साथ वास्तविक घुमाने करता है : ror r0, r0, r1
। तो जीसीसी को यह एहसास नहीं होता है कि रोटेट-काउंट स्वाभाविक रूप से मॉड्यूलर हैं। जैसा कि एआरएम डॉक्स कहते हैं, "शिफ्ट की लंबाई के साथ आरओआर, n
32 से अधिक शिफ्ट लंबाई के साथ आरओआर के समान है n-32
" । मुझे लगता है कि जीएमसी यहां उलझन में है क्योंकि एआरएम पर बाएं / दाएं बदलाव गिनती को संतृप्त करते हैं, इसलिए 32 या अधिक की शिफ्ट से रजिस्टर साफ हो जाएगा। (X86 के विपरीत, जहां बदलाव मुखौटा को घुमाए जाने के समान है)। यह संभवत: यह तय करता है कि घुमाए जाने वाले मुहावरे को पहचानने से पहले इसे एक निर्देश की जरूरत है, क्योंकि गैर-परिपत्र बदलाव उस लक्ष्य पर कैसे काम करते हैं।
वर्तमान x86 कंपाइलर अभी भी 8 और 16-बिट रोटेट के लिए एक वैरिएबल काउंट को मास्क करने के लिए एक अतिरिक्त निर्देश का उपयोग करते हैं, शायद उसी कारण से वे ARM पर AND से नहीं बचते हैं। यह एक चूक अनुकूलन है, क्योंकि प्रदर्शन किसी भी x86-64 सीपीयू पर रोटेट काउंट पर निर्भर नहीं करता है। (परफॉर्मेंस कारणों से काउंट्स की मास्किंग को 286 के साथ पेश किया गया था, क्योंकि यह चलने-फिरने में बदलाव करता है, आधुनिक सीपीयू जैसे निरंतर-विलंबता के साथ नहीं।)
BTW, वैरिएबल-काउंट रोटेट के लिए रोटेट-राईट को प्राथमिकता देते हैं, कम्पेर करने से बचने के 32-n
लिए ARM और MIPS जैसे आर्किटेक्चर पर लेफ्ट रोटेट को लागू करने के लिए करते हैं जो केवल रोटेट-राइट प्रदान करते हैं। (यह संकलन-समय-निरंतर गणना के साथ दूर हो जाता है।)
मज़ेदार तथ्य: एआरएम वास्तव में समर्पित पारी / घुमाएँ निर्देश नहीं है, इसके साथ सिर्फ MOV है स्रोत संकार्य ROR मोड में प्रति बैरल-शिफ्टर से गुजर रही : mov r0, r0, ror r1
। तो एक बारी बारी से एक ईओआर निर्देश या कुछ के लिए एक रजिस्टर-सोर्स ऑपरेंड में बदल सकता है।
सुनिश्चित करें कि आप के लिए अहस्ताक्षरित प्रकार n
और वापसी मूल्य का उपयोग करते हैं, अन्यथा यह एक घुमाव नहीं होगा । (x86 लक्ष्य के लिए जीसी अंकगणित सही बदलाव करता है, शून्य के बजाय साइन-बिट की प्रतियों में शिफ्टिंग, एक समस्या के लिए अग्रणी है जब आप OR
दो मानों को एक साथ स्थानांतरित करते हैं। नकारात्मक हस्ताक्षरित पूर्णांकों के राइट-शिफ्ट कार्यान्वयन सी में परिभाषित व्यवहार है।)
यह भी सुनिश्चित करें कि शिफ्ट काउंट एक अहस्ताक्षरित प्रकार है , क्योंकि (-n)&31
एक हस्ताक्षरित प्रकार के साथ एक का पूरक या संकेत / परिमाण हो सकता है, और मॉड्यूलर 2 ^ n के समान नहीं है जो आपको अहस्ताक्षरित या दो के पूरक के साथ मिलता है। (रेगर की ब्लॉग पोस्ट पर टिप्पणियां देखें)। unsigned int
हर कंपाइलर पर अच्छी तरह से करता हूं, जिसकी हर चौड़ाई पर मैंने गौर किया है x
। कुछ अन्य प्रकार वास्तव में कुछ संकलक के लिए मुहावरे-मान्यता को पराजित करते हैं, इसलिए केवल उसी प्रकार का उपयोग न करें जैसा कि x
।
कुछ संकलक घुमाने के लिए आंतरिक प्रदान करते हैं , जो कि इनलाइन-एएसएम की तुलना में कहीं बेहतर है यदि पोर्टेबल संस्करण आपके द्वारा लक्षित टारगेट पर अच्छा कोड उत्पन्न नहीं करता है। किसी भी संकलक के लिए क्रॉस-प्लेटफ़ॉर्म इंट्रिंसिक्स नहीं हैं जो मुझे पता है। ये x86 विकल्पों में से कुछ हैं:
- इंटेल दस्तावेज़ जो
<immintrin.h>
प्रदान करता है _rotl
और _rotl64
आंतरिक , और सही बदलाव के लिए समान है। MSVC की आवश्यकता होती है <intrin.h>
, जबकि gcc की आवश्यकता होती है <x86intrin.h>
। एक #ifdef
जीसीसी बनाम आईसीसी का ख्याल रखता है, लेकिन बजना, उन्हें कहीं भी प्रदान करने के लिए प्रतीत नहीं होता है के साथ MSVC संगतता मोड में छोड़कर-fms-extensions -fms-compatibility -fms-compatibility-version=17.00
। और asm यह उनके लिए बेकार है (अतिरिक्त मास्किंग और एक CMOV)।
- MSVC:
_rotr8
और_rotr16
।
- जीसीसी और आईसीसी (बजना नहीं):
<x86intrin.h>
यह भी प्रदान करता है __rolb
/ __rorb
के लिए 8 बिट बाएं घुमाएं / सही, __rolw
/ __rorw
(16-बिट), __rold
/ __rord
(32-बिट), __rolq
/ __rorq
(64-बिट, केवल 64-बिट लक्ष्यों के लिए परिभाषित)। संकीर्ण रोटेट के लिए, कार्यान्वयन का उपयोग करता है __builtin_ia32_rolhi
या ...qi
, लेकिन 32 / और 64-बिट रोटेट को शिफ्ट / या (यूबी के खिलाफ कोई सुरक्षा नहीं होने के साथ परिभाषित किया गया है, क्योंकि कोड ia32intrin.h
केवल x86 के लिए gcc पर काम करना है)। 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 संकलक में आंतरिक भी हैं, लेकिन आइए उन सभी को शामिल करने के लिए इस समुदाय-विकी उत्तर का विस्तार न करें। (हो सकता है कि आंतरिक जवाब के बारे में मौजूदा जवाब में )।
(इस उत्तर के पुराने संस्करण ने सुझाव दिया कि MSVC- विशिष्ट इनलाइन asm (जो कि केवल 32 बिट x86 कोड के लिए काम करता है), या C संस्करण के लिए http://www.devx.com/tips/Tip/14043 है । टिप्पणियां इसका उत्तर दे रही हैं। ।)
इनलाइन asm कई अनुकूलन को हराता है , विशेष रूप से MSVC- शैली क्योंकि यह इनपुट को संग्रहीत / पुनः लोड करने के लिए मजबूर करता है । एक सावधानी से लिखा गया GNU C इनलाइन-asm रोटेट, गणना को संकलित-समय-स्थिर शिफ्ट काउंट के लिए एक तत्काल ऑपरेंड होने की अनुमति देता है, लेकिन यह अभी भी पूरी तरह से अनुकूलन नहीं कर सकता है यदि स्थानांतरित किया जाने वाला मान भी एक संकलन-समय स्थिरांक है inlining के बाद। https://gcc.gnu.org/wiki/DontUseInlineAsm ।