Skip to content

perf(object/GC): finish the common-object header shrink from 56 B to 40 B after shape-transition migration #8047

Description

@proggeramlug

Summary

The measured object-representation campaign still has one concrete, high-value step with no open ticket: shrink a common two-slot object from the current 56 bytes to 40 bytes by removing the derivable object_type, field_count, and keys_array words once every inline-cache consumer is shape-transition-safe.

This is the unlanded endgame identified in #7916. That issue closed after delivering its measured proposal, while #6759/#8009 migrated important shape consumers but explicitly did not perform the header shrink. Current main still uses:

  • 8-byte GcHeader;
  • 32-byte ObjectHeader; and
  • two 8-byte slots;

for 56 bytes total on {a,b}.

Measured prize

The exact mirror probe (grow the header by 16 B and read pad16 -> base as the shrink) measured, with byte-exact output and exit 0 on both arms:

benchmark instructions peak RSS copying minors
retain -25.63% -26.19% 6 -> 4
retain1 noise -18.72% 3 -> 2
retain_wide flat -10.52% 8 -> 6
retain_wide1 flat -13.42% 4 -> 3
tree flat -18.64% unchanged
tree_wide flat -10.66% unchanged

Full evidence and harness references are in #7916’s final measurement comment and gc-handoff/HDR-NOTES.md.

Why it was blocked

The hot inline guard historically loaded, in one receiver block:

object_type @0
class_id @4
field_count @12
keys_array @16

Three of those are the fields to delete. keys_array identity could not simply become a ShapeId comparison while deletion preserved class identity and cloned/compacted the keys array. The hidden-class/shape-transition migration therefore had to land first.

Several prerequisites have now landed, including birth stamps and shape-based cache migration through #8007/#8009/#8010/#8015/#8025/#8029. Re-audit all header consumers on current main before assuming the prerequisite is complete; a partial nine-consumer migration is not enough to change the ABI.

Required work

  1. Census every runtime/codegen/FFI consumer of ObjectHeader layout and all object_type, field_count, and keys_array reads.
  2. Prove deletion, descriptor installation, prototype mutation, exotic-object state, cross-module aliases, and realm-local class registries mint/compare the right shape identity.
  3. Replace remaining load-bearing header-field guards with exact ShapeId/shape-descriptor facts.
  4. Remove the three derivable fields and move all offsets/ABI declarations together.
  5. Retain the current 8-byte GcHeader; this issue is not the refuted “add another generation bit” proposal from perf(gc): write-barrier tower B3 (slot elision) + B4 (generation bit in the header) — the unstarted half of #7187 #7715.

Acceptance

  • {a,b} is 40 B, not merely 48/56 B; report size_of::<ObjectHeader>() and total bytes including GcHeader and two slots.
  • retain_wide reaches the corresponding 88 B common layout.
  • Exact source/IR census proves there are no stale old-offset or keys-pointer identity consumers.
  • Delete/defineProperty/prototype/cross-module/realm-isolation adversarial tests pass.
  • Forced evacuation, evacuation verification, and protect-fromspace canaries pass with the moving collector demonstrably live.
  • The 19-program corpus is byte-exact and exit-checked; report instructions/cycles/RSS/copying-minor counts rather than extrapolating from the old padding probe.
  • Any asyncpipe measurement first re-derives its current plateau using both copying minors and incremental cycle starts (bench: asyncpipe@120 sits exactly on a nursery trigger boundary and is not a usable A/B target #7926).

No version bump belongs in the implementation PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceRuntime, compile-time, build-size, or memory performance

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions