오늘 V8에 Float16과 관련된 기능을 머지했습니다. (Float16은 IEEE754에서 정의하는 반정밀도 부동소숫점입니다.)

CL: https://chromium-review.googlesource.com/c/v8/v8/+/5082566

image

Javascript 엔진에 Float16Array, DataView.getFloat16, DataView.setFloat16, Math.fround16 을 추가했습니다.

변화한 파일은 총 82개, -409 +1385 에 달하는 꽤 사이즈가 큰 변화였습니다.

이슈를 잡고 머지하기까지 거의 6개월에 걸친 시간이 걸렸네요.

2023년 7월, 주로 Blink에 기여하며 CSS/HTML에 한정되어 있는 기여 대상을 V8을 통해 Javascript에 기여하겠다는 방향을 새롭게 정했습니다.

이미 Blink에 다수 기여하며 커미터 권한을 가지고 있어 Chromium 워크플로우에 익숙하기 때문에 적절한 난이도의 이슈를 잡고 해결해 나가면서 지식을 확장하면 되겠단 판단을 했습니다.

그렇게 Implement Float16Array 라는 이슈를 발견했고, ArrayBuffer와 전반적인 메모리 관리를 배울 수 있겠다는 생각과, 가뭄에 단비처럼 나타난 구현 테스크란 점을 고려해서 이슈를 붙잡았습니다.

구현을 준비하고 구현하고, 코드 리뷰하는 과정에 다양한 우여곡절이 많았습니다.

전부 담지는 못하겠지만 짤막하게 과정을 적어봤습니다.

구현 과정

Stage-3 표준 살펴보기

https://tc39.es/proposal-float16array/

우선 표준을 살펴봤습니다.

Float16Array는 Stage 3 proposal로써 구현되기를 기대하는 기능으로 표준을 보며 구현하면 될 것이라는 생각을 했습니다.

표준 내용을 보면 알겠지만, 각 타입별로 저장하거나 불러올 때 변환 시 float16 타입이 추가된 점과 DataView, Math에 관련 기능이 추가된다는 점을 알 수 있습니다.

구현 지점 찾기

코드를 보며 구현 지점을 찾았습니다. V8은 DRY 원칙을 최대한 지키려고 하여 매크로를 적극적으로 사용하고 있습니다.

image

이런 식으로 말이죠.

가끔 chromium source에서 심볼을 못 따라가고 메크로로 prefix나 suffix가 붙는 것 때문에 처음 보면 검색하기 어렵다는 경험을 했습니다. 다만 시간이 흘러 학습되고 나서 구현할 때는 상당히 편하기도 했습니다.

저기에 타입을 추가하면서 컴파일 에러가 발생하는 지점을 찾아 미구현 사항을 보완하며 수정지점을 찾으면 되겠다는 전략을 세웠습니다.

Float16 에 대하여

위 이미지에서 볼 수 있듯이 C type을 지정해야 합니다. 우선 모든 빌드 Target에 대해 float16을 지원하는지 미지수였고, 이 부분은 컴파일러 옵션과 로우레벨 최적화 관련된 변화가 필요할 것으로 판단했습니다.

관련 변화를 한 번에 하려고 RISC-V 스펙과 x86 스펙을 보면서 구현을 시도하려 했었는데, 생각보다 변화가 필요한 영역이 너무 넓어지는 경향이 있어 관련한 변화는 분리가 가능한 문제로 보고 분리하여 구현하는 게 좋겠다는 의견과 함께 Google의 V8 커미터인 syg@ (감사합니다!) 에게 질문 메일을 남겼고 답변으로 방향을 어느 정도 잡게 되었습니다.

따라서 컴파일러 및 언어(혹은 하드웨어)가 지원하는 Native float 16 타입을 사용하지 않고, 2바이트의 같은 크기를 가진 uint16_t 타입을 활용하여 float16을 구현해야겠단 생각을 했습니다.

변환 로직 구현하기

