2P by GN⁺ | ★ favorite | 댓글 1개
  • AVX-512의 vpternlogd는 세 입력의 임의 비트 단위 Boolean 논리를 8비트 즉시값으로 골라 한 번에 실행하는 SIMD 명령임
  • 핵심은 복잡한 논리식을 외우는 것이 아니라, #imm8을 세 입력 A/B/C의 8가지 경우에 대한 룩업 테이블로 보는 데 있음
  • 1985년 Amiga blitter도 세 비트맵 소스와 8비트 minterm으로 같은 방식의 논리 조합을 지정했으며, masked sprite에는 0xE2가 자주 쓰였음
  • 결과 열의 8비트를 아래에서 위로 읽으면 즉시값이 되므로, “세 입력 중 정확히 두 개만 1” 같은 조건도 0x68로 직접 만들 수 있음
  • Intel 문서의 vpternlogd 예시와 Amiga demoscene의 흔한 0xE2가 맞닿으며, 현대 SIMD 명령과 레트로 그래픽 하드웨어가 같은 설계 감각을 공유함

AVX-512의 vpternlogd

  • vpternlogd는 AVX-512 ISA 설계 발표 자료에서 눈에 띈 비트 단위 삼항 논리 명령임
  • 세 입력 A, B, C를 받아 임의의 Boolean 논리를 한 명령어로 표현할 수 있음
    • 예: (NOT A) OR ((NOT B) XOR (C AND A))
  • 입력은 512비트 레지스터일 수 있어, 복잡한 논리를 512비트 전체에 동시에 적용 가능함
  • foo_and_a_or_not_b 같은 전용 명령을 계속 추가하는 대신, 하나의 유연한 명령과 8비트 즉시값으로 여러 논리를 처리함
VPTERNLOGD r0, r1, r3, #imm8
  • #imm8은 어떤 비트 논리 함수를 수행할지 결정함
  • 많은 문서가 즉시값이 “특정 이진 함수를 결정한다”고만 설명해 실제 계산법을 직관적으로 파악하기 어려움

Amiga blitter와 minterm

  • 1980년대 컴퓨터는 그래픽 처리를 위해 커스텀 칩을 사용하는 경우가 흔했음
  • Commodore Amiga 500의 blitter는 비트맵 그래픽을 한 위치에서 다른 위치로 옮기면서 논리 연산을 적용했음
  • 최대 세 비트맵 소스를 다루고, 이들 사이의 논리 연산은 8비트 값인 minterm으로 지정함
  • 세 소스와 8비트 값으로 논리 조합을 고른다는 점에서 vpternlogd와 같은 구조를 가짐
  • 많은 Amiga 프로그래머는 minterm 계산법을 직접 이해하기보다 자주 쓰이는 값을 재사용했음
    • 버퍼를 지울 때는 0x00
    • masked sprite를 그릴 때는 0xE2
  • 1989년 “Amiga Hardware Reference Manual”은 minterm 계산을 혼란스러운 기호로 설명해 당시 demo 제작자들에게 큰 도움이 되지 않았음

8비트 값을 룩업 테이블로 계산하기

  • #imm8 또는 minterm은 논리 연산자의 조합이 아니라 8개 항목짜리 룩업 테이블로 볼 수 있음
  • 세 입력 A, B, C는 각각 0 또는 1이므로 가능한 조합은 8개임
  • 원하는 결과를 네 번째 열에 직접 채우면 됨
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 ?
  • 예를 들어 세 입력 중 정확히 두 개만 1일 때 결과를 1로 만들고 싶다면, 네 번째 열에 그 조건에 맞는 값을 채움
  • 그 결과 열의 8비트를 아래에서 위로 읽으면 01101000, 즉 0x68이 됨
  • 따라서 0x68 함수는 세 입력 중 정확히 두 입력이 1일 때 결과를 1로 설정함
  • 같은 방식으로 세 소스 사이의 임의 논리 함수에 필요한 #imm8 값을 얻을 수 있음

masked sprite의 0xE2

  • Amiga에서 매우 흔한 minterm 값 중 하나는 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 문서와의 우연한 연결

