← All reports

DateInstance packs its broken-down time inline

Component: JSC Date runtime | 6124a5f

JavaScript의 모든 Date 객체는 DateInstance가 뒷받침합니다. UTC timestamp를 지역 시간 기준의 연/월/일/시로 바꾸려면 timezone lookup이 필요한데, accessor마다 이 작업을 반복하기에는 비용이 너무 큽니다. 그래서 engine은 달력 형태로 분해한 표현을 캐시해 둡니다. 기존에는 이 캐시가 별도로 할당된 reference-counted DateInstanceData 블록에 놓였고, DateInstanceCache를 통해 공유되었습니다. 이 구조 탓에 DateInstance는 destructible GC 객체일 수밖에 없었는데, JSC의 collector 입장에서는 그만큼 부담이 커집니다. 또한 timezone이 바뀌어도 캐시에서 여전히 도달 가능한 instance만 무효화되었습니다.

이번 commit에서는 RefPtr<DateInstanceData>가 완전히 제거되었습니다. 지역 시간과 UTC 양쪽의 분해 결과는 객체에 직접 놓이는 두 개의 inline 64비트 word(PlainGregorianDateTime)로 packing되었습니다. 이때 "한 번도 계산되지 않음" 상태와 "stale" 상태는 bit-packed sentinel 값으로 구분합니다. 그 결과 DateInstance는 non-destructible 객체가 되었습니다. DFG/FTL에는 DateGetStorage라는 node가 새로 추가되었는데, isUTC flag를 받아 compiled code가 해당하는 packed word를 곧바로 읽도록 합니다. timezone이 변경되면 이제 캐시에서 도달 가능한 것만이 아니라, 살아 있는 모든 DateInstance가 heap scan을 통해 무효화됩니다.

Date 객체에서 destructor와 별도의 heap allocation이 함께 사라지고, JIT으로 compile된 accessor는 별도 블록까지 pointer를 따라가는 대신 inline load 한 번으로 필드를 읽습니다. 같은 Date에 걸린 accessor들끼리는 이 load와 유효성 검사를 공유할 수 있습니다. 한편 heap scan 기반 무효화는 실제 correctness 버그를 함께 해결합니다. 호스트의 timezone이 바뀐 뒤에도 캐시된 지역 시간 필드가 stale 상태로 남아 있을 수 있었던 문제입니다.

앞으로 눈여겨볼 패턴은, out-of-line 캐시를 bit-packed inline storage로 접어 넣고 유효성을 sentinel로 인코딩하는 방식입니다. Narrow: 이제 하나의 word가 세 가지 의미를 담습니다. 한 번도 계산되지 않은 상태, stale 상태, 그리고 실제 날짜입니다. 따라서 표현 가능한 PlainGregorianDateTime 값 전체 범위를 놓고 sentinel 선택이 타당한지 점검할 필요가 있습니다. 값이 겹치면 어떤 검사도 걸리지 않은 채 stale 데이터가 유효한 값으로 읽히게 됩니다. Wider: DateGetStorage는 유효성 검사 책임을 JIT 쪽으로 옮깁니다. 같은 Date의 accessor들이 그 검사를 공유한다면, compiler는 그 사이에서 word를 무효화할 수 있는 동작이 전혀 없음을 증명해야 합니다. packed 필드에 값을 기록하거나 timezone heap scan을 유발하는 연산을 모두 나열한 뒤, node가 선언한 effect와 하나씩 대조해 보는 편이 좋습니다. Widest: out-of-line refcounted 캐시에서 inline packed storage로 옮겨가는 객체는 모두 같은 두 가지 질문을 그대로 물려받습니다. sentinel이 실제 값 범위와 겹치지 않는가, 그리고 공유된 검사가 무효화를 놓치지 않는가입니다. 앞으로 나올 non-destructible 전환에도 동일한 검토가 적용됩니다. 판별 신호는 값이 아니라 storage를 반환하는 JIT node인데, 그런 경우 유효성 검사가 load 지점이 아니라 사용 지점에 놓이기 때문입니다.