박종훈 기술블로그
close menu

JDK-8379867 기여기: C2 VectorAPI의 TypeVectMask 리네이밍

이번 글에서는 OpenJDK의 JDK-8379867 이슈에 기여하며, C2 VectorAPI 내부의 벡터 마스크 타입 시스템과 리네이밍의 배경을 정리해본다.

이슈의 제목과 내용은 다음과 같다.

C2 VectorAPI: Rename TypeVectMask -> TypePVectMask for clarity

During work of JDK-8378968, we noticed that the naming of TypeVectMask and the related isa_vectmask is not very clear. It might suggest that it checks for any mask type. But it actually only checks for PVectMask type, and returns false for BVectMask and NVectMask.

Vector API란

Java의 Vector API는 벡터 연산을 Java 코드로 표현하면, 런타임에 C2 컴파일러가 CPU의 SIMD 명령어(x64의 SSE/AVX, AArch64의 NEON/SVE 등)로 컴파일해주는 API다. JDK 16부터 incubator 모듈로 제공되고 있으며, Project Valhalla의 value class가 preview로 제공되면 그에 맞춰 정식 API로 승격될 예정이다.

HotSpot에는 이미 auto-vectorization이 있지만, 변환 가능한 연산이 제한적이고 코드 형태가 조금만 바뀌어도 벡터화가 깨지는 문제가 있다. JEP 문서에서도 “수십 년간의 연구에도 불구하고, 스칼라 코드의 auto-vectorization은 신뢰할 수 있는 최적화 전략이 아니다”라고 언급하고 있다. Vector API는 개발자가 벡터 연산 의도를 명시적으로 표현할 수 있게 해서, auto-vectorizer가 최적화하지 못하는 Arrays::hashCode나 배열 사전순 비교 같은 연산도 벡터화할 수 있도록 한다. 데이터 처리, 암호화, 금융 시뮬레이션, 그리고 JDK 내부 라이브러리 성능 개선 등 다양한 영역에서 활용이 기대된다.

이슈 내용

JDK-8378968 작업 중 TypeVectMask와 관련 메서드 isa_vectmask의 이름이 모호하다는 점이 발견되었다. 이름만 보면 모든 마스크 타입을 검사하는 것처럼 보이지만, 실제로는 PVectMask(Predicate Vector Mask) 타입만 검사하고 BVectMask나 NVectMask에 대해서는 false를 반환한다.

이름과 실제 동작이 다르니, 이름을 동작에 맞게 바꾸자는 것이 이슈의 핵심이다.

배경 지식: 벡터 마스크의 3가지 표현 방식

이 리네이밍이 왜 필요한지 이해하려면, C2 컴파일러에서 벡터 마스크를 어떻게 표현하는지를 먼저 알아야 한다. C2의 VectorAPI에서 마스크는 3가지 표현 방식으로 나뉜다.

BVectMask (Boolean Vector Mask)

플랫폼 독립적인 표현이다. 벡터 레지스터에 저장되며, 각 lane이 8비트이고 값은 1(true) 또는 0(false)이다. Java의 boolean[] 메모리에서 로드/저장할 때 사용하는 형태이며, 타입 시스템에서는 TypeVectA ~ TypeVectZ로 표현된다.

NVectMask (Normal Vector Mask)

플랫폼 종속적인 표현이다. 역시 벡터 레지스터에 저장되지만, 각 lane이 원소 타입과 동일한 비트 폭(N비트)을 가지며 모든 비트가 set(true) 또는 unset(false)이다. 전용 predicate 레지스터가 없는 아키텍처에서 사용된다. AArch64 NEON, x86 AVX2가 이에 해당한다. 타입 시스템에서는 BVectMask와 마찬가지로 TypeVectA ~ TypeVectZ로 표현된다.

PVectMask (Predicate Vector Mask)

플랫폼 종속적인 표현이며, 전용 predicate/mask 레지스터에 저장된다. x86 AVX-512의 k0~k7 레지스터, AArch64 SVE의 p 레지스터, RISC-V RVV 등 전용 마스크 레지스터가 있는 아키텍처에서 사용된다. 타입 시스템에서 TypeVectMask(이번 리네이밍의 대상)로 표현된다.

변환 흐름

Java boolean[]
     ↕ (load/store)
  BVectMask (벡터 레지스터, 8비트 lane)
     ↕ VectorLoadMask / VectorStoreMask
  NVectMask (벡터 레지스터, N비트 lane)  ← predicate 레지스터 없는 경우
  PVectMask (predicate 레지스터)         ← predicate 레지스터 있는 경우

BVectMask가 필요한 이유

NVectMask와 PVectMask가 실제 연산에 사용되는 형식이라면, BVectMask는 왜 필요할까? BVectMask는 Java 메모리(boolean[])와의 인터페이스 역할을 한다. Java에서 마스크 데이터를 저장하거나 로드할 때는 8비트 boolean 형태가 필요한데, 이것이 BVectMask의 역할이다.

반면 하드웨어는 마스크를 다른 방식으로 다룬다. 예를 들어 x86 AVX-512에는 전용 k0~k7 mask 레지스터(64비트)가 있어서, vaddps zmm0 {k1}, zmm1, zmm2처럼 mask 레지스터를 직접 써서 조건부 연산을 수행한다. 이런 하드웨어 네이티브 형식이 NVectMask와 PVectMask이다.

