[JSC] Extract CheckInBounds from StringAt and StringCodePointAt
The bounds check that got deleted because it had already passed.
Component: JavaScriptCore DFG/FTL JIT | 5d1761d
Source/JavaScriptCore/dfg/DFGSSALoweringPhase.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
JSC의 DFG와 FTL tier는 string index 접근이 범위를 벗어나지 않는다고 speculate합니다. 이 가정은 StringAt/StringCodePointAt 같은 노드 내부에 암묵적인 check 형태로 들어가 있습니다. index가 범위를 벗어나면 해당 노드는 exit, 즉 deoptimize를 수행해 하위 tier로 돌아가게 됩니다. 한편 abstract interpreter는 이 check가 반드시 통과했다는 사실을 근거로 삼습니다. 그래서 === undefined 같은 이후 비교 연산을 상수로 fold합니다. 이때 원래 노드의 값을 사용하는 쪽이 사라지고, dead-code elimination이 노드 자체를 제거합니다. 이번 commit은 SSA lowering 단계에서 bounds check를 독립적인 CheckInBounds 노드로 분리했습니다. 그 결과 StringAt, StringCharCodeAt, StringCodePointAt이 모두 lowerStringBoundsCheck()를 거치게 되었습니다. 또한 FTL lowering은 check가 이미 분리된 경우 자체 index clamping과 slow path를 건너뛰도록 수정되었습니다.
Before:
string.codePointAt(index) === undefined
├─ AI folds comparison to `false` (assumes the implicit bounds check fired)
└─ DCE: StringCodePointAt has no users ──► entire node removed
Runtime: OOB/negative index no longer exits — folded `false` used anyway
After (SSALoweringPhase):
CheckInBounds(index, length) ← separate node, survives DCE
StringAt / StringCodePointAt ← still removable if unused
Runtime: OOB/negative index still triggers CheckInBounds exit
Significance
패치 이전에는 string.codePointAt(index) === undefined가 out-of-bounds나 음수 index에 대해 safety exit를 타지 않고, 이미 fold된 낡은 결과를 반환할 수 있었습니다. 그래서 JIT으로 컴파일된 코드가 잘못된 bounds check 가정 위에서 계산을 수행했습니다.
Audit directions
다른 곳에서도 찾아볼 만한 bug class는, safety check — bounds, type, null — 를 독립적인 graph 노드로 표현하지 않고 자기 semantics 안에 묶어 둔 DFG 노드입니다. 이런 구조에서는 abstract interpreter의 folding이 노드의 값을 죽은 값으로 만드는 순간, check까지 조용히 함께 삭제됩니다. Narrow: DFGSSALoweringPhase.cpp와 FTLLowerDFGToB3.cpp에서 GetByVal/PutByVal을 비롯한 TypedArray 및 array 접근 노드들을 같은 관점으로 점검할 필요가 있습니다. 암묵적 check와 DCE가 맞물리는 동일한 위험이 있는지 확인하는 작업입니다. 특히 speculation의 결과가 interpreter가 fold할 수 있는 비교 연산에만 쓰이는 경우를 우선순위에 두면 좋습니다. Wider: 같은 형태는 array가 아닌 노드에 접혀 들어간 check에도 적용됩니다. interpreter가 상수로 증명할 수 있는 경우가 대상입니다. 노드의 효과는 guard인데 graph가 추적하는 것은 값뿐인 지점이 전부 여기에 해당합니다. 도달 범위는 lowerStringBoundsCheck 계열 helper가 다루지 않는 모든 노드 종류입니다. Widest: 어떤 optimizing compiler에서든, IR에서 일급 effect로 표현되지 않은 safety check는 liveness analysis 한 번이면 삭제될 수 있는 상태입니다. 지켜야 할 invariant는 guard가 노드의 속성이 아니라 노드 그 자체여야 한다는 점입니다. Code-review tell: lowering phase의 노드 case가 index clamp나 range 비교를 인라인으로 처리하면서, 대응되는 Check* 노드는 함께 emit하지 않는 경우입니다.