[JSC] Replace fixup-inserted RegExp primordial `TryGetById` chains with a single `CheckStructure`
JIT가 RegExp 인자와 함께 String.prototype.replace, .match, .search, .split에 대한 fast intrinsic을 emit하려면, 해당 인자가 "primordial"한지 먼저 증명해야 합니다. 이는 spec이 경유하는 prototype 메서드를 instance가 shadowing하지 않는다는 것을 확인하는 과정입니다. 해당 메서드들은 observable하기 때문에 이 검증을 건너뛸 수 없습니다.
기존 fixup helper는 property마다 TryGetById+CheckIsConstant guard를 emit했습니다. replace helper는 exec, flags, 8개의 flag getter, @@replace까지 포함하면 총 11개에 달합니다. 이 TryGetById들은 profiling 이후에 삽입되므로 type 정보를 갖지 않으며, FTL에서는 비용이 높은 opaque IC patchpoint로 남게 됩니다.
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
이 commit은 호출 지점마다 최대 11쌍의 TryGetById+CheckIsConstant를 단일 CheckStructure로 대체했습니다. 대조 대상은 패치되지 않은 원래 RegExp structure입니다. 동시에 StringPrototypeReplace, StringPrototypeReplaceAll, RegExpSearchIntrinsic에서 누락되어 있던 BadCache exit bail도 함께 추가되었습니다.
이 접근 방식은 두 가지 불변 조건을 활용합니다. 먼저 RegExp.prototype이 변경되면 regExpPrimordialPropertiesWatchpoint가 발동되어 컴파일된 코드 전체가 무효화됩니다. 또한 자체 property를 추가하면 객체의 structure가 전환됩니다. 따라서 structure가 일치한다는 사실은, 해당 instance에 shadowing own property가 없고 prototype도 이미 clean한 상태임을 증명하는 셈입니다.
Significance
FTL 컴파일 코드에서 호출 지점당 최대 11개의 opaque IC patchpoint가 제거됨으로써, hot regexp/string-replace path가 1.3~1.6배 빨라집니다. 또한 새 guard가 드러낸 잠재적 recompilation loop 버그도 이번 commit에서 함께 수정되었습니다.
Audit directions
이 변경은 semantic guard(각 shadowing property를 개별 확인하는 방식)를 structural guard로 좁혔습니다. hidden-class identity를 primordial 상태의 대리 지표로 신뢰하는 방식으로의 전환입니다. JSC 내에 expected transition 없이 structure를 원래 상태로 되돌릴 수 있는 경로가 있는지 살펴볼 필요가 있습니다. delete 후 add 시퀀스나, 일반 transition chain을 우회하는 내부 object 연산이 그 예에 해당합니다.
Proxy 상호작용도 검증이 필요합니다. RegExp를 감싸는 Proxy는 원래 structure와 일치하지 않아야 하고, fall through해야 하지만, 실제 동작은 직접 확인할 필요가 있습니다.
가장 예리한 분석 지점은 누락되어 있던 BadCache bail입니다. 수정 이전에는 bytecode parser가 BadConstantValue(기존 guard의 exit kind)만 확인하고 BadCache는 확인하지 않았습니다. 이 때문에 recompilation 시에도 intrinsic이 계속 선택되어 무한 loop에 빠질 가능성이 있었습니다.
stress test는 recompile 횟수를 2회로 제한합니다. concurrent JIT, loop 중간에서의 OSR entry, IC polymorphism 등 다양한 코드 형태에 대해서도 살펴볼 필요가 있습니다. 새 CheckStructure 경로를 통해 recompile loop이나 miscompilation이 유발될 수 있는지 fuzzing으로 확인하는 것이 좋습니다.