Resizable and growable ArrayBuffers S4
- Stage: Stage 4
- Status: Finished
- ECMAScript edition: ES2024
- Synchronized: Aug 28, 2026
- 中文译文 · Source repository
This proposal extends ArrayBuffer and SharedArrayBuffer constructors to accept an optional maximum length, enabling in-place resizing (ArrayBuffer) or growing (SharedArrayBuffer). TypedArrays backed by such buffers can auto-track length or exhibit detached-like behavior when out of bounds.
The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.
In-Place Resizable and Growable ArrayBuffers
Stage: 4, landed in the specification.
Author: Shu-yu Guo (@syg)
Champion: Shu-yu Guo (@syg)
Introduction
ArrayBuffers have enabled in-memory handling of binary data and have enjoyed great success. This proposal extends the ArrayBuffer constructors to take an additional maximum length that allows in-place growth and shrinking of buffers. Similarly, SharedArrayBuffer is extended to take an additional maximum length that allows in-place growth.
Motivation and use cases
Better memory management
Growing a new buffer right now requires allocating a new buffer and copying. Not only is this inefficient, it needlessly fragments the address space on 32-bit systems.
Sync up capability with WebAssembly memory.grow
WebAssembly memory can grow. Every time it does, wasm vends a new ArrayBuffer instance and detaches the old one. Any JS-side "pointers" into wasm memory would need to be updated when a grow happens. This is an open problem and currently requires polling, which is super slow:
It also spurred proposals such as having a signal handler-like synchronous callback on growth events for wasm's JS API, which doesn't feel great due to the issues of signal handler re-entrancy being difficult to reason about.
Having growable ArrayBuffers and auto-tracking TypedArrays would solve this problem more cleanly.
WebGPU buffers
WebGPU would like to repoint the same ArrayBuffer instances to different backing buffers. This is important for performance during animations, as remaking ArrayBuffer instances multiple times per frame of animation incurs GC pressure and pauses.
Having a resizable ArrayBuffer would let WebGPU explain repointing as a resize + overwrite. Under the hood, browsers can implement WebGPU-vended resizable ArrayBuffers as repointable without actually adding a repointable ArrayBuffer into the language.
Proposal
ArrayBuffer
SharedArrayBuffer
Modifications to TypedArray
TypedArrays are extended to make use of these buffers. When a TypedArray is backed by a resizable buffer, its byte offset length may automatically change if the backing buffer is resized.
The TypedArray (buffer, [, byteOffset [, length ] ] ) constructor is modified as follows:
- If buffer is a resizable
ArrayBufferor a growableSharedArrayBuffer, if the length isundefined, then the constructed TA automatically tracks the length of the backing buffer.
The length getter on TypedArray.prototype is modified as follows:
- If this TA is backed by a resizable
ArrayBufferor growableSharedArrayBufferand is automatically tracking the length of the backing buffer, then return floor((buffer byte length - byte offset) / element size). - If this TA is backed by a resizable
ArrayBufferand the length is out of bounds, then return 0.
All methods and internal methods that access indexed properties on TypedArrays are modified as follow:
- If this TA is backed by a resizable
ArrayBufferand the translated byte index on the backing buffer is out of bounds, return undefined. - If this TA is backed by a resizable
ArrayBufferand the translated byte length is out of bounds, return 0.
This change generalizes the detachment check: if a fixed-length window on a backing buffer becomes out of bounds, either in whole or in part, due to resizing, treat it like a detached buffer.
This generalized bounds check is performed on every index access on TypedArrays backed by resizable ArrayBuffer.
Growable SharedArrayBuffers can only grow, so TAs backed by growable SharedArrayBuffers cannot go out of bounds.
An example:
Implementation
-
Both resizable
ArrayBufferand growableSharedArrayBufferare designed to be direct buffers where the virtual memory is reserved for the address range but not backed by physical memory until needed. -
TypedArrays that are backed by resizable and growable buffers have more complex, but similar-in-kind, logic to detachment checks. The performance expectation is that these TypedArrays will be slower than TypedArrays backed by fixed-size buffers. In tight loops, however, this generalized check is hoistable in the same way that the current detach check is hoistable.
-
TypedArrays that are backed by resizable and growable buffers are recommended to have a distinct hidden class from TypedArrays backed by fixed-size buffers for maintainability of security-sensitive fast paths. This unfortunately makes use sites polymorphic. The slowdown from the polymorphism needs to be benchmarked.
Security
ArrayBuffers and TypedArrays are one of the most common attack vectors for web browsers. Resizability adds non-zero security risk to the platform in that bugs in bounds checking code for resizable buffers may be easily exploited.
This security risk is intrinsic to the proposal and is not entirely eliminable. This proposal tries to mitigate with the following design choices:
- Existing uses of
ArrayBufferandSharedArrayBufferconstructors remain fixed-length and are not retrofitted to be resizable. Internally the resizable buffer types may have different hidden classes so existing code paths can be kept separate - Make partially OOB TypedArrays act like their buffers are detached instead of auto-updating the length
- Make in-place implementation always possible to limit data pointer moves
FAQ and design rationale tradeoffs
What happened to transfer()? It used to be here.
It has been separated into its own proposal to further explore the design space.
Why not retrofit all ArrayBuffers to be resizable?
Retrofitting the single-parameter ArrayBuffer instead of adding an explicit opt-in overload is hard because of both language and implementation concerns:
- TypedArray views have particular offsets and lengths that would need to be updated. It is messy to determine what TAs' lengths should be updated. If growing, it seems like user intention needs to be taken into account, and those with explicitly provided lengths should not be updated. If shrinking, it seems like all views need to be updated. This would not only require tracking all created views but is not clean to reason about.
- Browsers and VMs have battle-hardened code paths around existing TypedArrays and
ArrayBuffers, as they are the most popular way to attack browsers. By introducing new types, we hope to leave those existing paths alone. Otherwise we'd need to audit all existing paths, of which there are many because of web APIs' use of buffers, to ensure they handle the possibility of growth and shrinking. This is scary and is likely a security bug farm.
Why require maximum length?
The API is designed to be implementable as an in-place growth. Non in-place growth (i.e. realloc) semantics presents more challenges for implementation as well as a bigger attack surface. In-place growth has the guarantee that the data pointer of the backing store does not move.
Under the hood, this means the backing store pointer can be made immovable. Note that this immovability of the data pointer is unobservable from within JS. For resizable ArrayBuffers, it would be conformant, but possibly undesirable, to implement growth and shrinking as realloc. For growable SharedArrayBuffers, due to memory model constraints, it is unlikely that a realloc implementation is possible.
Why can't growable SharedArrayBuffer shrink?
Shrinking shared memory is scary and seems like a bad time.
How would growable SharedArrayBuffer growth work with the memory model?
Growing a growable SharedArrayBuffer performs a SeqCst access on the buffer length. Explicit accesses to length, such as to the byteLength accessor, and built-in functions, such as slice, perform a SeqCst access on the buffer length. Bounds checks as part of indexed access, such as via ta[idx] and Atomics.load(ta, idx), perform an Unordered access on the buffer length.
This aligns with WebAssembly as well as enable more optimization opportunities for bounds checking codegen. It also means that other threads are not guaranteed to see the grown length without synchronizing on an explicit length access, such as by reading the byteLength accessor.
Open questions
Should resize(0) be allowed?
Currently a length of 0 always denotes a detached buffer. Are there use cases for resize(0)? Should it mean detach if allowed? Or should the buffer be allowed to grow again afterwards?
https://github.com/tc39/proposal-resizablearraybuffer/issues/22 points out that ArrayBuffer(0) is already a thing. This proposal thus allows resize(0).
History and acknowledgment
Thanks to:
- @lukewagner for the original straw-person https://gist.github.com/lukewagner/2735af7eea411e18cf20
- @domenic for https://github.com/domenic/proposal-arraybuffer-transfer
- @ulan for discussion and guidance of the implementation of ArrayBuffers in V8 and Chrome
- @kainino0x for discussion of the WebGPU use case
- @binji and @azakai for discussion of the WebAssembly use case