[JSC] Implement `String#split` in C++
Source/JavaScriptCore/builtins/BuiltinNames.h
Source/JavaScriptCore/builtins/StringPrototype.js
Source/JavaScriptCore/runtime/RegExpObjectInlines.h
JSC uses two tiers of optimization for ECMAScript builtins: self-hosted JS builtins (compiled to bytecode, then JIT'd generically) and DFG intrinsics (type-specialized nodes emitting direct C++ calls). Watchpoints are invalidation hooks: optimized code assumes invariants (e.g., RegExp.prototype[@@split] is unmodified) and registers a watchpoint that fires when those invariants break.
This commit moves String.prototype.split from a self-hosted JS builtin to a C++ host function and introduces a new StringSplit DFG node. The node dispatches to operationStringSplit for string separators or operationStringSplitRegExp for primordial RegExp separators. Three new watchpoint sets guard the fast paths: m_stringSymbolSplitWatchpointSet, m_regExpSpeciesWatchpointSet, and splitSymbol added to regExpPrimordialPropertiesWatchpointSet. Per-instance overrides are caught by a structure check inside the new isSymbolSplitFastAndNonObservable, which mirrors the existing isSymbolReplaceFastAndNonObservable.
String#split(sep) call
|
[DFG StringSplit node]
|
watchpoints valid?
yes / no
|
sep type check
|- String --> operationStringSplit
|- RegExp? --> isSymbolSplitFastAndNonObservable
|- yes --> operationStringSplitRegExp (C++ fast)
|- no --> RegExp.prototype[@@split] (JS, observable)
Significance
The new fast-path gate adds three watchpoints and a structure check that mirror the historical bug surface of String#replace's analogous C++ migration — the same shape that has produced repeat watchpoint-race and structure-check bypass classes. An 11–23% speedup on split-heavy workloads is the headline, but every new watchpoint-guarded fast path opens a fresh window for observable side-effect leaks if the gate's invariants aren't fully preserved.
Audit directions
- Watchpoint invalidation races.
m_stringSymbolSplitWatchpointSetandm_regExpSpeciesWatchpointSetare new. If either watchpoint can be triggered after the fast-path guard but before the C++ operation completes — or if their invalidation order relative to JIT code patching is wrong — an attacker could make the JIT operate on a regex whose@@splithas been swapped without triggering recompilation. This is the same class of bug as older@@replacewatchpoint races. - Structure-ID recycling vs.
isSymbolSplitFastAndNonObservable. Per-instance@@splitoverrides are caught by a structure check. Structure IDs are recycled after GC; a carefully timed allocation sequence that causes structure ID reuse could make a modified RegExp appear to pass the structure check. Worth diffing against thereplacecounterpart for divergences. - Separator type dispatch boundary. The DFG node selects between string and RegExp paths based on type speculation. What happens if a separator object has both a
toStringand aSymbol.split? What if the separator is a String object (wrapped) rather than a primitive? The C++ fast path may handle these differently from the old JS builtin, which followed the spec's explicittypeofbranches.