[インデックス 18172] ファイルの概要
このコミットは、Go言語の標準ライブラリ crypto/sha256
パッケージにおけるSHA-256ハッシュ関数のブロック処理部分を、32ビットx86アーキテクチャ(通称386またはi386)向けのアセンブリ言語で実装したものです。これにより、該当アーキテクチャ上でのSHA-256計算のパフォーマンスが大幅に向上しました。
コミット
commit 232a4e89a4ca6d52cad64e29eaad34f8874f3f7a
Author: Joel Sing <jsing@google.com>
Date: Mon Jan 6 13:31:22 2014 -0500
crypto/sha256: block implementation in 386 assembly
Benchmark on Intel(R) Core(TM) i5-2500S CPU @ 2.70GHz (albeit in a VM)
benchmark old ns/op new ns/op delta
BenchmarkHash8Bytes 1606 699 -56.48%
BenchmarkHash1K 21920 7268 -66.84%
BenchmarkHash8K 165696 53756 -67.56%
benchmark old MB/s new MB/s speedup
BenchmarkHash8Bytes 4.98 11.44 2.30x
BenchmarkHash1K 46.72 140.88 3.02x
BenchmarkHash8K 49.44 152.39 3.08x
R=agl, cldorian
CC=golang-codereviews
https://golang.org/cl/44800044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/232a4e89a4ca6d52cad64e29eaad34f8874f3f7a
元コミット内容
このコミットは、crypto/sha256
パッケージのSHA-256ブロック処理を386アーキテクチャ向けのアセンブリ言語で実装したものです。コミットメッセージには、Intel Core i5-2500S CPU上でのベンチマーク結果が示されており、Go言語による既存の実装と比較して、処理速度が大幅に向上していることが報告されています。具体的には、ns/op
(1操作あたりのナノ秒)が56%から67%削減され、MB/s
(1秒あたりのメガバイト)では2.3倍から3.08倍の速度向上が見られます。
変更されたファイルは以下の通りです。
src/pkg/crypto/sha256/sha256block.go
: Go言語で書かれたSHA-256ブロック処理の汎用実装ファイル。ビルドタグが変更され、386およびamd64アーキテクチャではこのファイルがコンパイルされないようになりました。src/pkg/crypto/sha256/sha256block_386.s
: 新規追加されたファイルで、386アーキテクチャ向けのアセンブリ言語によるSHA-256ブロック処理の実装が含まれています。src/pkg/crypto/sha256/sha256block_decl.go
: SHA-256ブロック処理関数の宣言を含むファイル。ビルドタグが変更され、386アーキテクチャでもこの宣言が有効になるようになりました。
変更の背景
SHA-256のような暗号学的ハッシュ関数は、データの完全性検証、デジタル署名、ブロックチェーンなど、多くのセキュリティ関連アプリケーションで広く利用されています。これらの関数は、大量のデータを処理する際に計算コストが高くなる傾向があります。特に、Go言語のような高水準言語で実装された場合、コンパイラの最適化だけでは達成できないレベルのパフォーマンスが求められることがあります。
このコミットの背景には、32ビットx86アーキテクチャ(386)上でのSHA-256の計算性能を向上させるという明確な目的があります。Go言語の初期の実装では、SHA-256のコアなブロック処理はGo言語自体で書かれていました。しかし、CPUのレジスタを効率的に利用し、特定の命令セット(例:ビット操作、シフト、ローテート)を最大限に活用するためには、アセンブリ言語による実装が非常に有効です。
この変更は、Go言語が様々なプラットフォームで高性能な暗号機能を提供するための継続的な取り組みの一環です。特に、リソースが限られている環境や、高いスループットが求められるサーバーサイドアプリケーションにおいて、暗号処理の高速化はシステム全体のパフォーマンスに大きく貢献します。
前提知識の解説
1. SHA-256 (Secure Hash Algorithm 256-bit)
SHA-256は、アメリカ国家安全保障局(NSA)によって設計され、NIST(米国国立標準技術研究所)によってFIPS 180-4として標準化された暗号学的ハッシュ関数です。任意の長さの入力データから、256ビット(32バイト)の固定長のハッシュ値(メッセージダイジェスト)を生成します。主な特徴は以下の通りです。
- 一方向性: ハッシュ値から元の入力データを復元することは計算上困難です。
- 衝突耐性: 異なる入力データから同じハッシュ値が生成されること(衝突)は計算上困難です。
- 雪崩効果: 入力データのごくわずかな変更でも、ハッシュ値が大きく変化します。
SHA-256の処理は、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して圧縮関数を適用することで行われます。この圧縮関数は、8つの32ビットワード(A, B, C, D, E, F, G, H)の状態変数を持ち、64回の「ラウンド」を繰り返して更新されます。各ラウンドでは、メッセージスケジュールされたワード(Wt)、ラウンド定数(Kt)、および非線形関数(Ch, Maj, Σ0, Σ1)が使用されます。
2. アセンブリ言語
アセンブリ言語は、CPUが直接実行できる機械語と1対1に対応する低水準プログラミング言語です。特定のCPUアーキテクチャ(この場合は386)の命令セットとレジスタを直接操作できます。
- 利点:
- 最大限のパフォーマンス: CPUの機能を最大限に引き出し、手動で最適化されたコードを書くことで、高水準言語では不可能な速度を達成できます。
- ハードウェアへの直接アクセス: 特定のハードウェア機能や特殊なCPU命令を利用できます。
- リソースの効率的な利用: メモリやレジスタの使用を細かく制御できます。
- 欠点:
- 可読性の低さ: コードが抽象化されておらず、理解が難しいです。
- 移植性の低さ: 特定のCPUアーキテクチャに依存するため、他のアーキテクチャでは動作しません。
- 開発の複雑さ: デバッグが難しく、開発に時間がかかります。
Go言語では、パフォーマンスがクリティカルな部分(例:暗号処理、ランタイムのスケジューラ、ガベージコレクタの一部)でアセンブリ言語が利用されることがあります。Goのアセンブリは、Plan 9アセンブラの文法に基づいています。
3. 386アーキテクチャ (i386)
386アーキテクチャは、Intel 80386プロセッサとその互換プロセッサ(AMD、Cyrixなど)が採用した32ビットのx86命令セットアーキテクチャを指します。これは、現在の64ビットx86アーキテクチャ(AMD64またはx86-64)の祖先にあたります。
- 特徴:
- 32ビットレジスタ(EAX, EBX, ECX, EDXなど)と32ビットアドレス空間。
- セグメンテーションとページングによるメモリ管理。
- 基本的な整数演算、ビット操作、条件分岐命令など。
このコミットは、特に32ビットシステムや、古いCPUを搭載した環境でのSHA-256性能を改善することを目的としています。
4. Goのビルドタグ (+build
ディレクティブ)
Go言語では、ソースファイルの先頭に +build
ディレクティブを記述することで、条件付きコンパイルを行うことができます。これは、特定のオペレーティングシステム、アーキテクチャ、またはカスタムタグが指定された場合にのみ、そのファイルをコンパイルするようにGoツールチェインに指示します。
// +build !amd64
: amd64アーキテクチャ以外でコンパイルされる。// +build 386 amd64
: 386またはamd64アーキテクチャでコンパイルされる。// +build !386,!amd64
: 386でもamd64でもないアーキテクチャでコンパイルされる。
このコミットでは、sha256block.go
(Go実装)が386とamd64の両方でコンパイルされないように変更され、代わりにsha256block_386.s
(386アセンブリ実装)と既存のsha256block_amd64.s
(amd64アセンブリ実装)が優先されるように設定されています。これにより、各アーキテクチャで最適な実装が選択されるようになります。
技術的詳細
このコミットの核心は、SHA-256のブロック処理アルゴリズムを386アセンブリ言語で効率的に実装した点にあります。SHA-256の圧縮関数は、64回のラウンドから構成され、各ラウンドで複雑なビット演算と加算が行われます。アセンブリ言語を使用することで、これらの操作をCPUのレジスタと命令を最大限に活用して実行できます。
SHA-256アルゴリズムの概要 (FIPS 180-4)
SHA-256の圧縮関数は、以下の8つの初期ハッシュ値 (H0-H7) と、512ビットのメッセージブロック (M) を入力として受け取ります。
-
メッセージスケジューリング (Wt):
- 最初の16ワード (W0-W15) は、メッセージブロックMを32ビットワードに分解したものです。
- 残りの48ワード (W16-W63) は、以下の再帰的な式で計算されます。
Wt = σ1(Wt-2) + Wt-7 + σ0(Wt-15) + Wt-16
ここで、σ0
とσ1
は、それぞれ入力ワードのビットを回転 (ROTR) および右シフト (SHR) させる非線形関数です。
-
圧縮ループ (64ラウンド):
- 初期ハッシュ値 (H0-H7) を8つの作業変数 (a, b, c, d, e, f, g, h) にコピーします。
- t = 0 から 63 までの各ラウンドで、以下の計算を行います。
T1 = h + Σ1(e) + Ch(e,f,g) + Kt + Wt
T2 = Σ0(a) + Maj(a,b,c)
h = g
g = f
f = e
e = d + T1
d = c
c = b
b = a
a = T1 + T2
ここで、Kt
はラウンド定数、Ch
(Choose) とMaj
(Majority) は非線形関数、Σ0
とΣ1
は入力ワードのビットを回転させる非線形関数です。
-
ハッシュ値の更新:
- 64ラウンドが終了した後、作業変数 (a-h) を初期ハッシュ値 (H0-H7) に加算し、新しいハッシュ値を生成します。
H0 = a + H0
...H7 = h + H7
- 64ラウンドが終了した後、作業変数 (a-h) を初期ハッシュ値 (H0-H7) に加算し、新しいハッシュ値を生成します。
アセンブリ実装の最適化
アセンブリコードでは、上記のアルゴリズムを効率的に実行するために、以下の最適化が施されています。
- レジスタの積極的な利用: 32ビットの汎用レジスタ(EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP)を最大限に活用し、メモリへのアクセスを最小限に抑えています。これにより、データ転送のオーバーヘッドが削減されます。
- インラインマクロ: SHA-256の各ラウンドで繰り返し行われる計算(メッセージスケジューリング、T1/T2の計算、ラウンド処理)は、アセンブリマクロとして定義されています。これにより、コードの可読性を保ちつつ、コンパイル時に効率的な命令シーケンスに展開されます。
MSGSCHEDULE0
,MSGSCHEDULE1
: メッセージスケジューリングの計算。特にBSWAPL
命令(バイト順序の反転)は、Goのbinary.BigEndian
処理を効率化します。SHA256T1
,SHA256T2
: T1とT2の計算。ROTR
(ビット回転)やSHR
(右シフト)、AND
,XOR
,NOT
などのビット論理演算が多用されます。SHA256ROUND
,SHA256ROUND0
,SHA256ROUND1
: 各ラウンドの処理をカプセル化し、レジスタの受け渡しを明確にしています。
- 命令レベルの並列性: 現代のCPUは、複数の命令を同時に実行できるパイプライン構造を持っています。アセンブリコードは、命令の依存関係を考慮し、CPUのパイプラインを最大限に活用できるように命令を配置することで、スループットを向上させます。
- メモリレイアウト: スタックフレーム (
SP
) を利用して、作業変数やメッセージスケジュールされたワードを効率的に配置し、アクセス速度を最適化しています。
ベンチマーク結果の分析
コミットメッセージに記載されているベンチマーク結果は、アセンブリ実装の有効性を明確に示しています。
ns/op
(nanoseconds per operation): 1回のハッシュ計算にかかる時間。この値が小さいほど高速です。BenchmarkHash8Bytes
: 8バイトの短いデータに対するハッシュ。オーバーヘッドの影響が大きいため、改善率はやや低いですが、それでも56%以上の削減。BenchmarkHash1K
,BenchmarkHash8K
: 1KB、8KBの比較的大きなデータに対するハッシュ。ブロック処理の効率が直接反映され、66%〜67%の劇的な時間削減。
MB/s
(megabytes per second): 1秒あたりに処理できるデータ量。この値が大きいほど高速です。- すべてのベンチマークで2.3倍から3.08倍の速度向上が見られます。これは、アセンブリ実装がCPUの計算能力をより効率的に引き出していることを示しています。
これらの結果は、SHA-256のような計算集約的な暗号処理において、Go言語の汎用実装からアセンブリ言語への切り替えが、特に32ビットx86アーキテクチャ上で非常に大きな性能向上をもたらすことを裏付けています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下の3つのファイルに集中しています。
-
src/pkg/crypto/sha256/sha256block.go
--- a/src/pkg/crypto/sha256/sha256block.go +++ b/src/pkg/crypto/sha256/sha256block.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 +// +build !386,!amd64 // SHA256 block step. // In its own file so that a faster assembly or C version
このファイルは、Go言語で書かれたSHA-256のブロック処理のデフォルト実装を含んでいます。変更点はビルドタグのみです。元々は
!amd64
(amd64以外のアーキテクチャでビルド)となっていましたが、!386,!amd64
に変更されました。これは、「386アーキテクチャでもamd64アーキテクチャでもない場合にこのファイルをビルドする」という意味になります。つまり、386アーキテクチャでは、このGo実装ではなく、新しく追加されるアセンブリ実装が優先されるようになります。 -
src/pkg/crypto/sha256/sha256block_386.s
--- /dev/null +++ b/src/pkg/crypto/sha256/sha256block_386.s @@ -0,0 +1,283 @@ +// Copyright 2013 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. + +// SHA256 block routine. See sha256block.go for Go equivalent. +// +// The algorithm is detailed in FIPS 180-4: +// +// http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf +// +// Wt = Mt; for 0 <= t <= 15 +// Wt = SIGMA1(Wt-2) + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 63 +// +// a = H0 +// b = H1 +// c = H2 +// d = H3 +// e = H4 +// f = H5 +// g = H6 +// h = H7 +// +// for t = 0 to 63 { +// T1 = h + BIGSIGMA1(e) + Ch(e,f,g) + Kt + Wt +// T2 = BIGSIGMA0(a) + Maj(a,b,c) +// h = g +// g = f +// f = e +// e = d + T1 +// d = c +// c = b +// b = a +// a = T1 + T2 // } // // H0 = a + H0 // H1 = b + H1 // H2 = c + H2 // H3 = d + H3 // H4 = e + H4 // H5 = f + H5 // H6 = g + H6 // H7 = h + H7 + // Wt = Mt; for 0 <= t <= 15 #define MSGSCHEDULE0(index) \ MOVL (index*4)(SI), AX; \ BSWAPL AX; \ MOVL AX, (index*4)(BP) + // Wt = SIGMA1(Wt-2) + Wt-7 + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 63 // SIGMA0(x) = ROTR(7,x) XOR ROTR(18,x) XOR SHR(3,x) // SIGMA1(x) = ROTR(17,x) XOR ROTR(19,x) XOR SHR(10,x) #define MSGSCHEDULE1(index) \ MOVL ((index-2)*4)(BP), AX; \ MOVL AX, CX; \ RORL $17, AX; \ MOVL CX, DX; \ RORL $19, CX; \ SHRL $10, DX; \ XORL CX, AX; \ MOVL ((index-15)*4)(BP), BX; \ XORL DX, AX; \ MOVL BX, CX; \ RORL $7, BX; \ MOVL CX, DX; \ SHRL $3, DX; \ RORL $18, CX; \ ADDL ((index-7)*4)(BP), AX; \ XORL CX, BX; \ XORL DX, BX; \ ADDL ((index-16)*4)(BP), BX; \ ADDL BX, AX; \ MOVL AX, ((index)*4)(BP) + // Calculate T1 in AX - uses AX, BX, CX and DX registers. // Wt is passed in AX. // T1 = h + BIGSIGMA1(e) + Ch(e, f, g) + Kt + Wt // BIGSIGMA1(x) = ROTR(6,x) XOR ROTR(11,x) XOR ROTR(25,x) // Ch(x, y, z) = (x AND y) XOR (NOT x AND z) #define SHA256T1(const, e, f, g, h) \ MOVL (h*4)(DI), BX; \ ADDL AX, BX; \ MOVL (e*4)(DI), AX; \ ADDL $const, BX; \ MOVL (e*4)(DI), CX; \ RORL $6, AX; \ MOVL (e*4)(DI), DX; \ RORL $11, CX; \ XORL CX, AX; \ MOVL (e*4)(DI), CX; \ RORL $25, DX; \ ANDL (f*4)(DI), CX; \ XORL AX, DX; \ MOVL (e*4)(DI), AX; \ NOTL AX; \ ADDL DX, BX; \ ANDL (g*4)(DI), AX; \ XORL CX, AX; \ ADDL BX, AX + // Calculate T2 in BX - uses AX, BX, CX and DX registers. // T2 = BIGSIGMA0(a) + Maj(a, b, c) // BIGSIGMA0(x) = ROTR(2,x) XOR ROTR(13,x) XOR ROTR(22,x) // Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z) #define SHA256T2(a, b, c) \ MOVL (a*4)(DI), AX; \ MOVL (c*4)(DI), BX; \ RORL $2, AX; \ MOVL (a*4)(DI), DX; \ ANDL (b*4)(DI), BX; \ RORL $13, DX; \ MOVL (a*4)(DI), CX; \ ANDL (c*4)(DI), CX; \ XORL DX, AX; \ XORL CX, BX; \ MOVL (a*4)(DI), DX; \ MOVL (b*4)(DI), CX; \ RORL $22, DX; \ ANDL (a*4)(DI), CX; \ XORL CX, BX; \ XORL DX, AX; \ ADDL AX, BX + // Calculate T1 and T2, then e = d + T1 and a = T1 + T2. // The values for e and a are stored in d and h, ready for rotation. #define SHA256ROUND(index, const, a, b, c, d, e, f, g, h) \ SHA256T1(const, e, f, g, h); \ MOVL AX, 292(SP); \ SHA256T2(a, b, c); \ MOVL 292(SP), AX; \ ADDL AX, BX; \ ADDL AX, (d*4)(DI); \ MOVL BX, (h*4)(DI) + #define SHA256ROUND0(index, const, a, b, c, d, e, f, g, h) \ MSGSCHEDULE0(index); \ SHA256ROUND(index, const, a, b, c, d, e, f, g, h) + #define SHA256ROUND1(index, const, a, b, c, d, e, f, g, h) \ MSGSCHEDULE1(index); \ SHA256ROUND(index, const, a, b, c, d, e, f, g, h) + +TEXT ·block(SB),0,$296-12 + MOVL p_base+4(FP), SI + MOVL p_len+8(FP), DX + SHRL $6, DX + SHLL $6, DX + + LEAL (SI)(DX*1), DI + MOVL DI, 288(SP) + CMPL SI, DI + JEQ end + + LEAL 256(SP), DI // variables + + MOVL dig+0(FP), BP + MOVL (0*4)(BP), AX // a = H0 + MOVL AX, (0*4)(DI) + MOVL (1*4)(BP), BX // b = H1 + MOVL BX, (1*4)(DI) + MOVL (2*4)(BP), CX // c = H2 + MOVL CX, (2*4)(DI) + MOVL (3*4)(BP), DX // d = H3 + MOVL DX, (3*4)(DI) + MOVL (4*4)(BP), AX // e = H4 + MOVL AX, (4*4)(DI) + MOVL (5*4)(BP), BX // f = H5 + MOVL BX, (5*4)(DI) + MOVL (6*4)(BP), CX // g = H6 + MOVL CX, (6*4)(DI) + MOVL (7*4)(BP), DX // h = H7 + MOVL DX, (7*4)(DI) + +loop: + MOVL SP, BP // message schedule + + SHA256ROUND0(0, 0x428a2f98, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND0(1, 0x71374491, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND0(2, 0xb5c0fbcf, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND0(3, 0xe9b5dba5, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND0(4, 0x3956c25b, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND0(5, 0x59f111f1, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND0(6, 0x923f82a4, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND0(7, 0xab1c5ed5, 1, 2, 3, 4, 5, 6, 7, 0) + SHA256ROUND0(8, 0xd807aa98, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND0(9, 0x12835b01, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND0(10, 0x243185be, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND0(11, 0x550c7dc3, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND0(12, 0x72be5d74, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND0(13, 0x80deb1fe, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND0(14, 0x9bdc06a7, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND0(15, 0xc19bf174, 1, 2, 3, 4, 5, 6, 7, 0) + + SHA256ROUND1(16, 0xe49b69c1, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND1(17, 0xefbe4786, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND1(18, 0x0fc19dc6, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND1(19, 0x240ca1cc, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND1(20, 0x2de92c6f, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND1(21, 0x4a7484aa, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND1(22, 0x5cb0a9dc, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND1(23, 0x76f988da, 1, 2, 3, 4, 5, 6, 7, 0) + SHA256ROUND1(24, 0x983e5152, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND1(25, 0xa831c66d, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND1(26, 0xb00327c8, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND1(27, 0xbf597fc7, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND1(28, 0xc6e00bf3, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND1(29, 0xd5a79147, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND1(30, 0x06ca6351, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND1(31, 0x14292967, 1, 2, 3, 4, 5, 6, 7, 0) + SHA256ROUND1(32, 0x27b70a85, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND1(33, 0x2e1b2138, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND1(34, 0x4d2c6dfc, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND1(35, 0x53380d13, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND1(36, 0x650a7354, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND1(37, 0x766a0abb, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND1(38, 0x81c2c92e, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND1(39, 0x92722c85, 1, 2, 3, 4, 5, 6, 7, 0) + SHA256ROUND1(40, 0xa2bfe8a1, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND1(41, 0xa81a664b, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND1(42, 0xc24b8b70, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND1(43, 0xc76c51a3, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND1(44, 0xd192e819, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND1(45, 0xd6990624, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND1(46, 0xf40e3585, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND1(47, 0x106aa070, 1, 2, 3, 4, 5, 6, 7, 0) + SHA256ROUND1(48, 0x19a4c116, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND1(49, 0x1e376c08, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND1(50, 0x2748774c, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND1(51, 0x34b0bcb5, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND1(52, 0x391c0cb3, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND1(53, 0x4ed8aa4a, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND1(54, 0x5b9cca4f, 2, 3, 4, 5, 6, 7, 0, 1) + SHA26ROUND1(55, 0x682e6ff3, 1, 2, 3, 4, 5, 6, 7, 0) + SHA256ROUND1(56, 0x748f82ee, 0, 1, 2, 3, 4, 5, 6, 7) + SHA256ROUND1(57, 0x78a5636f, 7, 0, 1, 2, 3, 4, 5, 6) + SHA256ROUND1(58, 0x84c87814, 6, 7, 0, 1, 2, 3, 4, 5) + SHA256ROUND1(59, 0x8cc70208, 5, 6, 7, 0, 1, 2, 3, 4) + SHA256ROUND1(60, 0x90befffa, 4, 5, 6, 7, 0, 1, 2, 3) + SHA256ROUND1(61, 0xa4506ceb, 3, 4, 5, 6, 7, 0, 1, 2) + SHA256ROUND1(62, 0xbef9a3f7, 2, 3, 4, 5, 6, 7, 0, 1) + SHA256ROUND1(63, 0xc67178f2, 1, 2, 3, 4, 5, 6, 7, 0) + + MOVL dig+0(FP), BP + MOVL (0*4)(BP), AX // H0 = a + H0 + ADDL (0*4)(DI), AX + MOVL AX, (0*4)(DI) + MOVL AX, (0*4)(BP) + MOVL (1*4)(BP), BX // H1 = b + H1 + ADDL (1*4)(DI), BX + MOVL BX, (1*4)(DI) + MOVL BX, (1*4)(BP) + MOVL (2*4)(BP), CX // H2 = c + H2 + ADDL (2*4)(DI), CX + MOVL CX, (2*4)(DI) + MOVL CX, (2*4)(BP) + MOVL (3*4)(BP), DX // H3 = d + H3 + ADDL (3*4)(DI), DX + MOVL DX, (3*4)(DI) + MOVL DX, (3*4)(BP) + MOVL (4*4)(BP), AX // H4 = e + H4 + ADDL (4*4)(DI), AX + MOVL AX, (4*4)(DI) + MOVL AX, (4*4)(BP) + MOVL (5*4)(BP), BX // H5 = f + H5 + ADDL (5*4)(DI), BX + MOVL BX, (5*4)(DI) + MOVL BX, (5*4)(BP) + MOVL (6*4)(BP), CX // H6 = g + H6 + ADDL (6*4)(DI), CX + MOVL CX, (6*4)(DI) + MOVL CX, (6*4)(BP) + MOVL (7*4)(BP), DX // H7 = h + H7 + ADDL (7*4)(DI), DX + MOVL DX, (7*4)(DI) + MOVL DX, (7*4)(BP) + + ADDL $64, SI + CMPL SI, 288(SP) + JB loop + +end: + RET
このファイルは新規追加されたもので、SHA-256のブロック処理を386アセンブリ言語で記述しています。GoのアセンブリはPlan 9アセンブラの文法に準拠しており、
TEXT
ディレクティブで関数を定義し、MOVL
(32ビット移動)、ADDL
(32ビット加算)、XORL
(排他的論理和)、RORL
(ビット回転)、SHRL
(論理右シフト)、BSWAPL
(バイトスワップ)などの命令を駆使してSHA-256の各ステップを実装しています。 特に注目すべきは、#define
で定義されたマクロ群です。これらはSHA-256アルゴリズムの複雑な計算(メッセージスケジューリング、T1/T2の計算、ラウンド処理)を抽象化し、コードの再利用性と可読性を高めています。 -
src/pkg/crypto/sha256/sha256block_decl.go
--- a/src/pkg/crypto/sha256/sha256block_decl.go +++ b/src/pkg/crypto/sha256/sha256block_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. -// +build amd64 +// +build 386 amd64 package sha256
このファイルは、アセンブリで実装された
block
関数のGo言語側の宣言を含んでいます。元々は+build amd64
(amd64アーキテクチャでのみビルド)となっていましたが、+build 386 amd64
に変更されました。これにより、386アーキテクチャでもこの宣言が有効になり、Go言語のコードからアセンブリ実装のblock
関数を呼び出せるようになります。
これらの変更により、Goのビルドシステムは、386アーキテクチャでコンパイルする際に、Go言語で書かれた汎用実装ではなく、新しく追加された最適化された386アセンブリ実装を自動的に選択するようになります。
コアとなるコードの解説
src/pkg/crypto/sha256/sha256block_386.s
ファイルは、SHA-256のブロック処理の心臓部であり、その効率的な実装がパフォーマンス向上に直結しています。
アセンブリマクロの詳細
ファイル冒頭に定義されているマクロは、SHA-256アルゴリズムの各ステップをアセンブリ命令のシーケンスに変換するためのものです。
-
MSGSCHEDULE0(index)
:- メッセージスケジューリングの最初の16ワード (W0-W15) を処理します。
MOVL (index*4)(SI), AX
: 入力メッセージブロックから32ビットワードをAX
レジスタにロードします。SI
は入力メッセージブロックのベースアドレス、index*4
はワードのオフセットです(各ワードは4バイト)。BSWAPL AX
:AX
レジスタ内のバイト順序を反転させます。SHA-256はビッグエンディアンで処理されるため、リトルエンディアンのx86システムではこのバイトスワップが必要です。MOVL AX, (index*4)(BP)
: バイトスワップされたワードをメッセージスケジュール配列(スタック上のBP
レジスタが指す領域)に保存します。
-
MSGSCHEDULE1(index)
:- メッセージスケジューリングの残りの48ワード (W16-W63) を処理します。
Wt = σ1(Wt-2) + Wt-7 + σ0(Wt-15) + Wt-16
の計算をアセンブリ命令で実装しています。ROTR
(Rotate Right) とSHR
(Shift Right) 命令を組み合わせて、σ0
とσ1
関数を効率的に実行します。- 複数のレジスタ(
AX
,BX
,CX
,DX
)を一時的な計算に利用し、中間結果を保持します。
-
SHA256T1(const, e, f, g, h)
:- SHA-256ラウンドのT1項 (
T1 = h + Σ1(e) + Ch(e,f,g) + Kt + Wt
) を計算します。 const
はラウンド定数Kt
です。e, f, g, h
はSHA-256の状態変数に対応するレジスタインデックスです。BIGSIGMA1(e)
(Σ1(e)
) とCh(e,f,g)
((e AND f) XOR (NOT e AND g)
) の計算をビット論理命令 (ANDL
,XORL
,NOTL
) と回転命令 (RORL
) で行います。ADDL
命令で各項を加算し、最終的なT1値をAX
レジスタに格納します。
- SHA-256ラウンドのT1項 (
-
SHA256T2(a, b, c)
:- SHA-256ラウンドのT2項 (
T2 = Σ0(a) + Maj(a,b,c)
) を計算します。 a, b, c
はSHA-256の状態変数に対応するレジスタインデックスです。BIGSIGMA0(a)
(Σ0(a)
) とMaj(a,b,c)
((a AND b) XOR (a AND c) XOR (b AND c)
) の計算をビット論理命令と回転命令で行います。- 最終的なT2値を
BX
レジスタに格納します。
- SHA-256ラウンドのT2項 (
-
SHA256ROUND(...)
:- 各ラウンドのメインロジックをカプセル化します。
SHA256T1
とSHA256T2
を呼び出し、その結果を使って状態変数a
とe
を更新します。MOVL AX, 292(SP)
のように、スタックポインタ (SP
) を基準としたオフセットで一時的な値を保存・ロードし、レジスタの競合を避けています。
TEXT ·block(SB)
関数
これは、Go言語から呼び出される実際のSHA-256ブロック処理関数です。
-
引数のロードと初期化:
p_base+4(FP)
とp_len+8(FP)
は、Go言語の関数呼び出し規約に従って、入力メッセージのポインタと長さをフレームポインタ (FP
) からロードします。SI
レジスタに入力メッセージのベースアドレス、DX
レジスタに長さを設定します。SHRL $6, DX
とSHLL $6, DX
は、長さを64バイト(SHA-256のブロックサイズ)の倍数に切り詰める操作です。LEAL 256(SP), DI
は、スタック上にSHA-256の状態変数(a-h)を格納するための領域を確保し、そのアドレスをDI
レジスタに設定します。dig+0(FP)
から初期ハッシュ値 (H0-H7) をロードし、DI
が指すスタック上の領域にコピーします。
-
メインループ (
loop
):- 入力メッセージブロックが残っている限り、ループを繰り返します。
MOVL SP, BP
: メッセージスケジュール配列のベースアドレスとしてBP
レジスタに現在のスタックポインタをコピーします。SHA256ROUND0
とSHA256ROUND1
マクロを64回呼び出し、SHA-256の64ラウンドを実行します。各呼び出しには、ラウンドインデックス、ラウンド定数、および状態変数のレジスタインデックスが渡されます。
-
ハッシュ値の更新と終了:
- ループが終了した後、最終的な状態変数 (a-h) を元のハッシュ値 (H0-H7) に加算し、結果を
dig+0(FP)
が指すメモリ(Go言語側のハッシュコンテキスト)に書き戻します。 ADDL $64, SI
: 処理したメッセージブロックのサイズ(64バイト)分、入力メッセージポインタSI
を進めます。CMPL SI, 288(SP)
とJB loop
: 次のメッセージブロックがあるかチェックし、あればloop
ラベルにジャンプして処理を続行します。RET
: 関数から戻ります。
- ループが終了した後、最終的な状態変数 (a-h) を元のハッシュ値 (H0-H7) に加算し、結果を
このアセンブリコードは、SHA-256アルゴリズムの各ステップを、386アーキテクチャの命令セットとレジスタを最大限に活用して、非常に細かく最適化された形で実装しています。これにより、Go言語の汎用実装では達成できないレベルのパフォーマンスが実現されています。
関連リンク
- Go言語の暗号パッケージ: https://pkg.go.dev/crypto/sha256
- Go言語のアセンブリについて: https://go.dev/doc/asm
- FIPS 180-4 (Secure Hash Standard): https://csrc.nist.gov/publications/detail/fips/180/4/archive/2012-03-06
参考にした情報源リンク
- Go言語のソースコード (crypto/sha256): https://github.com/golang/go/tree/master/src/crypto/sha256
- Intel 64 and IA-32 Architectures Software Developer's Manuals: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html (x86アセンブリ命令の詳細)
- SHA-256 Wikipedia: https://ja.wikipedia.org/wiki/SHA-2
- Go Assembly by Example: https://go.dev/doc/articles/go_asm.html