From b247aa89d65757e724573f139ff9e7de3ca42dc2 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Thu, 21 May 2026 23:16:40 +0200 Subject: [PATCH] feat: add support for SharedArrayBuffer in TypedArray and TypedArrayOf --- doc/typed_array.md | 15 +++++++++++++++ doc/typed_array_of.md | 28 ++++++++++++++++++++++++++++ napi-inl.h | 30 ++++++++++++++++++++++++++++++ napi.h | 42 +++++++++++++++++++++++++++++++++++++++++- test/typedarray.cc | 25 +++++++++++++++++++++++++ test/typedarray.js | 36 ++++++++++++++++++++++++++++++++++++ 6 files changed, 175 insertions(+), 1 deletion(-) diff --git a/doc/typed_array.md b/doc/typed_array.md index c2d3773a0..95aefb2e9 100644 --- a/doc/typed_array.md +++ b/doc/typed_array.md @@ -43,6 +43,21 @@ Napi::ArrayBuffer Napi::TypedArray::ArrayBuffer() const; Returns the backing array buffer. +**NOTE**: If the `Napi::TypedArray` is not backed by an `Napi::ArrayBuffer`, +this method will terminate the process with a fatal error when using +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior +otherwise. Use `Buffer()` instead to get the backing buffer without assuming its +type. + +### Buffer + +```cpp +Napi::Value Napi::TypedArray::Buffer() const; +``` + +Returns the backing array buffer as a generic `Napi::Value`, allowing optional +type-checking with `Is*()` and type-casting with `As<>()` methods. + ### ElementSize ```cpp diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index a982b80e5..4ced5841c 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -77,6 +77,34 @@ static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, Returns a new `Napi::TypedArrayOf` instance. +### New + +Wraps the provided `Napi::SharedArrayBuffer` into a new `Napi::TypedArray` instance. + +The array `type` parameter can normally be omitted (because it is inferred from +the template parameter `T`), except when creating a "clamped" array. + +```cpp +static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type); +``` + +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance. +- `[in] elementLength`: The length to array, in elements. +- `[in] arrayBuffer`: The backing `Napi::SharedArrayBuffer` instance. +- `[in] bufferOffset`: The offset into the `Napi::SharedArrayBuffer` where the array starts, + in bytes. +- `[in] type`: The type of array to allocate (optional). + +Returns a new `Napi::TypedArrayOf` instance. + +**NOTE**: The support for this overload of `Napi::TypedArrayOf::New()` is only +available when using `NAPI_EXPERIMENTAL` and building against Node.js headers +that supports this feature. + ### Constructor Initializes an empty instance of the `Napi::TypedArrayOf` class. diff --git a/napi-inl.h b/napi-inl.h index cc2fad6f7..9c0c47859 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2594,6 +2594,14 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { return Napi::ArrayBuffer(_env, arrayBuffer); } +inline Napi::Value TypedArray::Buffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); +} + //////////////////////////////////////////////////////////////////////////////// // TypedArrayOf class //////////////////////////////////////////////////////////////////////////////// @@ -2645,6 +2653,28 @@ inline TypedArrayOf TypedArrayOf::New(napi_env env, bufferOffset)); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); +} +#endif + template inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} diff --git a/napi.h b/napi.h index eaae1e711..870a5c290 100644 --- a/napi.h +++ b/napi.h @@ -1339,7 +1339,21 @@ class TypedArray : public Object { napi_typedarray_type TypedArrayType() const; ///< Gets the type of this typed-array. - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + + // Gets the backing `ArrayBuffer`. + // + // If this `TypedArray` is not backed by an `ArrayBuffer`, this method will + // terminate the process with a fatal error when using + // `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior + // otherwise. Use `Buffer()` instead to get the backing buffer without + // assuming its type. + Napi::ArrayBuffer ArrayBuffer() const; + + // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`). + // + // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the + // backing buffer prior to casting with `As()`. + Napi::Value Buffer() const; uint8_t ElementSize() const; ///< Gets the size in bytes of one element in the array. @@ -1433,6 +1447,32 @@ class TypedArrayOf : public TypedArray { ///< template parameter T. ); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + /// Creates a new TypedArray instance over a provided SharedArrayBuffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::SharedArrayBuffer + arrayBuffer, ///< Backing shared array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); +#endif + static void CheckCast(napi_env env, napi_value value); TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. diff --git a/test/typedarray.cc b/test/typedarray.cc index 0b9e0970e..795f2819e 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -297,6 +297,11 @@ Value GetTypedArrayBuffer(const CallbackInfo& info) { return array.ArrayBuffer(); } +Value GetTypedArrayBufferValue(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return array.Buffer(); +} + Value GetTypedArrayElement(const CallbackInfo& info) { TypedArray array = info[0].As(); size_t index = info[1].As().Uint32Value(); @@ -389,12 +394,30 @@ void SetTypedArrayElement(const CallbackInfo& info) { } } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +Value CreateInt8TypedArrayFromSharedArrayBuffer(const CallbackInfo& info) { + auto buffer = info[0].As(); + size_t length = buffer.ByteLength(); + + return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, + info.Env(), + length, + buffer.As(), + 0, + napi_int8_array); +} +#endif + } // end anonymous namespace Object InitTypedArray(Env env) { Object exports = Object::New(env); exports["createTypedArray"] = Function::New(env, CreateTypedArray); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createInt8TypedArrayFromSharedArrayBuffer"] = + Function::New(env, CreateInt8TypedArrayFromSharedArrayBuffer); +#endif exports["createInvalidTypedArray"] = Function::New(env, CreateInvalidTypedArray); exports["getTypedArrayType"] = Function::New(env, GetTypedArrayType); @@ -405,6 +428,8 @@ Object InitTypedArray(Env env) { exports["getTypedArrayByteLength"] = Function::New(env, GetTypedArrayByteLength); exports["getTypedArrayBuffer"] = Function::New(env, GetTypedArrayBuffer); + exports["getTypedArrayBufferValue"] = + Function::New(env, GetTypedArrayBufferValue); exports["getTypedArrayElement"] = Function::New(env, GetTypedArrayElement); exports["setTypedArrayElement"] = Function::New(env, SetTypedArrayElement); exports["checkBufferContent"] = Function::New(env, CheckBufferContent); diff --git a/test/typedarray.js b/test/typedarray.js index f7224efb7..b6ae4a7f7 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -2,6 +2,8 @@ const assert = require('assert'); +let runSharedArrayBufferTests = true; + module.exports = require('./common').runTest(test); function test (binding) { @@ -61,6 +63,9 @@ function test (binding) { const b = binding.typedarray.getTypedArrayBuffer(t); assert.ok(b instanceof ArrayBuffer); + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof ArrayBuffer); + assert.strictEqual(b, bAsValue); } catch (e) { console.log(data); throw e; @@ -100,4 +105,35 @@ function test (binding) { assert.throws(() => { binding.typedarray.createInvalidTypedArray(); }, /Invalid (pointer passed as )?argument/); + + if (binding.hasSharedArrayBuffer && runSharedArrayBufferTests) { + const length = 4; + const sab = new SharedArrayBuffer(length); + /** @type {Int8Array} */ + let t; + + try { + t = binding.typedarray.createInt8TypedArrayFromSharedArrayBuffer(sab); + } catch (ex) { + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating TypedArrays on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } + + throw ex; + } + + assert.ok(t instanceof Int8Array); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), 'int8'); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + for (let i = 0; i < length; i++) { + const value = 2 ** (i + 1); + t[i] = value; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, i), value); + } + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof SharedArrayBuffer); + assert.strictEqual(bAsValue, sab); + } }