← All reports

[JSC] String#match implemented in C++

Component: JSC DFG and FTL JIT | 1440f86

Source/JavaScriptCore/dfg/DFGFixupPhase.cpp

+void FixupPhase::addStringMatchPrimordialChecks(Node* node)
+{
+ // Single CheckStructure on the primordial RegExp structure instead of
+ // TryGetById chain, since fixup-inserted TryGetByIds never specialize.
+ ...
+}

Source/JavaScriptCore/runtime/RegExpObjectInlines.h

+bool RegExpObject::isSymbolMatchFastAndNonObservable()
+{
+ // mirrors the replace variant; lastIndex must be a number since
+ // RegExp.prototype[@@match] reads it. No species watchpoint needed.
+ ...
+}

Source/JavaScriptCore/runtime/StringPrototype.cpp

+JSC_DEFINE_HOST_FUNCTION(stringProtoFuncMatch, ...)
+{
+ // re-validate after ToString(this): user toString() can mutate the
+ // regexp before RegExp.prototype[@@match] reads flags/exec.
+ ...
+}

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해야 합니다.

Builtin call overhead를 제거함으로써 match가 많은 코드에서 최대 1.4배 가까운 성능 향상이 나타나며, 이에 따라 RegExp intrinsic에 대한 DFG/FTL fast-path 적용 범위도 함께 넓어집니다.

좁게 보면, addStringMatchPrimordialChecksCheckStructure와 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입니다.