[JSC] String#match implemented in C++
Component: JSC DFG and FTL JIT | 1440f86
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/runtime/RegExpObjectInlines.h
Source/JavaScriptCore/runtime/StringPrototype.cpp
JIT가 built-in method에 대해 특화된 코드를 생성하려면, fast path를 타기 전에 런타임에서 한 가지를 검증해야 합니다. 바로 객체의 prototype chain이 조작되지 않았다는 사실입니다. 즉 누군가 RegExp.prototype[Symbol.match]를 override하거나 lastIndex를 재할당하지 않았는지를 확인하는 과정이며, 이를 primordial check라고 부릅니다. 이번 commit은 String.prototype.match를 JS builtin 대신 C++ host function으로 다시 작성합니다. 또한 fixup phase에서 RegExpMatchFast로 변환되는 DFG StringMatch node를 추가하고, addStringMatchPrimordialChecks를 통해 primordial check를 설치합니다. 이 check는 단일 CheckStructure guard를 사용하는데, String#replace에서 쓰이던 TryGetById chain보다 엄격한 방식입니다. 여기에 isSymbolMatchFastAndNonObservable()의 watchpoint 기반 순수성 검사가 함께 결합됩니다. 이 가정 중 하나라도 깨지면 JIT는 spec을 준수하는 slow path로 OSR-exit해야 합니다.
Significance
Builtin call overhead를 제거함으로써 match가 많은 코드에서 최대 1.4배 가까운 성능 향상이 나타나며, 이에 따라 RegExp intrinsic에 대한 DFG/FTL fast-path 적용 범위도 함께 넓어집니다.
Audit directions
좁게 보면, addStringMatchPrimordialChecks의 CheckStructure와 watchpoint 조합이 실행 중간에 무효화될 수 있는지를 점검할 필요가 있습니다. 구체적으로는 guard 통과 이후, RegExpMatchFast 실행 이전 시점에 toString이나 getter의 side effect가 RegExp의 flags나 prototype을 변경하는 경우입니다. 아울러 speculation 이후 isSymbolMatchFastAndNonObservable()의 가정이 깨졌을 때 OSR exit가 상태를 올바르게 복원하는지도 함께 확인해야 합니다. 넓게 보면, 이번 fix는 하나의 계열에서 세 번째에 해당하는 사례입니다. String#split, String#replace에 이어 이번에 String#match가 추가되었고, 각각 고유한 isSymbol*FastAndNonObservable() predicate를 갖습니다. 이 세 predicate를 서로 비교하고, 대응하는 @@ method의 spec상 observable-operation 목록과도 함께 대조해볼 필요가 있습니다. 셋 중 둘에는 존재하지만 나머지 하나에는 빠져 있는 조건이 있다면, 바로 그것이 추적해야 할 variant입니다. 리뷰에서 눈여겨볼 신호는, 왜 다른지에 대한 설명 없이 형제 함수들과 watchpoint 구성이 다른 primordial-check helper입니다.