Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18470] ファイルの概要

コミット

commit 44c252bda29585b4ee9f18e77e68c658207e6f0a
Author: Nick Craig-Wood <nick@craig-wood.com>
Date:   Wed Feb 12 13:24:52 2014 -0500

    An ARM version of sha1block.go with a big improvement in throughput
    (up to 2.8x).
    
    This is a partially unrolled version which performs better for small
    hashes and only sacrifices a small amount of ultimate speed to a fully
    unrolled version which uses 5k of code.
    
    Code size
    
      Before 1636 bytes
      After 1880 bytes
      15% larger
    
    Benchmarks on Samsung Exynos 5 ARMv7 Chromebook
    
    benchmark              old ns/op    new ns/op    delta
    BenchmarkHash8Bytes         1907         1136  -40.43%
    BenchmarkHash1K            20280         7547  -62.79%
    BenchmarkHash8K           148469        52576  -64.59%
    
    benchmark               old MB/s     new MB/s  speedup
    BenchmarkHash8Bytes         4.19         7.04    1.68x
    BenchmarkHash1K            50.49       135.68    2.69x
    BenchmarkHash8K            55.18       155.81    2.82x
    
    LGTM=dave, agl
    R=dave, bradfitz, agl, adg, nick
    CC=golang-codereviews
    https://golang.org/cl/56990044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/44c252bda29585b4ee9f18e77e68c658207e6f0a

元コミット内容

このコミットは、Go言語のcrypto/sha1パッケージにARMアーキテクチャ向けのSHA-1ハッシュ計算の最適化されたアセンブリ実装を追加するものです。具体的には、sha1block.goの既存のGo実装に代わるsha1block_arm.sという新しいファイルが導入され、スループットが最大2.8倍向上しています。

この最適化は、部分的にアンロールされた(unrolled)バージョンであり、小さなハッシュに対してより良いパフォーマンスを発揮し、完全にアンロールされたバージョン(コードサイズが5KBになる)と比較して、最終的な速度のわずかな犠牲でコードサイズの増加を抑えています。

ベンチマーク結果は、Samsung Exynos 5 ARMv7 Chromebook上で、特に大きなデータサイズ(1KB、8KB)において、処理速度が大幅に向上していることを示しています。

変更の背景

Go言語の標準ライブラリに含まれる暗号化パッケージは、様々なプラットフォームで効率的に動作することが求められます。特に、モバイルデバイスや組み込みシステムで広く利用されているARMアーキテクチャにおいて、ハッシュ計算のようなCPU負荷の高い処理のパフォーマンスは重要です。

このコミットの背景には、Goのcrypto/sha1パッケージの既存のGo実装がARMプロセッサ上で十分な性能を発揮していなかったという課題がありました。SHA-1のような暗号学的ハッシュ関数は、データ整合性の検証、デジタル署名、パスワードの保存など、多くのアプリケーションで利用されます。これらの処理が高速化されることで、Goアプリケーション全体の応答性や効率が向上します。

アセンブリ言語による最適化は、特定のCPUアーキテクチャの特性(レジスタの利用、命令セット、パイプライン処理など)を最大限に活用し、Go言語で書かれたコードよりも高いパフォーマンスを引き出すための一般的な手法です。このコミットは、ARMv7アーキテクチャに特化した最適化を導入することで、GoのSHA-1実装のボトルネックを解消し、ARMベースのシステムでのGoアプリケーションの競争力を高めることを目的としています。

前提知識の解説

SHA-1 (Secure Hash Algorithm 1)

