[JSC] Extract fields of descriptor in Object.defineProperty in DFG / FTL
Component: JSC | 93e2909
JSC's optimizing pipeline has two upper tiers: DFG, the mid-tier dataflow-graph JIT, and FTL, the top tier built on the B3 backend. Object.defineProperty has historically been opaque to both because the descriptor is a general object whose fields the compiler cannot see through. A watchpoint is JSC's mechanism for compiling under an assumption and invalidating the compiled code if the assumption is ever broken.
JSTests/microbenchmarks/object-define-property-put-by-id-direct.js
JSTests/stress/object-define-property-fields-refinement.js
The commit uses two watchpoints — the "sane chain" watchpoint guarding that Object.prototype has not been mutated in lookup-relevant ways, plus a descriptor field watchpoint — to prove the descriptor has a known structure at compile time. With that proof, DFG inserts GetByOffset nodes reading each descriptor field (enumerable, configurable, value, writable, get, set) directly, exposing their values to downstream optimization through a new ObjectDefinePropertyFromFields IR node. FTL's allocation-sinking pass can then eliminate the descriptor object allocation outright. When all attributes are default and the base has no existing property for the key, the node collapses further to PutByIdDirect, skipping the defineProperty machinery entirely.
ObjectDefineProperty(obj, key, desc) [DFG, given known descriptor struct + watchpoints]
│
▼
ObjectDefinePropertyFromFields(obj, key, e, c, v, w, get, set)
each field = GetByOffset(desc, field-offset) OR empty (absent)
│
├─[e=true, c=true, w=true, no existing prop, non-indexed key]
│ └──► PutByIdDirect(obj, key, value) ← IC fast path, ~12x faster
│
├─[data descriptor, any attr differs]
│ └──► DefineDataProperty
│
└─[accessor descriptor]
└──► DefineAccessorProperty
Significance
The default-attribute case collapses to PutByIdDirect and hits the inline-cache fast path, up to 11.9x faster than the generic descriptor call. Getting there means rewriting how Object.defineProperty compiles across several IR lowering stages, touching watchpoint logic, structure assumptions, property-storage semantics, and FTL allocation sinking.
Audit directions
Narrow: the PutByIdDirect preconditions in tryFoldDefineDataPropertyToPutByIdDirect — no existing property, non-indexed key, all attributes true — are checked structurally at compile time via PropertyStatus. Confirm no runtime path can violate them after the IC stub is generated: Proxy-wrapped targets, objects with numeric string keys that pass the non-indexed check, and prototype-chain lookups are the candidates. The match tell is a compile-time status.isFound() guard with no corresponding runtime recheck in the emitted stub.
Wider: the watchpoint scope. The optimization compiles under sane-chain plus descriptor-field watchpoints on Object.prototype. A prototype mutation that fails to trigger invalidation — a non-enumerable property, a Symbol property, a Proxy interposed on the chain — leaves the JIT reading stale structure offsets through GetByOffset on the descriptor, yielding wrong-slot or out-of-range reads. Cross-realm descriptors carry a different Object.prototype and are an explicit stress-test case; verify the watchpoint scope covers them rather than assuming realm identity. Adjacent to this is the "empty" versus present-but-undefined distinction: each field is extracted as GetByOffset(desc, offset) or empty, and a structure analysis that concludes a field is absent when it is present-but-undefined hands defineProperty wrong attribute bits, silently creating non-writable or non-configurable properties. The accessor/data field overlap — get/set offsets versus value/writable offsets in the same descriptor slot — is where that confusion would land.
Widest: FTL allocation sinking. FTL eliminates the descriptor allocation when every use is a GetByOffset the compiler can see. If the sunk allocation escapes through an untracked path — an exception thrown inside defineProperty, an OSR exit, a bailout that materializes the object — escape-analysis failure produces use-after-free or wrong-typed materialization. Check whether ObjectDefinePropertyFromFields is marked as an escape point on its slow-path transition, and apply the same question to every other node that consumes a sinkable allocation and can transition to a runtime call.