mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 20:09:04 +00:00
## Summary Adds a **DenseArray fast path** for `structuredClone` / `postMessage` that completely skips byte-buffer serialization when an `ArrayWithContiguous` array contains **flat objects** (whose property values are only primitives or strings). This builds on #26814 which added fast paths for Int32/Double/Contiguous arrays of primitives and strings. The main remaining slow case was **arrays of objects** — the most common real-world pattern (e.g. `[{name: "Alice", age: 30}, {name: "Bob", age: 25}]`). Previously, these fell back to the full serialization path because the Contiguous fast path rejected non-string cell elements. ## How it works ### Serialization The existing Contiguous array handler is extended to recognize object elements that pass `isObjectFastPathCandidate` (FinalObject, no getters/setters, no indexed properties, all enumerable). For qualifying objects, properties are collected into a `SimpleCloneableObject` struct (reusing the existing `SimpleInMemoryPropertyTableEntry` type). The result is stored as a `FixedVector<DenseArrayElement>` where `DenseArrayElement = std::variant<JSValue, String, SimpleCloneableObject>`. If no object elements are found, the existing `SimpleArray` path is used (no regression). ### Deserialization A **Structure cache** avoids repeated Structure transitions when the array contains many same-shape objects (the common case). The first object is built via `constructEmptyObject` + `putDirect`, and its final Structure + Identifiers are cached. Subsequent objects with matching property names are created directly with `JSFinalObject::create(vm, cachedStructure)`, skipping all transitions. Safety guards: - Cache is only used when property count AND all property names match - Cache is disabled when `outOfLineCapacity() > 0` (properties exceed `maxInlineCapacity`), since `JSFinalObject::create` cannot allocate a butterfly ### Fallback conditions | Condition | Behavior | |-----------|----------| | Elements are only primitives/strings | SimpleArray (existing) | | Elements include `isObjectFastPathCandidate` objects | **DenseArray (NEW)** | | Object property value is an object/array | Fallback to normal path | | Elements include Date, RegExp, Map, Set, ArrayBuffer, etc. | Fallback to normal path | | Array has holes | Fallback to normal path | ## Benchmarks Apple M4 Max, release build vs system Bun v1.3.8 and Node.js v24.12: | Benchmark | Node.js v24.12 | Bun v1.3.8 | **This PR** | vs Bun | vs Node | |-----------|---------------|------------|-------------|--------|---------| | `[10 objects]` | 2.83 µs | 2.72 µs | **1.56 µs** | **1.7x** | **1.8x** | | `[100 objects]` | 24.51 µs | 25.98 µs | **14.11 µs** | **1.8x** | **1.7x** | ## Test coverage 28 new edge-case tests covering: - **Property value variants**: empty objects, special numbers (NaN, Infinity, -0), null/undefined values, empty string keys, boolean-only values, numeric string keys - **Structure cache correctness**: alternating shapes, objects interleaved with primitives, >maxInlineCapacity properties (100+), 1000 same-shape objects (stress test), repeated clone independence - **Fallback correctness**: array property values, nested objects, Date/RegExp/Map/Set/ArrayBuffer elements, getters, non-enumerable properties, `Object.create(null)`, class instances - **Frozen/sealed**: clones are mutable regardless of source - **postMessage via MessageChannel**: mixed arrays with objects, empty object arrays ## Changed files - `src/bun.js/bindings/webcore/SerializedScriptValue.h` — `SimpleCloneableObject`, `DenseArrayElement`, `FastPath::DenseArray`, factory/constructor/member - `src/bun.js/bindings/webcore/SerializedScriptValue.cpp` — serialize, deserialize, `computeMemoryCost` - `test/js/web/structured-clone-fastpath.test.ts` — 28 new tests - `bench/snippets/structuredClone.mjs` — object array benchmarks --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>