2 ポイント 投稿者 GN⁺ 2024-10-07 | 1件のコメント | WhatsAppで共有
  • 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件のコメント

 
GN⁺ 2024-10-07
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 では自分で定義すればよい。

    • 魔法のように見えないようにするには、2進数で書けばよい: A = 0b11110000, B = 0b11001100, C = 0b10101010
    • Amiga のマニュアルは、加法標準形に正規化することを勧めている。
  • タイトルだけ見て、この命令が正しく動作しないという意味だと思った。実際の記事は単に動作の仕組みを説明している。

    • ここでの “busted” は、Intel の人たちが Amiga ファンだと見抜いた、という意味だと理解した。著者の母語であるフランス語から英語に移る過程で、何かが抜け落ちたように思える。
  • 10代のころの私は、ハードウェアマニュアルのそのページに “CRAP!” と書き込んだわけではないが、理解しようとして本当に長い時間にらめっこしていた。
    結局、ほとんどの人がそうしたように、Bobs と単純コピー用の BLTCON0 を見つけて使い、その部分は見なかったことにした。
    それでも数年後、大学で計算論理学の A+ を取ったので、そのトラウマも多少は役に立ったのかもしれない。

  • タイトルについて言えば、“ternary logic” は通常、3つの真理値を持つ論理を意味する。しかしこの記事は、3入力を受け取るあらゆる二値論理ゲートを扱うコンパイラ命令についてのものだ。

    • x86 命令名が ternlog で、intrinsic も ternarylogic なので、残念ではあるがタイトルは適切だ。
      さらに bitwise がすでに「3値論理」との区別にある程度役立っており、ternary は3入力という意味でも非常によく使われる。a ? b : c も一般に三項演算子と呼ばれ、実際に ternlog はこの三項演算を模倣でき、記事もまさにその内容を扱っている。
    • 「三値論理命令」ではなく、「三項である論理命令」に近い。
    • 3つの真理値を持つ論理が何なのか分からない。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入力を取るあらゆるビット単位の三項演算子を実装する。
    • 私もこの点で混乱した。ただ「3つの項からなる二値式を評価する」という名前は、あまり簡潔ではない。
  • Windows の BitBlt 関数に似たものなのかと思った。少なくとも Windows 3.1 のころからあったように思うが、op パラメータでソース、ターゲット、マスクをどう組み合わせるかを指定する。
    入力に関係なく黒を作る BLACKNESS、ソースをターゲットへコピーする COPY のようなコード名があったと記憶している。BLACKNESSWHITENESS は、どこか詩的な響きがあった。
    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 が任意の論理関数をルックアップテーブルとして実装する方法と同じだ。

    • 基本的に CPU、GPU、FPGA はすべて、計算というカニに似た同じ進化形へ収束している。互いに異なる最適化領域を持ちながら、同じ能力を公開している。
    • すべての論理はメモリで実装でき、すべてのメモリは何らかのフィードバック構成を持つ論理で実装できる。
      ただし FPGA やこの記事の命令のような特殊用途を除けば、通常はそうしない。高速レジスタや静的 RAM はときどき論理で作られるが、ゲートよりもトランジスタで直接作るほうが一般的だ。
    • ほとんどはそうだが、すべてではない。Actel/Microsemi は小さなマルチプレクサとゲートのツリーを使っている。
    • 74181 算術論理演算装置もそうしている。
  • https://www.sandpile.org に行き、3バイト opcode のページ https://www.sandpile.org/x86/opc_3.htmVPTERNLOG を探すと、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...