처음에는 변환 알고리즘을 찾고 이해하는데 시간을 많이 썼습니다. 이미 구현되어 있는 구현체는 꽤 있었고 비트연산과 float 곱셈의 원리를 이용하여 구현된 로직을 사용하기로 했습니다. (이는 추후 코드 리뷰로 이미 Chromium 내에서 사용하던 모듈로 변경됩니다.)

C++로 구현된 구현체뿐만 아닌 CSA (Code Stub Assembler)로도 해당 구현체를 구현해야 합니다.

어셈블리어로 구현하는 느낌과 유사했습니다. 조금 더 편한 어셈블리어 느낌을 받았습니다.

따라서 아래처럼 C++ 구현체를 CSA 코드로 구현하는 작업도 진행했습니다.

image

변환 로직 적용

이제 메모리에 넣고 뺄 때 위 변환 로직을 적용해야 합니다.

v8에서는 그 부분을 Template Specialization으로 적극 구현되어 있습니다.

따라서 값을 메모리에 넣고 뺄 때 부동 소수점 타입(double, float)을 변환하여 저장할 수 있도록 구현했습니다.

image

위 이미지는 elements.cc에서 double 타입을 float16 으로 표현된 uint16_t 값을 반환하는 Template Specialized 함수입니다.

또한 표준에서 정의하는 DataView.getFloat16, DataView.setFloat16, Math.fround16 함수에 해당 변환 로직을 적용합니다.

아래는 DataView의 getFloat16 구현체입니다. torque 라는 언어로 구현되어 있고 컴파일하면 CSA코드로 컴파일됩니다. 따라서 CSA로 구현된 구현체를 연결할 수 있습니다.

image

JIT 막기: Deoptimize

V8의 TurbofanMaglev 파이프라인을 타게 되면 머신코드로 컴파일되는 JIT이 동작하게 됩니다. 아직 float16 타입은 머신에서 지원하지 않고, 추후 개선을 위해 소프트웨어적으로 구현해야 하므로 바이트코드 그래프를 만들 때 의도적으로 float16 타입이면Deoptimize 노드를 추가하여 최적화 동작을 막도록 했습니다.

image

JIT 지원을 임시로 막기 위해 별도의 조건 없이 Deoptimize 노드를 추가하면 deopt loop을 형성하는 문제가 있는데 해결을 위해 작업을 진행할 예정입니다.

테스트코드

이미 Test262, mjsunit 테스트를 통해 TypedArray에 대한 테스트가 충분히 진행되고 있었습니다. 저는 여기에 Float16Array를 적용하여 실패하는 지점을 보완하고 Float16타입의 특성을 가진 테스트 케이스를 추가하였습니다.

코드 리뷰

코드 리뷰 과정이 거의 3개월 걸린 것 같습니다. 변화량도 많고 비동기로 커뮤니케이션을 진행하다 보니 많은 시간이 소요되었습니다. 리뷰를 통해 많은 부분 개선되고 더 깔끔하게 구현체가 정리된 것 같습니다.

이 글을 빌어 제 CL을 리뷰해 준 syg@, cbruni@, dmercadier@, dmercadier@ 에 감사 인사드립니다!

마무리

각 단계마다 길을 잃기도 하고 잘못된 길을 들어가서 한참 헤매는 과정을 겪었습니다. 덕분에 시간은 많이 들었지만 v8의 내부 구조와 경로를 많이 파악할 수 있었습니다. 사이즈도 크고 리뷰도 오래 걸려 처음 v8에 기여를 시작한 CL이지만 다른 기여들이 먼저 머지되었었네요.

직접 d8을 컴파일 하게 되면 --js_float16array 라는 옵션을 발견할 수 있습니다! 그 옵션과 함께 Float16Array을 사용해 볼 수 있습니다.

실제 유저에게 이 기능이 노출되는 건 아마도 시간이 소요될 것 같습니다. 여전히 Turbofan optimization Loop, Hardware support 등 해결해야 할 문제가 있기 때문입니다. 이 지점도 꾸준히 기여하여 언젠간 Float16Array가 세상에 등장할 수 있도록 노력하겠습니다.