댓글과 토론

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
    • Amiga 매뉴얼은 논리곱 표준형으로 정규화하라고 권함
  • 제목만 보고 이 명령어가 제대로 동작하지 않는다는 뜻인 줄 알았음. 실제 글은 그냥 동작 방식을 설명함

    • 여기서 “busted”는 Intel 사람들이 Amiga 팬이라는 걸 잡아냈다는 의미로 이해했음. 저자의 모국어인 프랑스어에서 영어로 옮겨지며 뭔가 빠진 듯함
  • 십대 시절의 나는 하드웨어 매뉴얼 그 페이지에 “CRAP!”이라고 쓰진 않았지만, 이해하려고 정말 오래 들여다봤음
    결국 거의 모두가 그랬듯이 Bobs와 단순 복사용 BLTCON0을 찾아 쓰고, 그 부분은 못 본 척했음
    그래도 몇 년 뒤 대학에서 계산 논리학 A+를 받았으니, 그 트라우마가 어느 정도는 도움이 됐을지도 모름

  • 제목에 대해 말하자면, “ternary logic”은 보통 세 가지 진릿값을 갖는 논리를 뜻함. 하지만 이 글은 세 입력을 받는 모든 이진 논리 게이트를 처리하는 컴파일러 명령을 다룸

    • x86 명령어 이름이 ternlog이고 intrinsic도 ternarylogic이니, 아쉽긴 해도 제목은 적절함
      게다가 bitwise가 이미 “세 값 논리”와 구분되는 역할을 어느 정도 하고, ternary는 세 입력이라는 뜻으로도 매우 자주 쓰임. a ? b : c도 흔히 삼항 연산자라고 부르고, 실제로 ternlog는 이 삼항 연산을 흉내 낼 수 있으며 글도 바로 그 내용을 다룸
    • “삼항 논리 명령”이 아니라 “삼항인 논리 명령”에 가까움
    • 세 가지 진릿값을 갖는 논리가 뭔지 모르겠음. ternary가 아니라 trinary를 생각한 것 아닌가 싶음
      C++, JavaScript, Python 관점에서는 (a < b) ? 5 : 2 형태의 삼항 표현식이 가장 흔한 용례로 보임. https://www.programiz.com/cpp-programming/ternary-operator
      어쨌든 어떤 가정을 하든 크게 상관은 없음. 단어나 구는 여러 의미를 갖기 마련이고, ternary는 세 부분으로 이루어졌다는 뜻이니 여기에도 맞음
    • 여기서 ternary는 그런 뜻이 아님
      C에서 +는 입력을 두 개 받으므로 이항 연산자이고, ?:는 입력을 세 개 받으므로 삼항 연산자임. C에서 유일해서 보통 “the ternary operator”라고 부를 뿐, 본질적으로 특별한 건 없음
      vpternlogd는 세 입력을 받는 모든 비트 단위 삼항 연산자를 구현함
    • 나도 이 부분이 헷갈렸음. 다만 “세 항으로 된 이진 표현식을 평가한다”라는 이름은 덜 간결하긴 함
  • Windows의 BitBlt 함수와 비슷한 건가 싶음. 적어도 Windows 3.1 때부터 있었던 것 같은데, op 매개변수로 소스, 대상, 마스크를 어떻게 조합할지 정함
    입력과 상관없이 검은색을 만드는 BLACKNESS, 소스를 대상으로 복사하는 COPY 같은 코드 이름이 있었던 걸로 기억함. BLACKNESSWHITENESS는 어쩐지 시적인 울림이 있었음
    Petzold에서 본 기억으로는 소프트웨어로 구현되어 있지만, 호출 시 opcode가 함수 내부에서 맞춤 어셈블리로 변환되는 식이라 Windows 운영체제에서 드문 자기 수정 코드 사례였던 듯함

    • 맞음. 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.htm에서 VPTERNLOG를 찾으면, Intel이 과거에 계획했던 것으로 보이는 바이트/워드 마스킹 변형인 AVX512BITALG2도 볼 수 있음
    또한 Ib 피연산자에서 모든 256가지 경우를 담은 삼항 논리표 페이지 https://www.sandpile.org/x86/ternlog.htm로 연결됨

  • 문서 예제로 E2 함수를 고른 건, 세 입력 불리언 함수 중 거의 가장 기본적이고 표준적인 mux라서 그럴 수 있음: B이면 A, 아니면 C
    이는 보편 함수라서 꼭 Amiga 팬이어야 고를 만한 건 아님. 물론 실제로 팬이었을 수도 있음

  • 비트 단위 연산을 정수에 담는 또 다른 예로 Win32의 GDI ROP 코드가 있음: https://learn.microsoft.com/en-us/windows/win32/gdi/ternary-...

  • 공식 Amiga 하드웨어 매뉴얼은 없었고 대신 “Mapping the Amiga”라는 책을 봤음. 같은 내용을 조금 더 장황하게 설명했음
    그때 어떤 최소항을 썼는지는 기억나지 않지만, 이 책을 보고 shadebobs, bobs, XOR 3D 선 그리기 같은 것들을 어떻게든 구현했던 것 같음
    Mapping the Amiga의 해당 페이지: https://archive.org/details/1993-thomson-randy-rhett-anderso...