Skip to content

Change ArrayBuffer implementation from copyBuffer to moveBuffer (#56691)#56691

Open
christophpurrer wants to merge 3 commits intofacebook:mainfrom
christophpurrer:export-D96034456
Open

Change ArrayBuffer implementation from copyBuffer to moveBuffer (#56691)#56691
christophpurrer wants to merge 3 commits intofacebook:mainfrom
christophpurrer:export-D96034456

Conversation

@christophpurrer
Copy link
Copy Markdown
Contributor

@christophpurrer christophpurrer commented May 5, 2026

Summary:

Switches the jsi::ArrayBuffer ⇄ native byte buffer bridge in TurboModules from a copy-based implementation (OwnedMutableBuffer) to a zero-copy borrow implementation (BorrowedMutableBuffer) on the paths where the caller can guarantee the source memory's lifetime, eliminating an O(N) copy on every ArrayBuffer that crosses the JS/native boundary.

ReactCommon/react/bridging/ArrayBuffer.h — adds BorrowedMutableBuffer: holds a raw uint8_t* + size, plus an optional std::function<void()> release callback invoked from the destructor. Caller is responsible for keeping the underlying memory alive — typically by capturing a JNI global_ref or ARC-retained ObjC object in the release lambda. Non-copyable; documents thread-safety requirements (release may fire on the JS GC thread). OwnedMutableBuffer and the Bridging<std::vector<uint8_t>> specialization are preserved for paths where the source lifetime cannot be guaranteed.

Android — JavaTurboModule.cpp — wraps the Java ByteBuffer's direct memory in a BorrowedMutableBuffer. Pins the buffer with jni::make_global(jByteBuffer) and captures the global ref in the release lambda so Java GC cannot collect it while the JS ArrayBuffer is alive. Tightens the Ljava/nio/ByteBuffer; argument check from isObject() to isObject() && isArrayBuffer().

iOS — RCTTurboModule.mmconvertJSIObjectToObjCObject (sync arg path) returns NSMutableData dataWithBytesNoCopy:length:freeWhenDone:NO instead of copying via [NSData dataWithBytes:length:]. Safe because the JS ArrayBuffer outlives the synchronous call. convertReturnIdToJSIValue ArrayBufferKind (sync return path) wraps NSMutableData.mutableBytes with BorrowedMutableBuffer, retaining the NSMutableData via the block capture for the lifetime of the JS ArrayBuffer. convertObjCObjectToJSIValue (async/promise path) adds an NSData → ArrayBuffer branch that still copies via OwnedMutableBuffer, since the source NSData lifetime cannot be guaranteed past the async callback.

Codegen — serializeMethod.js + snapshot — ObjC++ spec generator now emits NSMutableData * (instead of NSData *) for ArrayBufferTypeAnnotation, both for parameters and return types. Module authors must update their ObjC implementations.

Changelog:
[General][Changed] - TurboModule ArrayBuffer transfers between JS and native are now zero-copy where caller lifetime can be guaranteed; ObjC TurboModule specs now use NSMutableData * for ArrayBufferTypeAnnotation parameters and return types

Differential Revision: D96034456

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 5, 2026
@facebook-github-tools facebook-github-tools Bot added p: Facebook Partner: Facebook Partner labels May 5, 2026
@meta-codesync
Copy link
Copy Markdown

meta-codesync Bot commented May 5, 2026

@christophpurrer has exported this pull request. If you are a Meta employee, you can view the originating Diff in D96034456.

christophpurrer and others added 3 commits May 5, 2026 16:27
…etween JavaScript and native

Summary:
## Changelog:
[General] [Added] - Add ArrayBuffer support for React Native Turbo Modules

react-native-community/discussions-and-proposals#947

Adds first-class `ArrayBuffer` support across the entire TurboModule pipeline, enabling efficient binary data transfer between JavaScript and native code without base64 encoding overhead.

**Schema & Parser:**
- Added `NativeModuleArrayBufferTypeAnnotation` to `CodegenSchema.js` and `.d.ts`
- Added `emitArrayBuffer` to `parsers-primitives.js` type map

**C++ (Bridging & Generator):**
- Added `ArrayBufferKind` to `TurboModuleMethodValueKind` enum
- Created `ArrayBuffer.h` bridging header with `OwnedMutableBuffer` and `Bridging<std::vector<uint8_t>>`
- Added `jsi::ArrayBuffer` conversion operators to `Convert.h`
- Mapped `ArrayBufferTypeAnnotation` → `jsi::ArrayBuffer` in `GenerateModuleH.js`

**Android (Java/JNI):**
- Mapped `ArrayBufferTypeAnnotation` → `java.nio.ByteBuffer` in Java spec generator
- Mapped to `Ljava/nio/ByteBuffer;` JNI signature in JNI generator
- Added `ByteBuffer` arg/return handling in `JavaTurboModule.cpp` via `NewDirectByteBuffer`/`GetDirectBufferAddress`

**iOS (ObjC):**
- Mapped `ArrayBufferTypeAnnotation` → `NSData *` in ObjC spec generator
- Added ArrayBuffer→NSData conversion in `convertJSIValueToObjCObject()`
- Added NSData→ArrayBuffer conversion in `convertReturnIdToJSIValue()`

**Flow-Schema:**
- Added `ArrayBuffer` to `JAVASCRIPT_BUILTINS` in `BoundaryTypes.js`

**Tests:**
- Added Flow and TypeScript parser fixtures (`NATIVE_MODULE_WITH_ARRAYBUFFER`)
- Added generator schema fixture (`ARRAYBUFFER_MODULE`)
- Added `emitArrayBuffer` unit tests

Differential Revision: D95649873
Summary:
## Changelog:
[General] [Added] - Add ArrayBuffer sample methods to TurboModule examples

react-native-community/discussions-and-proposals#947

Add `getArrayBuffer` method to NativeCxxModuleExample and NativeSampleTurboModule
specs to demonstrate and validate ArrayBuffer support across all platforms.

- **JS Specs:** Added `getArrayBuffer: (arg: ArrayBuffer) => ArrayBuffer` to both
  NativeCxxModuleExample.js and NativeSampleTurboModule.js
- **C++:** Implemented in NativeCxxModuleExample.cpp using `OwnedMutableBuffer` to
  copy and return the ArrayBuffer
- **Java/Kotlin:** Implemented in SampleTurboModule.kt with `ByteBuffer` param/return
- **ObjC:** Implemented in RCTSampleTurboModule.mm (iOS + macOS) with `NSData *` param/return

Differential Revision: D95652569
…book#56691)

Summary:
Pull Request resolved: facebook#56691

Switches the `jsi::ArrayBuffer ⇄ native byte buffer` bridge in TurboModules from a copy-based implementation (`OwnedMutableBuffer`) to a zero-copy borrow implementation (`BorrowedMutableBuffer`) on the paths where the caller can guarantee the source memory's lifetime, eliminating an O(N) copy on every ArrayBuffer that crosses the JS/native boundary.

**`ReactCommon/react/bridging/ArrayBuffer.h`** — adds `BorrowedMutableBuffer`: holds a raw `uint8_t*` + size, plus an optional `std::function<void()>` release callback invoked from the destructor. Caller is responsible for keeping the underlying memory alive — typically by capturing a JNI `global_ref` or ARC-retained ObjC object in the release lambda. Non-copyable; documents thread-safety requirements (release may fire on the JS GC thread). `OwnedMutableBuffer` and the `Bridging<std::vector<uint8_t>>` specialization are preserved for paths where the source lifetime cannot be guaranteed.

**Android — `JavaTurboModule.cpp`** — wraps the Java `ByteBuffer`'s direct memory in a `BorrowedMutableBuffer`. Pins the buffer with `jni::make_global(jByteBuffer)` and captures the global ref in the release lambda so Java GC cannot collect it while the JS ArrayBuffer is alive. Tightens the `Ljava/nio/ByteBuffer;` argument check from `isObject()` to `isObject() && isArrayBuffer()`.

**iOS — `RCTTurboModule.mm`** — `convertJSIObjectToObjCObject` (sync arg path) returns `NSMutableData dataWithBytesNoCopy:length:freeWhenDone:NO` instead of copying via `[NSData dataWithBytes:length:]`. Safe because the JS ArrayBuffer outlives the synchronous call. `convertReturnIdToJSIValue` `ArrayBufferKind` (sync return path) wraps `NSMutableData.mutableBytes` with `BorrowedMutableBuffer`, retaining the `NSMutableData` via the block capture for the lifetime of the JS ArrayBuffer. `convertObjCObjectToJSIValue` (async/promise path) adds an `NSData → ArrayBuffer` branch that still copies via `OwnedMutableBuffer`, since the source NSData lifetime cannot be guaranteed past the async callback.

**Codegen — `serializeMethod.js` + snapshot** — ObjC++ spec generator now emits `NSMutableData *` (instead of `NSData *`) for `ArrayBufferTypeAnnotation`, both for parameters and return types. Module authors must update their ObjC implementations.

Changelog:
[General][Changed] - TurboModule ArrayBuffer transfers between JS and native are now zero-copy where caller lifetime can be guaranteed; ObjC TurboModule specs now use `NSMutableData *` for `ArrayBufferTypeAnnotation` parameters and return types

Differential Revision: D96034456
@meta-codesync meta-codesync Bot changed the title Change ArrayBuffer implementation from copyBuffer to moveBuffer Change ArrayBuffer implementation from copyBuffer to moveBuffer (#56691) May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. fb-exported meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant