Skip to content

Commit 00b95ef

Browse files
authored
feat: add support for SharedArrayBuffer in TypedArray and TypedArrayOf<T> (#1731)
1 parent 6a9456f commit 00b95ef

6 files changed

Lines changed: 175 additions & 1 deletion

File tree

doc/typed_array.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ Napi::ArrayBuffer Napi::TypedArray::ArrayBuffer() const;
4343

4444
Returns the backing array buffer.
4545

46+
**NOTE**: If the `Napi::TypedArray` is not backed by an `Napi::ArrayBuffer`,
47+
this method will terminate the process with a fatal error when using
48+
`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior
49+
otherwise. Use `Buffer()` instead to get the backing buffer without assuming its
50+
type.
51+
52+
### Buffer
53+
54+
```cpp
55+
Napi::Value Napi::TypedArray::Buffer() const;
56+
```
57+
58+
Returns the backing array buffer as a generic `Napi::Value`, allowing optional
59+
type-checking with `Is*()` and type-casting with `As<>()` methods.
60+
4661
### ElementSize
4762

4863
```cpp

doc/typed_array_of.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env,
7777
7878
Returns a new `Napi::TypedArrayOf` instance.
7979
80+
### New
81+
82+
Wraps the provided `Napi::SharedArrayBuffer` into a new `Napi::TypedArray` instance.
83+
84+
The array `type` parameter can normally be omitted (because it is inferred from
85+
the template parameter `T`), except when creating a "clamped" array.
86+
87+
```cpp
88+
static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env,
89+
size_t elementLength,
90+
Napi::SharedArrayBuffer arrayBuffer,
91+
size_t bufferOffset,
92+
napi_typedarray_type type);
93+
```
94+
95+
- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance.
96+
- `[in] elementLength`: The length to array, in elements.
97+
- `[in] arrayBuffer`: The backing `Napi::SharedArrayBuffer` instance.
98+
- `[in] bufferOffset`: The offset into the `Napi::SharedArrayBuffer` where the array starts,
99+
in bytes.
100+
- `[in] type`: The type of array to allocate (optional).
101+
102+
Returns a new `Napi::TypedArrayOf` instance.
103+
104+
**NOTE**: The support for this overload of `Napi::TypedArrayOf::New()` is only
105+
available when using `NAPI_EXPERIMENTAL` and building against Node.js headers
106+
that supports this feature.
107+
80108
### Constructor
81109

82110
Initializes an empty instance of the `Napi::TypedArrayOf` class.

napi-inl.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,6 +2594,14 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const {
25942594
return Napi::ArrayBuffer(_env, arrayBuffer);
25952595
}
25962596

2597+
inline Napi::Value TypedArray::Buffer() const {
2598+
napi_value arrayBuffer;
2599+
napi_status status = napi_get_typedarray_info(
2600+
_env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr);
2601+
NAPI_THROW_IF_FAILED(_env, status, Napi::Value());
2602+
return Napi::Value(_env, arrayBuffer);
2603+
}
2604+
25972605
////////////////////////////////////////////////////////////////////////////////
25982606
// TypedArrayOf<T> class
25992607
////////////////////////////////////////////////////////////////////////////////
@@ -2645,6 +2653,28 @@ inline TypedArrayOf<T> TypedArrayOf<T>::New(napi_env env,
26452653
bufferOffset));
26462654
}
26472655

2656+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
2657+
template <typename T>
2658+
inline TypedArrayOf<T> TypedArrayOf<T>::New(napi_env env,
2659+
size_t elementLength,
2660+
Napi::SharedArrayBuffer arrayBuffer,
2661+
size_t bufferOffset,
2662+
napi_typedarray_type type) {
2663+
napi_value value;
2664+
napi_status status = napi_create_typedarray(
2665+
env, type, elementLength, arrayBuffer, bufferOffset, &value);
2666+
NAPI_THROW_IF_FAILED(env, status, TypedArrayOf<T>());
2667+
2668+
return TypedArrayOf<T>(
2669+
env,
2670+
value,
2671+
type,
2672+
elementLength,
2673+
reinterpret_cast<T*>(reinterpret_cast<uint8_t*>(arrayBuffer.Data()) +
2674+
bufferOffset));
2675+
}
2676+
#endif
2677+
26482678
template <typename T>
26492679
inline TypedArrayOf<T>::TypedArrayOf() : TypedArray(), _data(nullptr) {}
26502680