비유하자면 BVectMask는 사람이 읽을 수 있는 10진수 문자열이고, NVectMask/PVectMask는 CPU가 직접 연산하는 2진수 같은 것이다. 메모리에 저장할 때는 “교환 형식”인 BVectMask를 쓰고, 실제 연산할 때는 “실행 형식”인 NVectMask/PVectMask로 변환해서 쓴다.

표현역할
BVectMaskJava 메모리와의 인터페이스 (저장/로드용) — “교환 형식”
NVectMask / PVectMask실제 연산 시 하드웨어 네이티브 형식 — “실행 형식”

NVectMask vs PVectMask

PVectMask가 더 효율적이다. NVectMask는 전용 마스크 레지스터가 없는 아키텍처의 차선책으로, 범용 벡터 레지스터를 마스크 용도로 “빌려 쓴다”. 마스크가 벡터 레지스터 하나를 차지하므로 연산용 레지스터가 줄어들고, 마스크 적용 시 별도 명령어(비교 → blend 등)가 필요하다.

반면 PVectMask는 별도의 predicate 레지스터를 사용하므로 벡터 레지스터를 소모하지 않고, 마스크 연산을 전용 하드웨어로 처리하며, 마스크를 명령어에 직접 붙일 수 있다.

이름이 혼란스러웠던 이유

C2의 벡터 타입 계층 구조를 보면 문제가 명확해진다.

Type
└── TypeVect (추상 베이스 클래스)
    ├── TypeVectA (scalable: AArch64 SVE, RISC-V RVV)
    ├── TypeVectS (32-bit)
    ├── TypeVectD (64-bit)
    ├── TypeVectX (128-bit)
    ├── TypeVectY (256-bit)
    ├── TypeVectZ (512-bit)
    └── TypeVectMask  ← 이 이름이 문제

TypeVectMask라는 이름은 “벡터 마스크 전체를 대표하는 타입”처럼 보인다. 하지만 실제로 BVectMask와 NVectMask는 TypeVectA ~ TypeVectZ로 표현되고, TypeVectMask는 PVectMask만을 나타낸다. is_vectmask()도 마찬가지로, 모든 마스크를 판별하는 것이 아니라 PVectMask인지만 확인한다. 이름에 P가 없으니 혼란이 생기는 것이다.

.ad 파일: 아키텍처 기술 파일

이번 변경에서는 .ad 파일도 다수 수정되었다. .ad 파일은 Architecture Description 파일로, C2 JIT 컴파일러의 명령어 선택(instruction selection) 규칙을 정의하는 DSL(Domain-Specific Language)이다. 정식 명칭은 ADL (Architecture Description Language)이다.

C2가 중간 표현(IR)의 노드를 실제 머신 명령어로 변환하는 규칙을 기술하며, 아키텍처별로 별도 파일이 존재한다.

파일내용
x86/x86.adx86/x86-64 명령어 규칙
aarch64/aarch64.adAArch64 기본 규칙
aarch64/aarch64_vector.adAArch64 벡터(SVE/NEON) 규칙
riscv/riscv.adRISC-V 기본 규칙
riscv/riscv_v.adRISC-V 벡터 확장 규칙

.ad 파일은 C++ 소스가 아니라 ADL 파일이다. HotSpot 빌드 과정에서 adlc(ADL Compiler)가 이 파일을 읽어 C++ 코드를 자동 생성한다.

aarch64.ad  →  adlc(컴파일러)  →  ad_aarch64.cpp  →  일반 C++ 컴파일

생성된 C++ 파일에 필요한 #include가 자동 포함되므로, .ad 파일 자체에 include 없이도 isa_pvectmask() 같은 메서드를 사용할 수 있다.

.ad 파일에서 isa_pvectmask()가 사용되는 예시는 다음과 같다.

instruct vmask_and(kReg dst, kReg src1, kReg src2) %{
  predicate(n->bottom_type()->isa_pvectmask());
  match(Set dst (AndVMask src1 src2));
  format %{ "vmask_and $dst, $src1, $src2" %}
  ins_encode %{ ... %}
%}

predicate에서 isa_pvectmask()를 사용해 “이 노드가 PVectMask 타입일 때만 이 명령어 패턴을 적용하라”는 조건을 건다.

참고로 aarch64_vector_ad.m4m4 매크로 템플릿이고, 이를 처리하면 aarch64_vector.ad가 생성된다. 따라서 .m4.ad 파일 양쪽 모두를 수정해야 한다.

수정 내용

변경 항목BeforeAfter
클래스명TypeVectMaskTypePVectMask
메서드is_vectmask()is_pvectmask()
메서드isa_vectmask()isa_pvectmask()

마무리

리네이밍이라 코드 변경 자체는 단순하지만, 15개 파일에 걸쳐 있어 놓치는 곳이 없는지 확인하는 데 신경을 썼다. 무엇보다 이번 작업을 통해 C2 컴파일러의 벡터 타입 시스템이 하드웨어 아키텍처의 차이를 어떻게 추상화하는지, 그리고 .ad 파일이라는 독특한 DSL이 명령어 선택 과정에서 어떤 역할을 하는지를 배울 수 있었다. 이렇게 조금씩 접점을 넓혀가는 것에 의미를 두고 있다.

categories: 개발

tags: JDK , OpenJDK , HotSpot , C2 , VectorAPI , 오픈소스 기여