- O vpternlogd do AVX-512 é uma instrução SIMD que executa, de uma vez, qualquer lógica Booleana arbitrária bit a bit de três entradas, escolhida por um valor imediato de 8 bits
- O ponto principal não é decorar expressões lógicas complexas, mas enxergar o
#imm8 como uma tabela de consulta para os 8 casos possíveis das três entradas A/B/C
- O blitter do Amiga, em 1985, também especificava combinações lógicas do mesmo jeito com três fontes de bitmap e um minterm de 8 bits;
0xE2 era muito usado para sprites com máscara
- Ao ler os 8 bits da coluna de resultado de baixo para cima, obtém-se o valor imediato, então condições como “exatamente duas das três entradas são 1” também podem ser montadas diretamente como
0x68
- O exemplo de
vpternlogd na documentação da Intel e o comum 0xE2 da demoscene do Amiga se encontram nesse ponto, mostrando que instruções SIMD modernas e hardware gráfico retrô compartilham a mesma sensibilidade de projeto
vpternlogd do AVX-512
- O vpternlogd é uma instrução ternária de lógica bit a bit que chamou atenção no material de apresentação do projeto da ISA AVX-512
- Ela recebe três entradas A, B e C e pode expressar qualquer lógica Booleana em uma única instrução
- Ex.:
(NOT A) OR ((NOT B) XOR (C AND A))
- As entradas podem ser registradores de 512 bits, permitindo aplicar uma lógica complexa aos 512 bits inteiros ao mesmo tempo
- Em vez de continuar adicionando instruções dedicadas como
foo_and_a_or_not_b, várias lógicas são tratadas com uma única instrução flexível e um valor imediato de 8 bits
VPTERNLOGD r0, r1, r3, #imm8
- O
#imm8 determina qual função de lógica de bits será executada
- Muitos documentos apenas dizem que o valor imediato “define uma função binária específica”, o que dificulta entender de forma intuitiva como ele é calculado na prática
Blitter do Amiga e minterm
- Nos computadores dos anos 1980, era comum usar chips customizados para processamento gráfico
- O blitter do Commodore Amiga 500 aplicava operações lógicas ao mover gráficos bitmap de uma posição para outra
- Ele lidava com até três fontes de bitmap, e a operação lógica entre elas era especificada por um valor de 8 bits chamado minterm
- Nesse sentido, ele tem a mesma estrutura do
vpternlogd: três fontes e um valor de 8 bits para escolher a combinação lógica
- Muitos programadores de Amiga reutilizavam valores frequentes em vez de entender diretamente como calcular o minterm
0x00 para limpar um buffer
0xE2 para desenhar um sprite com máscara
- O “Amiga Hardware Reference Manual”, de 1989, explicava o cálculo de minterms com uma notação confusa, o que não ajudava muito os criadores de demos da época
Calculando o valor de 8 bits como tabela de consulta
- O
#imm8 ou minterm pode ser visto não como uma combinação de operadores lógicos, mas como uma tabela de consulta com 8 entradas
- Como as três entradas A, B e C só podem ser 0 ou 1, existem 8 combinações possíveis
- Basta preencher diretamente a quarta coluna com o resultado desejado
| A |
B |
C |
Resultado desejado |
| 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 exemplo, se você quiser que o resultado seja 1 quando exatamente duas das três entradas forem 1, preencha a quarta coluna com os valores correspondentes a essa condição
- Lendo os 8 bits dessa coluna de baixo para cima, obtém-se
01101000, ou seja, 0x68
- Portanto, a função
0x68 define o resultado como 1 quando exatamente duas das três entradas são 1
- Da mesma forma, é possível obter o valor de
#imm8 necessário para qualquer função lógica arbitrária entre as três fontes
O 0xE2 do sprite com máscara
- Um dos valores de minterm mais comuns no Amiga é
0xE2
- Esse valor era muito usado para renderizar sprites 2D com máscara
- A: bitmap do sprite
- B: máscara do sprite
- C: plano de fundo
- A condição pode ser expressa como uma lógica de programa simples
- se o pixel de máscara B estiver ativado, o resultado é o sprite A
- se o pixel de máscara B não estiver ativado, o resultado é o plano de fundo C
| A |
B |
C |
Resultado desejado |
| 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 |
- Lendo a coluna de resultado de baixo para cima, obtemos
11100010, isto é, 0xE2
0xE2 é um valor de minterm extremamente comum na cultura demoscene do Amiga
Uma conexão curiosa com a documentação da Intel
1 comentários
Comentários no Hacker News
Há uma forma simples de obter o valor imediato a partir da expressão que você quer calcular. Por exemplo, se quiser calcular
(NOT A) OR ((NOT B) XOR (C AND A)), basta escrever algo como~_MM_TERNLOG_A | (~_MM_TERNLOG_B ^ (_MM_TERNLOG_C & _MM_TERNLOG_A))É literalmente a expressão que você quer calcular, e ela é avaliada como o valor imediato a partir das constantes
_MM_TERNLOG_A/B/Cdefinidas nos headers de intrinsics do gcc e do clang:typedef enum { _MM_TERNLOG_A = 0xF0, _MM_TERNLOG_B = 0xCC, _MM_TERNLOG_C = 0xAA } _MM_TERNLOG_ENUM;No MSVC, basta defini-las diretamente
A = 0b11110000,B = 0b11001100,C = 0b10101010Pelo título, achei que queria dizer que essa instrução não funcionava direito. Na verdade, o texto só explica como ela funciona
Meu eu adolescente não escreveu “CRAP!” naquela página do manual de hardware, mas fiquei olhando para ela por muito tempo tentando entender
No fim, como quase todo mundo, encontrei os Bobs e o BLTCON0 para cópias simples e fingi que aquela parte não existia
Ainda assim, alguns anos depois tirei A+ em lógica computacional na universidade, então talvez aquele trauma tenha ajudado em alguma medida
Sobre o título: “ternary logic” normalmente significa lógica com três valores de verdade. Mas este texto trata de uma instrução de compilador que lida com qualquer porta lógica binária de três entradas
ternloge o intrinsic também éternarylogic, então, embora seja meio lamentável, o título é adequadoAlém disso,
bitwisejá ajuda de certa forma a distinguir de “lógica de três valores”, eternarytambém é usado com muita frequência para significar três entradas.a ? b : ctambém costuma ser chamado de operador ternário e, de fato,ternlogconsegue imitar essa operação ternária; o texto trata justamente dissotrinary, nãoternaryDo ponto de vista de C++, JavaScript e Python, a expressão ternária no formato
(a < b) ? 5 : 2parece ser o uso mais comum. https://www.programiz.com/cpp-programming/ternary-operatorDe todo modo, qualquer suposição não importa muito. Palavras ou expressões costumam ter vários significados, e
ternarysignifica composto por três partes, então também se aplica aquiternarynão tem esse sentidoEm C,
+é um operador binário porque recebe duas entradas, e?:é um operador ternário porque recebe três entradas. Ele só costuma ser chamado de “the ternary operator” por ser o único em C; não há nada essencialmente especial nelevpternlogdimplementa qualquer operador ternário bit a bit com três entradasFiquei pensando se não é parecido com a função
BitBltdo Windows. Pelo menos desde o Windows 3.1, se não me engano, ela existe, e o parâmetroopdefine como combinar origem, destino e máscaraLembro que havia nomes de códigos como
BLACKNESS, que gera preto independentemente das entradas, eCOPY, que copia a origem para o destino.BLACKNESSeWHITENESStinham uma sonoridade estranhamente poéticaPelo que lembro do Petzold, ela era implementada em software, mas na chamada o opcode era convertido em assembly sob medida dentro da função, então parecia ser um caso raro de código automodificável no sistema operacional Windows
BitBltoriginalmente usava “operation codes” complexos de 16 bits que armazenavam operações binárias em notação polonesa reversaDepois foi adicionado um “operation index” que guardava a mesma informação em 1 byte, como no Amiga, mais curto e elegante. Hoje cada raster operation code contém tanto o operation index quanto o operation code, então a codificação é redundante. https://devblogs.microsoft.com/oldnewthing/20180528-00/?p=98...
É igual ao modo como FPGAs implementam funções lógicas arbitrárias como tabelas de consulta
Só que, exceto para propósitos especiais como FPGAs ou a instrução deste texto, normalmente não se faz isso. Registradores rápidos e RAM estática às vezes são feitos com lógica, mas é mais comum fazê-los diretamente com transistores do que com portas
74181também faz issoSe você for a https://www.sandpile.org e procurar
VPTERNLOGna página de opcodes de 3 bytes https://www.sandpile.org/x86/opc_3.htm, também verá a AVX512BITALG2, uma variante de mascaramento de byte/word que parece ter sido planejada pela Intel no passadoEla também aponta, a partir do operando
Ib, para uma página de tabela-verdade ternária com todos os 256 casos: https://www.sandpile.org/x86/ternlog.htmA escolha da função
E2como exemplo na documentação talvez seja porque ela é um mux praticamente básico e padrão entre as funções booleanas de três entradas: seB, entãoA; caso contrário,CÉ uma função universal, então não é preciso ser necessariamente fã do Amiga para escolhê-la. Claro, talvez fossem fãs mesmo
Outro exemplo de operações bit a bit guardadas em um inteiro são os códigos ROP do GDI do Win32: https://learn.microsoft.com/en-us/windows/win32/gdi/ternary-...
Eu não tinha o manual oficial de hardware do Amiga; em vez disso, li um livro chamado “Mapping the Amiga”. Ele explicava a mesma coisa de forma um pouco mais prolixa
Não lembro quais mintermos usei na época, mas acho que, com esse livro, de algum modo implementei coisas como shadebobs, bobs e desenho de linhas 3D com XOR
A página correspondente de Mapping the Amiga: https://archive.org/details/1993-thomson-randy-rhett-anderso...