別の回転質問でこの回答の以前のバージョンも参照してくださいasm gcc / clangがx86用に生成するものについての詳細を含む。
未定義の動作を回避するCおよびC ++で回転を表現する最もコンパイラーに適した方法は、John Regehrの実装のようです。タイプの幅に応じて回転するように調整しました(などの固定幅タイプを使用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
でなく、任意の符号なし整数型で機能するため、他のサイズのバージョンを作成できます。
多くの安全チェック(タイプ幅が2の累乗であることを含む)を備えたC ++ 11テンプレートバージョンも参照してくださいstatic_assert
。これは、一部の24ビットDSPまたは36ビットメインフレームには当てはまりません。
回転幅を明示的に含む名前のラッパーのバックエンドとしてのみテンプレートを使用することをお勧めします。 整数昇格ルールは、rotl_template(u16 & 0x11UL, 7)
16ではなく32または64ビットローテーションを実行することを意味します(幅に応じてunsigned long
)。でもuint16_t & uint16_t
に昇格されるsigned int
プラットフォームを除いて、C ++の整数プロモーションルールによってint
より広いnoですuint16_t
。
x86では、このバージョンは、コンパイラーがx86の回転およびシフト命令を認識しているため、それを処理するコンパイラーを使用して単一rol r32, cl
(またはrol r32, imm8
)にインライン化します。がCソースと同じ方法でシフトカウントをマスクです。
用のx86上でこのUB-回避イディオムのためのコンパイラのサポート、uint32_t x
およびunsigned int n
可変数のシフトのために:
- clang:clang3.5以降、可変カウントローテーションで認識され、その前に複数のシフト+またはインス
- gcc:gcc4.9以降、変数カウントローテーションで認識され、その前に複数のシフト+またはinsns。gcc5以降では、変数カウントの
ror
or rol
命令のみを使用して、ウィキペディアバージョンのブランチとマスクも最適化します。
- icc:ICC13以前から変数カウントローテーションでサポートされています。BMI2がMOVの保存に使用できない場合、定数カウントローテーション
shld edi,edi,7
はrol edi,7
、一部のCPU(特にAMD、一部のIntel)に比べて低速で多くのバイトを使用しますrorx eax,edi,25
。
- MSVC:x86-64 CL19:定数カウントローテーションでのみ認識されます。(ウィキペディアのイディオムは認識されていますが、ブランチとANDは最適化されていません)。x86(x86-64を含む)の
_rotl
/ _rotr
組み込み関数を使用し<intrin.h>
ます。
ARMのgccはand r1, r1, #31
for変数カウント循環を使用しますが、実際の回転は単一の命令で実行しますror r0, r0, r1
。そのため、gccは、回転カウントが本質的にモジュール式であることを認識していません。ARMのドキュメントにあるように、「シフト長n
が32を超えるRORは、シフト長が指定されたRORと同じですn-32
」。ARMでの左/右シフトがカウントを飽和させるため、gccはここで混乱すると思います。32以上シフトするとレジスタがクリアされます。(x86とは異なり、シフトはカウントを回転と同じようにマスクします)。回転イディオムを認識する前にAND命令が必要であると判断するのは、そのターゲットで非循環シフトがどのように機能するかによります。
現在のx86コンパイラは、8および16ビットローテートの変数カウントをマスクするために追加の命令をまだ使用しています。これは、おそらくARMでのANDを回避しないのと同じ理由によります。パフォーマンスはどのx86-64 CPUの回転数にも依存しないため、これは最適化されていません。(カウントのマスキングは、シフトを反復的に処理するため、パフォーマンス上の理由から286で導入されました。最新のCPUのような一定のレイテンシではありません。)
ところで、回転数32-n
だけを提供するARMやMIPSのようなアーキテクチャでコンパイラが左回転を実装するのを避けるために、変数カウントの回転では回転右を優先します。(これは、コンパイル時定数のカウントにより最適化されます。)
おもしろい事実:ARMには実際には専用のシフト/回転命令がありません。これは、RORモードでソースオペランドがバレルシフターを通過する MOV ですmov r0, r0, ror r1
。したがって、回転は、EOR命令などのレジスタソースオペランドに折りたたむことができます。
に符号なしの型n
と戻り値を使用してください。そうしないと、ローテートにはなりません。(x86ターゲットのgccは算術右シフトを実行し、ゼロではなく符号ビットのコピーをシフトインするOR
ため、2つの値を一緒にシフトすると問題が発生します。負の符号付き整数の右シフトは、Cの実装定義の動作です。)
また、シフトカウントが符号なしの型であることを確認してくださいであること(-n)&31
をで 1の補数または符号/大きさであり、符号なしまたは2の補数で得られるモジュラー2 ^ nと同じではない場合があります。(Regehrのブログ投稿のコメントを参照してください)。 unsigned int
私が調べたすべてのコンパイラで、すべての幅でうまく機能しx
ます。他のいくつかの型は、一部のコンパイラのイディオム認識を無効にするため、と同じ型を使用しないでくださいx
。
一部のコンパイラーは、対象のコンパイラーでポータブルバージョンが適切なコードを生成しない場合、inline-asmよりもはるかに優れたrotatesの組み込み関数を提供します。私が知っているコンパイラには、クロスプラットフォームの組み込み関数はありません。これらは、x86オプションの一部です。
// 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の回答を拡張してそれらすべてを含めることはできません。(多分、組み込みについての既存の答えでそれをするでしょう)。
(この回答の古いバージョンでは、MSVC固有のインラインasm(32ビットx86コードでのみ機能))、またはhttp://www.devx.com/tips/Tip/14043が推奨されていました Cバージョンのがました。コメントへの返信。)
インラインasmは入力を強制的に格納/再ロードするため、特にMSVCスタイルの多くの最適化を無効にします。丁寧に書かれたGNU C inline-asm rotateは、カウントをコンパイル時定数シフトカウントの直接のオペランドにすることを可能にしますが、シフトされる値もコンパイル時定数である場合、完全に最適化できませんでした。インライン化後。 https://gcc.gnu.org/wiki/DontUseInlineAsm。