napi.h

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,21 @@ class TypedArray : public Object {
13391339

13401340
napi_typedarray_type TypedArrayType()
13411341
const; ///< Gets the type of this typed-array.
1342-
Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer.
1342+
1343+
// Gets the backing `ArrayBuffer`.
1344+
//
1345+
// If this `TypedArray` is not backed by an `ArrayBuffer`, this method will
1346+
// terminate the process with a fatal error when using
1347+
// `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior
1348+
// otherwise. Use `Buffer()` instead to get the backing buffer without
1349+
// assuming its type.
1350+
Napi::ArrayBuffer ArrayBuffer() const;
1351+
1352+
// Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`).
1353+
//
1354+
// Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the
1355+
// backing buffer prior to casting with `As<T>()`.
1356+
Napi::Value Buffer() const;
13431357

13441358
uint8_t ElementSize()
13451359
const; ///< Gets the size in bytes of one element in the array.
@@ -1433,6 +1447,32 @@ class TypedArrayOf : public TypedArray {
14331447
///< template parameter T.
14341448
);
14351449

1450+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
1451+
/// Creates a new TypedArray instance over a provided SharedArrayBuffer.
1452+
///
1453+
/// The array type parameter can normally be omitted (because it is inferred
1454+
/// from the template parameter T), except when creating a "clamped" array:
1455+
///
1456+
/// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array)
1457+
static TypedArrayOf New(
1458+
napi_env env, ///< Node-API environment
1459+
size_t elementLength, ///< Length of the created array, as a number of
1460+
///< elements
1461+
Napi::SharedArrayBuffer
1462+
arrayBuffer, ///< Backing shared array buffer instance to use
1463+
size_t bufferOffset, ///< Offset into the array buffer where the
1464+
///< typed-array starts
1465+
#if defined(NAPI_HAS_CONSTEXPR)
1466+
napi_typedarray_type type =
1467+
TypedArray::TypedArrayTypeForPrimitiveType<T>()
1468+
#else
1469+
napi_typedarray_type type
1470+
#endif
1471+
///< Type of array, if different from the default array type for the
1472+
///< template parameter T.
1473+
);
1474+
#endif
1475+
14361476
static void CheckCast(napi_env env, napi_value value);
14371477

14381478
TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance.

test/typedarray.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ Value GetTypedArrayBuffer(const CallbackInfo& info) {
297297
return array.ArrayBuffer();
298298
}
299299

300+
Value GetTypedArrayBufferValue(const CallbackInfo& info) {
301+
TypedArray array = info[0].As<TypedArray>();
302+
return array.Buffer();
303+
}
304+
300305
Value GetTypedArrayElement(const CallbackInfo& info) {
301306
TypedArray array = info[0].As<TypedArray>();
302307
size_t index = info[1].As<Number>().Uint32Value();
@@ -389,12 +394,30 @@ void SetTypedArrayElement(const CallbackInfo& info) {
389394
}
390395
}
391396

397+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
398+
Value CreateInt8TypedArrayFromSharedArrayBuffer(const CallbackInfo& info) {
399+
auto buffer = info[0].As<SharedArrayBuffer>();
400+
size_t length = buffer.ByteLength();
401+
402+
return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array,
403+
info.Env(),
404+
length,
405+
buffer.As<SharedArrayBuffer>(),
406+
0,
407+
napi_int8_array);
408+
}
409+
#endif
410+
392411
} // end anonymous namespace
393412

394413
Object InitTypedArray(Env env) {
395414
Object exports = Object::New(env);
396415

397416
exports["createTypedArray"] = Function::New(env, CreateTypedArray);
417+
#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER
418+
exports["createInt8TypedArrayFromSharedArrayBuffer"] =
419+
Function::New(env, CreateInt8TypedArrayFromSharedArrayBuffer);
420+
#endif
398421
exports["createInvalidTypedArray"] =
399422
Function::New(env, CreateInvalidTypedArray);
400423
exports["getTypedArrayType"] = Function::New(env, GetTypedArrayType);
@@ -405,6 +428,8 @@ Object InitTypedArray(Env env) {
405428
exports["getTypedArrayByteLength"] =
406429
Function::New(env, GetTypedArrayByteLength);
407430
exports["getTypedArrayBuffer"] = Function::New(env, GetTypedArrayBuffer);
431+
exports["getTypedArrayBufferValue"] =
432+
Function::New(env, GetTypedArrayBufferValue);
408433
exports["getTypedArrayElement"] = Function::New(env, GetTypedArrayElement);
409434
exports["setTypedArrayElement"] = Function::New(env, SetTypedArrayElement);
410435
exports["checkBufferContent"] = Function::New(env, CheckBufferContent);

test/typedarray.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
const assert = require('assert');
44

5+
let runSharedArrayBufferTests = true;
6+
57
module.exports = require('./common').runTest(test);
68

79
function test (binding) {
@@ -61,6 +63,9 @@ function test (binding) {
6163

6264
const b = binding.typedarray.getTypedArrayBuffer(t);
6365
assert.ok(b instanceof ArrayBuffer);
66+
const bAsValue = binding.typedarray.getTypedArrayBufferValue(t);
67+
assert.ok(bAsValue instanceof ArrayBuffer);
68+
assert.strictEqual(b, bAsValue);
6469
} catch (e) {
6570
console.log(data);
6671
throw e;
@@ -100,4 +105,35 @@ function test (binding) {
100105
assert.throws(() => {
101106
binding.typedarray.createInvalidTypedArray();
102107
}, /Invalid (pointer passed as )?argument/);
108+
109+
if (binding.hasSharedArrayBuffer && runSharedArrayBufferTests) {
110+
const length = 4;
111+
const sab = new SharedArrayBuffer(length);
112+
/** @type {Int8Array<SharedArrayBuffer>} */
113+
let t;
114+
115+
try {
116+
t = binding.typedarray.createInt8TypedArrayFromSharedArrayBuffer(sab);
117+
} catch (ex) {
118+
if (ex.message === 'Invalid argument') {
119+
console.warn(`The current version of Node.js (${process.version}) does not support creating TypedArrays on SharedArrayBuffers; skipping tests.`);
120+
runSharedArrayBufferTests = false;
121+
return;
122+
}
123+
124+
throw ex;
125+
}
126+
127+
assert.ok(t instanceof Int8Array);
128+
assert.strictEqual(binding.typedarray.getTypedArrayType(t), 'int8');
129+
assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length);
130+
for (let i = 0; i < length; i++) {
131+
const value = 2 ** (i + 1);
132+
t[i] = value;
133+
assert.strictEqual(binding.typedarray.getTypedArrayElement(t, i), value);
134+
}
135+
const bAsValue = binding.typedarray.getTypedArrayBufferValue(t);
136+
assert.ok(bAsValue instanceof SharedArrayBuffer);
137+
assert.strictEqual(bAsValue, sab);
138+
}
103139
}

0 commit comments

Comments
 (0)