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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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