Skip to content

The #7850 probe family has moved to the property-get IC-miss tail: get_field_by_name_tail probes 4 registries before reading the GcHeader it then switches on (1.8% of pipeline_big) #7867

Description

@proggeramlug

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).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions