Skip to content

Simplify map-get.cc to only handle the regular OrderedHashMap#361

Open
szegedi wants to merge 2 commits into
mainfrom
simplify-map-get-ordered-only
Open

Simplify map-get.cc to only handle the regular OrderedHashMap#361
szegedi wants to merge 2 commits into
mainfrom
simplify-map-get-ordered-only

Conversation

@szegedi

@szegedi szegedi commented Jun 26, 2026

Copy link
Copy Markdown

Summary

bindings/map-get.cc previously detected and parsed two V8 hash-table representations — SmallOrderedHashMap and the regular OrderedHashMap — and dispatched between them at runtime. The small-map handling is dead code: a JS Map always uses the regular OrderedHashMap, and we only ever read AsyncContextFrame (CPED) maps, which are ordinary JS Maps.

This drops:

  • the SmallOrderedHashMapLayout struct,
  • IsSmallOrderedHashMap header sniffing,
  • the now-unnecessary templating on FindEntryByHash / FindValueByHash (one layout left → they take const OrderedHashMapLayout* directly),
  • the small-map branch in GetValueFromMap.

Net: 24 insertions, 146 deletions. The public GetValueFromMap signature is unchanged, so wall.cc is unaffected. IsOrderedHashMap validation is retained as defensive hygiene (a mis-aimed pointer walk yields "not found", not garbage).

Verified: addon builds cleanly, clang-format is idempotent, and all 7 test-get-value-from-map-profiler tests pass.

Proof that a JS Map's backing table is always OrderedHashMap (never SmallOrderedHashMap)

Traced against Node's bundled V8 (the copies vendored under the sibling v8/node checkouts; the relevant code is stable across Node v22–v26).

1. An AsyncContextFrame is just a JS Map.
In node/lib/internal/async_context_frame.js the hierarchy is:

class AsyncContextFrame extends InactiveAsyncContextFrame { ... }
class InactiveAsyncContextFrame extends SafeMap { ... }   // SafeMap = makeSafe(Map, …)

SafeMap is a frozen-method wrapper around the global Map; its instances are ordinary JSMap objects with the identical backing store. The constructor (super(AsyncContextFrame.current()); this.set(store, data)) is just new Map(iterable) + Map.prototype.set. So whatever V8 does for any Map is what it does here.

2. A Map's table is allocated as an OrderedHashMap, and stays one.
In v8/src/builtins/builtins-collections-gen.cc, the constructor path (GenerateConstructorAddConstructorEntriesAllocateTable) does, for the Map variant:

TNode<HeapObject> AllocateTable(Variant variant, ...) {
  if (variant == kMap) {
    return AllocateOrderedHashMap();   // <-- always the regular table
  }
  ...
}

and AllocateOrderedHashMap() (code-stub-assembler.cc) allocates an OrderedHashMap at kInitialCapacity. The strings SmallOrderedHashMap / SmallOrderedHashSet do not appear anywhere in builtins-collections-gen.cc — not in the constructor, set, nor the grow/shrink/rehash paths. The runtime fallbacks confirm the invariant: runtime-collections.cc unconditionally does Cast<OrderedHashMap>(holder->table()) on every access (it would CHECK-fail if a Small table were ever installed).

3. The only code that produces a SmallOrderedHashMap-then-promotes is test-only.
V8 does have a "start small, transition to large" abstraction — OrderedHashMapHandler / OrderedHashSetHandler with AdjustRepresentation() (ordered-hash-table.cc). That is the only machinery that would place a SmallOrderedHashMap where an OrderedHashMap is later expected. The only callers of OrderedHashMapHandler::Add / OrderedHashSetHandler::* in the entire tree are in test/cctest/test-orderedhashtable.cc. No production path — and specifically not JSMap/JSSet — uses these handlers.

4. The Small variants are effectively unused in this build.
The Torque AllocateSmallOrderedHashSet / AllocateSmallOrderedHashMap macros have zero callers. The C++ NewSmallOrderedHashSet / Map factory functions are reached only via the test-only handler. Remaining references (objects.cc) are just GC body-size descriptors. The genuinely-live "small ordered" type is SmallOrderedNameDictionary (dictionary-mode object property storage) — a different code path that never backs a Map.

Why it was implemented but never used

V8 keeps two parallel families: OrderedHashMap/OrderedHashSet (the canonical growable backing store for JS Map/Set) and the compact SmallOrderedHashMap/SmallOrderedHashSet. The "small first, promote on growth" optimization was implemented (the handler + AdjustRepresentation) but never wired into the JSMap/JSSet builtins — the CSA constructors hardcode AllocateOrderedHashMap(). So a JS Map is born with a regular OrderedHashMap even when empty, and grows in place as one.

Caveat

This is a guarantee about V8's current implementation (the AllocateTable hardcoding), not a spec-level invariant. The retained IsOrderedHashMap validation keeps the reader resilient to future V8 drift: if the layout ever fails to validate, GetValueFromMap returns 0 ("not found") rather than reading garbage.

🤖 Generated with Claude Code

A JS Map always uses the regular OrderedHashMap as its backing table:
V8's Map constructor hardcodes AllocateOrderedHashMap(), and the only
path that could install a SmallOrderedHashMap (OrderedHashMapHandler /
AdjustRepresentation) is test-only, never used by the JSMap/JSSet
builtins. We only ever read AsyncContextFrame (CPED) maps, which are
ordinary JS Maps, so the SmallOrderedHashMap handling was dead code.

Drop the SmallOrderedHashMapLayout struct, IsSmallOrderedHashMap header
sniffing, and the now-unnecessary templating on FindEntryByHash /
FindValueByHash. The public GetValueFromMap signature is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@datadog-official

datadog-official Bot commented Jun 26, 2026

Copy link
Copy Markdown

Pipelines

Fix all issues with BitsAI

⚠️ Warnings

🚦 1 Pipeline job failed

Pull Request Labels | label   View in Datadog   GitHub Actions

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 9df76de | Docs | Datadog PR Page | Give us feedback!

@github-actions

Copy link
Copy Markdown

Overall package size

Self size: 2.18 MB
Deduped: 2.88 MB
No deduping: 2.88 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | pprof-format | 2.2.2 | 500.53 kB | 500.53 kB | | source-map | 0.7.6 | 185.63 kB | 185.63 kB | | node-gyp-build | 4.8.4 | 13.86 kB | 13.86 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@szegedi szegedi added the semver-patch Bug or security fixes, mainly label Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-patch Bug or security fixes, mainly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant