- AVX-512の vpternlogd は、3つの入力に対する任意のビット単位 Boolean 論理を、8ビット即値で選んで一度に実行する SIMD 命令である
- 重要なのは複雑な論理式を暗記することではなく、
#imm8 を3つの入力 A/B/C の8通りのケースに対するルックアップテーブルとして見ることにある
- 1985年の Amiga blitter も、3つのビットマップソースと8ビットの minterm で同じ方式の論理組み合わせを指定しており、masked sprite では
0xE2 がよく使われていた
- 結果列の8ビットを下から上に読めば即値になるため、「3つの入力のうちちょうど2つだけが1」のような条件も
0x68 として直接作れる
- Intel 文書の vpternlogd の例と、Amiga demoscene でよく見られる
0xE2 がつながっており、現代の SIMD 命令とレトロなグラフィックスハードウェアが同じ設計感覚を共有している
AVX-512の vpternlogd
- vpternlogd は、AVX-512 ISA 設計の発表資料で目を引いたビット単位の三項論理命令である
- 3つの入力 A、B、C を受け取り、任意の Boolean 論理を1命令で表現できる
- 例:
(NOT A) OR ((NOT B) XOR (C AND A))
- 入力は 512ビットレジスタ にできるため、複雑な論理を512ビット全体に同時適用できる
foo_and_a_or_not_b のような専用命令を増やし続ける代わりに、1つの柔軟な命令と8ビット即値で多様な論理を処理する
VPTERNLOGD r0, r1, r3, #imm8
#imm8 は、どのビット論理関数を実行するかを決める
- 多くの文書では、即値が「特定の二項関数を決定する」とだけ説明されており、実際の計算方法を直感的に把握しづらい
Amiga blitter と minterm
- 1980年代のコンピュータでは、グラフィックス処理のために カスタムチップ を使うことがよくあった
- Commodore Amiga 500 の blitter は、ビットマップグラフィックスをある場所から別の場所へ移動しながら論理演算を適用していた
- 最大3つのビットマップソースを扱い、それらの間の論理演算は8ビット値の minterm で指定した
- 3つのソースと8ビット値で論理の組み合わせを選ぶという点で、vpternlogd と同じ構造を持つ
- 多くの Amiga プログラマは minterm の計算方法を直接理解するよりも、よく使う値を再利用していた
- バッファを消去するときは
0x00
- masked sprite を描画するときは
0xE2
- 1989年の “Amiga Hardware Reference Manual” は、minterm の計算を分かりにくい記号で説明しており、当時のデモ制作者たちにとってあまり役に立たなかった
8ビット値をルックアップテーブルとして計算する
#imm8 または minterm は、論理演算子の組み合わせではなく、8項目のルックアップテーブルとして見なせる
- 3つの入力 A、B、C はそれぞれ 0 または 1 なので、可能な組み合わせは8通りある
- 欲しい結果を4列目に直接埋めればよい
| A |
B |
C |
欲しい結果 |
| 0 |
0 |
0 |
? |
| 0 |
0 |
1 |
? |
| 0 |
1 |
0 |
? |
| 0 |
1 |
1 |
? |
| 1 |
0 |
0 |
? |
| 1 |
0 |
1 |
? |
| 1 |
1 |
0 |
? |
| 1 |
1 |
1 |
? |
- たとえば、3つの入力のうちちょうど2つだけが1のときに結果を1にしたいなら、4列目にその条件に合う値を埋める
- その結果列の8ビットを下から上へ読むと
01101000、すなわち 0x68 になる
- したがって
0x68 の関数は、3つの入力のうちちょうど2つが1のときに結果を1に設定する
- 同じ方法で、3つのソース間の任意の論理関数に必要な
#imm8 の値を得られる
masked sprite の 0xE2
- Amiga で非常によく使われる minterm 値の1つが
0xE2 である
- この値は masked 2D sprite をレンダリングするときによく使われる
- A: sprite bitmap
- B: sprite mask
- C: background
- 条件は単純なプログラムロジックで表現できる
- mask ピクセル B がセットされていれば、結果は sprite A
- mask ピクセル B がセットされていなければ、結果は background C
| A |
B |
C |
欲しい結果 |
| 0 |
0 |
0 |
0 |
| 0 |
0 |
1 |
1 |
| 0 |
1 |
0 |
0 |
| 0 |
1 |
1 |
0 |
| 1 |
0 |
0 |
0 |
| 1 |
0 |
1 |
1 |
| 1 |
1 |
0 |
1 |
| 1 |
1 |
1 |
1 |
- 結果列を下から上に読むと
11100010、すなわち 0xE2 になる
0xE2 は Amiga demoscene 文化で非常によく見られる minterm 値である
Intel 文書との思いがけないつながり
1件のコメント
Hacker News のコメント
計算したい式から即値を得る簡単な方法がある。たとえば
(NOT A) OR ((NOT B) XOR (C AND A))を計算したいなら、~_MM_TERNLOG_A | (~_MM_TERNLOG_B ^ (_MM_TERNLOG_C & _MM_TERNLOG_A))のように書けばよい。文字どおり計算したい式であり、gcc と clang の intrinsic ヘッダーに定義されている
_MM_TERNLOG_A/B/C定数から即値へ評価される:typedef enum { _MM_TERNLOG_A = 0xF0, _MM_TERNLOG_B = 0xCC, _MM_TERNLOG_C = 0xAA } _MM_TERNLOG_ENUM;MSVC では自分で定義すればよい。
A = 0b11110000,B = 0b11001100,C = 0b10101010タイトルだけ見て、この命令が正しく動作しないという意味だと思った。実際の記事は単に動作の仕組みを説明している。
10代のころの私は、ハードウェアマニュアルのそのページに “CRAP!” と書き込んだわけではないが、理解しようとして本当に長い時間にらめっこしていた。
結局、ほとんどの人がそうしたように、Bobs と単純コピー用の BLTCON0 を見つけて使い、その部分は見なかったことにした。
それでも数年後、大学で計算論理学の A+ を取ったので、そのトラウマも多少は役に立ったのかもしれない。
タイトルについて言えば、“ternary logic” は通常、3つの真理値を持つ論理を意味する。しかしこの記事は、3入力を受け取るあらゆる二値論理ゲートを扱うコンパイラ命令についてのものだ。
ternlogで、intrinsic もternarylogicなので、残念ではあるがタイトルは適切だ。さらに
bitwiseがすでに「3値論理」との区別にある程度役立っており、ternaryは3入力という意味でも非常によく使われる。a ? b : cも一般に三項演算子と呼ばれ、実際にternlogはこの三項演算を模倣でき、記事もまさにその内容を扱っている。ternaryではなくtrinaryのことを考えているのではないかと思う。C++、JavaScript、Python の観点では、
(a < b) ? 5 : 2という形の三項式が最も一般的な用例に見える。https://www.programiz.com/cpp-programming/ternary-operatorいずれにせよ、どんな前提を置いても大きな問題ではない。単語や句は複数の意味を持つものだし、
ternaryは3つの部分から成るという意味なので、ここにも合っている。ternaryはそういう意味ではない。C では
+は入力を2つ取るので二項演算子であり、?:は入力を3つ取るので三項演算子である。C では唯一なので普通 “the ternary operator” と呼ばれるだけで、本質的に特別なものではない。vpternlogdは3入力を取るあらゆるビット単位の三項演算子を実装する。Windows の
BitBlt関数に似たものなのかと思った。少なくとも Windows 3.1 のころからあったように思うが、opパラメータでソース、ターゲット、マスクをどう組み合わせるかを指定する。入力に関係なく黒を作る
BLACKNESS、ソースをターゲットへコピーするCOPYのようなコード名があったと記憶している。BLACKNESSとWHITENESSは、どこか詩的な響きがあった。Petzold で読んだ記憶ではソフトウェアで実装されているが、呼び出し時に opcode が関数内部でカスタムアセンブリへ変換される方式で、Windows OS では珍しい自己書き換えコードの例だったようだ。
BitBltはもともと、二値演算を逆ポーランド記法で格納する複雑な16ビットの “operation codes” を使っていた。その後 Amiga のように、同じ情報を1バイトに収める “operation index” が追加された。こちらのほうが短くてエレガントだ。現在では各 raster operation code が operation index と operation code の両方を含んでおり、エンコーディングが重複している。https://devblogs.microsoft.com/oldnewthing/20180528-00/?p=98...
FPGA が任意の論理関数をルックアップテーブルとして実装する方法と同じだ。
ただし FPGA やこの記事の命令のような特殊用途を除けば、通常はそうしない。高速レジスタや静的 RAM はときどき論理で作られるが、ゲートよりもトランジスタで直接作るほうが一般的だ。
74181算術論理演算装置もそうしている。https://www.sandpile.org に行き、3バイト opcode のページ https://www.sandpile.org/x86/opc_3.htm で
VPTERNLOGを探すと、Intel が過去に計画していたと思われる、バイト/ワードのマスキング変種である AVX512BITALG2 も見られる。また、
Ibオペランドで全256通りを収めた三項論理表のページ https://www.sandpile.org/x86/ternlog.htm にもリンクされている。ドキュメントの例に
E2関数を選んだのは、それが3入力ブール関数の中でもほぼ最も基本的で標準的な mux だからかもしれない:BならA、そうでなければC。これは万能関数なので、Amiga ファンでなければ選ばないというものではない。もちろん実際にファンだった可能性もある。
ビット単位演算を整数に収める別の例として、Win32 の GDI ROP コードがある: https://learn.microsoft.com/en-us/windows/win32/gdi/ternary-...
公式の Amiga ハードウェアマニュアルは持っておらず、代わりに “Mapping the Amiga” という本を読んでいた。同じ内容をもう少し冗長に説明していた。
そのときどの minterm を使ったかは覚えていないが、この本を見て shadebobs、bobs、XOR 3D 線描画のようなものをどうにか実装したと思う。
Mapping the Amiga の該当ページ: https://archive.org/details/1993-thomson-randy-rhett-anderso...