Summary
object::field_get_set::get_field_by_name_tail — the by-name property-get IC-miss tail —
runs four address-keyed side-registry probes before it reads the GcHeader, and then
reads that header anyway and switches on it for the rest of the function:
:246 buffer::is_registered_buffer(obj)
:403 typedarray::lookup_typed_array_kind(obj)
:467 set::is_registered_set(obj)
:517 symbol::is_registered_symbol(obj) <-- process-global Mutex + SipHash
:543 let gc_type = (*gc_header).obj_type; <-- and from here it is all obj_type
:709 if gc_type == GC_TYPE_ERROR ...
:892 if gc_type == GC_TYPE_ARRAY ...
:1006 if gc_type == GC_TYPE_STRING ...
:1086 if gc_type == GC_TYPE_MAP || gc_type == GC_TYPE_SET <-- done the right way
:1099 if gc_type == GC_TYPE_OBJECT && regex::is_regex_pointer(obj)
The same file already contains both idioms: the Map/Set arm at :1086 is header-directed,
while the Set probe at :467 and the Symbol probe at :517 are not. is_registered_set
ends in obj_type == GC_TYPE_SET (set.rs:262) and alloc_symbol uses
gc_malloc(_, GC_TYPE_STRING), so the header can rule both out for free — the same
argument as #7474 / #7765 / #7850.
Measurement — this is where the #7850 family actually lives now
gc-handoff/bench/pipeline_big.ts, PERRY_DEBUG_SYMBOLS=1, sample at 1 ms,
DispatchQueue section only, 2435 main-thread samples:
perry_fn_pipeline_big_ts__main
-> js_object_get_field_ic_miss
-> get_field_by_name_object_tail
-> symbol::is_registered_symbol_slow 43 samples (1.8%)
Every single is_registered_symbol_slow sample in the profile comes from that chain.
Zero come from native_call_method::gc_pointer_and_type_from_value, which is where
#7850 measured 6.5% before #7852 landed — that PR removed pipeline's
generic-specialization miss, and the dynamic-dispatch load the probe was riding
disappeared with it (pipeline 0.483 s → 0.274 s on the quiet mini). The family did not
go away; it moved from the method-dispatch path to the property-get miss path.
Caveat, stated because this family has produced vacuous numbers before: the profile above
was taken on a contended dev machine, so it is attribution, not a timing claim. The
absolute cost must be re-measured on the quiet mini before anyone sizes a fix from it,
and the cost is workload-dependent by construction — it scales with IC misses, not
with property reads.
Why this is more delicate than #7850 and needs its own PR
get_field_by_name_tail is ~1500 lines on the hottest path in the runtime, and the
ordering is load-bearing in places: small Buffer allocations carry no GcHeader,
so the buffer and typed-array probes at :246/:403 cannot be moved behind a header
read; and is_registered_set documents (set.rs) that probing addr - 8 for an
arbitrary above-band candidate segfaults on Linux where freed pages get unmapped. A fix
should therefore gate only the Set (:467) and Symbol (:517) probes on the header the
function already reads at :543, leaving the headerless kinds where they are.
For the Symbol probe specifically, #7850 added symbol::may_be_symbol_header(ptr) — a
single 4-byte SYMBOL_MAGIC load that is exact in the false direction and covers both
gc_malloc'd and Box-leaked symbols. That is the piece this site needs, and it already
exists.
Found while fixing #7850. Sibling: #7865 (js_dyn_index_get / js_dyn_index_set).
Summary
object::field_get_set::get_field_by_name_tail— the by-name property-get IC-miss tail —runs four address-keyed side-registry probes before it reads the
GcHeader, and thenreads that header anyway and switches on it for the rest of the function:
The same file already contains both idioms: the Map/Set arm at
:1086is header-directed,while the Set probe at
:467and the Symbol probe at:517are not.is_registered_setends in
obj_type == GC_TYPE_SET(set.rs:262) andalloc_symbolusesgc_malloc(_, GC_TYPE_STRING), so the header can rule both out for free — the sameargument as #7474 / #7765 / #7850.
Measurement — this is where the #7850 family actually lives now
gc-handoff/bench/pipeline_big.ts,PERRY_DEBUG_SYMBOLS=1,sampleat 1 ms,DispatchQueue section only, 2435 main-thread samples:
Every single
is_registered_symbol_slowsample in the profile comes from that chain.Zero come from
native_call_method::gc_pointer_and_type_from_value, which is where#7850 measured 6.5% before #7852 landed — that PR removed
pipeline'sgeneric-specialization miss, and the dynamic-dispatch load the probe was riding
disappeared with it (
pipeline0.483 s → 0.274 s on the quiet mini). The family did notgo away; it moved from the method-dispatch path to the property-get miss path.
Caveat, stated because this family has produced vacuous numbers before: the profile above
was taken on a contended dev machine, so it is attribution, not a timing claim. The
absolute cost must be re-measured on the quiet mini before anyone sizes a fix from it,
and the cost is workload-dependent by construction — it scales with IC misses, not
with property reads.
Why this is more delicate than #7850 and needs its own PR
get_field_by_name_tailis ~1500 lines on the hottest path in the runtime, and theordering is load-bearing in places: small
Bufferallocations carry noGcHeader,so the buffer and typed-array probes at
:246/:403cannot be moved behind a headerread; and
is_registered_setdocuments (set.rs) that probingaddr - 8for anarbitrary above-band candidate segfaults on Linux where freed pages get unmapped. A fix
should therefore gate only the Set (
:467) and Symbol (:517) probes on the header thefunction already reads at
:543, leaving the headerless kinds where they are.For the Symbol probe specifically, #7850 added
symbol::may_be_symbol_header(ptr)— asingle 4-byte
SYMBOL_MAGICload that is exact in thefalsedirection and covers bothgc_malloc'd andBox-leaked symbols. That is the piece this site needs, and it alreadyexists.
Found while fixing #7850. Sibling: #7865 (
js_dyn_index_get/js_dyn_index_set).