SHA-1は、アメリカ国家安全保障局(NSA)によって設計された暗号学的ハッシュ関数です。任意の長さの入力データから、160ビット(20バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。このハッシュ値は、入力データが少しでも変更されると大きく変化するという性質(雪崩効果)を持ち、データの改ざん検出に利用されます。

SHA-1の計算プロセスは、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して80ラウンドの複雑なビット演算と論理演算を繰り返すことで行われます。各ラウンドでは、5つの32ビットレジスタ(A, B, C, D, E)の状態が更新され、最終的にこれらのレジスタの値がハッシュ値となります。

ARMアーキテクチャ

ARM(Advanced RISC Machine)は、モバイルデバイス、組み込みシステム、IoTデバイスなどで広く採用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高い性能効率が特徴です。ARMプロセッサは、特定のタスクを高速化するためのSIMD(Single Instruction, Multiple Data)命令や暗号化拡張命令など、様々な拡張機能を持っています。

アセンブリ言語

アセンブリ言語は、特定のCPUアーキテクチャの機械語命令と1対1に対応する低レベルのプログラミング言語です。アセンブリ言語を使用することで、プログラマはCPUのレジスタ、メモリ、命令セットを直接制御し、非常に最適化されたコードを書くことができます。Go言語では、パフォーマンスが重要な部分(特に暗号化やシステムコールなど)で、Goアセンブリ言語(Plan 9アセンブラの構文に基づく)が利用されることがあります。

ループアンローリング (Loop Unrolling)

ループアンローリングは、プログラムの最適化手法の一つです。ループの各イテレーションで実行される処理を複数回分、ループ本体に展開することで、ループのオーバーヘッド(ループカウンタの更新、条件分岐の評価など)を削減し、命令レベルの並列性を高めることを目的とします。

SHA-1のような固定回数(80ラウンド)のループを持つアルゴリズムでは、ループアンローリングは非常に効果的です。例えば、80回のループを1回ずつ実行する代わりに、5回分の処理をまとめて1つの大きなブロックとして実行し、それを16回繰り返すといった形です。これにより、CPUのパイプラインがより効率的に利用され、キャッシュミスが減少し、全体的な実行速度が向上します。

ただし、ループアンローリングはコードサイズを増加させるというデメリットもあります。このコミットでは、「部分的にアンロールされたバージョン」を採用することで、コードサイズの増加を抑えつつ、パフォーマンス向上を図っています。

技術的詳細

このコミットの主要な技術的詳細は、Goのcrypto/sha1パッケージにおけるSHA-1ブロック処理のARMアセンブリ実装です。

SHA-1ブロック処理の構造

SHA-1アルゴリズムのコアは、512ビット(64バイト)の入力ブロックを処理し、5つの32ビットレジスタ(A, B, C, D, E)の状態を更新する80ラウンドの処理です。これらのラウンドは、以下の4つのタイプに分類されます。

  • ラウンド 0-15: タイプ1。入力データ(w[i])をロードし、計算に含めます。
  • ラウンド 16-19: タイプ1。データはロードせず、以前のw値からシャッフルして新しいw値を生成します。
  • ラウンド 20-39: タイプ2。データはロードせず、シャッフルしたw値を使用します。
  • ラウンド 40-59: タイプ3。データはロードせず、シャッフルしたw値を使用します。
  • ラウンド 60-79: タイプ4。データはロードせず、シャッフルしたw値を使用します。

各ラウンドでは、以下の処理が行われます。

  1. データのロードまたはシャッフル。
  2. b, c, dレジスタに対するラウンドごとの関数(FUNC1, FUNC2, FUNC3, FUNC4)の計算。
  3. 結果をa, b, c, d, eの5つのレジスタに混合し、レジスタをローテーション。

アセンブリ実装の最適化

sha1block_arm.sでは、以下の最適化が施されています。

  1. レジスタ割り当て: ARMプロセッサのレジスタを効率的に使用しています。

    • data (R0): 入力データへのポインタ
    • const (R1): SHAラウンドの定数
    • a, b, c, d, e (R2-R6): SHA-1アキュムレータ
    • t0, t1, t2 (R7, R8, R11): 一時レジスタ
    • ctr (R12): ループカウンタ
    • w (R14): wバッファへのポインタ 特に、a, b, c, d, eレジスタのローテーションは、明示的なMOV命令ではなく、ラウンドマクロへの引数をローテーションさせることで実装されており、命令数を削減しています。
  2. マクロの活用: SHA-1の各ラウンドの処理をLOAD, SHUFFLE, FUNC1FUNC4, MIX, ROUND1ROUND4といったマクロとして定義しています。これにより、コードの可読性を保ちつつ、繰り返しパターンを効率的に記述し、コンパイル時に展開されることでインライン化の恩恵を受けます。

    • LOAD(e): 入力データから4バイトを読み込み、32ビットワードに変換してwバッファに格納し、eレジスタに加算します。バイトオーダーの変換(ビッグエンディアンからリトルエンディアン、またはその逆)も考慮されています。
    • SHUFFLE(e): SHA-1のメッセージスケジュール関数を実装しています。以前のw値から新しいw値を計算し、eレジスタに加算します。
    • FUNC1FUNC4: SHA-1のラウンド関数を実装しています。これらはビット単位の論理演算(AND, ORR, EOR, MVN)を組み合わせています。
    • MIX: 各ラウンドの最後に、レジスタaを左に5ビットローテートし、bを左に30ビットローテートし、eにラウンド関数と定数を加算する処理を行います。
  3. 部分的なループアンローリング: コミットメッセージにあるように、この実装は「部分的にアンロールされたバージョン」です。loop1, loop2, loop3, loop4というラベルで示されるように、各ラウンドタイプで複数のROUNDマクロ呼び出しが連続して記述されており、これがアンローリングされた部分です。例えば、loop1ではROUND1が5回連続で呼び出されています。これにより、ループのオーバーヘッドを削減し、命令の並列実行を促進します。

  4. スタックフレームの管理: p_end, p_data, w_buf, savedといったオフセットでスタックフレームを定義し、関数呼び出し規約に従ってレジスタと一時データを効率的に保存・復元しています。特に、SHA-1の5つのアキュムレータレジスタ(a,b,c,d,e)は、各ブロック処理の開始時にロードされ、終了時に保存されます。

ベンチマーク結果の分析

コミットメッセージに記載されているベンチマーク結果は、この最適化の有効性を示しています。

  • ns/op (nanoseconds per operation): 処理にかかる時間。値が小さいほど高速。
  • MB/s (megabytes per second): 1秒あたりの処理量(スループット)。値が大きいほど高速。

特に注目すべきは、BenchmarkHash1KBenchmarkHash8KにおけるMB/sの劇的な向上です。それぞれ2.69倍、2.82倍のスループット向上を達成しており、これは大きなデータブロックのハッシュ計算において、新しいARMアセンブリ実装が既存のGo実装よりもはるかに効率的であることを示しています。BenchmarkHash8Bytesのような小さなデータサイズでも1.68倍の向上を見せており、部分的なアンローリングが小さなハッシュにも効果的であることがわかります。

コードサイズは15%増加していますが、パフォーマンスの向上と比較すると許容範囲内と判断されています。

コアとなるコードの変更箇所

このコミットでは、主に以下の3つのファイルが変更されています。

  1. src/pkg/crypto/sha1/sha1block.go
  2. src/pkg/crypto/sha1/sha1block_arm.s (新規追加)
  3. src/pkg/crypto/sha1/sha1block_decl.go

src/pkg/crypto/sha1/sha1block.go

--- a/src/pkg/crypto/sha1/sha1block.go
+++ b/src/pkg/crypto/sha1/sha1block.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build !amd64,!386
+// +build !amd64,!386,!arm
 
 // SHA1 block step.
 // In its own file so that a faster assembly or C version

このファイルは、Goで書かれたSHA-1ブロック処理のデフォルト実装です。変更点は、ビルドタグに!armが追加されたことです。これは、「amd64386またはarm以外のアーキテクチャの場合にこのファイルを使用する」という意味になります。これにより、ARMアーキテクチャでは新しく追加されるアセンブリ実装が優先的に使用されるようになります。

src/pkg/crypto/sha1/sha1block_arm.s

new file mode 100644
index 0000000000..5917e8b24b
--- /dev/null
+++ b/src/pkg/crypto/sha1/sha1block_arm.s
@@ -0,0 +1,217 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// ARM version of md5block.go
+
+#include "../../../cmd/ld/textflag.h"
+
+// SHA1 block routine. See sha1block.go for Go equivalent.
+//
+// There are 80 rounds of 4 types:
+//   - rounds 0-15 are type 1 and load data (ROUND1 macro).
+//   - rounds 16-19 are type 1 and do not load data (ROUND1x macro).
+//   - rounds 20-39 are type 2 and do not load data (ROUND2 macro).
+//   - rounds 40-59 are type 3 and do not load data (ROUND3 macro).
+//   - rounds 60-79 are type 4 and do not load data (ROUND4 macro).
+//
+// Each round loads or shuffles the data, then computes a per-round
+// function of b, c, d, and then mixes the result into and rotates the
+// five registers a, b, c, d, e holding the intermediate results.
+//
+// The register rotation is implemented by rotating the arguments to
+// the round macros instead of by explicit move instructions.
+
+// Register definitions
+data = 0	// Pointer to incoming data
+const = 1	// Current constant for SHA round
+a = 2		// SHA1 accumulator
+b = 3		// SHA1 accumulator
+c = 4		// SHA1 accumulator
+d = 5		// SHA1 accumulator
+e = 6		// SHA1 accumulator
+t0 = 7		// Temporary
+t1 = 8		// Temporary
+// r9, r10 are forbidden
+// r11 is OK provided you check the assembler that no synthetic instructions use it
+t2 = 11		// Temporary
+ctr = 12	// loop counter
+w = 14		// point to w buffer
+
+// func block(dig *digest, p []byte)
+// 0(FP) is *digest
+// 4(FP) is p.array (struct Slice)
+// 8(FP) is p.len
+//12(FP) is p.cap
+//
+// Stack frame
+p_end = -4		// -4(SP) pointer to the end of data
+p_data = p_end - 4	// -8(SP) current data pointer
+w_buf = p_data - 4*80	// -328(SP) 80 words temporary buffer w uint32[80]
+saved = w_buf - 4*5	// -348(SP) saved sha1 registers a,b,c,d,e - these must be last
+// Total size +4 for saved LR is 352
+
+	// w[i] = p[j]<<24 | p[j+1]<<16 | p[j+2]<<8 | p[j+3]
+	// e += w[i]
+#define LOAD(e) \
+	MOVBU	2(R(data)), R(t0) ; \
+	MOVBU	3(R(data)), R(t1) ; \
+	MOVBU	1(R(data)), R(t2) ; \
+	ORR	R(t0)<<8, R(t1), R(t0)	    ; \
+	MOVBU.P	4(R(data)), R(t1) ; \
+	ORR	R(t2)<<16, R(t0), R(t0)	    ; \
+	ORR	R(t1)<<24, R(t0), R(t0)	    ; \
+	MOVW.P	R(t0), 4(R(w))\t\t    ; \
+	ADD	R(t0), R(e), R(e)
+	
+	// tmp := w[(i-3)&0xf] ^ w[(i-8)&0xf] ^ w[(i-14)&0xf] ^ w[(i)&0xf]
+	// w[i&0xf] = tmp<<1 | tmp>>(32-1)
+	// e += w[i&0xf] 
+#define SHUFFLE(e) \
+	MOVW	(-16*4)(R(w)), R(t0) ; \
+	MOVW	(-14*4)(R(w)), R(t1) ; \
+	MOVW	(-8*4)(R(w)), R(t2)  ; \
+	EOR	R(t0), R(t1), R(t0)  ; \
+	MOVW	(-3*4)(R(w)), R(t1)  ; \
+	EOR	R(t2), R(t0), R(t0)  ; \
+	EOR	R(t0), R(t1), R(t0)  ; \
+	MOVW	R(t0)@>(32-1), R(t0)  ; \
+	MOVW.P	R(t0), 4(R(w))\t  ; \
+	ADD	R(t0), R(e), R(e)
+
+	// t1 = (b & c) | ((~b) & d)
+#define FUNC1(a, b, c, d, e) \
+	MVN	R(b), R(t1)\t   ; \
+	AND	R(b), R(c), R(t0)  ; \
+	AND	R(d), R(t1), R(t1) ; \
+	ORR	R(t0), R(t1), R(t1)
+
+	// t1 = b ^ c ^ d
+#define FUNC2(a, b, c, d, e) \
+	EOR	R(b), R(c), R(t1) ; \
+	EOR	R(d), R(t1), R(t1)
+
+	// t1 = (b & c) | (b & d) | (c & d) =
+	// t1 = (b & c) | ((b | c) & d)
+#define FUNC3(a, b, c, d, e) \
+	ORR	R(b), R(c), R(t0)  ; \
+	AND	R(b), R(c), R(t1)  ; \
+	AND	R(d), R(t0), R(t0) ; \
+	ORR	R(t0), R(t1), R(t1)
+
+#define FUNC4 FUNC2
+
+	// a5 := a<<5 | a>>(32-5)
+	// b = b<<30 | b>>(32-30)
+	// e = a5 + t1 + e + const
+#define MIX(a, b, c, d, e) \
+	ADD	R(t1), R(e), R(e)\t ; \
+	MOVW	R(b)@>(32-30), R(b)\t ; \
+	ADD	R(a)@>(32-5), R(e), R(e) ; \
+	ADD	R(const), R(e), R(e)
+
+#define ROUND1(a, b, c, d, e) \
+	LOAD(e)\t\t; \
+	FUNC1(a, b, c, d, e)\t; \
+	MIX(a, b, c, d, e)
+
+#define ROUND1x(a, b, c, d, e) \
+	SHUFFLE(e)\t; \
+	FUNC1(a, b, c, d, e)\t; \
+	MIX(a, b, c, d, e)
+
+#define ROUND2(a, b, c, d, e) \
+	SHUFFLE(e)\t; \
+	FUNC2(a, b, c, d, e)\t; \
+	MIX(a, b, c, d, e)
+
+#define ROUND3(a, b, c, d, e) \
+	SHUFFLE(e)\t; \
+	FUNC3(a, b, c, d, e)\t; \
+	MIX(a, b, c, d, e)
+
+#define ROUND4(a, b, c, d, e) \
+	SHUFFLE(e)\t; \
+	FUNC4(a, b, c, d, e)\t; \
+	MIX(a, b, c, d, e)
+
+
+// func block(dig *digest, p []byte)
+TEXT	·block(SB), 0, $352-16
+	MOVW	p+4(FP), R(data)	// pointer to the data
+	MOVW	p_len+8(FP), R(t0)	// number of bytes
+	ADD	R(data), R(t0)
+	MOVW	R(t0), p_end(SP)	// pointer to end of data
+
+	// Load up initial SHA1 accumulator
+	MOVW	dig+0(FP), R(t0)
+	MOVM.IA (R(t0)), [R(a),R(b),R(c),R(d),R(e)]
+
+loop:
+	// Save registers at SP+4 onwards
+	MOVM.IB [R(a),R(b),R(c),R(d),R(e)], (R13)
+
+	MOVW	$w_buf(SP), R(w)
+	MOVW	$0x5A827999, R(const)
+	MOVW	$3, R(ctr)
+loop1:	ROUND1(a, b, c, d, e)
+	ROUND1(e, a, b, c, d)
+	ROUND1(d, e, a, b, c)
+	ROUND1(c, d, e, a, b)
+	ROUND1(b, c, d, e, a)
+	SUB.S	$1, R(ctr)
+	BNE	loop1
+
+	ROUND1(a, b, c, d, e)
+	ROUND1x(e, a, b, c, d)
+	ROUND1x(d, e, a, b, c)
+	ROUND1x(c, d, e, a, b)
+	ROUND1x(b, c, d, e, a)
+	
+	MOVW	$0x6ED9EBA1, R(const)
+	MOVW	$4, R(ctr)
+loop2:	ROUND2(a, b, c, d, e)
+	ROUND2(e, a, b, c, d)
+	ROUND2(d, e, a, b, c)
+	ROUND2(c, d, e, a, b)
+	ROUND2(b, c, d, e, a)
+	SUB.S	$1, R(ctr)
+	BNE	loop2
+	
+	MOVW	$0x8F1BBCDC, R(const)
+	MOVW	$4, R(ctr)
+loop3:	ROUND3(a, b, c, d, e)
+	ROUND3(e, a, b, c, d)
+	ROUND3(d, e, a, b, c)
+	ROUND3(c, d, e, a, b)
+	ROUND3(b, c, d, e, a)
+	SUB.S	$1, R(ctr)
+	BNE	loop3
+	
+	MOVW	$0xCA62C1D6, R(const)
+	MOVW	$4, R(ctr)
+loop4:	ROUND4(a, b, c, d, e)
+	ROUND4(e, a, b, c, d)
+	ROUND4(d, e, a, b, c)
+	ROUND4(c, d, e, a, b)
+	ROUND4(b, c, d, e, a)
+	SUB.S	$1, R(ctr)
+	BNE	loop4
+
+	// Accumulate - restoring registers from SP+4
+	MOVM.IB (R13), [R(t0),R(t1),R(t2),R(ctr),R(w)]
+	ADD	R(t0), R(a)
+	ADD	R(t1), R(b)
+	ADD	R(t2), R(c)
+	ADD	R(ctr), R(d)
+	ADD	R(w), R(e)
+
+	MOVW	p_end(SP), R(t0)
+	CMP	R(t0), R(data)
+	BLO	loop
+
+	// Save final SHA1 accumulator
+	MOVW	dig+0(FP), R(t0)
+	MOVM.IA [R(a),R(b),R(c),R(d),R(e)], (R(t0))
+
+	RET

このファイルは新規追加されたもので、ARMアーキテクチャ向けのSHA-1ブロック処理のアセンブリ実装です。

  • ヘッダー: Goアセンブリの標準的なヘッダーと、../../../cmd/ld/textflag.hのインクルードが含まれています。
  • レジスタ定義: SHA-1計算に必要なレジスタ(data, const, ae, t0t2, ctr, w)にシンボリックな名前を割り当てています。
  • 関数シグネチャ: func block(dig *digest, p []byte)というGo関数のアセンブリ実装であることを示しています。引数とスタックフレームのレイアウトがコメントで説明されています。
  • マクロ定義: LOAD, SHUFFLE, FUNC1FUNC4, MIX, ROUND1ROUND4といったマクロが定義されています。これらはSHA-1の各ステップをARMアセンブリ命令で実装しており、レジスタ操作、ビットシフト、論理演算、加算などが含まれます。特に、LOADマクロはバイトオーダーの変換とwバッファへの格納を効率的に行っています。
  • メインルーチン (TEXT ·block(SB)):
    • 入力データポインタと長さをレジスタにロードし、データの終端ポインタを計算します。
    • SHA-1の初期アキュムレータ値(dig構造体から)をレジスタaeにロードします。
    • loopラベルで示されるメインループは、入力データブロックを順次処理します。
    • 各ブロック処理の開始時に、現在のSHA-1アキュムレータレジスタの値をスタックに保存します(MOVM.IB)。
    • SHA-1の80ラウンドは、loop1, loop2, loop3, loop4という部分的にアンロールされたループで実装されています。各ループは、対応するROUNDマクロを複数回呼び出すことで、効率的な計算を行います。
    • 各ラウンドタイプに対応する定数(0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6)がconstレジスタにロードされます。
    • 各ブロック処理の終了時に、保存しておいたレジスタの値を復元し、現在のSHA-1アキュムレータ値に加算します(ADD)。
    • すべてのデータブロックが処理されるまでループを続けます。
    • 最終的なSHA-1アキュムレータ値をdig構造体に保存し、関数を終了します(RET)。

src/pkg/crypto/sha1/sha1block_decl.go

--- a/src/pkg/crypto/sha1/sha1block_decl.go
+++ b/src/pkg/crypto/sha1/sha1block_decl.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.\n
 
-// +build amd64 386
+// +build amd64 386 arm
 
 package sha1
 

このファイルは、amd64および386アーキテクチャ向けにSHA-1ブロック処理のGo宣言(アセンブリ実装のGo側のインターフェース)を提供するものです。変更点は、ビルドタグにarmが追加されたことです。これにより、amd64386、**およびarm**アーキテクチャの場合にこのファイルがビルドに含まれるようになります。これは、sha1block_arm.sで定義されたアセンブリ関数blockがGoコードから呼び出せるようにするための宣言です。

コアとなるコードの解説

このコミットのコアとなるコードは、src/pkg/crypto/sha1/sha1block_arm.sに記述されたARMアセンブリ実装です。

このアセンブリコードは、SHA-1アルゴリズムの計算をARMプロセッサ上で最大限に効率化するために設計されています。

  1. レジスタの最適利用: SHA-1の計算は、5つの32ビットレジスタ(A, B, C, D, E)の状態を繰り返し更新するプロセスです。このアセンブリコードでは、これらのレジスタをARMの物理レジスタに直接マッピングし、中間結果を保持するために一時レジスタを賢く利用しています。特に、SHA-1のラウンドごとにレジスタがローテーションする特性を、マクロの引数をローテーションさせることで、余分なMOV命令を発生させずに実現しています。これは、命令数を減らし、パイプラインの効率を高める上で非常に重要です。

  2. マクロによる抽象化と最適化:

    • LOADマクロは、入力バイト列から32ビットワードを効率的に読み込み、必要に応じてバイトオーダーを調整し、SHA-1のメッセージスケジュールバッファwに格納します。同時に、その値を現在のハッシュ状態に加算します。MOVBU(バイトを符号なしで移動)、ORR(ビット単位OR)、MOVW.P(ワードを移動し、ポインタを更新)などの命令を組み合わせて、メモリからのデータロードと変換を最適化しています。
    • SHUFFLEマクロは、SHA-1のメッセージスケジュール関数(w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]) << 1 | ...)を実装しています。これは、以前のw値から新しいw値を計算するための複雑なビット演算を含みます。EOR(排他的OR)とMOVW(ワード移動とローテート)命令を駆使して、この計算を効率的に行います。
    • FUNC1FUNC4マクロは、SHA-1の各ラウンドで使用される非線形関数を実装しています。これらはAND, ORR, MVN(ビット反転)などの基本的な論理演算を組み合わせています。
    • MIXマクロは、各ラウンドの最後にハッシュ状態を更新する部分です。レジスタのローテーション(aを5ビット、bを30ビット)と、ラウンド関数、現在のハッシュ状態、ラウンド定数を加算する処理を含みます。ADDMOVW(ローテート付き)命令が使用されます。
  3. 部分的なループアンローリング: SHA-1の80ラウンドは、4つの異なるタイプのラウンドに分けられます。このアセンブリコードでは、各ラウンドタイプに対して部分的にループをアンロールしています。例えば、loop1ではROUND1マクロが5回連続で呼び出され、その後ループカウンタを減らして分岐します。これにより、ループの開始・終了時のオーバーヘッド(カウンタの初期化、比較、分岐)が削減され、CPUの命令パイプラインがより深く、効率的に利用されるようになります。完全にアンロールするとコードサイズが大きくなりすぎるため、この「部分的なアンローリング」は、パフォーマンスとコードサイズのバランスを取るための賢明な選択です。

  4. メモリとスタックの管理: w_bufとして80ワード(320バイト)の一時バッファをスタック上に確保し、SHA-1のメッセージスケジュールに必要なデータを格納しています。また、関数呼び出し規約に従って、SHA-1のアキュムレータレジスタをスタックに保存・復元することで、関数呼び出し間の状態を正しく維持しています。

これらの最適化の組み合わせにより、ARMプロセッサ上でのSHA-1ハッシュ計算のスループットが大幅に向上し、特に大きなデータブロックの処理において顕著な効果を発揮しています。

関連リンク

参考にした情報源リンク

  • Go言語のcrypto/sha1パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/crypto/sha1
  • Go言語のアセンブリに関する公式ドキュメント: https://go.dev/doc/asm
  • ARMの最適化ガイド(一般的な情報源として): https://developer.arm.com/documentation/den0024/a/
  • SHA-1アルゴリズムの詳細な説明(RFC 3174など)
  • Web検索結果 (Google Search):
    • go.dev
    • medium.com
    • min.io
    • github.com
    • arm.com
    • stackexchange.com
    • ntu.edu.tw
    • golang.org
    • rosettacode.org (これらのURLは、Web検索ツールが提供した情報源のドメインです。具体的な記事への直接リンクは、検索結果の変動性やプライバシーの観点から、上記「GitHub上でのコミットページへのリンク」のように固定されたもの以外は記載していません。)