- vpternlogd de AVX-512 es una instrucción SIMD que ejecuta de una sola vez cualquier lógica booleana arbitraria a nivel de bit sobre tres entradas, eligiéndola con un valor inmediato de 8 bits
- La clave no está en memorizar expresiones lógicas complejas, sino en ver
#imm8 como una tabla de consulta para los 8 casos posibles de las tres entradas A/B/C
- El blitter del Amiga de 1985 también usaba el mismo método para definir combinaciones lógicas con tres fuentes de mapa de bits y un minterm de 8 bits, y
0xE2 se usaba a menudo para sprites con máscara
- Si se leen los 8 bits de la columna de resultados de abajo hacia arriba, se obtiene el valor inmediato, así que condiciones como “exactamente dos de las tres entradas son 1” también pueden construirse directamente como
0x68
- El ejemplo de
vpternlogd en la documentación de Intel y el común 0xE2 de la demoscene de Amiga se cruzan aquí, mostrando que las instrucciones SIMD modernas y el hardware gráfico retro comparten la misma intuición de diseño
vpternlogd de AVX-512
- vpternlogd es una instrucción ternaria de lógica a nivel de bit que destacó en el material de presentación del diseño de la ISA AVX-512
- Recibe tres entradas A, B y C, y puede expresar cualquier lógica booleana en una sola instrucción
- Ejemplo:
(NOT A) OR ((NOT B) XOR (C AND A))
- Las entradas pueden ser registros de 512 bits, por lo que es posible aplicar una lógica compleja sobre los 512 bits completos al mismo tiempo
- En vez de seguir agregando instrucciones dedicadas como
foo_and_a_or_not_b, se resuelven muchas lógicas con una sola instrucción flexible y un valor inmediato de 8 bits
VPTERNLOGD r0, r1, r3, #imm8
#imm8 decide qué función de lógica de bits se ejecuta
- Muchos documentos solo explican que el valor inmediato “determina cierta función binaria”, lo que dificulta entender de forma intuitiva cómo se calcula realmente
El blitter de Amiga y los minterms
- En las computadoras de los años 80 era común usar chips personalizados para el procesamiento gráfico
- El blitter de la Commodore Amiga 500 aplicaba operaciones lógicas mientras movía gráficos de mapa de bits de una posición a otra
- Podía manejar hasta tres fuentes de mapa de bits, y las operaciones lógicas entre ellas se especificaban con un valor de 8 bits llamado minterm
- Tiene la misma estructura que
vpternlogd en el sentido de que selecciona una combinación lógica a partir de tres fuentes y un valor de 8 bits
- Muchos programadores de Amiga reutilizaban valores frecuentes en vez de entender directamente cómo calcular el minterm
- Para limpiar un búfer:
0x00
- Para dibujar un sprite con máscara:
0xE2
- El “Amiga Hardware Reference Manual” de 1989 explicaba el cálculo de minterms con una notación confusa, así que no fue de gran ayuda para quienes hacían demos en esa época
Calcular el valor de 8 bits como tabla de consulta
#imm8 o el minterm puede verse no como una combinación de operadores lógicos, sino como una tabla de consulta de 8 entradas
- Como las tres entradas A, B y C pueden ser 0 o 1, existen 8 combinaciones posibles
- Basta con llenar directamente la cuarta columna con el resultado deseado
| A |
B |
C |
Resultado deseado |
| 0 |
0 |
0 |
? |
| 0 |
0 |
1 |
? |
| 0 |
1 |
0 |
? |
| 0 |
1 |
1 |
? |
| 1 |
0 |
0 |
? |
| 1 |
0 |
1 |
? |
| 1 |
1 |
0 |
? |
| 1 |
1 |
1 |
? |
- Por ejemplo, si se quiere que el resultado sea 1 cuando exactamente dos de las tres entradas son 1, se llena la cuarta columna con los valores que cumplen esa condición
- Si luego se leen los 8 bits de esa columna de abajo hacia arriba, se obtiene
01101000, es decir 0x68
- Por lo tanto, la función
0x68 pone el resultado en 1 cuando exactamente dos entradas valen 1
- Del mismo modo, se puede obtener el valor
#imm8 necesario para cualquier función lógica arbitraria entre las tres fuentes
El 0xE2 de los sprites con máscara
- Uno de los valores de minterm más comunes en Amiga es
0xE2
- Este valor se usaba con frecuencia al renderizar un sprite 2D con máscara
- A: bitmap del sprite
- B: máscara del sprite
- C: fondo
- La condición puede expresarse con una lógica de programa simple
- Si el píxel de máscara B está activado, el resultado es el sprite A
- Si el píxel de máscara B no está activado, el resultado es el fondo C
| A |
B |
C |
Resultado deseado |
| 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 |
- Si se lee la columna de resultados de abajo hacia arriba, se obtiene
11100010, es decir 0xE2
0xE2 es un valor de minterm muy común en la cultura demoscene de Amiga
Una conexión casual con la documentación de Intel
1 comentarios
Opiniones en Hacker News
Hay una forma sencilla de obtener el valor inmediato a partir de la expresión que se quiere calcular. Por ejemplo, si quieres calcular
(NOT A) OR ((NOT B) XOR (C AND A)), puedes escribirlo como~_MM_TERNLOG_A | (~_MM_TERNLOG_B ^ (_MM_TERNLOG_C & _MM_TERNLOG_A))Es literalmente la expresión que se quiere calcular, y se evalúa como un valor inmediato a partir de las constantes
_MM_TERNLOG_A/B/Cdefinidas en los headers de intrinsics de gcc y clang:typedef enum { _MM_TERNLOG_A = 0xF0, _MM_TERNLOG_B = 0xCC, _MM_TERNLOG_C = 0xAA } _MM_TERNLOG_ENUM;En MSVC se pueden definir manualmente
A = 0b11110000,B = 0b11001100,C = 0b10101010Al ver solo el título pensé que significaba que esta instrucción no funcionaba bien. En realidad, el artículo solo explica cómo funciona
Mi yo adolescente no escribió “CRAP!” en esa página del manual de hardware, pero sí me quedé mirándola muchísimo tiempo tratando de entenderla
Al final, como casi todos, terminé buscando y usando Bobs y BLTCON0 para copias simples, y fingí no haber visto esa parte
Aun así, años después saqué A+ en lógica computacional en la universidad, así que quizá ese trauma ayudó en algo
Sobre el título: “ternary logic” normalmente significa lógica con tres valores de verdad. Pero este artículo trata de una instrucción de compilador que maneja todas las compuertas lógicas binarias con tres entradas
ternlogy el intrinsic también esternarylogic, así que, aunque sea una lástima, el título es adecuadoAdemás,
bitwiseya ayuda en cierta medida a distinguirlo de la “lógica de tres valores”, yternarytambién se usa muy a menudo para indicar tres entradas. Aa ? b : cse le suele llamar operador ternario, y de hechoternlogpuede imitar esa operación ternaria; el artículo trata justo de esotrinaryen vez deternaryDesde la perspectiva de C++, JavaScript y Python, una expresión ternaria de la forma
(a < b) ? 5 : 2parece ser el uso más común. https://www.programiz.com/cpp-programming/ternary-operatorEn cualquier caso, no importa mucho qué suposición se haga. Las palabras o frases suelen tener varios significados, y
ternarysignifica compuesto de tres partes, así que también encaja aquíternaryno significa esoEn C,
+es un operador binario porque recibe dos entradas, y?:es un operador ternario porque recibe tres. Como es el único en C, normalmente se le llama “the ternary operator”, pero no tiene nada intrínsecamente especialvpternlogdimplementa todos los operadores ternarios bit a bit con tres entradasMe pregunto si se parece a la función
BitBltde Windows. Creo que existía al menos desde Windows 3.1, y el parámetroopdefinía cómo combinar origen, destino y máscaraRecuerdo que había nombres de códigos como
BLACKNESS, que genera negro sin importar la entrada, yCOPY, que copia el origen al destino.BLACKNESSyWHITENESStenían un eco extrañamente poéticoPor lo que recuerdo de Petzold, estaba implementado en software, pero al invocarlo el opcode se traducía dentro de la función a ensamblador personalizado, así que parecía ser un raro caso de código automodificable en el sistema operativo Windows
BitBltusaba “operation codes” complejos de 16 bits que almacenaban operaciones binarias en notación polaca inversaLuego, al estilo de Amiga, se agregó un “operation index” que guarda la misma información en 1 byte, más corto y elegante. Hoy cada raster operation code codifica tanto el operation index como el operation code, por lo que la codificación queda duplicada. https://devblogs.microsoft.com/oldnewthing/20180528-00/?p=98...
Es igual a la forma en que una FPGA implementa funciones lógicas arbitrarias con tablas de consulta
Pero salvo para propósitos especiales como las FPGA o la instrucción de este artículo, normalmente no se hace así. Los registros rápidos y la RAM estática a veces se construyen con lógica, pero es más común hacerlos directamente con transistores que con compuertas
74181también lo hace asíSi vas a https://www.sandpile.org y buscas
VPTERNLOGen la página de opcodes de 3 bytes https://www.sandpile.org/x86/opc_3.htm, también puedes ver AVX512BITALG2, una variante de enmascaramiento de bytes/words que Intel parece haber planeado en el pasadoAdemás, desde el operando
Ibenlaza a una página de tabla lógica ternaria con los 256 casos posibles: https://www.sandpile.org/x86/ternlog.htmPuede que hayan elegido la función
E2como ejemplo en la documentación porque es casi la mux más básica y estándar entre las funciones booleanas de tres entradas: siB, entoncesA; si no,CEs una función universal, así que no hace falta ser fan de Amiga para elegirla. Aunque, claro, también podrían haberlo sido
Otro ejemplo de empaquetar operaciones bit a bit dentro de un entero son los códigos ROP de GDI en Win32: https://learn.microsoft.com/en-us/windows/win32/gdi/ternary-...
No tenía el manual oficial de hardware de Amiga; en su lugar consultaba un libro llamado “Mapping the Amiga”. Explicaba lo mismo de forma un poco más extensa
No recuerdo qué minterms usé entonces, pero creo que con ese libro de alguna manera logré implementar cosas como shadebobs, bobs y dibujo de líneas 3D con XOR
La página correspondiente de Mapping the Amiga: https://archive.org/details/1993-thomson-randy-rhett-anderso...