[4] [JSC] Add write barrier to op_del_by_id/op_del_by_val in baseline JIT
Severity: High | Component: JSC Baseline JIT | 1cdc540
High로 평가한 이유는 diff가 baseline op_del_by_*의 generational write barrier 누락을 수정하기 때문입니다. inline cache가 base cell의 Structure를 barrier 없이 transition하며, 이후 eden GC가 실행되면 새로 저장된 Structure가 sweep 대상이 될 수 있습니다. 결과적으로 live JSCell에서 dangling Structure*가 참조 가능한 상태가 됩니다.
barrier 발생 시점이 dst에 결과를 쓰기 이전으로 이동되었습니다. 또한 ShouldFilterBase 모드가 WriteBarrierForCellState로 교체되어, barrier가 덮어쓰여진 virtual register에서 base를 다시 읽는 대신 IC slot에서 cell을 직접 읽도록 변경되었습니다.
Source/JavaScriptCore/jit/JITPropertyAccess.cpp
property deletion inline cache에 의해 Structure pointer가 transition된 JSCell에 generational write barrier가 누락된 패턴.
Patch Details
emit_op_del_by_id와 emit_op_del_by_val 양쪽 모두에서, 결과가 dst에 boxing되기 이전에 barrier가 발생하도록 변경되었습니다. owner는 virtual register base가 아닌 IC의 base cell register이며, o = delete o.x와 같이 base와 dst가 동일한 레지스터를 공유하는 경우에 이 차이가 핵심입니다.
Background
JSC의 generational GC는 old-gen에서 new-gen으로의 모든 pointer write를 remembered set에 기록해야 합니다. op_del_by_*가 생성하는 inline cache는 base 객체의 Structure를 transition할 수 있습니다. 이 사실은 소스 주석에서도 명시됩니다: "IC can write new Structure without write-barrier if a base is cell". 이 경우 barrier 수행 책임은 JIT에 있으며, emitWriteBarrier(base, ShouldFilterBase)는 base virtual register를 읽어 cell 여부를 확인한 뒤 barrier를 수행합니다.
Analysis
패치 이전 o = delete o.x의 실행 순서는 다음과 같았습니다.
(1) o의 Structure를 transition할 수 있는 IC fast path
(2) boxBoolean(resultJSR)
(3) emitPutVirtualRegister(dst, resultJSR) — o를 boolean 값으로 덮어씀
(4) emitWriteBarrier(base, ShouldFilterBase) — virtual register를 다시 읽음
base와 dst가 o를 가리키는 동일한 local register이므로, 단계 (4)에서는 boolean 값을 읽게 됩니다. ShouldFilterBase의 branchIfNotCell early-exit이 이를 cell이 아닌 값으로 판단하여 barrier를 완전히 건너뜁니다.