- vpternlogd in AVX-512 ist eine SIMD-Instruktion, die beliebige bitweise Boolesche Logik mit drei Eingaben in einem Schritt ausführt, ausgewählt per 8-Bit-Immediate
- Der Kern ist nicht, sich komplizierte Logikformeln zu merken, sondern
#imm8 als Lookup-Tabelle für die 8 möglichen Fälle der drei Eingaben A/B/C zu betrachten
- Schon der Amiga-Blitter von 1985 spezifizierte auf dieselbe Weise logische Kombinationen mit drei Bitmap-Quellen und einem 8-Bit-Minterm; für masked Sprites wurde oft
0xE2 verwendet
- Liest man die 8 Bits der Ergebnisspalte von unten nach oben, erhält man den Immediate-Wert, sodass sich auch Bedingungen wie „genau zwei der drei Eingaben sind 1“ direkt als
0x68 konstruieren lassen
- Das
vpternlogd-Beispiel aus der Intel-Dokumentation und das in der Amiga-Demoszene verbreitete 0xE2 treffen hier zusammen und zeigen, dass moderne SIMD-Instruktionen und Retro-Grafikhardware dieselbe Designidee teilen
vpternlogd in AVX-512
- vpternlogd ist eine bitweise ternäre Logik-Instruktion, die in den Präsentationsunterlagen zum AVX-512-ISA-Design auffällt
- Sie nimmt drei Eingaben A, B und C an und kann beliebige Boolesche Logik in einer einzigen Instruktion ausdrücken
- Beispiel:
(NOT A) OR ((NOT B) XOR (C AND A))
- Die Eingaben können 512-Bit-Register sein, sodass sich komplexe Logik gleichzeitig auf alle 512 Bit anwenden lässt
- Statt fortlaufend Spezialinstruktionen wie
foo_and_a_or_not_b hinzuzufügen, verarbeitet eine flexible Instruktion zusammen mit einem 8-Bit-Immediate viele verschiedene Logikfunktionen
VPTERNLOGD r0, r1, r3, #imm8
#imm8 bestimmt, welche bitlogische Funktion ausgeführt wird
- Viele Dokumente erklären nur, dass der Immediate „eine bestimmte Binärfunktion festlegt“, wodurch die eigentliche Berechnung schwer intuitiv zu erfassen ist
Amiga-Blitter und Minterm
- In den 1980er Jahren war es üblich, für die Grafikverarbeitung Custom Chips einzusetzen
- Der Blitter des Commodore Amiga 500 verschob Bitmap-Grafiken von einer Position zur anderen und konnte dabei logische Operationen anwenden
- Er konnte bis zu drei Bitmap-Quellen verarbeiten, und die logische Verknüpfung zwischen ihnen wurde durch einen 8-Bit-Wert, den Minterm, festgelegt
- Darin ähnelt seine Struktur
vpternlogd: Drei Quellen plus ein 8-Bit-Wert wählen eine logische Kombination aus
- Viele Amiga-Programmierer verstanden die Berechnung von Minterms nicht direkt, sondern verwendeten häufig gebrauchte Werte wieder
- Zum Löschen eines Puffers:
0x00
- Zum Zeichnen eines masked Sprite:
0xE2
- Das „Amiga Hardware Reference Manual“ von 1989 erklärte die Minterm-Berechnung mit verwirrender Symbolik und war für damalige Demo-Programmierer wenig hilfreich
Den 8-Bit-Wert als Lookup-Tabelle berechnen
#imm8 beziehungsweise der Minterm lässt sich nicht als Kombination von Logikoperatoren, sondern als Lookup-Tabelle mit 8 Einträgen verstehen
- Da die drei Eingaben A, B und C jeweils 0 oder 1 sein können, gibt es 8 mögliche Kombinationen
- Man muss nur das gewünschte Ergebnis direkt in die vierte Spalte eintragen
| A |
B |
C |
Gewünschtes Ergebnis |
| 0 |
0 |
0 |
? |
| 0 |
0 |
1 |
? |
| 0 |
1 |
0 |
? |
| 0 |
1 |
1 |
? |
| 1 |
0 |
0 |
? |
| 1 |
0 |
1 |
? |
| 1 |
1 |
0 |
? |
| 1 |
1 |
1 |
? |
- Möchte man zum Beispiel genau dann 1 als Ergebnis erhalten, wenn von den drei Eingaben genau zwei 1 sind, trägt man in die vierte Spalte entsprechend passende Werte ein
- Liest man die 8 Bits dieser Ergebnisspalte von unten nach oben, erhält man
01101000, also 0x68
- Die Funktion
0x68 setzt das Ergebnis also genau dann auf 1, wenn zwei der drei Eingaben 1 sind
- Auf dieselbe Weise lässt sich der benötigte
#imm8-Wert für jede beliebige logische Funktion zwischen drei Quellen bestimmen
0xE2 für masked Sprites
- Einer der auf dem Amiga besonders häufig verwendeten Minterm-Werte ist
0xE2
- Dieser Wert wird oft beim Rendern eines masked 2D-Sprite verwendet
- A: Sprite-Bitmap
- B: Sprite-Maske
- C: Hintergrund
- Die Bedingung lässt sich als einfache Programmlogik ausdrücken
- Wenn das Maskenpixel B gesetzt ist, ist das Ergebnis Sprite A
- Wenn das Maskenpixel B nicht gesetzt ist, ist das Ergebnis Hintergrund C
| A |
B |
C |
Gewünschtes Ergebnis |
| 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 |
- Liest man die Ergebnisspalte von unten nach oben, erhält man
11100010, also 0xE2
0xE2 ist in der Amiga-Demoszene ein sehr verbreiteter Minterm-Wert
Zufällige Verbindung zur Intel-Dokumentation
1 Kommentare
Hacker-News-Kommentare
Es gibt eine einfache Möglichkeit, den Immediate-Wert aus dem Ausdruck zu erhalten, den man berechnen will. Wenn man zum Beispiel
(NOT A) OR ((NOT B) XOR (C AND A))berechnen möchte, kann man das so schreiben:~_MM_TERNLOG_A | (~_MM_TERNLOG_B ^ (_MM_TERNLOG_C & _MM_TERNLOG_A))Das ist buchstäblich der Ausdruck, den man berechnen will, und er wird aus den in den Intrinsic-Headern von gcc und clang definierten Konstanten
_MM_TERNLOG_A/B/Czum Immediate-Wert ausgewertet:typedef enum { _MM_TERNLOG_A = 0xF0, _MM_TERNLOG_B = 0xCC, _MM_TERNLOG_C = 0xAA } _MM_TERNLOG_ENUM;In MSVC kann man sie einfach selbst definieren.
A = 0b11110000,B = 0b11001100,C = 0b10101010Nur vom Titel her dachte ich, die Anweisung funktioniere nicht richtig. Der eigentliche Artikel erklärt einfach nur ihre Funktionsweise.
Mein Teenager-Ich hat auf diese Seite im Hardware-Handbuch zwar nicht „CRAP!“ geschrieben, aber ich habe wirklich lange daraufgestarrt, um sie zu verstehen.
Am Ende habe ich, wie fast alle, die Bobs und BLTCON0 für einfaches Kopieren nachgeschlagen und so getan, als hätte ich diesen Teil nicht gesehen.
Trotzdem habe ich ein paar Jahre später an der Uni in mathematischer Logik eine 1+ bekommen; vielleicht hat dieses Trauma also bis zu einem gewissen Grad geholfen.
Zum Titel: „ternary logic“ bezeichnet normalerweise Logik mit drei Wahrheitswerten. Dieser Artikel behandelt aber eine Compiler-Instruktion für beliebige binäre Logikgatter mit drei Eingaben.
ternlog, und das Intrinsic heißt ebenfallsternarylogic; daher ist der Titel, wenn auch etwas unglücklich, passend.Außerdem grenzt
bitwisees bereits ein Stück weit von „dreiwertiger Logik“ ab, undternarywird sehr häufig auch im Sinn von drei Eingaben verwendet.a ? b : cnennt man ebenfalls oft den ternären Operator, und tatsächlich kannternlogdiese ternäre Operation nachbilden; genau darum geht es im Artikel auch.trinarystattternary.Aus Sicht von C++, JavaScript und Python scheint eine ternäre Expression der Form
(a < b) ? 5 : 2die häufigste Verwendung zu sein. https://www.programiz.com/cpp-programming/ternary-operatorWie auch immer, es ist nicht besonders wichtig, welche Annahme man trifft. Wörter und Wendungen haben nun einmal mehrere Bedeutungen, und
ternarybedeutet aus drei Teilen bestehend, also passt es auch hier.ternarybedeutet hier nicht das.In C ist
+ein binärer Operator, weil er zwei Eingaben nimmt, und?:ein ternärer Operator, weil er drei Eingaben nimmt. Er wird gewöhnlich nur deshalb „the ternary operator“ genannt, weil er in C der einzige ist; grundsätzlich ist daran nichts Besonderes.vpternlogdimplementiert jeden bitweisen ternären Operator mit drei Eingaben.Ich fragte mich, ob das der Windows-Funktion
BitBltähnelt. Die gab es, glaube ich, mindestens seit Windows 3.1; über den Parameteropwird festgelegt, wie Quelle, Ziel und Maske kombiniert werden.Ich erinnere mich an Codenamen wie
BLACKNESS, das unabhängig von den Eingaben Schwarz erzeugt, undCOPY, das die Quelle ins Ziel kopiert.BLACKNESSundWHITENESShatten irgendwie einen poetischen Klang.Aus Petzold habe ich in Erinnerung, dass es zwar in Software implementiert war, der Opcode beim Aufruf aber innerhalb der Funktion in maßgeschneiderten Assembler übersetzt wurde; das wirkte wie ein seltener Fall von selbstmodifizierendem Code im Windows-Betriebssystem.
BitBltverwendete ursprünglich komplexe 16-Bit-„operation codes“, die binäre Operationen in umgekehrter polnischer Notation speicherten.Später kam, ähnlich wie beim Amiga, ein „operation index“ hinzu, der dieselben Informationen in 1 Byte fasste; kürzer und eleganter. Heute enthält jeder Raster-Operation-Code sowohl den operation index als auch den operation code, sodass die Codierung redundant ist. https://devblogs.microsoft.com/oldnewthing/20180528-00/?p=98...
Das ist dasselbe Prinzip, nach dem ein FPGA beliebige logische Funktionen als Lookup-Tabelle implementiert.
Abgesehen von Spezialzwecken wie FPGAs oder der Instruktion in diesem Artikel macht man das normalerweise aber nicht. Schnelle Register und statisches RAM werden bisweilen aus Logik gebaut, häufiger ist es jedoch, sie direkt aus Transistoren statt aus Gates aufzubauen.
74181macht das auch so.Wenn man auf https://www.sandpile.org geht und auf der Seite mit 3-Byte-Opcodes https://www.sandpile.org/x86/opc_3.htm nach
VPTERNLOGsucht, sieht man auch AVX512BITALG2, offenbar von Intel früher geplante Varianten mit Byte-/Word-Masking.Außerdem wird von dort auf eine Seite mit der ternären Wahrheitstabelle für alle 256 Fälle im Operanden
Ibverlinkt: https://www.sandpile.org/x86/ternlog.htmDass in der Dokumentation die Funktion
E2als Beispiel gewählt wurde, könnte daran liegen, dass sie unter den booleschen Funktionen mit drei Eingaben so ziemlich der grundlegendste und standardmäßigste Mux ist: wennB, dannA, sonstC.Das ist eine universelle Funktion, man muss also kein Amiga-Fan sein, um sie auszuwählen. Natürlich könnte man trotzdem einer gewesen sein.
Ein weiteres Beispiel dafür, bitweise Operationen in Integern zu kodieren, sind die GDI-ROP-Codes von Win32: https://learn.microsoft.com/en-us/windows/win32/gdi/ternary-...
Das offizielle Amiga-Hardwarehandbuch hatte ich nicht; stattdessen habe ich das Buch „Mapping the Amiga“ gelesen. Es erklärte dasselbe etwas ausführlicher.
Ich weiß nicht mehr, welche Minterme damals verwendet wurden, aber mithilfe dieses Buches habe ich es irgendwie geschafft, Dinge wie shadebobs, bobs und XOR-3D-Linienzeichnen zu implementieren.
Die entsprechende Seite aus Mapping the Amiga: https://archive.org/details/1993-thomson-randy-rhett-anderso...