← All reports

[JSC] New DateNow DFG node

Component: JSC DFG and FTL JIT | d53865e

Source/JavaScriptCore/dfg/DFGClobberize.h

+ case DateNow:
+ read(WallClock);
+ write(WallClock);
+ return;

Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp

+ case DateNowIntrinsic: {
+ if (!is64Bit())
+ return CallOptimizationResult::DidNothing;
+ insertChecks();
+ setResult(addToGraph(DateNow));
+ return CallOptimizationResult::Inlined;
+ }

Source/JavaScriptCore/dfg/DFGOperations.cpp

+JSC_DEFINE_NOEXCEPT_JIT_OPERATION(operationDateNow, double, (void))
+{
+ return jsCurrentTime();
+}

JSC의 DFG와 FTL tier는 intrinsic 목록을 관리합니다. Math.random이나 Date.now처럼 잘 알려진 함수들은 bytecode parsing 단계에서 특별 취급되어, 일반적인 호출 대신 JIT가 직접 최적화된 코드를 생성할 수 있게 됩니다. 새로운 intrinsic node를 추가할 때는 몇 가지 사항을 함께 선언해야 합니다. 먼저 Clobberize에 side effect를 선언해야 하는데, 이는 해당 node가 어떤 메모리나 상태를 읽고 쓰는지를 나타내며 optimizer가 CSE나 hoisting을 수행할 수 있는지를 좌우합니다. 여기에 더해 abstract interpreter에 알려줄 type, 그리고 GC를 유발할 수 있는지 또는 speculative하게 실행해도 안전한지 여부도 함께 선언해야 합니다. 이번 commit은 DateNow를 추가하여 Date.now()operationDateNow에 대한 직접 호출로 inline시키고, 이 함수가 의존하는 observable 상태를 모델링하기 위해 WallClock이라는 새로운 abstract heap을 도입합니다. Inlining은 64비트에서만 이루어집니다. 32비트에서는 parser가 CallOptimizationResult::DidNothing을 통해 처리를 포기하며, DateNowSpeculativeJIT32_64의 미처리 node 목록에 함께 등록됩니다. 또한 LoopUnrollingPhaseisNumericComputationNode 목록에도 포함되는데, 이는 loop-invariant hoisting이 아니라 unrolling의 이득을 판단하는 heuristic에 영향을 줍니다.

Host call 경계를 제거함으로써 Date.now() 위주의 코드에서 대략 1.18~1.19배의 성능 향상을 얻습니다. 다만 그 대가로 모든 compiler pass가 새롭게 고려해야 할 clobber category가 하나 늘어납니다.

새로운 DFG node type은 JSC 버그가 반복적으로 발생하는 지점입니다. Clobberize, SafeToExecute, abstract interpreter, DoesGC가 모두 일관되게 갱신되어야 하는데, 이 중 하나라도 누락되면 compiler가 해당 node를 잘못 CSE하거나 재배치하거나, OSR exit 경계를 넘어 잘못 speculate하게 됩니다. 좁게 보면, DateNowWallClock effect가 Clobberize를 참조하는 모든 지점에서 제대로 준수되는지 확인할 필요가 있습니다. Store elimination, dead-code elimination, 그리고 선언된 effect가 없는 호출을 pure로 취급하는 pass들이 여기에 해당합니다. 넓게 보면, 재사용 가능한 점검 방향은 같은 형태를 가진 다른 최근 추가 node를 찾는 것입니다. 지난 감사 이후 DFGNodeType.h에 추가된 node type을 검색하고, 각각을 네 개의 테이블과 교차 확인해야 합니다. clobberize에는 존재하지만 safeToExecute에는 빠져 있는 (혹은 그 반대인) node가 전형적인 누락 패턴입니다. 아울러 64비트 전용 gating이 32비트 빌드에서 mismatch된 code path를 남기지 않는지도 확인해야 합니다. 매칭 단서는, 하나의 commit에서 새로 도입된 abstract heap이 정확히 하나의 reader와 하나의 writer만 갖는 경우입니다. Heap range를 범용적으로 참조하는 코드가 바로 이런 누락이 발생하기 쉬운 지점입니다.